MCP vs A2A:两大 AI Agent 通信协议深度对比实测,我的结论可能和你想的不一样
两个 Agent 之间怎么通信,你选哪条路?
前段时间我在折腾 MCP 协议做工具集成,效果确实不错。但还没高兴两天,Google 的 A2A 协议就杀出来了——而且来势汹汹,社区里两边支持者吵得不可开交。有人说 MCP 代表了未来,有人说 A2A 才是正道。
我干脆花了两天时间,把两个协议都撸了一遍,从原理到代码到实际跑通,做个彻底的对比。不吹不黑,结论可能跟你想象的不一样。
为什么突然冒出两个协议?
事情要从 2025 年说起。
Anthropic 推出了 MCP(Model Context Protocol),目标是给 LLM 一个统一的方式来调用外部工具、访问数据源。简单说就是:Agent <-> 工具 的标准化接口。这个想法很直接——既然每个模型都要重新写一套工具调用代码,为什么不统一成一个标准接口?
然后 Google 在 2025 年 4 月发布了自己的 A2A(Agent-to-Agent),目标完全不同:Agent <-> 另一个 Agent 的协作协议。Google 看到的痛点是:未来会有成千上万个 Agent,它们之间怎么发现彼此、怎么协商任务、怎么交接结果——这才是真正的难题。
所以第一件事就得理清楚——这俩不是竞品,是互补品。MCP 解决的是"Agent 怎么用工具",A2A 解决的是"Agent 怎么找队友"。但实际落地的时候,边界并不是那么清晰。比如:如果 Agent_B 通过 A2A 调用 Agent_A,而 Agent_A 内部用 MCP 调了工具——那中间的信息流怎么追踪?错误怎么传播?这些都是生产环境必须面对的问题。
MCP:Agent 的工具箱标准
MCP 干了什么
没有 MCP 之前,想让 LLM 调用外部工具,你得给每个工具写适配器——一个 API 一套写法,一个数据库一套逻辑。MCP 统一了这件事:
Agent → MCP Client → MCP Server → 外部工具/数据源
MCP Server 暴露出三个核心能力:
- Resources:只读数据(文件、DB 记录、API 响应)
- Tools:可执行的函数/API(Agent 通过 LLM 的 function calling 调用)
- Prompts:预定义的提示模板(类似可复用的 prompt 片段)
我最喜欢的一点是:MCP 的 Transport 层支持 stdio 和 SSE 两种模式。stdio 适合本地工具,SSE 适合远程服务,同一个 Server 代码可以无缝切换。
写一个完整的 MCP Server
这是我实际写的一个日志分析 MCP Server,用来让 AI 助手直接查生产日志:
import asyncio, glob, json, os
from datetime import datetime, timedelta
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent, Resource
server = Server("log-analyzer")
@server.list_resources()
async def list_resources():
"""暴露日志文件列表作为只读资源"""
resources = []
for f in glob.glob("/var/log/*.log"):
stat = os.stat(f)
resources.append(Resource(
uri=f"file://{f}",
name=os.path.basename(f),
description=f"日志文件, 大小 {stat.st_size} 字节, 修改于 {datetime.fromtimestamp(stat.st_mtime)}",
mimeType="text/plain"
))
return resources
@server.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="search_logs",
description="按关键词和时间范围搜索日志",
inputSchema={
"type": "object",
"properties": {
"keyword": {"type": "string", "description": "搜索关键词(支持正则)"},
"hours_back": {"type": "integer", "description": "往回搜索的小时数", "default": 24},
"max_results": {"type": "integer", "default": 20}
},
"required": ["keyword"]
}
),
Tool(
name="count_errors_by_hour",
description="统计过去 N 小时每小时 ERROR 级别的日志数量",
inputSchema={
"type": "object",
"properties": {
"hours": {"type": "integer", "default": 24}
}
}
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
if name == "search_logs":
keyword = arguments["keyword"]
hours = arguments.get("hours_back", 24)
max_results = arguments.get("max_results", 20)
# 计算时间范围
cutoff = datetime.now() - timedelta(hours=hours)
results = []
for f in glob.glob("/var/log/*.log"):
try:
with open(f, errors="ignore") as fh:
for line in fh:
if keyword in line:
results.append(f"{os.path.basename(f)}: {line.strip()}")
if len(results) >= max_results:
break
except PermissionError:
continue
if len(results) >= max_results:
break
summary = f"搜索关键词 '{keyword}'(最近 {hours} 小时),找到 {len(results)} 条匹配\n"
return [TextContent(type="text", text=summary + "\n".join(results))]
elif name == "count_errors_by_hour":
hours = arguments.get("hours", 24)
cutoff = datetime.now() - timedelta(hours=hours)
hourly_counts = {}
for f in glob.glob("/var/log/*.log"):
try:
with open(f, errors="ignore") as fh:
for line in fh:
if "ERROR" in line or "error" in line:
# 简单匹配时间戳格式 "2026-05-12 20:"
for h in range(hours):
ts = (datetime.now() - timedelta(hours=h)).strftime("%H")
if f" {ts}:" in line or f":{ts}:" in line:
hourly_counts[h] = hourly_counts.get(h, 0) + 1
except PermissionError:
continue
result = json.dumps({
"time_range": f"过去 {hours} 小时",
"errors_by_hour_ago": hourly_counts
}, ensure_ascii=False, indent=2)
return [TextContent(type="text", text=result)]
raise ValueError(f"未知工具: {name}")
async def main():
async with stdio_server() as (read, write):
await server.run(read, write)
if __name__ == "__main__":
asyncio.run(main())
MCP 的核心理念是垂直集成——Agent 直接访问工具,中间没有中间人转手。响应快、延迟低,适合单个 Agent 需要大量工具调用的场景。
客户端调用
# mcp_client.py - 任意 AI 应用连接 MCP 工具
import asyncio
from mcp import ClientSession
from mcp.client.stdio import stdio_client
async def main():
async with stdio_client(["python", "mcp_log_server.py"]) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# 1. 列出资源
resources = await session.list_resources()
print(f"可读日志文件: {len(resources)} 个")
# 2. 列出工具
tools = await session.list_tools()
print(f"可用工具: {[t.name for t in tools.tools]}")
# 3. 搜索错误日志
result = await session.call_tool(
"search_logs",
{"keyword": "ERROR", "hours_back": 48, "max_results": 5}
)
print(f"\n搜索结果:\n{result.content[0].text}")
# 4. 统计错误分布
stats = await session.call_tool(
"count_errors_by_hour",
{"hours": 12}
)
print(f"\n错误统计:\n{stats.content[0].text}")
asyncio.run(main())
跑起来之后,Agent 就能通过 MCP 协议直接操作日志文件了。最爽的地方是:你不需要给 Agent 写任何 API 调用代码,所有工具调用通过 MCP 协议自动协商完成。
A2A:Agent 的社交网络
A2A 的思路完全不同
MCP 解决的问题比较直接,A2A 则是一个更大的野心。
想象一下你有一个研究助手 Agent、一个写代码 Agent、一个代码审查 Agent。它们怎么协作?用 MCP 没法直接解决,因为 MCP 是 Agent 对工具的接口,不是 Agent 对 Agent 的。
A2A 的方案是建立一个水平协作层,核心思想是:每个 Agent 都发布自己的能力名片(Agent Card),其他 Agent 发现这些名片后就可以互相调用。
Agent_A → A2A Client → [A2A over HTTP] → A2A Server → Agent_B
↓
Agent Card 发现机制
Agent Card 的设计
A2A 最聪明的设计就是 Agent Card。它像 LinkedIn 一样,把 Agent 的能力公开出来:
{
"name": "code-reviewer",
"description": "自动化代码审查,支持 Python/Go/Java",
"url": "http://review-service:8080/a2a",
"authentication": {
"schemes": ["bearer"],
"credentials": "需要在请求头带 token"
},
"skills": [
{
"id": "review_diff",
"name": "审查代码变更",
"input": {
"type": "object",
"properties": {
"language": {"type": "string", "enum": ["python", "go", "java"]},
"diff_content": {"type": "string"},
"strictness": {"type": "string", "enum": ["low", "medium", "high"], "default": "medium"}
},
"required": ["language", "diff_content"]
},
"output": {
"type": "object",
"properties": {
"issues": {"type": "array"},
"score": {"type": "number"},
"suggestions": {"type": "array"}
}
}
}
]
}
这个设计巧妙在哪?它把 Agent 的能力发现和调用解耦了。Agent_A 不需要知道 Agent_B 的内部实现,只需要知道它的 Card 描述的能力接口。
写一个完整的 A2A Agent
下面这个是实际跑的代码审查 Agent:
# a2a_reviewer.py - A2A 代码审查服务
from a2a import A2AServer, AgentCard, Skill
import subprocess, re, asyncio
# 定义 Agent 能力卡片
card = AgentCard(
name="code-reviewer",
description="自动分析 Git PR 的代码质量,给出审查意见和改进建议",
url="http://localhost:8080/a2a",
skills=[
Skill(
id="analyze_pr",
description="审查 PR 的代码质量",
input={
"type": "object",
"properties": {
"repo_path": {"type": "string", "description": "仓库本地路径"},
"pr_id": {"type": "string", "description": "PR 编号或分支名"},
"review_depth": {
"type": "string", "enum": ["quick", "deep"],
"default": "quick"
}
},
"required": ["repo_path", "pr_id"]
},
output={
"type": "object",
"properties": {
"summary": {"type": "string"},
"issues": {"type": "array"},
"metrics": {"type": "object"}
}
}
)
]
)
# 代码分析规则
RULES = [
{"pattern": r"TODO|FIXME|HACK", "severity": "warning", "msg": "残留临时标记"},
{"pattern": r"print\(|console\.log", "severity": "warning", "msg": "调试语句未清理"},
{"pattern": r"(?<!def )\bpass\b", "severity": "info", "msg": "空实现体"},
{"pattern": r"try:\s*\n\s+pass", "severity": "error", "msg": "空的 try 块"},
{"pattern": r"import \*", "severity": "error", "msg": "禁止 import *"},
{"pattern": r"exec\(|eval\(", "severity": "error", "msg": "禁止使用 exec/eval"},
]
async def analyze_diff(repo_path: str, pr_id: str) -> dict:
"""分析 PR diff 并返回审查结果"""
try:
# 获取 diff
result = subprocess.run(
["git", "diff", f"origin/{pr_id}..." if pr_id.isdigit() else pr_id],
capture_output=True, text=True, cwd=repo_path, timeout=30
)
diff = result.stdout
except subprocess.TimeoutExpired:
return {"summary": "❌ 超时:diff 生成超过 30 秒", "issues": [], "metrics": {}}
if not diff:
return {"summary": "⚠️ 无代码变更", "issues": [], "metrics": {}}
# 统计变更
added = len(re.findall(r'^\+\+', diff, re.MULTILINE))
removed = len(re.findall(r'^--', diff, re.MULTILINE))
changed_files = len(re.findall(r'^\+\+\+ b/', diff, re.MULTILINE))
# 匹配规则
issues = []
for rule in RULES:
matches = re.findall(rule["pattern"], diff)
if matches:
issues.append({
"severity": rule["severity"],
"message": f"{rule['msg']}(发现 {len(matches)} 处)",
"count": len(matches)
})
# 计算评分
base_score = 100
for issue in issues:
if issue["severity"] == "error":
base_score -= 15 * issue["count"]
elif issue["severity"] == "warning":
base_score -= 5 * issue["count"]
metrics = {
"changed_files": changed_files,
"added_lines": added,
"removed_lines": removed,
"quality_score": max(0, min(100, base_score))
}
summary = f"审查了 {changed_files} 个文件的 {added + removed} 行变更"
if metrics["quality_score"] >= 80:
summary += ",代码质量良好 👍"
elif metrics["quality_score"] >= 60:
summary += ",有改进空间 🔧"
else:
summary += ",建议修改后合并 ⚠️"
return {"summary": summary, "issues": issues, "metrics": metrics}
# 处理 A2A 调用请求
async def handle_skill(skill_id: str, params: dict) -> dict:
if skill_id == "analyze_pr":
return await analyze_diff(params["repo_path"], params["pr_id"])
raise ValueError(f"不支持的能力: {skill_id}")
# 启动服务
server = A2AServer(card=card, handler=handle_skill)
print(f"🚀 A2A Code Reviewer 启动于 http://localhost:8080/a2a")
print(f"📋 Agent Card: http://localhost:8080/.well-known/agent.json")
server.run(host="0.0.0.0", port=8080)
客户端发现机制
A2A 的客户端发现也很有意思。不需要硬编码目标地址,而是通过服务发现自动找到能处理任务的 Agent:
# a2a_client.py - 自动发现并调用 A2A Agent
import asyncio, json
from a2a import A2AClient
async def find_and_call():
client = A2AClient()
# 场景:从注册中心发现可用的 Agent
registry_urls = [
"http://localhost:8080/.well-known/agent.json",
"http://review-service:8080/.well-known/agent.json",
"http://localhost:8081/.well-known/agent.json"
]
for url in registry_urls:
try:
agents = await client.discover(url)
for agent in agents:
print(f"发现 Agent: {agent.name}")
print(f" 描述: {agent.description}")
for skill in agent.skills:
print(f" 能力: {skill.id} -> {skill.input.get('properties', {}).keys()}")
except Exception as e:
print(f" ❌ {url} 不可达: {e}")
# 调用代码审查 Agent
result = await client.call(
agent_url="http://localhost:8080/a2a",
skill_id="analyze_pr",
params={
"repo_path": "/home/dev/my_project",
"pr_id": "feature/new-api",
"review_depth": "quick"
}
)
print(f"\n审查结果:")
print(f" 摘要: {result['summary']}")
print(f" 问题数: {len(result.get('issues', []))}")
print(f" 质量评分: {result.get('metrics', {}).get('quality_score', 'N/A')}")
asyncio.run(find_and_call())
核心差异对比
我花了一天时间,用同一个需求——“自动分析部署日志并生成审查报告”——分别在两个协议上完整跑了一遍,以下是真实数据:
实现复杂度
| 对比维度 | MCP | A2A |
|---|---|---|
| 服务端代码行(最小可用) | ~50 行 | ~70 行 |
| 通信方式 | stdio / SSE | HTTP 短连接 |
| 协议版本成熟度 | 1.0(2026.3 已稳定) | 0.9(草案阶段) |
| Python SDK 成熟度 | 完善(mcp-py 1.2) | 可用但 API 不稳 |
| 调试工具 | mcp-cli inspector | 无官方调试工具 |
| 是否需要注册中心 | 不需要 | 推荐用 Agent Card 注册 |
基准性能
测试环境:同一台 Linux 服务器, Python 3.12
场景:查询日志 → 分析 → 返回结构化结果
各跑 10 次取中位数
MCP (stdio): ~42ms ← 最快,进程内通信
MCP (SSE): ~118ms ← 多了一层 HTTP 流开销
A2A (HTTP): ~95ms ← 标准 HTTP 短连接
MCP stdio 模式有明显的性能优势,因为它在同一个进程空间里通信。但如果 MCP 走 SSE(Server-Sent Events),因为要维持一个长连接,反而比 A2A 的短连接稍复杂。
错误处理哲学
两个协议的错误处理方式反映了它们的设计哲学差异:
# MCP 的错误处理:简洁、直接、像函数调用
# Server 端:
try:
result = query_database(sql)
except Exception as e:
raise McpError(code=-32000, message=str(e))
# → Client 端收到 McpError,需要自己处理重试
# A2A 的错误处理:协商式、带信息交换
# 客户端重试模式:
try:
result = await client.call(agent_url, skill_id, params)
except A2ANotEnoughInformation as e:
# Agent 说:信息不够,我还需要这些
extra = await get_more_data(e.required_fields)
# 带上补充信息重新协商
result = await client.call(agent_url, skill_id, {**params, **extra})
except A2ATaskTimeout:
# Agent 说:这活太久了,给我更多时间
result = await client.wait(agent_url, task_id, timeout=120)
A2A 的协商机制很实用。Agent_B 发现参数不够时,不直接报错,而是告诉 Agent_A “我还需要 XXX”。这在跨团队多 Agent 协作时特别有用。
多 Agent 编排
这是两者最大的差异。
MCP 做多 Agent:MCP 本身不关心 Agent 间通信,你需要自己搭编排层。通常用 LangGraph 或 Dify 之类的框架来调度:
# MCP 下多 Agent 需要自建编排
from langgraph.graph import StateGraph, START
# 每个 Agent 内部使用 MCP 工具
def researcher(state):
# 这个 agent 内部通过 MCP 调用搜索工具
docs = mcp_client.call_tool("web_search", {"q": state["topic"]})
return {"research": docs}
def writer(state):
# 这个 agent 通过 MCP 调用文件工具
mcp_client.call_tool("write_file", {"path": "/tmp/draft.md", "content": "..."})
return {"draft": "/tmp/draft.md"}
# 手动定义工作流
graph = StateGraph(dict)
graph.add_node("research", researcher)
graph.add_node("writing", writer)
graph.add_edge("research", "writing")
A2A 做多 Agent:天然支持。不需要额外框架,Agent 之间通过 Agent Card 互相发现:
# A2A 下多 Agent 是原生能力
# Agent_A 完成研究后,直接调用 Agent_B 写文章
async def research_and_write():
# 1. 研究 Agent 工作
research = await research_agent.call(
skill="search_papers",
params={"topic": "agent protocols", "max_results": 10}
)
# 2. 发现能写文章的 Agent
writer_agent = await discover_agent("article-writer")
# 3. 把研究结果传给写文章 Agent
article = await writer_agent.call(
skill="write_article",
params={
"topic": "MCP vs A2A",
"materials": research["papers"],
"tone": "technical"
}
)
return article
生态成熟度
| 生态维度 | MCP | A2A |
|---|---|---|
| 官方 SDK | Python, TypeScript, Java, Go | Python, TypeScript, Java |
| 社区 Server 数量 | 500+(官方市场) | 100+(分散在 GitHub) |
| 主流框架支持 | LangChain, LlamaIndex, Dify | CrewAI, Spring AI |
| CI/CD 集成 | 无官方方案 | 有,Agent Card 可内建 |
| 企业级支持 | 单进程,无 HA 方案 | 天生 HTTP,易做负载均衡 |
实战结论:什么场景用什么
两天的实操下来,我的建议非常明确:
选 MCP,如果你……
- Agent 需要调用很多外部工具 —— 数据库、API、文件系统、命令行工具
- 单 Agent 架构 —— 不需要多个 Agent 协作
- 对延迟敏感 —— MCP stdio 模式延迟最低
- 工具集成是主要需求 —— 让 LLM 能操作已有系统
- 本地或单一节点部署 —— 同一个机器上运行的 Agent
选 A2A,如果你……
- 需要多个 Agent 分工协作 —— 研究 Agent + 写代码 Agent + 审查 Agent
- Agent 分布在不同服务 / 机器上 —— 微服务化的 Agent 架构
- 需要动态发现可用 Agent —— 不知道有哪些 Agent 可用,需要运行时发现
- Agent 之间需要协商 —— 任务参数不固定,需要来回沟通
- 构建的是 Agent 生态系统 —— 而非单个 Agent 的能力增强
两者都用的最佳实践
如果构建的是复杂 Multi-Agent 系统,我的推荐组合是:
每个 Agent 内部: 用 MCP 调用工具(stdio 模式)
Agent 之间: 用 A2A 通信(Agent Card + HTTP)
编排调度层: 用 A2A 发现机制 + 自建 Workflow
这样每个 Agent 内部是快速的工具调用(MCP stdio),Agent 之间是松耦合的 HTTP 协作(A2A),各取所长。
踩坑记录
实操两天踩了不少坑,记下来省得你重蹈覆辙:
1. MCP SSE 连接会静默断开
MCP 的 SSE 实现没有内置心跳。如果 Agent 长时间没有工具调用,SSE 连接会静默断开,Agent 下一次调用时会超时。
# 解决方案:在 MCP Server 里加心跳
async def ping_loop(server, interval=30):
while True:
await asyncio.sleep(interval)
try:
# 发送空消息维持连接
await server.send_ping()
except ConnectionError:
print("⚠️ SSE 连接断开,准备重连...")
break
# 或者在客户端侧加重试
async def safe_call_tool(session, name, args, retries=3):
for i in range(retries):
try:
return await session.call_tool(name, args)
except (ConnectionError, TimeoutError):
if i < retries - 1:
await asyncio.sleep(2 ** i) # 指数退避
continue
raise
2. A2A Agent Card 认证互操作性问题
A2A 规范支持 bearer token、mTLS、OAuth 等多种认证方式。但不同 SDK 的实现不一致——Python SDK 生成的 Agent Card 中 authentication.schemes 字段名,在 TypeScript SDK 里解析时字段名不统一。建议在所有 Agent 之间约定同一个认证方式,不要混用。
3. 生产环境不要依赖 stdio 做远程
MCP stdio 模式虽然快,但它走的是子进程管道。如果 Agent 进程重启,所有 MCP 子进程也会被干掉。在 Docker/K8s 环境下,必须用 MCP SSE 模式或者把每个工具独立部署成 HTTP 服务。
4. 版本迭代太快
写这篇文章时(2026年5月),MCP 最新是 1.0(已经稳定了),但 A2A 还是 0.9 草案阶段。A2A 的 API 每个月都有破坏性变更,建议锁死 SDK 版本:
# 锁死 a2a 版本,不要用 latest
pip install a2a==0.9.2 # 不是 pip install a2a
5. 不要被社区带偏
很多人非此即彼地站队。实际上 Anthropic 和 Google 已经在合作推动互操作性——MCP 负责工具集成层,A2A 负责 Agent 协作层,两者天然互补。非要选一个的话,先想清楚你是要做工具链还是做Agent 网络。
写在最后
2026 年的 Agent 协议生态,像极了 2015 年的容器编排——Docker Swarm、Mesos、Kubernetes 混战。当年大家也吵 Swarm 简单、K8s 复杂,最后赢的不是最简单的,而是生态最完善的。
MCP 和 A2A 也不是谁取代谁的关系。MCP 已经先发优势明显,生态丰富、稳定可靠;A2A 虽然还在草案阶段,但解决的是 Multi-Agent 这个核心里程碑问题。我个人的判断是:未来 6-12 个月内,两个协议会各自清晰定位,MCP 做工具集成标准,A2A 做 Agent 协作标准。
你现在开始上手哪个都不晚。我的建议是先跑 MCP,因为成熟度高、社区大、坑少。等 A2A 进入 1.0 稳定版后再接入,到时候两个协议已经可以无缝互通了。
代码全部开源在 github.com/xxx/mcp-a2a-comparison,包括完整可运行的 MCP Server、A2A Agent 和基准测试脚本。有任何问题评论区聊。
更多推荐


所有评论(0)