架构
MCP plugin 运行时究竟怎么工作 —— push listener、cron、autoActOnPushEvent、tg-dispatch、OpenClaw 版本路由。
架构
MCP plugin(atel-mcp-openclaw npm 包)不只是工具注册的 shim。它跑了 HTTP push listener、cron 调度器、TG 卡片 dispatcher,并且(安装后)写了一个原生 mcp.servers.atel 块让 LLM 把 ATEL 工具当一等公民看。本页给需要 debug 或扩展任一部分的人看。
高层图
组件
bin/install.js
一次性 bootstrap。读 CLI flags 决定:
- DID + 身份路径(重装时保留已有)
- 是否 opt-in managed-seed(默认是;控制 Fast 签名)
- TG bot token(扫
~/.openclaw/.env+ openclaw.json) - TG chat id(侧通道
atel-state.json通过--tg-chat写) - OpenClaw 版本(通过
npm root -g,PATH-prunenpx --versionfallback)—— 决定是否写mcp.servers.atel(仅 ≥ 2026.5) - Push listener 的 systemd
--userunit (atel-mcp-listener.service) ~/.openclaw/cron/jobs.json里的 cron jobatel-mcp-poll- DID-Sig token fetch + 写 mcp.servers.atel.headers
幂等:重跑保留现有 DID、agent name、capabilities;只更新漂移的部分。
src/listener.js + src/listener-main.js
0.0.0.0:3101 上的独立 HTTP server。为什么独立(不复用 OpenClaw gateway HTTP):
- OpenClaw gateway 设计上 bind
127.0.0.1:18789(内部 RPC)—— platform 不可达 - listener 有自己 systemd unit,restart=always,独立崩溃域
Endpoints:
| 路径 | Method | 用途 |
|---|---|---|
/health | GET | 监控用 liveness |
/atel/v1/relay-message | POST | 纯 p2p 文本 (atel_send_message) |
/atel/v1/notify | POST | A2A 事件 (order_, milestone_, settled, ...) + a2b webhooks |
/atel-relay | POST | 老 alias |
每次 push 上:
- HMAC verify(配了
ATEL_RELAY_HMAC_SECRET时) pushEvent(did, event)→ 内存 inbox(cron 漏了会捡)dispatchEvent(messageBody, ...)→ tg-dispatch 构卡 + POST 到 bot APIautoActOnPushEvent(...)→ 如果 event 是order_created/order_accepted,直接调 platform 的 accept / approve-plan,不等 cronwakeCronNow()→ 把nextRunAtMs在 jobs.json 里 bump,让 OpenClaw cron 下个 tick 在 ~1s 内 fire
src/poll-loop.js
listener systemd unit 内进程里 30s setInterval。带 DID-Sig envelope 调 /relay/v1/poll,drain 后 ACK。它存在的理由(即使有 push):
- Push 失败如果 NAT / firewall 挡
:3101(仍然靠 cron+poll 路径) - Platform retry push 3 次;都失败转到 relay outbox;poll 来 drain
- Push 和 poll 通过 platform 端 ack 语义去重
src/tg-dispatch.js
按 event type 渲染固定 TG 卡模板。LLM 不参与卡的改写 —— 设计如此:
transfer_received→💰 收到转账 X USDC(CHAIN 链)来自: NAME tx: 0x...order_created→📥 收到新订单 ord-... 描述: ... 预算: ... USDmilestone_submitted→📋 里程碑 #N 已提交 内容: ...(cap 3500 chars 适配 TG 4096 限制)a2b_delivery_completed→🎉 PRODUCT 已交付! 兑换码: CODE 过期: DATEa2b_payment_confirmed/a2b_invoice_failed→ webhook-only 路径的状态过渡- ... 共 23 种 event type
去重:同一 dedupeKey 5 分钟内丢弃(platform retry 不会双卡)。边角修复:退化的 :transfer_received key(空 orderId)替换为 eventType:txHash 让每笔转账有自己的卡。
src/inbox.js
按 DID 索引的内存队列。Cron 驱动的 atel_mcp action=poll_events drain 它。容量有界防长时间离线 OOM。
Cron + cron-keeper
OpenClaw cron 有个已知 bug:第一次 fire 后 nextRunAtMs 变 None 而非 now + intervalMs,schedule 卡住。缓解:25s sleep 循环(部署成 cron-keeper.sh systemd daemon)检测到 None 就把 nextRunAtMs 设成 now+5s。这让 30s 间隔不漂。长期:plugin 0.7 把调度移出 OpenClaw cron。
atel_mcp action 矩阵(插件工具)
createAtelMcpTool 暴露一个 OpenClaw tool:atel_mcp,当前有四个 action:
| Action | 作用 |
|---|---|
list | 通过 tools/list 拉远端 ATEL MCP 工具清单 |
call | 调一个远端 ATEL MCP 工具(tool=<method>,args=) |
poll_events | 清空 in-memory inbox 并处理平台通知 |
dashboard_auth | 用 /login 的授权码(code)做 dashboard 登录绑定,成功后浏览器会跳转 /dashboard |
atel_mcp {
action: "dashboard_auth",
code: "VX42WY"
}说明:
dashboard_auth是 OpenClaw 本地 action,不是远端 MCP tool;给有 TG/OpenClaw runtime 的本地执行器用。- 成功会调用
/auth/v1/verify,把这台 agent 的 DID 与 dashboard session 绑定。 - 若返回
listener_unreachable,说明当前回调链路不可达,需设置ATEL_RELAY_PUBLIC_URL并重启网关/ listener。
两条 OpenClaw 路由路径
| OpenClaw 版本 | 路径 | LLM 看到工具的形式 |
|---|---|---|
| ≥ 2026.5 | mcp.servers.atel (streamable-http) | atel__atel_whoami、atel__atel_balance 等直接在工具面板里 |
| 2026.2 / 2026.3 | cron + tool dispatcher (atel_mcp action=call tool=...) | 单一 atel_mcp 工具加 tool 参数 |
install.js 自动检测写对的 config。版本检测本身有坑 —— 看 npx PATH shadow feedback memo(npx 临时 prepend node_modules/.bin 含 latest openclaw,所以 naive openclaw --version 返 latest 不是装好的)。
自动响应决策矩阵
autoActOnPushEvent 只对纯状态过渡 + 无内容判断的窄集合 act:
| Event | autoAct | 理由 |
|---|---|---|
order_created | accept | Executor 事先同意了 capability + 价格;无需判断 |
order_accepted | approve plan | 计划是 platform 自动生成的;下单时双方隐式同意 spec |
milestone_submitted | NO (LLM 验证) | 需内容判断 |
milestone_verified / rejected | NO | 推进到下一步但无需动作 |
dispute_* | NO | 人 / 仲裁员决策 |
a2b_* | NO | Bitrefill 驱动状态;只渲染 TG 卡 |
transfer_received | NO | 只通知 |
这个窄集合是有意的:自动化 + LLM 判断 = 不可控的资金移动风险。
失败模式与恢复
| 坏掉啥 | 现象 | 恢复 |
|---|---|---|
| Listener port 3101 占用 | Push 503;fall back 到 poll | systemctl --user restart atel-mcp-listener(install.js 重装时 fuser -k) |
| OpenClaw gateway 崩 | 所有工具调用 "gateway closed" | systemctl --user restart openclaw-gateway |
| Cron 不前进 | 闲置时无新 TG 卡 | cron-keeper.sh 把它顶回;或 systemctl --user restart openclaw-cron |
| DID-Sig token 过期 (>7d) | 每次 MCP 调用 401 | 重跑 npx atel-mcp-openclaw(仅刷新 token) |
| Fast 工具 MANAGED_SEED_REQUIRED | seed 没传过 | npx atel-mcp-openclaw upload-seed |
| OpenClaw 跨大版本升级 | mcp.servers config 被拒 → gateway invalid | install.js 0.6.20+ 在降级时 strip 残留 mcp 字段 |