1. 从一次协议选型的争论说起
前阵子在一个技术群里,有人抛出一个问题:给多个智能体做互联,到底该用 JSON-RPC 自己糊一套,还是直接上 MCP,或者干脆等 A2A 生态成熟?底下吵了几十楼,有人说"协议都是虚的,能跑就行",也有人说"选错了协议后面全是债"。这个场景其实特别典型——Agent 通信协议这两年冒出来一大堆,JSON-RPC、MCP、A2A、ACP 轮番刷屏,很多人第一反应是"怎么又来个新协议",第二反应是"我到底该用哪个"。
我自己在过去一年多里,先后在几个不同规模的项目里踩过这块的坑:有小团队用裸 JSON-RPC 硬扛的,有接 MCP 做工具调用的,也有尝试过 A2A 做多智能体协作的。踩完之后最大的感受是:这些协议不是互相替代的关系,而是解决不同层次问题的。把它们当成"竞品"去比较,本身就是个误区。
这篇内容我想干一件事:把 JSON-RPC、MCP、A2A、ACP 这几个东西放回它们各自诞生的场景里,讲清楚每个协议到底在解决什么问题、为什么会长成现在这个样子、实际落地时哪些地方最容易翻车。不管你是刚接触 Agent 开发的新手,还是已经在做多智能体系统的老手,看完应该都能对"协议选型"这件事有个更清醒的判断。全文基于我自己的实操经验和公开资料的整理,涉及具体实现的部分会给出可复现的思路,但不会绑定任何特定厂商或平台。
先说结论性的判断,方便你带着框架往下读:JSON-RPC 是底座,MCP 是"模型到工具"的标准化接口,A2A 是"智能体到智能体"的协作语言,ACP 更像是特定生态里的一种协作约定。它们处在不同的抽象层,很多时候是要组合使用的,而不是二选一。
2. JSON-RPC:所有上层协议都绕不开的那块地基
2.1 为什么大家不约而同回到 JSON-RPC
要理解后面那一堆协议,得先理解 JSON-RPC。这东西其实一点都不新,它是个很朴素的远程调用约定:客户端发一个 JSON 对象,里面带上方法名、参数、请求 ID,服务端返回一个 JSON 对象,带上结果或者错误。就这么简单。
那为什么 MCP、A2A 这些新协议,底层传输几乎都选了 JSON-RPC 而不是 REST 或者 gRPC?我自己的理解是三个原因。
第一,Agent 场景的调用天然是"动作导向"而不是"资源导向"。REST 的哲学是围绕资源做增删改查,URL 是名词,方法是动词。但 Agent 干的事情是"帮我执行一个动作"——读文件、查数据库、调一个外部服务,这更像 RPC 而不是 REST。硬套 REST 会拧巴。
第二,双向通信和流式返回的需求。Agent 和工具之间经常需要服务端主动推送(比如进度、中间结果),JSON-RPC 配合 SSE 或者 WebSocket 很容易做双向,而 REST 天生是请求-响应单向的。
第三,实现成本极低。一个 JSON-RPC 的服务端,几十行代码就能跑起来,任何语言都有现成库。对于快速迭代的 Agent 生态来说,这个门槛太重要了。
我实测过一个最小的 JSON-RPC 服务端,用 Python 写大概是这样:
import json from http.server import BaseHTTPRequestHandler, HTTPServer class Handler(BaseHTTPRequestHandler): def do_POST(self): length = int(self.headers['Content-Length']) req = json.loads(self.rfile.read(length)) # req 结构: {"jsonrpc":"2.0","method":"...","params":{...},"id":1} result = dispatch(req["method"], req.get("params", {})) resp = {"jsonrpc": "2.0", "id": req["id"], "result": result} body = json.dumps(resp).encode() self.send_response(200) self.send_header('Content-Type', 'application/json') self.send_header('Content-Length', str(len(body))) self.end_headers() self.wfile.write(body)就这么点东西,一个能用的 RPC 端点就有了。这也是为什么很多团队一开始都选择"自己糊一套 JSON-RPC"——门槛实在太低。
2.2 裸用 JSON-RPC 的三个隐形坑
但"能跑"和"能维护"是两回事。我自己裸用 JSON-RPC 踩过的坑,主要集中在三个地方,而且这三个坑在后面讲 MCP、A2A 的时候会反复出现,因为那些协议本质上就是在填这些坑。
坑一:方法名和参数没有约定,全靠口头对齐。你写了个read_file,同事写了个readFile,第三个服务写了个file.read。跑起来才发现对不上。没有 schema 约束的 RPC,在多人协作里就是灾难。这也是为什么后来 MCP 要引入一套标准化的工具描述格式。
坑二:错误处理各写各的。JSON-RPC 规范里定义了错误码,但实际项目里大家要么不用,要么乱用。有的把业务错误塞进result里,有的直接抛 HTTP 500。结果就是客户端根本没法统一处理错误。我见过一个项目,光"文件不存在"这一种情况,就有三种不同的返回格式。
坑三:能力发现缺失。客户端怎么知道服务端支持哪些方法?裸 JSON-RPC 没有这个机制,只能靠文档。文档一过期,调用就挂。这个坑直接催生了 MCP 里的"能力协商"设计。
提示:如果你现在正在裸用 JSON-RPC,至少做两件事——把方法名和参数结构写进一份共享的 schema 文件,以及统一错误返回格式。这两件事花不了多少时间,但能省掉后面大量的扯皮。
2.3 从"能调用"到"能协作"的鸿沟
JSON-RPC 解决的是"一次调用怎么发出去、结果怎么收回来"。但 Agent 系统真正难的地方,从来不是单次调用,而是多个组件之间怎么协作。
举个具体的例子。你要做一个"帮我分析这份财报并生成图表"的 Agent。它需要:读文件、调 LLM 做理解、调代码执行环境画图、把结果写回文件。这四步里,每一步都是一个 JSON-RPC 调用,但问题在于——谁来决定调用顺序?谁来传递中间状态?工具怎么知道自己的输出要交给谁?
裸 JSON-RPC 对这些一无所知。它只是个搬运工,不负责编排。于是每个团队都得自己写一套编排逻辑,写完之后发现:这套逻辑换个项目又得重写。这就是"能调用"和"能协作"之间的鸿沟,也是 MCP、A2A 这些协议要填的坑。
3. MCP:把"模型调工具"这件事标准化
3.1 MCP 到底标准化了什么
MCP 全称是 Model Context Protocol,它要解决的问题非常聚焦:让模型(或者 Agent)能够以一种统一的方式发现和调用外部工具、读取外部资源。
在 MCP 出现之前,每个模型厂商、每个 Agent 框架都有自己的工具调用格式。你给 A 框架写的工具,搬到 B 框架就得重写一遍。MCP 的价值就在于把这层接口标准化了——工具怎么描述自己、怎么被调用、怎么返回结果,都有统一约定。
它底层用的还是 JSON-RPC,但在此之上加了三样关键东西:
- 能力协商:客户端和服务端连接时,先交换各自支持的能力,避免调用不支持的方法。
- 工具/资源/提示的标准化描述:每个工具用 JSON Schema 描述自己的输入参数,模型能直接读懂。
- 传输层的灵活性:既支持本地 stdio,也支持远程 HTTP+SSE。
我自己的体感是,MCP 最实用的地方在于工具描述标准化。以前写工具,得给每个框架写一份适配;现在写一次 MCP server,理论上所有支持 MCP 的客户端都能用。这个"写一次到处跑"的诱惑力,是它快速铺开的核心原因。
3.2 一个 MCP Server 的最小骨架
光说概念没意思,直接看结构。一个 MCP server 的核心是注册工具,然后处理调用请求。伪代码大概长这样:
# 注册一个工具 server.register_tool( name="query_database", description="执行只读 SQL 查询并返回结果", input_schema={ "type": "object", "properties": { "sql": {"type": "string", "description": "要执行的 SQL 语句"}, "limit": {"type": "integer", "default": 100} }, "required": ["sql"] } ) # 处理调用 @server.handler("query_database") def handle_query(params): sql = params["sql"] limit = params.get("limit", 100) # 这里做实际的查询逻辑 rows = db.execute(sql, limit=limit) return {"rows": rows, "count": len(rows)}注意input_schema这一段——它就是 MCP 的灵魂。模型看到这个 schema,就知道这个工具需要什么参数、每个参数是什么类型、哪些是必填的。没有 schema 的工具描述,模型只能靠猜,猜错的概率高得离谱。
3.3 MCP 落地时最容易翻车的四个点
MCP 概念很清晰,但真上手做,坑不少。我按踩坑频率从高到低排一下。
第一,工具粒度的把握。新手最容易犯的错是把工具做得太细,比如read_file、write_file、list_dir、delete_file各来一个。结果模型面对几十个工具,选择困难,调用链还特别长。我的经验是:按"用户意图"而不是"底层操作"来切分工具。比如与其给四个文件操作工具,不如给一个manage_file,用参数区分动作。当然也不能太粗,一个工具干十件事,schema 会复杂到模型读不懂。这个度需要根据实际场景调。
第二,错误信息的可读性。MCP 工具返回的错误,最终是要给模型看的。如果你返回一个{"error": "E5001"},模型完全不知道该怎么办。正确的做法是返回自然语言描述的错误,比如{"error": "SQL 语法错误:缺少 FROM 子句,请检查查询语句"}。这样模型才有机会自我修正。
第三,超时和长任务的处置。有些工具调用要跑几十秒甚至几分钟。MCP 支持流式返回进度,但很多人没实现,结果客户端直接超时。我的做法是:超过 5 秒的工具,一律实现进度上报,让客户端知道还在跑。
第四,权限边界。MCP server 一旦暴露出去,模型就能调它。如果这个 server 能执行任意 SQL、能读写任意文件,那就是个巨大的风险点。我见过有人把数据库管理权限直接挂上去,模型一个幻觉就DROP TABLE了。工具层面必须做权限收敛,只读的就别给写权限,能限定范围的就别开放全量。
注意:MCP 的定位是"模型到工具",它不负责工具之间的编排,也不负责多个 Agent 之间的协作。如果你需要的是后者,MCP 解决不了,得看 A2A。
4. A2A:当协作的主角从"工具"变成"智能体"
4.1 A2A 和 MCP 的根本区别在哪
很多人第一次听说 A2A 会懵:不是有 MCP 了吗,怎么又来一个?这俩到底啥关系?
我用一个类比解释。MCP 像是"你给一个员工配了一套标准化的工具箱",员工用这些工具干活。A2A 像是"多个员工之间怎么开会、怎么分工、怎么交接任务"。MCP 管的是人和工具,A2A 管的是人和人。
这个区别决定了它们的设计重心完全不同。MCP 关心的是"工具怎么描述、怎么调用",A2A 关心的是"任务怎么分解、怎么分配、状态怎么同步、结果怎么汇总"。
A2A 里几个核心概念值得单独拎出来:
- Agent Card:每个 Agent 对外暴露一张"名片",说明自己是谁、能干什么、怎么联系。这解决了"我怎么知道有哪些 Agent 可用"的问题。
- Task:A2A 里的协作单位是任务,不是单次调用。一个任务可以包含多个步骤、多次交互。
- 消息与工件:Agent 之间通过消息沟通,通过工件(artifact)传递产出物。
4.2 多智能体协作里,协议真正要解决的是什么
我做过一个多 Agent 协作的小项目,让三个 Agent 分别负责"检索资料""分析数据""撰写报告"。做完之后最大的体会是:协议本身不难,难的是状态管理和失败处理。
先说状态管理。三个 Agent 串行工作,中间状态怎么传?A2A 的答案是"任务"这个概念——任务有生命周期,从提交到完成,中间的状态变更都有明确语义。这比裸 JSON-RPC 里自己维护一个状态机要省心得多。
再说失败处理。检索 Agent 挂了怎么办?分析 Agent 拿到一半数据怎么办?A2A 里任务有明确的状态(进行中、需要输入、已完成、失败),失败的任务可以被重新分配或者降级处理。这套状态语义是多 Agent 协作能不能稳定的关键。
我踩过的一个具体坑:早期自己用 JSON-RPC 做多 Agent 协作,没定义任务状态,结果一个 Agent 卡住了,其他 Agent 还在傻等,整个流程僵死。后来引入类似 A2A 的任务状态机,才把这个问题解决。
4.3 A2A 的适用边界:不是所有场景都值得上
这里我要泼一盆冷水。A2A 不是万能的,很多场景根本不需要它。
如果你的系统只有一个 Agent 调几个工具,那 MCP 就够了,上 A2A 是过度设计。如果你的多个 Agent 是固定流程、固定角色,那用代码硬编码编排可能比 A2A 更简单直接。
A2A 真正有价值的场景是:Agent 数量多、角色动态、需要跨组织或跨系统协作。比如一个 Agent 市场,不同团队开发的 Agent 要能互相发现、互相调用,这时候标准化的 Agent Card 和任务协议就非常必要。
我自己的判断标准是:当"谁和谁协作"这件事在编译期无法确定时,才需要 A2A 这种运行期发现和协商的协议。如果协作关系是固定的,别给自己找麻烦。
5. ACP:生态内的协作约定,别和通用协议混为一谈
5.1 ACP 的定位需要说清楚
ACP 这个词在不同语境下指代不太一样,容易混淆。在 Agent 通信这个语境里,它通常指的是一种面向特定生态或特定场景的智能体协作约定,而不是像 MCP、A2A 那样试图做通用标准。
这个区别很重要。MCP 和 A2A 的野心是"成为行业标准",所以它们设计上要考虑通用性、扩展性、向后兼容。而 ACP 这类协议往往是为某个具体生态服务的,设计上更务实,可能牺牲一些通用性换取落地效率。
我接触过的 ACP 类实现,普遍有几个特点:协议更轻、约定更具体、和特定平台的耦合更深。好处是上手快、坑少;坏处是换生态就得重来。
5.2 什么时候该考虑 ACP 而不是 A2A
这个问题我被问过好几次。我的回答是:看你是否已经在一个特定生态里。
如果你是在某个平台内部做 Agent 协作,平台已经提供了 ACP 类的协作机制,那直接用就好,没必要自己再搭一套 A2A。因为平台内的协作,很多边界条件是已知的,通用协议的那些灵活性你根本用不上,反而增加了复杂度。
但如果你要做的是跨平台、跨组织的协作,那 ACP 这类生态绑定的协议就不合适了,因为对方不一定支持。这时候 A2A 这种通用协议的价值才体现出来。
一句话总结:ACP 是"在自家院子里怎么协作",A2A 是"和邻居怎么协作"。场景不同,选择不同。
5.3 协议选型的决策框架
讲了这么多,落到实操,我给一个自己用的决策框架。这个框架不追求绝对正确,但能帮你快速排除明显不合适的选项。
| 你的场景 | 优先考虑 | 理由 |
|---|---|---|
| 单 Agent 调多个工具 | MCP | 工具标准化是核心需求,MCP 最成熟 |
| 多 Agent 固定流程协作 | 代码编排 + JSON-RPC | 流程固定,通用协议是过度设计 |
| 多 Agent 动态发现协作 | A2A | 运行期发现和任务协商是刚需 |
| 平台内 Agent 协作 | 平台提供的 ACP 类协议 | 生态内协作,通用性用不上 |
| 跨组织 Agent 协作 | A2A | 需要通用标准,生态绑定协议不适用 |
这个表不是铁律,但能帮你避开最常见的选型错误——用通用协议解决特定问题,或者用特定协议解决通用问题。
6. 协议背后的设计哲学:为什么会有这么多"重复造轮子"
6.1 分层思维:每一层解决一类问题
回头看这几个协议,会发现它们其实构成了一个清晰的分层:
- 传输层:JSON-RPC,负责"消息怎么发出去、怎么收回来"
- 工具层:MCP,负责"模型怎么发现和调用工具"
- 协作层:A2A,负责"多个 Agent 怎么分工和同步"
- 生态层:ACP 类,负责"特定生态内怎么约定协作"
每一层解决一类问题,层与层之间是组合关系。你完全可以在 A2A 的协作框架里,让每个 Agent 通过 MCP 调用工具,底层传输用 JSON-RPC。这三者不冲突。
理解了分层,就不会再有"为什么这么多协议"的困惑了。它们多,是因为问题本身就有多个层次。试图用一个协议解决所有层次的问题,才是真正的错误。
6.2 标准化的代价与收益
每个协议的出现,本质上都是一次"标准化"的尝试。标准化有收益,也有代价。
收益很明显:降低协作成本、提高互操作性、形成生态。MCP 让工具写一次到处跑,A2A 让 Agent 跨系统发现,这些都是实实在在的价值。
代价也很明显:标准化意味着约束,约束意味着某些场景下不够灵活。我见过一些团队,为了"符合 MCP 规范",把本来很简单的工具描述搞得极其复杂,结果模型反而读不懂了。这就是标准化的代价——为了通用性牺牲了针对性。
我的经验是:在标准化的收益大于代价时才用标准,否则自己定约定反而更高效。一个三人小团队内部协作,自己定个简单的 JSON 约定就够了,上 MCP 是浪费。
6.3 我踩过的"过度标准化"的坑
说个具体的。之前有个项目,需求很简单:一个 Agent 调三个内部工具。我一开始想着"要规范",硬是搭了一套 MCP server,把三个工具都包装成标准 MCP 工具。结果呢?开发时间翻了三倍,调试复杂度上去了,而实际收益几乎为零——因为这三个工具只有这一个 Agent 用,标准化带来的"复用性"根本用不上。
后来我把它改回简单的函数调用,代码量少了 70%,维护也轻松了。这个教训让我明白:协议是为场景服务的,不是反过来。别为了用协议而用协议。
7. 实操建议:怎么在真实项目里做协议决策
7.1 从最小可用开始,别一上来就上全套
我的建议很直接:从最简单的方案开始,遇到瓶颈再升级。
具体路径可以是:
- 先用裸 JSON-RPC 或者直接函数调用,把核心逻辑跑通。
- 当工具数量超过 5 个、或者需要跨框架复用时,考虑引入 MCP。
- 当 Agent 数量超过 3 个、且协作关系动态变化时,考虑引入 A2A。
- 如果已经在某个平台生态里,优先用平台提供的协作机制。
这个路径的核心逻辑是:让需求驱动协议选择,而不是让协议选择驱动架构。我见过太多项目,一开始就搭了个"完美"的协议架构,结果需求一变,架构全废。
7.2 协议迁移的成本要提前算
如果你现在用的是裸 JSON-RPC,未来可能要迁到 MCP,那在设计时就要留好口子。我的做法是:把工具的业务逻辑和协议适配层分开。
业务逻辑就是纯粹的函数,输入参数、返回结果,不涉及任何协议细节。协议适配层负责把业务逻辑包装成 JSON-RPC 或者 MCP 工具。这样将来换协议,只需要重写适配层,业务逻辑不动。
这个分层看起来简单,但能省掉大量迁移成本。我做过一次从裸 JSON-RPC 到 MCP 的迁移,因为提前做了分层,实际改动量只有预期的三分之一。
7.3 几个容易被忽略的工程细节
最后分享几个实操中容易忽略但很关键的细节。
第一,版本管理。协议本身有版本,你的工具描述也有版本。工具 schema 一变,所有调用方都可能受影响。我的做法是给每个工具的描述加版本号,schema 变更时递增,客户端可以据此判断兼容性。
第二,可观测性。协议调用出了问题,怎么排查?必须有完整的日志:谁调了谁、传了什么参数、返回了什么、耗时多少。多 Agent 协作场景下,一个请求可能跨好几个 Agent,没有链路追踪根本查不出来。我一般会在请求里带一个 trace ID,贯穿整个调用链。
第三,限流和熔断。Agent 调用工具的频率可能很高,尤其是模型陷入循环的时候。没有限流,一个失控的 Agent 能把下游服务打挂。这个在 MCP server 层面就要做,别指望调用方自觉。
第四,幂等性。网络抖动导致重试是常态。如果工具调用不是幂等的,重试就会产生副作用。写操作类的工具,一定要支持幂等,比如通过请求 ID 去重。
提示:这四个细节,任何一个没做好,都可能在生产环境给你带来大麻烦。它们和协议选型无关,是任何 Agent 通信系统都要面对的工程问题。
8. 我个人的一点判断
聊了这么多协议,最后说点我自己的看法。
Agent 通信协议现在处于一个"春秋战国"的阶段,标准很多,但还没有一个能一统天下。这其实是好事——竞争会推动协议快速进化。MCP 和 A2A 现在看起来是两条主线,但未来会不会合并、会不会有新的协议出现,谁也说不准。
对开发者来说,我的建议是:别急着站队,把底层原理搞懂。协议会变,但"分层""标准化""状态管理""失败处理"这些底层问题不会变。你把 JSON-RPC 吃透,理解 MCP 为什么这么设计,看懂 A2A 的任务模型,将来不管出什么新协议,你都能快速上手。
我自己现在做项目,基本是"能用简单的就不用复杂的"。单 Agent 场景,MCP 加裸 JSON-RPC 足够;多 Agent 场景,先想清楚协作关系是不是真的动态,不是的话代码编排更省事。协议是工具,不是目的。把问题解决掉,比用上最新协议重要得多。
至于那些还在纠结"到底选哪个"的朋友,我的建议是:先动手做一个最小版本,跑起来再说。很多选型的纠结,在真正跑起来之后会自然消解——因为你会发现,真正难的地方根本不在协议选择上。