# Session Bridge Mcp

> MCP server that lets multiple Claude Code sessions talk to each other over tmux (gestor/dev/reviewer workflow)

- **Type:** MCP server
- **Install:** `agentstack add mcp-c2developers-session-bridge-mcp`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [c2developers](https://agentstack.voostack.com/s/c2developers)
- **Installs:** 0
- **Category:** [Integrations](https://agentstack.voostack.com/c/integrations)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [c2developers](https://github.com/c2developers)
- **Source:** https://github.com/c2developers/session-bridge-mcp

## Install

```sh
agentstack add mcp-c2developers-session-bridge-mcp
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## 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](https://bun.sh) and [tmux](https://github.com/tmux/tmux), and
Claude Code launched **inside** a tmux pane.

```sh
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):

```sh
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`):

```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

```sh
# 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**.

1. **Save** a briefing per role (usually the `gestor` sets 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.

2. **Restore** happens on its own. A `SessionStart` hook 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):

```json
{
  "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 `register`ed 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

1. **You → gestor**: "implement issue #14".
2. **gestor → dev**: `send(to:'dev', from:'gestor', text:'Take issue #14: . Branch /14-slug. Ping the reviewer with the PR URL when ready.')`
3. **dev** codes. If it needs a user decision, the gestor (watching via `/loop` + `read_peer('dev')`) sees it and answers — or escalates to you.
4. **dev → reviewer**: `send(to:'reviewer', from:'dev', text:'PR #21 ready: . Please review.')`
5. **reviewer** reviews → `send(to:'dev', …)` with changes, or `send(to:'gestor', …)` with "approved".
6. **dev** fixes → back to step 4. **reviewer** confirms → merges, closes issue/PR.
7. **reviewer → gestor**: "merged & closed". The gestor sends `/clear` to 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 `register`ed 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](https://github.com/c2developers)
- **Source:** [c2developers/session-bridge-mcp](https://github.com/c2developers/session-bridge-mcp)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-c2developers-session-bridge-mcp
- Seller: https://agentstack.voostack.com/s/c2developers
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
