Skip to main content
Glama

tg-mcp

tg-mcp: Claude Code agents message you in Telegram, your replies land in the running session

A Telegram bridge for Claude Code agents: one small server process that all your agents reach over HTTP. Agents can message you, ask you questions with tap-to-answer buttons, and send files. You can write back, and your message is pushed into the running session as a Claude Code channel event, the same way the official local Telegram plugin does it, without a local process per session.

laptop: claude ──HTTPS (MCP Streamable HTTP + Bearer)──► reverse proxy ──► tg-mcp (one Bun process)
                                                                              │ long polling
                                                                        Telegram Bot API ⇄ you
  • Nothing runs on the client. Claude Code connects to a remote HTTP MCP server.

  • A topic per project. The bot creates a forum topic in your private chat with it for each working directory (sent by the client in a header) and reuses it across sessions.

  • Two-way. Messages in a topic go to the agent working in that project: pushed live when the session runs as a channel, otherwise queued until the agent calls inbox.

  • Permission relay. Tool-approval prompts from channel sessions show up in Telegram with Allow / Deny buttons.

  • Owner-only. The bot ignores everyone except OWNER_ID.

Tools

Tool

What it does

send

Send a Markdown message to the project's topic

ask

Ask a question (optionally with button options) and wait for the answer

send_file

Send a text or base64 file; images go as photos

get_attachment

Fetch a photo or file you sent (images come back as images)

inbox

Read queued messages (for sessions not running as a channel)

set_topic

Use a differently named topic for this session

Related MCP server: Claude Telegram Supercharged

Server setup

Requirements: Bun 1.3+, a bot from @BotFather with Threaded Mode enabled (so the bot can create topics in private chats), and a TLS reverse proxy.

git clone https://github.com/Eithery-rc/tg-mcp && cd tg-mcp
bun install
cp .env.example .env && chmod 600 .env
# fill in TELEGRAM_BOT_TOKEN and MCP_TOKEN (openssl rand -hex 32)
bun src/main.ts

Send /start to your bot. While OWNER_ID is empty the bot runs in pairing mode and replies with your numeric id; put it into .env and restart.

To run it permanently as a systemd user service (needs loginctl enable-linger $USER):

ln -s ~/tg-mcp/deploy/tg-mcp.service ~/.config/systemd/user/
systemctl --user daemon-reload && systemctl --user enable --now tg-mcp
journalctl --user -u tg-mcp -f

Reverse proxy (nginx). SSE streams and ask calls stay open for a long time, so turn off buffering and raise the read timeout:

location /tg-mcp/ {
    proxy_pass http://127.0.0.1:8790/;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_buffering off;
    proxy_read_timeout 3700s;
    client_max_body_size 64m;
}

Client setup (Claude Code)

Add the server once, at user scope. ${PWD} and ${TG_CHANNEL} are expanded by Claude Code at startup: the first tells the server which project the session belongs to, the second whether the session listens as a channel.

claude mcp add --transport http -s user telegram https://example.com/tg-mcp/mcp \
  --header 'Authorization: Bearer <MCP_TOKEN>' \
  --header 'X-Cwd: ${PWD}' \
  --header 'X-Channel: ${TG_CHANNEL:-0}'

Every session can now use the tools. To have your Telegram messages pushed into a session, start it as a channel. During the channels research preview custom channels need the development flag (Claude Code shows a confirmation prompt on start):

alias claude-channel='TG_CHANNEL=1 claude --dangerously-load-development-channels server:telegram'

On Windows PowerShell ${PWD} does not work, see the notes below. In herdr, the herdr launcher sets a topic per tab instead.

Client setup notes

  • The bearer is MCP_TOKEN, not the bot token. A wrong token gets a bare 401 unauthorized, the same answer as no header at all; claude mcp list shows "Server rejected the configured Authorization header (HTTP 401)".

  • Header variables are expanded once, when a session starts, from the environment Claude Code was launched with. Changes apply to sessions started afterwards.

  • An unset variable without a default is sent literally. claude mcp list warns Missing environment variables: PWD, and the server creates a topic named ${PWD}. Give every header variable a default, e.g. ${TG_KEY:-}. With an empty X-Cwd the server asks the client for its workspace root (MCP roots), but Claude Code does not answer that, so such sessions end up in a topic named unknown.

  • Header values must be ASCII. With e.g. a Cyrillic folder or workspace name in a header variable the connection fails with Header 'X-Cwd' has invalid value. Strip or replace such characters before putting a value into a header variable.

  • PowerShell has no PWD environment variable. $PWD exists only inside PowerShell, so ${PWD} never expands for a Claude Code started from pwsh. Set your own variable in the script you launch Claude Code with.

  • X-Cwd does not have to be a path. Its value is the routing key, and a key of the form topic:<name> (what set_topic uses) gives a topic with exactly that name. This lets you group sessions by something other than the folder, e.g. a terminal workspace.

  • Every session can be a channel. Routing picks the most recently active channel session of a topic, and a reply to a bot message goes to the session that sent it, so launching all sessions with the channel flag works fine.

  • Remove the official local Telegram plugin if it used the same bot. Telegram serves getUpdates to one consumer per token, and the plugin starts its own poller (a bun server.ts of about 250 MB) in every session where it is enabled. On Windows its stale-poller cleanup calls ps, which does not exist there, so the pollers pile up.

  • To see what the server received, look for the session #N up: <key> channel=<bool> line it logs for every connection: journalctl --user -u tg-mcp | grep ' up:'.

herdr launcher

If you run your agents in herdr, claude-tg gives each herdr tab its own topic: the workspace's first tab is named after the workspace (Billing), other tabs get the tab label appended (Billing / 2, or Billing / hotfix after renaming the tab). Outside herdr it falls back to the folder. It comes with the herdr-wake plugin:

herdr plugin install MokuTeam/herdr-agent-board/herdr-wake
herdr plugin action invoke herdr-wake.install-launcher

That puts claude-tg into ~/.local/bin (a sh script for Git Bash, Linux and macOS, a .cmd for PowerShell; that folder must be on PATH, the install says when it is not). It sets TG_KEY to the tab's routing key (non-ASCII characters dropped, since headers must be ASCII) and runs claude with whatever arguments it got: claude-tg, claude-tg --dangerously-skip-permissions, claude-tg --resume <session id>. On Linux and macOS the sh script replaces itself with Claude; on Windows a small cmd.exe waits for it. Run the action again after updating the plugin: the key script is a copy beside the launchers.

Add the server with --header 'X-Cwd: ${TG_KEY:-}', which works from PowerShell too, unlike ${PWD}. A session started without the launcher then sends an empty key and lands in the unknown topic, so start them all through it.

Use it wherever Claude starts:

  • By hand: claude-tg in place of claude.

  • New sessions from the agent board: "launch": { "claude": ["claude-tg"] } in the board's config.json, plus "--dangerously-skip-permissions" if that is how you run Claude.

  • Sessions herdr-wake wakes: "wakeCommand": "claude-tg --resume {session}" in its config.json (same flags as above). With herdr-wake's hooks installed, herdr then also resumes sessions through it after a herdr restart, instead of a plain claude --resume.

The router maps the same keys back to tabs, so a message in a tab's topic reaches the session in that tab. To make sessions channels as well, set TG_CHANNEL=1 in the environment, add --header 'X-Channel: ${TG_CHANNEL:-0}' and start them with claude-tg --dangerously-load-development-channels server:telegram. Claude Code's own switches, such as CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 for agent teams, are not the launcher's business: put them in env in ~/.claude/settings.json.

Using it from scripts

POST /notify sends a plain message without MCP, for cron jobs, CI or Claude Code hooks:

curl -s https://example.com/tg-mcp/notify \
  -H "Authorization: Bearer $MCP_TOKEN" -H 'content-type: application/json' \
  -d '{"text": "backup finished", "topic": "server"}'

Pass "cwd": "/path/to/project" instead of topic to post into a project's topic.

How routing works

  • A message in a topic goes to the session that most recently did something in that project. Reply to a specific bot message to target the session that sent it.

  • If no channel session is listening, the message is queued. Channel sessions receive the queue as soon as they connect; other sessions see a hint in their next tool result.

  • An open ask in the topic takes your next message (or button tap) as its answer.

  • /status in the bot lists connected sessions.

  • If you delete a topic, the bot creates a new one the next time the agent writes.

Local router (optional)

Channels need a development flag during the research preview, and a channel session only gets messages while it runs. As an alternative, a router on your machine can take the messages that no channel session took and deliver them itself: for example a terminal plugin that finds the agent's pane, restarts a stopped session there, and types the message in. Set ROUTER=1 on the server to enable it. Without it the server behaves exactly as before and these endpoints answer 404.

If you run your agents in herdr, the herdr-wake plugin in this repository is such a router. It also puts sessions that sat idle for an hour to sleep and wakes them again for a Telegram message or a SendMessage from another session, which saves a lot of memory with many sessions open:

herdr plugin install MokuTeam/herdr-agent-board/herdr-wake
herdr plugin action invoke herdr-wake.start
herdr plugin action invoke herdr-wake.install-hooks

With it, sessions no longer need to run as channels: drop --dangerously-load-development-channels and X-Channel from your launcher. See herdr-wake's README for setup and configuration.

The protocol, for writing your own router:

  • GET /router/wait?timeout=25 (bearer auth) long-polls for queued messages, at most 50 seconds. Each item has id, key, topic, text, meta (message_id, ts, attachment fields, in_reply_to), queued_at, pane and live_panes. Messages are leased to the router for 3 minutes. A topic is handed out as a whole and only while none of its messages is leased, so its messages stay in order.

  • POST /router/ack with {"id": 12, "outcome": "delivered" | "retry" | "skipped", "note": "...", "retry_sec": 30} (note and retry_sec optional). delivered drops the message and reacts 👀, retry hands it out again after retry_sec, skipped leaves it to the agents (channel flush, inbox). A note is posted in the topic as a reply to the message.

  • While the router polls, the messages it owns are hidden from inbox and channel flushes, and the bot does not answer "nobody is connected". When the router stops polling for a minute, they fall back to the agents.

  • Pane hints: add --header 'X-Herdr-Pane: ${HERDR_PANE_ID:-}' to the client config. The server remembers the last pane per routing key and the pane behind every bot message, so a reply can reach the right pane even after its session exited. Any short id works ([A-Za-z0-9_:.-], up to 64 characters).

  • With ROUTER=1 the server instructions also tell agents that a prompt starting with [telegram ...] is a Telegram message from the user, to be answered with send.

Security notes

  • .env holds the bot token and the MCP bearer token; it is git-ignored. Keep it chmod 600.

  • Anything you send to the bot is injected into an agent session that can run tools. The bot drops messages from anyone but OWNER_ID, and the HTTP endpoint requires the bearer token.

  • Permission relay lets you approve tool calls from your phone. Set RELAY_PERMISSIONS=0 to turn it off.

Development

bun test          # formatter, store and routing tests
bun run typecheck
bun run dev       # restart on change

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables remote control of Claude Code through Telegram, letting users start or resume sessions, review tool calls with approve/deny buttons, and manage long-running work from a phone.
    13 npm
    MIT
  • F
    license
    Not graded
    quality
    A
    maintenance
    Enables users to drive Claude Code around the clock from Telegram, with two-way voice notes, documents, stickers, scheduled reminders and calendar access. It keeps conversation memory across restarts behind a self-healing daemon and streams every tool call live through Agentic Mode.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables users to run Claude Code remotely through Telegram, with voice-note transcription and spoken replies, persistent memory, scheduled reminders and calendar access, permission approvals, and live tool-call progress. It also supports daemonized 24/7 operation and a session/subagent dashboard.
    MIT