Skip to main content
Glama

telegram-mcp

Telegram for Claude Code, through one always-on daemon instead of a Telegram connection per session.

Claude session A ─┐ stdio                                   
Claude session B ─┼─ tgmcp mcp (thin proxy, no Telegram) ── WebSocket ──► tgmcp daemon ──► Telegram
Claude session C ─┘                                          127.0.0.1      (launchd, KeepAlive)
  • The daemon (tgmcp daemon) runs under launchd, starts at login, and is restarted if it ever exits. It holds the only Telegram connection (your user account, via mtcute), reads the rules file, and keeps every session's watches.

  • The proxy (tgmcp mcp) is what Claude Code launches for each session. It has no Telegram connection. It forwards tool calls to the daemon, and if the daemon restarts it reconnects by itself and restores the session's watches. It never exits over daemon trouble, so Claude Code never marks the server "failed" and you never have to /mcp reconnect.

Staying connected without a human

  • mtcute reconnects dropped sockets by itself.

  • A watchdog pings Telegram every 30 s and rebuilds the client when two pings fail, when it has been stuck "connecting" for 90 s, or straight after the Mac wakes from sleep. Rebuilds retry forever, backing off up to a minute.

  • Messages that arrive while the connection is down are caught up after it reconnects (mtcute's update gap recovery), and each one is delivered only once.

  • A tool call made during a reconnect waits up to ~25 s for the connection instead of failing.

  • If the daemon process dies, launchd restarts it within 5 s. The proxies reconnect and re-register their watches.

  • Only a revoked or missing login stops it, because that needs you: tgmcp login.

Related MCP server: Claude Telegram Supercharged

Setup

bun install
bun link                          # puts `tgmcp` on your PATH (or use `bun run src/cli.ts …`)
tgmcp install                     # launchd agent: starts the daemon now and at every login
tgmcp login                       # in a terminal: api_id/api_hash (if not saved yet), phone, code, 2FA
tgmcp menubar                     # menu bar app: approvals, listening sessions, recent sends (starts at login)
tgmcp mcp-add                     # prints the `claude mcp add` command for your paths
tgmcp status

tgmcp call <tool> '<json>' runs any tool as a one-off session, e.g. tgmcp call telegram_get_rules.

API credentials come from TELEGRAM_API_ID / TELEGRAM_API_HASH if set, otherwise credentials.json (created by tgmcp login). Get them at https://my.telegram.org → API development tools.

Files

All in ~/.config/telegram-mcp/ (folder mode 0700; override with TELEGRAM_MCP_HOME):

File

What

config.json

The rules file (below). Edits apply as soon as you save. A broken edit keeps the last good version and shows up in tgmcp status.

credentials.json

api_id / api_hash

session.db

The Telegram login. Treat it like a password.

token

Secret for the local API, so web pages and other users can't talk to the daemon

daemon.log

The daemon log (tgmcp logs)

recent.json

The last 20 sent messages (shown in the menu bar)

The daemon listens on 127.0.0.1:47823 (TELEGRAM_MCP_PORT).

The rules file

{
  "guides": ["Free-text rules every session sees when it connects."],
  "footer": "\n\n✨ sᴇɴᴛ ᴠɪᴀ ᴄʟᴀᴜᴅᴇ",        // appended to every message, once
  "defaultPolicy": "approve",                // chats in no list: "approve" or "block"
  "rules": {
    "blocked":         [5550001111],         // never send
    "requireApproval": ["team-qa"],          // always ask you first ("Ask every time")
    "autoSend":        ["me", "@someone"],   // send without asking ("Always allow")
    "autoReply":       ["leo"]               // sessions may listen to these chats and reply without asking
  },
  "chats": {
    "leo": { "id": 123456789, "username": "leo_example", "name": "Leo Perera", "aliases": ["leonard"] },
    "team-qa": { "id": -1001234567890, "name": "Team QA", "topicId": 42 }
  }
}

Lists take a nickname, an @username, a numeric id, or "me". If a chat is on more than one list, the safer list wins: blocked > requireApproval > autoSend > autoReply > defaultPolicy. blocked matches the raw id, so 5550001111 also blocks -1005550001111.

autoReply chats are the ones Claude sessions may listen to. A session subscribes with telegram_watch and stays subscribed until it says stop. Every incoming message from that chat goes to every subscribed session, and each one decides how to reply (the auto reply happens on the Claude side). While subscribed, a session's replies to that chat go out without approval, unless the chat is also on requireApproval or blocked. Taking a chat off the list stops delivery at once.

First message to a new chat. A chat that is on neither autoSend nor requireApproval always needs approval. The approval also asks what should happen next time:

  • Always allow puts it on autoSend.

  • Ask every time puts it on requireApproval.

Approving also saves the chat under chats, with a suggested nickname you can edit: the first name, else the username, else the full name. From then on the chat follows that list. Only your choice in an approval adds chats to the lists; sessions can't.

Approval happens in the first place available:

  1. The menu bar app, when it's running. The draft appears there and as a notification. Both offer Approve / Deny, or Deny / Ask every time / Always allow for a new chat. The Claude tool call waits for your click: Claude Code backgrounds it after 2 minutes, and the result arrives when you decide.

  2. A Claude Code dialog showing the exact recipient and text, plus the next-time choice and nickname for a new chat.

  3. In chat: the tool returns an approval id. The model must ask you before it calls telegram_confirm_send, passing your next-time choice as remember.

Nothing is sent unless you accept. Drafts expire after 20 minutes. The rules are checked again at send time.

Nicknames (chats) let "send to leo" mean exactly one person. Only exact matches resolve: a nickname, an alias, a saved full name, a username or an id. A near match like "leon" is refused with suggestions, never picked. Before every send the daemon checks that the saved id still has the saved username. If either has changed, it refuses until the entry is re-saved. Sessions can add, update and remove nicknames (telegram_save_chat, telegram_remove_chat). They cannot change rules, footer or guides, and they cannot repoint, create or remove a name that the rules use: only you can, by editing the file.

Tools

Tool

telegram_status

connection, account, config errors, this session's watches

telegram_get_rules

guides, footer, lists, saved nicknames

telegram_list_chats

recent chats with unread counts and what sending there would do

telegram_search_chats

find a person or group to save

telegram_read_messages

read a chat or forum topic

telegram_send_message

send, following the rules

telegram_confirm_send / telegram_cancel_send

approval fallback for clients without dialogs

telegram_save_chat / telegram_remove_chat

manage nicknames

telegram_watch / telegram_unwatch

subscribe to an autoReply chat (optionally one sender, one topic)

telegram_get_events

collect watched messages nobody pushed yet

telegram_mark_read

mark a chat read

Watching ("tell me when Leo replies")

telegram_watch subscribes the session to a chat on the autoReply list until it calls telegram_unwatch or ends. Each new incoming message reaches every subscribed session once, by the first of these that is available to that session:

  1. Monitor (the default): telegram_watch returns a ws://127.0.0.1…/events?session=… URL, and Claude opens its Monitor tool on it. Each message becomes a notification in the session. A Monitor stops after 30 minutes, or when the daemon restarts. Claude is told to restart it straight away with the same URL whenever it ends while watches are active (telegram_status shows the URL again). Nothing is lost in between, because messages queue up and are flushed when the Monitor reconnects.

  2. Channel push (optional: tgmcp mcp --channel, plus claude --dangerously-load-development-channels server:<name>): messages appear in the session by themselves as <channel> events, with no Monitor needed. This uses Claude Code's channels research preview.

  3. Queue: telegram_get_events drains it. Every other tool result also mentions when messages are waiting.

A session's watches outlive a daemon restart, because the proxy re-sends them. They also outlive a proxy blip for 5 minutes. They end when the Claude session ends.

Menu bar app

tgmcp menubar builds menubar/build/Telegram MCP.app (SwiftUI, no Dock icon) and starts it at login. Choosing Quit keeps it closed until your next login. It shows:

  • the connection, with a warning icon when the daemon or Telegram is down

  • Waiting for approval: the exact text, recipient, reason and asking session, with Approve / Deny. A new chat gets an editable nickname and Deny / Ask every time / Always allow instead. Each new draft also posts a notification with the same buttons.

  • Listening: each session that is watching chats, and whether its Monitor is connected

  • Recently sent: the last 5 chats you sent to. Hover a chat to see its messages, when they went, how they were approved, and from which session.

The menu bar icon shows the number of drafts waiting. tgmcp menubar uninstall removes it.

Development

bun test          # unit tests + an end-to-end test that spawns the real proxy against a daemon with a fake Telegram
bun run typecheck
bun scripts/demo-daemon.ts      # daemon with a fake Telegram and sample state, for working on the menu bar app

License

MIT. See LICENSE.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    A remote HTTP bridge that lets Claude Code agents message you on Telegram, ask questions with tap-to-answer buttons, and send files, while your replies are pushed into running sessions as channel events — with per-project topics, message queueing, and tool-approval relay.
    19 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.
    -
  • F
    license
    Not graded
    quality
    A
    maintenance
    Enables driving Claude Code entirely from a Telegram chat, with voice messages transcribed both ways, memory that survives restarts, live visibility into each tool call as it runs, and scheduling plus Google Calendar access. It also keeps a supervised 24/7 session alive so conversations, groups, and long research tasks continue without manual restarts.
    -