跳转到内容

MCP 服务器与工具编写

本文档介绍 MCP 服务器定义如何在 coding-agent 中转化为可调用的 mcp_* 工具,以及当配置无效、重复、禁用或受认证限制时,运维人员应有何预期。

Config sources (.xcsh/.claude/.cursor/.vscode/mcp.json, mcp.json, etc.)
-> discovery providers normalize to canonical MCPServer
-> capability loader dedupes by server name (higher provider priority wins)
-> loadAllMCPConfigs converts to MCPServerConfig + skips enabled:false
-> MCPManager connects/listTools (with auth/header/env resolution)
-> MCPTool/DeferredMCPTool bridge exposes tools as mcp_<server>_<tool>
-> AgentSession.refreshMCPTools replaces live MCP tools immediately

src/mcp/types.ts 定义了 MCP 配置编写者和运行时使用的编写模型:

  • stdiotype 缺省时的默认值):需要 command,可选 argsenvcwd
  • http:需要 url,可选 headers
  • sse:需要 url,可选 headers(为兼容性保留)
  • 共享字段:enabledtimeoutauth

validateServerConfig()src/mcp/config.ts)执行传输层基本验证:

  • 拒绝同时设置 commandurl 的配置
  • stdio 要求提供 command
  • http/sse 要求提供 url
  • 拒绝未知的 type

config-writer.ts 在添加/更新操作时应用此验证,同时验证服务器名称:

  • 非空
  • 最多 100 个字符
  • 仅允许 [a-zA-Z0-9_.-]
  • 省略 type 意味着 stdio。如果你原本打算使用 HTTP/SSE 但省略了 type,则 command 变为必填项。
  • sse 仍然被接受,但在内部作为 HTTP 传输处理(createHttpTransport)。
  • 验证是结构性的,而非可达性验证:语法上合法的 URL 在连接时仍可能失败。

loadAllMCPConfigs()src/mcp/config.ts)通过 loadCapability(mcpCapability.id) 加载规范的 MCPServer 项。

能力层(src/capability/index.ts)随后:

  1. 按优先级顺序加载提供者
  2. server.name 去重(先到先得 = 最高优先级)
  3. 验证去重后的项

结果:跨来源的重复服务器名称不会合并。只有一个定义生效;较低优先级的重复项将被遮蔽。

src/discovery/mcp-json.ts 中的专用回退提供者读取项目根目录的 mcp.json.mcp.json(低优先级)。

实际上 MCP 服务器也来自更高优先级的提供者(例如原生 .xcsh/... 和工具特定的配置目录)。编写建议:

  • 优先使用 .xcsh/mcp.json(项目级)或 ~/.xcsh/mcp.json(用户级)以获得明确控制。
  • 当需要回退兼容性时使用根目录 mcp.json / .mcp.json
  • 在多个来源中使用相同的服务器名称会导致优先级遮蔽,而非合并。

convertToLegacyConfig()src/mcp/config.ts)将规范的 MCPServer 映射为运行时 MCPServerConfig

关键行为:

  • 传输类型推断为 server.transport ?? (command ? "stdio" : url ? "http" : "stdio")
  • 已禁用的服务器(enabled === false)在连接前被丢弃
  • 可选字段存在时予以保留

mcp-json.ts 使用 expandEnvVarsDeep() 展开字符串字段中的环境变量占位符:

  • 支持 ${VAR}${VAR:-default}
  • 未解析的值保持为字面量 ${VAR} 字符串

mcp-json.ts 还对用户 JSON 执行运行时类型检查,并对无效的 enabled/timeout 值记录警告,而非导致整个文件加载失败。

MCPManager.prepareConfig()/#resolveAuthConfig()src/mcp/manager.ts)是连接前的最终处理环节。

如果配置包含:

auth: { type: "oauth", credentialId: "..." }

且凭据存在于认证存储中:

  • http/sse:注入 Authorization: Bearer <access_token> 请求头
  • stdio:注入 OAUTH_ACCESS_TOKEN 环境变量

如果凭据查找失败,管理器会记录警告并在未解析认证的情况下继续运行。

连接前,管理器通过 resolveConfigValue()src/config/resolve-config-value.ts)解析每个请求头/环境变量值:

  • ! 开头的值 => 执行 shell 命令,使用修剪后的 stdout(有缓存)
  • 否则,首先将值视为环境变量名(process.env[name]),回退为字面值
  • 未解析的命令/环境变量值将从最终的 headers/env 映射中省略

运维注意事项:这意味着拼写错误的密钥命令/环境变量名可能会静默移除该请求头/环境变量条目,导致下游 401/403 或服务器启动失败。

4) 工具桥接:MCP -> 代理可调用工具

Section titled “4) 工具桥接:MCP -> 代理可调用工具”

src/mcp/tool-bridge.ts 将 MCP 工具定义转换为 CustomTool

工具名称生成规则为:

mcp_<sanitized_server_name>_<sanitized_tool_name>

规则:

  • 转为小写
  • [a-z_] 字符变为 _
  • 重复的下划线合并
  • 工具名称中冗余的 <server>_ 前缀被剥离一次

这避免了许多冲突,但并非全部。不同的原始名称仍可能清理为相同的标识符(例如 my-servermy.server 清理后相似),且注册表插入采用后写覆盖策略。

convertSchema() 基本保持 MCP JSON Schema 不变,但为缺少 properties 的对象 schema 补充 {} 以确保提供者兼容性。

MCPTool.execute() / DeferredMCPTool.execute()

  • 调用 MCP tools/call
  • 将 MCP 内容扁平化为可展示的文本
  • 返回结构化详情(serverNamemcpToolName、提供者元数据)
  • 将服务器报告的 isError 映射为 Error: ... 文本结果
  • 将抛出的传输/运行时故障映射为 MCP error: ...
  • 通过将 AbortError 转换为 ToolAbortError 保留中止语义

5) 运维生命周期:添加/编辑/删除与实时更新

Section titled “5) 运维生命周期:添加/编辑/删除与实时更新”

交互模式在 src/modes/controllers/mcp-command-controller.ts 中暴露 /mcp 命令。

支持的操作:

  • add(向导或快速添加)
  • remove / rm
  • enable / disable
  • test
  • reauth / unauth
  • reload

配置写入是原子性的(writeMCPConfigFile:临时文件 + 重命名)。

更改后,控制器调用 #reloadMCP()

  1. mcpManager.disconnectAll()
  2. mcpManager.discoverAndConnect()
  3. session.refreshMCPTools(mcpManager.getTools())

refreshMCPTools() 替换所有 mcp_ 注册表条目并立即重新激活最新的 MCP 工具集,因此更改无需重启会话即可生效。

  • 交互/TUI 模式/mcp 提供应用内用户体验(向导、OAuth 流程、连接状态文本、即时运行时重绑定)。
  • SDK/无头集成discoverAndLoadMCPTools()src/mcp/loader.ts)返回已加载的工具 + 按服务器的错误信息;无 /mcp 命令用户体验。

用户/运维人员常见的错误字符串:

  • 添加/更新验证失败:
    • Invalid server config: ...
    • Server "<name>" already exists in <path>
  • 快速添加参数问题:
    • Use either --url or -- <command...>, not both.
    • --token requires --url (HTTP/SSE transport).
  • 连接/测试失败:
    • Failed to connect to "<name>": <message>
    • 超时帮助文本建议增加超时时间
    • 401/403 的认证帮助文本
  • 认证/OAuth 流程:
    • Authentication required ... OAuth endpoints could not be discovered
    • OAuth flow timed out. Please try again.
    • OAuth authentication failed: ...
  • 使用已禁用的服务器:
    • Server "<name>" is disabled. Run /mcp enable <name> first.

发现过程中格式错误的源 JSON 通常以警告/日志形式处理;config-writer 路径会抛出明确的错误。

在此代码库中进行可靠的 MCP 编写:

  1. 在所有支持 MCP 的配置来源中保持服务器名称全局唯一。
  2. 优先使用字母数字/下划线名称,以避免生成的 mcp_* 工具名称中出现清理后的命名冲突。
  3. 使用显式 type 以避免意外的 stdio 默认行为。
  4. enabled: false 视为硬关闭:服务器将从运行时连接集合中省略。
  5. 对于 OAuth 配置,存储有效的 credentialId;否则认证注入将被跳过。
  6. 如果使用基于命令的密钥解析(!cmd),请验证命令输出是稳定且非空的。