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 支持的核心字段包括:
typeurlcommandargsenvcwdheaders
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 的解析结果”。
这就是 CliResolvedMcpConfig 和 CliResolvedMcpServer 存在的原因。
4. runtime 启动时到底会发生什么
CliMcpRuntimeManager.start() 是整条链真正进入运行态的地方。
它会对每个 resolved server 按顺序做判断:
- workspace 没启用,标成
disabled - 当前 session 被暂停,标成
paused - 配置不合法,标成
error - 否则尝试创建 client session 并连接
- 拉取
listTools() - 校验工具名
- 转换成 OpenAI function tool 形状
- 把 server 状态设成
connected
如果 workspace 引用了一个根本不存在于全局定义里的名字,还会额外生成:
missing
所以当前可见状态至少有五种:
connecteddisabledpausederrormissing
这也是为什么 /mcp 输出不是单纯列配置,而是列“当前运行视图”。
5. MCP tool 为什么比 skill 严格得多
CliMcpRuntimeManager.validateToolNames(...) 会做三层冲突校验:
5.1 不能撞 built-in tools
这些名字是保留的:
bashread_filewrite_fileapply_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 仍然是三类:
stdiossestreamable_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不会改全局 storepause也不会改 workspace.json- 它只是改当前会话内存里的
pausedMcpServers
然后通过 switchSessionRuntime(...) 重建当前 session 所用的 MCP runtime。
retry 也是同理,本质不是“对单连接做热修复”,而是让当前 session 重新走一遍 runtime 装配。
8. MCP tools 是怎样接进 Coding Agent 的
DefaultCodingCliAgentFactory.prepareMcpRuntime(...) 会先创建 CliMcpRuntimeManager。
如果 runtime 里真的产出了:
toolRegistrytoolExecutor
那么 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.json的enabledMcpServers里有名字- 但
~/.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 打印成:
nametypestateworkspace=enabled|disabledpaused=yes|notools=<count>error=<summary>(有错时)
末尾还会附上:
store=<globalMcpPath>workspaceConfig=<workspaceConfigPath>
这两个路径很重要,因为它们能直接告诉你问题到底落在:
- 全局定义层
- workspace 启用层
- 还是 runtime 连接层
12. 推荐的组织方式
如果你希望长期稳定使用 MCP,这样组织最稳:
- 把稳定 server definition 放进
~/.ai4j/mcp.json - 只在真正需要的 repo 里启用对应名字
- 会话级临时停用,用
/mcp pause - 遇到配置或连接修改,用
retry或重新切换 session runtime - 宿主侧临时注入则走 ACP
mcpServers
不太建议的做法是:
- 让所有 server 永久都在 workspace 启用
- 用同名 tool 在多个 server 间“碰运气”
- 把
pause当持久化配置开关
13. 扩展和排障时优先看哪里
最值得直接读的入口类:
ai4j-cli/.../mcp/CliMcpConfigManagerai4j-cli/.../mcp/CliMcpRuntimeManagerai4j-cli/.../factory/DefaultCodingCliAgentFactoryai4j-cli/.../runtime/CodingCliSessionRunnerai4j-cli/.../acp/AcpJsonRpcServerai4j/.../mcp/transport/McpTransportFactory
推荐排障顺序:
/mcp看状态是不是missing / error / paused- 看
store和workspaceConfig具体是哪个文件 - 检查 transport 是否被归一化成预期类型
- 检查 tool name 是否与 built-ins 或其他 server 冲突
- 如果是 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