tg-mcp
Bridges Claude Code sessions with a Telegram bot: sends Markdown messages, asks questions with tap-to-answer button options, sends text or base64 files (images as photos), fetches attachments you send back, and reads queued messages. Routes each project to its own forum topic, pushes owner replies back into running sessions, relays tool-approval prompts as Allow/Deny buttons, and restricts access to the owner.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@tg-mcpask me on Telegram with Allow/Deny buttons to approve the deployment"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
tg-mcp

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 ⇄ youNothing 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 a Markdown message to the project's topic |
| Ask a question (optionally with button options) and wait for the answer |
| Send a text or base64 file; images go as photos |
| Fetch a photo or file you sent (images come back as images) |
| Read queued messages (for sessions not running as a channel) |
| 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.tsSend /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 -fReverse 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 bare401 unauthorized, the same answer as no header at all;claude mcp listshows "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 listwarnsMissing environment variables: PWD, and the server creates a topic named${PWD}. Give every header variable a default, e.g.${TG_KEY:-}. With an emptyX-Cwdthe 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 namedunknown.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
PWDenvironment variable.$PWDexists 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-Cwddoes not have to be a path. Its value is the routing key, and a key of the formtopic:<name>(whatset_topicuses) 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
getUpdatesto one consumer per token, and the plugin starts its own poller (abun server.tsof about 250 MB) in every session where it is enabled. On Windows its stale-poller cleanup callsps, 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-launcherThat 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-tgin place ofclaude.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 plainclaude --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
askin the topic takes your next message (or button tap) as its answer./statusin 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-hooksWith 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 hasid,key,topic,text,meta(message_id, ts, attachment fields, in_reply_to),queued_at,paneandlive_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/ackwith{"id": 12, "outcome": "delivered" | "retry" | "skipped", "note": "...", "retry_sec": 30}(noteandretry_secoptional).delivereddrops the message and reacts 👀,retryhands it out again afterretry_sec,skippedleaves it to the agents (channel flush,inbox). Anoteis posted in the topic as a reply to the message.While the router polls, the messages it owns are hidden from
inboxand 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=1the server instructions also tell agents that a prompt starting with[telegram ...]is a Telegram message from the user, to be answered withsend.
Security notes
.envholds the bot token and the MCP bearer token; it is git-ignored. Keep itchmod 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=0to turn it off.
Development
bun test # formatter, store and routing tests
bun run typecheck
bun run dev # restart on changeLicense
This server cannot be deployed
Maintenance
Related MCP Connectors
Telegram bridge for your MCP-compatible agent. Bidirectional, no LLM in our stack.
Multi-tenant Telegram gateway for AI agents — HTTP+stdio, 8 tools, MTProto User API
Hosted Telegram MCP server for Claude, ChatGPT and Cursor, with each action switched per account.
Project memory, tasks and Telegram notifications for your coding agent. Chip account required.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables 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 npmMIT
- FlicenseNot gradedqualityAmaintenanceEnables 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.-
- AlicenseNot gradedqualityAmaintenanceEnables running Claude Code around the clock through Telegram, with voice notes exchanged in both directions, a self-healing daemon that keeps sessions alive, memory and history preserved across restarts, and a live view of every tool call as it happens.MIT
- AlicenseNot gradedqualityAmaintenanceEnables 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