Telegram MCP Server logo

Telegram MCP Server

CommunityPopular
chigwell

Telegram MCP server powered by Telethon to let MCP clients read chats, manage groups, and send/modify messages, media, contacts, and settings.

Publisherchigwell
Repositorytelegram-mcp
LanguagePython
Forks
430
Stars
1.7K
Available tools
0
Transport typestdio
Categories
LicenseApache-2.0
Links
  • Connect tools to AI workflows

    Telegram MCP Server exposes MCP capabilities that can be used by compatible AI clients and agents.

  • 0 available tools

    Browse the callable actions below, including names and descriptions when provided by the server.

  • Ready-to-copy setup

    Use the installation snippets to configure this server in your preferred MCP client.

  • Open source signals

    1.7K stars and 430 forks from the linked repository.

MCP Badge License: Apache 2.0 Python Lint & Format Check Docker Build & Compose Validation

A Telegram integration for Claude, Cursor, and other MCP-compatible clients. It exposes Telegram account, chat, message, contact, media, folder, and admin operations through the Model Context Protocol using Telethon.

🤖 MCP in Action

Basic Telegram MCP usage in Claude:

Telegram MCP in action

Asking Claude to analyze chat history and send a response:

Telegram MCP Request

Message sent successfully:

Telegram MCP Result

Contents

What It Can Do

The server currently includes 80+ MCP tools grouped into these areas:

  • Accounts: list configured accounts and route tool calls by account label.
  • Chats and groups: list chats, inspect metadata, create groups/channels, join or leave chats, invite or remove users, manage admins, bans, default permissions, slow mode, topics, invite links, common chats, read receipts, and message links.
  • Messages: send, schedule, edit, delete, forward, pin, unpin, mark read, reply, search, inspect context, create polls, manage reactions, inspect inline buttons, and press inline callbacks. send_message, reply_to_message, and edit_message support classic formatting (parse_mode='md'/'html') and server-side rich formatting (parse_mode='rich'/'rich_markdown'/'rich_html' — full Markdown/HTML with tables, headings, formulas, and collapsible sections). Rich modes require Telegram Premium on the account; Premium is re-checked on every call, and without it nothing is sent — the tool returns a structured telegram_premium_required result so the agent can reformat with classic modes and retry. send_message, reply_to_message, and edit_message also accept format_date to render a date as a tappable chip.
  • Contacts: list, search, add, delete, block, unblock, import, export, inspect direct chats, find recent contact interactions, and remember contacts by the names you actually use (see below).

Remembered contacts

set_contact_alias teaches the server what you call someone, and every tool that takes a chat_id understands it from then on — send_message("андрей бекендер", ...) just works. A contact can carry any number of aliases, which is how tags work: save both андрей бекендер and бекендер for the same person and either resolves.

Only an exact saved wording ever sends. Similar wording (Андрею бекендеру for a saved андрей бекендер) is matched too, but only to suggest: the tool sends nothing and asks you to confirm the contact by name. This is deliberate — Лена/Леня and Иван/Иванов differ exactly as much as a case ending does, so a matcher confident enough to handle declensions is also confident enough to message the wrong person whenever the one you meant is not saved yet. Confirming saves that wording as its own alias, so each new phrasing costs one yes/no the first time and nothing ever again. Set TELEGRAM_CONTACT_FUZZY=0 to drop the suggestions too.

When a reference is unknown, resembles one contact, matches several, or points at a contact that no longer resolves, tools send nothing and return a structured instruction telling the agent exactly what to ask you, to save the answer with set_contact_alias, and to retry once. list_contact_aliases shows one row per person with all their aliases (use it to spot a wrong memory), delete_contact_alias forgets one, and repointing an alias at someone else requires replace=True. The save path itself refuses a target it would have to guess at: contacts are saved by @username, phone, numeric ID, or an alias already confirmed for them.

Aliases live in ${XDG_STATE_HOME:-~/.local/state}/telegram-mcp/aliases.json (owner-only, written atomically); TELEGRAM_ALIASES_FILE overrides the path, and a pre-existing aliases.json next to the code is still read as a fallback.

  • Media: send files, download media, upload files, send voice notes, stickers, GIFs, inspect message media, and transcribe voice messages/video notes (see below).

Voice transcription

transcribe_voice(chat_id, message_id, engine=None) turns a voice message or video note into text. Two engines are available:

  • groq (default): uploads the recording to Groq's hosted whisper-large-v3-turbo. Leaves the server and costs a download+upload per call, but doesn't drop the recording's last few words the way native transcription does. Requires GROQ_API_KEY. Groq caps the size of a single upload, so a recording above TELEGRAM_TRANSCRIBE_GROQ_MAX_MB (default 25, the free-tier limit) is refused locally with a too_large error naming its size instead of being downloaded and rejected by the API. Raise the limit if your Groq tier allows bigger files, or transcribe that message with engine='telegram', which has no such cap.
  • telegram: native Telegram Premium transcription (messages.TranscribeAudioRequest). Free and never leaves Telegram, but empirically drops the last speech segment in roughly 2 of 3 recordings and requires Telegram Premium on the account. Long recordings come back pending and are polled automatically.

The engine is chosen per call via the engine argument, or otherwise defaults to TELEGRAM_TRANSCRIBE_ENGINE (groq or telegram). Results are cached by (chat_id, message_id, engine) in a local SQLite file so repeat reads and repeat listings never re-transcribe the same message. Concurrent requests for the same uncached recording are collapsed too: the second one waits for the first and returns its transcript, so a burst of callers costs one paid call, not one per caller. Every transcript is returned with a note marking it as a machine transcript, not a verbatim quote — treat it as a paraphrase, not exact wording.

get_history, get_messages, and list_messages fill in already-cached transcripts for voice messages instead of leaving the text empty, controlled by TELEGRAM_TRANSCRIBE:

  • off: transcription is disabled at runtime. The transcribe_voice tool stays registered and returns {"transcribed": false, "reason": "transcription_disabled"} instead of transcribing, and listings never show transcripts. Use TELEGRAM_EXPOSED_TOOLS to hide the tool itself.
  • on-demand (default): listings show cached transcripts but never spend an API call fetching a new one.
  • auto: listings also prefetch missing transcripts, bounded per call by TELEGRAM_TRANSCRIBE_MAX_VOICES/TELEGRAM_TRANSCRIBE_MAX_SECONDS (Groq isn't free, so this prefetch is budgeted rather than unbounded).

The cache lives in TELEGRAM_TRANSCRIPT_CACHE_DIR (default data/transcripts), written as a 700 directory / 600 file since it holds personal-chat text in plaintext — see Docker for why this needs its own volume mount in a container.

  • Profile and privacy: get your own account info, update profile fields, set or delete profile photos, inspect privacy settings, get user info/photos/status, and manage bot commands.
  • Folders and drafts: list, create, update, reorder, and delete Telegram folders; save, list, and clear drafts.
  • Events: wait for incoming messages with debounce (wait_for_new_message, wait_for_settled_message), optionally for one chat only via chat_id — without it any unrelated conversation wakes the wait — or enable the opt-in incoming event feed for callback-style delivery (see below).

All tool results that include Telegram user-controlled content are sanitized and, where practical, returned as structured JSON.

Reusing custom emoji

get_history, list_messages, search_messages, search_global, get_message_context (including replied_message), get_pinned_messages, and get_drafts include custom_emojis when the message contains custom emoji. get_messages and get_scheduled_messages include the same metadata as a JSON list in their text output. Ordinary messages keep their existing output.

json
{"text": "🍷 News", "custom_emojis": [{"emoji": "🍷", "id": "5368324170671202286"}]}

Each entry contains the fallback emoji and its Telegram document ID as a string. Repeated IDs appear once per message; different IDs remain separate even when their fallback emoji looks identical. This is a list of reusable emoji variants, not a map of their positions or a copy of all message formatting. Extraction uses the original Telegram entities, including TextCustomEmoji nodes in block-format rich_message content, and makes no additional API requests. Emoji joiners and flag tag characters are preserved in the fallback text.

To reuse an entry, set parse_mode="html" in send_message, reply_to_message, or edit_message, and insert <tg-emoji emoji-id="ID">EMOJI</tg-emoji> using its id and emoji. HTML-escape the fallback and other literal text. For example, the entry above becomes <tg-emoji emoji-id="5368324170671202286">🍷</tg-emoji>. Telegram's account restrictions still apply to sending custom emoji.

Tappable dates and times

Passing format_date to send_message, reply_to_message, or edit_message renders a tappable date/time chip — the same entity Telegram's apps attach when you type a recognizable date. Give the date text exactly as it appears in the message: '13/09', '13/09/2026', or '13/09 17:00'. The chip opens copy-date / add-to-calendar / reminder actions. Plain-text messages only — omit parse_mode. For example, send_message(chat_id, "Lunch 13/09 13:00", format_date="13/09 13:00") sends a message whose date opens that menu.

Incoming Event Feed (callback mode, Claude Code only)

By default, an agent waits for replies by calling wait_for_settled_message, which blocks up to the MCP tool timeout and must be re-called — that works everywhere (Codex, Cursor, etc.) and is unchanged.

Clients that can wake an agent on external output (Claude Code's persistent Monitor on tail -f) can switch to callback mode instead:

  1. The agent calls enable_incoming_feed (or set TELEGRAM_EVENT_FEED=1 in the environment to auto-enable). Each settled incoming burst is appended as one JSON line to ${XDG_STATE_HOME:-~/.local/state}/telegram-mcp/incoming_feed.jsonl, created owner-only (0600). Override the path with TELEGRAM_EVENT_FEED_FILE — an explicit path's directory must already exist. incoming_feed_status reports the effective path and a ready-to-use watch command.
  2. The agent arms a persistent Monitor with the watch_command returned by the tool. Every new line re-invokes the agent with the burst summary; no blocking tool call is held open, and the chat stays free.

disable_incoming_feed switches back; incoming_feed_status reports the current mode. While the feed is enabled it consumes settled bursts, so don't combine it with wait_for_settled_message. Feed lines contain user-generated name fields — treat them as untrusted data.

Requirements

  • Python 3.10+
  • Telegram API credentials from my.telegram.org/apps
  • A Telegram session string or file-based session
  • An MCP client such as Claude Desktop, Cursor, or another MCP-compatible host
  • Optional: uv for local development

Quick Start

Do not install this server with uvx telegram-mcp, uvx --from telegram-mcp, or pip install telegram-mcp. The telegram-mcp name on PyPI is currently owned by a different project and does not install this repository. Passing TELEGRAM_API_ID, TELEGRAM_API_HASH, or TELEGRAM_SESSION_STRING to that package can expose Telegram account credentials to unrelated third-party code.

1. Clone and Install

bash
git clone https://github.com/chigwell/telegram-mcp.git
cd telegram-mcp
uv sync

2. Generate a Session String

bash
uv run session_string_generator.py

Follow the prompts. Save the generated session string securely.

For scripted setup or operational runbooks, choose the login method explicitly:

bash
# QR login, recommended when you already have Telegram open on another device
uv run session_string_generator.py --qr

# Phone number + verification code login
uv run session_string_generator.py --phone

Without a flag, the generator keeps the interactive method prompt.

3. Configure Environment

Copy the example file and fill in your real values:

bash
cp .env.example .env

Single-account setup:

env
TELEGRAM_API_ID=your_api_id_here
TELEGRAM_API_HASH=your_api_hash_here
TELEGRAM_SESSION_STRING=your_session_string_here

By default, all Telegram MCP tools are exposed. If you want to prevent MCP clients from sending messages or performing chat/account mutations, set TELEGRAM_EXPOSED_TOOLS=read-only to expose only tools annotated with readOnlyHint=True:

env
TELEGRAM_EXPOSED_TOOLS=read-only

If read-only is too strict but all is too broad, append + and a comma-separated list of tool names to also expose those specific write tools. Every other write tool stays unregistered:

env
TELEGRAM_EXPOSED_TOOLS=read-only+send_message,reply_to_message,send_file

An unknown name in the allowlist aborts startup, so a typo cannot silently degrade into a narrower surface that looks like it worked.

This is an MCP tool-surface restriction, not a Telegram session sandbox or reduced Telegram account permission. The Telegram session string still has its normal authority inside the server process; read-only mode only prevents non-read-only tools from being registered and exposed through MCP. Accepted values are all (the default), read-only, and read-only+<tool>,<tool>.

A separate, hardcoded allowlist restricts send_voice, send_sticker, set_profile_photo, and edit_chat_photo to their expected file extensions; send_file and upload_file accept any extension by default. Use TELEGRAM_FILE_EXTENSIONS to add allowlists for those two, or to tighten or replace any of the hardcoded ones, in the same tool:.ext,.ext shape as TELEGRAM_EXPOSED_TOOLS, with entries separated by ;:

env
TELEGRAM_FILE_EXTENSIONS=send_file:.pdf,.png,.jpg;upload_file:.pdf,.png

Naming a tool that already has a hardcoded default replaces that tool's whole set rather than adding to it; any tool left unnamed keeps its default (so leaving this unset keeps today's behavior unchanged). Extensions are case-insensitive and the leading dot is optional (pdf and .pdf are equivalent). An unknown tool name, a malformed extension, or the same tool named twice aborts startup, the same way a typo in TELEGRAM_EXPOSED_TOOLS does.

This is defence in depth against accidents, not a security boundary. The check reads the final suffix, so an allowlist of .pdf still accepts payload.exe.pdf, and it says nothing about the bytes in the file. What it does reliably block is the careless case: extensionless secrets such as id_rsa or a bare .env. Files with ordinary extensions — .pem, .key, a .json token file, a .sqlite cookie store — are not covered unless you leave them out of the list yourself.

Voice transcription (see Voice transcription above) is off by default in the sense that no transcript is ever fetched unless you ask for one — transcribe_voice is always available, and listings only pick up already-cached transcripts. Enable prefetching or pick an engine explicitly:

env
TELEGRAM_TRANSCRIBE=on-demand       # off / on-demand (default) / auto
TELEGRAM_TRANSCRIBE_ENGINE=groq     # groq (default) or telegram
GROQ_API_KEY=your_groq_api_key_here # required whenever engine=groq is used

engine=groq requires GROQ_API_KEY; engine=telegram requires Telegram Premium on the account. TELEGRAM_TRANSCRIBE_MAX_VOICES (default 5) and TELEGRAM_TRANSCRIBE_MAX_SECONDS (default 300) bound how much auto mode prefetches per listing call; TELEGRAM_TRANSCRIPT_CACHE_DIR (default data/transcripts) sets where the SQLite cache is written; TELEGRAM_TRANSCRIBE_GROQ_MAX_MB (default 25) is the largest recording the groq engine will upload.

Telegram rate limits (FloodWaitError) are surfaced transparently to MCP clients and LLM agents with the exact wait duration and an explicit instruction not to retry immediately.

Use TELEGRAM_FLOOD_SLEEP_THRESHOLD to configure Telethon's internal silent-sleep threshold (default: 60 seconds). Set to 0 to disable silent sleeping and let the agent manage all backoff pacing:

env
TELEGRAM_FLOOD_SLEEP_THRESHOLD=60   # Max seconds Telethon sleeps silently on FloodWait (default 60)

Run the server locally:

bash
uv run main.py

MCP Client Configuration

For Claude Desktop or Cursor, point the MCP server at a cloned checkout of this project:

json
{
  "mcpServers": {
    "telegram-mcp": {
      "command": "uv",
      "args": [
        "--directory",
        "/full/path/to/telegram-mcp",
        "run",
        "main.py"
      ],
      "env": {
        "TELEGRAM_API_ID": "your_api_id_here",
        "TELEGRAM_API_HASH": "your_api_hash_here",
        "TELEGRAM_SESSION_STRING": "your_session_string_here"
      }
    }
  }
}

To expose only read-only tools in Claude Desktop or Cursor, add this to the server env block:

json
"TELEGRAM_EXPOSED_TOOLS": "read-only"

Or keep read-only as the baseline and allow a few write tools on top:

json
"TELEGRAM_EXPOSED_TOOLS": "read-only+send_message,reply_to_message"

Alternatively, install this repository directly from GitHub into a virtual environment using a specific release tag or commit:

bash
python -m venv .venv
. .venv/bin/activate
pip install "git+https://github.com/chigwell/telegram-mcp.git@<tag-or-commit>"

Then configure your MCP client to run the installed console script:

json
{
  "mcpServers": {
    "telegram-mcp": {
      "command": "/full/path/to/.venv/bin/telegram-mcp",
      "env": {
        "TELEGRAM_API_ID": "your_api_id_here",
        "TELEGRAM_API_HASH": "your_api_hash_here",
        "TELEGRAM_SESSION_STRING": "your_session_string_here"
      }
    }
  }
}

Generate a session string without cloning the repo by sourcing this repository from GitHub explicitly:

bash
uvx --from "git+https://github.com/chigwell/telegram-mcp.git@<pinned-release-tag-or-commit>" telegram-mcp-generate-session

Transports

The server speaks three MCP transports, selected with MCP_TRANSPORT:

ValueTransportUse case
stdiostdio (default)One dedicated server process per MCP client
httpstreamable HTTPOne shared server for many clients (Claude Code, Codex, Cursor)
sseSSE (legacy HTTP)Clients that only support the deprecated SSE transport

For http and sse, the server binds MCP_HOST:MCP_PORT (default 127.0.0.1:8765); the streamable HTTP endpoint is /mcp, the SSE endpoint is /sse.

If the server is reachable via a domain (e.g. behind a reverse proxy) rather than only 127.0.0.1/localhost, set MCP_ALLOWED_HOSTS (and optionally MCP_ALLOWED_ORIGINS) to enable DNS-rebinding protection and allow that Host header, e.g. MCP_ALLOWED_HOSTS=mcp.example.com. Comma-separated; supports a :* suffix to allow any port. Left unset, DNS-rebinding protection stays off (the historical default).

Prefer http when more than one MCP client (or many coding-agent sessions) will use the server: a single long-lived process holds one Telegram connection, instead of every client spawning its own Telethon session — Telegram throttles and may flag accounts that open many parallel sessions.

Every tool call also has a server-side ceiling of 55 seconds, configured with TELEGRAM_TOOL_TIMEOUT_SECONDS. A timed-out Telegram request returns an explicit MCP error instead of leaving the client waiting indefinitely. Set the value to 0 only for a deliberately unbounded operator session.

Register the shared server with clients:

bash
# Claude Code
claude mcp add --transport http telegram http://127.0.0.1:8765/mcp

# Codex
codex mcp add telegram --url http://127.0.0.1:8765/mcp

For stdio-only clients, bridge with mcp-remote:

json
{
  "mcpServers": {
    "telegram-mcp": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://127.0.0.1:8765/mcp"]
    }
  }
}

Multi-Account Setup

Use suffixed session variables to configure multiple Telegram accounts:

env
TELEGRAM_API_ID=your_api_id_here
TELEGRAM_API_HASH=your_api_hash_here
TELEGRAM_SESSION_STRING_WORK=session_string_for_work
TELEGRAM_SESSION_STRING_PERSONAL=session_string_for_personal

Labels are lowercased and become the account parameter value in tools.

  • In single-account mode, account is optional.
  • In multi-account mode, write tools require account.
  • Read-only tools fan out to all accounts when account is omitted.

Example prompts:

  • "List my accounts"
  • "Show unread messages from all accounts"

Session pool (one account, several concurrent clients)

To run several MCP clients against the same Telegram account at once (for example the desktop app and a terminal CLI), give each client its own authorized session. Telegram forbids one session (auth key) being used from two IPs simultaneously, so on a VPN or dual-stack host two local clients can collide with AuthKeyDuplicatedError. List several interchangeable session strings in TELEGRAM_SESSION_STRINGS (separated by whitespace, comma or semicolon); each process claims a free one via an advisory file lock, so clients deterministically pick distinct sessions:

env
TELEGRAM_SESSION_STRINGS=<session A> <session B> <session C>

Generate extra sessions with uv run session_string_generator.py. The pool takes precedence over TELEGRAM_SESSION_STRING for the default account. As an extra safety net, a transient AuthKeyDuplicatedError at connect time (e.g. during a VPN reconnect) is retried with backoff before the server gives up.

Size the pool to the number of clients you actually run concurrently. If every slot is already claimed, the server refuses to start with an explicit error rather than reusing a session another client holds — reuse would make Telegram permanently invalidate that session for both clients.

  • "Send this from my work account to @example"

Sharing one session from one host

Before connecting, every server process takes a per-session lock (see the AuthKeyDuplicatedError entry under Troubleshooting). By default it is exclusive: a second instance for the same session waits briefly for the first to exit and otherwise refuses to start. That lock can only see processes on the same host, and Telegram's rule is about IPs, not processes: instances on one host only collide when they reach Telegram from different IPs (dual-stack or split-tunnel VPN hosts). When they all share one egress IP — a laptop running several MCP clients — you can let them share the session instead of pooling:

env
TELEGRAM_SESSION_LOCK=shared

Shared instances coexist with each other but never overlap an exclusive one (except on Windows, where a shared instance takes no lock). If you are not sure every client egresses from the same IP, use the pool.

Device Identity

These optional variables control how the client appears in Telegram under Settings > Devices (the active-sessions list):

env
TELEGRAM_DEVICE_MODEL=Telegram MCP
TELEGRAM_SYSTEM_VERSION=1.0
TELEGRAM_APP_VERSION=1.0

If left unset, Telethon falls back to the host platform (for example arm64). Because these values are re-sent on every connection, a long-running server would otherwise overwrite the name chosen during login on each reconnect, so set them to keep a stable, recognisable device name. The same variables are read both by the session string generator (at login) and by the server (on every connect), so set them in the same place as your other credentials.

Proxy Support

Route Telegram traffic through a proxy by setting the TELEGRAM_PROXY_* environment variables. Supported types are socks5, socks4, http, and mtproxy.

SOCKS and HTTP proxies require the optional python-socks package:

bash
uv sync --extra proxy
# or
pip install python-socks

Single-account configuration:

env
TELEGRAM_PROXY_TYPE=socks5
TELEGRAM_PROXY_HOST=127.0.0.1
TELEGRAM_PROXY_PORT=1080
TELEGRAM_PROXY_USERNAME=optional_user
TELEGRAM_PROXY_PASSWORD=optional_pass
TELEGRAM_PROXY_RDNS=true

MTProxy:

env
TELEGRAM_PROXY_TYPE=mtproxy
TELEGRAM_PROXY_HOST=mtproxy.example
TELEGRAM_PROXY_PORT=443
TELEGRAM_PROXY_SECRET=ee0123456789abcdef...

Per-account overrides use the same _<LABEL> suffix as session variables and take precedence over the unsuffixed defaults:

env
TELEGRAM_PROXY_TYPE=socks5
TELEGRAM_PROXY_HOST=127.0.0.1
TELEGRAM_PROXY_PORT=1080

TELEGRAM_PROXY_TYPE_WORK=http
TELEGRAM_PROXY_HOST_WORK=proxy.work.example
TELEGRAM_PROXY_PORT_WORK=3128

Misconfigured proxy settings (unknown type, missing host/port, invalid port, missing MTProxy secret, or a missing python-socks package) cause the server to fail fast at startup with a clear error message instead of silently bypassing the proxy.

File Path Security

File-path tools are disabled until allowed roots are configured. This affects tools such as send_file, download_media, upload_file, send_voice, send_sticker, set_profile_photo, and edit_chat_photo.

Allowed roots can come from:

  • Server CLI arguments, used as a fallback.
  • MCP client Roots, when supported by the client.

Security behavior:

  • Client MCP Roots replace server CLI roots when available.
  • Some clients (notably Cursor) return workspace roots as bare absolute paths instead of file:// URIs. That breaks MCP SDK validation of list_roots; the server recovers those absolute paths from the validation error so file-path tools keep working.
  • Empty client Roots are treated as deny-all by default. Some clients implement the Roots capability but advertise an empty list, which disables file tools even when server CLI roots are configured. Set TELEGRAM_ALLOW_SERVER_ROOTS_FALLBACK=1 to fall back to the server CLI roots in that case (opt-in; the default stays deny-all). The same opt-in also applies when list_roots fails unexpectedly and no client paths could be recovered.
  • Paths are resolved through real paths and must stay inside an allowed root.
  • Traversal, wildcard-like, shell-like, and null-byte path patterns are rejected.
  • Relative paths resolve under the first allowed root.
  • Downloads default to <first_root>/downloads/.
  • Size and extension limits are enforced for sensitive media tools. send_file and upload_file have no extension limit by default; see TELEGRAM_FILE_EXTENSIONS above to add one.

Run with allowed roots:

bash
uv run main.py /data/telegram /tmp/telegram-mcp

From an MCP client configuration, pass the same roots after main.py:

json
{
  "mcpServers": {
    "telegram-mcp": {
      "command": "uv",
      "args": [
        "--directory",
        "/full/path/to/telegram-mcp",
        "run",
        "main.py",
        "/data/telegram",
        "/tmp/telegram-mcp"
      ],
      "env": {
        "TELEGRAM_API_ID": "your_api_id_here",
        "TELEGRAM_API_HASH": "your_api_hash_here",
        "TELEGRAM_SESSION_STRING": "your_session_string_here"
      }
    }
  }
}

Chat Access Privacy (Allowlist)

To restrict AI agents or MCP clients to specific chats only (preventing access to all private or corporate conversations), configure TELEGRAM_ALLOWED_CHAT_IDS:

env
# Comma-separated list of allowed chat IDs, supergroup IDs, or usernames
TELEGRAM_ALLOWED_CHAT_IDS=12345678,-100123456789,@allowed_channel

When TELEGRAM_ALLOWED_CHAT_IDS is set:

  • list_chats / get_chats: Only chats matching the allowlist are returned; all other conversations remain completely invisible to the agent.
  • Messaging & Chat Tools (send_message, get_messages, get_chat, create_poll, etc.): Any attempt to interact with a chat outside the allowlist is rejected with a structured error message: Access to chat '<chat_id>' is restricted by privacy policy (TELEGRAM_ALLOWED_CHAT_IDS).
  • Search & Drafts (search_global, get_drafts): Global searches and draft listings omit unallowed conversations.
  • Incoming Events: Notifications and debounce feeds only process events from allowed chats.

If TELEGRAM_ALLOWED_CHAT_IDS is unset or empty, the server operates in unrestricted mode (default), preserving 100% backward compatibility.

Docker

Build the image:

bash
docker build -t telegram-mcp:latest .

Shared server (recommended)

Run one long-lived container serving streamable HTTP, and point every MCP client at it (see Transports for client registration):

bash
docker run -d --name telegram-mcp --restart unless-stopped \
  --env-file .env \
  -e MCP_TRANSPORT=http \
  -e MCP_HOST=0.0.0.0 \
  -p 127.0.0.1:8765:8765 \
  telegram-mcp:latest

MCP_HOST=0.0.0.0 binds inside the container so the published port works; -p 127.0.0.1:8765:8765 keeps the server reachable only from the local machine — the endpoint is unauthenticated, so never publish it on a public interface.

The bundled Compose file runs the same setup:

bash
docker compose up --build -d

It also mounts ./transcript_cache into the container at /app/data/transcripts so the voice-transcription SQLite cache (see Voice transcription) survives a rebuild instead of living in the container's writable layer. Create it once, owned by the container's appuser (uid 1000), before starting:

bash
mkdir -p ./transcript_cache && chown 1000:1000 ./transcript_cache

One container per client (stdio)

Alternatively, an MCP client can spawn a dedicated container itself:

json
{
  "mcpServers": {
    "telegram-mcp": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "--env-file", "/full/path/to/.env", "telegram-mcp:latest"]
    }
  }
}

This is fine for a single client, but with several clients (or coding agents that spawn subagent sessions) each one starts its own container and its own Telegram session, which Telegram throttles; a client that exits uncleanly can also leave its container running. Prefer the shared server above in those setups.

For multiple accounts, pass variables such as TELEGRAM_SESSION_STRING_WORK and TELEGRAM_SESSION_STRING_PERSONAL.

Development

The implementation is split into a small compatibility entrypoint and modular package code:

text
main.py                    # historical entrypoint and compatibility exports
telegram_mcp/runtime.py    # shared MCP setup, account routing, validation, file safety
telegram_mcp/runner.py     # application startup
telegram_mcp/tools/        # tool modules grouped by domain
sanitize.py                # output sanitization helpers
tests/                     # pytest suite

Run tests:

bash
uv run pytest

Run tests with coverage:

bash
uv run pytest --cov --cov-report=term-missing --cov-report=xml

Coverage is configured in pyproject.toml with an 80% minimum gate for deterministic unit-testable core modules. GitHub Actions runs the same coverage command and uploads coverage.xml.

Run formatting checks:

bash
uv run black --check .
uv run flake8 .

Security Notes

  • Never commit .env, session strings, or .session files.
  • A Telegram session string grants access to the account it belongs to.
  • The telegram-mcp package name on PyPI is not controlled by this project. Avoid PyPI-based telegram-mcp install commands unless ownership changes and the package is verified.
  • This repository includes a best-effort startup guard that refuses installed telegram-mcp distributions without a source checkout or direct git/file install record. That guard cannot run when the unrelated PyPI package itself is launched, so use clone-based or explicit git installs.
  • Prefer session strings over file sessions when running multiple server instances.
  • By default, Telegram API calls go directly from your machine/container to Telegram. If TELEGRAM_PROXY_* is configured, Telegram traffic is routed through the configured SOCKS/HTTP/MTProxy proxy instead.
  • User-generated Telegram content is sanitized before being returned to MCP clients.

Prompt Injection Protection

Telegram messages, display names, chat titles, and button labels are untrusted content. The server mitigates prompt-injection risk with:

  • Structured JSON output for user-controlled data where practical.
  • sanitize_user_content(), sanitize_name(), and sanitize_dict() for control-character stripping, invisible-character stripping, and length limits.
  • MCP content annotations marking returned content as user audience data.
  • Tool descriptions that warn clients not to treat returned Telegram fields as model instructions.
  • No brittle keyword-based filtering.

Troubleshooting

  • No Telegram session configured: set TELEGRAM_SESSION_STRING, TELEGRAM_SESSION_NAME, or suffixed multi-account variants.
  • Session is not authorized: run uv run session_string_generator.py --qr outside the MCP server when you can scan from an existing Telegram app, or uv run session_string_generator.py --phone when you need phone-code login. Then set TELEGRAM_SESSION_STRING in .env. The MCP server does not perform interactive phone-code login over stdio.
  • Invalid API credentials: verify TELEGRAM_API_ID and TELEGRAM_API_HASH at my.telegram.org/apps.
  • Database is locked: prefer string sessions, or make sure no other process is using the same file session.
  • AuthKeyDuplicatedError / "Another telegram-mcp process is already connected with this session": two processes tried to connect the same Telegram session at once (e.g. an MCP client restarted the connector before the old process exited), which Telegram rejects and can invalidate the session for both. The server now takes an exclusive lock per session before connecting; a second concurrent launch waits briefly (default 20s, override with TELEGRAM_LOCK_GRACE_SECONDS) for the first to release it and otherwise exits without ever calling connect(), instead of racing into a duplicate connection. Retry once only one instance is running — the refusal names the PID holding the lock. If several instances on this host are meant to share one session (all reaching Telegram from the same IP), set TELEGRAM_SESSION_LOCK=shared; see Sharing one session from one host.
  • File tools are disabled: pass allowed roots or configure MCP Roots in your client.
  • Path rejected: ensure the path is inside an allowed root and does not use traversal or wildcard patterns.
  • Auth errors after password changes: regenerate your session string.
  • Bot-only tool rejected: regular user accounts cannot manage bot command settings.
  • Need details: check your MCP client logs, terminal output, and mcp_errors.log.

Contributing

  1. Fork and clone the repository.
  2. Install dependencies and git hooks:
    • uv sync
    • uv run pre-commit install --hook-type pre-commit --hook-type pre-push
  3. Create a focused branch.
  4. Add or update tests when behavior changes.
  5. Run checks locally:
    • uv run pre-commit run --all-files
    • uv run pre-commit run --hook-stage pre-push --all-files
  6. Open a pull request with a concise description.

License

This project is licensed under the Apache 2.0 License.

Acknowledgements

Maintained by @chigwell and @l1v0n1. PRs welcome.

Star History

Star History Chart

Contributors

Installation

TypingMind
{
  "mcpServers": {
    "telegram-mcp": {
      "command": "docker",
      "args": [
        "run",
        "-it",
        "--rm",
        "-e",
        "TELEGRAM_API_ID={{config.TELEGRAM_API_ID}}",
        "-e",
        "TELEGRAM_API_HASH={{config.TELEGRAM_API_HASH}}",
        "-e",
        "TELEGRAM_SESSION_STRING={{config.TELEGRAM_SESSION_STRING}}",
        "telegram-mcp:latest"
      ],
      "env": {
        "TELEGRAM_API_ID": "<TELEGRAM_API_ID>",
        "TELEGRAM_API_HASH": "<TELEGRAM_API_HASH>",
        "TELEGRAM_SESSION_STRING": "<TELEGRAM_SESSION_STRING>"
      }
    }
  }
}

Use Telegram MCP Server MCP with multiple AI models

TypingMind connects MCP tools at the workspace level, so once Telegram MCP Server is connected, you can use it with different AI models in TypingMind instead of setting it up separately for each model. This MCP runs locally through the TypingMind MCP connector on your device.

Setup guide to use the local connector

Use this when the MCP server needs access to local files, apps, or private resources on your computer.

1

Open the MCP settings

In TypingMind, go to Settings, Advanced Settings, then Model Context Protocol and choose Setup Connector.

  1. Open TypingMind in your browser.
  2. Click the Settings icon.
  3. Go to Advanced Settings.
  4. Open the Model Context Protocol section.
  5. Click Setup Connector and choose This Device.
TypingMind MCP connector setup screen with This Device selected
2

Run the connector command

Choose This Device, copy the command from TypingMind, and run it in Terminal. Keep the process running while you use MCP.

  1. Copy the setup command shown by TypingMind.
  2. Open Terminal on macOS or Windows Terminal on Windows.
  3. Paste and run the command.
  4. Approve the package install if Terminal asks you to proceed.
  5. Keep the Terminal window running while using MCP tools.
3

Add Telegram MCP Server as a server

When the connector status is Ready, click Edit Servers and paste the MCP server configuration.

  1. Wait until the connector status shows Ready.
  2. Click Edit Servers.
  3. Paste the Telegram MCP Server MCP server configuration.
  4. Save the server list.
  5. Refresh if you want to confirm the connector is still ready.
TypingMind MCP settings showing active server and Edit Servers button
{
  "mcpServers": {
    "telegram-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "<mcp-server-package>"
      ]
    }
  }
}
4

Use it across models

Save the server list, open Plugins, enable the Telegram MCP Server MCP tools, then select any supported AI model in TypingMind and use the tools in chat or assign them to an AI agent.

  1. Open the Plugins page in TypingMind.
  2. Enable the Telegram MCP Server MCP tools.
  3. Start a chat and choose the AI model you want to use.
  4. Use the MCP tools in chat or assign them to an AI agent.
  5. Switch to another AI model whenever needed without reconnecting MCP.
TypingMind chat using enabled MCP tools with a selected AI model
Can you use Telegram MCP Server to help me with this task?
Telegram MCP Server
Sure. I read it.
Here is what I found using Telegram MCP Server.

Frequently asked questions

What is the Telegram MCP Server MCP server used for?

Telegram MCP Server is an MCP server that lets compatible AI clients connect to external tools and context. In TypingMind, you can add this MCP server once and make its tools available in your AI workspace.

Can I use Telegram MCP Server MCP with multiple AI models in TypingMind?

Yes. TypingMind connects MCP tools at the workspace level, so you can use Telegram MCP Server with different AI models such as Claude, ChatGPT, Gemini, or other models you have configured in TypingMind without setting up the MCP server separately for each model.

Why use Telegram MCP Server MCP with TypingMind?

TypingMind is one of the best frontends for LLM chat because it brings multiple AI models, prompts, plugins, AI agents, API keys, and MCP tools into one workspace. With Telegram MCP Server connected, you can use its MCP tools across your preferred models while keeping your chat workflow organized in TypingMind.

How do I connect Telegram MCP Server MCP to TypingMind?

Telegram MCP Server runs through the TypingMind local MCP connector. This is best when the MCP server needs access to local files, desktop apps, command-line tools, or private resources on your computer.

What tools does Telegram MCP Server MCP provide in TypingMind?

Telegram MCP Server exposes MCP capabilities that can be enabled from the TypingMind Plugins page and used in chat or assigned to AI agents.

Do I need to share my API keys with TypingMind to use Telegram MCP Server MCP?

No. TypingMind is local-first and lets you keep your model providers, API keys, prompts, and MCP configuration under your control. If Telegram MCP Server requires authentication, add the required headers, OAuth settings, or local configuration for that MCP server when you create the connection.

Related MCP Servers

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇