mcp server
c3
One Telegram bot as a remote control for the Claude Code and Codex sessions already running on your machine — a topic per project, voice notes transcribed, permission prompts as tappable buttons, and
Set up
Runs on your computer. Pick your agent and operating system.
This project does not publish an install method we can turn into a config. Follow the README instead.
README
synced Oct 21, 2026C3 — every coding agent you run, in one Telegram chat
Every vendor's remote mode gives you one agent, in that vendor's app. C3 gives you all of them in one Telegram group: a forum topic per project, and whichever CLI is attached to the topic answers there. Claude Code on one project, Codex on the next, no app switch.
Why C3
- One inbox for every agent. Claude Code, Codex, Cursor, Grok Build, Antigravity, dcode, Claude Desktop. Attach any of them to a project topic; you message the project, not the vendor.
- The sessions are the ones already in your terminal. C3 is a bridge, not a runtime. No hosted copy of your repo, no second agent, no subscription. Your bot token, your queue, your hardware.
- Approve tool calls from your phone. With Claude Code, the topic shows the literal command and an Allow/Deny keyboard. A tap from an allowlisted operator authorizes it; anyone else's tap is ignored.
- Voice in. Send a voice note; the session receives the transcript as a native turn.
- Nothing gets lost. Messages are held durably until delivered or fetched, within the queue's documented limits (1,000 messages / 90 days). Duplicates after expiry, restart, or degraded mode are allowed; silent loss is not.
Go, MIT, Linux and macOS (Windows in beta).
Telegram topic "api" ─┐
├─⇄ c3-broker ⇄ attached CLI session
Web chat (operator) ───┘ │
no session attached?
│
▼
durable inbound queue
What it looks like
You send a voice note to the Telegram topic for a project:
Run the tests, fix the flaky one, and tell me what changed.
Claude Code receives a native <channel> turn whose content is:
[Transcribed voice]: Run the tests, fix the flaky one, and tell me what changed.
C3 writes the voice note to its durable queue before transcription starts. STT
runs in a bounded background scheduler, so a provider outage cannot hold the
Telegram offset or block other messages on that topic. A pending placeholder is
visible through fetch_queue; when transcription finishes, C3 resolves that row
and offers the final revision for delivery. Transient download failures retry automatically.
When a tool call needs approval, the topic shows the literal command and a real inline keyboard:
🔐 Permission: Bash
git push origin fix/flaky-test
[✅ Allow] [❌ Deny]
The agent's reply comes back to that same topic. If the session is down when you send the next message, C3 stores it and tells you so:
📨 Held — nothing lost. 1 message queued. Send /status to check.
Held counts only rows waiting for delivery, excluding open live attempts and fetch groups.
Telegram sends one Held reply per backlog episode, quoting a local source message.
Pending voice work uses its quoted readback instead, with a short held-count suffix
when needed. Automatic route diagnostics stay out of chat; /status describes delivery
availability. A brief channel timeout followed by confirmed inbox delivery produces no false Held notice.
Attach a session to that topic and the queued message is waiting for it. You don't re-record the voice note.
If C3 could not open its queue directory at startup, it holds inbound at Telegram:
⚠️ Held at Telegram — local queue unavailable.
C3's durable queue is DISABLED for this run — inbound is held at Telegram and replayed when the broker restarts with a working queue; anything delivered live in the meantime will arrive again.
Send /status to check.
It warns your DM at startup too, and /status keeps saying so until you fix it. Live delivery
continues best-effort. The held Telegram offset applies across all topics, so later inbound
can wait until the broker restarts with a working queue. Restart within Telegram's 24-hour update
retention; after roughly 100 waiting updates, newer messages stop arriving until then.
See Degraded mode in docs/USAGE.md.
(Those are the strings C3 actually renders, copied out of the code — not a mock-up.)
Adapters
An adapter is a small MCP server that connects one host CLI to the broker. Seven ship:
| Adapter | Host | Inbound delivery | Tools | Notes |
|---|---|---|---|---|
c3-claude-adapter | Claude Code | live push (channel or owning-session inbox) | 12 | The reference adapter. Only one with ask and the permission relay. |
c3-codex-adapter | Codex | queued input on supported Codex; pull-only if unsupported | 12 | Queue-acceptance ack, not transcript receipt. Adds codex_forward; no ask or permission relay. Launcher → app-server → adapter → TUI; opt-in native queue also supported. |
c3-desktop-adapter | Claude Desktop | pull only | 13 | Adds observe and open_inbox, plus an inbox panel. You ask Claude to check; it calls fetch_queue. |
c3-grok-adapter | Grok Build | live push, needs leader mode | 11 | Requires [cli] use_leader = true in Grok's config. Without it, pull only. |
c3-agy-adapter | Antigravity CLI | pull only | 11 | The host has no async push. Least travelled; no dedicated doc yet. |
c3-cursor-adapter | Cursor Agent CLI | pull only | 11 | Stock interactive TUI. No idle-wake / channel push on Cursor — use fetch_queue. Install: c3-broker install-cursor. |
c3-dcode-adapter | dcode (deepagents-code) | live push via the external-event socket; pull only without it | 11 | {"kind":"prompt"} events land as literal user turns. Needs DEEPAGENTS_CODE_EXTERNAL_EVENT_SOCKET=1 at TUI launch, else fetch_queue. Slash commands /skill:c3-{attach,fetch,topics}. Install: c3-broker install-dcode. |
Claude Code is the adapter the others are measured against — everything else trades something
away, and the Inbound column is where you'll feel it. Claude Code uses the C3
plugin; its launcher wrapper is optional. Other integrations use c3-broker install-codex-shim, install-desktop, install-grok,
install-agy, install-cursor, or install-dcode. Details in docs/ADAPTERS.md,
docs/DESKTOP.md, and docs/GROK-INJECT.md.
Channels
Telegram is the channel. A private web chat also lives in the broker as an opt-in preview: setup never enables it, and it needs a manual channels.web entry plus a trusted private CA on your devices.
| Channel | Status | Carries |
|---|---|---|
| Telegram | shipping | Markdown, quote-replies, six media kinds, edits, reactions, polls, inline buttons |
| Web | preview, opt-in | Private-CA HTTPS on the tailnet; rich text, typing, edits, held/system notices, and optional spoken replies in on-the-go mode; magic-link login through the Telegram operator DM |
Each CLI session drives one claimed route at a time: use attach web on the go and attach
back to a Telegram topic at the desk. The web surface is private-first and intentionally
drive-only in phase 1; permission prompts remain at the laptop. See
docs/WEB.md and docs/CHANNELS.md.
Bundled plugins
Two, compiled into the broker: stt and tts.
| Plugin | Does | Default | Needs |
|---|---|---|---|
stt | Transcribes Telegram voice notes into [Transcribed voice]: … | on | python3, an API key for at least one provider, and ffmpeg for the pre-send silence gate (optional) |
tts | Synthesizes speakable reply text as MP3 for voice-capable surfaces | on | python3, an API key for at least one provider, and ffmpeg for the OpenRouter fallback |
The STT Go hook shells out to four bundled Python providers until one returns a transcript:
OpenRouter Gemini 3 Flash, Soniox Async v5, ElevenLabs Scribe v2, then Sarvam Saaras v3;
C3_STT_CHAIN overrides that order. The TTS Go hook shells out until one provider returns
MP3 audio: Sarvam Bulbul v3, ElevenLabs Flash v2.5, then OpenRouter Gemini TTS;
C3_TTS_CHAIN overrides that order. Both handlers resolve through an explicit config path,
Claude Code's plugin root, a source checkout, or the runtime bundle beside release binaries;
install-desktop records the resolved paths for non-plugin hosts. Disable either with
plugins.<name>.enabled = false.
External or loadable plugins are not implemented: plugins.<name> in the config is a
settings bag for built-ins, not a loader. See docs/PLUGINS.md.
Is C3 for you?
C3 fits when:
- you run coding agents across several projects and want one phone inbox without losing track of which project a message belongs to;
- you want to steer the actual CLI process on your machine — including setups behind Bedrock, Vertex, an API gateway, or a proxy;
- you want a self-hosted bridge whose bot token and message queue stay on your hardware.
It works fine with a single session. The architecture starts to matter once you have several.
What ships today
- One bot, many sessions. A single broker owns the token. Every project gets a forum topic, and only the session holding that topic's claim receives its messages.
- Topic routing and session resume. Attach a session to a topic once; a resumed session silently re-attaches only to its own recorded topic. A fresh session asks before claiming anything.
- Inbound survives sleeping sessions. Messages enter a durable on-disk queue; broker-owned attempts retire negotiated deliveries only after host receipts.
- Rich two-way Telegram — markdown, quote-replies, attachments, edits, reactions, polls, and inline buttons.
- Voice notes — C3 persists an honest pending row first, then the bundled STT
chain turns phone audio into
[Transcribed voice]: …off the route worker; the original attachment stays available for re-transcription. - Remote permission decisions — Claude Code can relay its permission prompt as an Allow/Deny keyboard. Only a DM-paired operator's tap becomes a verdict, and command previews render literally rather than as markdown.
- Telegram approval of auto-mode denials (opt-in, off by default) — when Claude Code's auto-mode classifier denies a tool call, an operator can tap Allow once to let the model's next identical retry through, once. The card shows the full, unmasked tool input to everyone in the topic. Linux and macOS only. See Approving auto-mode denials.
- Your CLIs, your auth, your machine — C3 is a local multiplexer. Each host keeps its normal models, credentials, tools, sandboxes, and project directories.
- Local, inspectable state — Go binaries, MIT licence, a mode-0600 config file, and a queue you can read on disk. No C3 cloud relay.
Before you install — the trust model
Two things worth knowing up front, because they're easy to miss:
- Anyone in the paired Telegram group can drive the agent. C3 pipes chat messages into a coding CLI that holds tool access on your machine. Message content is trusted about as far as you trust the group. Approving a tool call is stricter — only a DM-paired operator's tap counts — but sending messages into the agent's context is group-wide. Pair a group you'd hand a shell to.
- Your bot token is a password. It lives in
~/.config/c3/mappings.json(mode 0600) on your machine. Anyone holding it can read every message in that group and post as the bot.
Install
Linux is the primary, fully supported platform. macOS has prebuilt binaries. Windows is
beta: prebuilt binaries are published, but inbound is poll-only and self-update is
refused while the .exe files may be live (quit C3 and re-extract the tarball instead).
In any Claude Code session, paste:
follow https://github.com/Andrometiq/c3/blob/master/INSTALL.md to install c3
The playbook asks which host you're installing for and sets up the matching path. You provide
a Telegram bot token and send two short pairing codes; setup discovers your user id and the
group's chat id without an id hunt. Codex integration is a deliberate opt-in step, because
C3's launcher is also named codex and takes the real command's place on PATH. Grok and
Antigravity and Cursor and dcode are set up after the base install with c3-broker install-grok / install-agy / install-cursor / install-dcode.
See INSTALL.md for the agent-driven playbook and
docs/INSTALL.md for the human walkthrough.
Why Claude Code needs a development-channels flag
Live inbound to Claude Code currently starts with:
claude --dangerously-load-development-channels=plugin:c3@c3
Claude Code applies that preview guardrail to every locally-installed channel plugin — it
isn't a C3 hack. Without the flag, C3 can use the session's inherited owning-session
inbox; only transcript-confirmed delivery consumes the queued message.
That route has no Telegram permission relay or native question answering, and its
peer user-turn shape is verified on Claude Code 2.1.263; mid-turn peer intake is
verified on 2.1.266. If neither route confirms,
inbound stays available through fetch_queue. With negotiated fetch receipts,
rows remain queued until the host records the complete tool result; unconfirmed
reservations become fetchable again after 60 seconds. Status shows which route is live. Eligible Claude sessions negotiate
broker-owned delivery: status shows waiting, live: channel, confirmed <age>,
live: inbox, confirmed <age>, or pull-only (<reason>). After a channel timeout,
the broker tries an eligible inbox with a new attempt. Flagless and background
sessions can use inbox first. After both transports are exhausted, retries wait
for reconnect, attach, changed eligibility, another confirmed route, or a new
message arriving at least 60 seconds after exhaustion. If the transcript or
inbox becomes available after startup, C3 reconnects to offer delivery automatically.
The first offer waits until the host finishes its MCP initialization.
The installer can
also offer a small claude shim so the flag is automatic for interactive
launches. The wrapper is opt-in, default no: setup changes ~/.local/bin/claude
only after an explicit yes, or with c3-broker setup finish --claude-shim.
Add it later with c3-broker install-claude-shim; remove it with
c3-broker uninstall-claude-shim. Installation checks daemon dispatch and
version, rolling back a new link if either check fails. Known Claude subcommands
pass through untouched. See Anthropic's
Channels documentation.
Stability
C3 is pre-1.0. Interfaces can still move between minor versions; what's pinned is pinned deliberately:
- The on-disk queue format and the adapter IPC wire are pinned as of v0.1.0. Every wire
field carries an explicit JSON name, so renaming a Go field can't silently move the format
under a queued message or a third-party adapter. The IPC handshake exchanges a
protocol_versionin both directions (currently 1; absent means 1). It is bumped only for a change a peer on the other version could misread — never for a new optional field or a new op, which stay compatible both ways. A mismatch is logged on both sides and the connection is kept, because a version skew is the normal state for a few seconds after an update. ~/.config/c3/mappings.jsoncarries aschema_version. The broker refuses a version it doesn't recognise rather than guessing at it.- Conversation, user, and message identifiers remain 64-bit integers in v0.1. Route keys namespace them by channel; phase-1 web deliberately reuses the numeric Telegram operator id. Transports with opaque string ids still need a durable mapping, and the interface may evolve when one lands — not silently underneath the pinned queue or IPC formats.
Updating C3
Run /c3:update (or /c3:build for a source checkout). The final broker bounce
compares running and installed build identities. Matching builds need no upgrade.
When builds differ and self-exec is enabled with a compatible contract, open
Claude sessions upgrade themselves after adapter requests and live deliveries
settle, so updates are picked up in place. Sessions that cannot self-exec or lack
a compatible contract receive a one-time notice to run /mcp and reconnect c3.
Release version ordering does not select the upgrade path. C3-controlled restarts
pause new prompts and live pushes, then wait up to 60 seconds for open prompts.
At the cap they cancel unanswered C3 requests and attempt notices on their original
routes. Cancelling a permission relay cannot cancel the host request at the laptop.
Notices can fail, with no later retry; local cancellation still takes effect.
Controlled shutdown has a 90-second watchdog armed at first intent; the client
waits up to 91 seconds for exit, measured from its own request rather than
from the broker's later intent, so it normally outlives that watchdog. SIGTERM/SIGINT
preempt a controlled drain and use ordinary shutdown. OS termination and crashes retain
the existing behavior. Older brokers reject the new restart request visibly.
See Updating C3 for the full boundary.
Releases
Release tarballs are published on GitHub alongside a SHA256SUMS file. The updater
(c3-broker update, or /c3:update inside Claude Code) fetches over HTTPS and verifies the
SHA-256 digest before it replaces anything; a mismatch aborts with nothing installed, and a
release with no SHA256SUMS asset is refused outright.
Releases are not cryptographically signed yet. A checksum published next to the artifact proves integrity of the download, not provenance of the build. Signing is planned, and from the version that introduces it onward it will be mandatory — checksum-only verification will stop being accepted at that point.
How is this different from X?
Bridging a coding agent to Telegram is a well-populated genre. What each neighbour does, and where C3 sits:
- Anthropic's official Claude Code Channels — the closest first-party option. It runs one bot poller per open session (no topic-per-project multiplexing), is Claude-Code-only, is blocked behind Bedrock/Vertex/gateways, and by its own docs delivers events only while the session is open — no offline queue. C3 is one bot → many sessions with a topic per project, cross-CLI, self-hosted behind any auth, and holds a durable backlog.
- Happy — polished native iOS/Android/web apps for Claude Code and Codex, with realtime voice and remote approvals. It's app-based: a session list, not one-bot topic-per-project routing, and no durable offline queue. C3 is chat-native (no app to install, group visibility) and queues what you send while a session is down.
- cc-connect — a Go daemon bridging many agents to many chat platforms, multi-project,
with built-in STT/TTS. It has no Telegram forum-topic model, no documented durable offline
queue, and handles permissions with a
/modetoggle rather than relaying the CLI's own prompt. C3 trades breadth for the topic-per-project + durable-queue + prompt-relay cut. - OpenClaw — a self-hosted 24/7 assistant gateway with its own agent runtime and a skills marketplace. Different product: it's an always-on assistant platform, not a multiplexer of the real CLI sessions you already run. C3 drives your actual CLIs.
No single trait here is unique — self-hosted, CLI-agnostic, one token multiplexed into per-project topics, a durable inbound queue: each one has a rival. C3 is where all four overlap. If you only need one of them, one of the above is probably the easier install.
Architecture
Telegram Bot API
│
┌──────┴───────┐
│ c3-broker │ one poller, routing + claims, durable queue,
│ (Go) │ plugin host, local IPC socket
└──────┬───────┘
│ local Unix socket
┌──────┼────────┬─────────┬─────────┬─────────┬─────────┐
▼ ▼ ▼ ▼ ▼ ▼ ▼
claude codex desktop grok agy cursor dcode
adapter adapter adapter adapter adapter adapter adapter
│ │ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼ ▼
CLI TUI Desktop TUI CLI CLI CLI
Broker. One long-running Go process owns the Telegram poller, web listener, per-route workers, claims, durable queues, outbound rate limits, and plugin hooks. Adapters connect over a local Unix socket. A singleton lock stops two brokers polling one token on the same machine.
Adapters. Thin MCP stdio servers connecting each host process to the broker. They expose only the capabilities that host can actually support, and reconnect after a broker bounce. Codex's live path adds its launcher and app-server because the app-server — not the TUI — owns MCP startup.
Channels. Telegram plus the private web chat (rich text, voice notes in, spoken replies out, hands-free mode, installable as an app), behind one internal transport interface.
Plugins. Built-in Go plugins subscribe to broker hooks. The shipped STT plugin drives a bundled Python provider chain; external loadable plugins remain roadmap work.
Config. ~/.config/c3/mappings.json, written mode 0600 with an atomic rewrite and one
backup. It contains the bot token — treat it like a password.
Routing
- Telegram topics are the primary path. A topic maps to at most one live session claim; a fresh session asks before creating, claiming, or stealing.
- Bot DMs route to whichever CLI explicitly claims
dm. - Groups without topics can map a whole group to one CLI.
Extending C3
- Add a built-in broker plugin under
internal/plugin/builtins/<name>/; STT is the worked example. - Implement a new channel behind
internal/channel/channel.goand wire it into the broker. - Build a new CLI adapter as an MCP + C3 IPC client with reconnect and capability reporting.
The real contracts and the honest effort involved are in
docs/PLUGINS.md, docs/CHANNELS.md, and
docs/ADAPTERS.md.
Building and contributing
Go ≥1.25. go build ./..., go test ./... (hermetic — no network needed), go vet ./...,
and gofmt -l . should print nothing. AGENTS.md is the operating doc for
contributors and AI coding agents alike; DECISIONS.md records why things are
the way they are.
Why the name?
C³ is the old military/NATO doctrine term — Command, Control, and Communications:
- Command — send intent to an agent.
- Control — supervise execution and answer its decisions.
- Communications — keep a reliable link between the phone and the local CLI.
Roadmap
What's next lives in ROADMAP.md. Shipped work is in git history.
License
MIT — see LICENSE.