MCP and ACP
MCP 和 ACP 在 Coding Agent 里经常一起出现,所以很容易被误写成“同一套接入机制的两个名字”。
从源码看,它们其实分属两侧完全不同的边界:
MCP:把外部能力接进模型可调用工具面ACP:把 Coding Session 暴露给 IDE / 桌面端 / 其它宿主
最稳的记法是:
- MCP 面向“模型还能调什么”
- ACP 面向“宿主怎么看、怎么控这次会话”
1. 先把两条链拆开
如果用当前实现画一条最短链路,可以写成:
MCP server
-> CliMcpRuntimeManager
-> toolRegistry / toolExecutor
-> CodingAgentBuilder
-> CodingSession
-> HeadlessCodingSessionRuntime
-> AcpJsonRpcServer
-> ACP host
这里每一段都不是同一层语义:
- 前半段在扩展 agent 的工具面
- 后半段在协议化暴露 session
这也是为什么把 MCP 和 ACP 混写会让文档失真。
2. MCP 在 Coding Agent 里的真实位置
当前 CLI / Coding Agent 路径里,MCP 运行时的主入口是:
CliMcpConfigManagerCliMcpRuntimeManagerCliResolvedMcpConfigCliResolvedMcpServer
最关键的类是:
ai4j-cli/.../mcp/CliMcpRuntimeManager.java
它不是静态配置对象,而是一个活的连接层。启动时会:
- 读取 resolved MCP config
- 为每个 server 建立
CliMcpConnectionHandle - connect
listTools()- 校验工具名
- 把
McpToolDefinition转成 OpenAI 风格Tool.Function - 生成
toolRegistry和toolExecutor
所以 MCP 在这条链里的角色不是“附加一段 prompt”,而是:
把远端 server 的工具正式投影成当前 agent 的结构化工具面。
3. MCP 配置为什么要分全局和工作区两层
当前 MCP 配置解析有明显的两层:
- 全局
- 工作区
设计动机很直接:
- 全局层解决“机器上有哪些 MCP server 定义”
- 工作区层解决“这个仓库当前启用哪些 server”
这也是为什么 Coding Agent 里的 MCP 不应该被理解成“启动参数再多几个 URL”。
它本质上是一套:
- 服务器定义
- 工作区启用状态
- session 可见状态
联动的运行时配置。
4. CliMcpRuntimeManager 为什么要维护状态机
当前实现里,MCP server 并不是只有“能用/不能用”两态,而是显式维护:
connecteddisabledpausederrormissing
这非常重要,因为在实际使用里,MCP 的问题来源可能完全不同:
- 工作区显式禁用了
- 当前 session 暂停了
- 配置存在但 server 不存在
- server 存在但连接失败
如果文档只说“MCP 不可用”,宿主和用户都很难定位到底是哪一类问题。
5. MCP 工具为什么要严格做名字冲突校验
CliMcpRuntimeManager 当前会显式保留这些内置工具名:
bashread_filewrite_fileapply_patch
如果某个 MCP server 返回了同名工具,运行时会直接视为冲突。
此外它还会检查:
- 同一 server 内部重复工具名
- 不同 server 之间重复工具名
这说明 AI4J 当前的态度很明确:
MCP 工具不是随便“挂进来就行”,而是必须先保证当前工具空间可判定。
否则宿主根本无法知道:
- 这个
read_file到底是本地工具还是远端 MCP 工具
6. MCP 工具进入模型前经历了什么转换
MCP server 返回的是:
McpToolDefinition
但模型侧最终看到的是:
Tool.Function
CliMcpRuntimeManager.convertTools(...) 会把:
- 名称
- 描述
- input schema
转换成当前 agent/runtime 可消费的 tool schema。
所以 MCP 接入不是“把 JSON 原样透传给模型”,而是先做了一层 tool surface 适配。
这层适配的价值在于:
- 模型侧看到的仍是统一 tool 形态
- 本地 tools 和 MCP tools 可以共存于同一个 registry
7. ACP 在 Coding Agent 里的真实位置
如果说 MCP 解决的是“能力从哪来”,那 ACP 解决的就是“会话如何出得去”。
当前 ACP 的主入口是:
AcpJsonRpcServerAcpCodingCliAgentFactoryAcpToolApprovalDecoratorHeadlessCodingSessionRuntime
其中最核心的是:
ai4j-cli/.../acp/AcpJsonRpcServer.java
它不是工具运行时,而是 session 协议网关。
8. ACP 当前实际暴露了哪些方法
从 AcpJsonRpcServer 里的常量可以直接看到,当前 ACP 至少显式处理:
initializesession/newsession/loadsession/listsession/promptsession/set_modesession/set_config_optionsession/cancelsession/request_permission
这组方法本身已经说明 ACP 的定位:
- 它不是单次“发 prompt 拿 answer”的薄 RPC
- 它是在协议层暴露一个可持续的 coding session
9. session/prompt 为什么要走 HeadlessCodingSessionRuntime
ACP 下,一次 prompt 并不是直接塞给 CodingSession.run() 就返回字符串。
AcpJsonRpcServer 实际会把它交给:
HeadlessCodingSessionRuntime
后者会把一次 prompt 运行拆成结构化事件:
USER_MESSAGEASSISTANT_MESSAGETOOL_CALLTOOL_RESULTAUTO_CONTINUEAUTO_STOPBLOCKEDCOMPACTERROR
这说明 ACP 的重点不是“把终端内容远程打印出来”,而是把 session 内部过程变成宿主能理解的事件流。
10. ACP 审批为什么是协议往返,不是本地终端交互
CLI/TUI 下,审批由:
CliToolApprovalDecorator
通过终端完成。
ACP 下则不同。
AcpToolApprovalDecorator 会通过:
PermissionGateway
把审批请求交回 AcpJsonRpcServer,再由它发出:
session/request_permission
因此 ACP 的审批本质上是:
- runtime 发权限请求
- 宿主返回 allow / reject 决策
- 再决定工具是否继续执行
这说明 ACP 不是“远程 CLI 镜像”,而是带权限往返的宿主协议。
11. ACP 和 MCP 为什么会在同一会话里同时生效
这是 Coding Agent 非常容易讲错的地方。
在同一个 session 里,可能同时发生两件事:
- MCP runtime 往 agent 注入远端工具
- ACP runtime 把 session 状态暴露给宿主
所以一个 ACP 宿主看到的工具调用,完全可能是:
- 本地
bash - 本地
apply_patch - 远端 MCP tool
而宿主不需要关心工具来源差异的底层实现,只需要消费统一事件流即可。
这也是分层的价值:
- MCP 管能力接入
- ACP 管会话协议
12. 当前实现最真实的边界
MCP 边界
MCP 在当前 CLI/Coding Agent 里解决的是:
- server connect
- tools discover
- conflict validation
- toolRegistry/toolExecutor 注入
它不直接解决:
- 宿主 UI 如何渲染
- session 怎样恢复
- 权限弹窗怎样展示
ACP 边界
ACP 在当前实现里解决的是:
- session 生命周期协议
- prompt 执行与取消
- permission request
- structured event delivery
它不直接解决:
- 外部工具从哪来
- 工具 schema 怎样定义
- MCP server 怎样连
13. 最容易踩坑的 5 个点
13.1 把 MCP 写成 Coding Agent 的一个“本地工具”
它不是单个工具,而是外部工具运行面的接入层。
13.2 把 ACP 写成“另一套 CLI”
它是会话协议层,不是终端壳层。
13.3 忽略 MCP 工具名冲突
当前运行时会严格拦 built-in 和跨 server 冲突,这不是小细节。
13.4 把 session/request_permission 理解成 UI 提示文本
它本质上是权限协议往返的一部分。
13.5 以为 ACP 宿主看到的只有文本输出
ACP 当前看到的是结构化 session 事件,而不只是最终 assistant 文本。
14. 这页最该记住的结论
AI4J 当前的 Coding Agent 里:
- MCP 负责把外部 server 工具接成当前 agent 可调用的 tool surface
- ACP 负责把当前 coding session 协议化暴露给宿主
两者会在一条运行链上同时出现,但处理的是完全不同的边界。
把这两层分清,才能真正理解“工具从哪里来”和“会话怎样出去”分别落在哪一层。