1. 为什么 Agent 通信协议突然“百花齐放”
过去一年,只要你在做 AI Agent 相关的东西,几乎绕不开一个困惑:为什么 Agent 之间的通信协议这么多?一会儿是 JSON-RPC,一会儿是 MCP,一会儿又冒出 A2A、ACP,每个看起来都像要“统一江湖”,但每个又都只解决了一部分问题。我刚开始接触这块的时候,也有过“是不是又有人在造概念”的怀疑,但真正把几个协议拉通对比、动手写过 demo 之后,我的判断变了:这不是重复造轮子,而是 Agent 这个物种本身太特殊,单一协议根本兜不住它的全部需求。
先把结论摆在前面:JSON-RPC 是底座,MCP 解决的是“Agent 怎么接工具和数据”,A2A 解决的是“Agent 怎么找 Agent、怎么协作”,ACP 则更偏向“Agent 之间怎么在运行时对话和编排”。它们不是互相替代的关系,而是处在不同层次、面向不同场景。你如果只记住这一句话,后面看任何新协议都不会晕。
这篇文章适合三类人:一是正在做 Agent 应用、被各种协议文档绕晕的开发者;二是想搞清楚“我到底该用哪个协议”的技术选型者;三是想理解 Agent 生态演进逻辑、不想只停留在调 API 层面的进阶读者。我会从设计动机、核心机制、实操要点、踩坑经验几个角度,把 JSON-RPC、MCP、A2A、ACP 这几个协议串起来讲清楚,尽量让你看完之后能自己判断:下一个新协议出来,它大概是在解决哪一层的问题。
在展开之前,先给一个我常用的“分层心智模型”,后面所有内容都挂在这个模型上:
| 层次 | 解决的问题 | 代表协议 |
|---|---|---|
| 传输层 | 消息怎么发、怎么收、怎么序列化 | JSON-RPC 2.0 |
| 工具/上下文层 | Agent 怎么调用外部工具、读取数据 | MCP |
| Agent 发现与协作层 | Agent 怎么找到彼此、怎么分工 | A2A |
| 运行时对话与编排层 | 多 Agent 怎么在会话中协同 | ACP |
这个模型不是官方标准,是我自己在做项目时总结的,但它能解释 90% 的“为什么又出一个协议”的疑问。下面逐层拆。
2. JSON-RPC:所有 Agent 协议的“普通话底座”
2.1 JSON-RPC 到底解决了什么问题
JSON-RPC 不是什么新东西,它本质上就是一个轻量级的远程调用规范:客户端发一个 JSON 对象,里面写明要调用的方法名和参数,服务端返回一个 JSON 对象,里面是结果或错误。就这么简单。它的核心价值在于把“调用”这件事标准化了,不管你是本地进程、HTTP 服务还是 WebSocket 连接,只要双方都认 JSON-RPC 格式,就能通信。
为什么 Agent 协议都爱用它?因为 Agent 的通信本质上是**“我要你帮我做一件事,做完告诉我结果”**,这跟 RPC 的语义天然吻合。而且 JSON 可读性好、调试方便,抓个包就能看懂,不像二进制协议那样需要专门工具。我在早期做 Agent 工具调用的时候,直接手写 JSON-RPC 请求,五分钟就能跑通一个 demo,这种低门槛是它被广泛采用的关键原因。
一个典型的 JSON-RPC 2.0 请求长这样:
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "search", "arguments": { "query": "agent protocol" } } }对应的响应:
{ "jsonrpc": "2.0", "id": 1, "result": { "content": [{ "type": "text", "text": "..." }] } }你看,没有任何多余的东西。id用来匹配请求和响应,method是方法名,params是参数,result是结果。这种极简设计让它在各种语言里都能轻松实现,Python、JavaScript、Go 都有现成库。
2.2 为什么 Agent 场景特别依赖它
Agent 的工作模式决定了它需要频繁地“调用—等待—处理结果”。比如一个 Agent 要查天气、再查航班、再订酒店,每一步都是一次调用。JSON-RPC 的请求-响应模型刚好匹配这种串行或并行的调用链。而且它的错误处理机制很清晰:错误码、错误信息、错误数据三段式,Agent 可以根据错误码决定是重试、换工具还是直接放弃。
但 JSON-RPC 也有明显的局限。它只规定了“怎么调用”,没规定“调用什么”。也就是说,它不知道有哪些工具可用、每个工具需要什么参数、返回什么格式。这就好比你知道怎么打电话,但不知道对方的电话号码和要说什么。这个空白,就是 MCP 要填的。
注意:JSON-RPC 本身不涉及传输层,它可以在 HTTP、WebSocket、stdio 上跑。很多新手会把它和 HTTP 混为一谈,其实 HTTP 只是它的一种承载方式。
2.3 实操心得:手写 JSON-RPC 的三个坑
第一个坑是id 冲突。如果你并发发多个请求,id 必须唯一,否则响应回来你分不清是哪个请求的结果。我见过有人用时间戳做 id,结果同一毫秒内发了两个请求,直接乱套。稳妥做法是用自增整数或 UUID。
第二个坑是错误处理不完整。JSON-RPC 定义了标准错误码,比如 -32700 是解析错误,-32601 是方法不存在。但很多实现只处理成功情况,遇到错误就抛异常,导致 Agent 无法优雅降级。建议在客户端封装一层,把错误码映射成可决策的状态。
第三个坑是批量请求的顺序问题。JSON-RPC 支持批量请求,但响应顺序不保证和请求顺序一致。如果你依赖顺序,必须用 id 匹配,不能靠数组下标。
3. MCP:让 Agent 真正“接得上”工具和数据
3.1 MCP 的核心设计动机
MCP 全称 Model Context Protocol,它要解决的是一个非常具体的问题:Agent 怎么知道有哪些工具可用,以及怎么调用它们。在 MCP 之前,每个 Agent 框架都有自己的工具定义方式,LangChain 一套、AutoGPT 一套、各家平台又一套,导致工具提供方要适配 N 个框架,开发者换个框架就得重写一遍工具接入层。MCP 的出现,就是想把这一层标准化。
它的核心思路是:把工具和数据源抽象成“服务器”,Agent 作为“客户端”去连接这些服务器,通过标准协议发现和调用能力。一个 MCP 服务器可以暴露多个工具、多个资源、多个提示模板,Agent 连上之后先问“你有什么”,然后根据需要调用。这就像 USB 接口,不管你是鼠标、键盘还是U盘,插上就能用,不需要为每个设备装不同驱动。
MCP 的通信底层用的就是 JSON-RPC 2.0,所以如果你理解了上一节,看 MCP 的消息格式会很亲切。它定义了几种核心原语:
- Tools:可调用的函数,比如搜索、计算、发邮件
- Resources:可读取的数据,比如文件、数据库记录
- Prompts:预定义的提示模板,方便复用
3.2 MCP 的握手与能力发现流程
MCP 连接建立后,第一件事是初始化握手。客户端发initialize请求,带上自己的协议版本和能力;服务端返回它支持的协议版本和能力列表。这个过程很关键,因为它决定了后续能用哪些功能。如果双方版本不匹配,或者服务端不支持客户端需要的功能,就得提前报错,而不是等到调用时才失败。
握手完成后,客户端可以发tools/list获取工具列表,每个工具包含名称、描述、参数 schema。这个 schema 通常是 JSON Schema,Agent 可以根据它自动生成调用参数。我实测下来,这一步是 MCP 最实用的地方:Agent 不需要硬编码工具知识,而是运行时动态发现。你新增一个工具,只要 MCP 服务器更新列表,Agent 下次连接就能看到,不用改代码。
调用工具时,客户端发tools/call,带上工具名和参数,服务端执行后返回结果。结果可以是文本、图片、结构化数据,MCP 定义了多种内容类型来承载。
3.3 实操:从零搭一个最小 MCP 服务器
下面用 Python 写一个最简单的 MCP 服务器,暴露一个“加法”工具。这里用官方提供的 SDK 风格,实际实现可能略有差异,但逻辑一致。
from mcp.server import Server from mcp.types import Tool, TextContent app = Server("demo-server") @app.list_tools() async def list_tools(): return [ Tool( name="add", description="计算两个数字之和", inputSchema={ "type": "object", "properties": { "a": {"type": "number"}, "b": {"type": "number"} }, "required": ["a", "b"] } ) ] @app.call_tool() async def call_tool(name: str, arguments: dict): if name == "add": result = arguments["a"] + arguments["b"] return [TextContent(type="text", text=str(result))] raise ValueError(f"未知工具: {name}") if __name__ == "__main__": app.run()这段代码的核心就三块:声明工具列表、实现调用逻辑、启动服务。Agent 连上来之后,先拿到add工具的描述和参数要求,然后就能调用。你可以把add换成任何实际功能,比如查数据库、调外部 API。
提示:MCP 服务器可以通过 stdio 或 HTTP+SSE 两种方式暴露。stdio 适合本地工具,HTTP 适合远程服务。选哪种取决于你的部署场景,本地开发用 stdio 调试最方便。
3.4 MCP 的边界与常见误区
MCP 不是万能的。它解决的是“Agent 到工具”的连接问题,但不解决 Agent 到 Agent 的协作问题。也就是说,如果你有两个 Agent 需要互相商量、分工,MCP 帮不上忙。这是很多人第一次接触 MCP 时的误解,以为它能搞定所有通信。实际上,MCP 的定位非常清晰:它就是工具和数据的接入层。
另一个误区是认为 MCP 会取代 REST API。不会。MCP 是在 REST API 之上的一层抽象,它让 Agent 更容易发现和调用 API,但底层还是 HTTP 请求。你完全可以把现有的 REST API 包装成 MCP 服务器,而不需要重写后端。
我在实际项目里踩过的一个坑是工具描述写得太模糊。MCP 的工具描述是给 Agent 看的,如果描述不清楚,Agent 可能选错工具或传错参数。比如一个工具叫process,描述写“处理数据”,Agent 根本不知道什么时候该用。后来我改成extract_keywords,描述写“从文本中提取关键词,返回字符串数组”,调用准确率立刻上来了。这个经验很朴素但很关键:给 Agent 看的文档,要比给人看的更明确。
4. A2A:Agent 之间怎么“找到彼此”并协作
4.1 为什么 MCP 不够,还需要 A2A
MCP 解决了 Agent 用工具的问题,但现实任务往往需要多个 Agent 配合。比如一个“市场分析”任务,可能需要一个 Agent 负责爬数据、一个负责分析、一个负责写报告。这三个 Agent 可能由不同团队开发、部署在不同地方,它们怎么互相发现、怎么约定协作方式?MCP 没有答案,因为它只关心“Agent 到工具”,不关心“Agent 到 Agent”。
A2A 全称 Agent-to-Agent Protocol,它的目标就是让 Agent 能够发布自己的能力、发现其他 Agent 的能力、并发起协作请求。你可以把它理解成 Agent 界的“服务发现 + 远程调用”。每个 Agent 可以暴露一个“Agent Card”,里面写明自己叫什么、能做什么、接受什么输入、返回什么输出。其他 Agent 拿到这张卡,就知道该怎么跟它打交道。
这个设计思路和微服务架构很像:服务注册中心、服务发现、服务调用。区别在于,Agent 的能力描述更偏向自然语言和任务语义,而不是严格的接口签名。因为 Agent 的任务往往比较模糊,需要一定的协商空间。
4.2 Agent Card 与能力发现机制
Agent Card 是 A2A 的核心概念。一个典型的 Agent Card 包含:
- 名称和描述:这个 Agent 是干什么的
- 能力列表:支持哪些任务类型
- 输入输出格式:接受什么、返回什么
- 通信端点:怎么联系它
- 认证要求:需不需要鉴权
当 Agent A 需要完成一个任务时,它可以先查询可用的 Agent 列表,根据 Agent Card 筛选出合适的候选,然后发起任务请求。任务请求通常包含任务描述、输入数据、期望的输出格式。Agent B 收到后,可以接受、拒绝或协商。
这种机制的好处是解耦。Agent A 不需要知道 Agent B 的内部实现,只需要知道它的能力。Agent B 可以随时升级、替换,只要 Agent Card 不变,A 就不受影响。这跟微服务的接口契约是一个道理。
4.3 任务生命周期与状态同步
A2A 定义了一套任务生命周期:提交、接受、进行中、完成、失败、取消。Agent 之间通过状态更新来同步进度。这对于长任务特别重要,比如一个数据分析任务可能跑几分钟,Agent A 需要知道 B 是在正常运行还是卡住了。
状态同步通常通过轮询或推送实现。轮询简单但实时性差,推送实时但实现复杂。我在项目里一般先用轮询,间隔 2-5 秒,等任务量上来了再考虑推送。这个取舍要看具体场景,没有绝对优劣。
注意:A2A 的任务状态机要设计得健壮,特别是超时和重试。我见过因为没处理“进行中”状态超时,导致 Agent A 一直等、Agent B 早就挂了的情况。建议每个状态都设超时,超时后走失败或重试逻辑。
4.4 实操:两个 Agent 协作完成一个任务
假设我们有一个“翻译 Agent”和一个“校对 Agent”,翻译 Agent 负责把中文翻成英文,校对 Agent 负责检查语法。流程如下:
- 翻译 Agent 收到任务,调用自己的翻译工具,得到初稿
- 翻译 Agent 通过 A2A 发现校对 Agent,发送校对请求
- 校对 Agent 接收请求,执行校对,返回修改建议
- 翻译 Agent 根据建议修改,返回最终结果
这个流程里,A2A 负责的是第 2 步的发现和第 3 步的调用。翻译 Agent 不需要知道校对 Agent 部署在哪、用什么模型,只需要知道它“能校对英文”。这种松耦合让系统更容易扩展:以后加一个“术语一致性检查 Agent”,只要注册到 A2A 网络,翻译 Agent 就能发现并使用。
5. ACP:运行时对话与编排的“会话层”
5.1 ACP 的定位:比 A2A 更偏运行时
ACP 全称 Agent Communication Protocol,它和 A2A 容易混淆,因为名字都带“Agent 通信”。但仔细看,它们的侧重点不同。A2A 更偏向能力发现和任务委派,是一次性的“我找你办事”;ACP 更偏向运行时对话和编排,是持续的“我们商量着办”。
举个例子:A2A 像你打电话给一家公司说“帮我做个 logo”,对方接单做完给你。ACP 像你和设计师在微信上反复沟通,改一版、聊一版,直到满意。前者是任务导向,后者是会话导向。很多复杂 Agent 场景其实是后者,需要多轮交互、上下文保持、动态调整。
ACP 的核心概念包括会话、消息、角色、状态。一个会话可以有多个参与者,每个参与者有角色(比如用户、助手、协调者),消息在会话中流转,状态在会话中累积。这比 A2A 的单次任务请求要复杂得多,但也更贴近真实协作。
5.2 会话管理与上下文传递
ACP 最关键的机制是会话上下文。在多轮对话中,每个 Agent 需要知道之前发生了什么,才能做出合理响应。上下文包括对话历史、共享变量、任务状态等。ACP 定义了上下文的传递方式,确保新加入的 Agent 能快速同步。
这里有个难点:上下文可能很大,全量传递效率低。常见做法是摘要 + 增量:把历史对话压缩成摘要,只传关键信息,新消息增量传递。我在项目里用过这个策略,效果不错,但摘要的质量很关键,摘要丢了关键信息,后续 Agent 就会跑偏。
另一个难点是上下文一致性。多个 Agent 同时修改共享状态时,可能冲突。ACP 通常用版本号或时间戳来解决,类似乐观锁。如果两个 Agent 基于同一版本修改,后提交的要么合并、要么重试。
5.3 编排模式:谁来决定下一步
ACP 支持多种编排模式:
- 中心化编排:一个协调者 Agent 决定谁做什么,其他 Agent 听指挥
- 去中心化编排:Agent 之间平等协商,根据能力自发分工
- 混合编排:大方向由协调者定,细节由 Agent 自主决定
中心化简单可控,但协调者容易成为瓶颈;去中心化灵活,但容易出现死锁或重复劳动。我一般建议先中心化,等流程稳定了再考虑去中心化。因为中心化调试容易,出问题能快速定位。去中心化的调试成本高很多,不适合早期。
提示:编排模式的选择要和任务复杂度匹配。简单任务用中心化,复杂开放任务才考虑去中心化。不要为了“先进”而过度设计。
5.4 实操:用 ACP 思路设计一个多 Agent 对话流程
假设我们要做一个“旅行规划”系统,涉及航班 Agent、酒店 Agent、行程 Agent。用 ACP 的思路,流程可以这样设计:
- 用户提出需求:“下个月去某地五天,预算中等”
- 协调者 Agent 创建会话,邀请三个 Agent 加入
- 航班 Agent 先给出航班建议,写入会话上下文
- 酒店 Agent 根据航班时间给出酒店建议
- 行程 Agent 综合两者,生成每日行程
- 协调者汇总,返回用户
- 用户反馈“酒店太贵”,协调者把反馈写入上下文,酒店 Agent 重新推荐
这个流程里,ACP 负责的是会话创建、消息流转、上下文共享、状态更新。每个 Agent 只需要关注自己的专业领域,不需要知道其他 Agent 的实现。这种设计让系统容易扩展:以后加一个“餐厅 Agent”,只要加入会话,就能参与协作。
6. 四个协议怎么选:一张决策表和一个真实案例
6.1 选型决策表
| 你的需求 | 推荐协议 | 理由 |
|---|---|---|
| 只是调用工具、读数据 | MCP | 标准化工具接入,动态发现 |
| 需要多个 Agent 分工 | A2A | 能力发现 + 任务委派 |
| 需要多轮对话、动态调整 | ACP | 会话管理 + 上下文传递 |
| 自己实现底层通信 | JSON-RPC | 轻量、通用、易调试 |
| 工具 + 多 Agent 协作 | MCP + A2A | 各管一层,组合使用 |
| 复杂多 Agent 对话 | MCP + ACP | 工具接入 + 会话编排 |
这张表不是绝对的,但能覆盖大部分场景。核心原则是:不要用一个协议解决所有问题,而是分层组合。MCP 管工具,A2A 管发现和委派,ACP 管会话和编排,JSON-RPC 管传输。每层各司其职,系统才清晰。
6.2 一个真实项目的协议组合案例
我之前参与过一个“智能客服”项目,涉及知识库查询、工单创建、人工转接三个环节。我们的协议组合是这样的:
- JSON-RPC:作为底层传输,所有消息都走这个格式
- MCP:知识库查询工具、工单创建工具都包装成 MCP 服务器,客服 Agent 动态发现和调用
- A2A:客服 Agent 需要转人工时,通过 A2A 发现人工坐席 Agent,发起转接请求
- ACP:转接后,人工坐席和客服 Agent 在同一个会话里协作,共享上下文
这个组合跑下来,效果比之前单体 Agent 好很多。知识库更新只需要改 MCP 服务器,不用动 Agent 代码;人工坐席可以独立部署、独立扩容;会话上下文让转接后的人工坐席能快速了解前因后果。
踩过的坑也不少。最大的坑是协议版本不一致。MCP 服务器升级了协议版本,但客户端没更新,导致握手失败。后来我们加了版本协商和降级逻辑,才稳定下来。另一个坑是A2A 的任务超时没处理好,人工坐席忙的时候,客服 Agent 一直等,用户体验很差。后来加了超时和排队机制,才解决。
7. 常见问题与排查技巧实录
7.1 协议选型常见困惑
问题一:MCP 和 A2A 能互相替代吗?不能。MCP 是 Agent 到工具,A2A 是 Agent 到 Agent。一个是“我用工具”,一个是“我找人帮忙”。场景不同,不能替代。
问题二:ACP 和 A2A 有什么区别?A2A 偏任务委派,一次性的;ACP 偏会话协作,持续的。A2A 像外包,ACP 像团队协作。
问题三:JSON-RPC 过时了吗?没有。它是底座,MCP 底层就是 JSON-RPC。你不需要直接用它,但理解它有助于理解上层协议。
7.2 实操排查速查表
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| MCP 握手失败 | 协议版本不匹配 | 检查双方版本,加降级逻辑 |
| 工具调用返回空 | 工具描述模糊,Agent 选错 | 优化工具描述,加示例 |
| A2A 任务一直进行中 | 超时未处理 | 加超时和重试 |
| ACP 上下文丢失 | 摘要丢关键信息 | 检查摘要策略,加关键字段 |
| 多 Agent 死锁 | 去中心化编排冲突 | 改中心化或加锁 |
| 消息乱序 | JSON-RPC id 冲突 | 用唯一 id,按 id 匹配 |
7.3 独家避坑技巧
第一个技巧:先跑通单 Agent,再上多 Agent。很多人一上来就搞多 Agent 协作,结果调试成本爆炸。我的经验是,单 Agent + MCP 工具先跑稳,确认工具调用没问题,再加 A2A 或 ACP。这样出问题容易定位。
第二个技巧:协议日志要打全。JSON-RPC 的请求和响应、MCP 的握手和能力列表、A2A 的任务状态、ACP 的会话消息,全部记日志。出问题时,日志是唯一能还原现场的东西。我一般用结构化日志,方便检索。
第三个技巧:版本协商要双向。不要假设对方和你版本一致。客户端和服务端都要声明版本,取交集。没有交集就报错,不要硬跑。
第四个技巧:超时是必须的。任何跨 Agent 调用都要设超时,包括 MCP 工具调用、A2A 任务、ACP 消息。没有超时,一个卡住的 Agent 能拖垮整个系统。
第五个技巧:上下文要可裁剪。ACP 的上下文可能很大,要能按需裁剪。比如只传最近 N 轮对话,或只传关键字段。全量传递在早期没问题,规模上来就是瓶颈。
8. 我对 Agent 协议演进的一点个人判断
做了这么多项目,我的体会是:Agent 协议的分层会越来越清晰,但短期内不会收敛成一个。因为 Agent 的场景太分散了,有的只需要调工具,有的需要多 Agent 协作,有的需要长会话。一个协议想通吃,要么太复杂没人用,要么太简单不够用。
我的建议是:不要追新协议,先理解分层。你把 JSON-RPC、MCP、A2A、ACP 各自解决什么问题搞清楚,新协议出来你也能快速定位它在哪一层。这比记住每个协议的 API 更有价值。
最后分享一个我常用的判断方法:拿到一个新协议,先问三个问题——它是管传输的、管工具的、管发现的,还是管会话的?它和现有协议是替代还是互补?它在我的系统里对应哪一层?回答完这三个问题,你就知道该不该用了。