关于 A2A to MCP bridge
A2A to MCP bridge 将配置的 A2A v1.0.1 Agent 转为 MCP 工具,适合需要从 MCP 客户端调用 A2A Agent 的场景。它映射消息、任务状态与内容,并明确标示协议转换中无法保留的信息。
主要功能
- 01发现 A2A Agent
- 02发送消息并读取回复
- 03读取和取消任务
- 04映射内容与任务状态
- 05指出转换中丢失的信息
适用场景
- 01从 MCP 客户端调用 A2A Agent
- 02发现已配置 Agent 的能力
- 03跟进需要多轮交互的任务
- 04取消不再需要的任务
MCP 相关
该桥接服务将一个或多个 A2A Agent 暴露为 MCP 工具,提供发现、发送消息、读取任务和取消任务功能;支持新版及旧版 MCP 路由,并在新版路由提供 tasks 扩展。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
a2a-to-mcp
在无状态的 MCP 2026-07-28 服务上,将 A2A v1.0.1 Agent 暴露为 MCP 工具,并支持 io.modelcontextprotocol/tasks 扩展。
通过 Agent 卡片将桥接服务指向一个或多个 A2A Agent。任何 MCP 客户端(包括 Claude Code 或 Cursor)都能看到四个常规工具:发现 Agent、向其发送消息、读取任务、取消任务。桥接服务会在两种协议之间转换部分内容、产物、任务状态和错误,并明确说明无法转换的内容。
| 接口 | 提供的版本 | 固定依赖 |
|---|---|---|
| MCP,现代路由 | 2026-07-28,通过 server/discover,无会话,每个请求带 _meta | @modelcontextprotocol/server 2.0.0 |
| MCP,旧版路由 | 2025-11-25,通过 initialize,同一端点 | 同上 |
| MCP tasks 扩展 | io.modelcontextprotocol/tasks schema 2026-07-28,仅现代路由 | 无,由桥接服务提供 |
| A2A | 1.0(A2A-Version: 1.0),JSON-RPC 绑定 | @a2a-js/sdk 1.1.0 |
需要 Node 20 或更高版本。Apache-2.0。
快速开始
npm install -g a2a-to-mcp
也可以从仓库中运行:
git clone https://github.com/AmirK-S/a2a-to-mcp.git && cd a2a-to-mcp && npm ci && npm run build
编写 agents.json,为每个 Agent 指定一个别名,并通过基础 URL 或卡片 URL 配置:
{
"agents": {
"hello": { "url": "http://localhost:9999" },
"planner": { "cardUrl": "https://planner.example.org/.well-known/agent-card.json" }
},
"port": 8931
}
运行:
node dist/cli.js --config agents.json
桥接服务监听 http://127.0.0.1:8931/mcp。除非在配置中设置 allowedHosts,否则 Host 和 Origin 标头只接受环回主机。此版本的两端均不提供身份验证。
Claude Code
将以下内容添加到项目根目录的 .mcp.json 中;桥接服务启动时会打印此配置块:
{
"mcpServers": {
"a2a-to-mcp": {
"type": "http",
"url": "http://127.0.0.1:8931/mcp"
}
}
}
Claude Code 从其 v2 运行时开始,通过 server/discover 协商 2026-07-28;文档从 2.1.232 版本起提供,此处使用 2.1.263 版本进行测量。旧版本会回退到 initialize,并通过旧版路由获取相同的四个工具。docs/DEMO-CLAUDE-CODE.md 中提供了 Claude Code 与桥接服务之间的完整注释跟踪记录,涵盖发现、带产物的任务以及一次 elicitation 往返。在无头模式(claude -p)下,没有人可以响应 elicitation,因此 Claude Code 会取消该请求,桥接服务也会取消 A2A 任务;交互模式则会询问用户。
Cursor
添加到 .cursor/mcp.json:
{
"mcpServers": {
"a2a": { "url": "http://localhost:8931/mcp" }
}
}
Cursor 使用 2025-11-25,通过同一端点上的 initialize 提供服务。
四个工具
配置了多个 Agent 时,每个工具都将 Agent 别名作为第一个参数;只有一个 Agent 时,可以省略别名,默认使用该 Agent。
| 工具 | 参数 | 返回值 |
|---|---|---|
a2a_discover | agent | Agent 提供的 Agent 卡片,经过结构检查后放入 structuredContent,并附带文本摘要和 _meta 中的卡片 URL |
a2a_send_message | agent、text、contextHandle?、taskHandle? | Agent 回复,详见下方的结果约定 |
a2a_get_task | agent、taskHandle、historyLength? | 包含历史记录的任务信封 |
a2a_cancel_task | agent、taskHandle | 取消请求发出后的任务信封 |
在网络上调用工具
承载消息的参数是 text。以下是在 2026-07-28 路由上由 MCP 客户端发送的完整 tools/call:
curl -sS http://127.0.0.1:8931/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2026-07-28' \
-H 'Mcp-Method: tools/call' \
-H 'Mcp-Name: a2a_send_message' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{
"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28",
"io.modelcontextprotocol/clientCapabilities":{}},
"name":"a2a_send_message",
"arguments":{"agent":"hello","text":"task: hello world"}}}'
docs/USAGE-REEL.md 中提供了使用公开 A2A Agent 运行的示例,其中展示了新手常犯的一个错误。
结果约定
每条回复都是一个 CallToolResult,其 content 包含已翻译的部分,structuredContent 则是一个封套:
{
"kind": "task",
"contextHandle": "cx_...",
"taskHandle": "tk_...",
"a2aState": "TASK_STATE_COMPLETED",
"status": "completed",
"note": "present when the mapping lost information",
"artifacts": [{ "artifactId": "...", "name": "result" }],
"data": {}
}
data 包含制品中的单个 A2A 数据部分;如果没有或有多个数据部分,则不包含 data。只有 a2a_get_task 会添加 history。
直接 A2A 消息的 kind 为 message,Agent 开启任务时则为 task。句柄是由桥接器生成的不透明字符串,而不是 A2A 标识符;它们隐藏的是桥接器表,而不是 Agent 选择写入自身消息的内容。默认情况下,句柄自上次使用后在内存中保留十五分钟(handleTtlMs),过期或未知的句柄会返回明确错误。传回 contextHandle 可继续同一对话;传回 taskHandle 可回复 Agent 留待输入的任务。
Agent 失败(FAILED、REJECTED、AUTH_REQUIRED),以及 Agent 留在未指定状态的任务,都会导致工具执行错误:isError: true,content 中包含 Agent 文本,封套中包含 A2A 状态和说明。带类型的 A2A 错误(代码 -32001 至 -32009)也会导致工具执行错误,代码位于 structuredContent.a2aErrorCode 中。桥接器绝不会将 A2A 错误代码重新作为 JSON-RPC 代码发出。JSON-RPC 将 -32000 至 -32099 保留给服务器定义的错误,MCP 已在该范围内使用 -32020、-32021 和 -32022,而在该范围内重放 A2A 代码会错误地赋予它一个并不存在的 MCP 含义。
长时间运行的任务
使用 tasks 扩展
在每个请求的 clientCapabilities.extensions 中声明 io.modelcontextprotocol/tasks 的客户端,会在 Agent 创建任务后立即从 a2a_send_message 收到 CreateTaskResult,随后轮询 tasks/get,并可调用 tasks/update 和 tasks/cancel。当卡片声明支持流式传输时,桥接器会在后台使用 SendStreamingMessage;否则使用 GetTask。
A2A TaskState | tasks/get 上的 MCP TaskStatus | 损失的信息 |
|---|---|---|
SUBMITTED | working | MCP 没有已接受但尚未开始的状态;statusMessage 会说明这一点 |
WORKING | working | 无 |
INPUT_REQUIRED | input_required,附带一个合成的表单征询 | A2A 通过自由文本提问;表单只有一个字符串字段 |
COMPLETED | completed,result 是完整的 CallToolResult | 无 |
FAILED、REJECTED、AUTH_REQUIRED | completed,result.isError 为 true | 如果没有信封,拒绝和授权请求会与失败无法区分 |
CANCELED | cancelled | 无,注意拼写 |
| 未指定 | completed,result.isError 为 true | A2A 未发送任何生命周期状态;桥接器不会臆测 |
| 桥接器再也无法访问该任务 | failed,带有 JSON-RPC error,A2A 代码位于 error.data 中 |
AUTH_REQUIRED 不会映射为 input_required,这是有意为之:MCP 禁止使用表单征询收集凭据,而 A2A 要求通过带外方式授权。Agent 文本通常包含需要访问的 URL,会在结果中返回。
该扩展仅在 2026-07-28 路由上提供。在旧版路由上,tasks/* 会返回 -32021,并附带所需能力。截至 2026-09-06,根据对该桥接器的测试,Claude Code 和 Cursor 这两个客户端都未声明此扩展。该扩展由本仓库中的测试套件以及任何按请求选择启用它的客户端进行测试。
除了扩展定义的字段外,CreateTaskResult 离开桥接器时还带有一个空的 content 数组。这是针对参考 SDK 的一种变通方案:该 SDK 会根据普通结果架构验证每个 tools/call 结果(typescript-sdk issue 2637)。现代路由上,三个扩展方法由 SDK 前方的一个小型路由器提供,因为 SDK 在查询处理程序之前就会对 tasks/get 和 tasks/cancel 返回 -32601(typescript-sdk issue 2598);若将这三个方法拆分到两层处理,它们可能会出现偏差。这两种变通方案都彼此隔离,待 SDK 更新后将移除。
不使用扩展
a2a_send_message 会等待 Agent 到达终止状态或中断状态,然后以内联方式返回。处于 INPUT_REQUIRED 状态的任务会这样返回:
- 对于声明了
elicitation的客户端,返回一个需要多次往返的input_required结果,其中包含一个表单征询;客户端随后会附上答案重新发送调用; - 对于其他客户端,返回普通结果,其信封会指出
input_required并携带taskHandle;客户端随后会使用该句柄发送答案。
桥接器传递的内容与不传递的内容
各部分:
A2A Part | MCP 内容块 |
|---|---|
text | TextContent |
data | 承载 JSON 的 TextContent,并在 _meta 中标记;如果它是任务工件中唯一的数据部分,也会出现在 structuredContent.data 中 |
url | ResourceLink,以 filename 作为名称,以 mediaType 作为 mimeType |
raw、图像或音频媒体类型 | 以 base64 编码的 ImageContent 或 AudioContent |
raw、其他媒体类型 | 位于 a2a://part/ URI 下的嵌入式二进制资源 |
filename、工件身份和名称、消息及上下文标识符,会通过每个块中的 _meta["io.github.amirk-s/a2a"] 传递。
按设计不会传递的内容:推送通知(MCP 没有 webhook)、Agent 卡片签名、securitySchemes、流式事件本身(会汇总到任务中)、ListTasks(由句柄取代),以及任何将自由文本路由到技能的功能。桥接器不会调用大模型,也不会在磁盘上保存任何内容。
一致性
解读成绩时也要看其原因:在 2026-07-28 这次运行中,37 个评分场景里有 32 个至少有一项检查未通过,其中 29 个有失败,3 个仅有警告。19 个场景调用了测试套件中写明的工具名称,13 个场景测试了提示词、资源或补全功能,而桥接到 A2A Agent 的服务并未声明这些功能;logging 不在其中,它只在 2025-11-25 这次运行中评分。测试套件有意将这两类都计为失败,并指出预期失败基线是解决方案(modelcontextprotocol/conformance#248)。本次运行共记录 67 项失败检查和 5 项警告,全部都在已提交的基线中,并附有原因;两个版本的测试套件都以 0 退出,而适用于桥接服务的协议检查均通过:线缆 schema、server/discover、声明的能力与已挂载处理程序之间的匹配、subscriptions/listen 确认、SEP-2243 标头、DNS 重绑定防护。具体数字见 conformance/REPORT.md,可通过下方命令重新生成。
npm run conformance 会针对 2026-07-28 和 2025-11-25 重放官方测试套件 @modelcontextprotocol/conformance 的 0.2.0-alpha.11 版本,并使用预期失败基线,其中每一行都注明了原因。报告见 conformance/REPORT.md。
测试套件能够证明的内容:桥接服务发出的每条消息都符合对应版本的 schema(两种线缆格式上的 wire-schema-valid 均通过),server/discover 和无状态线缆均正常运行,SEP-2243 标头验证得到执行,DNS 重绑定防护已启用。它无法衡量的内容:那些按名称调用测试套件自有固定工具的场景,以及 tasks-* 场景;桥接到真实 Agent 的服务无法满足这些场景,除非假装自己就是固定工具。任务扩展则通过 test/integration/tasks-extension.test.ts 中逐项编写的检查覆盖,这些检查针对线缆进行测试。
开发
npm ci
npm test # unit and integration, in-process fixture agent, no network
npm run test:helloworld # against the official A2A helloworld sample, see scripts/helloworld
npm run conformance # official MCP suite, both revisions, baseline applied
fixtures/agent 中的固定 Agent 是一个确定性的 A2A v1.0 Agent,不使用模型:其命令覆盖 Agent 可主动报告的所有任务状态、两轮输入需求循环、取消、四种 part 类型以及九种类型化错误。运行 npx tsx fixtures/agent/cli.ts --port 41241 可单独启动它。
维护与问题
问题和拉取请求会在七天内得到回复。针对固定版本的错误报告会得到处理;新功能请求会被阅读并回复,但不作承诺。此处列出了可能后续扩展的范围,以免有人误以为这些功能会悄然加入:每项 A2A skill 对应一个工具、带任务通知的 subscriptions/listen、采用配置中的 A2A 身份验证方案,以及反向桥接。
生命周期终止
任一协议的下一版本会造成哪些功能失效,已写入 docs/END-OF-LIFE.md。版本之所以精确锁定,正是有意为之。首次发布时的项目状态记录在 docs/ETAT-FINAL.md 中:哪些内容已验证、哪些内容已发布、哪些内容仍未解决且会保持如此。
许可证
Apache-2.0。两种协议的规范和 A2A SDK 均采用相同的许可证。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。