Skip to content

Tool Development Overview MCP

ChatAI Plugin implements its tool system based on MCP (Model Context Protocol) standard, supporting three tool sources.

Tool Sources

Three Tool Sources

Choose the appropriate development method based on your needs, from simple to complex: Custom JS → Built-in Tools → External MCP

SourceLocationDescriptionHot Reload
Built-in Toolssrc/mcp/tools/Core functionality, 25 categorized modules
Custom JSdata/tools/User scripts, no source code modification needed
External MCPdata/mcp-servers.jsonnpm packages or remote servers

Skills File System

In addition to the three tool sources above, the system also supports loading skill documents from the data/skills directory:

File FormatDescriptionAuto Activation
SKILL.mdMarkdown frontmatter + instruction bodyPer autoActivate config
*.skill.yamlYAML skill definitionPer autoActivate config
*.skill.jsonJSON skill definitionPer autoActivate config

Skill Loading Mechanism

  1. Default exposure: all skill files are scanned at startup, exposing only the skill list (name + description)
  2. Explicit loading: the model requests the full skill content through the load_skill tool
  3. Auto injection: skills with autoActivate: true are injected automatically at conversation start
  4. Context reload: already-loaded skills are re-injected after context compression

Scan Paths

The following directories are scanned by default:

  • data/skills/ - primary skill directory
  • .cursor/skills/ - IDE skill directory
  • .claude/skills/ - Claude Code skill directory
  • .codex/skills/ - Codex skill directory

Skills Config File

The full Skills module configuration lives in data/skills.yaml (loaded and validated by src/services/skills/SkillsConfig.js), decoupled from the MCP server config. The file is auto-created with defaults when missing and supports hot reload (hasChanged() detects mtime changes, then reload()). All configuration sits under the top-level skills key.

Top-level Structure

yaml
skills:
  enabled: true          # Whether the Skills module is enabled
  mode: 'hybrid'         # Working mode: hybrid / skills-only / mcp-only
  sources: {...}         # Tool source config
  groups: [...]          # Tool group config
  execution: {...}       # Tool execution config
  dispatch: {...}        # Dispatch config (lightweight model pre-selects tool groups)
  documents: {...}       # SKILL.md document skill config
  security: {...}        # Dangerous tool security config

Working Mode mode

ValueDescription
hybridDefault. Loads both Skills-configured tools (built-in + custom JS) and MCP server tools
skills-onlyLoads only Skills-configured tools and ignores MCP servers (isMcpEnabled() returns false)
mcp-onlyLoads only MCP server tools and ignores built-in and custom JS tools (legacy behavior)

Invalid value fallback

mode accepts only the three values above; any other value triggers a validation-time warning and falls back to hybrid.

Tool Sources sources

The three tool sources can be enabled/disabled independently, each keeping its own disabled-tool list:

yaml
sources:
  builtin:                 # Built-in tools
    enabled: true
    categories: []         # Enabled category list (empty array = enable all)
    disabledTools: []      # Disabled tools (takes precedence over categories)
  custom:                  # Custom JS tools
    enabled: true
    path: 'data/tools'     # JS tool directory (relative to the plugin root)
    autoReload: true       # Auto reload on file changes
  mcp:                     # External MCP server tools
    enabled: true
    servers: []            # Enabled server list
    disabledServers: []    # Disabled server list

disabledTools merge

getDisabledTools() deduplicates and merges the disabledTools of the builtin, custom, and mcp sources; a tool disabled by any source is disabled globally.

Tool Groups groups

groups divides tools into logical groups for dispatch and permission management. User-configured groups completely replace the defaults (no merging).

yaml
groups:
  - index: 0                    # Group index (unique)
    name: 'basic'               # Group name (unique)
    description: 'Basic tools: time, date, lunar date, festivals, system environment info, etc.'
    tools: ['get_current_time', 'get_lunar_date', ...]  # Tool name list
    enabled: true               # Whether this group is enabled
  - index: 6
    name: 'admin'
    description: 'Group admin: mute, kick, set group card/title, send announcements, etc.'
    tools: ['mute_member', 'kick_member', ...]
    enabled: true
    requiredPermission: 'admin' # Permission required for this group (optional)
  - index: 17
    name: 'shell'
    description: 'System commands (dangerous tools)'
    tools: ['execute_command', 'get_system_info', ...]
    enabled: false              # Dangerous group disabled by default
    requiredPermission: 'master'
FieldTypeDescription
indexnumberGroup index, retrievable via getGroupByIndex()
namestringGroup name, retrievable via getGroupByName(); warns when missing
descriptionstringGroup description
toolsstring[]Tool name list of the group; reset to [] when not an array
enabledbooleanWhether enabled; getEnabledGroups() filters enabled !== false
requiredPermissionstringPermission required for this group (e.g. admin, master), optional

Document Skills documents

Controls scanning and injection behavior of document skills like SKILL.md (see Skills File System):

yaml
documents:
  enabled: true
  mode: 'auto'                  # auto / all / explicit
  paths:                        # Directory scan list
    - 'data/skills'
    - '.cursor/skills'
    - '.claude/skills'
    - '.codex/skills'
  maxDepth: 6                   # Max directory recursion depth
  maxFileBytes: 65536           # Max bytes per skill file (reset to 1024 when < 1024)
  maxPromptChars: 20000         # Max chars injected into the system prompt (reset to 1000 when < 1000)
modeMatching Behavior
autoDefault. Matches skills by name/description/relativePath/triggers against the context text
allInjects all skills when none are explicitly specified
explicitInjects only the skills explicitly selected in selectedNames

Invalid value fallback

When mode is not auto/all/explicit, the default is restored; non-array paths and maxDepth < 0 also restore defaults.

Execution Config execution

yaml
execution:
  timeout: 30000        # Per-tool timeout (milliseconds); reset to 1000 when < 1000
  maxParallel: 5        # Max parallelism; reset to 1 when < 1, to 20 when > 20
  retryOnError: false   # Whether to retry on error
  maxRetries: 2         # Retry count
  cacheResults: true    # Whether to cache results
  cacheTTL: 60000       # Cache TTL (milliseconds)

Dispatch Config dispatch

When enabled, a lightweight model first decides which tool groups are needed, reducing token cost:

yaml
dispatch:
  enabled: false        # Disabled by default; can be enabled in the admin panel
  useSummary: true      # Use tool group summaries in the decision
  maxGroups: 3          # Max tool groups to select

Security Config security

yaml
security:
  dangerousTools:               # Dangerous tool list
    - kick_member
    - mute_member
    - recall_message
    - write_file
    - delete_file
    - execute_command
  allowDangerous: false         # Whether dangerous tools may execute
  dangerousRequiredPermission: 'master'  # Permission required for dangerous tools

Relationship to tool approval

security.dangerousTools affects the risk classification of tool approval: tools matching the list are rated high risk. Executing dangerous tools must additionally satisfy the allowDangerous and dangerousRequiredPermission constraints.

Tool Definition Format

All tools follow the MCP standard unified definition format:

javascript
{
  // Tool name (unique identifier, snake_case format)
  name: 'my_tool',
  
  // Tool description (visible to AI, clear description helps AI call correctly)
  description: 'Tool function description, explain when to use and parameter meanings',
  
  // Parameter definition (JSON Schema format)
  inputSchema: {
    type: 'object',
    properties: {
      param1: {
        type: 'string',
        description: 'Parameter description'
      }
    },
    required: ['param1']
  },
  
  // Handler function (async)
  handler: async (args) => {
    // Implementation logic
    return { result: '...' }
  }
}

Context Access

Tools access runtime context through the second argument context of the handler function. Platform differences, target ID types, send-result validation, and fallbacks for unsupported capabilities are all handled by the standard interface.

ToolContext API

context is a request-scoped object; tools created by the model must not import runtime singletons or read protocol-side objects directly.

javascript
handler: async (args, context) => {
  const api = context.getApi()
  const message = context.message
  const event = context.getEvent()
  const userId = event?.user_id
  const groupId = event?.group_id

  // Check whether the sender is the master
  const isMaster = context.isMaster()

  // Send/query through the standard interface; do not branch on QQBot, ICQQ, or OneBot inside tools
  const permission = await api.getBotPermission(groupId)
  // Returns: { role: 'owner'|'admin'|'member', isAdmin: boolean, isOwner: boolean, inGroup: boolean }
}

Return Format

Tool return values are automatically serialized and returned to AI:

javascript
return { text: 'Result text' }
javascript
return { 
  success: true,
  data: { key: 'value' }
}
javascript
return {
  text: 'Current time: 14:30',
  datetime: '2024-12-15T06:30:00.000Z',
  timestamp: 1702622400000
}
javascript
// Method 1: Throw error
throw new Error('Operation failed: Permission denied')

// Method 2: Return error object
return { error: true, message: 'Operation failed' }

Return Value Notes

  • Return values should be concise, avoid returning large amounts of irrelevant data
  • text field will be shown directly to AI, should be human-readable format
  • Structured data is suitable for scenarios requiring further processing

Knowledge Graph Tools

src/mcp/tools/knowledgeGraph.js provides 12 kg_* tools (category knowledgeGraph) backed by the kg_entities / kg_relationships tables, covering entity CRUD, version history, relationships, subgraph exploration and scope stats. When scope_id is omitted it is derived from the event context (group:<gid>:user:<uid> / group:<gid> / user:<uid> / global). See Built-in Tools for the full list.

Development Workflow

StepDescriptionKey Points
1. Determine TypeChoose built-in/custom/MCPUse custom JS for simple features, built-in for complex
2. Define InterfaceName, description, parametersClear description, use JSON Schema for params
3. Implement LogicWrite handler functionHandle exceptions and check permissions
4. Test & VerifyAPI test or conversation testUse #工具日志 to view call details
5. Deploy & EnableConfigure permissions and enableManage tool enable status via Web panel

Detailed Documentation

📚 Choose the Right Development Method

DocumentUse CaseDifficulty
Built-in ToolsDeep integration, access internal APIs⭐⭐⭐
Custom JS ToolsQuick development, no source modification⭐⭐
Advanced DevelopmentAdvanced techniques and best practices⭐⭐⭐
MCP ServerExternal services, reuse existing MCP⭐⭐
SecurityUnderstand tool security mechanisms⭐⭐

Released under the MIT License