Telegram MCP Server for Claude Code: Telethon, MTProto and Real Workflows
How to build a Telegram MCP server for Claude Code with Telethon: tools, auth, session strings, async traps, security boundaries and production workflows.

Telegram is where a lot of my actual work happens: DMs, group chats, channel discussions, source links, community questions, random ideas, people sending screenshots at 2am.
So I built a Telegram MCP server. It gives Claude Code controlled access to Telegram through Telethon: read messages, search chats, send replies, inspect context, draft posts, and pull source material into my workflows.
Quick answer: what a Telegram MCP server is
A Telegram MCP server is a bridge between Claude Code and Telegram. MCP exposes tools to the agent. Telethon talks to Telegram through MTProto as a user client. Together they let Claude Code work with Telegram like a tool, not like a website you manually scroll.
This is not the Bot API. A bot is great for public bot workflows. A Telethon user client is useful when you need your own account context: chats, groups, history, replies, and search.
Why Telegram as an MCP server
MCP (Model Context Protocol) is the standard layer for connecting AI agents to external tools. Claude Code can call MCP tools directly while it works in your repo or vault.
Telegram via MCP gives the agent access to the place where the messy context already lives:
- read recent messages from chats and channels;
- search across message history;
- fetch context around a linked message;
- draft or send replies;
- collect source links for articles;
- monitor discussions and summarize what changed.
The important word is controlled. I do not want an agent randomly posting as me. I want a tool layer with explicit methods, logging, limits, and human approval for anything public.
Bot API vs Telethon userbot
This is the first decision people get wrong.
| Option | Good for | Bad for |
|---|---|---|
| Telegram Bot API | Public bots, commands, webhooks, clean product flows | Reading your personal history, private chats, groups where the bot is absent |
| Telethon / MTProto | User-account workflows, history search, channel/group context, research | Anything that needs strict productized permission boundaries |
My setup uses Telethon because my use case is personal operations and research. The agent needs to understand context from my actual Telegram world, not from a narrow bot inbox.
That comes with responsibility. A userbot is powerful. Treat the session string like a production secret. Add rate limits. Keep destructive tools out until you have logs and approvals.
Architecture: Telethon + FastMCP
The stack is intentionally boring:
- Telethon — Python library for Telegram MTProto.
- FastMCP — minimal Python framework for MCP tools.
- Session string — portable Telegram auth for servers/containers.
- Claude Code — the agent that calls the tools.
Minimal shape:
from mcp.server.fastmcp import FastMCP
from telethon import TelegramClient
from telethon.sessions import StringSession
import os
mcp = FastMCP("telegram")
client = TelegramClient(
StringSession(os.environ["TELEGRAM_SESSION_STRING"]),
int(os.environ["TELEGRAM_API_ID"]),
os.environ["TELEGRAM_API_HASH"],
)
@mcp.tool()
async def get_messages(chat: str, limit: int = 20):
"""Read recent messages from a chat, channel or user."""
entity = await client.get_entity(chat)
result = []
async for msg in client.iter_messages(entity, limit=limit):
result.append({
"id": msg.id,
"text": msg.message or "[non-text]",
"date": msg.date.isoformat(),
"sender_id": msg.sender_id,
})
return resultThe non-obvious bug: use msg.message, not only msg.text. In some Telethon flows, especially replies/context calls, msg.text can be empty when msg.message still has the content.
Tools I would ship first
Do not start with 92 methods. Start with the small set that unlocks real work:
| Tool | Why it matters |
|---|---|
list_chats | Lets the agent find the right context without hardcoded IDs. |
get_messages | Reads recent messages for summaries and triage. |
search_messages | Turns Telegram into a searchable source base. |
get_message_context | Pulls surrounding thread/replies around one message. |
draft_message | Returns text for approval instead of posting directly. |
send_message | Only after approval/logging exists. |
The best first version is read-heavy and write-light. Reading context creates leverage immediately. Posting as yourself should be the last tool you trust.
Connecting to Claude Code
In your project's .mcp.json:
{
"mcpServers": {
"telegram": {
"command": "python",
"args": ["/path/to/telegram_mcp_server.py"],
"env": {
"TELEGRAM_API_ID": "${TELEGRAM_API_ID}",
"TELEGRAM_API_HASH": "${TELEGRAM_API_HASH}",
"TELEGRAM_SESSION_STRING": "${TELEGRAM_SESSION_STRING}"
}
}
}
}Keep secrets in environment variables or your deployment secret store. Do not paste a session string into a repo. It is basically a login.
The async traps
Telegram clients are async. MCP servers are async. Agents are impatient. That combination creates very boring bugs.
- Do not call
asyncio.run()inside an already-running loop. - Wrap
async forinside async functions. - Set hard limits on tools that scan message history.
- Return compact structured data, not raw Telegram objects.
- Split large searches into pages instead of dumping thousands of messages into context.
If your tool returns a wall of text, Claude Code will spend tokens reading noise. Better tools return small JSON with IDs, dates, senders, and short text snippets. Then the agent can ask for details only when needed.
Production boundaries
My rule for Telegram MCP: read is default, write is explicit.
Useful guardrails:
- separate read-only and write tools;
- require a dry-run/draft mode for public posts;
- log every send operation;
- rate-limit sends to avoid FloodWait pain;
- block unknown chats by default;
- never expose secrets or session strings in tool output.
Telegram MCP is powerful precisely because Telegram is personal. That is also why the security bar is higher than for a toy MCP demo.
My real workflows
The boring use cases are the valuable ones.
Community triage. “Check whether anyone asked a question in the last hour.” The agent reads discussion messages and gives me a short brief.
Content research. “Find messages where people discussed Claude Code skills.” The agent searches Telegram, pulls source links, and turns them into a source pack for a blog post.
Personal assistant. My server agent can receive messages, route them to projects, and update my Obsidian vault. Telegram is the input layer; Claude Code is the worker.
Drafting in my voice. The agent can draft a reply or post using dania.zip. I still approve what gets published. The agent drafts, I ship.
Troubleshooting checklist
- ChannelPrivateError: the account can see one context but cannot write to another. Read permissions and write permissions are not the same.
- sender is None: common when sender profiles are inaccessible. Handle missing sender fields gracefully.
- FloodWait: slow down. Add sleeps, batch fewer operations, and avoid loops that send repeatedly.
- Session works locally but not on server: use
StringSessionand verify env vars exist in the process that launches Claude Code. - Tool output is too large: return IDs/snippets first, then fetch full context on demand.
Where this fits with Claude Code
Telegram MCP is not a standalone product for me. It is one layer in the agent stack:
- Claude Code setup gives the agent tools and rules.
- Obsidian second brain gives it long-term memory.
- Telegram MCP gives it a communication and source-collection interface.
That combination is where it becomes genuinely useful. Telegram is input. Obsidian is memory. Claude Code is execution.
A Telegram MCP server is probably the most practical MCP server you can build if your work already lives in chats. Not because it is fancy. Because it connects the agent to the place where your context actually appears first.