一、一个续约问题,为什么会需要四种连接
用户在网页里问:“找出合同的自动续约条款,核对我们当前套餐,如果有争议,请供应商专家确认。”
如果只画一个 agent.run(),这句话后面至少藏着四个独立问题:
- 网页怎样知道 agent 已开始、正在查合同、等待确认,还是已经失败?
- Agent 怎样发现“合同搜索”和“CRM 套餐查询”,并以对方能理解的参数调用?
- 供应商专家是另一个独立 agent,我们怎样知道它会什么,又怎样跟踪一个可能运行几分钟的任务?
- 当 runtime 请求模型思考下一步时,历史、工具调用和流式返回怎样交给模型 provider?
这四个问题的两边都可能由不同团队、语言和产品实现。如果每次都自定义一组 JSON 和回调, 换前端、换工具服务或换远程 agent 都会重写适配层。协议要解决的,正是这种跨实现协作。
二、先给“协议”一个不绕的定义
协议是两个可以独立实现、独立升级的参与方,对“怎样发现能力、怎样发请求、怎样表示状态与结果”达成的公开约定。
Agent 框架与协议不在同一层。框架决定 runtime 内部由谁拥有 loop、session、tool gate、memory 和 graph; 协议则在某个对象需要走出这个 runtime 时,规定它怎样跨边界。同一个框架可以同时支持多种协议; 同一种协议也可以被很多框架实现。
阅读契约。 这篇只追问一件事:每个协议的两边站着谁?读完后,你应该能把同一次续约任务从前端重放到工具、远程 agent 和模型, 并说清哪个协议传的是 tool call、Task、UI event 还是 coding session update。
证据边界。 MCP、A2A、AG-UI 和 Agent Client Protocol 的语义以各自官方架构文档和已发布规范为准。 文中“边界地图”和“owner”是为了比较而做的工程归纳,不代表这些协议可以互相无损转换。
三、先画边界,再放协议名字
先不看 method 名称,只看续约助手的参与方。中间是 Agent Runtime,它拥有本次运行、会话和工具循环。 四条边界分别是:
- 用户前端 ↔ Agent Runtime:用 AG-UI 传输 run、消息、工具和状态事件。
- Agent Runtime ↔ 工具与数据服务:用 MCP 发现和调用 tools、resources 与 prompts。
- Agent Runtime ↔ 远程 Agent:用 A2A 发现对方能力,并跟踪 Message、Task 与 Artifact。
- 代码编辑器 ↔ Coding Agent:用 Agent Client Protocol 管理 coding session、流式更新和工具授权。
还有第五条边界:Agent Runtime ↔ 模型 Provider。OpenAI Responses API、Chat Completions API、Anthropic Messages API 都属于这一层 provider contract。它们当然也是 API 协议,但在 Agent 系统分层里,它们解决的是“runtime 怎样调模型”, 不是工具发现、远程 agent 协作或前端状态同步。
3.1 同一个续约任务,会得到多层身份
下面固定一件事:续约案例 RC-42。产品账本先为它保存用户与合同引用;每跨过一条协议边界,
对方再创建自己负责的身份。它们不是可以互换的五个名字,而是一条父子关联链:
RenewalCase RC-42
└─ AG-UI thread th-9
└─ frontend run r-3
├─ MCP tool call tc-7
└─ A2A task at-88
└─ Artifact ar-5
└─ final UI message m-12
Runtime 必须把这条关联写进自己的账本,因为单个协议只认识自己的工作单元。MCP Server 不需要看到整份合同历史,
它只收到 customer_id 与查询词;A2A 专家只收到争议条款摘要和问题;前端看到状态与可展示结果;
provider 则看到 runtime 为本次模型调用投影的上下文。协议标准化的是跨边界形状,不是把内部账本全部暴露出去。
| 边界 | 谁创建身份 | 跨出的最小数据 | 调用方必须保存什么 |
|---|---|---|---|
| AG-UI | 产品/runtime 协商 thread 与 run | 用户输入、可展示状态、typed events | RC-42 ↔ th-9 ↔ r-3 |
| MCP | 调用 Client 为请求分配 id | 工具名、校验后的参数 | r-3 ↔ tc-7 与工具结果 |
| A2A | 远程 agent 返回 Task id | 目标、必要上下文、可见附件 | r-3 ↔ at-88 与最后状态 |
| Provider API | runtime 与 provider 各保留请求/响应身份 | 本轮模型可见投影 | provider identity 或可重放历史 |
四、MCP:让 runtime 看见工具和数据
4.1 为什么不能只给模型一个 HTTP 地址
续约助手需要合同搜索和 CRM 查询。它不仅要知道 endpoint,还要知道对方现在暴露哪些能力、参数 schema 是什么、 返回内容是文本、图片还是资源引用。如果能力发生变化,runtime 还应该知道需要重新刷新工具表。
MCP 用 Host、Client、Server 三个角色表达这件事。续约助手是 Host;它为每个 MCP Server 维护一个 Client 连接; 合同库和 CRM 分别可以是 Server。Server 可以在本机通过 stdio 运行,也可以在远程通过 Streamable HTTP 服务。
4.2 一次 MCP 调用不是从 tools/call 开始
- Client 与 Server 先
initialize,交换协议版本和 capabilities。 - Client 通过
tools/list、resources/list等方法发现对方真正提供的能力。 - Runtime 把适合当前用户和会话的工具 schema 纳入模型输入。
- 模型选择
contract_search后,runtime 路由到对应 Client,再发出tools/call。 - Server 返回结构化 content,runtime 决定它怎样进入会话、模型上下文与 UI。
{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{
"name":"contract_search",
"arguments":{"customer_id":"c-42","query":"automatic renewal"}
}}
这是简化后的 wire shape。关键不是 JSON-RPC 这几个字段,而是工具在调用前可发现、可协商,调用后有标准内容形状。
MCP 规定 context 交换,不接管 Agent Runtime。 官方架构文档明确说,MCP 不规定 AI 应用如何使用 LLM,也不规定如何管理已取得的 context。 哪些工具对当前用户可见、是否要过权限关口、结果是否持久化,仍然由 Host 与框架决定。
也不要把 MCP 压缩成“远程 function calling”。除了 Server 端的 tools、resources、prompts,它还定义 sampling、elicitation、logging 等 Client 能力, 并且有用于延迟结果和状态跟踪的实验性 Tasks。它的主边界仍是 runtime 与工具/数据,但边界两端可以双向协作。
五、A2A:把一件事交给不透明的远程 agent
5.1 远程 agent 不是一个大工具
CRM 查询的输入和输出很稳定,适合当工具。供应商专家不一样:它有自己的模型、工具、记忆和审批流程,也可能向人补问信息。 调用方不应该知道它内部的 prompt 或 tool chain,只需要知道它承诺什么能力、任务现在是什么状态、最后交付了什么。
A2A 正是为独立、可能不透明的 agent 系统设计的。调用方先通过 Agent Card 发现能力、支持的交互形式和安全要求, 再向对方发送 Message。简单回答可以直接返回 Message;需要持续跟踪的工作则进入 Task,最终结果放进 Artifact。
5.2 Task 把长时间协作变成有身份的资源
- 续约助手读取 Agent Card,确认对方具有“解释供应商续约规则”的 skill。
- 它发送 Message,携带条款摘要和需要确认的问题。
- 对方返回 Task id,状态从 submitted 走向 working,中间可能进入 input-required。
- 调用方可以轮询、订阅流式更新,或为长任务配置 push notification;不再需要时也可以取消。
- 任务完成后,对方把“续约规则说明”作为 Artifact 交付,Task history 则保留这次多轮协作。
Agent Card → Message → Task: working
├─ status update
├─ input-required → Message
└─ completed → Artifact
A2A 1.0 把核心数据模型、抽象操作和具体 binding 分层。同一套 Task、Message、Agent Card 和 Artifact 语义可以映射到 JSON-RPC、gRPC 或 HTTP/REST。 因此“A2A 是不是 JSON-RPC”不是最重要的问题;更重要的是不同 binding 之上仍然共享同一份任务语义。
六、AG-UI:前端需要的不只是 token 流
6.1 一串文字无法还原一次 agent run
续约助手已经查了两个工具,又向远程 agent 发起任务。如果后端只向浏览器推送 assistant text delta,前端无法稳定回答: 这条 delta 属于哪次 run?哪个 tool call 正在等待?用户点击“批准”应该恢复哪个中断?页面重连后怎样重建状态?
AG-UI 把前端与 agent backend 之间的连接定义成双向事件协议。一次运行从 RunAgentInput 进入,
后端返回 BaseEvent 流。threadId 稳定标识会话线,runId 标识本次执行;
message、tool call、state 和 lifecycle 都有自己的 typed event。
RUN_STARTED(threadId, runId)
→ TOOL_CALL_START(toolCallId, "contract_search")
→ TOOL_CALL_ARGS(toolCallId, ...)
→ TOOL_CALL_RESULT(toolCallId, ...)
→ STATE_DELTA(...)
→ TEXT_MESSAGE_START(messageId)
→ TEXT_MESSAGE_CONTENT(messageId, delta)
→ RUN_FINISHED(runId)
这个序列让 UI 知道什么是一次 run,哪些 delta 属于同一条消息或工具调用。STATE_SNAPSHOT 可以建立完整基线,
STATE_DELTA 再用 JSON Patch 传递增量变化。前端不必从一串文本反推 runtime 状态。
6.2 中断是 run 的结果,不是一个没发完的回答
如果 CRM 工具要求用户确认读取敏感套餐,当前 run 可以以 interrupt outcome 结束。前端显示审批界面,用户回答后用新 run 携带 resume 信息。 这种设计保护了两个边界:前端知道上一次 run 已经到达明确终点;runtime 也不用把一个 HTTP 请求无限期悬挂等待用户。
AG-UI 翻译 runtime 事件,不会自动创造 runtime 语义。 如果框架内部没有稳定的 run id、tool-call lifecycle、interrupt 和 state owner,adapter 只能现场猜。 这就是为什么前面先读 AgentScope 的 typed event 和暂停恢复,再读前端协议。
七、ACP:先问清楚说的是哪一个 ACP
7.1 Agent Client Protocol 连接编辑器与 coding agent
把案例换成 coding agent:用户在编辑器里说“修改续约提醒逻辑,然后跑测试”。编辑器需要启动或连接 agent,建立多个 session, 把文件和用户 prompt 交给它,持续显示消息与 tool call,并在 agent 要执行命令时弹出权限询问。
这是一个旁支对照,不是 RC-42 执行到一半突然换协议。浏览器业务产品通常由 AG-UI
表达 run 与 UI state;当编辑器本身拥有 workspace、coding session 和命令授权入口时,ACP 才是更贴近产品 owner 的外层协议。
Agent Client Protocol 把这条边界标准化。对本地 agent,编辑器通常按需启动 agent 子进程, 双方通过 stdin/stdout 交换 JSON-RPC。Agent 用 notification 持续向 UI 推送 session update;又可以用双向 request 反过来请求编辑器批准工具调用。 协议尽量复用 MCP 类型,但 ACP 连接的两边仍然是编辑器和 coding agent,不是 MCP Client 与 MCP Server。
编辑器启动 agent 子进程
→ initialize
→ session/new
→ session/prompt
→ session/update ...
← session/request_permission
→ 用户决策
→ session/update ...
7.2 历史上还有 Agent Communication Protocol
搜索 ACP 时还会找到 BeeAI 的 Agent Communication Protocol。 它解决的是 agent、应用和人之间的远程通信,与前面的 A2A 更接近,不是编辑器协议。 该项目已于 2025 年归档,README 明确说明它已成为 Linux Foundation 下 A2A 的一部分。因此新文章里的 ACP 默认指 Agent Client Protocol; 遇到旧文档时,必须展开全称再判断。
八、Responses API 与 Chat Completions 为什么不和它们放在一格
AgentScope 篇已经具体比较了 OpenAI Responses API 与 Chat Completions API。
Responses 使用 typed items,可以通过 response identity 延续状态;Chat Completions 主要接收 messages 数组,工具调用和结果也有不同 wire shape。
AgentScope 需要两个 formatter 和 model adapter,再把它们归一成同一种内部 ChatResponse。
这个差异很重要,但它发生在 runtime 与模型 provider 之间。MCP 不规定你用 Responses 还是 Chat Completions 调模型; A2A 对面的 agent 也可以使用完全不同的 provider;AG-UI 前端只应该消费稳定的 UI event,不该直接绑定 provider delta。 把 provider API 单独画一层,才能让每个 adapter 对自己真正拥有的语义负责。
九、把整个续约任务重放一遍
正常成功路径并不难,真正能检验协议有没有接牢的是远程 agent 补问用户。下面让
at-88 进入 input-required,同时跟踪持久记录怎样变化:
- 浏览器通过 AG-UI 发送
RunAgentInput,runtime 把RC-42关联到th-9 / r-3。 - 模型决定查合同;runtime 校验权限后发出 MCP 调用
tc-7,把结果写回案例账本。 - 遇到争议条款时,runtime 向 A2A 专家发送摘要,保存远端返回的
at-88。 at-88返回input-required。Runtime 保存父 thread、Task id、问题和waiting_user,让r-3以 interrupt 结束。- AG-UI 把问题投影给前端。用户回答后创建新 run
r-4,而不是复活已结束的 HTTP 请求。 - Runtime 通过账本找到原 Task,把回答作为新 Message 发给
at-88;远端完成后交付ar-5。 - Runtime 保存 Artifact 引用,合并本地工具结果与远程证据,再调用模型生成消息
m-12。 - AG-UI 发出最终文本与
RUN_FINISHED;产品账本把RC-42标成 completed。
before input-required
RC-42: {run: r-3, a2a_task: at-88, status: waiting_remote}
after input-required
RC-42: {run: r-3, a2a_task: at-88, question: "Which region?",
status: waiting_user, resume_from: at-88}
after user reply
RC-42: {run: r-4, a2a_task: at-88, artifact: ar-5,
final_message: m-12, status: completed}
失败分支也各有 owner:MCP 失败要以 tool error 回到 r-3,不能伪装成空结果;
A2A 超时要保留 at-88 供查询、取消或稍后重附着;浏览器断线只中断事件交付,
重连时应从 snapshot 和后续 delta 重建 UI,不应偷偷重做工具调用。
如果入口是代码编辑器,最外层的 AG-UI 可能换成 Agent Client Protocol;里面的 coding agent 仍然可以使用 MCP 连接工具, 也可以用 A2A 委派远程 agent。这就是“主边界”的含义:协议各自有主要位置,但它们可以在一次运行里组合。
十、现在再用一张表压缩
| 边界 | 主要参与方 | 核心工作单元 | 发现与能力 | 长任务与恢复 | 谁仍然拥有内部运行 |
|---|---|---|---|---|---|
| MCP | AI Host/Client ↔ Tool/Data Server | tool call、resource、prompt | initialize + capabilities + */list |
notification;实验性 Tasks | Host 拥有 agent loop、context 选择和权限。 |
| A2A | Agent Client ↔ 独立远程 Agent | Message、Task、Artifact | Agent Card + skills + interfaces | status、stream、poll、push、cancel | 远程 agent 保持不透明的内部实现。 |
| AG-UI | User-facing App ↔ Agent Backend | RunAgentInput + BaseEvent | client/agent capabilities 与 adapter | run lifecycle、interrupt/resume、state snapshot/delta | Backend 拥有执行;Frontend 拥有用户可见状态。 |
| Agent Client Protocol | Code Editor ↔ Coding Agent | connection、session、prompt turn、update | initialize + agent registry/config | session identity、stream update、permission request | Coding agent 拥有 loop;编辑器拥有 workspace UX 与授权入口。 |
| Provider API | Agent Runtime ↔ Model Provider | response/item 或 messages/completion | model 与 API capability | response identity 或客户端重放历史 | Runtime 拥有工具循环与产品 session;provider 拥有模型调用。 |
十一、选协议时不要先问“支持吗”
一个框架在 README 里写“supports MCP/A2A/AG-UI”,只能证明它有某个接入面,不能证明所有语义都是完整的。 更有用的检查顺序是:
- 先画参与方:这是 runtime 调工具、委派 agent、服务前端,还是编辑器启动 coding agent?
- 再找 identity owner:tool call id、task id、run id、session id 分别由谁创建、持久化和恢复?
- 再看终止语义:成功、失败、取消、询问用户和稍后恢复,是协议一等状态还是自定义文本?
- 最后审安全边界:连接身份、用户授权、工具参数校验、Artifact 与 resource 的可见范围由谁强制?
协议不会自动带来信任。MCP Server 暴露一个 tool,不代表当前用户应该被允许调用;A2A Agent Card 声明一项 skill,不代表调用方已验证对方身份; AG-UI 能传授权结果,不代表前端可以绕过后端策略。协议只是让安全信息有一个可互操作的载体,真正的决策仍由各边 owner 强制。
11.1 “支持协议”要用恢复场景验收
真正采用某个 adapter 前,至少要跑过五个场景:MCP Server 更新 schema 后,Host 能刷新而不是继续发送旧参数;
调用方重启后能凭 at-88 重附着 A2A Task;AG-UI 断线后能从 snapshot 重建而不重复执行;
前端传来的授权仍由后端复核;更换 provider 后,前端不需要理解一套新的 delta。
这些场景通过,只说明协议语义被验证;把 adapter 配进生产、开放给哪些用户,仍是应用 owner 的采用决定。 因此“代码里有 adapter”“恢复测试通过”“产品启用”是三个不同状态。
十二、带着这张地图继续读框架
后面几篇会反复出现这些协议名称,但每次都要回到边界。ADK 的 Agent 与 Workflow 先回答框架内部怎样运行;Agno 会把这条主线放进 AgentOS 和多种对外界面; AutoGen 与 Microsoft Agent Framework 会把 multi-agent runtime 和生产编排放在一起比较;CrewAI 让角色协作与确定性 Flow 分开;Eino 用 Go component 与 graph 表达执行; tRPC-Agent-Go 则会直接展开 MCP、A2A、AG-UI 与 OpenAI-compatible server 的不同出口。
下一篇进入 ADK Python。当协议边界已经分清,我们可以重新回到框架内部,看 Agent 的自主决策与 Workflow 的确定性步骤怎样在同一条 Runner/Event 主线上共存。
官方规范与文档
- Model Context Protocol architecture
- A2A latest released specification
- AG-UI overview
- AG-UI core architecture
- AG-UI events
- Agent Client Protocol introduction
- Agent Client Protocol architecture
- Agent Client Protocol session setup
- Agent Client Protocol prompt turn
- Archived Agent Communication Protocol and A2A migration notice
- OpenAI Responses API migration guide