一次讲清 A2A 协议与 MCP 边界:从 Agent Card 到 Task 生命周期
**文章摘要:**A2A 和 MCP 分别解决什么问题?本文以 A2A v1.0 为基线,从调度 Agent 调用天气 Agent 的完整链路出发,讲清 Agent Card 发现机制、Message 与 Part、Task 生命周期、Artifact 交付、各种 ID、JSON-RPC、gRPC、HTTP+JSON 三种绑定,以及轮询、流式与 Webhook 更新方式;同时对比 A2A 与 MCP 的职责边界,补充安全要求、旧版迁移陷阱和生产落地检查清单。
当一个 Agent 同时连接天气、机票、酒店、支付等能力时,我们很容易产生一个疑问:这些能力究竟应该作为工具接入,还是应该拆成能够自主协作的专业 Agent?
这正是 MCP 与 A2A 的分界线:
text
Agent 调用工具、资源和 API → MCP
Agent 委派目标给另一个 Agent → A2A
两者不是竞争关系。一个常见架构是:调度 Agent 通过 A2A 把目标委派给天气 Agent,天气 Agent 再通过 MCP 使用天气 API 或数据库。
本文以截至 2026 年 8 月 21 日的 A2A 正式规范 1.0.0 为基线。需要特别注意:规范发布版本写作 1.0.0 ,但请求头和 Agent Card 中用于协议协商的版本是 1.0。
一、A2A 到底解决什么问题
A2A 全称 Agent2Agent Protocol,是面向独立 Agent 系统的互操作协议。
它关注的是:两个可能由不同团队、不同框架和不同厂商实现的 Agent,怎样在不了解对方内部模型、Prompt、Memory 和工具的前提下完成协作。
A2A 提供五类核心能力:
- 能力发现:远程 Agent 是谁、会什么、在哪里调用;
- 消息交换:传递文本、文件、二进制和结构化数据;
- 任务跟踪:管理长任务和多轮交互的生命周期;
- 结果交付:用结构化 Artifact 交付正式产物;
- 异步更新:支持轮询、流式连接和 Webhook 推送。
最容易记住的心智模型是:
text
Agent Card 负责认识对方
Message 负责交流
Task 负责跟踪工作
Artifact 负责交付结果
A2A 不负责定义 Agent 内部如何推理,也不是 LangGraph、CrewAI、ADK 一类 Agent 开发框架。它只规定跨系统边界时的公共语言。
二、一个最直观的协作场景
假设用户提出目标:
找出未来三天西雅图天气最好的一天,再查询那一天飞往纽约的机票。
系统可以拆成三个角色:
text
用户
↓
调度 Agent:理解目标、拆分任务、汇总结果
├─ A2A → 天气 Agent
│ └─ MCP → 天气 API
└─ A2A → 机票 Agent
└─ MCP → 航班系统
这里的关键不是"远程服务也能返回天气",而是天气 Agent 可以自主理解天气目标、选择数据源、进行多步处理、要求补充地点或日期,并以可跟踪任务的形式交付结果。
如果只需要调用一个参数明确的天气查询函数,那么 MCP Tool 往往已经足够;如果委派的是一个需要自主规划和多轮协作的目标,A2A 更合适。
三、理解 A2A v1.0 的三层结构
A2A 不能简单理解成"某种 JSON-RPC API"。v1.0 可以分成三层:
| 层次 | 解决的问题 | 典型内容 |
|---|---|---|
| Canonical Data Model | 双方交换什么对象 | Agent Card、Message、Part、Task、Artifact |
| Abstract Operations | 可以执行哪些动作 | SendMessage、GetTask、CancelTask |
| Protocol Bindings | 动作怎样映射到网络 | JSON-RPC、gRPC、HTTP+JSON |
三层关系如下:
text
A2A 领域语义
├─ 数据模型:Message、Task、Artifact......
└─ 抽象操作:SendMessage、GetTask......
↓ 选择一种 Binding
JSON-RPC / gRPC / HTTP+JSON
↓
网络与安全机制
也就是说,JSON-RPC 只是一种绑定方式,不等于 A2A 本身。
四、第一步:怎样发现另一个 Agent
"发现 Agent"其实包含两个不同阶段。
1. 先找到候选 Agent
调度 Agent 首先要知道可能存在哪些 Agent。常见来源有:
- 配置文件或环境变量;
- 企业内部服务目录;
- 平台私有 API;
- Curated Registry,即受治理的 Agent 注册中心;
- 已知的公开域名。
A2A v1.0 没有规定一个全球统一的"搜索全部 Agent"接口,也没有规定 Registry 必须使用什么注册和查询 API。
2. 再读取 Agent Card
已经知道某个候选域名后,Client 可以访问标准地址:
text
https://{agent-server-domain}/.well-known/agent-card.json
注意,这个地址只能回答"该域名上的 Agent 是谁、会什么",不能自动扫描网络找到所有 Agent。
3. Agent Card 包含什么
Agent Card 可以理解成 Agent 的"能力说明书 + 接入说明书"。一个简化示例如下:
json
{
"name": "Weather Agent",
"description": "查询指定城市的未来天气",
"supportedInterfaces": [
{
"url": "https://weather.example.com/a2a",
"protocolBinding": "HTTP+JSON",
"protocolVersion": "1.0"
}
],
"version": "2.3.0",
"capabilities": {
"streaming": true,
"pushNotifications": false
},
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain", "application/json"],
"skills": [
{
"id": "weather-forecast",
"name": "天气预报",
"description": "查询城市未来七天的天气",
"tags": ["weather", "forecast"]
}
]
}
阅读 Agent Card 时,重点检查:
| 字段 | 需要回答的问题 |
|---|---|
supportedInterfaces[] |
调用地址、Binding 和协议版本是什么 |
skills[] |
Agent 具体能完成哪些任务 |
capabilities |
是否支持流式、Push 等能力 |
| 输入输出模式 | 支持哪些 Media Type |
| 安全要求 | 使用 OAuth、API Key、mTLS 还是其他方案 |
version |
Agent 自身版本,不是 A2A 协议版本 |
一个 Agent 可以声明多种 Binding。supportedInterfaces[] 按偏好排序,Client 选择自己支持的第一项。
五、第二步:用 Message 发送请求
选定接口后,Client 调用抽象操作 SendMessage。
如果选择 HTTP+JSON Binding,请求可以写成:
http
POST /message:send HTTP/1.1
Host: weather.example.com
Content-Type: application/a2a+json
A2A-Version: 1.0
Authorization: Bearer <token>
{
"message": {
"messageId": "msg-001",
"role": "ROLE_USER",
"parts": [
{
"text": "查询西雅图未来三天的天气",
"mediaType": "text/plain"
}
]
},
"configuration": {
"acceptedOutputModes": ["application/json"],
"returnImmediately": false
}
}
如果选择 JSON-RPC Binding,A2A 领域对象基本不变,只是外面套上 JSON-RPC 信封:
json
{
"jsonrpc": "2.0",
"id": "rpc-001",
"method": "SendMessage",
"params": {
"message": {
"messageId": "msg-001",
"role": "ROLE_USER",
"parts": [
{"text": "查询西雅图未来三天的天气"}
]
}
}
}
两份请求表达的是同一个 A2A 操作。一次调用只会选择其中一种 Binding,不会同时发送两份请求。
六、Part:消息和产物的最小内容单元
Part 可以出现在 Message 或 Artifact 中。每个 Part 必须且只能包含以下四种内容字段之一:
| 字段 | 用途 |
|---|---|
text |
文本内容 |
raw |
内联二进制,JSON 中使用 Base64 |
url |
文件或内容地址 |
data |
任意 JSON 结构化数据 |
例如:
json
{"text": "天气晴朗", "mediaType": "text/plain"}
json
{"data": {"highC": 24, "condition": "sunny"}, "mediaType": "application/json"}
json
{
"url": "https://example.com/report.pdf",
"filename": "report.pdf",
"mediaType": "application/pdf"
}
filename、mediaType 和 metadata 是辅助字段,不是新的内容类型。
七、Message、Task 与 Artifact 有什么区别
SendMessage 的响应不一定是 Task,也可以直接是 Message。
| 对象 | 适用场景 |
|---|---|
| Message | 简单回复、补充信息、澄清问题和状态说明 |
| Task | 需要状态、历史、追加输入、取消或异步更新的工作 |
| Artifact | Task 的正式交付物,如报告、文件或结构化结果 |
可以把一次有状态工作理解成:
text
Message:请查询天气
↓
Task:正在查询,需要跟踪
↓
Message:请补充城市
↓
Task:继续执行
↓
Artifact:正式天气结果
一个已完成的 Task 可以是:
json
{
"task": {
"id": "task-001",
"contextId": "ctx-001",
"status": {
"state": "TASK_STATE_COMPLETED"
},
"artifacts": [
{
"artifactId": "forecast-001",
"name": "未来三天天气",
"parts": [
{
"data": {
"city": "Seattle",
"condition": "sunny",
"highC": 24
},
"mediaType": "application/json"
}
]
}
]
}
}
正式任务结果应该进入 Artifact,而不是只藏在一条状态 Message 中。
八、四种 ID 不要混淆
| ID | 作用 | 通常由谁创建 |
|---|---|---|
JSON-RPC id |
关联一次 RPC 请求和响应 | RPC 调用方 |
messageId |
唯一标识一条 Message | Message 创建方 |
Task id |
标识一个有状态工作单元 | A2A Server |
contextId |
把相关 Message 和 Task 归入同一交互上下文 | 通常由 Agent 生成 |
artifactId |
标识 Task 中的某个交付物 | Artifact 创建方 |
第一次请求通常省略 taskId 和 contextId。服务端返回后,Client 保存这些不透明标识,并在后续交互中复用。
如果 Task 尚未结束且需要补充信息,可以同时携带 taskId 和 contextId;如果一个新 Task 要引用旧 Task,可以使用 referenceTaskIds。
九、Task 生命周期
Task 状态可以分成三组:
text
处理中
TASK_STATE_SUBMITTED
TASK_STATE_WORKING
暂停但可以继续
TASK_STATE_INPUT_REQUIRED
TASK_STATE_AUTH_REQUIRED
终态
TASK_STATE_COMPLETED
TASK_STATE_FAILED
TASK_STATE_CANCELED
TASK_STATE_REJECTED
典型状态流转为:
text
SUBMITTED → WORKING → COMPLETED
├→ INPUT_REQUIRED → WORKING
├→ AUTH_REQUIRED → WORKING
├→ FAILED
└→ CANCELED
进入 INPUT_REQUIRED 后,Client 可以补充消息继续执行;进入终态后,该 Task 不再接受新消息。如果还要修改或扩展结果,应创建新 Task,并保留必要的上下文或旧任务引用。
十、长任务怎样获取进度
A2A 提供三种互补机制:
| 机制 | 工作方式 | 适用场景 |
|---|---|---|
| Polling | Client 定期调用 GetTask |
简单集成、更新不频繁 |
| Streaming | 通过 SSE 或对应 Binding 持续接收事件 | 交互式 UI、实时进度、增量产物 |
| Push Notification | Server 主动请求 Client 的 Webhook | 超长任务、离线通知、不保持长连接 |
SendMessageConfiguration.returnImmediately 决定普通发送是否立即返回:
false或不填:等待到 Task 进入终态或暂停态;true:先返回已创建的 Task,再用轮询、流式或 Push 跟踪。
使用流式能力前,需要确认 Agent Card 的 capabilities.streaming 为 true。网络断开后,还可以对未终止 Task 使用 SubscribeToTask 重新订阅。
Push Notification 不能只关注"能否回调成功",还必须防止 SSRF、伪造通知、重复投递和重放攻击。
十一、JSON-RPC 与 A2A 的边界
最简单的理解是:
JSON-RPC 规定远程方法怎样封装,A2A 规定 Agent 之间有哪些对象、操作和任务语义。
JSON-RPC 只认识 method、params、id、result 和 error。它并不知道什么是 Agent、Task、Artifact 或任务生命周期。
A2A 则定义:
SendMessage、GetTask、CancelTask等抽象操作;- Agent Card、Message、Part、Task、Artifact 等领域对象;
- 状态、多轮上下文、异步更新和错误语义;
- 上述操作怎样映射到 JSON-RPC、gRPC 和 HTTP+JSON。
可以类比为:JSON-RPC 是信封格式,A2A 是信里的业务表单和办理规则。
十二、A2A 与 MCP 的核心边界
| 维度 | A2A | MCP |
|---|---|---|
| 主要交互对象 | 独立 Agent 或 Agent 系统 | Tool、Resource、Prompt 等能力 |
| 任务粒度 | 委派一个目标 | 调用明确能力或读取资源 |
| 自主程度 | Remote Agent 可自行规划、调用工具 | Client 选择具体 Primitive |
| 状态特点 | 支持长任务、多轮、状态和异步更新 | 通常是边界清晰的请求与结果 |
| 能力发现 | Agent Card 与 Skills | Tools、Resources、Prompts 列表与 Schema |
| 正式交付 | Task 与 Artifact | Primitive 的调用结果 |
| 内部实现 | Remote Agent 保持不透明 | Client 明确知道 Server 暴露的能力 |
做选型时可以问三个问题:
1. 调用方是在指定"步骤"还是委派"目标"
- "执行
get_weather(city)"更像 MCP Tool; - "比较未来三天天气并选出最适合出行的一天"更像 A2A Task。
2. 被调用方是否需要自主规划
如果被调用方只执行一个明确函数,不必包装成 Agent;如果它需要自主拆解、选择工具、处理中断并交付结果,更适合暴露 A2A。
3. 是否需要跨团队、跨框架或跨信任边界
同一进程内的简单子 Agent 未必需要 A2A。A2A 更适合独立部署、内部实现不透明、需要稳定协议边界的 Agent 系统。
最常见的组合是:
text
调度 Agent
└─ A2A → 天气 Agent
├─ MCP Tool → 天气 API
└─ MCP Resource → 历史天气数据
十三、生产环境必须补齐的安全能力
A2A 面向跨 Agent、跨系统甚至跨组织协作,安全不能只依赖 Prompt。
至少应做到:
- 使用 HTTPS,并验证服务端身份;
- 从 Agent Card 读取认证要求,通过带外流程获得凭据;
- 凭据放在 Header 或 Binding Metadata 中,不塞进 Message 文本;
- 按调用者、用户、Skill、Task、数据和具体业务动作授权;
- Task 查询和列表只返回当前身份有权访问的内容;
- 对输入、输出和日志执行数据最小化;
- 为高风险业务动作增加人工确认、幂等和审计;
- 公共 Agent Card 不暴露密钥和敏感内部能力;
- 必要时使用认证后的 Extended Agent Card;
- 对 Agent Card 做缓存、刷新,并在提供签名时验证 JWS。
Remote Agent 内部"不透明",只表示实现封装,并不意味着它天然可信或安全。
十四、旧教程最容易踩的版本坑
A2A 在 v1.0 前经历过明显变化。阅读旧视频和旧文章时,重点检查:
| 旧写法 | v1.0 写法 |
|---|---|
/.well-known/agent.json |
/.well-known/agent-card.json |
| 把 A2A 等同于 JSON-RPC | JSON-RPC 只是标准 Binding 之一 |
message/send |
JSON-RPC 操作名 SendMessage |
message/stream |
SendStreamingMessage |
用 kind 区分对象 |
v1.0 由成员字段区分 |
role: "user" |
role: "ROLE_USER" |
state: "completed" |
TASK_STATE_COMPLETED |
文件放进嵌套 file 对象 |
Part 使用 raw、url 等内容字段 |
Agent Card 只有顶层 url |
使用有序的 supportedInterfaces[] |
| 每次请求都返回 Task | 简单交互也可以直接返回 Message |
如果要做真实实现,应固定规范版本,并以正式 Specification 和规范性 Proto 定义为准,不要直接照搬早期 Demo。
十五、生产落地检查清单
发现与兼容性
- 候选 Agent 来自可信配置或受治理的 Registry;
- 正确读取
/.well-known/agent-card.json; - 检查 Skill、Binding、协议版本和输入输出模式;
- Agent Card 支持缓存与刷新;
- 不把 Agent 自身版本和 A2A 协议版本混为一谈。
消息与任务
- 为每条 Message 生成唯一
messageId; - 正确区分直接 Message 与可跟踪 Task;
- 保存并复用服务端返回的
taskId和contextId; - Task 正式结果进入 Artifact;
- 正确处理
INPUT_REQUIRED、AUTH_REQUIRED和各种终态。
异步与可靠性
- 根据任务时长选择等待、Polling、Streaming 或 Push;
- 流式断开后能够恢复订阅或查询 Task;
- Webhook 防止 SSRF、伪造、重放和重复处理;
- 取消、超时和重试具备明确语义;
- 记录 Message、Task、Context 和 Artifact 的关联关系。
安全与治理
- 认证凭据不进入 Message 正文;
- 每个 Skill 和业务动作都经过授权;
- 高风险动作具备人工确认和审计记录;
- 不在公共 Agent Card 暴露敏感信息;
- 跨 Agent 传输遵循数据最小化原则。
十六、面试版回答
如果面试中被问到 A2A 与 MCP 的区别,可以这样回答:
MCP 主要解决 Agent 如何连接工具、资源和 Prompt 等外部能力,交互通常边界明确;A2A 主要解决独立 Agent 系统之间如何发现能力、委派目标、跟踪有状态任务并交付结果。A2A 的核心对象包括 Agent Card、Message、Task 和 Artifact,一个 Agent 常常对外使用 A2A 协作,对内使用 MCP 调用工具。因此二者是互补关系,不是替代关系。
总结
A2A v1.0 的主线可以压缩成一条完整链路:
text
配置或 Registry 找到候选 Agent
↓
Agent Card 确认能力、接口与认证
↓
SendMessage 发送 Message 与 Part
↓
简单请求直接返回 Message
复杂工作创建 Task 并跟踪生命周期
↓
通过 Polling、Streaming 或 Push 获取更新
↓
最终用 Artifact 交付正式结果
而 A2A 与 MCP 的边界可以记成一句话:MCP 让 Agent 使用能力,A2A 让 Agent 与另一个 Agent 协作完成目标。
如果这篇文章帮你理清了 A2A、MCP、Agent Card 和 Task 生命周期这些容易混淆的概念,别忘了点个赞、收藏一下,方便以后设计多 Agent 系统时随时回来查。如果还有没看懂的地方,或者你想看 A2A Python Demo、A2A 与 MCP 组合架构等实战内容,欢迎在评论区告诉我。后续还会继续分享 Agent、MCP、多智能体协作和后端工程相关内容,感兴趣的话点个关注,我们下一篇见!
参考资料
- 原始学习笔记:\[20_Workspaces/20_Portfolio/Agent/notes/A2A/A2A协议与MCP边界总览\|A2A 协议学习总览(v1.0)]
- A2A Protocol 官方网站
- A2A Protocol Specification 1.0.0
- What's New in A2A Protocol v1.0
- A2A Core Concepts
- Agent Discovery
- Life of a Task
- Streaming and Asynchronous Operations
- A2A and MCP
- A2A Enterprise Features