我 Mac 上跑着一个 OpenClaw Agent 写代码,云服务器上另一个做搜索分析——但它们互相不认识。所以我写了 openclaw-a2a-gateway,把 Google 的 A2A v0.3.0 协议落地到 OpenClaw,让不同机器上的 Agent 能双向通信。
在讲部署之前,我们先用 30 秒理解一下 A2A。
一句话总结:MCP 让 Agent 能用工具,A2A 让 Agent 能找 Agent。
MCP 大家应该不陌生了,它解决的是"Agent 怎么调用外部工具和数据"的问题。而 A2A 解决的是另一个层面——"Agent 怎么找到另一个 Agent,并把任务委派给它"。
A2A 协议由 Google 在 2025 年 4 月发布,目前版本 v0.3.0,已经被 Linux Foundation 接管,背后有 150+ 组织站台(Salesforce、SAP、微软、AWS 等等都在里面)。
它的核心其实就三样东西:
① Agent Card —— 每个 Agent 的"名片",告诉别人我是谁、我会什么、怎么联系我。一个 JSON 文件,放在固定路径 /.well-known/agent-card.json。
② Task —— Agent 之间协作的基本单位。有完整的生命周期:提交 → 处理中 → 需要更多输入 → 完成/失败。
③ Message & Parts —— 消息载体。支持文本(TextPart)、文件(FilePart)、结构化数据(DataPart)。
底层用的全是老朋友:HTTP + JSON-RPC + SSE。没有花哨的私有协议,任何能发 HTTP 请求的语言都能接入。
装上之后,你的 OpenClaw 实例就变成了一个 A2A 节点。具体来说,它干了这几件事:
对外暴露 A2A 端点:别的 Agent 可以通过标准的 JSON-RPC 接口向你的 Agent 发消息。
发布 Agent Card:让对端能发现你、知道你能做什么。
安全认证:支持 Bearer Token,不是谁都能跟你的 Agent 说话。
双向通信:不仅能接收消息,你的 Agent 也可以主动调用对端。
文件传输:支持发送文件给对端 Agent(URI 或 base64)。
架构长这样:
Server A (你的 Mac) Server B (云服务器)
┌─────────────────────┐ ┌─────────────────────┐
│ Agent: MacBot │ A2A │ Agent: CloudBot │
│ Port: 18800 │◄─────►│ Port: 18800 │
│ Peer: Server-B │ │ Peer: Server-A │
└─────────────────────┘ └─────────────────────┘
Tailscale / LAN / 公网
两个端点需要记住:
/.well-known/agent-card.json(GET)—— Agent Card 发现
/a2a/jsonrpc(POST)—— 消息收发
开始之前,确认你的环境满足这些条件:
# OpenClaw >= 2026.3.0
openclaw --version
# Node.js >= 22
node --version
# Gateway 正在运行
openclaw gateway status
# 至少配置了一个 AI Provider
openclaw config get auth.profiles
如果 OpenClaw 还没装,先跑一下:
npm install -g openclaw@latest
openclaw onboard --install-daemon
另外,两台机器之间要能互相访问(后面会讲网络方案)。
mkdir -p ~/.openclaw/workspace/plugins
cd ~/.openclaw/workspace/plugins
git clone https://github.com/win4r/openclaw-a2a-gateway.git a2a-gateway
cd a2a-gateway
npm install --production
装完之后目录结构是这样的:
a2a-gateway/
├── index.ts # 插件入口
├── openclaw.plugin.json # 插件描述文件
├── src/ # 核心源码
├── skill/ # Agent Skill(后面会讲)
│ ├── SKILL.md
│ ├── scripts/
│ │ └── a2a-send.mjs # 发送脚本
│ └── references/
│ └── tools-md-template.md
└── tests/
这一步要做三件事——加白名单、设路径、启用:
# 1) 添加到允许的插件列表
# 注意:保留你已有的插件,比如 telegram
openclaw config set plugins.allow '["telegram", "a2a-gateway"]'
# 2) 告诉 OpenClaw 去哪里加载这个插件
# ⚠️ 必须用绝对路径!用 ~ 或相对路径会静默失败!
openclaw config set plugins.load.paths \
'["<FULL_PATH_TO>/plugins/a2a-gateway"]'
# 3) 启用插件
openclaw config set plugins.entries.a2a-gateway.enabled true
把 <FULL_PATH_TO> 替换成你的实际路径,比如:
Linux:/home/ubuntu/.openclaw/workspace/plugins/a2a-gateway
macOS:/Users/你的用户名/.openclaw/workspace/plugins/a2a-gateway
踩坑提醒:plugins.load.paths 里面写 ~ 是不行的,必须写完整的绝对路径。这是新手最容易栽的地方。另外 plugins.allow 数组里要保留你已有的插件名,别只写一个 a2a-gateway 把别的覆盖掉了。
这一步是告诉外界"你的 Agent 是谁":
openclaw config set plugins.entries.a2a-gateway.config.agentCard.name 'MacBot'
openclaw config set plugins.entries.a2a-gateway.config.agentCard.description \
'我的 OpenClaw A2A Agent'
openclaw config set plugins.entries.a2a-gateway.config.agentCard.url \
'http://<YOUR_IP>:18800/a2a/jsonrpc'
openclaw config set plugins.entries.a2a-gateway.config.agentCard.skills \
'[{"id":"chat","name":"chat","description":"Bridge chat/messages to OpenClaw agents"}]'
把 <YOUR_IP> 换成对端能访问到的 IP(Tailscale IP、局域网 IP 或公网 IP)。
这里有个很容易搞混的点:
agentCard.url 指向你的 JSON-RPC 端点,路径是 /a2a/jsonrpc
后面配 Peer 时的 agentCardUrl 指向对端的 Agent Card,路径是 /.well-known/agent-card.json
一个是"怎么发消息给我",一个是"去哪里了解对方"。别搞反了。
openclaw config set plugins.entries.a2a-gateway.config.server.host '0.0.0.0'
openclaw config set plugins.entries.a2a-gateway.config.server.port 18800
0.0.0.0 表示监听所有网卡接口。端口默认 18800。
强烈建议开启 Token 认证,不然谁都能跟你的 Agent 说话:
TOKEN=$(openssl rand -hex 24)
echo "你的 A2A Token: $TOKEN"
openclaw config set plugins.entries.a2a-gateway.config.security.inboundAuth 'bearer'
openclaw config set plugins.entries.a2a-gateway.config.security.token "$TOKEN"
这个 Token 一定要保存好,后面对端连你的时候需要它。
建议把 Token 放到环境文件里,不要写进任何 Git 仓库:
mkdir -p ~/.config/openclaw
chmod 700 ~/.config/openclaw
echo "OPENCLAW_A2A_TOKEN=$TOKEN" >> ~/.config/openclaw/gateway.env
chmod 600 ~/.config/openclaw/gateway.env
openclaw config set plugins.entries.a2a-gateway.config.routing.defaultAgentId 'main'
如果你的 OpenClaw 跑了多个 Agent(比如 main、coder、researcher),这里设的是默认把 A2A 消息路由给谁。发送方也可以用 --agent-id 参数指定。
openclaw gateway restart
# 看 Agent Card 能不能正常返回
curl -s http://localhost:18800/.well-known/agent-card.json | python3 -m json.tool
# 确认插件已加载
openclaw plugins list
# 确认端口在监听
ss -tlnp | grep 18800 # Linux
lsof -i :18800 # macOS
如果 curl 返回了带 name、skills、url 的 JSON,恭喜,单机配置完成。
单机跑通只是第一步。要实现 Agent 间通信,还得把对方加为 Peer。
openclaw config set plugins.entries.a2a-gateway.config.peers '[
{
"name": "CloudBot",
"agentCardUrl": "http://<PEER_IP>:18800/.well-known/agent-card.json",
"auth": {
"type": "bearer",
"token": "<对端的 TOKEN>"
}
}
]'
openclaw gateway restart
如果你想让两边都能互相发消息,两台机器都要把对方加为 Peer:
Server A 的操作:
- 生成自己的 A_TOKEN
- 把 Server B 加为 Peer(用 B 的 TOKEN)
Server B 的操作:
- 生成自己的 B_TOKEN
- 把 Server A 加为 Peer(用 A 的 TOKEN)
简单说就是:**各自生成 Token,互相交换。**跟两个人互加微信好友一个道理。
peers 是个数组,可以同时加好几个:
openclaw config set plugins.entries.a2a-gateway.config.peers '[
{"name":"Server-B","agentCardUrl":"http://100.10.10.2:18800/.well-known/agent-card.json",
"auth":{"type":"bearer","token":"B_TOKEN"}},
{"name":"Server-C","agentCardUrl":"http://100.10.10.3:18800/.well-known/agent-card.json",
"auth":{"type":"bearer","token":"C_TOKEN"}}
]'
插件自带了一个基于 @a2a-js/sdk 的脚本,可以直接在终端发消息:
PLUGIN=~/.openclaw/workspace/plugins/a2a-gateway
node $PLUGIN/skill/scripts/a2a-send.mjs \
--peer-url http://<PEER_IP>:18800 \
--token <PEER_TOKEN> \
--message "你好,我是 Server A!"
脚本会自动发现对端的 Agent Card,处理认证,打印响应。
如果你的 Prompt 需要对端处理很久(比如多轮推理、深度分析),用同步模式可能会超时。这时候用异步模式:
node $PLUGIN/skill/scripts/a2a-send.mjs \
--peer-url http://<PEER_IP>:18800 \
--token <PEER_TOKEN> \
--non-blocking \
--wait \
--timeout-ms 600000 \
--poll-ms 1000 \
--message "分析 A2A 协议的优缺点,写一份 3000 字报告"
原理是先发送任务(立即返回 Task ID),然后持续轮询 tasks/get 接口直到任务完成。
--non-blocking:不阻塞,立即返回
--wait:自动轮询等待结果
--timeout-ms:超时时间,默认 10 分钟
--poll-ms:轮询间隔,默认 1 秒
如果对端跑了多个 Agent,你可以指定消息发给哪一个:
node $PLUGIN/skill/scripts/a2a-send.mjs \
--peer-url http://<PEER_IP>:18800 \
--token <PEER_TOKEN> \
--agent-id coder \
--message "帮我跑一下健康检查"
注意:--agent-id 是这个插件的扩展功能(非 A2A 标准),只在 JSON-RPC 传输上可靠。
# 发本地文件
node $PLUGIN/skill/scripts/a2a-send.mjs \
--peer-url http://<PEER_IP>:18800 \
--token <PEER_TOKEN> \
--file-path /path/to/document.pdf \
--message "请分析这份文档"
# 发 URL 引用
node $PLUGIN/skill/scripts/a2a-send.mjs \
--peer-url http://<PEER_IP>:18800 \
--token <PEER_TOKEN> \
--file-uri https://example.com/report.pdf \
--message "看看这份报告"
上面的方式都是人在终端里敲命令。但我们的终极目标是:用户跟 Agent 说一句自然语言,Agent 自己去调 Peer。
要实现这个,你需要在 Agent 的 TOOLS.md 里加一段 A2A 说明。插件提供了模板 skill/references/tools-md-template.md,把它塞到你的 TOOLS.md 里:
## A2A Gateway (Agent-to-Agent Communication)
You have an A2A Gateway plugin running on port 18800.
### Peers
| Peer | IP | Auth Token |
|------|-----|------------|
| CloudBot | 100.10.10.2 | <B_TOKEN> |
### How to send a message to a peer
Use the exec tool to run:
bash
node <PLUGIN_PATH>/skill/scripts/a2a-send.mjs \
--peer-url http://100.10.10.2:18800 \
--token <B_TOKEN> \
--message "YOUR MESSAGE HERE"
配完之后,你就可以这样用了:
"帮我问一下 CloudBot 服务器的状态"
"让 CloudBot 跑一下代码检查"
"Send to CloudBot: summarize today's logs"
Agent 会自动解析意图、拼接命令、调用 Peer、返回结果。这才是 Agent-to-Agent 的正确打开方式。
两台机器要互相访问,有三个方案:
Tailscale 会在你的设备之间建立一个加密的 Mesh 网络,不用配防火墙、不用端口转发。
# 两台机器都装
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up # 用同一个账号登录
# 看各自的 IP
tailscale status # 会分配 100.x.x.x 的地址
# 测试连通
ping 100.x.x.x
然后在 A2A 配置里用 Tailscale 的 100.x.x.x IP 就行了。
优势很明显:零配置、NAT 穿透、端到端加密、跨地域(家 ↔ 云 ↔ 办公室都行)。
两台机器在同一个网络里?直接用局域网 IP,确保 18800 端口没被防火墙挡住就行。
用公网 IP + Token 认证。建议加防火墙规则只放行已知 IP:
sudo ufw allow from <PEER_PUBLIC_IP> to any port 18800
下面是一个完整的双机部署流程,假设用 Tailscale 组网:
# 生成 Token
A_TOKEN=$(openssl rand -hex 24)
echo "Server A token: $A_TOKEN"
# Agent Card
openclaw config set plugins.entries.a2a-gateway.config.agentCard.name 'Server-A'
openclaw config set plugins.entries.a2a-gateway.config.agentCard.url \
'http://100.10.10.1:18800/a2a/jsonrpc'
openclaw config set plugins.entries.a2a-gateway.config.agentCard.skills \
'[{"id":"chat","name":"chat","description":"Chat bridge"}]'
# 服务器 + 安全 + 路由
openclaw config set plugins.entries.a2a-gateway.config.server.host '0.0.0.0'
openclaw config set plugins.entries.a2a-gateway.config.server.port 18800
openclaw config set plugins.entries.a2a-gateway.config.security.inboundAuth 'bearer'
openclaw config set plugins.entries.a2a-gateway.config.security.token "$A_TOKEN"
openclaw config set plugins.entries.a2a-gateway.config.routing.defaultAgentId 'main'
# 添加 Server B 为 Peer(需要 B 的 Token)
openclaw config set plugins.entries.a2a-gateway.config.peers \
'[{"name":"Server-B",
"agentCardUrl":"http://100.10.10.2:18800/.well-known/agent-card.json",
"auth":{"type":"bearer","token":"<B_TOKEN>"}}]'
openclaw gateway restart
B_TOKEN=$(openssl rand -hex 24)
echo "Server B token: $B_TOKEN"
openclaw config set plugins.entries.a2a-gateway.config.agentCard.name 'Server-B'
openclaw config set plugins.entries.a2a-gateway.config.agentCard.url \
'http://100.10.10.2:18800/a2a/jsonrpc'
openclaw config set plugins.entries.a2a-gateway.config.agentCard.skills \
'[{"id":"chat","name":"chat","description":"Chat bridge"}]'
openclaw config set plugins.entries.a2a-gateway.config.server.host '0.0.0.0'
openclaw config set plugins.entries.a2a-gateway.config.server.port 18800
openclaw config set plugins.entries.a2a-gateway.config.security.inboundAuth 'bearer'
openclaw config set plugins.entries.a2a-gateway.config.security.token "$B_TOKEN"
openclaw config set plugins.entries.a2a-gateway.config.routing.defaultAgentId 'main'
openclaw config set plugins.entries.a2a-gateway.config.peers \
'[{"name":"Server-A",
"agentCardUrl":"http://100.10.10.1:18800/.well-known/agent-card.json",
"auth":{"type":"bearer","token":"<A_TOKEN>"}}]'
openclaw gateway restart
# A 看 B 的名片
curl -s http://100.10.10.2:18800/.well-known/agent-card.json
# B 看 A 的名片
curl -s http://100.10.10.1:18800/.well-known/agent-card.json
# A → B 发消息
node $PLUGIN/skill/scripts/a2a-send.mjs \
--peer-url http://100.10.10.2:18800 \
--token <B_TOKEN> \
--message "Hello from Server A!"
# B → A 发消息
node $PLUGIN/skill/scripts/a2a-send.mjs \
--peer-url http://100.10.10.1:18800 \
--token <A_TOKEN> \
--message "Hello from Server B!"
看到对端的回复了?恭喜,你的两个 Agent 已经学会互相"打电话"了。
上面那一堆命令看着头疼?其实这个项目还自带了一个 Skill,可以让 AI Agent 自己来执行配置流程。
因为手动配置有太多容易搞错的地方:
把 agentCard.url 和 peers[].agentCardUrl 搞反
plugins.load.paths 写了相对路径(必须用绝对路径)
忘了更新 TOOLS.md(Agent 不知道怎么调 Peer)
只配了单向 Peer(两边都得互相加)
Skill 把这些全编码成了标准流程。
根据你用的工具选一种:
# OpenClaw
cp -r ~/.openclaw/workspace/plugins/a2a-gateway/skill \
~/.openclaw/workspace/skills/a2a-setup
# Codex CLI
cp -r ~/.openclaw/workspace/plugins/a2a-gateway/skill \
~/.codex/skills/a2a-setup
# Claude Code
cp -r ~/.openclaw/workspace/plugins/a2a-gateway/skill \
./skills/a2a-setup
装好后直接跟 Agent 说"配置 A2A"或"Add an A2A peer",它就会按流程自动走。
A2A 请求到了网关,但 Agent 没处理。两个常见原因:
原因 1:没配 AI Provider
openclaw config get auth.profiles
# 空的话,先配一个 Provider
原因 2:Agent 处理超时
解法一:发送方用异步模式 --non-blocking --wait
解法二:加大超时时间
openclaw config set \
plugins.entries.a2a-gateway.config.timeouts.agentResponseTimeoutMs 600000
插件没加载成功。挨个检查:
openclaw config get plugins.allow # 白名单里有没有
openclaw config get plugins.load.paths # 路径对不对(绝对路径!)
cat /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log | grep a2a # 看日志
ss -tlnp | grep 18800 # 在不在监听
openclaw gateway restart # 重启试试
sudo ufw status # 防火墙有没有挡
九成是 Token 没对上。检查这几个点:
复制 Token 时有没有多余空格或换行
对端改了 Token 但你这边没同步
对端 inboundAuth 是 none 但你配了 Token(或反过来)
macOS 上 Gateway 是通过 launchd 管理的,确保用正确的方式操作:
openclaw gateway install # 注册 launchd 服务
openclaw gateway restart # 重启
A2A 协议现在还很年轻,这个插件我也还在持续迭代(持久化任务存储、SSE 流式输出、健康检查、审计日志等都在 Roadmap 上,欢迎 PR),但核心的双向通信链路已经跑通了。
如果你也在自己的机器上跑着 OpenClaw,想让不同机器上的 Agent 协作起来,可以试试。Tailscale + A2A Gateway,十分钟就能让两个 Agent 互相说上话。
更重要的是,A2A 是一个开放标准。你的 OpenClaw Agent 不只能跟另一个 OpenClaw 通信——理论上任何实现了 A2A 协议的 Agent(不管是 Google ADK 写的、LangGraph 写的、还是 CrewAI 写的)都可以成为你的 Peer。
这才是 Agent 网络的起点。有问题欢迎在 GitHub 提 Issue。
📦 项目:https://github.com/win4r/openclaw-a2a-gateway
