做AI应用开发这几年,我每次在技术评审会上看到Agent相关的架构图,都会下意识数一数上面画了多少种协议。这是个很真实的困惑:为什么Agent通信协议这么多?JSON-RPC、MCP、A2A、ACP,名字一个比一个绕,光听概念就觉得头大。
这篇文章不打算把协议文档翻译成中文,而是想从“需求倒推”的角度,把这几个协议放在同一张坐标图里,理清楚它们各自解决什么问题、边界在哪里、项目里到底该怎么选。你可以把它理解成一篇“协议选型前的摸底笔记”,适合正在做Agent应用、正在纠结要不要引入某个协议、或者只是被各种缩写搞得晕头转向的朋友。
1. 先弄清楚:一套Agent系统里到底在“通什么信”
很多人在协议上纠结,本质上是没搞清楚一个问题:消息到底要从哪里出发,要到哪里停。一套Agent系统里,通信场景远不止“客户端发请求、服务端返回结果”这一种。
1.1 三条通信边界:应用、模型、工具、Agent各说各话
我把日常见到的Agent项目抽象一下,通信其实发生在三条边界上。
第一条是应用与模型服务的边界。比如你写了一个网页应用,前端要向后端AI服务发起一次对话请求,后端把结果流式返回给前端。这是最传统的“应用↔服务”通信,谁做都绕不开,常见方案就是HTTP加流式响应。
第二条是模型与外部工具的边界。模型在推理过程中,需要查数据库、调搜索接口、执行一段脚本,这就产生了“模型↔工具”之间的通信需求。早期大家都是靠业务代码自己封装,每个工具写一套调用方式,模型根本无法统一对接。
第三条是Agent与Agent之间的边界。一个Agent负责拆解任务,另一个Agent负责执行计算,第三个Agent负责生成报告。它们之间怎么互相发现、怎么传递中间结果、怎么同步任务状态,这是“Agent↔Agent”的协作通信。
这三条边界,天然指向不同的协议诉求。只是早期大家都把通信混在一起做,才会出现“一个项目一堆自定义接口”的混乱状态。
1.2 协议扎堆出现的根本原因:场景边界在裂变
通信协议不会凭空增多,它增多只有一种原因:通信的对象变了。最初,大家只需要一份API文档就能完成前后端联调。当LLM开始参与系统设计后,消息的一方变成了“模型”,它的交互模式是推理式的、流式返回的、需要按批次执行工具的,这跟传统的请求响应模型不一样,于是需要定义新的协议。
再往后,Agent的形态分化出“工具型Agent”“协作型Agent”“平台型Agent”,分别对应上面三条边界。每个领域都开始有人尝试制定标准——你做一个“模型怎么调工具”的标准,我做一个“Agent怎么协作”的标准,他做一个“客户端怎么连Agent服务”的标准。于是JSON-RPC、MCP、A2A、ACP就走到了台前。
这个发展路径,跟前端工程化的演进非常像。一开始大家只需要写HTML;后来JS越来越多,产生了模块加载问题;再后来状态管理、路由、打包器各自解决一个细分问题,才会出现React、Vue、Webpack这些工具并存。协议也是同理,不是越多越乱,而是分工越来越细。
2. 四个协议逐个拆解:它们到底干了什么
下面我把最常提到的四个协议,按照从底层到上层的顺序逐一拆开看。先说明一点:它们不是同一条赛道上的竞争者,更像是高速公路的不同路段。
2.1 JSON-RPC:几乎被所有上层协议继承的“共同语法”
JSON-RPC 2.0是一个非常轻量的远程调用协议,核心思想就一句话:把一次函数调用描述成一条JSON消息。它规定了几种消息结构:请求带一个id,响应带相同的id,通知没有id、也不需要响应,错误有统一编码。
{ "jsonrpc": "2.0", "id": 1, "method": "add", "params": [1, 2] }{ "jsonrpc": "2.0", "id": 1, "result": 3 }我一开始不太理解为什么上层协议都愿意用JSON-RPC,直到自己写了一遍才发现原因。它的优点有三个:一是和语言无关,任何语言都能解析JSON;二是实现极简单,不用像REST那样去纠结资源路径、状态码语义;三是它只描述“调用”,不夹带任何业务概念,所以可以作为所有上层协议的地基。
MCP的底层消息格式就是JSON-RPC 2.0,A2A的消息结构也保留了jsonrpc字段。你可以这样理解:JSON-RPC是语法规则,它规定了句子怎么说;至于句子说什么业务、在什么场景下说,它一律不管。
要注意,JSON-RPC不解决状态管理,也没有会话、鉴权、工具发现这些概念。它是“透明”的,上层需要什么,就在它的基础上扩展什么,这也是它能成为公共底座的真正原因。
2.2 MCP:给模型世界开一扇标准化窗口
MCP全称是Model Context Protocol,它解决的是上面说的第二条边界:模型与外部工具、数据源如何标准化的接口对接。MCP的定位很明确——让模型的上下文世界能连接任意工具和数据。
MCP的核心架构是Host、Client、Server三层。Host是LLM应用本身,Client负责与Server建立连接,Server负责暴露工具、资源、提示词。理解它最简单的模型是:MCP Server就是一个“工具箱”,模型按统一的协议去翻工具箱,找到合适的工具,然后用统一格式调用。
MCP定义了三种原语:Tools,可执行的工具调用,模型可以在推理中主动触发;Resources,可读取的数据对象;Prompts,预置的提示词模板。这三种原语正好覆盖了Agent接入外部能力的大部分诉求。
import datetime from mcp.server import Server from mcp.server.transport import stdio_server app = Server("calc-demo") @app.tool() def add(a: int, b: int) -> int: """两数相加,返回整数结果。适合做加法运算。""" return a + b @app.tool() def today() -> str: """返回今天的日期字符串,格式YYYY-MM-DD。""" return datetime.date.today().isoformat() if __name__ == "__main__": import asyncio async def main(): async with stdio_server() as (read_stream, write_stream): await app.run( read_stream, write_stream, app.create_initialization_options(), ) asyncio.run(main())MCP在设计上刻意做了一件事:底层使用JSON-RPC,但把工具结构、初始化握手、传输封装都标准化。这样工具提供方只需要实现一次Server,就能被所有支持MCP的客户端复用。我在实际体验里的感受是,MCP真正解决的是“碎片化”:你不用再为每个工具写一套专属SDK,模型侧只需要理解一套协议。
它也有自己的适用范围。MCP更适合“模型复用工具”的场景,比如Agent内部去查数据库、调用业务服务、读取文档。它并不直接解决多个Agent之间的协作问题——两个Agent互相派活、同步进度,那不是MCP的主场。
2.3 A2A:为Agent之间的长时间协作而设计
A2A全称是Agent2Agent,它解决的是第三条边界:Agent与Agent之间如何发现彼此能力、如何协同完成任务。它和MCP的关注点有明显不同,MCP是“模型→工具”,A2A是“Agent→Agent”。
A2A里有个核心设计叫Agent Card。每个Agent会公开一份JSON格式的能力描述,里面写清楚这个Agent能处理什么任务、它的通信端点在哪里、需要什么鉴权方式。另一个Agent可以通过获取AgentCard来“发现”对方的能力。这种设计思路很接近服务发现,只是这里被发现的不是微服务,而是有推理能力的Agent。
消息模型方面,A2A引入了Task的概念。一次协作会被建模成一个Task,Task下面有Message和Artifact。Message是Agent之间往来交流的信息,Artifact是任务产出物,比如一份报告、一段代码、一个计算结果。
{ "jsonrpc": "2.0", "id": "req-001", "method": "message/send", "params": { "agentCardPath": "https://agent.internal.example/agent.json", "taskId": "task-20240630-001", "messages": [ { "messageId": "msg-001", "role": "user", "content": [ { "type": "text", "text": "请把本周销售数据整理成摘要" } ] } ] } }A2A采用了一种“任务中心”的通信模型,也就是Task-Centric。它不是简单地把消息丢给另一端就结束,而是围绕任务状态去管理整个交互:任务创建、状态更新、结果产出、错误处理、取消操作。这一点我后来想通了——Agent之间的协作,天然需要长期运行、需要阶段性确认、需要处理失败重试,如果只做消息投递,根本没法支撑复杂协作。
相比MCP,A2A会更像“面向Agent的工作流协议”,它引入了任务状态机、回调、流式更新等概念,也更适合跨系统、跨组织的Agent协作场景。
2.4 ACP:把会话、流式、工具调用统一进一个连接
ACP,全称Agent Client Protocol,它和前面几个协议的切入点又不一样。ACP更关注“客户端怎么跟一个Agent服务建立并维持一条可持续的会话通道”。它解决的是对话型Agent接入的体验问题,重点在连接层。
我对ACP的理解是基于一个真实场景:你做一个IM机器人,或者做一个Agent原生客户端,用户会持续跟Agent对话,对话里可能会穿插多次工具调用,工具执行结果又要流式地返回给客户端。传统做法是把聊天接口和工具调用接口分开设计,还要自己搞websocket的心跳、重连、消息id匹配,很繁琐。
ACP的思路是把这些统一进一条连接里。它维护一个会话状态,连接一旦建立,服务端知道你是谁,知道你当前在哪个会话里,还能识别同一连接里的消息上下文。在这条连接上,既可以发送普通文本消息,也可以触发Agent内部的工具调用,工具执行的事件又能实时回流给客户端。它把“会话、流式、工具调用”揉在了同一个模型里。
可以这样对比:MCP是模型和工具间的桥梁,A2A是Agent和Agent间的协作协议,ACP更像是面向“终端用户↔Agent服务”的接入协议。它的核心假设是客户端会长期在同一会话里跟Agent交互,所以连接模型远比请求响应模型重要。
3. 它们是什么关系,以及怎么选型
搞清楚单个协议之后,最关键的问题来了:它们是怎么配合的,项目里怎么选?
3.1 分层视角:语法层、接入层、协作层、会话层
我用一张表格把四个协议按层次放好,方便大家对照:
| 协议 | 所属层次 | 核心解决对象 | 典型交互 |
|---|---|---|---|
| JSON-RPC | 语法层 | 消息的通用格式与调用语义 | 请求、响应、通知 |
| MCP | 接入层 | 模型与外部工具、数据源的标准化连接 | 工具发现与调用 |
| A2A | 协作层 | Agent与Agent之间的任务协同与能力发现 | 任务创建、状态同步 |
| ACP | 会话层 | 客户端与Agent服务的持续会话连接 | 流式消息、多轮会话 |
这四层并不互斥,反而可以叠加使用。一个Agent服务对外可以通过ACP做接入,内部用MCP接工具,同时通过A2A去跟其他Agent协作,所有消息底层都可以是JSON-RPC格式。协议叠起来用的时候,边界反而更清楚了。
我见过不少项目把MCP和A2A混在一起讨论,其实想清楚就一句话:你的Agent内部要不要调工具?要,就走MCP。你的Agent要不要跟别的Agent协作?要,再考虑A2A。两个问题的答案不冲突,因为解决的是不同层的事情。
3.2 选型决策表:什么场景选什么协议
我把常见的几种项目场景整理成一份决策表,方便直接抄作业:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 单应用内,让LLM调用自定义工具和内部数据 | MCP | 统一工具接入规范,模型侧只学一套协议 |
| 已有内部服务,只想做轻量RPC调用 | JSON-RPC直连 | 实现成本最低,不做额外抽象 |
| 多个Agent需要跨模块协同、任务流转 | A2A | 标准的任务模型、能力发现,适合协作 |
| 做面向外部用户的Agent产品,需要持续会话 | ACP或自定义WS协议 | 会话、流式、工具调用一体化,体验好 |
| 既有Agent工具复用的需要,又有跨系统协作的需要 | MCP + A2A | 内部工具归一化,对外协同标准化 |
选型有个朴素原则:不要为了协议而协议。如果只想实现两个服务间相互调用,直接JSON-RPC就够用了,没必要引入完整框架。反而是当你发现“接口碎片化”成为瓶颈时,再上MCP或A2A,效果会明显得多。
我自己的习惯是画一张通信边界图,把每个通信方向标出来,再逐条问自己:这一条通信里,消息方是模型还是Agent还是用户?它对状态、流式、长时间运行有没有要求?答案明确后,选型基本不会跑偏。
3.3 一个最小可跑的组合示例
演示一个最简单却完整的组合:在A2A的Agent服务内部,用MCP定义工具,让模型在响应协作请求时真正调用外部的计算服务。
第一步,按前面2.2的示例代码起一个MCP Server,暴露add和today两个工具。第二步,在A2A服务里维护一个Task,当收到协作请求时,转发给内部的模型,再通过MCP Client调用工具。第三步,把工具结果封装成Artifact,写到Task的产出物里。
流程上几个关键点需要敲定:任务状态要维护(working、completed、failed)、调用工具的结果要缓存、客户端轮询或回调要能拿到最终状态。如果只是演示,可以直接用一个内存字典保存Task状态,配合定时轮询。生产环境则要好好设计任务存储,避免服务重启后任务状态丢失。
4. 实操中的典型坑与排查方法
协议本身不复杂,真正让开发者头疼的是接入细节。我把实操中踩过的坑整理成几个典型问题,希望你能少走点弯路。
4.1 初始化握手没做对,后面全白搭
MCP这类协议一般都有初始化流程。客户端必须先发initialize请求,完成能力协商,之后才能正常调用工具。我见过很多“工具调用没反应”的问题,最后排查下来都是客户端没有先初始化,或者服务端没等初始化完成就接收了业务请求。
排查方法很简单:看日志里有没有initialize这步。没有,就先补握手;有,就检查服务端返回的capabilities是否和工具类型匹配。如果客户端声明不支持某项能力,服务端完全可以拒绝后续请求。
另外,请求ID的配对也很容易出错。JSON-RPC规定请求和响应用同一个id关联,有些实现里通知消息不带id,如果客户端错误地给通知也发了id,服务端可能误判为普通请求而返回result,两边状态就对不上了。
4.2 工具描述写太差,模型根本不知道怎么调用
这是MCP里面特别隐蔽的坑。模型决定是否调用某个工具,完全靠工具的name和description来理解。description写不清楚,或者参数schema含糊,模型要么不调用,要么乱填参数。我在实际测试中发现,描述从“两数相加”改成“输入两个整数,返回它们的和,适合计算总数、金额合计等场景”,调用准确率会明显提升。
所以,工具描述要满足两个原则:第一,说清楚触发场景;第二,参数类型和约束要写具体。
# 不推荐 add(a, b): 加法 # 推荐 add(a: int, b: int) -> int: 输入两个整数,返回它们的和。 当用户需要计算购物总额、统计数量总和、合并数值时使用。工具不是写给人看的,是写给模型看的。写工具描述的思路,要改成“站在模型的角度描述这个工具在什么场景下值得被调用”。这个观念转过来之后,工具被正确调用的概率会大幅提升。
4.3 轮询、回调、流式:任务状态管理的三种陷阱
A2A这种任务型协议,最常见的问题就是任务状态卡在working不动。排查方向有几个:任务执行进程是否还活着;回调地址是否被网络策略拦截;轮询接口是否有幂等性设计。
幂等这一点容易被忽略。任务系统要确保同一个taskId的重复消息不会产生两个相同任务,否则一次网络重试就会导致重复执行。我建议在任务日志里记录taskId、触发源和幂等标识,重复请求直接返回原状态,而不是新建任务。
流式返回也会引入风险。如果服务端对单个请求有超时限制,就尽量用“先返回任务id,再异步推送状态”的方式,避免长任务因为网关超时被掐断。我遇到过好几次“任务其实执行完了,但客户端因为连接超时没拿到结果”的情况,后来统一改成回调加兜底轮询,问题才彻底解决。
4.4 鉴权与安全性容易被忽略的两个点
第一点,很多内部Agent服务觉得“都是内网调用,不用校验”。实测下来,内网工具一旦被某个测试页面误调用,可能导致脏数据或额度被消耗。最简单的处理是给MCP Server加一个调用方校验,验证请求头或JSON-RPC的参数里是否包含约定的token。
第二点,AgentCard或能力描述文件里不应暴露过多的内部信息。A2A的AgentCard是用于能力发现的,它本身就是给外部看的,所以端点地址、支持的鉴权方式可以公开,但任何内部密钥、数据库连接串都不应该出现在里面。对外的Agent服务,还要给所有工具调用做一层审批或白名单控制。
鉴权这部分,我的经验是宁可先做一个简单的token校验,也不要裸奔上线。协议本身没有强制要求鉴权方式,你可以在上层自由实现,但它必须存在。
5. 我目前的实践结论
5.1 先MCP、再A2A,最后才考虑ACP
如果从零开始做一个Agent项目,我不会让团队一上来就铺开所有协议。我的习惯是按照演进路径走:第一阶段,只有一个Agent,核心诉求是复用内部工具,直接引入MCP,把工具接入标准化;第二阶段,当出现多个Agent需要配合时,再引入A2A,用AgentCard和Task模型把协作串起来;第三阶段,如果要做对外产品,需要考虑客户端持续会话体验,这时再看ACP有没有必要。
这个顺序背后的逻辑是:先解决单点效率,再解决多点协作,最后解决接入体验。每走一步都有明确的触发条件,避免“为了技术而技术”。实际项目里,很多团队连MCP都还没用好,就急着引入一堆协议,结果反而增加了沟通和维护成本。
A2A的普及依赖Agent生态的成熟度。如果协作对方还没支持它,强行引入只会增加适配成本。我的做法是在接口层做抽象,内部可以先自己实现一套轻量的任务状态管理,等生态成熟后,再平滑迁移到标准协议。这个思路也适合MCP前期——先用起来,再逐步规范化。
5.2 协议增多不是坏消息,是专业分工的信号
回到文章开头的问题:为什么Agent通信协议这么多?答案其实已经很明显了——因为它们服务的场景不一样。JSON-RPC是通用语法,MCP管模型与工具的衔接,A2A管Agent之间的任务协作,ACP管终端会话接入。它们不是内卷,而是各管一段。
我目前最深的体会是,协议本身不是复杂度来源,边界模糊才是。我见过最痛苦的架构,不是选错了协议,而是什么协议都想要,却说不清每一条通信线上走的是谁的消息、解决的是什么问题。只要先把通信边界画清楚,再看哪个协议贴合那个边界,你会发现这些协议并不冲突,甚至可以在同一套系统里各司其职。
最后再分享一个小技巧:把每个协议的核心关键词写在架构图的通信线上,比如“工具调用:MCP”“Agent协作:A2A”“接入会话:JSON-RPC+流式”。哪条线上没有标签,哪里就是还没想清楚的地方。等所有线都有标签了,架构图也就不会再有讨论不清的协议问题了。