Skip to main content
Glama

TeleMCP

Telegram intelligence and action layer for the Model Context Protocol.

TeleMCP turns Telegram conversations, groups, channels, files and links into a searchable, auditable knowledge layer that an AI agent can query — and, with a human in the loop, act on.

  • Telegram Bot API (webhook) and MTProto personal mode, cleanly separated

  • 40 MCP tools, 7 resources, 4 prompts

  • Hybrid search: PostgreSQL full text + trigram + pgvector semantic search

  • Inbox AI: remember a message, get it back by meaning months later

  • Human approval for everything that writes; reinforced approval for deletes

  • Full audit trail, per-chat policy, deny by default, multi-tenant isolation

  • Fly.io for compute, Neon PostgreSQL for durable state

How it behaves

Three rules explain most of the design:

  1. Reading is free, writing is not. Every WRITE tool returns APPROVAL_REQUIRED on its first call, with an approval id. A human approves; the agent calls again with the same arguments plus approval_id. Changing a single character invalidates the approval. Approvals are single-use and expire in minutes.

  2. Chats are deny-by-default. Connecting an account indexes nothing. A chat is read only after it is explicitly allowlisted.

  3. Message content is data, never instruction. Retrieved text is fenced before it reaches a model and flagged when it looks like prompt injection. The policy engine never reads content at all, which is what makes the defence real rather than advisory.

Related MCP server: NetGram

Quick start

git clone <your-fork> && cd telemcp
cp .env.example .env          # then fill it in — see "Configuration"
uv sync                       # or: pip install -e ".[dev,files]"

Bring up PostgreSQL with the required extensions:

docker run -d --name telemcp-db \
  -e POSTGRES_USER=telemcp -e POSTGRES_PASSWORD=telemcp -e POSTGRES_DB=telemcp \
  -p 5432:5432 pgvector/pgvector:pg16

Apply the schema and start the two processes:

python scripts/migrate.py upgrade
uvicorn app.main:app --host 0.0.0.0 --port 8080   # web
python -m app.jobs.worker                          # worker

Check it is alive:

curl localhost:8080/healthz    # {"status":"ok"}
curl localhost:8080/readyz     # database, extensions, migration revision

Connect a Telegram bot

  1. Create the bot with @BotFather and copy its token into TELEGRAM_BOT_TOKEN.

  2. Generate a webhook secret and set TELEGRAM_WEBHOOK_SECRET plus TELEGRAM_WEBHOOK_BASE_URL (your public HTTPS base URL).

  3. Register the webhook:

python scripts/create_bot_webhook.py
  1. Send /start to the bot. It replies with your Telegram user id; put it in TELEGRAM_OWNER_IDS and restart. Ownership is declared, never inferred — otherwise anyone in a group could change what gets indexed.

  2. Add the bot to a chat and send /allowchat. Nothing is indexed before that. /privacy shows exactly what is stored.

Connect the MCP endpoint

The endpoint is https://<host>/mcp (Streamable HTTP) and accepts two kinds of credential, because clients differ in what they can send.

A shared bearer token, for clients that support custom headers — Claude Code among them:

claude mcp add --transport http telemcp https://telemcp.fly.dev/mcp \
  --header "Authorization: Bearer $MCP_AUTH_SECRET"

OAuth 2.1, for clients that only speak OAuth — ChatGPT's custom connectors among them. Add the server URL in the client; it discovers the authorization server, registers itself, and sends you to a consent page. Approve it there with your MCP_AUTH_SECRET and the client receives a token of its own.

Both paths land on the same policy engine: an OAuth client is constrained by the scopes it was granted, and every write still needs human approval.

Personal mode (optional)

Personal mode connects your Telegram account over MTProto so history can be backfilled. It never bypasses Telegram's permissions — it sees only what your account can already see, and only in allowlisted chats.

python scripts/create_personal_session.py   # prompts for phone, code, 2FA

The session string is sealed with AES-256-GCM under SESSION_ENCRYPTION_KEY before it touches the database, is never logged, and can be destroyed at any time with the telegram_revoke_personal_session tool.

The approval flow, concretely

agent: telegram_send_message(chat_id, text)
   ↓
{"ok": false, "error": "APPROVAL_REQUIRED",
 "approval": {"id": "…", "summary": "send message · chat -100… · “…”"}}
   ↓
human: POST /approvals/{id}/approve
   ↓
agent: telegram_send_message(chat_id, text, approval_id="…")
   ↓
{"ok": true, "data": {"message_id": "…", "sent_at": "…"}}

GET /approvals lists what is waiting. Every step is audited.

Tool catalogue

Group

Tools

Messages

send_message, reply_message, get_message, get_recent_messages, search_messages, search_semantic, search, get_context, forward_message, delete_message

Chats

get_chat, list_known_chats, get_group_messages, get_channel_posts, get_thread

Files

download_file, send_file, send_photo, list_files, find_documents

Updates / Polls

get_updates, create_poll

Intelligence

summarize_chat, summarize_channel, summarize_thread, find_links, find_mentions, find_decisions, extract_tasks, extract_people, extract_topics

Inbox AI

remember_message, save_item, list_saved_items, search_saved_items, forget_item

Admin

sync_chat, reindex_chat, get_sync_status, revoke_personal_session

All names are prefixed telegram_. Approval requirements are listed in SECURITY.md.

Resources: telegram://chat/{id}, …/recent, telegram://message/{chat}/{id}, telegram://thread/{chat}/{id}, telegram://channel/{id}, telegram://file/{id}/metadata, telegram://saved/{id}.

Prompts: telegram_chat_digest, telegram_decision_review, telegram_action_items, telegram_research_digest.

Configuration

Everything is environment-driven; .env.example documents every key. The ones that matter most:

Variable

Meaning

DATABASE_URL

Neon pooled connection, used by web and worker

DATABASE_URL_DIRECT

Neon direct connection, used by migrations

MCP_AUTH_SECRET

Bearer token for /mcp and /approvals (≥32 chars)

TELEGRAM_WEBHOOK_SECRET

Verified on every webhook call

SESSION_ENCRYPTION_KEY

base64 32 bytes; seals MTProto sessions

CHAT_DEFAULT_POLICY

deny (keep it)

MCP_ALLOWED_HOSTS

Hostnames the MCP endpoint answers to; required in production

OAUTH_ISSUER

Public HTTPS base URL, used by OAuth discovery and consent

TELEGRAM_OWNER_IDS

Telegram ids allowed to run /allowchat

EMBEDDING_PROVIDER

hash (local, no key) or openai

LLM_PROVIDER

heuristic (local, no key) or openai

Running without any API keys

EMBEDDING_PROVIDER=hash and LLM_PROVIDER=heuristic are the defaults, and they are not stubs: hashed embeddings give a real, deterministic vector space, and the intelligence tools fall back to deterministic extraction. Every tool works end to end with no external AI provider. Each summary reports which path produced it in its engine field. Switch to openai when you want model-quality semantics.

Deploying to Fly.io

fly launch --no-deploy
fly secrets set \
  DATABASE_URL="…" DATABASE_URL_DIRECT="…" \
  MCP_AUTH_SECRET="…" TELEGRAM_BOT_TOKEN="…" \
  TELEGRAM_WEBHOOK_SECRET="…" SESSION_ENCRYPTION_KEY="…"
fly deploy
fly scale count web=1 worker=1
fly status && fly checks list

fly.toml runs migrations as the release command, so a deploy cannot serve traffic against an unmigrated schema. The worker is only required for embeddings, enrichment and personal mode — but without it, semantic search stays empty.

Neon setup

  1. Create the project and a telemcp database. Pick a region near your Fly region — gru pairs with São Paulo. Leave Neon Auth off: TeleMCP authenticates through its own bearer credential and provisions its own users, so Neon Auth would add unused tables.

  2. Copy both connection strings (pooled and direct).

  3. CREATE EXTENSION vector; CREATE EXTENSION pg_trgm; CREATE EXTENSION pgcrypto;

  4. python scripts/migrate.py upgrade

Verified against PostgreSQL 16. Newer majors work provided pgvector is available; if CREATE EXTENSION vector fails, recreate the project on 17.

Connect the application as a non-superuser role. Superusers bypass row level security, and RLS is what enforces tenant isolation at the database level. See SECURITY.md.

Operating it

python scripts/manage_policies.py list                     # the allowlist
python scripts/manage_policies.py allow -100123456         # index a chat
python scripts/manage_policies.py set -100123456 --write   # permit sending
python scripts/reindex.py                                  # rebuild the index

Bot commands: /start, /help, /status, /privacy, /remember, /search, /recent, /summary, /decisions, /tasks, /saved, and for the owner /allowchat, /denychat, /link, /unlink, /reindex.

/metrics exposes Prometheus counters and histograms: tool calls and errors, messages ingested, search latency, queue depth, approval requests.

Architecture

MCP client ──HTTP──▶ web process ──▶ Neon PostgreSQL ◀── worker process
                     ├ MCP server                       ├ job runner
                     ├ Telegram webhook                 ├ embeddings
                     └ approval API                     └ MTProto

Fly.io runs compute; Neon holds all durable state. The web process is stateless, so it scales and restarts freely.

Every tool call takes exactly one path:

authenticate → resolve principal → tool policy → scope → rate limit
             → chat policy → approval → execute → audit

A tool implementation never re-implements a check, and no tool can skip one.

Development

pip install -e ".[dev,files]"
ruff check .
pytest tests/unit tests/contract tests/security -q      # no services needed

docker run -d --name telemcp-test -p 5433:5432 \
  -e POSTGRES_USER=telemcp -e POSTGRES_PASSWORD=telemcp \
  -e POSTGRES_DB=telemcp_test pgvector/pgvector:pg16
export TEST_DATABASE_URL=postgresql+asyncpg://telemcp:telemcp@localhost:5433/telemcp_test
pytest -q                                                # everything

262 tests: unit, contract (every tool's schema, class and annotations), security (injection, SSRF, webhook spoofing, replay, tenant isolation) and integration against a real PostgreSQL with pgvector.

Project status

V0.1. See STATUS.md for the Definition of Done, item by item, and what is deliberately deferred.

License

Apache-2.0.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    B
    maintenance
    Connect any AI agent to your personal Telegram messages through the official Business API, enabling message history search and draft replies with optional manual approval.
    7
    15
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents to scoped, permissioned access to Telegram chats with per-chat read/write levels and human approval for sending messages.
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to interact with Telegram through 50+ tools for chats, messages, contacts, users, groups, and channels, with per-topic isolation and a reactive inbox for multi-agent use.
    MIT
  • F
    license
    Not graded
    quality
    A
    maintenance
    Enables AI assistants to read Telegram conversations, search messages, retrieve chat context, resolve recipients, and send messages with delivery status.
    1
    -