Integrations

telethon-plus

Try it

HTTP + MCP control plane over a REAL Telegram MTProto userbot (Telethon) — a full user account, not the Bot API. Send/read/edit/delete/forward messages, browse dialogs, resolve entities/contacts, download + send media, manage chats (create/join/leave/admin), run polls, and forward every incoming message to a webhook (TELETHON_POST_TO_URL). JSON in / JSON out on one port; MCP streamable-HTTP at /mcp/ mirrors the same tools; optional bearer auth. Built-in anti-flood throttling keeps the account under Telegram's limits. Use when the user wants an agent, script, or curl one-liner to drive their own Telegram account over HTTP/MCP — read a chat, DM someone, post to a channel, react/pin, or pipe incoming DMs into an app. Drives a real account acting AS the user with full account access.

What it does

HTTP + MCP control plane over a REAL Telegram MTProto userbot (Telethon) — a full user account, not the Bot API. Send/read/edit/delete/forward messages, browse dialogs, resolve entities/contacts, download + send media, manage chats (create/join/leave/admin), run polls, and forward every incoming message to a webhook (TELETHON_POST_TO_URL). JSON in / JSON out on one port; MCP streamable-HTTP at /mcp/ mirrors the same tools; optional bearer auth. Built-in anti-flood throttling keeps the account under Telegram's limits. Use when the user wants an agent, script, or curl one-liner to drive their own Telegram account over HTTP/MCP — read a chat, DM someone, post to a channel, react/pin, or pipe incoming DMs into an app. Drives a real account acting AS the user with full account access.

The skill document

telethon-plus

Your Telegram account behind an HTTP API. Wraps Telethon — the real MTProto userbot client, a full user account, not the neutered Bot API — behind a JSON HTTP API and a Model Context Protocol endpoint. Same tool registry, two doors: POST JSON at /api/..., or point an MCP-aware agent at /mcp/. Either way it talks to Telegram as you, with full account access.

Capabilities:

  • Messages — send text, send media from a URL, read history, fetch one message, edit, bulk-delete, forward, mark read.
  • Reactions / pins — react with an emoji, remove a reaction, pin / unpin.
  • Dialogs / entities — list your chats, resolve any @username / phone / t.me link / numeric ID to a profile, bulk-resolve.
  • Media — download an attachment (raw bytes over HTTP, or base64 via MCP).
  • Chats — create a supergroup/channel, delete one you own, join public / private-invite, leave, resolve a channel's linked discussion group, list participants.
  • Admin — ban / unban / kick / promote / demote in groups you administer.
  • Polls — create, vote, read results.
  • Incoming events — every new message / edit / delete / chat action streamed over WebSocket and POSTed to your webhook (TELETHON_POST_TO_URL).
  • Health / throttle — liveness, live rate-limit state, flood-risk tier, Prometheus metrics.

For installation, login, and container setup, see references/setup.md.

Security & safety

  • Declared capabilities (see permissions: in the frontmatter): shell (setup-only), network (runtime HTTP), and full Telegram account access. Setup-only shell vs. runtime API is a hard linedocker run / docker compose / curl in references/setup.md are one-time operator commands to stand up the container; every runtime call this skill makes afterward is a plain HTTP request (curl as an HTTP client, or MCP), never a new shell/docker invocation against the host.
  • Outbound HTTP — every API call is a curl/HTTP request to the telethon-plus server ($TELETHON_PLUS_URL), and the server itself makes outbound calls to Telegram's MTProto servers and, when file_url is used, to whatever URL is given (see the SSRF note below).
  • Drives a full-access Telegram user account — read AND write AND admin, not a sandboxed bot. Any call this skill makes acts as the account owner (see "Authorized / responsible use" below for the account-level rules).
  • Destructive / admin operations require explicit user confirmation naming the exact target (chat, message IDs, user) before running — see the per-endpoint warnings in the API tables below (DELETE /api/chats, ban/kick/promote, bulk DELETE /api/messages).
  • Deployment: bind the server to localhost/loopback or put it behind TLS on a reverse proxy, set TELETHON_AUTH_KEY (empty = no auth, wide open), and never expose /mcp/ or /api/ to untrusted agents/networks — either surface hands out full account control. See references/setup.md for the full deployment + auth guidance.

Authorized / responsible use

This drives a REAL Telegram user account, not a bot. Whoever holds the session string is the account owner — full read/write access to every DM, group, and channel that account can reach. Treat it like the account's password.

  • Only ever point this skill at an instance the user runs and owns, driving the user's own account (or an account they are explicitly authorized to operate).
  • Do not send spam, mass-DM strangers, scrape at volume, auto-join channels en masse, or run any automation that violates Telegram's Terms of Service — that gets the account banned (the throttle layer reduces flood risk, it does not make abuse safe).
  • Never provision, escalate, or log in a new account from here. This skill is consumer-only: it talks to an already-running, already-authorized instance. First-time login is an operator-side step (see setup) and is out of scope for the agent.
  • Writes are destructive and act as the user. DELETE /api/chats is irreversible; deleting / editing messages and banning users are visible actions others see. Confirm intent before running writes on shared chats.
  • Never print, log, or echo the session string, TELETHON_API_HASH, TELETHON_AUTH_KEY, phone numbers, or message contents beyond what the task needs.

When To Use

  • Read recent messages from a chat, or search a chat's history.
  • Send a DM, post to a channel/group, or drop a note in Saved Messages (chat=me).
  • Send a file/photo to a chat by URL, or download an attachment someone sent.
  • React to, pin, edit, forward, or delete messages.
  • List the account's dialogs, resolve a handle to a profile, or list a group's members.
  • Create / join / leave a chat; run group admin actions on chats the account administers.
  • Create or tally a poll.
  • Wire incoming Telegram messages into an app via the outbound webhook or the WS stream.

When NOT To Use

  • You don't have an instance the user owns — this is consumer-only; it never stands up or logs in an account.
  • Anything the user isn't authorized to do with that account — spam, mass outreach, bulk scraping, ToS-violating automation.
  • Bot-API-style multi-tenant bots — this is one user account per container, not a bot framework. If you need a bot token surface, use the Bot API instead.
  • Real-time streaming reads — history reads are request/response. For live events use the WS stream or the webhook, not a poll loop.
  • Bulk resolving cold numeric IDs — MTProto needs an access_hash. Numeric IDs only resolve for entities the session has already seen (via a prior @username / t.me lookup, a dialog list, or an incoming message). Resolve by @username first.

Setup

The container should already be running and logged in (operator-side — see setup). Set the base URL:

export TELETHON_PLUS_URL=http://localhost:8080

If the server was started with TELETHON_AUTH_KEY, export it too — every request except /healthz and /metrics then needs the bearer header:

export TELETHON_AUTH_KEY=
# add to every call below:  -H "Authorization: Bearer $TELETHON_AUTH_KEY"

Verify:

curl -s $TELETHON_PLUS_URL/healthz
# {"status": "ok", "authorized": true}

If the session is dead (bad/revoked string, or Telegram unreachable) the container fails to start rather than booting with authorized: false — the process exits and /healthz never comes up. Check docker logs and re-login. /healthz is always public.

For install / first-time login / env vars / ports, see references/setup.md.

Conventions

  • Response shape: every 2xx returns the resource directly — no {"result": ...} envelope. Lists are JSON arrays, singles are JSON objects. Errors return {"detail": ...} (FastAPI standard).
  • Chat references (chat, from_chat, to_chat) accept whatever Telethon accepts: @username, phone +1234567890, https://t.me/name, numeric ID 123456789, supergroup/channel ID -1001234567890, or me (your Saved Messages). In {chat} path segments, phone and t.me links must be URL-encoded (+%2B); usernames and IDs work inline.
  • Errors: 400 bad JSON / validation / bad input · 401 missing-or-wrong bearer (only when TELETHON_AUTH_KEY set) · 403 write attempted in read-only mode · 404 unknown endpoint / admin action · 502 Telegram RPC error, body {"telegram_error": "...", "message": "..."}.

API — GET /api/me

The authorized account's own profile.

curl -s $TELETHON_PLUS_URL/api/me | jq
{ "id": 123456789, "type": "User", "username": "someone", "first_name": "Some", "phone": "+..." }

API — GET /api/entities

Resolve any chat reference to a full profile.

ParamTypeDefaultNotes
chatstringrequired@username / phone / t.me link / numeric ID
curl -s "$TELETHON_PLUS_URL/api/entities?chat=@telegram" | jq
# { "id": 1234567, "type": "Channel", "username": "telegram", "title": "Telegram" }

API — POST /api/entities/bulk

Resolve many references at once, honoring the resolve-username bucket.

FieldTypeDefaultNotes
chatslist[string]requiredReferences to resolve

API — GET /api/dialogs

List the account's dialogs, newest activity first.

ParamTypeDefaultNotes
limitint201–200
archivedboolfalseInclude archived chats
searchstringCase-insensitive substring on title / username
curl -s "$TELETHON_PLUS_URL/api/dialogs?limit=10&archived=false" | jq

Each dialog carries id, type, username, first_name/title, unread_count, pinned, and a last_message object.

API — GET /api/messages

Read recent messages from a chat (newest first).

ParamTypeDefaultNotes
chatstringrequiredChat to read from
limitint201–200
offset_idint0Start before this message ID (pagination)
searchstringFull-text filter
curl -s "$TELETHON_PLUS_URL/api/messages?chat=me&limit=5&search=hello" | jq
[
  { "id": 4242, "date": "2026-04-29T12:00:00+00:00", "chat_id": 12345,
    "sender_id": 67890, "text": "hello", "out": false,
    "reply_to_msg_id": null, "media": false, "media_type": null }
]

Returns [] if nothing matches.

API — GET /api/messages/{id}

Fetch a single message. chat is a required query param.

curl -s "$TELETHON_PLUS_URL/api/messages/4242?chat=me" | jq

API — POST /api/messages

Send a message. One endpoint, two flavors — if the body has file_url, that URL is fetched and sent as media (with text as the caption); otherwise text is sent as a plain message.

SSRF note: file_url is fetched server-side — the container makes the HTTP request, not the caller. An attacker-controlled file_url can be used to probe internal/private network addresses reachable from the container. Don't pass arbitrary caller-supplied URLs through as file_url. Restrict it to https scheme and a small set of trusted, publicly-known domains you control or explicitly trust — never build file_url from untrusted input (a message someone else sent, a scraped page, an LLM-generated guess). Prefer local file upload over server-side URL fetches: fetch the file yourself (client-side, where you can validate it) and send it as a direct upload instead of handing the container a URL to fetch blind.

FieldTypeDefaultNotes
chatstringrequiredTarget chat
textstringrequired if no file_urlMessage text or caption (1–4096 chars)
file_urlstringnullHTTPS URL to fetch and send as media
parse_modestringnullmd / markdown / html / null
reply_tointnullMessage ID to reply to
silentboolfalseSend without notification
link_previewbooltrueShow link previews (text only)
schedulestringnullFuture ISO datetime to schedule the send
force_documentboolfalseWith file_url: send as generic document
max_bytesint52428800With file_url: reject files larger than this (max 2 GB)
# Text.
curl -s -X POST $TELETHON_PLUS_URL/api/messages \
  -H 'Content-Type: application/json' \
  -d '{"chat":"me","text":"**hello** from a container","parse_mode":"md","silent":true}' | jq

# Media from a URL (text becomes the caption).
curl -s -X POST $TELETHON_PLUS_URL/api/messages \
  -H 'Content-Type: application/json' \
  -d '{"chat":"me","file_url":"https://example.com/photo.jpg","text":"a caption"}' | jq

Returns the created message object.

API — PATCH /api/messages/{id}

Edit a message you sent. ID in the URL, rest in the body.

FieldTypeDefaultNotes
chatstringrequiredChat containing the message
textstringrequiredNew text (1–4096 chars)
parse_modestringnullmd / html / null
link_previewbooltrueShow link previews
curl -s -X PATCH $TELETHON_PLUS_URL/api/messages/4242 \
  -H 'Content-Type: application/json' \
  -d '{"chat":"me","text":"fixed version","parse_mode":"md"}' | jq

API — DELETE /api/messages

Bulk delete by ID (body carries the list).

FieldTypeDefaultNotes
chatstringrequiredChat containing the messages
message_idslist[int]requiredIDs to delete (max 100)
revokebooltrueDelete for everyone, not just yourself

Irreversible, and with revoke: true (the default) it deletes for everyone in the chat, not just you. Confirm the exact chat and message_ids with the user before calling this on a shared chat.

curl -s -X DELETE $TELETHON_PLUS_URL/api/messages \
  -H 'Content-Type: application/json' \
  -d '{"chat":"me","message_ids":[4242,4243],"revoke":true}' | jq
# { "deleted": 2, "requested": 2 }

API — POST /api/messages/forward

FieldTypeDefaultNotes
from_chatstringrequiredSource chat
to_chatstringrequiredDestination chat
message_idslist[int]requiredIDs to forward (max 100)
silentboolfalseForward without notification

API — POST /api/messages/read

Mark messages as read.

FieldTypeDefaultNotes
chatstringrequiredChat to mark read
max_idint0Mark up to this message ID. 0 = all.
curl -s -X POST $TELETHON_PLUS_URL/api/messages/read \
  -H 'Content-Type: application/json' -d '{"chat":"me","max_id":0}' | jq
# { "ok": true }

API — GET /api/messages/{id}/media

Download a message's attachment as raw bytes (binary stream). Content-Type comes from Telegram; Content-Disposition: attachment; filename=... is set. chat is a required query param.

ParamTypeDefaultNotes
chatstringrequiredChat containing the message
max_bytesint52428800Reject files larger than this
curl -s "$TELETHON_PLUS_URL/api/messages/4242/media?chat=me" -o attachment.bin

MCP clients: use the download_media tool instead — it returns base64 in the JSON result.

API — reactions & pins

Method + pathBody / paramsWhat it does
POST /api/messages/{id}/reactionschat, emoji, big?React to a message
DELETE /api/messages/{id}/reactionschatRemove your reaction
POST /api/messages/{id}/pinchat, silent?, pm_oneside?Pin
POST /api/messages/{id}/unpinchatUnpin
curl -s -X POST $TELETHON_PLUS_URL/api/messages/4242/reactions \
  -H 'Content-Type: application/json' -d '{"chat":"me","emoji":"🔥"}' | jq

API — GET /api/participants

List members of a group or channel.

ParamTypeDefaultNotes
chatstringrequiredGroup or channel
limitint1001–1000
searchstringFilter by name

Large public channels may return a limited set or require admin rights.

API — chats

Method + pathBody / paramsWhat it does
POST /api/chatstitle, megagroup? (default true)Create a supergroup (true) or broadcast channel (false)
DELETE /api/chatschatDelete a supergroup/channel you own — irreversible
POST /api/chats/joinchatJoin a public channel/group
POST /api/chats/inviteinviteJoin a private chat via a t.me/+hash link
POST /api/chats/leavechatLeave a channel/group
GET /api/chats/{chat}/linkedResolve a channel's linked discussion group

Destructive. DELETE /api/chats permanently deletes the chat for everyone in it and cannot be undone. Require explicit user confirmation naming the exact chat before calling it — prefer leaving it disabled/unused unless the task genuinely needs it.

curl -s -X POST $TELETHON_PLUS_URL/api/chats \
  -H 'Content-Type: application/json' -d '{"title":"my-group","megagroup":true}' | jq
# { "id": 1234567890, "type": "Channel", "title": "my-group" }

API — chat admin

POST /api/chats/{chat}/admin/{action} where {action} is one of ban, unban, kick, promote, demote. Requires admin rights in the chat.

ActionBodyNotes
banuser, until_seconds?Ban; until_seconds=0 = permanent — visible, disruptive
unbanuserLift a ban
kickuserBan + immediate unban (they can rejoin via invite) — visible, disruptive
promoteuser, title? (≤16 chars), plus booleans change_info, post_messages, edit_messages, delete_messages, ban_users, invite_users, pin_messages, add_admins, anonymous, manage_call (all default false)Grant admin rights — pass the specific booleans you want, omitting all of them grants admin with no permissions — privilege escalation, irreversible without another admin demoting them back
demoteuserStrip admin rights

Admin ops act on other people's membership/permissions and are visible to the whole chat. Require explicit user confirmation naming the exact chat and target user before calling ban, kick, or promote.

curl -s -X POST "$TELETHON_PLUS_URL/api/chats/-1001234567890/admin/ban" \
  -H 'Content-Type: application/json' -d '{"user":"@baduser"}' | jq

API — polls

Method + pathBody / paramsWhat it does
POST /api/pollschat, question, options, multiple_choice?, quiz?, correct_option?, solution?Create a poll
POST /api/polls/{id}/votechat, options (0-based indices)Vote
GET /api/polls/{id}/resultschatCurrent vote counts

API — status & observability

PathWhat it returns
GET /healthz{"status": "ok", "authorized": } — always public
GET /api/throttle/statusLive bucket usage, adaptive multiplier, recent flood events, cache stats
GET /api/account/health{"authorized", "risk", "multiplier", "flood_events_1h", ...}; risk is ok / warning / high
GET /metricsPrometheus exposition (public even when auth is on)

Every /api/... response also carries X-Throttle-Multiplier, X-Throttle-Flood-Events-1h, and X-RateLimit-Remaining- headers, so clients can self-throttle without polling.

The server ships conservative anti-flood throttling on by default (per-method token buckets, per-chat send/read intervals, global gap + jitter, adaptive backoff on FLOOD_WAIT, plus a persistent entity cache). If a call seems slow, it may be the throttle protecting the account — check GET /api/throttle/status. Tuning the buckets is an operator concern (see setup); don't try to defeat them.

Incoming-message webhook (TELETHON_POST_TO_URL)

Data exfiltration warning: enabling TELETHON_POST_TO_URL forwards every incoming Telegram event — full message content, sender metadata, and account activity — to that external endpoint, for as long as the container runs. Point it ONLY at a trusted HTTPS endpoint you control. Anyone who controls that endpoint gets a live copy of everything the account receives.

When the operator sets TELETHON_POST_TO_URL on the server, every incoming Telegram event (new message, edit, delete, chat action) is POSTed to that URL as JSON — fire-and-forget, so a slow or failing webhook never blocks Telethon's loop. This is the way to pipe incoming DMs/messages into a separate app or worker that can't hold a WebSocket open.

Event payload:

{
  "type": "NewMessage",
  "message": {
    "id": 4242, "date": "2026-04-29T12:00:00+00:00", "text": "hi",
    "out": false, "sender_id": 12345, "chat_id": -1001234567890,
    "reply_to_msg_id": null, "media": false, "media_type": null
  },
  "chat_id": -1001234567890
}

type is NewMessage, MessageEdited, MessageDeleted, or ChatAction — same shape, fewer fields on the non-message ones.

The same events also stream over WebSocket at GET /ws/updates (pass ?token= when auth is on — browsers can't set the Authorization header). Multiple subscribers each get their own queue; slow consumers drop events at TELETHON_UPDATES_BUFFER_SIZE (default 256). Updates are only registered when TELETHON_UPDATES_ENABLED=true (the default).

MCP Endpoint

An MCP server is mounted at /mcp/ using the streamable HTTP transport, on the same port as the HTTP API, behind the same bearer auth. It's stateless — every request is independent, no session juggling. Every tool in the registry (same names, same schemas as the endpoints above) is exposed automatically.

Point an MCP-aware agent at:

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

Wire into Claude Code:

claude mcp add --transport http telethon-plus $TELETHON_PLUS_URL/mcp/
# with auth:
claude mcp add --transport http telethon-plus $TELETHON_PLUS_URL/mcp/ \
  --header "Authorization: Bearer $TELETHON_AUTH_KEY"

The 34 tools, grouped: get_me, get_entity, bulk_resolve, get_dialogs, get_messages, get_message, send_message, send_file, edit_message, delete_messages, forward_messages, mark_read, download_media, set_reaction, remove_reaction, pin_message, unpin_message, get_participants, create_group, delete_chat, join_chat, leave_chat, join_via_invite, get_linked_chat, ban_user, unban_user, kick_user, promote_user, demote_user, create_poll, vote_poll, get_poll_results, throttle_status, account_health.

send_message and send_file are separate MCP tools (the POST /api/messages route dispatches between them by whether file_url is present). download_media returns base64 in data_base64 (the HTTP /media route streams raw bytes instead).

Raw JSON-RPC

The transport requires Accept: application/json, text/event-stream. For debugging or non-MCP callers, hit it directly:

# tools/list
curl -s $TELETHON_PLUS_URL/mcp/ \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

# tools/call — send a message
curl -s $TELETHON_PLUS_URL/mcp/ \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc":"2.0","id":2,"method":"tools/call",
    "params":{
      "name":"send_message",
      "arguments":{"chat":"me","text":"hi from mcp"}
    }
  }'

MCP tool errors come back inside the result (not as HTTP status): read-only → {"error":"read_only",...}, Telegram RPC → {"error":"telegram_rpc","type":...}, bad args → {"error":"invalid_argument",...}.

Bearer-Token Auth

If TELETHON_AUTH_KEY is set on the server, every route except /healthz and /metrics requires Authorization: Bearer (constant-time compared). Missing/wrong → 401 {"detail":"unauthorized"}. The WebSocket takes the key as ?token=. Empty/unset key = wide open — fine on a private network; for untrusted networks pair the key with a reverse proxy doing TLS.

curl -s -H "Authorization: Bearer $TELETHON_AUTH_KEY" $TELETHON_PLUS_URL/api/me | jq

Typical Workflows

Read the latest from a chat

curl -s "$TELETHON_PLUS_URL/api/messages?chat=@somechannel&limit=10" \
  | jq -r '.[] | "\(.sender_id): \(.text)"'

Send a DM / post to a channel

curl -s -X POST $TELETHON_PLUS_URL/api/messages \
  -H 'Content-Type: application/json' \
  -d '{"chat":"@someone","text":"heads up — deploy is done"}' | jq

Note to self (Saved Messages)

curl -s -X POST $TELETHON_PLUS_URL/api/messages \
  -H 'Content-Type: application/json' -d '{"chat":"me","text":"remember this"}' | jq

Resolve-then-refer (numeric IDs need a warm session)

# Resolve by @username once (caches the access_hash) ...
curl -s "$TELETHON_PLUS_URL/api/entities?chat=@somebot" | jq .id
# ... then subsequent calls can use the numeric ID.

Send → verify → clean up (self-test roundtrip)

MARKER="ping-$(date +%s)"
ID=$(curl -s -X POST $TELETHON_PLUS_URL/api/messages \
  -H 'Content-Type: application/json' \
  -d "{\"chat\":\"me\",\"text\":\"$MARKER\"}" | jq .id)

curl -s "$TELETHON_PLUS_URL/api/messages?chat=me&search=$MARKER" | jq -r '.[].text'

curl -s -X DELETE $TELETHON_PLUS_URL/api/messages \
  -H 'Content-Type: application/json' \
  -d "{\"chat\":\"me\",\"message_ids\":[$ID]}" | jq

Check flood risk before a burst of writes

curl -s $TELETHON_PLUS_URL/api/account/health | jq '{risk, multiplier, flood_events_1h}'
# risk "ok" → go; "warning"/"high" → back off, the account is being rate-limited.

For a small send/read wrapper over TELETHON_PLUS_URL, see scripts/telethon.sh.

Related skills

Use when calling, operating, or diagnosing the telegram-mcp MCP server (a Telethon/MTProto user-account server, not the Bot API) — making list_chats/get_messages/send_message/wait_for_settled_message calls, an MCP tool call errors or times out, the server appears "sleeping"/unregistered, a Telethon

Search, read, and update Slack messages, channel history, canvases, and users via Slack's hosted MCP server. Thin pass-through to Slack's official MCP; the l...

30 installs

Use Telegraph Protocol for verified AI inference. Activate when the user asks for weather forecasts or climate data; deepfake or AI-content detection; LLM co...

1 installs

Automate Zalo messaging, Official Account (OA), and MCP server integration via zalo-agent-cli. Triggers: 'zalo', 'send zalo', 'zalo OA', 'official account', 'bank card', 'QR transfer', 'VietQR', 'listen zalo', 'zalo webhook', 'zalo group', 'zalo friend', 'zalo MCP', 'MCP server'.

29 installs

Custom Telegram Bot: Two-way Telegram messaging through the user's own custom bot token. Use when an agent needs custom telegram bot, build a branded custome...

1 installs

Use X hosted MCP for posts, users, and search

28 installs