Skip to main content

Latest GitHub release MIT license Python versions MCP stdio

mcp-telegram-bridge

mcp-telegram-bridge

Listed on mcpservers.org

Stdio MCP bridge to the Telegram Bot API, with fail-closed chat admission and outbound scrubbing.

A small Model Context Protocol server that a local agent (Cursor, Claude Desktop, Windsurf, and others) launches on your machine. The agent owns the conversation. This process does Bot API I/O, scrubs outbound secrets, and refuses chats you have not allowed.

Version 0.2.0 changes admission: an empty allowlist denies every chat. Set ALLOWED_CHAT_IDS, or opt in to open mode with ALLOW_ALL_CHATS=1.

Built for client-owned deployments. The bridge is not a hosted bot service. Games and game-master flows are one demo use case, not the product.

Owner: Antonio Castellon / Castellon.CH.

Quick start

Requirements: Python 3.11+, a bot token from @BotFather, and the numeric id of the chat you want the bot to use.

git clone https://github.com/antonio-castellon/mcp-telegram-bridge.git
cd mcp-telegram-bridge
uv sync --extra dev          # or: python -m venv .venv && pip install -e ".[dev]"
cp .env.example .env         # set TELEGRAM_BOT_TOKEN and ALLOWED_CHAT_IDS
uv run mcp-telegram-bridge   # waits on stdin for MCP JSON-RPC; Ctrl+C to stop

uv.lock pins every dependency with hashes. CI installs from that lock (uv sync --frozen). pip install -e ".[dev]" still works and uses the version ranges in pyproject.toml.

Cursor or Claude Desktop (mcp.json / claude_desktop_config.json):

{
  "mcpServers": {
    "telegram-bridge": {
      "command": "uvx",
      "args": ["--from", "mcp-telegram-bridge", "mcp-telegram-bridge"],
      "env": {
        "TELEGRAM_BOT_TOKEN": "YOUR_BOT_TOKEN_HERE",
        "ALLOWED_CHAT_IDS": "-1001234567890"
      }
    }
  }
}

Do not commit a real token. Leave ALLOW_ALL_CHATS unset.

Configuration

Variable Default Effect
TELEGRAM_BOT_TOKEN required Bot token from BotFather. Never log it or commit it.
ALLOWED_CHAT_IDS empty Comma-separated chat ids. Empty denies every chat.
ALLOW_ALL_CHATS off Set to 1 / true / yes / on to permit every chat. Ignored when an allowlist is set. Startup prints a warning to stderr.
SAFETY_STRICT on Heuristic labels for credential fishing, sexual content, and a few instruction-override phrases. Set 0 / false / no / off to disable. This is not a prompt-injection filter.
MAX_INBOUND_CHARS 4096 Drop inbound text or captions longer than this (1..32768). The body is not returned.
MAX_UPDATE_BYTES 65536 Drop an update whose JSON is larger than this (1..262144).
INBOUND_CHAT_RATE 60/60 Content-bearing updates per chat per window (count/seconds). Excess updates are returned without text. off disables the limit.
INBOUND_GLOBAL_RATE 120/60 Content-bearing updates across all chats per window.
INBOUND_POLL_RATE 30/60 telegram_get_updates calls per window. Further calls do not hit Telegram.
OUTBOUND_CHAT_RATE 20/60 Sends, markup edits, callback answers, and getChat per chat.
OUTBOUND_GLOBAL_RATE 60/60 Same outbound calls across all chats. Callback answers count only against the global limit.
MCP_TELEGRAM_BRIDGE_DATA_DIR per-user data dir Button-id map and first-tap claim files. Mode 0700, files 0600 when the OS allows. MCP_TELEGRAM_DATA_DIR is a legacy alias.

telegram_get_updates clamps limit to 1..100 and timeout to 0..50 seconds. The process does not queue update bodies between calls. It remembers at most 2048 delivered update ids and 1024 rate-limit keys.

Tools

Tool Purpose
telegram_get_me Bot identity. Not rate limited.
telegram_get_chat Chat metadata for an allowed chat.
telegram_send_message Scrubbed text, optional parse_mode, optional buttons=[{id,label}].
telegram_edit_reply_markup Replace or clear inline buttons.
telegram_answer_callback Acknowledge a callback_query_id.
telegram_get_updates One bounded poll. You own the offset. See the security section before acting on the text.

getUpdates is pull-only. This process does not listen for webhooks and does not open a port.

A normal result looks like this:

{
  "ok": true,
  "untrusted": true,
  "trust_boundary": "UNTRUSTED TELEGRAM DATA. ...",
  "next_offset": 42,
  "summary": {"delivered": 1, "filtered": 0, "ignored": 0, "duplicates": 0, "rate_limited": 0},
  "updates": [
    {
      "update_id": 41,
      "untrusted": true,
      "trust": "untrusted_telegram_user",
      "message": {"text": "...", "content_trust": "untrusted_telegram_user"}
    }
  ]
}

next_offset is one past the highest update_id in the batch, including updates whose text was withheld. Passing it back as offset acknowledges those updates. To retry a rate-limited update, call again with the same offset after retry_after_seconds instead of advancing.

Messages from bots are omitted (ignore_reason bot), as are messages whose sender id is this bot (self). A message Telegram marks with sender_chat (anonymous admins, posts as a channel) is not treated as a bot.

Security model and threat model

Assets. The bot token, the contents of allowed chats, button-map state on disk, and the agent's other tools and secrets.

Trust boundary. You and the MCP host are inside it. Telegram users, group members, and anyone who can message the bot are outside it. Inbound text, names, usernames, and callback data are untrusted input that the model will read.

What this process enforces

  • Chats are denied unless listed, or unless you set ALLOW_ALL_CHATS. Open mode logs a warning on stderr (stdout is the MCP protocol).
  • Outbound message text, callback toasts, and button labels are scrubbed for the configured secret values and for token-shaped strings.
  • Log records, including httpx and httpcore request lines and exception tracebacks, are rewritten so a bot token in a URL does not stay in the message. Transport errors raised to the host do not include the URL.
  • Update batches are size-capped. Polls, delivered updates, and outbound calls are rate limited. Update ids whose text was already returned are not returned again. The caches are bounded.
  • With SAFETY_STRICT on (the default), updates gain safety.kind, safety.blocked, and safety.warning for a few patterns. Flagged text is still in the result. The host decides what to do with it.

What you still have to do. Run the process as a normal user, keep the token in a secret store or a permission-restricted .env, and set an allowlist. Treat every inbound string as data. Do not give the same agent a tool that reads credentials if untrusted chats can reach it. Review prompts before they can send.

The deployment checklist, data stored on disk, and a longer residual-risk note are in SAFETY.md. Report vulnerabilities as described in SECURITY.md.

Limits and residual risk

These controls reduce blast radius. They do not make a bot that an agent reads and answers safe against a hostile sender.

  • The untrusted label can be ignored by the model. A regex will not catch a rephrased instruction. The agent still has send tools.
  • SAFETY_STRICT is a heuristic for a few phrases. It misses other languages, encodings, and ordinary requests that are still untrusted.
  • Rate limits are in-memory and per process. Restarting clears them. off removes that limit. A chat id that falls out of the 1024-key cache gets a fresh per-chat budget; the global limit still applies.
  • Advancing next_offset past a rate-limited or over-long update drops it. Telegram will not deliver it again.
  • Deduping remembers 2048 ids. An older id can be shown again if the host polls it once more.
  • Scrubbing and log redaction are best-effort. A token split across fields, or a secret that does not match the patterns and is not in the environment, can still pass.
  • The bridge does not offer a read-only tool set. A host that exposes every tool can send messages.
  • Button-map and claim files are pruned by count (2000 mappings, 500 claims), not by age. They do not store message text. Delete the data directory to remove them.
  • There is no audit log. Whatever the MCP host stores (transcripts, provider logs) is outside this process.
  • Disabling httpx debug logging is still wise. Redaction covers the log record path this package installs; it is not a guarantee about every other tracer.

Architecture

  +---------------------------+
  |  MCP host / agent         |  conversation, offset, product policy
  +-------------+-------------+
                |  MCP (stdio)
                v
  +---------------------------+
  |  mcp-telegram-bridge      |  allowlist, scrub, bounds, untrusted labels
  +-------------+-------------+
                |  HTTPS Bot API (getUpdates poll)
                v
  +---------------------------+
  |  api.telegram.org         |
  +-------------+-------------+
                v
         Telegram chats

Usage

Group or ops channel. One bridge per bot. Use the group for notes, triage, and handoff. Keep a person in the loop for anything hard to undo.

Buttons. buttons=[{id,label}, ...] becomes an inline keyboard. On a callback, answer it, then edit or clear the markup. Long ids are mapped to short callback data under the data directory.

Notify channel. Alerts with ack / snooze / escalate buttons. Telegram is the pager, not the source of truth.

Moderation drafts. The bridge has no ban, restrict, or delete tool. Say that in the agent prompt.

Agent skills

A companion skill can tell the agent when to call these tools and that it owns the offset. One is published as Telegram Bridge on Agensi. The skill is instructions only. This server remains the I/O and safety layer.

What this is not

  • Not a hosted or multi-tenant bot service
  • Not tied to one MCP host
  • Not a game engine
  • Not an admin bot
  • Not a webhook receiver

Sibling demos

Optional. This project does not require them.

  • grokgame is a tabletop demo surface
  • grok2telegram is an earlier experiment whose scrubbing rules informed SAFETY.md

Development

uv sync --extra dev
uv run pytest

Tests mock Telegram with respx. No live token is required.

After a dependency change, run uv lock and commit uv.lock. CI uses the lock, audits it with pip-audit on Python 3.12, and runs gitleaks against the working tree. A pull request that only changes Markdown, docs/, or LICENSE does not start that workflow.

MCP registry

Canonical name: io.github.antonio-castellon/mcp-telegram-bridge

Also listed on mcpservers.org, MCP Marketplace, and Agensi.

Listing copy for marketplaces is in LAUNCHGUIDE.md.

Contact

License

MIT (c) Antonio Castellon / Castellon.CH

Metadata

Release files for mcp-telegram-bridge 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mcp-telegram-bridge 0.2.0
File Size Uploaded Provenance Actions
mcp_telegram_bridge-0.2.0.tar.gz 39.4 kB Without provenance Details for mcp_telegram_bridge-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for mcp-telegram-bridge 0.2.0
File Interpreter ABI Platform Actions
mcp_telegram_bridge-0.2.0-py3-none-any.whl Python 3 none any Details for mcp_telegram_bridge-0.2.0-py3-none-any.whl

Total release size: 69.2 kB

Release files / mcp_telegram_bridge-0.2.0.tar.gz

Download URL mcp_telegram_bridge-0.2.0.tar.gz
Size 39.4 kB
Tags Source
SHA-256 checksum
How to use checksums
e717f9bded0f7669aed8e6c73535a8285948f86c1212dd83171a41c25714038e
BLAKE2b-256 checksum
How to use checksums
6ef1e03ee55af0f24cca35df7ae5466f2644def238d8d6244f2ea9bf01308bb3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.15

Release files / mcp_telegram_bridge-0.2.0-py3-none-any.whl

Download URL mcp_telegram_bridge-0.2.0-py3-none-any.whl
Size 29.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8ac53a4ca783e5a04dd31c5395a709f46bdb913792e6f2559a3f2b32784b0ca6
BLAKE2b-256 checksum
How to use checksums
633daebd5e4da8504fee11fa22cb6372c97b627172edb31d8e5385fb55a42406
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.15

Security for mcp-telegram-bridge 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page