A TypeScript library + CLI that lets external UIs and orchestrators drive an already-running, already-authenticated Claude Code session — without restarting it, without scraping the terminal, and without talking to the model API directly.
Channels for inbound general content; MCP tools for outbound. A consumer pushes a message in via a Claude Code channel (notifications/claude/channel); Claude responds by calling one of three MCP tools (bridge_reply / bridge_progress / bridge_done); every turn is persisted as an append-only JSONL event log.
+-------------------------------------------+
| Consumer |
| ccb serve, T3 Code, agtx, ... |
+-------------------------------------------+
| ^
| sendMessage() | events(sessionId)
v |
+-------------------------------------------+
| Bridge |
| session state + event log |
+-------------------------------------------+
|| ^^
|| channels (inbound) || MCP tools (outbound)
vv ||
+-------------------------------------------+
| Claude Code |
| claude + ccb plugin |
+-------------------------------------------+
This is not an ACP replacement and not a universal agent runtime. It exists for the specific case where you want an already-authenticated interactive claude to be the runtime.
See docs/ARCHITECTURE.md for the full design, process topology, and a library-usage example.
1. Install the CLI globally (bun ≥ 1.3 must be on PATH):
> bun add -g @pablospe/claude-code-bridge2. Register the plugin inside a claude session:
/plugin marketplace add https://github.com/pablospe/claude-code-bridge
/plugin install ccb@claude-code-bridge
The plugin connects a normal claude session back to the bridge
automatically — including the hook relay, so you get tool.event visibility
out of the box.
What this installs / why order matters
- Four bins land on your PATH:
ccb(the CLI),ccb-channel-serverandccb-hook-relay(spawned by Claude Code), andccb-launcher(a Node-side launcher; see limitations). - npm name: published as
@pablospe/claude-code-bridge, scoped under the author's namespace; the GitHub repo name staysclaude-code-bridge. - Global install must come first: the plugin manifest invokes
ccb-channel-server/ccb-hook-relayby bare name, so without them on PATH claude reportscommand not found. - Channels is a research preview: it must be enabled / allowlisted for
your account — see
docs/SMOKE.mdfor that one-time step.
The bridge is the consumer side; your claude session is the runtime —
you drive claude from the bridge and watch the structured event stream.
1. Start the bridge (Terminal 1). It mints a session id, writes a
per-session MCP config, and prints the exact claude command for terminal 2:
ccb serve
# listening on 127.0.0.1:18484
# bridge_uuid: 4f3b6e10-…
# …
# in a second terminal, start claude pointed at this bridge:
# claude --dangerously-load-development-channels server:ccb \
# --mcp-config /tmp/ccb-serve-4f3b6e10-….mcp.json \
# --allowed-tools "mcp__ccb__bridge_reply mcp__ccb__bridge_progress mcp__ccb__bridge_done"2. Start claude (Terminal 2). Paste the command ccb serve printed.
3. Send a prompt (back in Terminal 1) — it's pushed to claude as a channel notification:
what is 11 squared?
Terminal 1 streams the round-trip as structured events:
[message.sent] m1 "what is 11 squared?"
[tool.event] PreToolUse Bash "…"
[tool.event] PostToolUse Bash (12 B)
[agent.reply final=true] "11 squared is 121."
[agent.done]
Why the pasted command matters / session-id tips
- The dev-channels flag is what enables inbound. It loads the
ccbchannel from the generated--mcp-config(whose env carries the endpoint + session id); a plain plugin launch gives outbound tools + hooks only. - Need a session id without copy-paste? Bun is already installed:
bun -e 'console.log(crypto.randomUUID())', or useuuidgenif you have it, and pass the same value toccb serve --session-idandexport CCB_SESSION_ID.
For the full verified walkthrough — including enabling the channels preview
and the alternative ccb-launcher flow — see docs/SMOKE.md.
A mock supervisor runs the whole event pipeline in-process, with no real
claude and no channels — handy as a smoke check:
ccb demo --supervisor=mock "what is 11 squared?"[session.started] d91356a6-…
[message.sent] 916c268f-… "what is 11 squared?"
[agent.progress] "thinking"
[agent.reply final=true] "echo: what is 11 squared?"
[session.ended]
From a source checkout — also the way to run the bridge before the npm package is published:
git clone https://github.com/pablospe/claude-code-bridge
cd claude-code-bridge
bun install
bun test
# the CLI runs straight from source (no global install):
bun apps/ccb/src/cli.ts demo --supervisor=mock "hello world"--channels=dev-flag exercises the real-claude channel surface without the
plugin (uses claude --dangerously-load-development-channels), so you can
test against your local source without publishing. To exercise the plugin
path against local source, bun link @pablospe/claude-code-bridge in this
checkout and bun link --global @pablospe/claude-code-bridge where you run
claude; the plugin manifest references bin names, so PATH resolution finds
the linked bins.
The real-claude paths need Claude Code v2.1.80+, authenticated, with the
channels research preview enabled for your account. The preview is gated
server-side (the tengu_harbor flag) and isn't on for everyone yet — see
docs/SMOKE.md for the availability diagnostics.
- Inbound is dev-flag-only. The full round-trip needs the
--dangerously-load-development-channels+--mcp-configpath (whatccb serveprints). The plugin path gives outbound tools + hooks but not inbound on individual accounts. - Managed launch needs
node-pty.ccb demo --supervisor=claudespawnsclaudevianode-pty(works under both Node and Bun); ifnode-ptycan't load on the host, use the two-terminal flow or theccb-launcherbin instead.
docs/ARCHITECTURE.md— full design, process topology, library-usage example, and channel-direction nuance.docs/CLI.md—ccbCLI command reference (demo,mcp-config,serve).docs/SMOKE.md— real-claudeverification procedure (plugin, dev-flag, and two-terminal launcher).docs/ROADMAP.md— milestones, planned work, and consumer-gated follow-ups.
MIT.