telethon-plus: Your Telegram Account as an HTTP API and MCP Server

Telegram has two APIs. The Bot API — neutered, restricted, limited to whatever Telegram decided bots are allowed to do. And MTProto — the actual protocol your phone uses, with full account access, no restrictions. Bots can’t read message history before they joined a chat. Bots can’t see who’s in a private group they’re not in. Bots can’t act as you. MTProto can.

Telethon is a Python MTProto client. telethon-plus wraps it in a Docker container with both an HTTP REST API and an MCP server. POST some JSON, or point an AI agent at /mcp/, and it talks to Telegram as you with full account access. One login. One session string. Never type a code again.

Userbot, Not a Bot

This is a userbot, not a bot. The distinction matters.

A bot is what BotFather gives you. Limited write access, no message history, no group membership without explicit invite, can’t react to most events. The Bot API is fine for sending notifications. It’s useless for anything that requires reading what’s actually happening in your chats.

A userbot is your account, controlled programmatically. Same access you have when you open Telegram on your phone. Read every message in every chat you’re in. Send messages as yourself. Forward, delete, edit. Join groups. Create channels. Act with your real identity, full history, full visibility.

The downside: it’s your account. If you abuse it, your account gets banned. Telegram rate-limits aggressively (FloodWaitError). Don’t spam. Use it for what it’s for: programmatic access to your Telegram, not mass messaging.

Quick Start

services:
  telethon-plus:
    image: psyb0t/telethon-plus
    ports:
      - "8080:8080"
    environment:
      TELETHON_API_ID: "123456"
      TELETHON_API_HASH: "your-api-hash"
      TELETHON_SESSION: "1Aa...long-string-from-login..."
      TELETHON_AUTH_KEY: "your-bearer-token"
    restart: unless-stopped

Get API_ID and API_HASH from my.telegram.org/apps. Get the session string from the login helper.

First-Time Login

Telegram makes you prove you’re human once — phone number, SMS code, optionally 2FA. The container has an interactive login mode that walks you through it and spits out a session string at the end:

docker run --rm -it \
  -e TELETHON_API_ID=123456 \
  -e TELETHON_API_HASH=your-api-hash \
  psyb0t/telethon-plus login

Or if you’ve cloned the repo: cp .env.example .env, fill in TELETHON_API_ID and TELETHON_API_HASH, then make login — it builds the image, runs the flow, and writes TELETHON_SESSION straight into your .env.

The session string is full account access. Whoever has it is you. Don’t commit it. Don’t paste it in Slack. Don’t tattoo it anywhere.

The Tools

Same tool registry, two surfaces. Every tool works as both a REST endpoint and an MCP tool — JSON in, JSON out, schema-validated by pydantic. Send garbage, get a 400 back with exactly what’s wrong.

  • get_me — return the authorized account profile
  • get_entity — resolve a username/ID/link to a full profile
  • send_message — send a text message (Markdown/HTML, reply, silent, link preview)
  • get_messages — read recent messages from a chat (newest first, search, pagination)
  • get_dialogs — list your chats, groups, and channels
  • forward_messages — forward messages between chats
  • delete_messages — delete messages by ID (revoke for everyone or just yourself)
  • edit_message — edit a message you sent
  • mark_read — mark messages in a chat as read
  • send_file — download a file from an HTTPS URL and send it to a chat
  • get_participants — list members of a group or channel
  • create_group — create a new supergroup or broadcast channel
  • delete_chat — delete a supergroup or channel you own
  • join_chat — join a public channel or supergroup
  • leave_chat — leave a channel or supergroup

Chat references accept whatever Telethon accepts: @username, phone numbers, t.me/... links, numeric IDs as strings, supergroup/channel IDs (negative numbers like -1001234567890), or me for your own Saved Messages.

HTTP API

Standard REST. /api/me, /api/messages, /api/dialogs, etc. Every response is {"result": ...} on success.

# who am I
curl http://localhost:8080/api/me \
  -H "Authorization: Bearer $TELETHON_AUTH_KEY"
# send a message with Markdown
curl -X POST http://localhost:8080/api/messages \
  -H "Authorization: Bearer $TELETHON_AUTH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"chat": "@somebody", "text": "**hello** from a container", "parse_mode": "md"}'
# read recent messages with full-text search
curl "http://localhost:8080/api/messages?chat=me&limit=5&search=hello" \
  -H "Authorization: Bearer $TELETHON_AUTH_KEY"
# send a file from an HTTPS URL — never touches your disk
curl -X POST http://localhost:8080/api/files \
  -H "Authorization: Bearer $TELETHON_AUTH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"chat": "@psyb0t", "file_url": "https://example.com/photo.jpg", "caption": "look at this"}'

Telegram auto-detects media type from the file extension. .jpg becomes a photo, .mp4 a video, .mp3 audio. Use force_document: true to override.

Validation is strict — pydantic schemas reject extra fields and bad types with 400 and a detailed error body. Telegram RPC errors (FloodWaitError, ChatWriteForbiddenError) come back as 502 with the error class and message preserved.

Health check at /healthz always public, no auth required. Returns {"status": "ok", "authorized": true}authorized: false means the session is fucked (bad string, revoked, or Telegram unreachable).

MCP Server

Mounted at /mcp/ using the streamable HTTP transport. Every tool from the list above shows up automatically as an MCP tool with the same name and schema. Stateless — every request is independent, no session juggling.

http://your-host:8080/mcp/

Drop it into Claude Desktop, a custom agent, an LLM gateway, anything that speaks MCP over streamable HTTP. Works out of the box.

This is the part that matters. Any function-calling model can now read your Telegram, write to it, manage groups, forward content. The model decides when. The agent acts. Want Claude to summarize the last 50 messages from a chat into a daily digest? Plug in the MCP endpoint, ask. Want a model to forward customer support questions from a public channel to your team chat? Same. Want an agent that monitors a group for mentions and replies on your behalf? Done.

Auth

Set TELETHON_AUTH_KEY to enable Bearer token auth on every endpoint except /healthz. If you don’t set it, the API runs unauthenticated — fine for localhost, suicidal exposed to a network. Always set it.

Authorization: Bearer your-secret-key

The session string is the real secret — losing it means losing your account. Don’t even put it in compose YAML committed to a repo. .env file, gitignored, sourced at runtime.

Configuration

Everything via environment variables.

  • TELETHON_API_ID, TELETHON_API_HASH, TELETHON_SESSION — required, from my.telegram.org and the login helper
  • TELETHON_AUTH_KEY — Bearer token for REST + MCP. Empty = no auth. Set this.
  • TELETHON_HTTP_LISTEN_ADDRESS — default 0.0.0.0:8080
  • TELETHON_REQUEST_TIMEOUT — per-request timeout (default 60 seconds)
  • TELETHON_FLOOD_SLEEP_THRESHOLD — auto-sleep through FloodWait errors below this many seconds (default 60). The safety valve.
  • TELETHON_DEVICE_MODEL, TELETHON_SYSTEM_VERSION, TELETHON_APP_VERSION — what Telegram thinks your client is
  • TELETHON_DOWNLOAD_DIR — scratch space for send_file uploads (default /tmp/telethon-plus)
  • TELETHON_PROXY — optional SOCKS5 proxy (socks5://user:pass@host:port)

The flood sleep threshold is the safety valve — Telegram will rate-limit you when you push too hard. Below the threshold, the client transparently sleeps and retries. Above it, you get a 502 back. Tune it for your use case.

Tests Hit Real Telegram

No mocking. The test suite runs the container against your actual account, with messages getting sent and deleted in a chat you specify (TEST_CHAT — use me for Saved Messages so nobody else sees the noise).

cp .env.example .env
$EDITOR .env  # TELETHON_API_ID, TELETHON_API_HASH, TELETHON_SESSION, TEST_CHAT
make test

Tests cover validation errors, send-edit-fetch-delete roundtrips, dialog listing, entity resolution, public channel reads, group create-and-delete, participant lists, MCP tool discovery and invocation, auth middleware (401 on missing/wrong tokens, healthz always public). If credentials aren’t set, the suite skips cleanly.

Security

The session string equals your account. Treat it like a credit card number that’s also your photo ID. Specifically:

  • Always set TELETHON_AUTH_KEY — never run unauthenticated except on isolated localhost
  • Never commit TELETHON_SESSION to git — .env file, gitignored
  • If you suspect leakage, log into Telegram on your phone and revoke active sessions immediately
  • Don’t expose port 8080 to the public internet — put it behind a reverse proxy with TLS, or a Cloudflare Tunnel
  • Rotate the auth key if anything ever leaks

The container itself is small — Python 3.12, Telethon, FastAPI, MCP SDK. /healthz is the only public endpoint. Everything else needs the bearer token if TELETHON_AUTH_KEY is set.

Plugged Into aigate

This is wired into aigate as one of its MCP servers. Flip TELETHON=1 in the aigate .env, set the credentials, and any function-calling model running through the gateway can now talk to your Telegram. Groq calls a tool, the tool hits Telegram, you get a message. The model decides when.

A free-tier Groq model that searches the web, scrapes a page, generates an image, and sends the result to your Saved Messages — zero tokens paid, one conversation, multiple tool calls. That’s the point.

The Bottom Line

Bot API is fine if you only need to send notifications. Userbots are what you want when you actually need to do things on Telegram — read messages, manage groups, act as yourself programmatically. telethon-plus puts a real MTProto client behind HTTP and MCP, so any tool that speaks JSON or any MCP-aware agent can drive your account.

Go grab it: github.com/psyb0t/docker-telethon-plus

Licensed under WTFPL — do whatever the fuck you want.