telegram-mcp
by AlexB00M
README.md
# telegram-mcp
An MCP server for Telegram on **Telethon**: works from your own account with any chat, bot,
group or channel — read, write, press **any button**, wait for replies and **edits**, download
media, use inline bots and, if you enable it, any raw Telegram API method.
Works as a regular stdio MCP server: in Claude Code, in Claude Desktop and in any MCP client.
[Русская версия](README.ru.md)
## Install — one command
```bash
curl -fsSL https://raw.githubusercontent.com/AlexB00M/telegram-mcp/main/install.sh | bash
```
Installs the server with [uv](https://docs.astral.sh/uv) and registers it in Claude Code and
Claude Desktop. Options: `--name` (server name, default `telegram`), `--with PATH` (an extra
package, e.g. `jarvis-mcp` for voice commands), `--login` (sign in right away in the terminal).
From a clone: `./install.sh`. Needs Python ≥ 3.10.
## Accounts — several, managed on a page
Ask Claude: **"log in to Telegram"** or **"open Telegram accounts"**. A local page opens in your
browser (127.0.0.1, random port, one-time token), styled like Telegram:
- the list of accounts with avatar, @username, masked phone and status;
- **add an account** — api_id / api_hash, phone, the code Telegram sends and the two-step password
if you have one; the page shows step by step where to get api_id / api_hash
(https://my.telegram.org → API development tools → Create application);
- **make default** — the account tools use unless a session picked another;
- **sign out and delete** — ends the session on Telegram's side and deletes the account's session
file **and its api_id / api_hash** from the computer.
Codes, passwords and api_hash go from the browser straight to the server — never through Claude,
and the page never shows an api_hash back. From a terminal: `telegram-mcp-login` adds an account.
Each account is its own folder `~/.telegram-mcp/accounts/<name>/` (0700): `session.session` —
**this is access to the account** — and `account.json` with its api_id / api_hash. Common settings
(aliases, default chat, proxy) are in `~/.telegram-mcp/config.json`. A single session from older
versions moves into an account by itself.
**Account prompts.** Each account can have a prompt — what the account is, when to use it and what
to do in it ("Work: shop clients and orders. Formal tone, no emoji"). Edit it on the page or ask
Claude (`tg_account_prompt`). Claude gets the accounts and their prompts in the server
instructions at session start and in `tg_accounts`, and picks the account that fits the request.
**Which account.** The default one is used unless a session picked another; change it on the page
or by command (`tg_set_default_account`) — it applies at once, nothing restarts.
`tg_use_account("work")` makes only this MCP session act as another account;
`TG_MCP_ACCOUNT=work` pins an MCP server entry to one account.
## One daemon per machine
A Telegram session is one SQLite file. If every MCP process (each Claude Code session, Claude
Desktop, a voice assistant) opened it itself, they would fail with "database is locked" while
another one holds a write. So only a daemon holds the session, and each `telegram-mcp` is a thin
front: tool calls go to the daemon over `~/.telegram-mcp/daemon.sock`.
The daemon **starts by itself on the first call** and **exits after `TG_MCP_IDLE_S` seconds
without calls** (default 600): when nobody uses Telegram there is no process, no connection, no
CPU. Log: `~/.telegram-mcp/daemon.log`. `TG_MCP_NO_DAEMON=1` runs everything in-process (tests,
debugging).
## Connect by hand
```bash
claude mcp add -s user telegram telegram-mcp
```
```json
{ "mcpServers": { "telegram": { "command": "telegram-mcp" } } }
```
## Tools
**Service**
| tool | what it does |
| --- | --- |
| `tg_status` | authorization, account, aliases, proxy, paths — call it first when something fails |
| `tg_login` | add an account / sign in again: the accounts page on its sign-in form |
| `tg_accounts_page` | the accounts page: list, add, make default, sign out and delete |
| `tg_accounts` | accounts: name, @username, prompt, default, which one this session uses |
| `tg_use_account` | act as another account in this session (empty — back to the default) |
| `tg_set_default_account` | make an account the default for everyone, at once |
| `tg_account_prompt` | set an account's prompt: what it is, when to use it, what to do in it |
| `tg_reconnect` | reconnect (session dropped, proxy changed) |
| `tg_dialogs` | chats with id, @username, type, unread; filter, bots only |
| `tg_entity` | info about a chat or bot: description, the bot's commands |
| `tg_members` | group members: search by name/username, "is @user in this chat" |
| `tg_alias` | short names for chats and the default chat |
**Reading**
| tool | what it does |
| --- | --- |
| `tg_history` | messages: limit, search, ids, from whom, media filter, paging, download |
| `tg_buttons` | what can be pressed right now: the reply keyboard + inline buttons with indexes |
| `tg_comments` | the thread under a channel post |
| `tg_discussion` | a channel's discussion group |
| `tg_watch` | wait for new messages **and edits**, optionally until a regex matches |
| `tg_download` | download media by ids or the last N |
| `tg_profile_photo` | a user's / group's / channel's avatar (history with `limit>1`) |
| `tg_stories` | active stories (gone in 24 h), archive, by id; with download |
| `tg_posts` | stories saved to a profile; with download |
**Actions**
| tool | what it does |
| --- | --- |
| `tg_send` | send text (reply, md/html, no preview, silent) and wait for the answer |
| `tg_send_file` | send a file / photo / video / voice; several — as an album |
| `tg_click` | press any button by text / regex / index / row+col |
| `tg_start_bot` | `/start` with a deep-link payload |
| `tg_inline_query` | a bot's inline mode (`@bot query`), optionally send a result |
| `tg_edit`, `tg_delete`, `tg_forward` | edit / delete / forward |
| `tg_mark_read`, `tg_action` | mark as read; "typing…" and other statuses |
| `tg_join` | join a channel or group by @username or t.me/+hash |
| `tg_raw` | **any** Telegram API method (off by default) |
Most actions take `wait` (seconds to wait for an answer, 0 — don't) and `download` (fetch media
from the answer). The answer comes in `reply`; edited messages carry `"event": "edited"`.
### Buttons
`tg_buttons` lists everything that can be pressed; then `tg_click` with `button: "like"`
(substring), `button: "^Open"` (regex), `button: "1"` (index) or `row`/`col`:
- `callback` — really pressed; `bot_answer` (the popup text) and the message's edits come back;
- `text` / reply keyboard — sent as the button's text;
- `url` / `webview` — the link is **returned**, no browser opens;
- `switch_inline` — use `tg_inline_query`;
- `request_phone` / `request_location` / `request_peer` give the bot your phone, location or a
contact, so they need an explicit `allow_sensitive: true`.
A reply keyboard arrives only in the message that set it, so the server remembers it in
`~/.telegram-mcp/keyboards.json` — one entry per chat, only button rows and the message id, never
message texts; at most 200 chats (`TG_MCP_KB_MAX_PEERS`).
### Where downloads go
| type | folder |
| --- | --- |
| photos, avatars | `~/Pictures/telegram/` |
| video, round videos, gif | `~/Movies/telegram/` |
| voice, music | `~/Music/telegram/` |
| documents, stickers, other | `~/.telegram-mcp/media/` |
Inside — a subfolder per source: `users/<who>`, `chats/<chat>`, `channels/<channel>`,
`bots/<bot>`. `tg_status` always shows the current paths.
## Environment
| variable | default | |
| --- | --- | --- |
| `TG_MCP_ACCOUNT` | the default account | the account this MCP server entry uses |
| `TG_MCP_DEFAULT_PEER` | — | the chat when `peer` is not given |
| `TG_MCP_PROXY` | — | `socks5://user:pass@host:port`, `http://…`, `mtproxy://secret@host:port` |
| `TG_MCP_ENABLE_RAW` | `0` | enable `tg_raw` |
| `TG_MCP_HOME` | `~/.telegram-mcp` | config, session, media |
| `TG_MCP_IDLE_S` | `600` | the daemon exits after this many seconds without calls |
| `TG_MCP_NO_DAEMON` | `0` | `1` — no daemon, everything in-process |
| `TG_MCP_APP_NAME` | `telegram` | the media subfolder name |
| `TG_MCP_PICTURES_DIR` / `_MOVIES_DIR` / `_MUSIC_DIR` | `~/Pictures/<app>`… | media roots |
| `TG_MCP_MAX_DOWNLOADS` | `10` | files per call |
| `TG_MCP_CONNECT_TIMEOUT` | `20` | seconds |
| `TG_MCP_LOG_LEVEL` | `WARNING` | logs go to stderr |
## Worth knowing
- This is a self-bot on your own account. Telegram treats bursts of automated actions as spam —
keep pauses, especially for mass clicks and messages.
- `session.session` is full access to the account. Never commit or share it. Revoke: delete the
account on the accounts page, or Telegram → Settings → Devices → end the `telegram-mcp` session.
- `tg_raw` can do literally anything, including irreversible things, so it is off by default.
## Voice commands (optional)
With `jarvis-mcp` — the SDK of the Jarvis voice assistant — installed next to the server
(`./install.sh --with path/to/jarvis-mcp`), the server also publishes fast voice commands in
Russian («непрочитанные», «прочитай сообщения от мамы», «напиши маме в телеграм …» — the last one
with confirmation). Without it this is a plain MCP server.
## Development
```bash
uv sync && uv run pytest
```
MIT License.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues