news 2026/10/14 6:41:27

Agent通信协议选型指南:JSON-RPC、MCP、A2A与ACP的分层实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent通信协议选型指南:JSON-RPC、MCP、A2A与ACP的分层实践

1. 为什么 Agent 通信协议突然“百花齐放”

过去一年里,只要你在做 AI Agent 相关的开发,几乎绕不开一个困惑:同样是让两个智能体互相说话,怎么一会儿是 JSON-RPC,一会儿是 MCP,转头又冒出 A2A、ACP?更让人头大的是,每个协议背后都有一拨人在推,文档写得都挺像那么回事,但真到选型落地的时候,你会发现它们解决的问题压根不在一个层面上。

我自己是从最原始的“手搓 HTTP 接口”开始做 Agent 协作的。最早那会儿,两个 Agent 要交换信息,无非就是约定一个 URL,POST 一段 JSON 过去,对面解析完再返回一段 JSON。简单粗暴,能跑就行。但项目一复杂,问题就来了:工具怎么暴露给模型?上下文怎么在多个 Agent 之间传递?一个 Agent 调用另一个 Agent 的能力时,怎么描述“我能干什么”?这些事靠裸 HTTP 加自定义 JSON 结构,维护成本高得离谱。

于是协议开始分层。JSON-RPC 是底座,它只规定“怎么调用一个远程方法”,不关心你调用的是工具、是模型还是另一个 Agent。MCP(Model Context Protocol)解决的是“模型怎么发现和调用外部工具与数据源”,它把工具、资源、提示词模板标准化,让模型侧不用为每个工具写一套适配。A2A(Agent-to-Agent)则更进一步,解决的是“Agent 之间怎么互相发现、协商、委派任务”,它假设双方都是有一定自主性的智能体,而不是单纯的函数。ACP(Agent Communication Protocol)走的是另一条路,强调消息传递的可靠性和会话状态管理,更像是一个面向 Agent 的消息中间层。

这篇文章不打算给你背协议规范,而是想把这几个协议为什么会长成这样、各自卡在哪个位置、实际选型时怎么权衡讲清楚。如果你正在做多 Agent 系统、工具调用平台,或者只是好奇“我到底该用哪个”,那这篇应该能帮你省下不少翻文档的时间。

2. 从 JSON-RPC 说起:所有 Agent 通信的“最小公约数”

2.1 JSON-RPC 到底规定了什么,没规定什么

JSON-RPC 本质上就是一个极简的远程调用约定。一个请求长这样:

{ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "search_web", "arguments": { "query": "agent protocol" } }, "id": 1 }

响应就是:

{ "jsonrpc": "2.0", "result": { "content": "..." }, "id": 1 }

它只干了三件事:方法名、参数、请求 ID。至于 method 叫什么、params 里放什么、result 怎么解析,全靠双方自己约定。这就是它最大的优点,也是最大的坑。

优点在于极度灵活。你可以用它调一个本地函数,也可以调一个远程服务,甚至可以用它传文件流(base64 塞进去就行)。缺点在于,没有语义层。当你的 Agent 要调用一个工具时,它怎么知道有哪些工具可用?每个工具需要什么参数?参数类型是什么?这些 JSON-RPC 一概不管。你得自己再写一套描述文件,或者硬编码在代码里。

我早期做过一个多 Agent 协作的小系统,当时就是用 JSON-RPC 做底层,上面自己定义了一套tool_list、tool_describe、tool_call的方法名。跑是能跑,但每接一个新工具,就要改一次描述逻辑,而且不同 Agent 之间的描述格式还不统一。那段时间我最大的感受就是:JSON-RPC 是很好的传输层,但它不是协议层。

2.2 为什么 Agent 场景下 JSON-RPC 不够用

Agent 和传统微服务最大的区别在于:调用方本身是概率性的。传统微服务里,A 服务调 B 服务,接口是确定的,参数是确定的,返回也是确定的。但 Agent 调用工具时,它是在“理解”工具描述之后,自己决定要不要调、怎么调、传什么参数。这就要求工具描述本身必须是机器可读、语义清晰的。

JSON-RPC 没有能力表达“这个工具是干什么的”“参数有什么约束”“返回结果是什么结构”。你当然可以在 params 里塞一个 JSON Schema,但那只是把问题往后推了一层:谁来生成这个 Schema?谁来保证不同 Agent 对同一个 Schema 的理解一致?

另一个问题是能力发现。在多 Agent 系统里,一个 Agent 需要知道其他 Agent 能做什么。JSON-RPC 没有服务发现机制,你得自己维护一个注册中心,或者硬编码地址列表。这在 Agent 数量少的时候还行,一旦超过十个,维护成本就指数级上升。

所以 JSON-RPC 的定位很明确:它是通信的底座,不是 Agent 协作的完整方案。MCP、A2A、ACP 这些协议,本质上都是在 JSON-RPC(或类似传输层)之上,补上了语义层、发现层和会话层。

3. MCP:让模型“看见”工具的那一层

3.1 MCP 解决的核心问题:工具描述的标准化

MCP 最核心的贡献,是把“工具”这个东西抽象成了模型可以理解的标准结构。一个 MCP Server 暴露出来的工具,长这样:

{ "name": "get_weather", "description": "查询指定城市的当前天气", "inputSchema": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称" } }, "required": ["city"] } }

模型看到这段描述,就知道有个叫get_weather的工具,需要传一个city参数。这看起来简单,但它是整个工具调用链路能跑通的前提。没有这个标准,每个模型厂商都要为每个工具写一套适配,工具开发者也要为每个模型写一套接口。

MCP 还定义了资源(Resources)和提示词模板(Prompts)。资源让模型可以读取外部数据,比如文件、数据库记录;提示词模板让模型可以复用预定义的指令。这三者加起来,构成了模型与外部世界交互的完整接口。

3.2 MCP 的传输层选择与实操细节

MCP 本身不强制传输层,但最常见的实现是stdio和HTTP+SSE。stdio 适合本地工具,比如你写一个 Python 脚本,通过标准输入输出和模型通信。HTTP+SSE 适合远程工具,模型通过 HTTP 发请求,工具通过 SSE 推事件。

我实测下来,stdio 的延迟最低,但调试麻烦,因为你看不到网络包。HTTP+SSE 调试方便,但要注意 SSE 的连接保持和重连逻辑。如果你用 Python 写 MCP Server,官方 SDK 已经帮你处理了大部分细节:

from mcp.server import Server from mcp.server.stdio import stdio_server app = Server("my-tool-server") @app.list_tools() async def list_tools(): return [ { "name": "search", "description": "搜索网页", "inputSchema": { "type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"] } } ] @app.call_tool() async def call_tool(name, arguments): if name == "search": return {"content": [{"type": "text", "text": "搜索结果..."}]}

这段代码的关键在于list_tools和call_tool两个钩子。模型侧会先调list_tools拿到工具列表,然后根据用户意图决定调哪个,再通过call_tool执行。整个过程对模型来说是透明的,它不需要知道工具是本地脚本还是远程服务。

注意:MCP 的inputSchema必须用 JSON Schema 描述,而且 description 字段要写得足够清晰。我踩过的坑是,description 写得太模糊,模型会反复调同一个工具但传错参数。后来我把每个参数的 description 都写成“什么时候该传这个值”,调用准确率明显提升。

3.3 MCP 的边界:它不解决 Agent 之间的协作

MCP 的定位很清晰:它是模型与工具之间的协议。但它不解决 Agent 与 Agent 之间的通信。比如你有两个 Agent,一个负责规划,一个负责执行,它们怎么互相传任务?MCP 没有定义这个。你当然可以让规划 Agent 把执行 Agent 当成一个“工具”来调用,但这会丢失很多语义:执行 Agent 的自主性、任务状态的同步、多轮协商的过程。

这就是 A2A 要解决的问题。

4. A2A:当 Agent 开始互相“对话”

4.1 A2A 的核心抽象:Agent Card 与任务生命周期

A2A 最关键的创新是Agent Card。每个 Agent 对外暴露一张卡片,描述自己是谁、能做什么、怎么联系:

{ "name": "research-agent", "description": "擅长文献检索和摘要", "capabilities": ["search", "summarize"], "endpoint": "https://example.com/a2a", "authentication": { "type": "bearer" } }

这张卡片让其他 Agent 可以“发现”它。发现之后,A2A 定义了一套任务生命周期:提交任务、接受任务、更新状态、返回结果。这比简单的“调用-返回”要复杂得多,因为 Agent 之间的任务往往不是一次性的,而是需要多轮交互。

比如规划 Agent 给研究 Agent 发一个任务:“帮我查一下最近三个月关于 Agent 协议的论文,总结成表格。”研究 Agent 可能先返回“已接受”,然后过一会儿返回“正在检索”,再返回“找到 15 篇,正在摘要”,最后返回结果。这个过程中,规划 Agent 可以随时查询状态,甚至取消任务。

A2A 的传输层通常也是 HTTP+JSON-RPC,但它在上层定义了更丰富的消息类型:Task、TaskStatus、Artifact、Message。这些结构让 Agent 之间的交互有了明确的语义。

4.2 A2A 与 MCP 的关系:互补而非竞争

很多人第一次看到 A2A 和 MCP 会懵:这俩不是都在讲 Agent 通信吗?其实它们解决的是不同层面的问题。

维度MCPA2A
通信对象模型 ↔ 工具Agent ↔ Agent
核心抽象Tool、Resource、PromptAgent Card、Task、Artifact
交互模式请求-响应任务生命周期、多轮协商
自主性工具无自主性Agent 有自主性
典型场景模型调用搜索、数据库多 Agent 协作完成复杂任务

我自己的做法是:底层用 MCP 接工具,上层用 A2A 做 Agent 协作。规划 Agent 通过 A2A 把任务委派给执行 Agent,执行 Agent 内部再用 MCP 调用具体工具。这样分层之后,每层职责清晰,调试也方便。

实操心得:A2A 的 Agent Card 一定要版本化。我遇到过 Agent 升级后能力变了,但卡片没更新,导致其他 Agent 还在按旧能力发任务。后来我在卡片里加了version字段,并且要求每次能力变更必须递增版本号,问题就解决了。

4.3 A2A 的落地难点:状态同步与超时处理

A2A 最麻烦的地方在于状态同步。因为 Agent 任务是异步的,规划 Agent 发出任务后,不能一直阻塞等待。它需要轮询或者订阅状态更新。A2A 支持 SSE 推送,但 SSE 连接不稳定,断线重连后状态可能不一致。

我的做法是:状态查询接口必须幂等,并且每次返回都带一个last_updated时间戳。规划 Agent 本地维护一个任务状态缓存,收到推送时更新,断线重连后先拉一次全量状态,再继续订阅。这样即使推送丢了,也能通过轮询兜底。

超时处理也很关键。A2A 任务可能跑很久,规划 Agent 不能无限等。我一般会设置三级超时:接受超时(30 秒内没返回“已接受”就重试)、心跳超时(5 分钟没状态更新就查询一次)、总超时(30 分钟没结果就取消)。这三层超时加上重试策略,基本能覆盖大部分异常情况。

5. ACP:被低估的“消息中间层”

5.1 ACP 的定位:可靠消息传递与会话管理

ACP 的知名度不如 MCP 和 A2A,但它在某些场景下更实用。ACP 的核心是消息队列 + 会话状态。它不关心消息内容是工具调用还是 Agent 任务,它只保证消息可靠送达,并且维护会话的上下文。

你可以把 ACP 理解成 Agent 世界的消息总线。一个 Agent 发消息到某个主题,其他 Agent 订阅这个主题。消息可以持久化,可以重放,可以保证顺序。这对于需要高可靠性的场景很重要,比如金融、医疗领域的 Agent 协作。

ACP 的消息格式通常包含:会话 ID、发送者、接收者、消息类型、负载、时间戳。会话 ID 让多轮对话可以关联起来,消息类型让接收者知道怎么解析负载。

5.2 ACP 与 A2A 的选型对比

A2A 和 ACP 都能做 Agent 间通信,但侧重点不同。

维度A2AACP
核心关注任务委派与生命周期消息可靠传递与会话
发现机制Agent Card主题订阅
交互模式任务导向消息导向
状态管理任务状态机会话上下文
适用场景多 Agent 协作事件驱动、高可靠
复杂度较高中等

我的经验是:如果你的 Agent 协作是任务驱动的,选 A2A;如果是事件驱动的,选 ACP。比如一个监控 Agent 发现异常,发消息给处理 Agent,处理 Agent 再发消息给通知 Agent,这种链式反应用 ACP 更自然。而如果是“帮我写一份报告”这种任务,拆解成多个子任务委派出去,用 A2A 更合适。

5.3 ACP 的实操要点:消息幂等与死信队列

ACP 落地时,消息幂等是必须处理的。因为消息可能重复投递,接收 Agent 必须能识别重复消息。我一般会在消息里加一个message_id,接收方维护一个已处理 ID 的集合,重复的直接丢弃。

死信队列也很重要。如果一条消息处理失败多次,不能一直重试,要丢到死信队列里人工介入。我见过一个系统因为死信队列没配,一条坏消息把整个 Agent 集群堵死了。后来加了死信队列,并且设置了最大重试次数,问题才解决。

注意:ACP 的会话状态不能无限增长。我一般会设置会话过期时间,比如 24 小时没活动的会话自动清理。否则内存会被慢慢吃光。

6. 协议选型实战:我如何为一个多 Agent 系统做决策

6.1 需求拆解:先搞清楚你要解决什么问题

选协议之前,先问自己三个问题:

  1. 通信双方是谁?模型和工具?Agent 和 Agent?还是 Agent 和消息总线?
  2. 交互模式是什么?请求-响应?任务委派?事件驱动?
  3. 可靠性要求多高?允许丢消息吗?需要持久化吗?

这三个问题的答案,基本能锁定协议范围。比如:

  • 模型调工具 → MCP
  • Agent 委派任务 → A2A
  • Agent 事件通知 → ACP
  • 底层传输 → JSON-RPC 或 HTTP

6.2 一个真实项目的协议组合方案

我之前做过一个多 Agent 研究助手系统,架构是这样的:

  • 工具层:用 MCP 接入了搜索、PDF 解析、数据库查询三个工具。
  • Agent 层:规划 Agent、检索 Agent、摘要 Agent、审核 Agent。
  • 协作层:规划 Agent 通过 A2A 把任务委派给其他 Agent。
  • 通知层:审核 Agent 发现问题时,通过 ACP 发消息给规划 Agent。

这个组合跑下来,最大的感受是分层清晰。MCP 让工具接入标准化,A2A 让任务委派有状态可查,ACP 让异常通知不丢。如果全用 JSON-RPC 硬搓,代码量至少翻三倍,而且维护起来很痛苦。

6.3 选型检查清单

检查项问题推荐协议
通信对象模型调工具MCP
通信对象Agent 调 AgentA2A
通信对象事件广播ACP
交互模式请求-响应JSON-RPC
交互模式任务生命周期A2A
交互模式发布-订阅ACP
可靠性允许丢消息JSON-RPC
可靠性不允许丢消息ACP
状态管理无状态JSON-RPC
状态管理有状态会话ACP
状态管理任务状态机A2A

这张表不是绝对的,但能帮你快速缩小范围。实际选型时,往往是组合使用,而不是二选一。

7. 常见问题与排查技巧实录

7.1 协议兼容性问题

问题:MCP Server 返回的工具描述,模型解析不了。

排查:先检查inputSchema是否符合 JSON Schema 规范。我遇到过required字段写成字符串而不是数组,导致模型解析失败。另外,description 里不要用特殊字符,有些模型对 Unicode 支持不好。

解决:用 JSON Schema 校验工具先验证一遍,确保格式正确。description 尽量用英文或简单中文,避免生僻字。

7.2 A2A 任务状态不一致

问题:规划 Agent 认为任务还在进行,执行 Agent 已经完成了。

排查:检查状态更新是否丢失。A2A 的 SSE 推送在弱网环境下容易丢。

解决:加轮询兜底。规划 Agent 每隔一段时间主动查询一次任务状态,以查询结果为准。同时,执行 Agent 的状态更新要带版本号,避免旧状态覆盖新状态。

7.3 ACP 消息重复消费

问题:同一个消息被处理了两次,导致重复操作。

排查:检查消息是否有唯一 ID,接收方是否做了幂等处理。

解决:消息里加message_id,接收方用 Redis 或本地缓存记录已处理的 ID,设置合理的过期时间。处理前先查 ID 是否存在,存在就跳过。

7.4 协议选型速查表

问题现象可能原因排查方向解决方案
模型调错工具工具描述模糊检查 description写清楚使用场景
任务状态不同步SSE 丢推送检查网络和重连加轮询兜底
消息重复处理无幂等设计检查消息 ID加去重逻辑
会话内存泄漏会话未过期检查过期策略设置 TTL
工具调用超时工具执行慢检查工具性能加超时和重试
Agent 发现失败Agent Card 未更新检查卡片版本版本化卡片

最后分享一个小技巧:不管用哪个协议,日志一定要打全。我一般会在每个协议层都加一个 trace_id,从模型发起调用到工具返回结果,全链路可追踪。这样出问题时,能快速定位是哪一层挂了。踩过几次坑之后,我发现排查时间至少缩短了一半。

这个领域还在快速演进,协议本身也在迭代。我的建议是:先跑通最小闭环,再逐步引入协议分层。不要一上来就追求大而全,那样很容易被协议细节淹没。先用 JSON-RPC 把链路跑通,然后根据实际痛点引入 MCP、A2A 或 ACP,这样每一步都有明确的收益。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 6:41:03

OpenCV DNN人脸检测实战:模型选型、预处理与后处理全链路解析

1. 这不是“调个API就完事”的人脸检测——OpenCV DNN模块的真实能力边界你是不是也见过这样的教程?标题写着“5行代码实现高精度人脸检测”,点进去就是cv2.dnn.readNet()加net.forward(),再套个cv2.rectangle()画框,最后配张检测…

作者头像 李华
网站建设 2026/10/14 6:40:48

位1的个数:从逐位检测到SWAR并行计数的优化全解

1. 题目拆解与学习价值"位1的个数"这道题,算法圈里一般叫 Popcount 或 Hamming Weight,讲人话就是:给你一个非负整数,你数一数它的二进制表示里有多少个 1。比方说数字 11,二进制是 1011,那答案就…

作者头像 李华
网站建设 2026/10/14 6:40:48

Rovio源码解读:无回环的EKF视觉惯性里程计

简介:rovio-master源码包是一份基于扩展卡尔曼滤波的视觉惯性里程计(VIO)实现,面向SLAM算法研究者和机器人开发者,用于在未知环境中完成实时定位与建图,尤其适合无人机、自动驾驶等计算资源受限的嵌入式平台…

作者头像 李华
网站建设 2026/10/14 6:40:23

【单片机毕设案例分享】基于STM32的婴儿床远程监控与摇篮联动装置设计 基于单片机的智能婴儿摇篮远程监护与报警系统设计(032703)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于单片机,STM32单片机,51单片机,J…

作者头像 李华
网站建设 2026/10/14 6:40:11

浏览器内置大模型实战:零成本实现网页文案优化建议

1. 浏览器内置大模型到底是个什么东西1.1 从"调API"到"用浏览器自带能力"的转变过去两年,给网页加AI能力几乎只有一条路:申请某个云端大模型的API Key,然后在前端或后端发请求。这条路能跑通,但问题也很明显—…

作者头像 李华