Architecture Report

claude-code-
mcp-bridge

把本地 Claude Code CLI 包装成 MCP 服务器——异步任务模型、持久化 session、飞书回调通知。本报告梳理架构、调用链与通知回传身份逻辑。

source: ~/projects/claude-code-mcp-bridge/src/  ·  2026-06-25

7
MCP 工具接口
4
核心模块
2
运行模式
4
消息路由规则
01 · Architecture

架构总览

三层结构:MCP 客户端 → bridge daemon → Claude CLI 子进程 + 飞书回调。daemon 以 HTTP 模式常驻,所有客户端共享同一任务注册表。

CLIENTS BRIDGE DAEMON :8787 SERVICES Hermes mcp_cc_claude_run Pi mcp_cc_claude_run Cursor / Desktop stdio / HTTP POST /mcp MCP Server 7 tools exposed TaskRegistry in-memory · ≤5000 events/task SessionStore sessions.json Notifier fire-and-forget ClaudeAdapter adapter.ts · stream-json.ts claude -p … --output-format stream-json AgentEvent stream text_delta · tool_use · agent_end spawn() events notify claude CLI Claude Code process stdout → stream-json events lark-cli +messages-reply / +messages-send as user / as bot · lark_home Feishu API im.v1 messages pad GET /api/state Dashboard :8787/ MCP JSON-RPC spawn 子进程 通知回调 事件流
02 · Modes

运行模式

两种接入方式。生产环境用 HTTP daemon 模式,所有客户端共享一个任务注册表和 Session Store。

stdio 模式

mcp

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

node bin/claude-code-mcp-bridge.mjs mcp

HTTP 模式

serve daemon

所有客户端共享同一任务注册表 + SessionStore。launchd 守护进程常驻,监听 127.0.0.1:8787。Hermes 和 Pi 同时可见对方的任务。

POST /mcp GET /api/state GET /healthz GET /
03 · Modules

核心模块

4 个模块分工明确。Notifier 是回调身份问题的唯一来源。

ClaudeAdapter

封装 claude CLI 子进程,把 stdout NDJSON 转成 AgentEvent 异步迭代器流。

adapter.tsstream-json.tslocal-sessions.ts

TaskRegistry

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

task-registry.tsUUID

SessionStore

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

session-store.tsJSON file

Notifier

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

notifier.tslark-cli
04 · Call Chain

完整调用链

从 Hermes 发起到飞书收到通知卡片的全流程。claude_run 立即返回 task_id,Claude 在后台持续跑。

发起方 → Bridge

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 → 释放 waiters → 触发 Notifier

回调通知卡片内容

notify_on_start 可选,派出时立即发

task_id · cwd · model

done / error / cancelled 状态变 terminal 必发

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


消息路由规则(anchor 优先)

条件命令效果
anchor_msg_id + reply_in_thread:true+messages-reply --reply-in-thread进话题 thread
anchor_msg_id,无 reply_in_thread+messages-replyReply 主聊流
无 anchor,有 chat_id+messages-send --chat-id发群主聊
两者都没有静默丢弃,只打 warn
05 · Identity

回调身份决策

as_identity(默认 "user")+ lark_home 共同决定飞书里谁在发通知消息。这也是"有时 user 有时 bot"的根本原因。

1 lark_home: "~/.hermes" + as_identity: "user"
爸爸账号(Hermes user token)发消息
2 lark_home: "~/.pi" + as_identity: "user"
Pi 账号(Pi user token)发消息
3 as_identity: "bot"(任意 lark_home)
Bot 身份(bot 需是群成员)
4 不传 lark_homeas_identity 走默认 "user"
daemon 自己的 user 环境 → 大概率 230002 报错
5 as_identity: "user" + 主聊 + 群在 gateway 监听
反馈环!通知被 gateway 当新指令处理
06 · Danger

反馈环(自激)风险

三个参数同时命中 → 半夜吵醒爸爸

触发条件(三条并成立):

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

派 CC
bridge 用爸爸账号在主聊发通知
gateway 误判为新指令
Hermes 开新 session
半夜主动回复爸爸

真实案例:2026-06-25 01:31,pi-yunzhan 群,notify_target 三条同时命中,session 20260625_013140_e0ab8582 被开,Hermes 懵逼查了 thread/task,01:34 主动回复。

修法 A:reply_in_thread: true

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

修法 B:as_identity: "bot"

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

铁律

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

07 · API

MCP 工具接口

7 个工具。异步任务模型:claude_run 立即返回,claude_status / claude_wait 轮询,claude_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,可用 session_id 续传