@safra36/telegram-mcp-server
v0.1.1
Published
Full-capability Telegram MCP server (stdio + Streamable HTTP) backed by your user account via mtcute.
Maintainers
Readme
telegram-mcp-server
Full-capability Telegram MCP (Model Context Protocol) server backed by your Telegram user account via mtcute. Drop it into Claude Desktop, Claude Code, Cursor, or any MCP host and let the model read your dialogs, search history, send messages, manage chats, and download media — with allowlists and rate limits keeping it on a leash.
Speaks both transports the spec defines:
- stdio — local subprocess (default for desktop hosts)
- Streamable HTTP — remote / multi-client, bearer-authed
⚠️ This is a Telegram user client, not a bot. It logs in with your phone number and can do anything you can. Read the Safety section before pointing an LLM at it.
Features
~40 tools across six categories. Write tools are hidden in the default read-only mode.
| Category | Tools |
| --- | --- |
| Read | list_dialogs, get_history, get_message, search_messages, get_chat_info, resolve_peer, get_user_info, get_chat_members, list_contacts, get_pinned_messages, get_message_reactions |
| Write | send_message, reply_to, edit_message, delete_messages, forward_messages, copy_message, pin_message, unpin_message, mark_read, send_reaction, send_typing |
| Media | send_media, send_album, download_media |
| Admin | join_chat, leave_chat, create_group, create_channel, set_chat_title, set_chat_description, ban_member, unban_member, kick_member, create_invite_link, revoke_invite_link, add_contact, remove_contact, block_user, unblock_user, delete_history |
| Account | get_me, get_common_chats, update_profile, set_username |
| Updates | wait_for_message |
Plus MCP resources (telegram://me, telegram://chat/{id}) and prompts (summarize_chat, draft_reply).
Guardrails:
- Allowlist per-category (
send/delete/admin) with*wildcard - Token-bucket rate limit: global + per-chat
- FloodWait auto-retry for short waits, surfaces longer ones as errors
- Log redaction of message text, captions, phone numbers, codes, tokens
- Mode gating: in
read-onlymode write tools aren't even registered (the model can't call what it can't see)
Quick start
1. Install
npm install -g @safra36/telegram-mcp-serverOr run from source:
git clone https://github.com/safra36/telegram-mcp-server
cd telegram-mcp-server
npm install
npm run build2. Get Telegram API credentials
Go to https://my.telegram.org/apps, sign in, create an app, and grab api_id + api_hash.
3. Configure
Two equally supported ways to feed config in. Pick whichever fits how you're invoking the server.
A. --config JSON argument — best for MCP host config blocks:
telegram-mcp-server start --config '{"apiId":1234567,"apiHash":"abc...","session":"./tg.session","mode":"read-only"}'Or point at a JSON file:
telegram-mcp-server start --config ./tg-mcp.jsonB. Environment variables — best for shell/dev use or hosts that prefer env:
export TG_API_ID=1234567
export TG_API_HASH=abc...
export TG_SESSION=./tg.session
telegram-mcp-server startPrecedence is --config > env vars > defaults, applied per-key. So you can put secrets in env and non-secret settings in JSON, or vice versa.
The server does not auto-load a .env file — that's the host's job, not a library's. If you want one anyway: npx dotenv -e .env -- telegram-mcp-server start.
Minimum required: apiId + apiHash. See the config reference for the full shape.
4. Log in (one-time)
telegram-mcp-server loginThis walks you through phone → code → optional 2FA password and writes the session file. You only need to do this once per session file.
5. Verify
telegram-mcp-server doctorConnects with your session and prints your user info.
6. Wire it into a host
telegram-mcp-server mcp-config --host claude-desktopCopy the printed JSON block into your host config. For Claude Desktop on Windows, that's %APPDATA%\Claude\claude_desktop_config.json; for macOS ~/Library/Application Support/Claude/claude_desktop_config.json.
Recommended shape — config inlined into args (no env block needed, all settings in one place):
{
"mcpServers": {
"telegram": {
"command": "npx",
"args": [
"-y", "@safra36/telegram-mcp-server", "start",
"--config", "{\"apiId\":1234567,\"apiHash\":\"abc...\",\"session\":\"C:/Users/you/tg.session\",\"mode\":\"read-only\"}"
]
}
}
}Or, equivalently, via env (use mcp-config --format env to generate):
{
"mcpServers": {
"telegram": {
"command": "npx",
"args": ["-y", "@safra36/telegram-mcp-server", "start"],
"env": {
"TG_API_ID": "1234567",
"TG_API_HASH": "abc...",
"TG_SESSION": "C:/Users/you/tg.session",
"TG_MODE": "read-only"
}
}
}
}Transports
stdio (default)
telegram-mcp-server startThe host spawns this as a subprocess and talks JSON-RPC over stdin/stdout. Logs go to stderr.
Streamable HTTP
telegram-mcp-server start --transport httpRequires TG_HTTP_TOKEN (≥16 chars) in the environment. Defaults to 127.0.0.1:7878. The server warns if you bind to a non-loopback address — you really want a reverse proxy with TLS in front of it if you do that.
Clients hit POST/GET/DELETE /mcp with Authorization: Bearer <token>. Sessions are tracked via Mcp-Session-Id per the Streamable HTTP spec.
CORS is allowlist-based (TG_HTTP_CORS_ORIGINS, comma-separated).
Modes & allowlists
TG_MODE controls what categories of tools are even registered:
read-only(default, recommended) — only read tools. The LLM literally cannot send a message.read-write— registers all tools, but write tools still gate on allowlists.
Allowlists (comma-separated; chat ids or usernames; * = any):
TG_ALLOWLIST_SEND=me,@my_test_group,-1001234567890
TG_ALLOWLIST_DELETE=me
TG_ALLOWLIST_ADMIN=If an allowlist is empty, that category is denied for every chat. If it's *, it's allowed everywhere — use with care.
Rate limiting
Token-bucket, refilled per-second:
TG_RATE_GLOBAL_PER_SEC=2
TG_RATE_GLOBAL_BURST=5
TG_RATE_CHAT_PER_SEC=1
TG_RATE_CHAT_BURST=3When exhausted, the tool returns an error result with the suggested retryAfterMs — the model can back off and try again.
FloodWait handling
Telegram returns FLOOD_WAIT_<n> when you're being too chatty. The server auto-retries once for short waits:
TG_FLOOD_WAIT_MAX_AUTO_RETRY_SEC=10Longer waits surface as errors (with the wait duration) so the model can decide whether to wait or do something else.
Configuration reference
Every setting has both a JSON key (for --config) and an env var. Either form works.
| JSON key | Env var | Default | Purpose |
| --- | --- | --- | --- |
| apiId | TG_API_ID | — | Required. From my.telegram.org |
| apiHash | TG_API_HASH | — | Required. From my.telegram.org |
| session | TG_SESSION | ./tg-session | mtcute session file/dir path |
| mode | TG_MODE | read-only | read-only | read-write |
| allowlist.send | TG_ALLOWLIST_SEND | (empty) | Chats where sending is allowed (* = any) |
| allowlist.delete | TG_ALLOWLIST_DELETE | (empty) | Chats where deleting is allowed |
| allowlist.admin | TG_ALLOWLIST_ADMIN | (empty) | Chats where admin ops are allowed |
| rateLimit.globalPerMin | TG_RATE_GLOBAL_PER_MIN | 20 | Global rate cap |
| rateLimit.perChatPerMin | TG_RATE_PER_CHAT_PER_MIN | 5 | Per-chat rate cap |
| floodWait.maxAutoRetrySec | TG_FLOODWAIT_MAX_RETRY_SEC | 30 | Auto-retry FloodWait up to this many seconds |
| downloadDir | TG_DOWNLOAD_DIR | ./downloads | Where download_media writes files |
| http.host | TG_HTTP_HOST | 127.0.0.1 | HTTP bind address |
| http.port | TG_HTTP_PORT | — | Set to enable HTTP transport |
| http.token | TG_HTTP_TOKEN | — | Bearer token, required when HTTP is on (≥16 chars) |
| http.cors | TG_HTTP_CORS | (empty) | CORS allowlist (env: comma-separated) |
| log.file | TG_LOG_FILE | — | Optional log file (otherwise stderr only) |
| log.redact | TG_LOG_REDACT | true | Redact message text / phone / token fields in logs |
| proxy | TG_PROXY_URL | — | Proxy URL — SOCKS4/5, HTTP/HTTPS, or MTProxy. See Proxy support |
Env vars taking lists (TG_ALLOWLIST_*, TG_HTTP_CORS) are comma-separated. JSON equivalents are arrays.
Proxy support
The server can route Telegram traffic through SOCKS4/5, HTTP/HTTPS, or MTProxy. Pass a URL in proxy / TG_PROXY_URL:
socks5://user:[email protected]:1080
socks4://1.2.3.4:1080
http://user:[email protected]:8080
https://user:[email protected]:443
https://t.me/proxy?server=example.com&port=443&secret=3dpBFlW2hP6Hq_WOwiNeKBYThe MTProxy form accepts both the raw secret hex and Telegram's t.me/proxy?... share URLs (including Fake TLS variants).
Proxy credentials in the URL are redacted by doctor output and the structured logger.
Safety
Read this.
- You are giving an LLM access to your Telegram account. Anything you can do, it can do. Start in
read-onlymode. Graduate toread-writeonly after you've watched it behave for a while, and only with a tight allowlist. - Use a dedicated test chat for write experiments. Set
allowlist.sendto just["@my_llm_sandbox"]and nothing else. delete_messagesanddelete_historyare irreversible. Keepallowlist.deleteempty unless you really mean it.- Session file = full account access. Treat it like a password. Don't commit it. Don't sync it to cloud storage.
- Logs may contain message metadata even with redaction on. Set
log.filecarefully. - HTTP transport on non-loopback = exposure. Bind to
127.0.0.1and tunnel via SSH, or put a TLS-terminating reverse proxy in front with the bearer token enforced.
Programmatic use
import { buildServer, loadConfig, startServerClient, createLogger } from '@safra36/telegram-mcp-server'
const config = loadConfig()
const log = createLogger(config.log)
const client = await startServerClient(config, log)
const server = buildServer({ client, config, log })
// Connect your own transport hereLicense
MIT
