diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..dfdfd9d --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +TELEGRAM_SESSION_NAME=anon +TELEGRAM_API_ID=12345678 +TELEGRAM_API_HASH=12345678123456781234567812345678 \ No newline at end of file diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/README.md b/README.md index 3e03405..59595ff 100644 --- a/README.md +++ b/README.md @@ -1 +1,131 @@ -# telegram-mcp +![](https://badge.mcpx.dev 'MCP') +[![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-green?style=flat-square)](https://opensource.org/licenses/Apache-2.0) +[![LinkedIn](https://img.shields.io/badge/LinkedIn-blue)](https://www.linkedin.com/in/eugene-evstafev-716669181/) + +# Telegram MCP Server + +A Telegram MCP (Model Context Protocol) server built using Python, Telethon, and MCP Python SDK. This MCP server provides simple tools for interacting with Telegram chats directly through MCP-compatible hosts, such as Claude for Desktop. + +## Tools Provided + +- **`get_chats`**: Retrieve a paginated list of your Telegram chats. +- **`get_messages`**: Retrieve paginated messages from a specific chat. +- **`send_message`**: Send a message to a specific chat. + +## Requirements + +- Python 3.10 or higher +- [Telethon](https://docs.telethon.dev/) package +- [MCP Python SDK](https://modelcontextprotocol.io/docs/) +- [UV](https://astral.sh/uv/) (optional but recommended) + +## Installation and Setup + +### Clone the Repository + +```bash +git clone https://github.com/chigwell/telegram-mcp +cd telegram-mcp +``` + +### Create Environment File + +Copy and rename `.env.example` to `.env` and fill it with your Telegram credentials obtained from [https://my.telegram.org/apps](https://my.telegram.org/apps): + +```bash +cp .env.example .env +``` + +Your `.env` file should look like: + +```env +TELEGRAM_API_ID=your_api_id_here +TELEGRAM_API_HASH=your_api_hash_here +TELEGRAM_SESSION_NAME=your_session_name +``` + +### Setup Python Environment + +Use `uv` to set up the Python environment and install dependencies: + +```bash +uv venv +source .venv/bin/activate +uv add "mcp[cli]" telethon python-dotenv nest_asyncio +``` + +### Run the Server (First-time Auth) + +The first time you run the server, Telethon will prompt you to enter a Telegram authentication code: + +```bash +uv run main.py +``` + +Authenticate by entering the code sent to your Telegram client. This step is only required once. + +## Integrating with Claude for Desktop + +### macOS/Linux + +Edit your Claude Desktop configuration: + +```bash +nano ~/Library/Application\ Support/Claude/claude_desktop_config.json +``` + +Add this MCP server configuration: + +```json +{ + "mcpServers": { + "telegram-mcp": { + "command": "uv", + "args": [ + "--directory", + "/ABSOLUTE_PATH/telegram-mcp", + "run", + "main.py" + ] + } + } +} +``` + +Ensure you replace `/ABSOLUTE_PATH/telegram-mcp` with your project's absolute path. + +### Windows + +Edit your Claude Desktop configuration: + +```powershell +nano $env:AppData\Claude\claude_desktop_config.json +``` + +Add this MCP server configuration: + +```json +{ + "mcpServers": { + "telegram-mcp": { + "command": "uv", + "args": [ + "--directory", + "C:\\ABSOLUTE_PATH\\telegram-mcp", + "run", + "main.py" + ] + } + } +} +``` + +Ensure you replace `C:\ABSOLUTE_PATH\telegram-mcp` with your project's absolute path. + +## Usage + +Once integrated, your Telegram tools (`get_chats`, `get_messages`, and `send_message`) will become available within the Claude for Desktop UI or any other MCP-compatible client. + +## License + +This project is licensed under the [Apache 2.0 License](https://opensource.org/licenses/Apache-2.0). \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..6fbc0ab --- /dev/null +++ b/main.py @@ -0,0 +1,100 @@ +import os +from dotenv import load_dotenv +import asyncio +import nest_asyncio +from mcp.server.fastmcp import FastMCP +from telethon import TelegramClient + +load_dotenv() + +TELEGRAM_API_ID = int(os.getenv("TELEGRAM_API_ID")) +TELEGRAM_API_HASH = os.getenv("TELEGRAM_API_HASH") +TELEGRAM_SESSION_NAME = os.getenv("TELEGRAM_SESSION_NAME") + + +mcp = FastMCP("telegram") +client = TelegramClient(TELEGRAM_SESSION_NAME, TELEGRAM_API_ID, TELEGRAM_API_HASH) + + +@mcp.tool() +async def get_chats(page: int, page_size: int = 20) -> str: + """ + Get a paginated list of chats. + + Args: + page: Page number (1-indexed). + page_size: Number of chats per page. + """ + 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: + # For groups or channels, use the title; for users, show first name. + 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) + + +@mcp.tool() +async def get_messages(chat_id: int, page: int, page_size: int = 20) -> str: + """ + Get paginated messages from a specific chat. + + Args: + chat_id: The ID of the chat. + page: Page number (1-indexed). + page_size: Number of messages per page. + """ + try: + entity = await client.get_entity(chat_id) + except Exception as e: + return f"Could not resolve chat with ID {chat_id}: {e}" + + offset = (page - 1) * page_size + messages = await client.get_messages(entity, limit=page_size, add_offset=offset) + if not messages: + return "No messages found for this page." + lines = [] + for msg in messages: + lines.append(f"ID: {msg.id} | Date: {msg.date} | Message: {msg.message}") + return "\n".join(lines) + + +@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) + except Exception as e: + return f"Could not resolve chat with ID {chat_id}: {e}" + + try: + await client.send_message(entity, message) + return "Message sent successfully." + except Exception as e: + return f"Failed to send message: {e}" + + +if __name__ == "__main__": + nest_asyncio.apply() + + async def main() -> None: + # Start the Telethon client. + await client.start() + print("Telegram client started. Running MCP server...") + # Use the asynchronous entrypoint instead of mcp.run() + await mcp.run_stdio_async() + + asyncio.run(main()) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ca85f0d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "telegram-mcp" +version = "2025.3.201549" +description = "A Telegram MCP (Model Context Protocol) server built using Python, Telethon, and MCP Python SDK. This MCP server provides simple tools for interacting with Telegram chats directly through MCP-compatible hosts, such as Claude for Desktop." +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "dotenv>=0.9.9", + "httpx>=0.28.1", + "mcp[cli]>=1.4.1", + "nest-asyncio>=1.6.0", + "telethon>=1.39.0", +]