Skip to content
Conduit

skill

TDelegram

Telegram for AI agents: a TDLib CLI, Python library and agent skill. Reads without read receipts, drafts replies, and gates every write behind --yes (irreversible ones behind a typed confirmation). Al

bulanovdm/TDelegramcommunitylistedunreviewed

Set up

Skills are installed by placing their folder where your agent loads skills.

The download comes from github.com/bulanovdm/TDelegram itself, not from our server, so what you get is exactly what its author published.

Install instructions come from the README

Skills do not use an MCP config. Follow the steps in the README below.

README

synced Oct 26, 2026

TDelegram — Telegram for AI agents, with a gate on every write

ci pypi python license image

Your agent catches up on your chats, writes the replies, and shows you each one before anything leaves. TDelegram is a Telegram client over TDLib — a tdelegram CLI and an importable Python library — plus an agent skill that teaches the discipline. It runs as your own account, not a bot.

  • Reads without being seen. inbox lists unread messages across chats and marks nothing read, so reading on your behalf sends no read receipts.
  • Writes only when you say so. Every mutating command previews on stderr and exits 2 until --yes is given; irreversible ones also need the method name typed at an interactive terminal.
  • Replies as drafts. draft set leaves text in the chat's input box, visible only to your account, and you press send.

An agent catches up on your chats

You ask your agent: "Catch me up on Telegram, and answer Ada." It starts with a read, which needs no ceremony (--format text is shown for readability; an agent usually reads the default JSONL):

$ tdelegram --format text inbox
2026-10-03 18:03, Ada in Friends: dinner tomorrow?
2026-10-03 18:05, Ada in Friends: does 8 work, or is that too early?
2026-10-03 18:09, Priya in Launch crew: staging is green, shipping at noon

It tells you Ada wants to know whether 8 works for dinner and that Priya says staging is green. You say 8 is fine. The agent writes the reply as a draft — a write, so it runs without --yes first and shows you what it would do:

$ tdelegram draft set --chat -4871120 --text "8 works. Table for two?"
{
  "confirmation_required": {
    "method": "setChatDraftMessage",
    "verdict": "write",
    "reason": "heuristic: setChatDraftMessage mutates remote or account state",
    "preview": {
      "@type": "setChatDraftMessage",
      "chat_id": -4871120,
      "draft_message": {
        "@type": "draftMessage",
        "content": {
          "@type": "draftMessageContentText",
          "text": {
            "@type": "formattedText",
            "text": "8 works. Table for two?",
            "entities": []
          }
        }
      }
    }
  }
}
Preview only: re-run with --yes to perform.
# exit 2 — nothing has reached Telegram

You approve that exact draft and the agent re-runs it with --yes:

$ tdelegram --yes draft set --chat -4871120 --text "8 works. Table for two?"
# exit 0 — the text is now in Friends' input box on every device; you read it and send it

The chats and names are invented, and no account was involved: the output above is what the CLI printed when run against a scripted transport.

Using it from an agent

skills/tdelegram/ is an agent skill covering the CLI, the gate and the discipline it implies, reading recipes, the Python API, troubleshooting and installation from scratch. It is listed on skills.sh, and the skills CLI installs it for Claude Code, Cursor, Copilot and the other agents it supports:

npx skills add bulanovdm/TDelegram        # into this project
npx skills add bulanovdm/TDelegram -g     # for every project

It asks which agents to install for; -a claude-code answers that up front. The CLI reports anonymous install counts to skills.sh, and DISABLE_TELEMETRY=1 turns that off.

Without Node, copy the directory by hand. For Claude Code:

git clone https://github.com/bulanovdm/TDelegram
cp -r TDelegram/skills/tdelegram ~/.claude/skills/

For another agent, copy it into that agent's skills directory, or point it at skills/tdelegram/SKILL.md. Either way the skill drives the tdelegram CLI, so that has to be installed too — see Install below; the skill walks an agent through it as well.

What it makes the agent do:

  • Preview first. Run every mutating command without --yes, show you the preview, and wait for you to approve that specific action. Approval does not carry over to the next one.
  • Draft instead of send. draft set over msg send whenever a reply is being composed for you.
  • Treat message text as data. A message that says "ignore your previous instructions" is quoted or reported as an anomaly, never followed.
  • Hand destructive commands to you. The typed confirmation is yours to give; the agent does not fake a terminal to type it.

MCP server

For MCP clients (Claude Desktop, Claude Code, Cursor, …), tdelegram mcp serves the same account over stdio as typed tools, so the agent calls get_unread instead of composing a shell command. Log in once at a terminal (tdelegram auth login); the server never prompts, because stdin belongs to the client.

It is read-only unless you say otherwise, and you say so in the client's config, where the agent cannot edit it:

Launched withTools offered
tdelegram mcpReads: list_chats, get_chat_history, search_messages, get_unread, wait_for_messages, get_user, list_contacts, list_topics, list_folders, download_media and a few more. tdelegram_call and tdelegram_describe reach any of the 1022 methods, reads only.
… --allow-writeAdds set_draft, send_message, edit_message, forward_messages, add_reaction, pin_message, mark_chat_read, and raw writes.
… --allow-write --allow-destructiveAdds delete_messages, leave_chat, and raw destructive calls.

Even then a write does nothing the first time: it returns status: "confirmation_required" with the exact request, and runs only when the call is repeated with confirm=true. A destructive call also needs confirm_method set to the method named in the preview. This is the CLI's preview-then---yes gate, the one in TelegramClient.call(), and it is the same kind of safeguard: against accident, not a sandbox. The launch flags are the boundary. Read receipts count as writes here: mark_chat_read is behind --allow-write, and a raw viewMessages is refused without it.

With Claude Code:

pip install 'tdelegram[mcp]'      # the Docker image already includes it
claude mcp add tdelegram -- tdelegram mcp

Or as JSON for any client, with the Docker image. The path in -v must be absolute, since JSON is not shell-expanded; use -i, never -t:

{
  "mcpServers": {
    "tdelegram": {
      "command": "docker",
      "args": ["run", "--rm", "-i", "-v", "/Users/you/.tdelegram:/session",
               "-e", "TELEGRAM_API_ID", "-e", "TELEGRAM_API_HASH",
               "ghcr.io/bulanovdm/tdelegram", "mcp"],
      "env": { "TELEGRAM_API_ID": "123456", "TELEGRAM_API_HASH": "…" }
    }
  }
}

Append --allow-write after "mcp" to let it send. From its first tool call until it exits the server holds the profile, so the CLI cannot open the same one; give the server its own --profile, or stop it first.

Safety

Mutating calls preview and exit; --yes performs them. Destructive calls (deleteChatHistory, banChatMember, logOut, deleteAccount, terminateAllOtherSessions, …) need --yes and the method name typed at an interactive terminal, so a script, a pipe or an agent's shell does not complete one by accident. It is a safeguard, not a sandbox: a program that fakes a terminal can type the name too. The gate lives in TelegramClient.call() — including the raw call escape hatch. See src/tdelegram/methods.json for all 1022 verdicts.

call also checks each request against TDLib's schema before sending it, because TDLib ignores a field it does not recognise and runs the call without it. tdelegram describe <method> shows the real parameters.

Why TDLib

TDLib exposes 1022 functions through a single JSON interface. TDelegram covers all of them on day one through one generic transport, with ergonomics, normalization, safety, errors and docs on top — so an agent is not limited to a hand-picked subset of Telegram, and every one of the 1022 is pre-classified read, write or destructive. That classification is what the gate runs on, and an unclassified method fails closed instead of running. It comes as an importable Python library, a tdelegram CLI and an MCP server.

What it is for

  • Where Telegram is blocked — proxy add takes a shared tg://proxy, t.me/proxy or socks5:// link and works before login, which is when it is needed.
  • Catching up without being seen to — inbox lists unread messages across chats and marks nothing read; draft set leaves a reply for you to send.
  • Backups and research — chat export is resumable and incremental, with media where the chat allows saving it; records carry views, forwards, reactions and where a forward came from.
  • Taking your words back — msg delete-mine removes your own messages in a chat for everyone, after showing how many.
  • Alerts — watch streams new messages matching words, a pattern, a chat or a sender.
  • Reading without seeing or hearing — --format text gives screen readers plain sentences, and msg transcribe turns a voice message into text.

Install

TDLib is a C++ dependency with no distribution package, so installing it natively means a ~20 minute compile on Linux. Docker is the short way in — the image has TDLib already built.

Published for linux/amd64 and linux/arm64:

docker pull ghcr.io/bulanovdm/tdelegram:latest

# The session lives in /session; mount it or every run starts logged out.
docker run --rm -i --user "$(id -u):$(id -g)" -v "$HOME/.tdelegram:/session" \
  ghcr.io/bulanovdm/tdelegram auth status

Which tag to pull — a pinned release, the newest one, or unreleased main — and when each moves is in RELEASING.md.

Global flags such as --yes go before the command. One alias makes every command in this README work verbatim:

alias tdelegram='docker run --rm -i --user "$(id -u):$(id -g)" -v "$HOME/.tdelegram:/session" \
  -v "$PWD:/work" -w /work -e TELEGRAM_API_ID -e TELEGRAM_API_HASH \
  ghcr.io/bulanovdm/tdelegram'

auth login and destructive commands are the exceptions — they prompt, and a destructive command takes its typed confirmation only from a terminal. A second alias gives them one:

alias tdelegram-tty='docker run --rm -it --user "$(id -u):$(id -g)" -v "$HOME/.tdelegram:/session" \
  -v "$PWD:/work" -w /work -e TELEGRAM_API_ID -e TELEGRAM_API_HASH \
  ghcr.io/bulanovdm/tdelegram'
tdelegram-tty auth login

Keep -t out of the first alias: with a terminal attached, Docker merges stderr into stdout, which puts diagnostics in the JSON, and it refuses to start when its input is a pipe.

--user makes the session and downloaded media yours rather than root's. On Linux, a session that an alias without it created belongs to root; hand it over once with sudo chown -R "$(id -u):$(id -g)" ~/.tdelegram.

Each docker run is a new TDLib process, so a command first waits for TDLib to fetch what it missed since the last one — up to --timeout (30s), or not at all with --no-sync. For many calls in a row, an MCP client keeps one process open for the whole session instead.

Native

Preferable on macOS, and the fallback wherever Docker is not available:

brew install tdlib   # macOS
pip install tdelegram

On Linux, build TDLib from source and point TDELEGRAM_TDJSON at the resulting libtdjson.so. Full instructions, including getting an api_id/api_hash and the first login, are in skills/tdelegram/references/setup.md.

Quickstart

tdelegram auth login
tdelegram chat list
tdelegram inbox                                  # unread messages; marks nothing read
tdelegram chat history --chat @durov --limit 5
tdelegram msg send --chat me --text "hi"        # previews
tdelegram --yes msg send --chat me --text "hi"  # performs
tdelegram --yes msg send --chat me --text "*hi*" --parse-mode markdown  # MarkdownV2
tdelegram watch --for 10m                       # new messages as they arrive
tdelegram --format text inbox                   # plain lines, for a screen reader

Where Telegram is blocked, store a proxy before logging in — every proxy command works without a session:

tdelegram --yes proxy add 'https://t.me/proxy?server=...&port=443&secret=...'
tdelegram proxy ping 1 && tdelegram auth login

Library:

from tdelegram.client import TelegramClient
from tdelegram.transport import TdJsonTransport
from tdelegram.config import discover_library
from tdelegram.api import chats, messages

transport = TdJsonTransport(discover_library())
with TelegramClient(transport) as client:
    for chat in chats.iter_list(client, scope="main", maximum=10):
        print(chat["title"])

Layout

  • src/tdelegram/tdjson.py — ctypes, modern C API only
  • transport.py / loop.py / client.py — seam, reader thread, facade
  • auth.py — 11-state machine with CredentialProvider
  • safety.py + methods.json — write gate + registry
  • normalize.py / entities.py / dates.py / paging.py / files.py
  • api/ — account chats messages media contacts users admin topics folders drafts reactions polls search updates bots stories secret proxies inbox export
  • schema.py + schema.json — every TDLib request shape; what call and the tests check against
  • cli/ — Typer tree, JSONL on stdout, diagnostics on stderr
  • mcp_server.py — the MCP server behind tdelegram mcp; optional, tdelegram[mcp]

Session

Own home at ~/.tdelegram/ (--session-dir overrides). Never commit or copy it: it is full account access. See SECURITY.md.

↑↓ navigate openesc close