Telegram MCP 服务器

一个功能全面的Telegram集成工具,通过Model Context Protocol (MCP)为Claude、Cursor等客户端提供自动化消息发送、群组管理等功能。

  • by 14kear
  • Python
  • 本地部署
  • 1 星标
  • 更新于
在 GitHub 查看 14kear/telegram_mcp

使用说明


🤖 MCP在行动

以下是Telegram MCP功能的演示 克劳德:

基本用法示例:

  1. 示例:让Claude分析聊天记录并发送响应:
  1. 已成功向组发送消息:

如您所见,AI可以与您的Telegram帐户无缝交互,以自然的方式检索和显示您的聊天记录、消息和其他数据。


为Claude、Cursor和任何兼容MCP的客户端提供功能齐全的Telegram集成,由 电话马拉松 和那个 模型上下文协议(MCP)。此项目允许您以编程方式与您的Telegram帐户交互,自动化从消息传递到组管理的一切。

此叉子还包括:

  • MCP服务器和会话生成器的Python 3.14安全启动路径
  • 单独的 telegram-codex-bridge 电报流程->食品法典和电报->外壳控制
  • repo本地启动器: run_telegram_mcp.py 和 run_telegram_codex_bridge.py

对于从克隆到工作设置的干净的首次引导,请从以下内容开始 SETUP_BOSS.md.桥梁的具体说明见 TELEGRAM_CODEX_BRIDGE.md.


🚀 功能和工具

这个MCP服务器公开了一套庞大的Telegram工具。 Telegram/Telethon的每个主要功能都可以作为工具使用!

聊天和群管理

  • get_chats(页面,页面大小):分页聊天列表
  • list_chats(chat_type,限制):列出带有元数据和过滤的聊天记录
  • get_chat(chat_id):聊天的详细信息
  • create_group(标题、用户ID):创建新组
  • invite_togroup(group_id,user_id):邀请用户加入群组或频道
  • create_channel(标题、关于、超级组):创建频道或超群
  • edit_chat_title(聊天id,标题):更改聊天/群组/频道标题
  • 删除chat_photo(chat_id):删除聊天/群组/频道照片
  • leave_chat(chat_id):离开群组或频道
  • get_参与者(chat_id):列出所有参与者
  • get_admins(chat_id):列出所有管理员
  • get_banned_users(chat_id):列出所有被禁止的用户
  • 升级管理员(聊天ID、用户ID):将用户提升为管理员
  • 降级管理员(聊天ID、用户ID):将管理员降级为用户
  • ban_user(聊天id、用户id):禁止用户
  • unban_user(chat_id,user_id):取消阻止用户
  • get_invite_link(chat_id):获取邀请链接
  • export_chat_invite(chat_id):导出邀请链接
  • import_chat_invite(哈希):通过邀请哈希加入聊天
  • join_chat_by_link(链接):通过邀请链接加入聊天
  • subscribe_public_channel(频道):通过用户名或ID订阅公共频道或超级群组

消息传递

  • 获取消息(chat_id、page、page_size):分页消息
  • list_message(chat_id、限制、搜索查询、从日期到日期):筛选邮件
  • list_targets(chat_id、limit、offset_topic、search_query):在超级群组中列出论坛主题
  • send_message(chat_id,消息):发送消息
  • reply_to_message(聊天id、消息id、文本):回复消息
  • edit_message(聊天id、消息id、新文本):编辑您的消息
  • 删除消息(聊天ID、消息ID):删除消息
  • 转发消息(从chat_id、message_id到chat_id):转发消息
  • Pin消息(聊天ID、消息ID):固定消息
  • 取消拉各斯消息(聊天ID、消息ID):令人不快的消息
  • mark_as_read(chat_id):将所有内容标记为已读
  • get_message_context(chat_id、message_id、context_size):消息周围的上下文
  • 获取历史记录(chat_id,限制):完整的聊天记录
  • get_pinned_message(chat_id):列出固定邮件
  • get_last_interaction(contact_id):联系人的最新消息
  • create_poll(chat_id、问题、选项、多选、quiz_mode、public_votes、close_date):创建投票
  • list_inline_buttons(聊天id、消息id、限制):检查内联键盘以发现按钮文本/索引
  • 按line_button(聊天id、消息id、按钮文本、按钮索引):按标签或索引触发内联键盘回调
  • send_reaction(聊天id、消息id、表情符号、big=False):在消息中添加反应
  • remove_reaction(chat_id,message_id):从消息中删除反应
  • get_message_reactions(chat_id,message_id,限制=50):获取消息的所有反应

联系人管理

  • list_contacts():列出所有联系人
  • search_contacts(查询):搜索联系人
  • add_contact(电话、名字、姓氏):添加联系人
  • delete_contact(user_id):删除联系人
  • block_user(user_id):阻止用户
  • unblock_user(user_id):解除用户锁定
  • import_contacts(联系人):批量导入联系人
  • export_contacts():将所有联系人导出为JSON
  • get_blocked_users():列出被阻止的用户
  • get_tact_ids():列出所有联系人ID
  • get_direct_chat_by_contact(contact_query):查找与联系人的直接聊天
  • get_contact_chats(contact_id):列出与联系人的所有聊天记录

用户和个人资料

  • get_me():获取您的用户信息
  • update_profile(first_name、last_name、about):更新您的个人资料
  • set_profile_photo(文件路径):从允许的根路径设置个人资料照片
  • delete_profile_photo():删除您的个人资料照片
  • get_user_photos(user_id,限制):获取用户的个人资料照片
  • get_user_status(user_id):获取用户的在线状态

媒体

  • get_media_info(chat_id,message_id):在消息中获取媒体信息
  • send_file(聊天id、文件路径、标题):从允许的根发送本地文件
  • download_media(聊天id、消息id、文件路径):在允许的根目录下保存消息媒体
  • upload_file(文件路径):上传本地文件并返回上传元数据
  • send_voice(chat_id,file_path):发送 .ogg/.opus 来自允许根的语音注释
  • send_stricker(chat_id,file_path):发送 .webp 从允许的根部粘贴
  • edit_chat_photo(聊天id,文件路径):从允许的根更新聊天照片

搜索与发现

  • search_public_chats(查询,限制):使用可配置的结果限制搜索公共聊天/频道/机器人
  • search_message(chat_id、query、limit):搜索聊天中的消息
  • search_global(查询、页面、页面大小):使用分页在全局范围内搜索邮件
  • resolve_username(用户名):将用户名解析为ID

贴纸、GIF、机器人

  • get_sticker_sets():列出贴纸集
  • get_bot_info(bot_username):获取有关机器人的信息
  • set_bot_command(bot_username,命令):设置bot命令(仅限bot帐户)

隐私、设置和其他

  • get_private_settings():获取隐私设置
  • set_privacy_settings(key、allow_users、disallow_user):设置隐私设置
  • mute_chat(chat_id):将通知静音
  • 取消聊天静音(聊天ID):取消通知静音
  • archive_chat(chat_id):存档聊天记录
  • 无存档聊天(聊天ID):存档聊天记录
  • get_recent_actions(chat_id):获取最近的管理员操作

草稿

  • 保存草稿(聊天id、消息、回复_msg_id、无网页):将草稿消息保存到聊天室/频道
  • get_drafts():获取所有聊天记录中的所有草稿消息
  • clear_draft(chat_id):从特定聊天中清除/删除草稿

输入验证

为了提高鲁棒性,所有功能都接受 chat_id 或 user_id 参数现在包括输入验证。您可以对这些ID使用以下任何格式:

  • 整数ID:用户、聊天或频道的直接整数ID(例如。, 123456789 或 -1001234567890).
  • 字符串ID:以字符串形式提供的整数ID(例如。, "123456789").
  • 用户名:用户或频道的公共用户名(例如。, "@username" 或 "username").

服务器将自动验证输入并将其转换为正确的格式,然后再向Telegram发出请求。如果输入无效,将返回一条明确的错误消息。

文件路径工具安全模型

文件路径工具可用,但 默认禁用 直到配置了允许的根。

支持的文件路径工具:

  • send_file, download_media, set_profile_photo, edit_chat_photo, send_voice, send_sticker, upload_file

安全语义(与MCP文件系统服务器一致):

  • 通过CLI位置参数的服务器端allowlist(当不支持Roots API时回退)。
  • 客户端提供的MCP根在可用时替换服务器分配列表。
  • 如果客户端返回空的根列表,则文件路径工具将被禁用(全部拒绝)。
  • 所有路径都通过realpath解析,并且必须保持在允许的根内。
  • 横向/球状图案被拒绝(.., *, ?, ~等等)。
  • 相对路径根据第一个允许的根进行解析。
  • 写入工具默认为 <first_root>/downloads/ 当 file_path 省略。

使用已分配根目录启动服务器的示例:

uv --directory /full/path/to/telegram-mcp run main.py /data/telegram /tmp/telegram-mcp

GIF工具目前有限: get_gif_search 和 send_gif 可用,同时 get_saved_gifs 由于Telethon/Telegram API交互中的可靠性限制而未实现。


📋 需求


🔧 安装和设置

1.分叉和克隆

git clone https://github.com/14kear/telegram_mcp.git
cd telegram_mcp

2.使用uv安装依赖项

uv sync

3.生成会话字符串

uv run session_string_generator.py

按照提示进行身份验证并更新您的 .env 文件。

4.配置.env

复制 .env.example 到 .env 并填写您的值:

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

在获取API证书 my.telegram.org/apps.


🐳 使用Docker运行

如果你安装了Docker和Docker Compose,你可以在容器中构建和运行服务器,简化依赖关系管理。

1.塑造形象

从项目根目录构建Docker镜像:

docker build -t telegram-mcp:latest .

2.运行容器

您有两个选择:

选项A:使用Docker Compose(建议本地使用)

此方法使用 docker-compose.yml 文件并自动从中读取您的凭据 .env 文件。

  1. 创建 .env 文件: 确保你有一个 .env 项目根目录中包含您的 TELEGRAM_API_ID, TELEGRAM_API_HASH,以及 TELEGRAM_SESSION_STRING (或 TELEGRAM_SESSION_NAME).使用 .env.example 作为模板。
  2. 运行作曲:
    docker compose up --build
    
    • 使用 docker compose up -d 以分离模式(后台)运行。
    • 按 Ctrl+C 停止服务器。

选项B:使用 docker run

您可以直接运行容器,将凭据作为环境变量传递。

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 而不是 TELEGRAM_SESSION_STRING 如果您更喜欢基于文件的会话(需要卷装载,请参阅 docker-compose.yml 例如)。
  • 这 -it 标志对于与服务器交互至关重要。

⚙️ Claude和Cursor的配置

MCP配置

编辑您的Claude桌面配置(例如。 ~/Library/Application Support/Claude/claude_desktop_config.json)或光标配置(~/.cursor/mcp.json):

{
  "mcpServers": {
    "telegram-mcp": {
      "command": "uv",
      "args": [
        "--directory",
        "/full/path/to/telegram-mcp",
        "run",
        "python",
        "run_telegram_mcp.py"
      ]
    }
  }
}

📝 带有代码和输出的工具示例

下面是最常用的工具及其实现和示例输出的示例。

获取您的聊天记录

@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."

输出示例:

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."

输出示例:

Message sent successfully.

列出内联按钮

@mcp.tool()
async def list_inline_buttons(
    chat_id: Union[int, str],
    message_id: Optional[int] = None,
    limit: int = 20,
) -> str:
    """
    Discover inline keyboard layout, including button indices, callback availability, and URLs.
    """

示例用法:

list_inline_buttons(chat_id="@sample_tasks_bot")

这将返回类似以下内容:

Buttons for message 42 (date 2025-01-01 12:00:00+00:00):
[0] text='📋 View tasks', callback=yes
[1] text='ℹ️ Help', callback=yes
[2] text='🌐 Visit site', callback=no, url=https://example.org

按下内联按钮

@mcp.tool()
async def press_inline_button(
    chat_id: Union[int, str],
    message_id: Optional[int] = None,
    button_text: Optional[str] = None,
    button_index: Optional[int] = None,
) -> str:
    """
    Press an inline keyboard button by label or zero-based index.
    If message_id is omitted, the server searches recent messages for the latest inline keyboard.
    """

示例用法:

press_inline_button(chat_id="@sample_tasks_bot", button_text="📋 View tasks")

使用 list_inline_buttons 首先,如果你需要检查可用的按钮——传递一个假按钮 button_text 快速列出选项或致电 list_inline_buttons 直接。一旦你知道了文本或索引, press_inline_button 发送回叫,就像在本地Telegram客户端中点击按钮一样。

订阅公共频道

@mcp.tool()
async def subscribe_public_channel(channel: Union[int, str]) -> str:
    """
    Join a public channel or supergroup by username (e.g., "@examplechannel") or ID.
    """

示例用法:

subscribe_public_channel(channel="@daily_updates_feed")

如果该帐户已经是参与者,该工具会报告而不是失败,从而确保 在需要幂等连接的工作流中重复运行。

获取聊天邀请链接

这 get_invite_link 函数在多种回退方法下特别稳健:

@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}"

输出示例:

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}"

输出示例:

Successfully joined chat: Developer Community

搜索公共聊天

@mcp.tool()
async def search_public_chats(query: str, limit: int = 20) -> str:
    """
    Search for public chats, channels, or bots by username or title.
    """
    try:
        result = await client(functions.contacts.SearchRequest(q=query, limit=limit))
        entities = [format_entity(e) for e in result.chats + result.users]
        return json.dumps(entities, indent=2)
    except Exception as e:
        return f"Error searching public chats: {e}"

输出示例:

[
  {
    "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}"

输出示例:

Chat ID: 123456789, Contact: John Smith, Username: @johnsmith, Unread: 3

🎮 用法示例

  • “显示我最近的聊天记录”
  • “发送‘Hello world’到聊天室123456789”
  • “添加联系人,电话+1234567890,姓名John Doe”
  • 创建一个包含用户111、222、333的组“项目团队”
  • “从聊天123456789中的消息42下载媒体”
  • “将聊天通知静音123456789”
  • “将用户111提升为组123456789中的管理员”
  • “搜索有关‘新闻’的公共频道”
  • “使用邀请链接加入Telegram群https://t.me/+AbCdEfGhIjK”
  • “向我保存的邮件发送贴纸”
  • “获取我所有的贴纸套装”

您可以通过Claude、Cursor或任何兼容MCP的客户端中的自然语言使用这些工具。


🧠 错误处理和稳健性

此实现包括全面的错误处理:

  • 会话管理:适用于基于文件和基于字符串的会话
  • 错误报告:详细错误记录到 mcp_errors.log
  • 优雅降级:关键功能的多种回退方法
  • 用户友好的消息:清晰、可操作的错误消息,而不是技术错误
  • 帐户类型检测:当与用户帐户一起使用时,需要机器人帐户检测和通知的功能
  • 邀请链接处理:处理各种链接格式和已成为成员的案例

该代码旨在针对常见的Telegram API问题和限制进行稳健设计。


🛠️ 贡献指南

  1. 分叉此仓库: 14kear/电报_mcp
  2. 克隆你的叉子:
    git clone https://github.com/<your-github-username>/telegram_mcp.git
    
  3. 创建新分支:
    git checkout -b my-feature
    
  4. 进行更改,必要时添加测试/文档。
  5. 推送并打开拉取请求 到 14kear/电报_mcp 描述清晰。
  6. 标签@chigwell或@l1v0n1 在您的PR中进行审查。

🔒 安全考虑

  • 永远不要承诺你的 .env 或会话字符串。
  • 会话字符串可以完全访问您的Telegram帐户——请确保安全!
  • 所有处理都是本地的;除了Telegram的API之外,没有任何数据被发送到任何地方。
  • 使用 .env.example 作为模板,保留您的实际 .env 文件私有。
  • 测试文件将自动排除在 .gitignore.

🛠️ 故障排除

  • 检查日志 在您的MCP客户端(Claude/Cursor)和终端中检查错误。
  • 详细的错误日志 可以在以下网址找到 mcp_errors.log.
  • 口译员错误? 确保你的 .venv 创建并选择。
  • 数据库锁定? 使用会话字符串身份验证,而不是基于文件的会话。
  • iCloud/Dropbox问题? 如果看到奇怪的错误,请将项目移动到没有空格的本地路径。
  • 重新生成会话字符串 如果您更改了Telegram密码或看到身份验证错误。
  • 仅机器人功能 当与常规用户帐户一起使用时,将显示清晰的消息。
  • 测试脚本失败? 检查中的测试配置 .env 对于有效的测试帐户/组。

📄 许可证

该项目根据 Apache 2.0许可证.


🙏 致谢


Fork在此存储库中维护。原始上游信用仍保留在git历史和源元数据中。

星迹