claude-session-bridge
v0.2.0
Published
MCP message bus that bridges multiple Claude Code sessions (frontend / backend / infra) within a project. Send results from one session, auto-receive them in another.
Maintainers
Readme
claude-session-bridge
让两个(或多个)Claude Code 会话互相对话——并且还能选择让它们自我编排。
你为后端打开一个 Claude Code 会话,又为前端打开另一个。通常它们彼此一无所知——当后端改了某个 API,你只能手动把结果复制粘贴到前端会话里。
这个工具去掉了复制粘贴这一步。一个会话发送它的结果,同伴会话便会自动接收到。打开可选的自动模式后,一个完成工作的后端可以唤醒前端去做出响应——无需动手的乒乓往返——与此同时,你仍然完全掌控各项安全护栏(不自动提交、部署关卡、循环保护),并且可以在 Claude 内部修改每一项设置。
第一次用?请从头读到尾——每条命令都包含在内。你不需要理解内部原理就能使用它。
目录
如何想象它
两个简单的概念:
- 项目 = 一个聊天室。 用相同项目名启动的会话处在同一个房间里,可以对话。不同的项目名 = 不同的房间,彼此隔离。
- 角色 = 你在那个房间里的昵称。
backend、frontend、infra——任何你喜欢的标签。你把消息发给某个角色。
会话 1 项目 "shop" 角色 "backend" ┐
├─ 房间 "shop"(它们对话)
会话 2 项目 "shop" 角色 "frontend" ┘
会话 3 项目 "blog" 角色 "backend" ─── 房间 "blog"(独立,隔离)这里没有"连接"按钮。 用相同项目名启动两个会话本身就是把它们连接起来了。
环境要求
- Node.js 18+——用
node --version检查 - Claude Code(即
claude命令)——用claude --version检查 - tmux——推荐安装;默认的自动模式驱动通过它来驱动你正在运行的会话。(如果你只用消息总线,或者使用
spawn驱动,则不需要它。)
安装(一次性)
这件事你只做一次。 之后你再也不用编辑这些文件——每个会话唯一会变的就是两个环境变量(下文会讲)。
推荐:用 npx 安装——无需 clone。 请在一个 Claude Code 会话内部运行这些命令(这样它会针对该会话的配置目录生效——见下方说明):
npx claude-session-bridge install # 注册 MCP 服务器 + 各个 hook
session-bridge doctor # 核对都装好了什么,以及装在哪里install 会把包的运行时复制到 ~/.claude/bridge/runtime/<version>/,并在 settings.json 里注册那个路径——而不是 npx 缓存。npx 缓存会被 npm 定期清理,注册它会让 MCP 服务器悄无声息地坏掉。它还会把接收(receive) hook、block-git hook(拦截 git commit/push/reset),以及一对 SessionStart/SessionEnd hook(记录这个会话的存活状态、cwd、tmux pane 和 session id,让 resume/auto 驱动知道之后该唤醒什么)合并进你当前生效的 settings.json——以幂等方式合并,不会动你的其他设置。如果你希望会话能自行提交,就加上 --no-block-git。
要移除 install 添加的全部内容:
session-bridge uninstall # 只移除我们的 MCP 服务器 + hook 条目
session-bridge uninstall --purge --yes # 同时清空整个总线:消息、会话注册表、已复制的运行时uninstall 只会清除它能识别为自己添加的条目,其余设置原样保留。
工作账号 / 多个配置档(profile)。 当
CLAUDE_CONFIG_DIR这个环境变量被设置时(单独的工作登录账号常见这种情况),Claude Code 会读取$CLAUDE_CONFIG_DIR/settings.json,否则读取~/.claude/settings.json。这是"我改了设置却毫无变化"的头号原因——你改错了文件。在某个配置档的会话内部运行session-bridge install,会自动针对正确的文件生效。每个配置档运行一次即可。用session-bridge doctor来核对,它会打印出当前生效的配置目录。
如果你正在开发这个仓库本身,就用这种方式安装,让 settings.json 指向本地 checkout 的 dist/,而不是复制出来的运行时:
git clone <this-repo-url> claude-session-bridge-mcp
cd claude-session-bridge-mcp
npm install
npm run build
npm link # 可选:把 `session-bridge` 加入 PATH
session-bridge install --no-copy # 直接注册这个仓库的 dist/
session-bridge doctor--no-copy 也是 install 在检测到没有已发布版本的开发 checkout 时内部使用的同一个 flag。
把以下内容加进正确的那个 settings.json(替换 /ABSOLUTE/PATH):
{
"mcpServers": {
"session-bridge": {
"command": "node",
"args": ["/ABSOLUTE/PATH/claude-session-bridge-mcp/dist/server.js"]
}
},
"hooks": {
"UserPromptSubmit": [
{ "hooks": [ { "type": "command",
"command": "node /ABSOLUTE/PATH/claude-session-bridge-mcp/dist/hooks/recv.js" } ] }
],
"PreToolUse": [
{ "matcher": "Bash", "hooks": [ { "type": "command",
"command": "node /ABSOLUTE/PATH/claude-session-bridge-mcp/dist/hooks/block-git.js" } ] }
]
}
}所有 hook 在未接入 bridge 的会话里都会安全地空操作(no-op),所以全局安装它们也没问题。这就是全部的安装步骤。
用 .bridge.json + up 启动会话
手动设置 BRIDGE_PROJECT/BRIDGE_ROLE(下一节)在任何地方都行得通,但如果你的各个角色总是位于同样的仓库路径,不如声明一次,让 up 替你完成这些输入。在仓库根目录放一个 .bridge.json:
{
"project": "shop",
"roles": {
"backend": { "cwd": "." },
"web": { "cwd": "../shop-web", "model": "sonnet" }
}
}cwd 是相对于 .bridge.json 所在位置解析的(没有 cwd 的角色就在那个目录本身运行);model 和 permissionMode 是可选项,up 第一次运行该角色时会用它们填充 spawner 的角色级配置。查找方式和 git 一样,从当前目录向上逐级查找,所以 up 在仓库内任何地方运行都能生效。
启动单个角色:
session-bridge up backend # 设置好 BRIDGE_PROJECT/BRIDGE_ROLE 后启动 `claude`
session-bridge up backend --admin # 同上,但额外授予 BRIDGE_ADMIN=1(可以修改 spawner 设置)一次性启动所有已声明的角色:
session-bridge up在 tmux 里,这会为每个角色各拆出一个新 pane(你当前的 pane 保持不动)并把布局整理成平铺状——每个 pane 里的会话都会通过 SessionStart hook 自动注册。在 tmux 之外,它只会列出该在各自终端里运行的 session-bridge up <role> 命令。
下面的环境变量方式仍然有效,当角色不对应固定的本地路径时(比如被 spawn 出来的或远程的 checkout),就该用这种方式。
连接两个会话
要把一个会话放进某个房间,在启动它时给它两个值:
| 变量 | 含义 | 示例 |
|----------|---------|---------|
| BRIDGE_PROJECT | 房间名 | shop |
| BRIDGE_ROLE | 该会话的昵称 | backend |
# 终端 1 —— 项目 "shop" 的后端
BRIDGE_PROJECT=shop BRIDGE_ROLE=backend claude
# 终端 2 —— 项目 "shop" 的前端
BRIDGE_PROJECT=shop BRIDGE_ROLE=frontend claude两者都用了项目 shop,所以它们在同一个房间里,可以对话。
设置变量的两种方式
选一种用——但别把它们搞混;这是新手第一大坑。
方式 A —— 写在同一行上(最简单,推荐):
BRIDGE_PROJECT=shop BRIDGE_ROLE=frontend claude这些变量只对那一次 claude 生效。没有任何需要清理的东西。
方式 B —— 先设置,再启动。 分行写时你必须用 export:
export BRIDGE_PROJECT=shop
export BRIDGE_ROLE=frontend
claude⚠️ 陷阱:
BRIDGE_PROJECT=shop单独成行而不加export,设置的是一个 shell 变量,claude将看不到它——bridge 会悄无声息地不连接。请使用export(方式 B),或者把所有内容写在一行上(方式 A)。
小技巧——做几个别名(alias),免得每次都重新敲一遍。在 ~/.zshrc(或 ~/.bashrc)里:
alias shop-back='BRIDGE_PROJECT=shop BRIDGE_ROLE=backend claude'
alias shop-front='BRIDGE_PROJECT=shop BRIDGE_ROLE=frontend claude'使用 tmux
tmux 对启动方式毫无影响——每个面板(pane)都是它自己的 shell,就像一个独立的终端。在每个面板里设置好变量并运行 claude:
tmux
├ 面板 1: BRIDGE_PROJECT=shop BRIDGE_ROLE=backend claude
├ 面板 2: BRIDGE_PROJECT=shop BRIDGE_ROLE=frontend claude ← 与面板 1 同一房间
└ 面板 3: BRIDGE_PROJECT=blog BRIDGE_ROLE=backend claude ← 不同房间tmux 对自动模式最为重要:默认驱动会自动唤醒这些正在运行的面板(见事件 spawner)。在 tmux 内部启动的会话会自行登记它的面板——无需任何手动设置。
如果你在某个面板里用了
export(方式 B),随后又在同一个面板里启动另一个项目的claude,旧的export会残留下来。在 tmux 内部建议优先用方式 A,以免出现意外。
发送与接收消息
一旦两个会话共享同一个房间,下面这套就会自然生效:
在后端会话里,当你有值得分享的东西时:
"把新的
/users响应结构发给前端会话。"它会调用
bridge_send工具来投递这条消息。在前端会话里,下一次你输入任何内容时,那条消息会被自动加入它的上下文——无需复制粘贴。
这就是默认的("总线 / bus")模式:发送是有意为之的(模型分享的是一个真实结果,而不是一大段文字),而接收在下一轮自动发生。方向盘仍然握在你手里。
你也可以在任一会话里显式操作:"检查一下 bridge 有没有新消息"(bridge_recv)或者*"把这个发给 infra:staging 已就绪"*(bridge_send)。
如果一个会话完成了工作却没有值得分享的东西,它就干脆不发送——什么都不会上总线,任何乒乓往返都会自然结束。空闲的会话不会刷屏。
角色只是标签
角色就是任意一个你想要的字符串——不限于 backend/frontend。你想往一个房间里加多少会话都行:
BRIDGE_PROJECT=shop BRIDGE_ROLE=backend claude
BRIDGE_PROJECT=shop BRIDGE_ROLE=web claude
BRIDGE_PROJECT=shop BRIDGE_ROLE=infra claude
BRIDGE_PROJECT=shop BRIDGE_ROLE=qa claude发送时,可以指定一个具体角色(to: "infra"),或者广播给其他所有人(to: "*")。一条发给 frontend 的消息只会投递给 frontend——infra/qa 甚至根本看不到它,所以它们不会误打误撞地对它采取行动。(只有 to: "*" 才会送达每个人。)所以一个多步骤的流程很自然:
backend 完成一个 API → 告诉 web → web 更新 UI → 告诉 infra → infra 重新部署。
这条链是否自动运行,取决于下文的 spawner——而对于部署,请先读小心自动部署。
同时运行多个项目
使用不同的项目名;它们永远看不到彼此的消息:
BRIDGE_PROJECT=shop BRIDGE_ROLE=backend claude # 房间 "shop"
BRIDGE_PROJECT=blog BRIDGE_ROLE=frontend claude # 房间 "blog" —— 隔离这种隔离是结构性的(消息存放在按项目划分的文件夹下),所以 shop 和 blog 不可能串到一起。
自动模式:事件 spawner
上面所有内容都把你留在回路里——一个会话只在你给它一个回合时才行动。事件 spawner 是一个可选的后台程序(一个守护进程 / daemon),它去掉了这一步:当一个角色收到消息时,spawner 会自动唤醒那个角色去处理它,于是双方可以在没有人类介入的情况下乒乓往返。你在它自己的终端里运行它;它是选择性启用(opt-in)的,关闭也很容易。
两种驱动:tmux 与 spawn
**驱动(driver)**决定一个角色是怎么被唤醒的:
| | tmux(默认) | spawn |
|---|---|---|
| 它做什么 | 向你已经打开的、正在运行的会话里输入内容(tmux send-keys) | 每个事件启动一个全新的 claude -p 进程 |
| 会话 | 同一个会话延续下去(保留上下文) | 每次都是新会话 |
| 可见吗? | 是——你在面板里看着它跑 | 后台(只有日志) |
| 用 claude -p 吗? | 不用 | 用 |
| 需要 tmux 吗? | 需要 | 不需要 |
打个比方:tmux 是去拍一拍已经坐在工位上的员工;spawn 是为每个任务雇一个新的临时工。
为什么 tmux 是默认:它编排的是你已经在其中工作的打开着的会话,保留它们的上下文,让你能看着并打断它,并且不使用 claude -p(所以它不受 headless/SDK 计费方式变动的影响)。当你无法运行 tmux,或者想要隔离的一次性运行时,就使用 spawn。
配置并运行
一个角色只有在被设置好之后才会被自动唤醒——开箱即用时 spawner 什么都不做,所以你需要一个角色一个角色地选择性启用:
# tmux 驱动:只要在 tmux 内部打开该角色的会话即可——它的面板会自动登记。
# spawn 驱动:告诉它该角色的代码在哪里:
session-bridge spawner set shop backend cwd=/path/to/backend
session-bridge spawner on # 全局启用
session-bridge spawner off shop # ……或者按项目
session-bridge spawner off shop web # ……或者只针对一个角色
session-bridge spawner status # 查看当前配置(session-bridge 是随本包安装的 CLI。如果它不在你的 PATH 上,运行 node /ABSOLUTE/PATH/dist/cli.js spawner ...。)
在它自己的终端里运行守护进程:
session-bridge spawner run # 前台运行;按 Ctrl-C 停止
# 或者把它放到后台:
nohup session-bridge spawner run > ~/.claude/bridge/spawner.log 2>&1 &配置在每个事件发生时都会被重新读取,所以 on/off 及其他改动会实时生效——无需重启。
安全护栏
| 护栏 | 默认 | 它做什么 |
|------|---------|--------------|
| maxHops(循环保护) | 6 | 一个按项目计数的链路计数器,给连续自动唤醒的次数封顶,并在出现一段安静的间隙后重置。约束失控的乒乓往返(对两种驱动都有效)。 |
| rateLimitPerMinute | 12 | 每个角色每分钟最多的自动唤醒次数。 |
| 敏感角色 | infra、qa | 除非被显式启用,否则绝不会被自动唤醒——一条投递错误的消息无法触发部署。 |
| 不自动提交 | 开启 | git commit/push/reset 被拦截(见下文)。 |
| 单飞 / 冷却(single-flight / cooldown) | — | 每个角色同时只允许一次实时运行(spawn);再次推动有一个冷却时间(tmux)。 |
| 必须有目标 | — | 没有实时面板(tmux)或没有 cwd(spawn)的角色永远不会被唤醒。 |
敏感角色默认关闭
infra 和 qa 出厂时就在 defaultOffRoles 里——即使被配置了,spawner 也不会自动唤醒它们。 你必须按名字把它们显式启用。
为什么? 发送方决定一条消息发给谁,而发送方是一个语言模型。如果它有朝一日广播了(to: "*")或者寻址错误,infra/qa 就可能收到一个本不该给它们的请求——而这些角色干的是风险最高的事(部署、发布、破坏性测试)。让它们保持关闭是一种不依赖模型寻址正确的保证:一条投递错误的消息只会静静地待在收件箱里,直到有人类打开那个会话。
把它们打开(如果你想要完全自动化):
session-bridge spawner on shop infra # 显式选择启用(必需)请先读小心自动部署。要修改这份名单,编辑 <BRIDGE_ROOT>/spawner.config.json 里的 defaultOffRoles。
绝不自动提交(以及如何允许它)
默认情况下,自动驱动的会话不能提交。两层机制来保证这一点:
- spawn 驱动: 会话启动时带着
--disallowed-tools "Bash(git commit:*)" "Bash(git push:*)" ...。拒绝(deny)规则优先于允许 / 绕过(allow/bypass),所以模型在物理上无法提交。 - tmux 驱动(实时会话): 那些标志不适用,所以请安装
block-gitPreToolUse hook(在安装里)。无论一个会话的回合是怎么被触发的,它都会拒绝其中的git commit/push/reset。
一条
CLAUDE.md里的提醒("不要提交")算个加分项,但对自主循环来说远远不够——模型会跑偏。hook / 拒绝规则才是真正的保证。
如果你确实想要提交 / 推送自动化(有些人确实想),就退出这层保护:
- 不要安装
block-githook,并且 - 对于 spawn 驱动,从
denyTools(session-bridge spawner配置)里移除 git 相关条目。 - 或者用
BRIDGE_BLOCK_GIT(一个正则;例如允许 commit 但仍然拦截 push)来收窄被拦截的范围。
⚠️ 自动
push是对外的,而且很难撤销。如果你允许它,请推送到一个功能分支(feature branch),并保护好main(分支保护)。不要自动推送到main。
小心自动部署
⚠️ commit/push 被拦截了——但部署不是 git。 kubectl apply、vercel deploy、terraform apply、一个 SSH 部署脚本——这些默认都不会被拦截,所以一个自主的 infra 会话可能会运行它们。部署是对外的,而且很难撤销;在没有人类在场的情况下做这件事是有风险的。
如果你想让 infra 进入回路,从中挑一种:
- 让 infra 保持手动(默认——它是关闭的)。消息排着队;你打开会话,自己去部署。
- 拦截部署命令(把它们加进
denyTools或 block-git 的正则)。 - 只自动化安全的步骤——staging/dry-run 自动化,生产环境手动。
在 Claude 内部修改设置
你不必去编辑 JSON,也不必记住 CLI 标志。MCP 服务器让你能在 Claude 会话内部读取和修改每一项自动化设置——既可以是一个仪表盘式的对话,也可以通过 /mcp。
安全:受 admin 门控。 设置只能从一个用 BRIDGE_ADMIN=1 启动的会话(你的 "control" 会话)里被修改。自动驱动的会话没有这个标志,所以一个失控的循环无法翻动它自己的安全设置。读取配置则始终允许。
# 你的 control / 仪表盘会话
BRIDGE_PROJECT=shop BRIDGE_ROLE=control BRIDGE_ADMIN=1 claude两种驱动它的方式:
A. 直接跟它说话(由模型来调用这些工具):
"显示 bridge 配置。" · "把 frontend 设为 auto。" · "maxHops 设为 3,driver 设为 tmux。" · "把自动化关掉。"
B. /mcp 提示词(当场应用改动的 slash 命令):
/mcp__session-bridge__show-config 显示当前设置
/mcp__session-bridge__set-mode project、role、auto|manual
/mcp__session-bridge__configure-role cwd / model / tmuxTarget
/mcp__session-bridge__set-driver tmux | spawn
/mcp__session-bridge__automation on | off(总开关)
/mcp__session-bridge__set-limits maxHops / rateLimitPerMinute挑一个,填好参数,它就会立刻把改动写入(仅限 admin 会话)。守护进程会实时拾取它。
参考
工具(可由会话调用)
| 工具 | Admin? | 用途 |
|------|--------|---------|
| bridge_send(to, body) | 否 | 发给某个角色,或用 "*" 广播 |
| bridge_recv() | 否 | 拉取并消费未读消息 |
| bridge_peek() | 否 | 预览未读消息但不消费 |
| bridge_tail(limit?) | 否 | 查看最近的消息 |
| bridge_roles() | 否 | 列出项目里的各个角色 |
| bridge_whoami() | 否 | 显示该会话的项目 + 角色 |
| bridge_config() | 否 | 显示自动化设置 |
| bridge_mode(project, role, auto\|manual) | 是 | 设置一个角色的模式 |
| bridge_set(project, role, {...}) | 是 | 配置一个角色(cwd/model/tmuxTarget/permissionMode) |
| bridge_settings({...}) | 是 | 全局开关(driver/maxHops/rate/…) |
CLI
# 安装设置(针对当前生效的配置目录:$CLAUDE_CONFIG_DIR 或 ~/.claude)
session-bridge install [--no-block-git] [--config-dir <dir>]
session-bridge doctor
# 查看消息总线(用任意角色作为你的身份)
BRIDGE_PROJECT=shop BRIDGE_ROLE=cli session-bridge roles
BRIDGE_PROJECT=shop BRIDGE_ROLE=cli session-bridge send frontend "hello"
BRIDGE_PROJECT=shop BRIDGE_ROLE=cli session-bridge tail 20
session-bridge root # 消息总线的存储目录
# spawner
session-bridge spawner set <project> <role> cwd=<path> [model=<m>] [tmuxTarget=<t>]
session-bridge spawner on [project] [role]
session-bridge spawner off [project] [role]
session-bridge spawner status
session-bridge spawner run [--replay]环境变量
| 变量 | 必需 | 默认 | 含义 |
|-----|----------|---------|---------|
| BRIDGE_PROJECT | 是 | — | 项目(房间)名——隔离的边界 |
| BRIDGE_ROLE | 是 | — | 该会话的角色(昵称) |
| BRIDGE_ADMIN | 否 | 关闭 | 1 让该会话可以修改自动化设置 |
| BRIDGE_ROOT | 否 | ~/.claude/bridge | 消息 / 配置的存储位置 |
| BRIDGE_BLOCK_GIT | 否 | commit\|push\|reset | block-git hook 要拒绝的 git 操作的正则 |
| BRIDGE_AUTOSEND | 否 | 关闭 | 1 启用 Stop 自动发送 hook |
| BRIDGE_SEND_TO | 否 | * | 自动发送的默认收件人 |
| BRIDGE_HOP | 否 | 0 | 循环保护的跳数(由 spawn 驱动设置) |
可选:每一轮自动发送
默认关闭——更推荐有意为之的 bridge_send。要让一个会话的最后一条消息在每一轮自动广播出去,设置 BRIDGE_AUTOSEND=1 并把 Stop hook 加进 settings.json(dist/hooks/send.js)。用 BRIDGE_SEND_TO 覆盖目标。比有意发送更吵、更费 token。
工作原理(给好奇的人)
消息和配置都是 BRIDGE_ROOT 下面的普通文件:
~/.claude/bridge/
spawner.config.json 自动化设置(实时编辑)
<project>/
<role>.inbox.jsonl 该角色消息的只追加(append-only)日志
.cursors/<role>.cursor 该角色已经读了多少条消息
.sessions/<role>.pane tmux 面板 id,供 tmux 驱动找到它bridge_send 把消息追加到收件人的收件箱里。接收 hook 从你的游标(cursor)位置往后读你的收件箱,把新的几行注入进来,并推进游标——所以你永远不会看到同一条消息两次。隔离不过就是那个按项目划分的文件夹。
测试
npm test # MCP 总线端到端测试
npm run test:config # 配置仪表盘 + admin 门控
npm run test:prompts # /mcp 提示词应用改动(受 admin 门控)
npm run test:spawner # spawn 驱动的乒乓往返 + 循环保护
npm run test:tmux # tmux 驱动的乒乓往返 + 循环保护
npm run test:real # 使用 `claude -p` 的真实双会话测试许可证
MIT
