跳到主要内容

MCP 对接

这页不讲 MCP 协议本身,而是讲它在 Coding Agent 里怎样变成“活的工具面”。

因为在当前实现里,MCP 不是一份静态配置,也不是简单的 JSON 转发。

它要经过:

  • 全局定义
  • workspace 启用选择
  • session 级暂停状态
  • 连接建立
  • tool 列表拉取
  • 命名冲突校验
  • registry / executor 重建

最后才会真的进入 agent 可用工具集。


1. 先看真实装配链

把 CLI 路径压成一条执行链:

/mcp add|enable|pause...
-> CliMcpConfigManager
-> CliResolvedMcpConfig
-> CliMcpRuntimeManager.initialize(...).start()
-> 连接各个 MCP server
-> listTools()
-> validateToolNames(...)
-> convertTools(...)
-> StaticToolRegistry + ToolExecutor
-> DefaultCodingCliAgentFactory.attachMcpRuntime(...)
-> CodingAgentBuilder.toolRegistry/toolExecutor(...)

这里最关键的判断是:

MCP 在 Coding Agent 里并不是“协议名词”,而是一个真实的外部工具 runtime。

它有连接态、有错误态、有暂停态,也会因为命名冲突而整体失效。


2. 配置为什么分成两层

当前 CLI 路径把 MCP 配置拆成两部分:

2.1 全局定义

路径:

~/.ai4j/mcp.json

它存的是 server definition,也就是“这个 MCP server 是谁、怎么连”。

当前 CliMcpServerDefinition 支持的核心字段包括:

  • type
  • url
  • command
  • args
  • env
  • cwd
  • headers

2.2 工作区启用状态

路径:

<workspace>/.ai4j/workspace.json

这里真正关心的是:

  • enabledMcpServers

它回答的是另一个问题:

  • 这个仓库当前要启用哪些全局已定义的 MCP server

这两层拆分的价值很大:

  • 你可以在机器级维护一套稳定 server 定义
  • 但每个 repo 只开启自己真正需要的那几项
  • 不会把“存在于机器上”和“本仓真的要启用”混成一个概念

3. CliMcpConfigManager 真正负责什么

CliMcpConfigManager 不只是读写 JSON。

它还负责:

  • 归一化 server 名称和字段
  • http 统一映射成 streamable_http
  • 当缺省 type 且有 command 时,自动补成 stdio
  • 校验 stdio 是否有 command
  • 校验 sse / streamable_http 是否有 url
  • 把全局定义和 workspace 启用状态解析成 CliResolvedMcpConfig

所以它输出的不是“原始配置”,而是“当前 session 可据此启动 runtime 的解析结果”。

这就是 CliResolvedMcpConfigCliResolvedMcpServer 存在的原因。


4. runtime 启动时到底会发生什么

CliMcpRuntimeManager.start() 是整条链真正进入运行态的地方。

它会对每个 resolved server 按顺序做判断:

  1. workspace 没启用,标成 disabled
  2. 当前 session 被暂停,标成 paused
  3. 配置不合法,标成 error
  4. 否则尝试创建 client session 并连接
  5. 拉取 listTools()
  6. 校验工具名
  7. 转换成 OpenAI function tool 形状
  8. 把 server 状态设成 connected

如果 workspace 引用了一个根本不存在于全局定义里的名字,还会额外生成:

  • missing

所以当前可见状态至少有五种:

  • connected
  • disabled
  • paused
  • error
  • missing

这也是为什么 /mcp 输出不是单纯列配置,而是列“当前运行视图”。


5. MCP tool 为什么比 skill 严格得多

CliMcpRuntimeManager.validateToolNames(...) 会做三层冲突校验:

5.1 不能撞 built-in tools

这些名字是保留的:

  • bash
  • read_file
  • write_file
  • apply_patch

如果某个 MCP server 返回了同名 tool,当前 server 会直接进入错误态。

5.2 同一个 server 内不能重复

一个 server 如果返回了重复 tool name,也会直接报错。

5.3 不同 server 之间不能重名

第一个 server claim 了某个 tool name 后,后面的 server 再返回同名 tool,也会被视为冲突。

这里和 skill 的处理方式完全不同。

  • skill 冲突是提示层遮蔽
  • MCP 冲突是执行层失败

因为一旦执行层名字不唯一,tool routing 会直接失去确定性。


6. 三种 transport 在当前实现里的真实语义

核心 transport 仍然是三类:

  • stdio
  • sse
  • streamable_http

适用面大致可以这样理解:

stdio

本地进程式 MCP server。

最适合:

  • 本地二进制
  • Node/Python 启动脚本
  • 需要直接由 CLI 拉起的 server

sse

远端事件流式 MCP endpoint。

最适合:

  • 已经在线上的服务
  • 需要通过 SSE 保持消息流的对接方式

streamable_http

标准 HTTP 形式的 MCP endpoint。

最适合:

  • 已有 HTTP 化 MCP 服务
  • 希望走更标准、更容易穿透代理的部署方式

一个容易混淆的点是:

  • CLI 命令层仍然让你写 --transport http
  • CliMcpConfigManager.normalizeTransportType(...) 会把它归一化成 streamable_http
  • McpTransportFactory 也把历史 http 作为兼容别名处理

所以:

  • CLI 输入里可以继续写 http
  • 配置文件和 ACP 注入里更推荐直接写 streamable_http

7. /mcp 系列命令各自改的是哪一层

CLI 里的 /mcp 命令本质上操作的是三层不同状态。

7.1 add / remove

操作全局定义层。

影响文件:

  • ~/.ai4j/mcp.json

/mcp add --transport <...> <name> <target> 只是在全局 store 里创建一个 server definition。

它不会自动代表当前 workspace 已启用。

7.2 enable / disable

操作 workspace 启用层。

影响文件:

  • <workspace>/.ai4j/workspace.json

也就是改 enabledMcpServers

一旦启用或禁用,当前 session runtime 会重建。

7.3 pause / resume / retry

操作 session 运行层。

这里最容易误解:

  • pause 不会改全局 store
  • pause 也不会改 workspace.json
  • 它只是改当前会话内存里的 pausedMcpServers

然后通过 switchSessionRuntime(...) 重建当前 session 所用的 MCP runtime。

retry 也是同理,本质不是“对单连接做热修复”,而是让当前 session 重新走一遍 runtime 装配。


8. MCP tools 是怎样接进 Coding Agent 的

DefaultCodingCliAgentFactory.prepareMcpRuntime(...) 会先创建 CliMcpRuntimeManager

如果 runtime 里真的产出了:

  • toolRegistry
  • toolExecutor

那么 attachMcpRuntime(...) 就会把它们塞给 CodingAgentBuilder

也就是说当前 MCP 接入方式不是“built-in 内部隐藏分支”,而是:

  • 先在 CLI 宿主层准备一个独立 runtime
  • 再把它作为外部 tool surface 注入 CodingAgentBuilder

然后由 CodingAgentBuilder.mergeToolRegistry(...) / mergeToolExecutor(...) 与 built-ins、subagent tools 一起合并。

这也是为什么:

  • MCP 本质上属于宿主装配层
  • 不是 ai4j-agent 内核里写死的一部分

9. 错误和失败路径怎么判断

当前最常见的失败路径有五类。

9.1 workspace 引用了未定义 server

现象:

  • /mcp 里看到 state=missing

含义:

  • workspace.jsonenabledMcpServers 里有名字
  • ~/.ai4j/mcp.json 里找不到对应 definition

9.2 配置字段不完整

现象:

  • state=error
  • error 里通常会看到 stdio transport requires command<type> transport requires url

9.3 连接或 listTools() 失败

现象:

  • state=error
  • 启动时还可能收到 Warning: MCP unavailable: ...

9.4 tool 名冲突

现象:

  • 某个 server 直接进入 error
  • 常见报错包括 built-in 冲突、同 server 重复、跨 server 重复

9.5 当前 session 暂停了 server

现象:

  • state=paused
  • workspace 仍然是 enabled
  • 只是这一轮 session runtime 没把它接进可用工具集

10. ACP 下的 MCP 为什么又是另一条链

ACP 不一定依赖本机的 ~/.ai4j/mcp.json

AcpJsonRpcServer.createSession(...) 在处理 session/new / session/load 时,可以直接从请求参数里接收 mcpServers

随后它会调用自己的 resolveMcpConfig(...)

  • 把每个传入 server 直接转成 CliResolvedMcpServer
  • 默认都视为 workspace enabled
  • 不走本地全局 store
  • 不走 workspace.json 的 enabledMcpServers

这条链适合:

  • IDE 插件动态注入 MCP
  • 桌面宿主按项目临时分配 MCP
  • 多租户宿主不希望依赖用户本地全局配置

所以 ACP 下的 MCP 更像:

  • host-managed session-scoped MCP config

而 CLI 下的 MCP 更像:

  • machine-scoped definition + workspace-scoped enablement

11. /mcp 输出应该怎么读

CodingCliSessionRunner.renderMcpOutput() 当前会把每个 server 打印成:

  • name
  • type
  • state
  • workspace=enabled|disabled
  • paused=yes|no
  • tools=<count>
  • error=<summary>(有错时)

末尾还会附上:

  • store=<globalMcpPath>
  • workspaceConfig=<workspaceConfigPath>

这两个路径很重要,因为它们能直接告诉你问题到底落在:

  • 全局定义层
  • workspace 启用层
  • 还是 runtime 连接层

12. 推荐的组织方式

如果你希望长期稳定使用 MCP,这样组织最稳:

  1. 把稳定 server definition 放进 ~/.ai4j/mcp.json
  2. 只在真正需要的 repo 里启用对应名字
  3. 会话级临时停用,用 /mcp pause
  4. 遇到配置或连接修改,用 retry 或重新切换 session runtime
  5. 宿主侧临时注入则走 ACP mcpServers

不太建议的做法是:

  • 让所有 server 永久都在 workspace 启用
  • 用同名 tool 在多个 server 间“碰运气”
  • pause 当持久化配置开关

13. 扩展和排障时优先看哪里

最值得直接读的入口类:

  • ai4j-cli/.../mcp/CliMcpConfigManager
  • ai4j-cli/.../mcp/CliMcpRuntimeManager
  • ai4j-cli/.../factory/DefaultCodingCliAgentFactory
  • ai4j-cli/.../runtime/CodingCliSessionRunner
  • ai4j-cli/.../acp/AcpJsonRpcServer
  • ai4j/.../mcp/transport/McpTransportFactory

推荐排障顺序:

  1. /mcp 看状态是不是 missing / error / paused
  2. storeworkspaceConfig 具体是哪个文件
  3. 检查 transport 是否被归一化成预期类型
  4. 检查 tool name 是否与 built-ins 或其他 server 冲突
  5. 如果是 ACP,确认 mcpServers 是不是宿主按会话正确传入

14. 这页最该记住的结论

  • Coding Agent 里的 MCP 是活的 runtime,不是静态配置片段
  • 全局 mcp.json 负责“定义 server”,workspace.json 负责“这个仓库启用谁”
  • pause/resume 是 session 级状态,不会持久化到配置文件
  • tool 名冲突会让 MCP server 直接进错误态,而不是像 skill 那样静默遮蔽
  • CLI 的 http 只是兼容输入,最终会归一化成 streamable_http
  • ACP 可以绕过本地全局 store,按会话直接注入 MCP server

15. 继续阅读

  1. MCP 与 ACP
  2. Tools 与审批机制
  3. ACP 集成
  4. MCP 总览