Extends Claude Code's agent teams across different machines using a relay server over HTTP and Server-Sent Events. Install the bridge on one host, point each Claude Code session at it via SSE transport, and agents get six tools: bridge_send, bridge_receive, bridge_channels, bridge_ping, bridge_clear, and bridge_status. Messages route through named channels with incremental polling via since_id cursors. Works over localhost for single-machine setups, LAN IPs for local networks, or Tailscale/mesh VPNs when your orchestrator and workers live on completely separate networks. Includes a web dashboard and optional stdio mode for subprocess spawning without HTTP overhead.
Real-time cross-machine communication for Claude Code agents.
Claude Code's native Agent Teams coordinate multiple instances on the same machine. Claude Bridge fills the gap — it lets Claude Code agents on different machines communicate in real time over a shared MCP relay server.
Windows PC (Claude Code) MacBook (Claude Code)
| |
| SSE · Tailscale / LAN | ← server runs here
+-------> Claude Bridge <--------+
:8765
One agent sends. The other receives. No polling hacks, no shared filesystems, no cloud dependencies.

pip install claude-code-bridge # server + web dashboard
pip install claude-code-bridge[tui] # also brings the terminal UI
Why the PyPI name differs from the project name:
claude-bridgewas already taken on PyPI by an unrelated project. The distribution name isclaude-code-bridge; the import name (import claude_bridge) and the CLI command (claude-bridge) are unchanged.
Or from a clone if you'd like to hack on it:
git clone https://github.com/constripacity/Claude-Bridge.git
cd Claude-Bridge
pip install -e .[dev] # editable install with test/lint deps
Or run the official container image (published to GHCR on every release):
docker run -p 8765:8765 -v claude-bridge-data:/data \
ghcr.io/constripacity/claude-bridge:latest
# with auth, retention, and the audit log:
docker run -p 8765:8765 -v claude-bridge-data:/data \
-e CLAUDE_BRIDGE_AUTH_TOKEN=secret \
ghcr.io/constripacity/claude-bridge:latest --host 0.0.0.0 --retention-days 30 --audit-log
The image binds 0.0.0.0 by default (publishing the port is already an opt-in to network exposure) and stores its SQLite DB in the /data volume. Tags: latest + MAJOR.MINOR + exact X.Y.Z per release, plus a moving edge from main.
claude-bridge # defaults: 127.0.0.1:8765, ./claude-bridge.db
# accept connections from other machines on the network:
claude-bridge --host 0.0.0.0
# or pick a custom port / db path:
claude-bridge --port 9000 --db /var/lib/claude-bridge/bridge.db
# or disable the web dashboard if you only want the MCP transport:
claude-bridge --no-dashboard
# or run as a pure stdio MCP server (no HTTP, no dashboard, no banner):
claude-bridge --stdio
The default bind changed in v0.7.3 from
0.0.0.0to127.0.0.1so a fresh install on a laptop doesn't silently expose the bridge to whatever network it's on. Pass--host 0.0.0.0(or a specific interface IP) when you actually want cross-machine reach. Combine with Authentication for anything less trusted than your own LAN/tailnet.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Claude Bridge — General MCP Relay Server
Version: 0.9.1
http://localhost:8765/ ← Dashboard
http://localhost:8765/sse ← Local MCP config
http://<host-address>:8765/sse ← Remote machines (LAN/Tailscale)
http://localhost:8765/api/state ← JSON state for dashboard
http://localhost:8765/status ← Health check
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Use the claude mcp add CLI on every machine that should use the bridge — including the host. Don't edit ~/.claude/settings.json directly; current Claude Code rejects the legacy mcpServers block at the schema level.
Host machine — points at the local server:
claude mcp add --transport sse -s user claude-bridge http://localhost:8765/sse
Remote machines — point at the host's reachable address (LAN IP, Tailscale IP, or any other network route). Make sure the host bridge was started with --host 0.0.0.0 (or the specific interface IP) — the default 127.0.0.1 only accepts local connections.
claude mcp add --transport sse -s user claude-bridge http://<host-address>:8765/sse
Single-machine / stdio mode — when there's no need for cross-machine reach, Claude Code can spawn the bridge as a subprocess and talk to it over stdin/stdout, no HTTP at all:
claude mcp add -s user claude-bridge -- claude-bridge --stdio
# share the SQLite store with an HTTP instance if you also run one:
claude mcp add -s user claude-bridge -- claude-bridge --stdio --db /path/to/shared.db
Use -s user to share the entry across all your projects, or -s local to scope it to one. Verify with claude mcp list — claude-bridge should show as ✓ Connected. If a Claude Code session is already running, type /mcp inside it to re-handshake (or restart the session) so the new tools register.
That's it. Every connected Claude Code session now has six new tools.
| Tool | Description |
|---|---|
bridge_send | Send a message to a named channel |
bridge_receive | Read messages — pass since_id for incremental polling |
bridge_channels | List all active channels and message counts |
bridge_ping | Health check + server stats |
bridge_clear | Clear all messages from a channel |
bridge_status | Cross-channel overview with recent messages |
Agents communicate over named channels. Convention: <project>:<role>.
Machine A (orchestrator):
bridge_send(
channel="myproject:orchestrator",
sender="windows",
content='{"type":"task","phase":1,"action":"run_tests"}'
)
Machine B (worker):
bridge_receive(channel="myproject:orchestrator")
→ gets the task
bridge_send(
channel="myproject:worker",
sender="mac",
content='{"type":"result","phase":1,"status":"complete","tests_run":61,"failures":0}'
)
Machine A polls for results:
bridge_receive(channel="myproject:worker", since_id="<last_id>")
The since_id parameter ensures each agent only processes new messages on every poll.
If the cursor goes stale — for example after another agent runs bridge_clear and the message your since_id referred to no longer exists — bridge_receive (and GET /api/messages) returns an empty result with a since_id_not_found warning instead of silently dumping the channel from the start. Drop the bad cursor and call again without since_id to re-sync.
Channels are created on first write — no registration needed.
<project>:orchestrator → A sends tasks to B
<project>:worker → B sends results to A
<project>:events → shared event log
<project>:debug → verbose diagnostics
general:sync → cross-project coordination
The bridge is plain HTTP + Server-Sent Events. As long as the client machine can reach the host's :8765, it works — pick whatever connectivity fits your setup:
localhost. Nothing to set up.192.168.1.42). No port forwarding needed.:8765 firewalled to the tailnet — no public exposure.Claude Code's built-in Agent Teams (experimental, requires CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1) coordinate agents on the same machine. They share a process, a filesystem, and a local network. There's no mechanism for two agents running on separate physical machines to talk to each other.
Claude Bridge is the missing layer. It's intentionally minimal — a relay, not an orchestrator. Your agents stay in control of their own logic.
A ready-to-use CLAUDE.md is included. Drop it in your project root or add it to your global ~/.claude/CLAUDE.md to give every Claude Code session full context on how to use the bridge, which channels belong to which project, and what each machine's role is.
Open http://localhost:8765/ in any browser for a live monitor of channels, messages, and senders. Messages appear the instant they land — no 2-second lag. Click into any message for a JSON-highlighted inspector. Includes a send composer (pick a sender, type or paste JSON, ⌘↵ / Ctrl↵ to send) and a per-channel clear button. Adapts to mobile viewports automatically.
The dashboard uses a live SSE stream for the active channel's message feed and continues polling /api/state every 2 seconds for channel list and counts.
The JSON API (polling endpoints remain available for scripts and external tools):
| Endpoint | Purpose |
|---|---|
GET /api/state | All channels + counts + senders + uptime in one call |
GET /api/messages?channel=X[&since_id=Y][&limit=N] | Feed for one channel |
GET /api/messages/{id} | Full message detail (parsed JSON, byte size) |
POST /api/send {channel,sender,content} | Same effect as bridge_send |
POST /api/clear {channel} | Drop all messages on a channel |
GET /events/channel/<name>[?since_id=Y] | Live SSE stream for one channel |
GET /api/audit[?limit=N] | Recent audit events (when --audit-log is on) |
Sends from the dashboard are indistinguishable from MCP bridge_send calls — they share the same INSERT path.
GET /events/channel/<name> is a Server-Sent Events endpoint. Open it and you get a real-time push of every message and clear event on that channel with ~0 ms latency. The polling JSON endpoints remain unchanged for backwards compatibility.
Events emitted:
| Event | Payload | When |
|---|---|---|
message | {seq, id, channel, sender, content, timestamp} | On every new message |
clear | {channel, cleared} | When bridge_clear or POST /api/clear runs |
cursor_stale | {since_id} | Reconnect cursor no longer exists — re-sync via /api/messages |
replay_truncated | {limit} | Backlog > 500 rows — re-sync via /api/messages |
Browser (EventSource):
const es = new EventSource('/events/channel/myproject:worker');
es.addEventListener('message', e => console.log(JSON.parse(e.data)));
es.addEventListener('clear', e => console.log('channel cleared'));
The browser EventSource API cannot send custom headers, so when auth is enabled, pass the token as a query parameter instead:
const es = new EventSource(`/events/channel/myproject:worker?token=${encodeURIComponent(token)}`);
Access-log note: query-parameter tokens appear in server access logs. If you run the bridge behind a reverse proxy (nginx, Caddy), add a log-scrub rule for
/events/.*[?&]token=to avoid leaking tokens into your log retention.
Backlog replay on reconnect: browsers send Last-Event-ID automatically on reconnect (using the id: field from each SSE frame). TUI clients can pass ?since_id=<last-msg-id> explicitly. Up to 500 messages are replayed; if more exist, a replay_truncated event fires and the client should re-fetch via /api/messages.
Caps (env-tunable, no restart needed):
| Variable | Default | What it limits |
|---|---|---|
CLAUDE_BRIDGE_MAX_SSE | 100 | Total concurrent subscribers across all channels |
CLAUDE_BRIDGE_MAX_SSE_PER_CHANNEL | 25 | Subscribers per channel |
CLAUDE_BRIDGE_SSE_REPLAY_LIMIT | 500 | Backlog rows replayed on reconnect |
Requests past a cap get 503 Service Unavailable. A comment-line keepalive is sent every 15 s to survive the 30–60 s idle cutoff most reverse proxies enforce.
If you live in a terminal, run the TUI companion instead of (or alongside) the web dashboard. Install the [tui] extra and use the module entry point:
pip install claude-bridge[tui]
python -m claude_bridge.tui
# or: python -m claude_bridge.tui --url http://<host>:8765 --sender mac
It's a Textual app that talks to the same JSON API as the dashboard, so they're always in sync. Channels in a sidebar, live-polled feed with sender/type colouring, a JSON-highlighted inspector, send composer, filter, clear, and pause — all keyboard-driven (? for help, q to quit).
Design reference for every layout (full / compact / narrow / states) lives in docs/design/terminal/ — open index.html to browse the artboards.
The bridge runs without auth by default — anyone who can reach :8765 can read or write any channel. That's fine for localhost, a trusted LAN, or a tailnet. If you need to expose the bridge to a less-trusted network, set a token:
# Recommended: env var (token never appears in `ps` output)
export CLAUDE_BRIDGE_AUTH_TOKEN="$(openssl rand -hex 32)"
claude-bridge
# Or read it from a file (safer than --auth-token on shared hosts)
echo "$(openssl rand -hex 32)" > /etc/claude-bridge/token
chmod 600 /etc/claude-bridge/token
claude-bridge --auth-token-file /etc/claude-bridge/token
# Or the literal value on the CLI — easy but the value shows up in `ps`
# output and /proc/<pid>/cmdline; fine for local dev, avoid on shared hosts
claude-bridge --auth-token "$(openssl rand -hex 32)"
Precedence: --auth-token > --auth-token-file > CLAUDE_BRIDGE_AUTH_TOKEN. When set, every endpoint except /status (the healthcheck) requires Authorization: Bearer <token>. Constant-time comparison; no other state. Unset everything and the bridge runs exactly as it did before — auth is fully opt-in.
Connecting clients with auth on:
# claude mcp add — pass the header on registration:
claude mcp add --transport sse -s user claude-bridge \
http://<host-address>:8765/sse \
--header "Authorization: Bearer $CLAUDE_BRIDGE_AUTH_TOKEN"
# TUI — picks up CLAUDE_BRIDGE_AUTH_TOKEN automatically, or pass --token:
python -m claude_bridge.tui --url http://<host-address>:8765 \
--token "$CLAUDE_BRIDGE_AUTH_TOKEN"
# Web dashboard — open the URL in a browser. On the first /api/* call it
# prompts you for the token, stores it in localStorage, and attaches it as
# a Bearer header to every subsequent request. A green "Auth ✓" badge in
# the top-right with a `clear` button lets you rotate or forget.
stdio mode is unaffected — claude-bridge --stdio is a subprocess pipe, not a network surface, so it skips the auth check entirely.
By default the bridge serves plain HTTP and the recommended cross-machine setup terminates TLS upstream — a reverse proxy (nginx, Caddy) or an overlay like Tailscale. For a self-contained HTTPS listener with no proxy, point the bridge at a cert + key:
claude-bridge --host 0.0.0.0 --tls-cert /etc/claude-bridge/cert.pem \
--tls-key /etc/claude-bridge/key.pem
Both flags are required together; the cert lifecycle (issuance, renewal) is the operator's responsibility. The banner switches to https:// URLs when TLS is on. stdio mode ignores these flags.
Opt-in forensic trail. With --audit-log (or CLAUDE_BRIDGE_AUDIT_LOG=1) the bridge records security-relevant events to a separate audit table:
| Event | Logged when |
|---|---|
auth_failure | A protected request is rejected (401) |
oversize_reject | A request body exceeds the size cap (413) |
channel_clear | A channel is cleared (records the count) |
channel_create | The first message lands on a new channel |
Each row carries a UTC timestamp and the client IP. Read recent events via GET /api/audit?limit=N (auth-protected when a token is set; returns {"enabled": false, "events": []} when the log is off). Audit rows are not removed by message retention — forensic logs usually need to outlive content.
Messages are persisted to a local SQLite database (./claude-bridge.db by default) so they survive server restarts. Override the path with the CLAUDE_BRIDGE_DB environment variable:
CLAUDE_BRIDGE_DB=/var/lib/claude-bridge/bridge.db claude-bridge
# or: claude-bridge --db /var/lib/claude-bridge/bridge.db
The schema is a single messages table — easy to inspect with sqlite3. Use bridge_clear to drop a channel.
By default messages are kept forever. On a long-running host you can cap growth with a retention window — a background sweep deletes messages older than the cutoff:
claude-bridge --retention-days 30 # or CLAUDE_BRIDGE_RETENTION_DAYS=30
This is opt-in and deletes historical state, so the startup banner prints a loud warning when it's on. The sweep runs on startup and then every CLAUDE_BRIDGE_RETENTION_SWEEP_SECONDS (default 3600). retention_days is surfaced in /api/state. Long-term project memory should live in your notes/repo regardless — the bridge is a transport, not an archive.
claude-bridge PyPI package + CLI entrypointio.github.constripacity/claude-code-bridgeGET /events/channel/<name>) — dashboard + TUI switch from polling to push--tls-cert / --tls-key)--retention-days, background sweep)--audit-log, GET /api/audit)mcp, starlette, uvicorn, anyio, sse-starlette (declared in pyproject.toml; installed automatically by pip install claude-bridge)localhost, LAN, Tailscale, or any other route (see Networking)The server is intentionally lightweight:
Safe to run on a MacBook Air M3 without thermal impact.
See CONTRIBUTING.md. PRs welcome, especially for the roadmap items above.
Constripacity – for founding this project and architecting the bridge.
MIT — see LICENSE.
CLAUDE_BRIDGE_DBSQLite database path (default: ./claude-bridge.db). Share the same path across HTTP and stdio instances to share state.
CLAUDE_BRIDGE_AUTH_TOKENsecretOptional Bearer token. Only applies to HTTP mode (not stdio). When set, requires Authorization: Bearer <token> on all endpoints except /status.