1. 先搞明白:这些协议到底在“对谁说话”
最近在搭一个多Agent协作的模拟项目X,核心任务是让不同职能的Agent相互配合完成业务流程。翻了几天资料,发现相关缩写一下冒出好几个:MCP、A2A、ACP,底层还都号称基于JSON-RPC。第一反应是“又出新协议了,生态要分裂了”。等把架构图和数据流真正梳理完,才发现完全不是这么回事——这三个家伙虽然都被称为Agent通信协议,但通信对象截然不同,解决的问题根本不在一个层次。
1.1 MCP、A2A、ACP,通信对象完全不同
日常口语里“Agent通信协议”这个词太容易制造误解了。稍微拆一下就会发现,这个大帽子底下至少盖着三类截然不同的通信关系。
先看MCP(Model Context Protocol),它解决的是一个Agent或一个LLM应用,如何标准化地接入外部工具、数据源和业务系统。它的名字已经说得很直白:给模型补齐上下文,让模型能触达那些原本封闭的API和数据。通信双方是“LLM应用”和“外部工具/数据源”,这是纵向的连接。
再看A2A(Agent2Agent),它解决的是Agent与Agent之间如何互相发现能力、互相派发任务、反馈进度和交换结果。通信双方是“Agent”和“另一个Agent”,这是横向的连接。典型场景是主Agent把订酒店、查排期、发通知这类子任务分发给不同的专业Agent,并跟踪它们各自的执行状态。
最后是ACP(Agent Communication Protocol),同样面向Agent与Agent的通信,但它的建模方式更偏“会话”:把一次Agent协作当作一段可以控制的对话,消息被组织成Session和Turn,强调过程可控、人机可共治。
所以,MCP是Agent把工具接在自己身上的“接入协议”,A2A和ACP是Agent之间互相打交道的“协作协议”。把它们统称为“Agent通信协议”,再放在一起抱怨“为什么标准不统一”,其实是把三层问题压缩成了一层。我一开始就犯了这种认知错误。
1.2 通信对象不同,协议自然分裂
为什么这个区别会直接导致协议形态不同?因为不同通信对象对协议的需求约束完全不一样。
接入工具的场景,要求协议足够轻,能被模型端在几百行代码里快速调通;工具的清单和能力描述往往是相对静态的,所以协议里要设计一套“能力发现”机制,让客户端知道服务端到底提供了什么。MCP的tools/list就是干这件事的。
Agent协作的场景,要求协议能表达更复杂的交互状态。一个Agent把任务委派给另一个Agent,任务可能持续几分钟甚至几小时,执行中会经历排队、进行中、请求补充信息、完成、失败、取消等状态。更麻烦的是,任务执行到一半,可能还需要人类或其他Agent介入。这种需求没办法被一个简单的“请求-响应”模型覆盖,必须引入任务状态机或者会话这种更厚重的抽象。
通信对象决定数据模型,数据模型决定协议形态。拿生活里的例子类比:USB-C解决的是“设备怎么插进电脑”的问题,网线和HTTP解决的是“电脑之间怎么互相访问”的问题。你不能因为两者都叫“连接标准”,就要求它们合并成一个协议。四个字总结:“通信对象”是所有分歧的根源。
2. 一切的地基:为什么大家都选JSON-RPC
顺着MCP、A2A、ACP往下追,会发现它们不约而同都用了JSON-RPC 2.0作为消息层基础。这个细节很容易被忽略,但恰恰是理解整个协议族的关键。
2.1 JSON-RPC 2.0把远程调用压缩到三个字段
JSON-RPC是一个极简的远程过程调用协议,一次完整的请求只需要三个核心字段:版本标识、方法名、参数。看一个实际例子,一次工具调用长这样。
{ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "query_weather", "arguments": {"city": "某城市A"} }, "id": 1 }服务端成功时返回result,失败时返回error对象,携带标准的错误码。如果请求里没有id,则表示这是一个通知(notification),服务端不需要回响应。请求和响应的对应关系靠id匹配,思路和HTTP请求里的correlation id一模一样。
{ "jsonrpc": "2.0", "result": { "content": [ {"type": "text", "text": "某城市A:晴,18℃"} ] }, "id": 1 }{ "jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": 1 }规范里还定义了批处理,多个调用可以打包成一个JSON数组一次传输。整个规范文本很短,十几分钟就能读完,实现成本低到可以手写。这种朴素特性在协议竞争里反而是极强的护城河。
2.2 为什么不是REST、gRPC、GraphQL
不少人会问:HTTP时代,为什么不直接用REST?追求性能用gRPC?或者做数据聚合用GraphQL?这些疑问在接触Agent协议时几乎一定会冒出来。
REST的问题在于资源语义太重。REST把“对象”建模成资源,用HTTP动词表达对资源的操作。但Agent之间的调用,很多时候不是“对资源增删改查”,而是“向对方发起一段交互过程”。硬套REST,最后会变成到处POST,语义从“操作资源”滑向“触发动作”,REST带来的规范化收益就消失了。
gRPC的问题在于契约和二进制链路。它需要定义proto文件、生成代码、管理序列化和HTTP/2连接。对一个希望“模型能快速生成客户端代码”的场景来说,这套东西太重了。而且二进制协议不好抓包调试,后端服务和外围系统对接时排障成本明显更高。
GraphQL站在另一个极端。它最擅长查询图结构数据,有强schema、复杂查询语义和权限控制,但“调用远端某个方法并拿结果”这件事它并不直接。Agent协作本质上是过程调用,不是数据查询,用GraphQL做底层会有一种绕远路的别扭感。
JSON-RPC恰好落在中间:方法调用语义直接,消息是纯文本JSON,可读、可调试、跨语言不需要代码生成,同时支持通知这种轻量机制,很适合长连接上的事件推送。而且还有一个LLM时代的隐藏优势——模型对JSON的理解能力非常强,让它“读着JSON-RPC规范生成客户端”或者“生成一个符合格式的调用包”,都是很容易的事;换成gRPC的二进制协议,这条路就走不通了。各方案对比可以总结成一张表。
| 方案 | 擅长场景 | 在Agent通信中的核心问题 |
|---|---|---|
| REST | 资源型API | 动词有限,调用语义容易被拉伸 |
| gRPC | 高性能内部服务 | 合约与二进制链路重,AI代写客户端成本高 |
| GraphQL | 数据查询与BFF | 查询模型强,过程调用弱 |
| JSON-RPC | 跨语言远程方法调用 | 极简、可读、模型友好,几乎无缺点 |
2.3 大家只把JSON-RPC当“消息信封”,真正的协议在信封之上
这里要澄清一个高频误解:MCP、A2A、ACP都基于JSON-RPC,并不代表它们就是同一种东西,更不代表“换了个名字的JSON-RPC”。
JSON-RPC只定义了“方法调用”这个信封结构。信封里装哪些方法、方法之间按什么顺序编排、消息有哪些状态生命周期、两端如何握手认证,全部由上层协议自己定义。
类比来说,JSON-RPC相当于信封和邮票。MCP规定了“我寄给你的是工具调用单”,A2A规定了“我寄给你的是任务委派单”,ACP规定了“我寄给你的是会话中的一条消息”。用的是同一个信封,但信件内容、投递规则、跟踪机制完全不同。
理解到这一层,再看整个协议族就不会乱了。真正有差异的部分不在消息编解码,而在消息的语义组织和生命周期管理。这也是后面MCP和A2A/ACP在形态上分道扬镳的根本原因。
3. MCP:Agent接入工具世界的标准插座
三个协议里,MCP是当前生态发展最快、生产环境落地最多的一个。对大多数做Agent项目的人来说,MCP也是第一个应该接触的协议。
3.1 没有MCP之前:一半代码都写在胶水层
用LLM接外部工具这件事,在MCP出现之前做得极其痛苦。
每个工具一套API文档,代码里要维护对应请求函数;参数要手工映射到模型的function calling定义;返回结果要转成模型能读懂的文本;换一个工具,几乎就要重写一遍类似的胶水代码。工具越多,胶水层越厚。更麻烦的是,模型端每接一个新工具都要重新调整function schema,schema一变,决定“模型什么时候该调这个工具”的逻辑又得重新测试。
这个痛点和USB-C统一充电口之前的情况很像:抽屉里堆满各种充电线,每根线只能给特定设备用。MCP做的,就是规定出插座和插头的标准形状:工具方统一暴露能力描述,LLM应用统一方式调用,双方不再需要针对每家的接口单独适配。
3.2 MCP的三个核心原语与传输方式
MCP把一次交互抽象成三个原语,理解这三个词基本就掌握了MCP的使用面。
Tools是最常用的。它是一个可以被模型调用的外部功能,包含名称、描述、入参schema。模型通过tools/call调用它,执行后返回结构化内容。这个语义和function calling很像,但MCP的价值在于把schema定义和服务发现放在了协议层,客户端可以自动感知有哪些工具、怎么调。
Resources是数据源,解决“把外部数据按需喂给模型”的问题。它先描述资源,再按需读取,适合让Agent从知识库、数据库、文件系统里拉取上下文。
Prompts是一些预置的提示模板和交互流程,把用户意图映射成Agent可以执行的指令。典型场景是固定业务流程,比如“投诉处理”“退货退款”这类有固定步骤的对话。
三个原语对应的是不同场景的接入需求,很好记:要执行就用Tools,要读数据就用Resources,要流程引导就用Prompts。
| 原语 | 解决的问题 | 典型场景 |
|---|---|---|
| Tools | 让模型执行外部功能 | 调用API、执行命令、查询订单 |
| Resources | 把外部数据喂给模型 | 读知识库、数据库查询 |
| Prompts | 预置提示与交互流程 | 固定业务步骤的引导 |
架构上,MCP是标准的Client-Server模型。一个LLM应用作为Host,可以挂载多个MCP Client,每个Client连接一个MCP Server,Server就是工具和数据能力的提供方。传输层支持三类:Stdio适合本地子进程;HTTP+SSE适合远程服务;Streamable HTTP是更现代的方案,用一个HTTP端点同时支持流式和非流式通信。新项目尽量选Streamable HTTP,不要再用旧式SSE-only模式。
一个真实项目里常见的拓扑是:主Agent运行在应用层,通过MCP Client连接多个Server,分别对接CRM、搜索服务、内部知识库。工具调用走tools/call,数据读取走resources/read,权限和审批由Host层统一拦截。这种模式让“接入一个新工具”变成了“接入一个配置好的Server”,重复劳动大幅减少。
3.3 一个最小的MCP Server有多简单
实际写一个MCP Server,比大部分人想象中简单。下面的例子基于一个使用广泛的开源MCP SDK,注册一个查询天气的工具,核心代码不到二十行。
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; const server = new McpServer({ name: "weather-server", version: "1.0.0" }); server.registerTool( "query_weather", { city: { type: "string", description: "城市名称" } }, async ({ city }) => ({ content: [{ type: "text", text: `${city}: 晴, 18℃` }] }) ); const transport = new StdioServerTransport(); await server.connect(transport);这段代码跑起来之后,任意MCP Client都能自动发现这个Server有query_weather工具,看到它的schema,然后发起调用。SDK把JSON-RPC消息编解码、握手、方法分发全部处理掉了。
这个体验很重要。协议的价值不是让每个开发者手写协议实现,而是让接入变成“调SDK里的一个注册函数”。MCP能快速铺开,除了协议本身设计得当,更关键的是生态里快速出现了一批成熟的SDK和开箱即用的现成Server。新项目接一个Server往往只要几个小时,这种体验一传开,生态就滚起来了。
3.4 MCP不管Agent之间怎么对话
MCP再火,也回答不了“两个Agent如何协作”的问题。这是它最容易背上误解的地方。
MCP的通信角色天然不对称:一端是Host/Client,一端是Server,关系本质上是“消费者”和“提供者”。哪怕AB两个Agent互相都把对方当作MCP Server接入,它们也只是互相把对方当成工具来调,而不是对等的协作者。中间缺了“如何描述Agent身份”“如何形成任务闭环”“谁对最终结果负责”“任务中途如何协商”这些机制。
这些职责要交给A2A或ACP这类Agent协作协议来处理。一句话:MCP解决“连接工具”的问题,A2A/ACP解决“连接智能体”的问题,两者并不互相替代。
4. A2A与ACP:Agent之间的两种外交语言
A2A和ACP都面向Agent间协作,但两者对“一次协作”的建模方式不同。很多人把它们当成纯竞争关系,我更愿意把它们理解成具有不同侧重的协作范式。
4.1 A2A:从Agent Card到Task生命周期
A2A是目前由某搜索巨头联合一批厂商推动的Agent互联协议,设计目标很明确:让不同组织、不同技术栈的Agent能动态发现对方,并完成任务级协作。它的两个核心概念是Agent Card和Task。
Agent Card是一份公开的元数据,描述Agent的身份、能力、端点和通信方式。一个Agent要加入协作网络,先放出一张Agent Card,其他Agent看到Card就能判断“这个Agent能干什么事,我应该把什么任务交给它”。下面是一份极简示例。
{ "identifier": "agent-weather-001", "displayName": "天气助手Agent", "capabilities": { "tasks": { "streaming": true } }, "endpoints": [ { "url": "https://agent-service.example/a2a", "protocol": "a2a" } ] }A2A的另一个核心机制是Task。它不是简单“一个请求拿一个响应”,而是“委派一个任务并跟踪它走到完成”。Task有明确的状态机:提交、工作中、需要输入、完成、失败、取消。执行期间的中间结果和最终结果会以Message和Artifact形式返回,支持通过SSE流式推送状态更新。
这个模型对真实业务非常友好。比如主Agent预订差旅,向“酒店预订Agent”发起一个Task,对方逐步上报进度;中途需要补充乘客信息,状态切到“需要输入”,主Agent再请求用户补充。这种端到端任务闭环,比“调一个函数拿一个返回值”的表达能力强得多。
4.2 ACP:把Agent对话建模成状态机
ACP走的是另一条路线。它由某操作系统与云计算厂商主导,设计上更偏向“会话式”协作。
ACP将交互组织成Session和Turn。Session是一段持续对话的上下文容器,Turn是会话中的一轮交流。每个Turn都有明确的角色和消息类型,结构上天然支持多轮态势感知。一个大幅简化的ACP消息大概长这样。
{ "type": "message", "session_id": "session-001", "turn_id": 1, "role": "user", "payload": { "kind": "content", "content": {"type": "text", "text": "把会议室订在明天上午10点"} } }ACP同样具备Agent发现机制,但它的重点不是“任务完成状态机”,而是“对话过程控制”。它把Agent之间的通信看成一段可以控制、可以审计、可以中途插入干预的对话。在需要严格人机共治、过程审计的场景里,这种建模更顺手。
举个例子,两个Agent在协商过程中,人类需要随时插入一句“这里停一下”。在会话模型里,这就是一个普通的新Turn,很容易表达;换成任务模型,就得专门设计“请求人类输入”的中间状态,绕了一小段路。
4.3 为什么会有两套“外交语言”并存
A2A和ACP定位相近,却同时存在。这个现象值得认真复盘。
其一,发起方不同,设计哲学必然不同。A2A偏“任务契约”,强调异步、长时任务、跨组织协作;ACP偏“会话协议”,强调过程、状态、多轮对话的精确控制。一个更贴近工作流,一个更贴近对话协同。
其二,标准化初期,各方都想拿到定义权。通信协议是典型的“赢者通吃”领域,先铺开生态的一方,后来者就要付出巨大的替换成本。所以厂商不会轻易放弃自己的赛道,两个协议在相当长一段时间里会各自迭代。
其三,两者正在互相吸收。A2A的新版本已经加入更多会话控制能力,ACP也在扩展任务编排语义。短期看是双雄并存,长期大概率是其中一个占据主流,另一个通过兼容层继续存在。对应用侧来说,现在没必要死磕二选一,正确的做法是把Agent间协作逻辑封装在一个适配层里,让上层业务不直接绑定某一套协议。
5. 三套协议放在一张桌子上:对比与组合
把三个协议摊开之后,最需要做的是系统性对比,然后找到它们在真实架构里的位置。
5.1 一张表说清区别
下表是我在梳理模拟项目X时反复核对的结论,尽量把关注点压缩到核心维度。
| 协议 | 解决的层级 | 核心通信对象 | 消息组织方式 | 典型传输 | 生态成熟度 |
|---|---|---|---|---|---|
| JSON-RPC 2.0 | 消息承载层 | 任意客户端与服务端 | 请求-响应、通知、批处理 | HTTP/WebSocket/stdio/任意流 | 高,长期稳定 |
| MCP | 工具与数据接入层 | LLM应用与外部工具/数据源 | 能力发现、工具调用、资源读取 | stdio、SSE、Streamable HTTP | 高,工具生态快速扩展 |
| A2A | Agent协作层 | Agent与Agent | 任务生命周期、消息、产物 | HTTP、SSE | 中,头部厂商推动中 |
| ACP | Agent协作层 | Agent与Agent | 会话、回合消息 | HTTP、WebSocket | 中低,仍在演进 |
这张表最关键的信息不是“哪个更好”,而是MCP和A2A/ACP并不在同一层。MCP属于接入层,A2A/ACP属于协作层。这个区别决定了它们在不同项目里扮演不同角色,也决定了同时使用它们完全合理。
5.2 组合架构:MCP向上接入,A2A/ACP向外协同
在一个复杂多Agent系统里,三套协议完全可以各司其职。我在模拟项目X中的典型拓扑是这样的。
一个调度Agent作为入口,自己负责理解用户意图,通过MCP Client接入一组工具Server,比如内部订单系统、知识库、日历服务。与此同时,这个调度Agent通过A2A/ACP接口对外暴露一张Agent Card。其他团队部署的垂直Agent,比如客服Agent、数据分析Agent,可以直接向它派发任务或请求协作。
在这个架构里,MCP负责“向下”的工具接入,保证调度Agent有能力执行;A2A/ACP负责“平级”的Agent互联,保证各Agent能互相发现和分工。两条通信链路互不干扰,甚至可以在同一个进程里同时存在。
这里我要特别提醒一个容易混淆的点:A2A/ACP和“多Agent编排框架”不是一回事。编排框架承载的是“谁先跑、谁后跑、结果怎么融合”的控制流逻辑;A2A/ACP承载的是“双方如何描述自己、如何交换任务消息”的通信协议。实践中,可以用编排框架管理内部Agent的控制流,只在真正需要跨进程、跨组织互通的地方引入A2A/ACP。两者搭配而不是二选一,架构会干净很多。
5.3 协议泡沫:也要警惕过度标准化
话虽如此,我还是要泼一盆冷水。当前Agent协议领域已经出现了明显的“过度标准化”迹象。
当一个新概念成为热点,资本和注意力涌入,就会出现大量协议草案和标准组织。有的协议解决的是真实互操作痛点,有的纯粹是为了占坑。对应用团队来说,追逐每一个新协议是性价比极低的事情。协议本身不产生业务价值,只有它能帮我们消除跨系统互操作的重复劳动时,才值得被引入。
我判断一个协议值不值得跟进,主要看三件事:是否解决实际手工劳动;是否有多个相互独立的企业在真实使用;是否具备从点到面的生态扩展能力。三条里占两条以上,才认真投入。目前MCP三条全占,A2A占前两条,ACP还在争取第二条的路上。这套判断也直接影响了模拟项目X的方案取舍。
6. 实际工程里的选型与落地:从模拟项目X积累的经验
最后这部分,聊聊我在实际项目里验证过的选型逻辑和踩过的坑,属于“文档里不会写、跑一遍才发现”的经验。
6.1 什么项目适合什么协议
先给出一份我常用的选型判断。
| 项目形态 | 建议 |
|---|---|
| 单个Agent只需要接几个API/数据源 | 直接用MCP,别自己发明格式 |
| LLM应用想消费现成工具生态 | 直接对接MCP Client,现成Server很多 |
| 多个Agent在同一个进程内编排 | 优先用编排框架控制流,协议层不是重点 |
| 跨进程、跨团队部署的Agent互相调任务 | 用A2A,任务模型更贴近业务闭环 |
| 需要严格会话控制、人机共治、过程审计 | 考虑ACP的建模思路,必要时封装成适配层 |
| 需求不明确,先做概念验证 | 从MCP起步,再补一个Agent协作协议适配层 |
这套判断的核心逻辑其实就是四句话:接入用MCP,协作用A2A/ACP,控制流用编排框架,而JSON-RPC作为所有消息的统一底座。协议是“层”的选择,不是“一个替代另一个”的对立关系。
6.2 接入MCP最容易踩的四个坑
先说第一个坑:Transport选错。很多早期项目用的是SSE+HTTP GET的组合,它只适合单向事件流,双向交互会被迫开两个连接,部署和鉴权都比较别扭。现在的新项目应该优先选Streamable HTTP,一个端点搞定双向通信,后续扩展也省心。
第二个坑是工具Schema膨胀。工具一多,MCP返回的tools/list可能包含上百个方法,模型在执行时容易选错。这时候需要做工具分组、按需加载,甚至让模型先走一次“工具发现”再决定调用。把所有工具一股脑喂给模型,效果和prompt过长一样,决策质量反而下降。
第三个坑是认证和授权混在业务代码里。MCP本身把权限职责留给Host层,但很多团队把所有Server放在可信内网,不做鉴权。短期内没问题,一旦暴露到生产环境或者跨团队调用,认证问题会集中爆发。我在项目里做的处理是:每个Server独立签发token,Host统一管理凭证,调用层只关心业务,不接触敏感密钥。
第四个坑是版本漂移。MCP还在快速迭代,SDK版本和Server版本的兼容性并不总是一致。上线前要锁定SDK版本,还要对关键工具做回归测试,不能随手升级。我吃过一次亏:某个版本的SDK改了Transport握手逻辑,服务器端没同步更新,所有远程调用静默失败,排查了很久才发现是版本不匹配。
6.3 试跑A2A/ACP之后的几点体验
我在一个跨Agent演示系统里,同时跑了A2A和ACP的简化实现,有三点感受很直接。
第一,A2A的任务状态机落地时必须配合超时和心跳。真实Agent执行任务经常超过默认超时时间。如果协定里没有心跳机制,客户端很容易在长任务执行到一半时误判为失败。我在项目里给每条Task消息增加了进度字段和时间戳,接收方超过30秒没收到消息就会主动发起状态查询,这个改动直接解决了“任务明明在跑却报超时”的诡异问题。
第二,ACP的会话模型在中途插入人类干预时确实好用。实现“人类在Agent对话中喊停”这个需求,用ACP的Turn模型几乎不用额外设计;换成A2A的任务模型,则需要额外的input-required状态来兜底。如果你的产品特别重视人机共治,这个差异值得认真评估。
第三,Agent Card的发现机制更适合跨组织边界。在同一个团队、同一个进程内的多个Agent之间,用Agent Card交换反而显得重。团队内应用可以先用内部服务发现,把Agent Card留给对外边界场景。协议设计再先进,也要分场景使用,不然只是增加了无谓的复杂度。
6.4 别纠结协议,先把通信边界画清楚
最后说一句踩过坑之后才真正想明白的话:面对这么多协议,最开始要做的不是追新,而是先把自己系统里的通信边界画清楚。
先问自己四个问题:谁和谁需要直接通信?通信方向是纵向接工具,还是横向协同Agent?每次通信是短请求还是长任务?消息状态和失败由谁负责闭环?这四个问题回答完,选型基本就自己浮出来了——纵向接工具就找MCP,横向Agent协同再比较A2A和ACP,短请求直接用JSON-RPC也完全够。
我见过很多团队一上来就铺全套协议栈,结果大部分通信根本不需要协议化,做了不少重复建设。反过来,真正需要跨组织的Agent互操作时,自己发明一套格式又往往不够严谨。协议存在的意义不是让架构看起来很现代,而是省掉那层没必要的胶水代码。
我现在的做法很朴素:MCP管工具接入,A2A管外部Agent协作,内部控制流交给编排逻辑,JSON-RPC作为所有消息的统一底座。这套组合跑了几个月,改动成本明显低于早期“每个接口一套格式”的做法。先把边界画清楚,再谈协议选型,页面上那些花花绿绿的缩写就不会再让你焦虑了。