Open source · MIT · Python 3.12+
CLI and MCP server for AI agents on a personal Telegram account
Read chats, search messages and resolve usernames over MTProto from Claude Code, Claude Desktop, Codex or Cursor; every write is a plan a person applies.
What it does
- Task-shaped access to a personal Telegram account over MTProto: read chats, search messages, resolve who a username belongs to, check who is in a group.
- Plan, then apply: anything that changes something on Telegram is saved as a plan, and only tg-ai plan apply carries it out. No MCP tool applies a plan.
- Read-only by default: the readonly profile publishes no telegram_plan_* tools; plan tools appear only under profile: plan.
- Fail-closed allowlists: direct messages, sending, joining and admin actions are closed until chat ids are listed; 777000 and Saved Messages are closed in code.
- Audit log before and after every RPC, rate limits that persist across restarts, and an identical-send ledger.
- One CLI with a --json form and an MCP server over stdio or loopback HTTP with a bearer token; installs with uvx, pipx, uv tool or Docker.
What it does
| Command | Answers |
|---|---|
tg-ai fleet | Which accounts are configured, authorized, expired or locked right now |
tg-ai chats | Which chats exist, and what their chat id is (search by title) |
tg-ai chat read | What was said in a chat — paged with --before-id, filtered to one forum topic with --topic-id, with media metadata |
tg-ai chat topics | Which topics a forum supergroup has, and where the unread messages are |
tg-ai inbox | What's waiting for a reply right now, across every configured account |
tg-ai watch | Wait for the next incoming message instead of polling for it — a burst of fast replies comes back as one answer, and the wait is capped |
tg-ai search | Which messages match a phrase, and where — with --context N, the messages either side of each match |
tg-ai whois | Who a @username, numeric id or invite resolves to; for channels, the "message the admins" Direct Messages chat — its name, and an id you can read or send to without joining anything |
tg-ai chat members | Who's in a chat, and who administers it |
tg-ai media fetch | Save a message's photo, video or document to a server-controlled path |
tg-ai media transcribe | Turn a voice message into text — Whisper in an optional container, on your own machine, with the network switched off |
tg-ai archive sync | Copy one named chat into a local SQLite archive, resuming where the last run stopped |
tg-ai archive search | Search that archive offline — substring or regular expression, by sender, by date — with no Telegram request at all |
tg-ai archive status | Which chats are archived, how many messages, and when each was last synced |
tg-ai archive forget | Erase one chat, and every message of it, from the local archive |
tg-ai drafts | What was started and never sent, and in which chat |
tg-ai scheduled | What is queued to be sent to a chat later, and when |
tg-ai account sessions | Which devices and apps this account is signed in on — and whether one of them isn't yours |
tg-ai message send / message send-file / chat join / chat promote / … | Validate and save an intent — send a message or a file, edit, delete, forward, join, leave, create a group, invite, promote, change the profile — as a plan. Nothing goes out yet |
tg-ai chat ban / unban / kick / restrict / demote | Moderation, planned the same way: one member per plan, and the preview says who, where, which rights and for how long |
tg-ai message schedule | Queue a message for an exact time — or for when the other person is next online — as a plan. Once applied it sits in Telegram's own scheduled queue, visible and cancellable in the app |
tg-ai chat archive / chat mute | Put a chat in Archived, or silence it for a while or indefinitely — settings only this account can see, planned like every other write |
tg-ai folder add | Put a chat into one of the folders the account already has. Telegram replaces a folder wholesale when it is edited, so everything already in it is preserved verbatim — including the chats this configuration may not list |
tg-ai plan list / plan show <id> | See what's waiting for a decision, and exactly what applying it would do |
tg-ai plan apply <id> | Carry out exactly what a saved plan describes |
tg-ai plan reject <id> | Decline a pending plan |
tg-ai daemon serve --account <label> | Hold one account open so several local callers share it instead of the second one being refused |
tg-ai mcp / tg-ai mcp --http | Serve MCP over stdio, or over loopback HTTP with a mandatory bearer token |
Safety
Read operations run immediately. Nothing else does — every operation that changes something on Telegram is a two-step plan, then apply.
| Caller | Read | Plan | Apply |
|---|---|---|---|
| CLI, a person at a terminal | immediate | tg-ai message send … (each write operation is its own command; it saves a plan and prints the id) | tg-ai plan apply <id> |
| MCP client with no shell access (Claude Desktop, most IDE/editor integrations) | immediate | telegram_plan_<operation> tool — only under profile: plan; the default readonly profile does not publish plan tools at all | not reachable — there is no MCP tool that applies a plan |
| MCP client that also has its own shell (Claude Code, Codex, and similar coding agents) | immediate | telegram_plan_<operation> tool — only under profile: plan | can run tg-ai plan apply <id> itself, through its own shell — see the note below |
Read-only by default, writing by explicit opt-in. A fresh install runs the readonly profile, and its MCP tool list contains no telegram_plan_* tool: a client sees the read tools and four local tools that write only into configured directories on this machine (see MCP tools), and a plan tool called by name anyway is refused with FORBIDDEN_BY_PROFILE before anything runs. Publishing the plan tools takes profile: plan in the config or TGAI_PROFILE=plan in the client's environment — and even then nothing is sent until a person runs tg-ai plan apply.
A write command never sends anything by itself — it records a plan and tells you how to carry it out:
$ tg-ai message send --chat 987654321 --text "Meeting moved to 3pm"
{
"plan_id": "4f0f8a2b9c7e1d3a5b6c7d8e9f0a1b2c",
"operation": "message.send",
"summary": "send to Marketing Team (group, 987654321): \"Meeting moved to 3pm\"",
"state": "pending",
"next": "tg-ai plan apply 4f0f8a2b9c7e1d3a5b6c7d8e9f0a1b2c"
}
tg-ai plan show <id> prints exactly what applying it would do — every field already passed through the same sanitizer that protects the terminal from a hostile chat title or message body (see render.py) — and only tg-ai plan apply <id> (with a confirmation prompt, or --yes to skip it) actually carries it out.
The honest limit of this design, stated plainly: there is no MCP tool that applies a plan, on purpose — a confirmation an agent can send over MCP is a confirmation prompt injection can send. But that boundary is a property of the transport, not of the agent. An MCP client that has its own shell — Claude Code, Codex, and comparable coding agents — can simply run tg-ai plan apply <id> as a shell command, the same as a person would. Nothing in this project detects or blocks that, because nothing at the process level can tell "the person typed this command" apart from "the agent typed this command" once both share a terminal.
For that class of client, this design does not pretend to hold a hard line at plan/apply. The line it actually offers is: every attempt and every outcome is written to the audit log before and after the RPC (audit.py), rate limits persist across restarts so an agent (or an injection driving one) cannot out-run them by being restarted (limits.py), the hard denylist on 777000 (Telegram's login-code chat) and Saved Messages cannot be reconfigured away, and everything an agent is about to approve is sanitized before it reaches a terminal so the text being approved is the text that will actually send (render.py). If your MCP client can run shell commands, you are trusting the agent, and the safety net is visibility and limits, not an unbreakable gate. A profile named full — direct send with no plan step, from any caller — does not exist in this project; it was considered and rejected. A stronger boundary (a separate OS principal, or a TOTP-gated apply) is out of scope for v0.1 and is written up as a path forward in docs/threat-model.md.
Reading, chats and profiles
Groups and channels are readable as soon as an account is configured; direct messages are not readable until a chat id is explicitly allowlisted — an empty dms allowlist means none, not all. 777000 (Telegram Service Notifications, where login codes and 2FA resets arrive) and Saved Messages are closed in code and cannot be reopened by any configuration. Every write capability (send, admin, join, profile changes) is fail-closed the same way: an empty list means nothing is permitted until you add to it. Two profiles exist — readonly (default: only the read and local tools are published) and plan (read, plus creating plans; the plan tools appear only under it). See telegram_ai_cli_mcp/safety.py for the exact rule order.
Importing an existing session
Importing a tdata folder from Telegram Desktop is one way to authorize an account without a fresh login flow. Two things to know before you do it:
USE_CURRENT_SESSIONlogs the original Telegram Desktop out. Convertingtdatainto a usable MTProto session can either mint a brand-new session (the desktop app stays logged in) or take over the existing one (the desktop app is signed out the moment the imported session connects). If you want to keep using Telegram Desktop with the account you're importing, do not ask for the current-session mode.- Telegram treats a
.sessionfile as equivalent to holding the account's auth key. Anyone who obtains it controls the account until the session is revoked from a device. Store it accordingly — seedocs/threat-model.md.
Install
uvx --from telegram-ai-cli-mcp tg-ai mcp
That one command is the server. It speaks MCP on stdin and waits; every client block below runs the same uvx command. Sign the account in first, from a terminal, because neither the server nor the Desktop extension can prompt for a login code:
uvx --from telegram-ai-cli-mcp tg-ai account login --label work --phone "<your phone, E.164>"
--label names the account; put your own number in E.164 form; --phone is needed only on the first sign-in of that label. account login-qr --label work signs in by QR code instead, with no phone number.
The session is stored under ~/.local/state/telegram-ai-cli-mcp/. The server sends nothing by itself: a plan tool records an intention, and a person applies it with tg-ai plan apply.
The package is telegram-ai-cli-mcp on PyPI; uvx caches what it installed and may keep running that version; --from telegram-ai-cli-mcp@latest checks PyPI again, and --from telegram-ai-cli-mcp==0.1.3 pins one. This README follows main, so what the changelog lists under Unreleased is not in the PyPI release yet. Since 0.1.2 the readonly profile lists no telegram_plan_* tool; 0.1.1 and earlier still list them under readonly and refuse every call, so a uvx cache holding one of those shows plan tools that do not work. To run main itself, use uvx --from git+https://github.com/stufently/telegram-ai-cli-mcp tg-ai mcp. For a lasting tg-ai on PATH, pipx install telegram-ai-cli-mcp or uv tool install telegram-ai-cli-mcp. A checkout still works, for development:
git clone https://github.com/stufently/telegram-ai-cli-mcp.git
cd telegram-ai-cli-mcp
pip install .
tg-ai --version
Give it an environment of its own — pipx, uv tool, or a plain
python3 -m venv — rather than a shared user site, and put the resulting
bin/tg-ai on PATH. The dependency floor here is deliberately current
(mcp>=2.0, telethon>=1.44), and a shared site-packages holds exactly one
version of a package for every program on the machine: anything pinned below
that floor loses, silently, the moment this project is installed or upgraded
alongside it.
That collision is live on the maintainer's host, where the tg-claude-userbot
project pins mcp==1.27.2 and telethon==1.43.2. Since 2026-09-03 tg-ai
lives in its own virtualenv there and the shared entry point is a symlink into
it:
/home/deploy/.venvs/tg-ai # editable install of this repo
/home/deploy/.local/bin/tg-ai -> /home/deploy/.venvs/tg-ai/bin/tg-ai
Details of that layout, and what was left behind in the shared site, are in Operations.
docker build -t telegram-ai-cli-mcp .
docker run --rm telegram-ai-cli-mcp --version # arguments go to tg-ai
docker run --rm -i telegram-ai-cli-mcp # default: tg-ai mcp over stdioExample prompts
Ask in plain language. The server picks the tool.
- What did I miss in Telegram since yesterday morning?
- Who mentioned me in the project chat, and what did they actually say?
- Who is this username, and which groups do we have in common?
- Draft a reply to Alex's last message, but do not send it.
- Which devices is this account signed in on right now?
FAQ
How do I let Claude read my Telegram?
Run tg-ai mcp and register it as an MCP server with Claude Code or Claude Desktop. The readonly profile is active by default and groups and channels are readable immediately; direct messages stay closed until you add specific chat ids to safety.read.dms.allow.
Is there an MCP server for Telegram user accounts?
Yes, this one. It is built on MTProto through Telethon, not the Bot API, so it can read existing chat history, resolve usernames and see who is in a group the way a human member's client can.
Can an AI agent send messages on my Telegram account through this?
Only after a two-step plan and apply. An MCP client without shell access cannot apply a plan. A client that also has a shell (Claude Code, Codex and similar) can run tg-ai plan apply itself, so for those the safety net is the audit log and rate limits, not a hard gate.
Is this a Telegram bot?
No. It automates a personal user account over MTProto. A Bot API bot is a different kind of Telegram entity with different limits and different visibility into chat history.
Will Telegram ban my account for using this?
Telegram can limit or ban any account it judges to be running abusive automation, whatever tool sent the requests. The tool is built around ordinary reading and occasional allowlisted sending; mass joins, high-volume sends and member scraping are not something it tries to make safe.
Which Python version do I need?
3.12 or newer; 3.12 to 3.14 are tested in CI. Linux is tested, macOS is expected to work but not tested, and Windows is not supported because session locking uses fcntl.
Documentation pages
- telegram-ai-cli-mcp — CLI and MCP server for AI agents on a personal Telegram accounttelegram-ai-cli-mcp is a CLI and MCP server, built from one Python codebase, for automating a personal Telegram account over MTProto. It gives Claude…
- Operations referenceEvery capability of this tool is declared once, as an Operation in opspec.py. The CLI renders that declaration into a command, the MCP server renders it…
- Configuration referenceEverything on this page is defined in config.py and read exactly once per process into a Settings object. Policy decisions are made against that object,…
- Threat modelThis is the fuller version of the reasoning summarized in SECURITY.md. It exists so a reader deciding whether to trust this tool with their own Telegram…
- Product boundaries for v0.1This file turns “maybe someday” notes into explicit decisions. The project is a bounded personal-account CLI/MCP server, not a general Telegram client, a…