by chigwell
Provides a full‑featured Telegram integration for MCP‑compatible clients, enabling programmatic access to chats, messages, contacts, profile management, and group administration.
A server that wraps the Telethon library behind the Model Context Protocol (MCP), exposing virtually every Telegram feature as callable tools. These tools can be invoked from Claude, Cursor, or any MCP‑compatible AI agent, allowing the AI to read, send, and manage Telegram content as if it were a human user.
session_string_generator.py
)..env
file with TELEGRAM_API_ID
, TELEGRAM_API_HASH
, and TELEGRAM_SESSION_STRING
(or a session name).python -m uvicorn main:app
(or similar entrypoint) or via Docker (docker compose up
).Q: Do I need a bot token? A: No. The server works with a regular user session string, granting full access to your account.
Q: Can I run the server on a VPS without Docker?
A: Yes. Install Python 3.10+, create a virtual environment, install dependencies from requirements.txt
, and start the script.
Q: Why are file‑related tools (send_file, download_media) missing? A: The current MCP environment does not support direct filesystem paths, so those tools were removed for reliability.
Q: How secure is my session string?
A: Treat it like a password; never commit it to a repo and keep .env
out of version control.
Q: Which clients can use this server? A: Any MCP‑compatible client, such as Claude Desktop, Cursor, or custom agents built on the MCP Python SDK.
Q: What if I need to manage multiple Telegram accounts?
A: Run separate instances of the server, each with its own .env
configuration and distinct server name in the MCP client config.
Here's a demonstration of the Telegram MCP capabilities in Claude:
Basic usage example:
As you can see, the AI can seamlessly interact with your Telegram account, retrieving and displaying your chats, messages, and other data in a natural way.
A full-featured Telegram integration for Claude, Cursor, and any MCP-compatible client, powered by Telethon and the Model Context Protocol (MCP). This project lets you interact with your Telegram account programmatically, automating everything from messaging to group management.
This MCP server exposes a huge suite of Telegram tools. Every major Telegram/Telethon feature is available as a tool!
Please note that tools requiring direct file path access on the server (send_file
, download_media
, set_profile_photo
, edit_chat_photo
, send_voice
, send_sticker
, upload_file
) have been removed from main.py
. This is due to limitations in the current MCP environment regarding handling file attachments and local file system paths.
Additionally, GIF-related tools (get_gif_search
, get_saved_gifs
, send_gif
) have been removed due to ongoing issues with reliability in the Telethon library or Telegram API interactions.
git clone https://github.com/chigwell/telegram-mcp.git
cd telegram-mcp
python3 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install -r requirements.txt
python3 session_string_generator.py
Follow the prompts to authenticate and update your .env
file.
Copy .env.example
to .env
and fill in your values:
TELEGRAM_API_ID=your_api_id_here
TELEGRAM_API_HASH=your_api_hash_here
TELEGRAM_SESSION_NAME=anon
TELEGRAM_SESSION_STRING=your_session_string_here
Get your API credentials at my.telegram.org/apps.
If you have Docker and Docker Compose installed, you can build and run the server in a container, simplifying dependency management.
From the project root directory, build the Docker image:
docker build -t telegram-mcp:latest .
You have two options:
Option A: Using Docker Compose (Recommended for Local Use)
This method uses the docker-compose.yml
file and automatically reads your credentials from a .env
file.
.env
File: Ensure you have a .env
file in the project root containing your TELEGRAM_API_ID
, TELEGRAM_API_HASH
, and TELEGRAM_SESSION_STRING
(or TELEGRAM_SESSION_NAME
). Use .env.example
as a template.docker compose up --build
docker compose up -d
to run in detached mode (background).Ctrl+C
to stop the server.Option B: Using docker run
You can run the container directly, passing credentials as environment variables.
docker run -it --rm \
-e TELEGRAM_API_ID="YOUR_API_ID" \
-e TELEGRAM_API_HASH="YOUR_API_HASH" \
-e TELEGRAM_SESSION_STRING="YOUR_SESSION_STRING" \
telegram-mcp:latest
-e TELEGRAM_SESSION_NAME=your_session_file_name
instead of TELEGRAM_SESSION_STRING
if you prefer file-based sessions (requires volume mounting, see docker-compose.yml
for an example).-it
flags are crucial for interacting with the server.Edit your Claude desktop config (e.g. ~/Library/Application Support/Claude/claude_desktop_config.json
) or Cursor config (~/.cursor/mcp.json
):
{
"mcpServers": {
"telegram-mcp": {
"command": "uv",
"args": [
"--directory",
"/full/path/to/telegram-mcp",
"run",
"main.py"
]
}
}
}
Below are examples of the most commonly used tools with their implementation and sample output.
@mcp.tool()
async def get_chats(page: int = 1, page_size: int = 20) -> str:
"""
Get a paginated list of chats.
Args:
page: Page number (1-indexed).
page_size: Number of chats per page.
"""
try:
dialogs = await client.get_dialogs()
start = (page - 1) * page_size
end = start + page_size
if start >= len(dialogs):
return "Page out of range."
chats = dialogs[start:end]
lines = []
for dialog in chats:
entity = dialog.entity
chat_id = entity.id
title = getattr(entity, "title", None) or getattr(entity, "first_name", "Unknown")
lines.append(f"Chat ID: {chat_id}, Title: {title}")
return "\n".join(lines)
except Exception as e:
logger.exception(f"get_chats failed (page={page}, page_size={page_size})")
return "An error occurred (code: GETCHATS-ERR-001). Check mcp_errors.log for details."
Example output:
Chat ID: 123456789, Title: John Doe
Chat ID: -100987654321, Title: My Project Group
Chat ID: 111223344, Title: Jane Smith
Chat ID: -200123456789, Title: News Channel
@mcp.tool()
async def send_message(chat_id: int, message: str) -> str:
"""
Send a message to a specific chat.
Args:
chat_id: The ID of the chat.
message: The message content to send.
"""
try:
entity = await client.get_entity(chat_id)
await client.send_message(entity, message)
return "Message sent successfully."
except Exception as e:
logger.exception(f"send_message failed (chat_id={chat_id})")
return "An error occurred (code: SENDMSG-ERR-001). Check mcp_errors.log for details."
Example output:
Message sent successfully.
The get_invite_link
function is particularly robust with multiple fallback methods:
@mcp.tool()
async def get_invite_link(chat_id: int) -> str:
"""
Get the invite link for a group or channel.
"""
try:
entity = await client.get_entity(chat_id)
# Try using ExportChatInviteRequest first
try:
from telethon.tl import functions
result = await client(functions.messages.ExportChatInviteRequest(
peer=entity
))
return result.link
except AttributeError:
# If the function doesn't exist in the current Telethon version
logger.warning("ExportChatInviteRequest not available, using alternative method")
except Exception as e1:
# If that fails, log and try alternative approach
logger.warning(f"ExportChatInviteRequest failed: {e1}")
# Alternative approach using client.export_chat_invite_link
try:
invite_link = await client.export_chat_invite_link(entity)
return invite_link
except Exception as e2:
logger.warning(f"export_chat_invite_link failed: {e2}")
# Last resort: Try directly fetching chat info
try:
if isinstance(entity, (Chat, Channel)):
full_chat = await client(functions.messages.GetFullChatRequest(
chat_id=entity.id
))
if hasattr(full_chat, 'full_chat') and hasattr(full_chat.full_chat, 'invite_link'):
return full_chat.full_chat.invite_link or "No invite link available."
except Exception as e3:
logger.warning(f"GetFullChatRequest failed: {e3}")
return "Could not retrieve invite link for this chat."
except Exception as e:
logger.exception(f"get_invite_link failed (chat_id={chat_id})")
return f"Error getting invite link: {e}"
Example output:
https://t.me/+AbCdEfGhIjKlMnOp
@mcp.tool()
async def join_chat_by_link(link: str) -> str:
"""
Join a chat by invite link.
"""
try:
# Extract the hash from the invite link
if '/' in link:
hash_part = link.split('/')[-1]
if hash_part.startswith('+'):
hash_part = hash_part[1:] # Remove the '+' if present
else:
hash_part = link
# Try checking the invite before joining
try:
# Try to check invite info first (will often fail if not a member)
invite_info = await client(functions.messages.CheckChatInviteRequest(hash=hash_part))
if hasattr(invite_info, 'chat') and invite_info.chat:
# If we got chat info, we're already a member
chat_title = getattr(invite_info.chat, 'title', 'Unknown Chat')
return f"You are already a member of this chat: {chat_title}"
except Exception:
# This often fails if not a member - just continue
pass
# Join the chat using the hash
result = await client(functions.messages.ImportChatInviteRequest(hash=hash_part))
if result and hasattr(result, 'chats') and result.chats:
chat_title = getattr(result.chats[0], 'title', 'Unknown Chat')
return f"Successfully joined chat: {chat_title}"
return f"Joined chat via invite hash."
except Exception as e:
err_str = str(e).lower()
if "expired" in err_str:
return "The invite hash has expired and is no longer valid."
elif "invalid" in err_str:
return "The invite hash is invalid or malformed."
elif "already" in err_str and "participant" in err_str:
return "You are already a member of this chat."
logger.exception(f"join_chat_by_link failed (link={link})")
return f"Error joining chat: {e}"
Example output:
Successfully joined chat: Developer Community
@mcp.tool()
async def search_public_chats(query: str) -> str:
"""
Search for public chats, channels, or bots by username or title.
"""
try:
result = await client(functions.contacts.SearchRequest(q=query, limit=20))
return json.dumps([format_entity(u) for u in result.users], indent=2)
except Exception as e:
return f"Error searching public chats: {e}"
Example output:
[
{
"id": 123456789,
"name": "TelegramBot",
"type": "user",
"username": "telegram_bot"
},
{
"id": 987654321,
"name": "Telegram News",
"type": "user",
"username": "telegram_news"
}
]
@mcp.tool()
async def get_direct_chat_by_contact(contact_query: str) -> str:
"""
Find a direct chat with a specific contact by name, username, or phone.
Args:
contact_query: Name, username, or phone number to search for.
"""
try:
# Fetch all contacts using the correct Telethon method
result = await client(functions.contacts.GetContactsRequest(hash=0))
contacts = result.users
found_contacts = []
for contact in contacts:
if not contact:
continue
name = f"{getattr(contact, 'first_name', '')} {getattr(contact, 'last_name', '')}".strip()
username = getattr(contact, 'username', '')
phone = getattr(contact, 'phone', '')
if (contact_query.lower() in name.lower() or
(username and contact_query.lower() in username.lower()) or
(phone and contact_query in phone)):
found_contacts.append(contact)
if not found_contacts:
return f"No contacts found matching '{contact_query}'."
# If we found contacts, look for direct chats with them
results = []
dialogs = await client.get_dialogs()
for contact in found_contacts:
contact_name = f"{getattr(contact, 'first_name', '')} {getattr(contact, 'last_name', '')}".strip()
for dialog in dialogs:
if isinstance(dialog.entity, User) and dialog.entity.id == contact.id:
chat_info = f"Chat ID: {dialog.entity.id}, Contact: {contact_name}"
if getattr(contact, 'username', ''):
chat_info += f", Username: @{contact.username}"
if dialog.unread_count:
chat_info += f", Unread: {dialog.unread_count}"
results.append(chat_info)
break
if not results:
return f"Found contacts matching '{contact_query}', but no direct chats with them."
return "\n".join(results)
except Exception as e:
return f"Error searching for direct chat: {e}"
Example output:
Chat ID: 123456789, Contact: John Smith, Username: @johnsmith, Unread: 3
You can use these tools via natural language in Claude, Cursor, or any MCP-compatible client.
This implementation includes comprehensive error handling:
mcp_errors.log
The code is designed to be robust against common Telegram API issues and limitations.
git clone https://github.com/<your-github-username>/telegram-mcp.git
git checkout -b my-feature
.env
or session string..env.example
as a template and keep your actual .env
file private..gitignore
.mcp_errors.log
..venv
is created and selected..env
for valid test accounts/groups.This project is licensed under the Apache 2.0 License.
Maintained by @chigwell and @l1v0n1. PRs welcome!
Please log in to share your review and rating for this MCP.
Explore related MCPs that share similar capabilities and solve comparable challenges
by lharries
Enables searching, reading, and sending personal WhatsApp messages and media through a Model Context Protocol (MCP) server, storing all data locally in SQLite and exposing controlled tools for LLMs like Claude.
by iFurySt
Provides authenticated access to XiaoHongShu (RedNote) notes, supporting keyword search, note retrieval by URL, and cookie persistence via a Model Context Protocol server.
by korotovsky
Provides a powerful Model Context Protocol interface for Slack workspaces, enabling message retrieval, search, and optional posting via Stdio or SSE transports without requiring bot permissions.
by ZubeidHendricks
Provides a standardized interface for interacting with YouTube content, enabling video retrieval, transcript access, channel and playlist management, and advanced analytics through the Model Context Protocol.
by InditexTech
Provides Microsoft Teams integration via the Model Context Protocol, enabling reading, creating, replying to messages and mentioning members.
by EnesCinr
Interact with Twitter to post tweets and search tweets programmatically via an MCP server.
by chaindead
Manages Telegram dialogs, messages, drafts, and read statuses via the Model Context Protocol, enabling AI assistants to query and interact with Telegram accounts.
by carterlasalle
Securely interfaces with the macOS Messages database via MCP, enabling LLMs to query, analyze, filter, and send iMessage or SMS communications with built‑in phone number validation, attachment handling, and group chat support.
by v-3
Enables large language models to interact with Discord channels, allowing them to send and read messages through Discord's API while keeping user control and security.