🌉 claude-code-mcp-bridge 架构 & 通知逻辑

调用链 · 核心模块 · 回调身份决策 · 反馈环风险  ·  2026-06-25

01

架构总览

三层结构:MCP 客户端 → bridge daemon → Claude CLI + 飞书回调

Hermes mcp_cc_claude_run Pi mcp_cc_claude_run Cursor / Desktop stdio / HTTP BRIDGE DAEMON :8787 MCP Server (6 tools) TaskRegistry SessionStore Notifier spawn claude CLI --output-format stream-json AgentEvent stream events fire-and-forget lark-cli +messages-reply +messages-send 飞书 API ✅/❌ 通知卡片 GET /api/state Dashboard :8787/ MCP JSON-RPC spawn子进程 notify回调 事件流
02

运行模式

两种接入方式,生产用 HTTP daemon 模式共享任务注册表

📦

stdio 模式

mcp

一个进程服务一个 MCP 客户端。进程退出,所有任务记录消失(内存注册表,不持久化)。适合 Cursor / Claude Desktop 单机接入。

查看启动命令
node bin/claude-code-mcp-bridge.mjs mcp \
  --cwd-root ~/projects
🌐

HTTP 模式

serve daemon

所有客户端共享同一个任务注册表 + Session Store,跨 Hermes / Pi 可见。launchd 守护,监听 127.0.0.1:8787

端点列表
路径用途
/mcpMCP JSON-RPC 入口
/api/state任务 + session 快照
/Dashboard(实时轮询)
/healthz存活探针
03

核心模块

4 个模块分工明确,Notifier 是回调身份问题的根源所在

🤖

ClaudeAdapter

封装 claude CLI 进程,把 stdout 转成 AgentEvent 异步流。

关键文件
adapter.tsstream-json.tslocal-sessions.ts
📋

TaskRegistry

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

生命周期

running → done/error/cancelled → 触发 Notifier → 释放所有 waiters

💾

SessionStore

持久化 Claude session ID 到 sessions.json。重启后可用 session_id 续传对话。

session-store.ts
🔔

Notifier

任务完成/失败时 fire-and-forget 调 lark-cli身份由 as_identity + lark_home 共同决定。

默认值

as_identity 默认 "user"(notifier.ts:38)。不传 lark_home 则用 daemon 自己的环境,大概率触发 230002。

04

完整调用链

从 Hermes 发起到飞书收到通知的全流程

1

Hermes 调用 mcp_cc_claude_run

传入 promptcwdmodelsession_id(续传)、notify_target(回调配置)

2

MCP Server 验证 + 分发

校验 cwd-root 约束 → 生成 UUID task_id → 调用 TaskRegistry.start()

3

ClaudeAdapter spawn 子进程

claude -p <prompt> --output-format stream-json,进程 stdout 持续推送 JSON 事件

4

立即返回 task_id(不阻塞)

Hermes 收到 task_id 后可用 claude_status / claude_wait 轮询进展

~

后台:事件流 → TaskRegistry 缓冲

text_delta 累加文本 · tool_use 更新 currentTool · agent_end 触发状态变 done/error

完成后自动触发 Notifier

状态变 terminal(done/error/cancelled)→ fireFeishuNotification() → spawn lark-cli → 飞书收到卡片


通知卡片内容(两种时机)

🚀 派出通知notify_on_start: true 时发)

task_id · cwd · model

✅/❌/⛔ 结束通知(状态变 terminal 时必发)

duration · tools 数 · tokens · cost · 最后 1500 字输出

05

回调身份决策

as_identity + lark_home 共同决定飞书里谁在发通知

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 监听
🔴 反馈环!通知被 gateway 当新指令

消息路由规则(notifier.ts 实现顺序,anchor 优先于 chat_id)

条件lark-cli 命令效果
anchor_msg_id + reply_in_thread: true +messages-reply --reply-in-thread 进飞书话题(thread)✅
anchor_msg_id,无 reply_in_thread +messages-reply reply 那条消息,主聊流 ✅
无 anchor,有 chat_id +messages-send --chat-id 直接发群主聊 ✅
anchor 和 chat_id 都没有 静默丢弃,只打 warn log ⚠️
06

反馈环(自激)风险

三个参数同时命中 → CC 通知被 gateway 当新指令 → 半夜吵醒爸爸

⚠️ 触发条件(三条同时成立)

as_identity: "user"    ② reply_in_thread: false(发主聊)   ③ 目标群在 gateway 监听

派 CC
bridge 用爸爸账号发主聊通知
gateway 误判为新指令
Hermes 开新 session
半夜回复爸爸 💀
真实案例(2026-06-25 01:31)
01:31:37  Hermes 派 CC 跑 pi-yunzhan loose-threads
          notify_target 三条撞了:
            ① as_identity = "user"
            ② reply_in_thread = false
            ③ chat_id = pi-yunzhan 群(gateway 监听中)

01:31:41  飞书 gateway 收到"爸爸在群里发了消息"
          → 开新 session 20260625_013140_e0ab8582
          → 把 CC 自报的"🚀 任务已派出..."当成指令

01:34:34  Hermes 懵逼查了 thread / task / session
          → 主动回复爸爸"这是 CC notify..."
          → 爸爸半夜被叫醒

✅ 修法 A:reply_in_thread: true

通知进话题,gateway 通常不监听 thread 子消息,不会误触发

✅ 修法 B:as_identity: "bot"

gateway 识别为自家 bot 消息,直接 ignore,不开新 session

🔒 铁律

reply_in_thread=false(发主聊)时,必须as_identity="bot"。禁止 user + 主聊 + 被监听群三条同时出现。

07

MCP 工具接口(6 个)

异步任务模型:run 立即返回,status/wait 轮询,cancel 终止

工具功能关键参数
claude_run启动异步任务,立即返回 task_idprompt · 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,可续传
source: ~/projects/claude-code-mcp-bridge/src/ · html-artifact-output skill · 2026-06-25