Install
$ agentstack add mcp-c2developers-session-bridge-mcp ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
🌉 session-bridge-mcp
Let multiple Claude Code sessions talk to each other.
One session writes straight into another's prompt over tmux, and every message lands in a shared conversation log any session can review.
[](https://modelcontextprotocol.io) [](https://bun.sh) [](https://github.com/tmux/tmux) [](#license)
Why
You run one Claude Code session to write code and another to review the PR — and you keep playing carrier pigeon between them. session-bridge removes the pigeon. Each session lives in its own tmux pane under a role, and they message each other directly.
The canonical setup is a 3-session loop:
┌─────────────────────────────────────────────────────────┐
│ gestor · talks to you, orchestrates the workflow │
└───────────────┬─────────────────────────┬────────────────┘
send │ │ send
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ dev │ ───────▶ │ reviewer │
│ writes code │ send │ reviews the PR │
└──────────────┘ ◀─────── └──────────────────┘
send
> gestor = manager. Rename the roles to whatever you like — they are just > strings.
How it works
Two transports, on purpose:
| Transport | What | Needs polling? | |---|---|---| | PUSH | send injects text into the peer's tmux pane (set-buffer → bracketed-paste → Enter). The peer receives it as a brand-new user turn. Claude Code even queues it if the peer is mid-task. | No — it's pushed. | | PULL | read_peer captures what's currently on a peer's screen. Used to watch a session (e.g. the manager noticing the dev is asking the user something). | Yes — pair with the native /loop skill. |
Every message is also logged twice, so nothing is lost and any session can catch up:
conversation.jsonl— the single ordered cross-session thread.mailbox/.jsonl— a per-role inbox (audit + fallback).
Bracketed paste (paste-buffer -p) means multi-line messages, quotes and whitespace survive intact — the TUI won't treat embedded newlines as submits.
State lives under ~/.claude/session-bridge/, so it is shared across every Claude Code session on the machine and survives /clear.
~/.claude/session-bridge/
├── registry.json # role → tmux pane mapping
├── prompts.json # role → persistent system prompt (restored on /clear)
├── conversation.jsonl # global ordered thread
└── mailbox/
├── gestor.jsonl
├── dev.jsonl
└── reviewer.jsonl
Install
Requires Bun and tmux, and Claude Code launched inside a tmux pane.
git clone git@github.com:c2developers/session-bridge-mcp.git
cd session-bridge-mcp
bun install
Register the server with Claude Code at user scope (available in every project):
claude mcp add session-bridge -s user -- bun /abs/path/to/session-bridge-mcp/src/index.ts
Or drop it into a project's .mcp.json (see .mcp.example.json):
{
"mcpServers": {
"session-bridge": {
"command": "bun",
"args": ["/abs/path/to/session-bridge-mcp/src/index.ts"]
}
}
}
Configure the state directory with SESSION_BRIDGE_DIR if you don't want the default ~/.claude/session-bridge/.
Quickstart — 3 sessions in 60 seconds
# Three tmux sessions, each will run its own Claude Code
tmux new-session -d -s gestor
tmux new-session -d -s dev
tmux new-session -d -s reviewer
# In each one, launch `claude` and give it its role as the first message:
# gestor → "registra esta sesión como 'gestor' con session-bridge"
# dev → "registra esta sesión como 'dev' con session-bridge"
# reviewer → "registra esta sesión como 'reviewer' con session-bridge"
Each session calls register(role) once — it auto-detects its own pane from $TMUX_PANE. After that they can reach each other by role name.
> Bootstrap trick: an already-registered session can register the others for > you by injecting the instruction into their panes (the manager does exactly > this). It just needs their pane ids from tmux list-panes -a.
Tools
| Tool | Signature | Purpose | |---|---|---| | register | (role, pane?) | Register the current session under a role. Auto-detects the tmux pane from $TMUX_PANE; pass pane to override. Call once per session. | | list_peers | () | All registered roles, their panes, cwd and last-seen time. | | send | (to, from, text, submit?, tag?) | Push a message into a peer's prompt. submit=false types it without sending; tag=false drops the [from:…] prefix. Also logs to the thread + mailbox. | | read_peer | (role, lines?) | Capture the peer's visible screen (default 50 lines of scrollback). | | detect_question | (role, lines?) | Classify the peer's screen — busy / waiting_select / waiting_text / idle / unknown — and parse any open menu's options + heading. Lets the manager notice when a peer needs an answer. | | respond | (role, keys) | Send raw control keys to drive an interactive menu/permission prompt, e.g. ['1'], ['Down','Down','Enter'], ['Escape']. For prose, use send. | | history | (limit?, role?) | Read the shared conversation, newest last. Filter by a role involved. | | inbox | (role, limit?) | Read a role's mailbox — a poll/fallback if a pushed message was missed. | | set_prompt | (role, prompt) | Save a persistent "system prompt" / briefing for a role. Survives /clear and restarts; auto-reinjected by the SessionStart hook. | | get_prompt | (role) | Read back a role's saved prompt ({found:false} if none). | | list_prompts | () | All saved role prompts. | | delete_prompt | (role) | Forget a role's saved prompt so it stops being reinjected. |
The watch loop (PULL) — answering a peer's questions
Receiving messages is push, so no loop is needed for that. A loop is only for watching a peer's screen and noticing when it's stuck on a question (plan-mode approval, a clarifying menu, a permission prompt). detect_question classifies the screen so the loop doesn't bother a busy peer; respond then drives the menu.
With Claude Code's native /loop skill, run this in the manager:
/loop 30s para 'dev' y 'reviewer' llama detect_question. Si state=='waiting_select'
o 'waiting_text', enséñame la pregunta y las opciones y decide la respuesta según el
flujo: contéstala con respond(role, keys) para menús (p.ej. ['1','Enter']) o con
send(...) para texto. Si la decisión es del usuario, párate y pregúntame. Si state
=='busy' o 'idle', no hagas nada.
> Menus vs prose. A menu/permission prompt is not a text turn — typing > into it does nothing useful. Use respond with key tokens for those, and > send only when the peer is genuinely waiting for free text.
Sending slash commands across sessions
Slash commands are just text injected into the prompt:
send(to:'dev', from:'gestor', text:'/clear')
Persistent role prompts — surviving /clear
/clear wipes a session's context — including who it is and how it should behave. Re-typing each role's briefing every time is the pigeon problem all over again. So a role's "system prompt" can be saved once and restored automatically.
- Save a briefing per role (usually the
gestorsets them up):
`` set_prompt(role:'dev', prompt:'You are the dev. Work on the assigned issue, keep commits small, open a PR and ping the reviewer. Never merge yourself.') ``
It's written to prompts.json and survives /clear, restarts and reboots.
- Restore happens on its own. A
SessionStarthook fires on/clear(and
on startup/resume), reads the session's $TMUX_PANE, reverse-looks-up its role in registry.json, and reinjects that role's saved prompt as additionalContext. The session wakes up already knowing its identity — no re-typing, no manual get_prompt.
Wire the hook once in ~/.claude/settings.json (user scope, so it fires in every session regardless of project):
{
"hooks": {
"SessionStart": [
{
"matcher": "clear|startup|resume",
"hooks": [
{
"type": "command",
"command": "/abs/path/to/bun run /abs/path/to/session-bridge-mcp/src/session-start-hook.ts",
"timeout": 15
}
]
}
]
}
}
The hook is a silent no-op when it can't help: outside tmux, on an unregistered pane, or when no prompt is saved for the role — so it never blocks a session from starting. If a pane is bound to several roles (e.g. dev and developer), the most recently registered role wins.
> The prompt is restored as context, not as a literal model system prompt — > Claude Code has no API to overwrite its own system prompt mid-session. In > practice an injected role briefing at session start behaves the same way.
Full workflow example
- You → gestor: "implement issue #14".
- gestor → dev:
send(to:'dev', from:'gestor', text:'Take issue #14: . Branch /14-slug. Ping the reviewer with the PR URL when ready.') - dev codes. If it needs a user decision, the gestor (watching via
/loop+read_peer('dev')) sees it and answers — or escalates to you. - dev → reviewer:
send(to:'reviewer', from:'dev', text:'PR #21 ready: . Please review.') - reviewer reviews →
send(to:'dev', …)with changes, orsend(to:'gestor', …)with "approved". - dev fixes → back to step 4. reviewer confirms → merges, closes issue/PR.
- reviewer → gestor: "merged & closed". The gestor sends
/clearto dev and kicks off the next task.
Troubleshooting
| Symptom | Fix | |---|---| | register fails: $TMUX_PANE is unset | You launched Claude Code outside tmux. Start it inside tmux, or pass pane (tmux display-message -p '#{pane_id}'). | | Message logged but delivered:false | The target pane died. Re-register that session (pane ids change when tmux restarts). The message is still in the mailbox. | | Peer didn't react to a pushed message | It was busy; Claude Code queues it. Or check its inbox(role). | | MCP added mid-session but tools missing | Restart Claude Code — user-scope servers load at startup. | | Prompt not restored after /clear | The session must have been registered in that pane and have a saved prompt (get_prompt(role)). The hook only fires if wired in settings.json; check it with /hooks or run the script manually piping {"source":"clear"} with TMUX_PANE set. |
Security
send injects text into another Claude Code prompt and read_peer reads its screen. Anyone who can write to ~/.claude/session-bridge/registry.json, or drive your tmux server, can inject prompts into your sessions. Run this only on a trusted local machine. There is no network listener — all coordination is local files + the local tmux socket.
License
MIT © c2developers
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: c2developers
- Source: c2developers/session-bridge-mcp
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.