Fork of Anthropic's official
telegramplugin (claude-plugins-official, v0.0.6), forked into this repo for local development. See the workspace CLAUDE.md for context.
Connect a Telegram bot to your Claude Code with an MCP server.
The MCP server logs into Telegram as a bot and provides tools to Claude to reply, react, or edit messages. When you message the bot, the server forwards the message to your Claude Code session.
- Bun — the MCP server runs on Bun. Install with
curl -fsSL https://bun.sh/install | bash.
Default pairing flow for a single-user DM bot. See ACCESS.md for groups and multi-user setups.
1. Create a bot with BotFather.
Open a chat with @BotFather on Telegram and send /newbot. BotFather asks for two things:
- Name — the display name shown in chat headers (anything, can contain spaces)
- Username — a unique handle ending in
bot(e.g.my_assistant_bot). This becomes your bot's link:t.me/my_assistant_bot.
BotFather replies with a token that looks like 123456789:AAHfiqksKZ8... — that's the whole token, copy it including the leading number and colon.
2. Install the plugin.
These are Claude Code commands — run claude to start a session first.
Install the plugin:
/plugin install telegram-ng@cameri-skills
/reload-plugins
3. Give the server the token.
/telegram-ng:configure 123456789:AAHfiqksKZ8...
Writes TELEGRAM_BOT_TOKEN=... to ~/.claude/channels/telegram/.env. You can also write that file by hand, or set the variable in your shell environment — shell takes precedence.
To run multiple bots on one machine (different tokens, separate allowlists), point
TELEGRAM_STATE_DIRat a different directory per instance.
4. Relaunch with the channel flag.
The server won't connect without this — exit your session and start a new one:
claude --channels plugin:telegram-ng@cameri-skills5. Pair.
With Claude Code running from the previous step, DM your bot on Telegram — it replies with a 6-character pairing code. If the bot doesn't respond, make sure your session is running with --channels. In your Claude Code session:
/telegram-ng:access pair <code>
Your next DM reaches the assistant.
Unlike Discord, there's no server invite step — Telegram bots accept DMs immediately. Pairing handles the user-ID lookup so you never touch numeric IDs.
6. Lock it down.
Pairing is for capturing IDs. Once you're in, switch to allowlist so strangers don't get pairing-code replies. Ask Claude to do it, or /telegram-ng:access policy allowlist directly.
See ACCESS.md for DM policies, groups, mention detection, delivery config, skill commands, and the access.json schema.
Quick reference: IDs are numeric user IDs (get yours from @userinfobot). Default policy is pairing. ackReaction only accepts Telegram's fixed emoji whitelist.
| Tool | Purpose |
|---|---|
reply |
Send to a chat. Takes chat_id + text, optionally reply_to (message ID) for native threading and files (absolute paths) for attachments. Images (.jpg/.png/.gif/.webp) send as photos with inline preview; other types send as documents. Max 50MB each. format selects rendering: rich (default; Bot API 10.1+ structured messages — bold/italic, tables, headings, fenced code blocks, math, collapsible sections; 32768-char cap instead of 4096; no escaping needed, but inline single/double-backtick code spans aren't rendered — use a fenced code block instead), markdownv2 (classic Telegram formatting including inline code, but requires escaping special chars), or plain text (no formatting applied at all). Optional receiver_user_id sends an ephemeral reply visible only to that one group member instead of a normal group post (needs the bot to be a group admin — see bot_is_admin below); confirmed live — Telegram returns message_id: 0 for these since they're not persisted as normal messages, not an error. Auto-chunks text; files send as separate messages after the text. Returns the sent message ID(s). |
react |
Add an emoji reaction to a message by ID. Only Telegram's fixed whitelist is accepted (👍 👎 ❤ 🔥 👀 etc). |
edit_message |
Edit a message the bot previously sent. Useful for "working…" → result progress updates. Supports the same format values as reply. Only works on the bot's own messages. |
start_typing / stop_typing |
Keep Telegram's "typing…" indicator alive during a long tool-use stretch (Telegram clears it every ~5s on its own). reply clears it automatically once sent. |
stream_draft |
Stream a live "composing" preview of a message to a private chat while it's still being generated. Auto-expires after 30s and is never persisted — reply still has to send the final text. |
send_poll / stop_poll |
Send a poll (chat_id, question, options[]; non-anonymous by default so votes are attributable) and later close it for a final tally. Each vote/retraction arrives as its own informational <channel> notification — no reply is expected per vote. |
Inbound messages get an emoji reaction (if ackReaction is configured) as an
instant "seen" acknowledgment. The assistant calls start_typing itself for
longer work — see the tools table above.
Inbound <channel> meta may also include reply_to_text/reply_to_user (a
quoted message's text and sender), forwarded_from (a best-effort forward
provenance label), link_entities (a JSON array of hyperlink/mention targets
not visible in the plain text), and, in groups, bot_is_admin.
Poll votes/retractions arrive as their own <channel> notification (content
like "voted for: ..." or "retracted their vote"), with poll_id in the meta.
Only non-anonymous polls carry a voter to attribute — anonymous polls are
silently skipped.
Inbound photos are downloaded to ~/.claude/channels/telegram/inbox/ and the
local path is included in the <channel> notification so the assistant can
Read it. Telegram compresses photos — if you need the original file, send it
as a document instead (long-press → Send as File).
Telegram's Bot API exposes neither message history nor search. The bot
only sees messages as they arrive — no fetch_messages tool exists. If the
assistant needs earlier context, it will ask you to paste or summarize.
This also means there's no download_attachment tool for historical messages
— photos are downloaded eagerly on arrival since there's no way to fetch them
later.