A two-way bridge that lets two Claude Code sessions push tasks to each other and act on them automatically — no copy-paste, no "check your inbox." Built on Claude Code's official Channels feature.
It also ships a Telegram control channel (message your agents from your phone) and a PreToolUse firewall that hard-blocks dangerous commands.
session A ──send_to_peer tool──► bridge(:8801) ──HTTP POST + secret──► bridge(:8802)
│ notifications/claude/channel
▼
session B sees <channel source="A"> and acts
◄───────────────────────── symmetric in reverse ─────────────────────────►
Each project runs a small MCP channel server (bridge/). It:
- declares the
claude/channelcapability, so Claude Code injects pushed events into the live session; - exposes a
send_to_peertool the local Claude calls to message the other session; - listens on a localhost port — inbound peer messages (secret-gated) are pushed into the
session as
<channel source="…">events.
Two instances point at each other over localhost. No central broker.
- Node 22+
- Claude Code v2.1.80+ (Channels is a research preview)
- claude.ai or Console API-key auth (not Bedrock/Vertex/Foundry); channels not blocked by org policy
npm install && npm link(installs deps; putscc-bridge/cc-firewallon your PATH)- Create the shared secret both sides read:
openssl rand -hex 32 > ~/.claude/bridge-secret && chmod 600 ~/.claude/bridge-secret(or setBRIDGE_SECRETin the env instead). - Add a
bridgeserver to each project's.mcp.json(seeexamples/*/.mcp.jsonfor the exact shape):"command": "cc-bridge"with itsenv:SELF_NAME,PEER_NAME,SELF_PORT,PEER_URL. - Launch each session (load whichever channels you use):
claude --dangerously-load-development-channels server:bridge server:telegram
In session A, tell Claude: "send a message to the peer: …". It calls send_to_peer, the
message arrives in session B as a channel event, and B acts on it on its own. Reply the same
way in reverse.
Message your agents from your phone. Run one bot per agent in a common Telegram group;
Telegram's group privacy mode means each bot only sees messages that @mention it, so
@myproj_native_bot update the client reaches only that agent. Replies come back in the group.
Only your Telegram user id is allowed to command the agents. You can also DM a single bot
directly (no @mention needed) — handy for one agent or quick testing.
Setup:
- Create one bot per agent with @BotFather; save each token:
pbpaste | npm run set-token -- server(then… -- native) →~/.claude/telegram-<agent>.token(chmod 600). - Get your numeric id from @userinfobot and save it outside the repo:
echo <id> > ~/.claude/telegram-allowed-user(or setALLOWED_USER_IDin the env). Keeping it in~/.claudekeeps your id out of git. - Make a group with you + both bots (keep bot privacy mode ON).
- Add a
telegramserver to each project's.mcp.json("command": "cc-bridge-telegram",env:AGENT_NAME,ALLOWED_USER_ID) — seeexamples/*/.mcp.json— and includeserver:telegramin the--dangerously-load-development-channelslaunch.
In the group, @mention an agent to task it; it answers via the reply_to_telegram tool.
Images: send a photo — caption it @bot … in a group, or just send it in a DM — and the agent
downloads it to <project>/.cc-telegram/ and views it with the Read tool. e.g. send a screenshot
and ask what's wrong.
Permission relay (optional): set TELEGRAM_CHAT_ID (the group id) in the telegram env, and
risky tool approvals are forwarded to the group — reply yes <id> / no <id> to approve or deny
from your phone. The terminal prompt also stays live, and the first answer wins.
A received peer message is untrusted input (a prompt-injection surface). The defenses live in
security/settings.template.json — copy it into each project's .claude/settings.json:
- Run with normal permissions (never
--dangerously-skip-permissions) so risky ops pause for your approval. - A deny-list blocks destructive commands outright.
- The PreToolUse firewall (
cc-firewall) hard-blocks dangerous Bash and writes outside the project / to secret paths — deterministically, regardless of the model. - The secret gate means only the paired peer can inject messages.
The firewall is a pattern backstop, not airtight — keeping permissions on is what makes the combination safe.
| Path | What |
|---|---|
bridge/ |
the channel server, split by concern (main, config, channel-server, send-tool, peer-client, inbound-server, echo-guard, types) |
telegram/ |
Telegram control channel (cc-bridge-telegram): client, routing, reply tool, poll loop |
firewall/ |
PreToolUse safety hook: pure rules.ts + thin main.ts, run as cc-firewall |
security/settings.template.json |
permissions + hook wiring to copy into a project |
examples/{server-side,native-side}/.mcp.json |
runnable two-instance demo (ports 8801/8802) |
test/ |
unit + integration tests (npm test) |
scripts/ |
helpers (e.g. set-token for saving bot tokens) |
bin/ |
cc-bridge / cc-bridge-telegram / cc-firewall launchers (linked via npm link) |
Run npm link once, then reference the path-free commands cc-bridge, cc-bridge-telegram,
and cc-firewall from any project's .mcp.json / .claude/settings.json — see examples/. No
absolute paths, so moving or renaming this repo doesn't break consumers (re-run npm link if
you move it).
Research preview — the --channels flag and protocol may change. Events arrive only while a
session is open. Delivery is best-effort (no ack).