把”调用工具”和”协同智能体”混在一起,是当下 AI Agent 落地里最常见的认知误区。MCP(Model Context Protocol)解决的是”Agent 怎么连到数据源和工具”这条纵向链路,而 A2A(Agent-to-Agent)则专攻”多个 Agent 怎么互相发现、分工、交付”这条横向链路。两者不是替代关系,而是同一座智能体网络里的两根承重柱:MCP 负责把单个 Agent 武装到牙齿,A2A 负责让武装好的 Agent 组成队伍。下面把它们拆开看,重点是工程团队最关心的”什么时候该用谁”。
一、两个协议各自的定位差异
要理解 A2A,必须先理解它”不做什么”。A2A 不关心一个 Agent 内部如何调用数据库、如何解析文件、如何调 REST API——那是 MCP 的活。A2A 只关心:当 Agent A 需要 Agent B 的能力时,A 怎么知道 B 在哪、能干啥、怎么把任务递过去、又怎么把结果拿回来。
| 维度 | MCP | A2A |
|---|---|---|
| 提出方 | Anthropic | Google 联合 50+ 生态伙伴 |
| 协议层级 | Agent ↔ 工具 / 数据源 | Agent ↔ Agent |
| 通信模式 | Host-Client-Server 状态化会话 | Client-Remote Agent 任务委派 |
| 传输层 | JSON-RPC 2.0,STDIO / HTTP SSE | JSON-RPC 2.0,HTTP / SSE |
| 核心原语 | Tools / Resources / Prompts | Agent Card / Task / Artifact |
| 典型使用 | 调一次数据库、读一份文件 | 跨厂商派一个长周期任务给另一个 Agent |
IBM 的技术资料里给了一个很直观的例子:零售商的库存 Agent 用 MCP 查库、读 SKU,发现某商品缺货后,再用 A2A 通知外部供应商的订单 Agent 去补货。这条链路里 MCP 干的是”读”,A2A 干的是”派活”。
二、A2A 的核心工作机制
A2A 的设计哲学是”基于现有 Web 标准做增量”,避免另起炉灶。它跑在 HTTP / SSE / JSON-RPC 2.0 之上,对熟悉 REST 的工程师几乎没有学习成本。整条任务链分三步推进:
- 发现:客户端 Agent 通过对方域名下的
/.well-known/agent.json拉取 Agent Card,决定该 Remote Agent 能否处理这个任务; - 鉴权:按 Agent Card 声明的认证方案(如 API Key、OAuth 2.0、mTLS)完成身份校验,授权由服务端 Agent 负责;
- 通信:通过 JSON-RPC 2.0 发起
SendMessage,服务端以 Task 对象推进生命周期,必要时回input-required反问客户端。
2.1 Agent Card:智能体时代的”简历”
Agent Card 是一段托管在固定 URL 的 JSON 描述文件,作用类似 LLM 的 Model Card,但描述的是 Agent 本身。它至少包含名称、版本、接入端点、技能清单(skills)、支持的能力开关(streaming、pushNotifications)、安全方案(securitySchemes)。客户端不需要预先知道任何关于对方栈的信息,读完这张”简历”就能决定要不要把任务派过去。
2.2 Task 生命周期:让长任务有”户口”
A2A 把每一次委派建模为一个有状态的 Task 对象,状态机是确定性的:submitted → working → input-required / completed / failed / canceled。这一步是 A2A 与 MCP 在工程上明显的体感差异:MCP 工具调用偏短事务,A2A 任务可以跨越小时甚至天,并允许人类在中间环节介入(input-required 即”问你一句”)。结果产物叫 Artifact,由 Part 组成,支持文本、文件、JSON、流式媒体多种形态。
下面这段是 A2A 客户端在拉取完 Agent Card 之后,发起任务并轮询完成的最小代码骨架:
import httpx, time
BASE = "https://data-agent.example.com"
# 1. 拉 Agent Card
card = httpx.get(f"{BASE}/.well-known/agent.json").json()
print(card["name"], "skills:", [s["id"] for s in card["skills"]])
# 2. 发起任务
task = httpx.post(f"{BASE}/a2a/tasks", json={
"method": "SendMessage",
"params": {
"message": {"role": "user", "parts": [
{"type": "text", "text": "分析附件里的 CSV 并产出统计摘要"}
]},
"blocking": False
},
"id": 1
}).json()
task_id = task["result"]["id"]
# 3. 轮询直到 completed / failed
while True:
state = httpx.get(f"{BASE}/a2a/tasks/{task_id}").json()["result"]["status"]["state"]
if state in ("completed", "failed", "canceled"):
break
time.sleep(2)
# 4. 取出 Artifact
artifact = httpx.get(f"{BASE}/a2a/tasks/{task_id}/artifact").json()
print(artifact)
效果与注意点:A2A 任务通常横跨多个服务,轮询要带退避;如果 Agent Card 声明 streaming: true,应改用 SendStreamingMessage 走 SSE,能省掉一半延迟。Auth 头必须从 securitySchemes 里推断,不要在客户端硬编码。
三、什么时候用 A2A,什么时候用 MCP
落地时一种常见的反模式,是拿 A2A 当 MCP 用——让 Agent 互相”调用”,结果陷入 N×N 的发现泥潭;或者反过来,让 MCP 工具去”对话”,结果工具只能返回单次结果,无法表达”我需要你确认一下”。
| 场景 | 选谁 | 理由 |
|---|---|---|
| 让 Agent 读一次数据库 | MCP | 短事务、状态化、Tools 即可 |
| 让 Agent 调一个 REST API | MCP | 工具能力,原生支持 |
| 跨厂商派一个长任务 | A2A | 异步、Artifact、人类可介入 |
| 多 Agent 协同做规划 | A2A | 任务状态机支持多轮协商 |
| Agent 内部组合多个工具 | MCP | Client-Server 适合做工具编排 |
3.1 互补的典型组合
真实的 Agent 团队通常会把两者叠起来用:每个 Agent 内部用 MCP 武装(接数据库、接 API、接文件系统),Agent 之间用 A2A 协作(一个 Planner Agent 派活给 Researcher Agent,Researcher 完成后用 Artifact 交付)。这样每个 Agent 都保持”上下文干净”——委派方不需要把全部中间状态塞给执行方,正是 Google 在 A2A 文档里强调的隐私与上下文卫生。
四、两个协议的形象比喻
如果用网络分层来类比,MCP 更像”USB-C”:解决”设备 ↔ 外设”的物理与协议接口问题,让同一根线接键盘、硬盘、显示器都不用换。A2A 更像”TCP/IP”:解决”主机 ↔ 主机”的端到端通信问题,不管对方跑的是 Linux 还是 Windows、Python 还是 Go,都能彼此发现、彼此握手。USB-C 不替代 TCP/IP,TCP/IP 也不替代 USB-C——它们解决的是不同距离上的连接问题。理解这一点,工程团队就不会在”该用哪个协议”的问题上反复摇摆。
五、选型时的两个常见误区
第一,把 A2A 当作 MCP 的”升级版”。A2A 出现在 MCP 之后不意味着它取代 MCP——两者由不同生态主导、解决不同问题。第二,忽略生态适配成本。MCP 已经有 TypeScript、Python、Java、Kotlin、C#、Go 等多语言 SDK,Agent 框架接入成本低;A2A 目前主要由 Google 及 50+ 早期合作伙伴推动,跨语言 SDK 与参考实现仍在补齐中,工程团队要预留适配窗口。
第三,混用传输层引发一致性塌方。MCP 走 STDIO 假设 Server 与 Client 同生命周期,一旦跨网络就要加心跳、鉴权与多路复用;A2A 任务天然异步,客户端必须实现轮询或 SSE 订阅,不能用同步 RPC 直接替代。把这些传输层细节当成”实现小事”,是上线后第一波故障的常见来源。
常见问题(FAQ)
Q1:MCP 和 A2A 必须二选一吗?
不是。生产系统通常同时用:MCP 武装单个 Agent,A2A 串联多个 Agent。
Q2:A2A 的 Agent Card 必须放 /.well-known/agent.json 吗?
路径是惯例以便自动发现,但只要客户端能拉到等价的元数据即可。
Q3:A2A 任务能中途取消吗?
可以。Task 状态支持 canceled,客户端调用 CancelTask 即可优雅终止。