🌉 claude-code-mcp-bridge

架构图 · 调用链 · 回调通知逻辑 · 反馈环风险  |  2026-06-25

运行模式

📦 stdio 模式 mcp

MCP Clientstdin/stdoutbridge process
一个进程服务一个客户端。进程退出,所有任务记录消失。适合 Cursor / Claude Desktop 单机接入。

🌐 HTTP 模式 serve daemon

HermesPOST /mcpshared daemon :8787
PiPOST /mcp
所有客户端共享一个任务注册表 + Session Store。launchd 守护进程,当前 PID 监听 127.0.0.1:8787。

核心模块

🤖

ClaudeAdapter

封装 claude CLI 进程。调用 claude -p … --output-format stream-json,把 stdout 转成 AgentEvent 流。

adapter.tsstream-json.ts
📋

TaskRegistry

内存注册表。每个 task 有独立事件缓冲(最多 5000 条)和 waiters 队列,供 claude_wait 长轮询。

task-registry.tsuuid
💾

SessionStore

持久化 Claude 会话 ID。Task 完成时落盘到 sessions.json,重启后可用 session_id 续传。

session-store.tsJSON
🔔

Notifier

任务完成/失败时 fire-and-forget 调用 lark-cli,把结果卡片发到飞书。身份由 notify_target 决定。

notifier.tslark-cli

完整调用链 (Hermes → CC → 飞书)

STEP 1
Hermes 调用 mcp_cc_claude_run
传入 promptcwdmodelsession_id(续传用)、notify_target(回调配置)
STEP 2
MCP Server 接收请求
验证 cwd-root 约束 → 生成 task_id (UUID) → 调用 TaskRegistry.start()
STEP 3
ClaudeAdapter.run() spawn 子进程
claude -p <prompt> --output-format stream-json
进程 stdout 持续推送 JSON 事件流
STEP 4
立即返回 task_id
不等 Claude 跑完。Hermes 拿到 task_id 后可用 claude_status / claude_wait 轮询
STEP 5 — 后台持续
事件流 → TaskRegistry 缓冲
text_delta 累加 → tool_use 更新 currentTool → agent_end 触发完成 → 状态变 done/error → 通知 Notifier → 释放所有 waiters
回调路径
NOTIFY A — 任务派出(可选)
notify_on_start = true 时立即发
内容:🚀 task_id + cwd + model,让爸爸知道任务已派出
NOTIFY B — 任务结束
状态变 done / error / cancelled
内容:✅/❌/⛔ + duration + tools 数 + tokens + cost + 最后 1500 字输出
NOTIFY — 路由判断
anchor_msg_id 优先于 chat_id
anchor_msg_id+messages-reply
reply_in_thread → 加 --reply-in-thread(进话题)
无 anchor → +messages-send --chat-id(主流)
两者都无 → 静默丢弃 + log warn
NOTIFY — 身份判断
as_identity + lark_home 决定谁发
as_identity 默认 "user"
lark_home 决定用哪套 lark-cli 凭据
~/.hermes → 爸爸账号发  |  ~/.pi → Pi 账号发
不传 → daemon 自己的环境 → 易 230002 报错
NOTIFY — 执行
spawn lark-cli 子进程
fire-and-forget,失败只 log 不 crash bridge。
LARK_BIN 环境变量可覆盖 lark-cli 路径

回传身份决策规则

谁在飞书里发出那条通知消息?

❶ 传了 lark_home: "~/.hermes" + as_identity: "user"
✅ 爸爸账号(Hermes user token)发消息
❷ 传了 lark_home: "~/.pi" + as_identity: "user"
✅ Pi 账号(Pi user token)发消息
❸ 传了 as_identity: "bot"(任意 lark_home)
✅ Bot 身份发消息(bot 必须是群成员)
❹ 没传 lark_homeas_identity 走默认 "user"
⚠️ daemon 自己的 user 环境 → 大概率不在群里 → 230002
❺ 传了 as_identity: "user" + 监听群 + 主流
🔴 反馈环!gateway 把通知当新指令处理

消息路由规则(notifier.ts 实现顺序)

条件 lark-cli 命令 效果
anchor_msg_id + reply_in_thread: true +messages-reply --message-id <om_xxx> --reply-in-thread 进入飞书话题(thread),爸爸在话题内看到
anchor_msg_id,没有 reply_in_thread +messages-reply --message-id <om_xxx> Reply 那条消息,进主聊流
无 anchor_msg_id,有 chat_id +messages-send --chat-id <oc_xxx> 直接发群主聊流
anchor 和 chat_id 都没有 静默丢弃,只打 warn log

🔴 反馈环(自激)风险

⚠️ 触发条件:三个参数同时命中

as_identity: "user"
+
reply_in_thread: false
+
chat_id = 正在被 gateway 监听的群
派 CC
bridge 用爸爸账号在群主聊发通知
gateway 误判为爸爸新指令
Hermes 开新 session 回复
半夜吵醒爸爸 💀
✅ 修法 Areply_in_thread: true — 通知进话题,gateway 不监听 thread 子消息
✅ 修法 Bas_identity: "bot" — gateway 识别为自家 bot 消息,直接 ignore
⚠️ 铁律 reply_in_thread=false 时必须配 as_identity="bot",禁止两个都走 user+主聊

MCP 工具接口 (6 个)

工具名功能关键参数
claude_run启动异步任务,立即返回prompt, cwd, model, session_id, notify_target
claude_status获取任务快照(文本/状态/计数器)task_id
claude_wait长轮询新事件(chunk-by-chunk)task_id, from_seq, timeout_ms
claude_cancelSIGTERM → SIGKILL 终止任务task_id
claude_list列出所有任务(运行中+已结束)
claude_forget从内存删除已结束任务task_id
claude_sessions列出持久化 session,可用 session_id 续传
Generated from source: ~/projects/claude-code-mcp-bridge/src/  ·  2026-06-25