Skip to content
Conduit

mcp server

telegram-gateway

Self-hosted Telegram REST and MCP gateway with TDLib, OAuth, account isolation and rootless Podman deployment.

JavoxirJava/telegram-gatewaycommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

This project does not publish an install method we can turn into a config. Follow the README instead.

README

synced Oct 26, 2026

Unofficial Telegram Gateway

CI Release License: MIT

Current release: v2.0.2 — OAuth security fixes, chat permissions, image/video-frame inspection and controlled sending. Read the changelog and upgrade notes before updating: existing chats start with no AI access.

A self-hosted Telegram gateway with a permission-controlled REST API and MCP server. Search your synchronized messages, list chats and contacts, and retrieve message attachments from AI clients or your own scripts.

TDLib connects to Telegram. PostgreSQL stores the mirror, MinIO stores media, Redis handles rate limits. Legacy NATS jobs are retained but background sync is disabled. This is an independent project, not affiliated with Telegram or an AI provider.

Try the hosted MCP

You can try the running gateway without installing the project:

Entry pointURL
MCP serverhttps://tg-geteway.javohir-dev.uz/mcp
Telegram sign-inhttps://tg-geteway.javohir-dev.uz/login
Your account, tokens and connected AI clientshttps://tg-geteway.javohir-dev.uz/account
  1. Add the MCP server URL to your AI client's custom MCP/connector settings. See client examples below.
  2. Start the client's OAuth connection. In the browser page it opens, sign in with your own Telegram account and approve the displayed scopes. No personal Telegram API ID/hash is needed for this hosted trial.
  3. Open /account, click Telegramdan yuklash, select a chat, and enable Chatni o‘qish and/or Xabar yuborish. Every chat is denied initially, including chats cached before this upgrade. Loading this list fetches metadata only; it does not read history.
  4. Return to the AI client and enable the connection. Try asking: "Use Telegram Gateway to show my profile and list my first 5 chats." This exercises get_profile and list_chats. Reads refresh requested data; Telegram rate limits or an unready session produce a retryable error.
  5. Manage personal tokens or revoke an AI client's grant from your account page. You can connect the same Telegram account from another AI client; each client has its own grant and sees only the account it was authorized to access.

Opening /mcp directly in a browser without authentication returns 401 unauthorized; that is expected. Use an MCP client's OAuth flow, or a personal bearer token created on your account page.

This is a hosted test instance; availability depends on the maintainer's server. AI/API reads store requested data on that server, whose operator controls the stored data. Read Before connecting an account before signing in. For your own installation, follow the Linux quick start below.

What works

  • Phone/code, two-step verification, email verification and QR login through TDLib.
  • Multi-user sign-in: verified Telegram identities map to separate accounts; returning users recover their existing mirror.
  • On-demand chat metadata, history, contacts, members, search and requested media.
  • REST pagination/search and 12 account-scoped MCP tools, including image/video-frame inspection and permission-controlled sending.
  • OAuth discovery, dynamic client registration, PKCE S256, rotating refresh tokens and per-client revocation; personal API tokens are also supported.
  • Private media download links, account isolation tests, audit chains, checksummed database migrations and restart-safe session storage.
  • Linux deployment with rootless Podman Quadlet and an optional HTTPS tunnel.

Before connecting an account

Automatic synchronization is disabled (TELEGRAM_AUTO_SYNC=false). Starting with TELEGRAM_AUTO_SYNC=true now fails configuration validation. No gateway background history, live-event mirroring, or media downloads run.

Only browser-authenticated account owners can change chat permissions at /account. Read and send permissions are independent and apply to all clients of that account; clients also need the appropriate OAuth/token scope. Bearer tokens cannot edit permissions. Revocation blocks subsequent REST/MCP reads and existing media links. A request already dispatched to Telegram cannot be recalled, and content already returned to an AI cannot be erased by revoking access.

AI chat listing/search returns only approved cached chat metadata and does not query Telegram. Explicit browser Telegramdan yuklash fetches one metadata page; Yana yuklash fetches the next, and browser search finds chats by name. Message reads and searches query only an approved chat on demand, with a 45-second budget. search_messages now requires chat_id; account-wide message search is disabled. Attachments are fetched only when requested. Contacts remain separately controlled by contacts:read. MCP instructions prohibit automatic polling; the gateway cannot independently verify whether a client's individual tool call followed a human prompt.

Local Telegram limits are 60 account operations/minute (burst 15), 20 history/list operations/minute per method (burst 6), and 10 searches/minute per method (burst 3). Attachment limits stay unchanged. Telegram FLOOD_WAIT cooldowns still apply.

Existing cache and legacy queued jobs are retained. Background workers and update journal processing are paused and cannot be enabled through configuration. Telegram connections remain online and TDLib maintains its own session/cache; gateway live events are not mirrored into PostgreSQL in on-demand mode. Storage quotas, automatic retention and self-service account deletion are not implemented.

The operator controls the host and its stored data. Keep .env, session keys, TDLib directories, backups and media private. AI clients receive the tool results you authorize; self-hosting does not make an external AI's processing local. See SECURITY.md for the access boundaries and reporting process.

Using the source does not grant rights to Telegram content. Review the Telegram API terms and content/AI terms, including the content-specific consent conditions. A gateway login does not establish consent from everyone whose messages appear in a chat. This repository is not a claim of approval by Telegram, OpenAI, Anthropic or any directory.

Quick start: your own Linux machine

Requirements: rootless Podman with Quadlet/cgroup v2, user systemd and Python 3. The image builds Go and native TDLib; a host Go installation is only needed for local development/tests. TDLib's first build can take several minutes.

git clone https://github.com/JavoxirJava/telegram-gateway.git
cd telegram-gateway
cp .env.example .env

Create your own application at https://my.telegram.org/apps. Set TELEGRAM_API_ID and TELEGRAM_API_HASH in the local .env, then run:

python3 deploy/configure.py
# Build the pinned MinIO dependency from source; the old registry image is unavailable.
podman build --layers -f Dockerfile.minio \
  -t quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z .
podman build --layers -f Dockerfile.tdlib -t localhost/telegram-gateway-tdlib:d1085f9 .
podman build --layers --build-arg VCS_REF="$(git rev-parse HEAD)" \
  --build-arg BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  -t localhost/telegram-gateway:local .
python3 deploy/install.py

The service listens on http://127.0.0.1:8086. PostgreSQL, Redis, NATS and MinIO have no published host ports. These scripts install one tgw-* stack per Linux user and preserve existing volumes.

  • /version: running release version, Git commit and build timestamp.
  • /health/ready: dependency checks.
  • /login: sign in to your Telegram account; background sync stays off by default.
  • /account: per-chat read/send permissions, personal tokens and AI grants.
  • /manage: operator panel; use GATEWAY_ADMIN_TOKEN from your local .env.
  • /mcp: Streamable HTTP MCP endpoint. A tokenless browser request returns 401 with OAuth discovery information; add this URL to an MCP client.

configure.py generates secrets locally and preserves existing credentials and PUBLIC_URL. No application credentials, admin tokens or Telegram sessions are provided by this repository. Do not share the admin token with AI clients.

Optional: a public HTTPS endpoint

Hosted AI clients need to reach your own server over HTTPS. Configure an origin you control before installing/restarting the service:

python3 deploy/configure.py --public-url https://gateway.example.com
python3 deploy/install.py

Route that hostname to http://127.0.0.1:8086 with your HTTPS reverse proxy. Alternatively, after installing and authenticating cloudflared for your domain:

python3 deploy/tunnel.py

The tunnel helper uses the hostname from .env and installs tgw-tunnel.service. It does not replace an existing DNS record unless --overwrite-dns is supplied. The machine must remain powered on and connected for remote clients to work.

Optional: development Compose stack

After building both images and generating .env, run podman compose up -d with a Compose provider installed. docker-compose.yml uses separate volumes and http://127.0.0.1:18086. Its migration container initializes the schema before starting the API. The Quadlet installation above is the primary deployment path.

Connect an AI client

The examples below use the hosted test endpoint: https://tg-geteway.javohir-dev.uz/mcp. For your own installation, replace it with your configured HTTPS origin followed by /mcp, or http://127.0.0.1:8086/mcp for a local client on the same machine.

Choose OAuth where supported. The client opens the gateway's sign-in page; sign in to your own Telegram account and approve the displayed scopes. Enter Telegram codes/passwords only in that page. Each authorization gets a separate account-bound grant, so revoking one does not revoke the others.

Codex CLI

codex mcp add telegram-gateway --url https://tg-geteway.javohir-dev.uz/mcp
codex mcp login telegram-gateway

Cursor

Example .cursor/mcp.json for the hosted trial:

{
  "mcpServers": {
    "telegram-gateway": {
      "url": "https://tg-geteway.javohir-dev.uz/mcp"
    }
  }
}

Gemini CLI

Add to ~/.gemini/settings.json, then run /mcp auth telegram-gateway:

{
  "mcpServers": {
    "telegram-gateway": {
      "httpUrl": "https://tg-geteway.javohir-dev.uz/mcp"
    }
  }
}

This describes Gemini CLI, not a promise of custom MCP support in the consumer Gemini web app.

Claude and ChatGPT

Where your client/account permits custom remote MCP connections, add https://tg-geteway.javohir-dev.uz/mcp and authenticate with OAuth. Enable the connection in the conversation's tool menu if required. Product features and workspace policies vary; see the current Claude guide and OpenAI connection guide. Private/custom use and public directory approval are separate processes. This project has not been approved or listed in those directories.

API tokens and stdio

Create a personal token in /account for a client that accepts bearer tokens. Personal tokens last 30 days. OAuth access tokens last one hour and refresh tokens rotate with a 30-day lifetime. Revoke a grant in /account to stop that client's gateway access and refresh tokens. Browser sign-out alone does not do so.

Reusing a consumed refresh token closes its entire authorization grant, including successor refresh tokens and access tokens. Clients must serialize refreshes; after an ambiguous refresh failure, reconnect instead of replaying the old token. /oauth/revoke also closes the entire grant, whether given its access token or refresh token. Separate consent grants are unaffected.

Unused dynamic client registrations expire after 24 hours and may be evicted earlier when the 1,000-entry unused pool fills. Register again if an unused client ID becomes invalid. Registrations with pending consent or token records are protected from eviction; consented clients remain registered. Anonymous registrations cannot fill a permanent global quota and block new connections.

Build the stdio bridge with go build -o bin/gateway-mcp ./cmd/mcp-stdio, or use /usr/local/bin/gateway-mcp inside the image. It reads GATEWAY_URL and GATEWAY_TOKEN. Keep token-bearing client configurations out of Git.

Available tools

get_profile, list_chats, search_chats, get_messages, search_messages, list_contacts, search_contacts, list_chat_members, list_message_media, get_media_url, inspect_media, send_message.

Scopes: profile:read, chats:list, chat:read, messages:read, messages:search, messages:send, contacts:read, members:read, media:read. The tool list is filtered by granted scopes. Existing grants must reconnect and consent to messages:send before sending; personal token creation offers an explicit send checkbox. There are no join or Telegram-delete tools.

inspect_media(media_id, second=0) returns native MCP image content: a photo or one video frame at the requested timestamp. Request further timestamps to inspect other parts of a video. It does not transcribe audio or analyze an entire video. Image input is limited to 16 MiB / 40 megapixels; video input to 100 MiB, frame time to 0–86400 seconds, conversion to 20 seconds, and output to a 1280-pixel edge. The container includes FFmpeg; native installations need ffmpeg on PATH.

send_message(chat_id, text, request_id) sends plain text (1–4096 characters). Use a fresh UUID request_id for a new message and the same UUID/text on retries. A durable reservation prevents automatic duplicate dispatch after timeouts or crashes. accepted means TDLib accepted it, not that final delivery was confirmed. An unknown/pending result must be checked in Telegram before starting a new send. Sending requires both messages:send scope and the chat's send permission. The account limit is a burst of 5 sends, refilling at 6 per minute.

REST

All /v1/ endpoints require Authorization: Bearer <token>.

EndpointPurpose
GET /v1/profileConnected profile
GET /v1/chatsPaginated approved chats and read/send flags
GET /v1/chats/search?q=...Search chat titles/usernames
GET /v1/chats/{chatID}/messagesPaginated chat messages
GET /v1/messages/search?q=...&chat_id=...Search one approved chat
GET /v1/contactsPaginated contacts
GET /v1/contacts/search?q=...Search contacts
GET /v1/chats/{chatID}/membersPaginated visible members
GET /v1/chats/{chatID}/mediaPaginated attachment metadata
GET /v1/media/{mediaID}/urlFive-minute download link
GET /v1/media/{mediaID}/inspect?second=0Photo or video frame as base64 JPEG
POST /v1/chats/{chatID}/messagesSend { "text": "...", "request_id": "UUID" }

IDs in these paths are gateway UUIDs returned by the API. Use limit=1..100 and cursor=<next_cursor> for paginated lists. Search returns at most 100 results. Downloads recheck token validity and the active media projection, so expiry, revocation and Telegram deletion invalidate previously issued links.

Operate and stop

systemctl --user status tgw-api tgw-postgres tgw-redis tgw-nats tgw-minio
journalctl --user -u tgw-api -n 100 --no-pager

# Stop the API and all Telegram synchronization; retain stored data.
systemctl --user stop tgw-api.service

# Start it again; default mode only refreshes data when requested.
systemctl --user start tgw-api.service

Quadlets live in ~/.config/containers/systemd/tgw-*. The installer enables linger so user services can start at boot. Persistent volumes are tgw-postgres, tgw-redis, tgw-nats, tgw-minio and tgw-tdlib. Never remove these volumes as an application update step.

For this permission upgrade, rebuild the application image (includes FFmpeg), keep TELEGRAM_AUTO_SYNC=false, and run migration 000016 before starting the updated API. No existing chat is automatically approved; use /account to grant access. Reconnect clients that need the new send scope.

Rebuild the application image and rerun deploy/install.py to update. Migrations run under an owner role; the runtime database role has data permissions but cannot alter the schema or update/delete audit entries.

python3 deploy/backup.py creates a private PostgreSQL/configuration backup under data/backups/. Back up MinIO and TDLib volumes separately. Preserve TELEGRAM_SESSION_KEY with session backups: TDLib databases use keys derived from it and their storage-directory identifiers.

To stop the whole stack without deleting data:

# If the optional tunnel is installed, stop it first.
systemctl --user stop tgw-tunnel.service
systemctl --user stop tgw-api tgw-minio tgw-nats tgw-redis tgw-postgres

Development and tests

Use the Go version in go.mod.

go vet ./...
go test -race -count=1 ./...
python3 deploy/test-infra.py
python3 deploy/test.py
podman stop tgw-test-postgres tgw-test-redis tgw-test-nats tgw-test-minio

Without test configuration, the integration packages skip. deploy/test.py provides a dedicated _test database and MinIO credentials from a private /tmp/telegram-gateway-test/ directory. Test services bind only to loopback ports 15486/16386/14286/19086. They are separate from the permanent stack. Set GO=/path/to/go if necessary; set TDLIB_LIBRARY=/path/to/libtdjson.so to also exercise native encrypted TDLib session initialization. Tests do not authenticate to a real Telegram account.

Integration coverage includes returning-user identity, browser CSRF, cross-user REST/MCP/media isolation, OAuth consent binding, PKCE, token rotation/revocation races, durable updates, tombstones and media file identity across restarts.

python3 deploy/verify-public.py is an optional live smoke test for a configured Quadlet installation with an active operator account. It checks the deployed version/commit, OAuth, MCP tool discovery and denial for a cached closed chat. It does not grant chat permissions or fetch histories, media contents or send messages. It creates temporary grants and revokes them afterward. It requires no credentials in command arguments and prints only statuses/counts. Do not run it against a paused account; the native-ready checks intentionally require an active session.

Current limits

  • One combined API/session/worker instance with local TDLib storage; horizontal sharding and a managed hosted service are not provided.
  • Telegram controls available history and member lists. Secret chats are disabled.
  • Synchronization is eventually consistent and respects persisted FLOOD_WAIT cooldowns. API/client revocation does not erase stored mirror data.
  • Permanent message deletions are hidden by tombstones; deleted contents can remain in private storage and backups. No automatic retention policy is supplied.
  • Account isolation has automated tests; a complete independent security audit and end-to-end tests in every named AI product have not been performed.

Contributing and license

See CONTRIBUTING.md and SECURITY.md. Project-authored code is licensed under MIT. Third-party dependencies, container components and Telegram content retain their own licenses and terms; the project license does not relicense those materials.

↑↓ navigate openesc close