Slim Telegram MCP server with skill-based lazy loading, encrypted sessions, and prompt-injection defense
Project description
slim-tg-mcp
A slim, security-hardened Telegram MCP server for Claude Code and any MCP-compatible client.
一个精简、安全加固的 Telegram MCP 服务器,专为 Claude Code 与任何兼容 MCP 的客户端设计。
English
Why slim-tg-mcp?
The popular chigwell/telegram-mcp exposes 73 tools — that's roughly
11 000 tokens permanently consumed in every conversation. This project
keeps only 8 search/read tools as MCP and ships every write/admin
operation as a lazy-load Skill. Same capabilities (now with feature
parity at v0.4.0 across 11 Skills), ~87 % less context overhead,
and a security model designed from day one to survive prompt injection.
Architecture at a glance
┌───────────────────────┐
│ Claude Code / Desktop │
└───────────┬───────────┘
│
┌────────┴─────────┐
▼ ▼
[Slim MCP — 8 tools] [Skills — 11 lazy-loaded]
search/read messaging / group-admin (13 ops)
download contacts / media-upload
resolve polls (incl. edit) / scheduling (incl. edit)
profile (incl. 2FA) / export
privacy / folders / stickers-gifs
│ │
└─────────┬────────┘
▼
[TG Daemon (Unix socket)]
├─ multi-account session pool
├─ AES-GCM session storage + Keychain
├─ prompt-injection sanitizer
├─ flock-based singleton + audit log
└─ Telethon (MTProto)
▼
Telegram
What's in the box (v0.4.0)
Always-loaded MCP tools (~1.5 k tokens):
| Tool | Purpose |
|---|---|
tg_search_global |
Search keyword across all chats |
tg_search_in_chat |
Search a specific chat (sender / date filters) |
tg_list_dialogs |
Discover chat IDs |
tg_get_messages |
Read recent messages |
tg_get_message_context |
N before/after a hit |
tg_resolve_entity |
username / link → marked peer ID |
tg_chat_info |
Title, kind, member count |
tg_download_media |
Download media into the daemon's safe dir |
Lazy-loaded Skills (described, not preloaded):
tg-messaging— send / edit / delete / forward / pin / react / mark-readtg-group-admin— create groups, add/kick/ban, invites, rename, leave; channel admin (participants, signatures, slow-mode, discussion, admin-log)tg-contacts— add (E.164 enforced), delete, block, unblock, searchtg-media-upload— send local files (photo / video / document / voice)tg-polls— create / close / inspect / edit (anonymous, public, multiple, quiz)tg-scheduling— schedule / list / cancel / edit + draft save/get/cleartg-profile— update name / bio / username / photo / status / 2FA (set / change / remove)tg-export— export chat history to local disk as JSON + mediatg-privacy— read / change all 10 privacy keys with the full 6-rule grammartg-folders— list / create-or-update / delete chat folders (dialog filters)tg-stickers-gifs— saved-GIF list+send, sticker-pack list / resolve / send
Security highlights
For the full threat model see docs/security.md.
Headline defenses:
- Encrypted sessions — AES-GCM at rest, data key in OS Keychain (macOS Keychain / libsecret / Windows DPAPI), with scrypt-based passphrase fallback when the keychain is unavailable.
- Prompt-injection defense — every Telegram message is sanitized
(zero-width chars stripped, common injection markers neutralized) and
wrapped in
<tg_msg trust="high|medium|low">tags with provenance. Forwards are automatically downgraded so an attacker can't reach high-trust by getting the user to forward their message. - TOCTOU-resistant file ops — all caller-supplied paths
(
tg-media-upload,tg-export,tg-profile photo) go throughlstat → O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC open → fstat (st_dev, st_ino) match. Telethon receives the fd, never the path. Export usesdir_fd-relative ops the whole way down so the export root is never re-resolved by name. - Singleton daemon —
fcntl.flockis the authoritative liveness signal (kernel auto-releases on process exit). Pid-based shutdown is replaced with a daemon-side/shutdownRPC bound to a per-processinstance_id; SIGTERM is only a fallback for transport failure. - Path safety — runtime artifacts (socket, lock, pid) live in
$XDG_RUNTIME_DIR(validated for ownership / 0700) or/tmp/tgmcp-<uid>. Persistent paths come frompwd.getpwuid(getuid()), ignoring$HOME. - Uniform 400 schema surface — every Pydantic / model-validator
failure returns
400 {"error":"ValidationError", ...}, replacing FastAPI's mixed 400/422. Single status code for CLI / Skill /DaemonClientconsumers to branch on. - Audit log — every write operation appends to
~/.config/tgmcp/audit.log. Sensitive content (passphrases, full paths, message bodies, phone numbers, 2FA passwords, privacy user-id allowlists) is never logged.
Install
From PyPI (recommended):
pip install slim-tg-mcp
Or from source (for hacking on it):
git clone https://github.com/haoyu-haoyu/slim-tg-mcp.git
cd slim-tg-mcp
pip install -e .
First-time setup
# 1. Get api_id / api_hash from https://my.telegram.org
export TG_API_ID=123456
export TG_API_HASH=abcdef0123456789...
# 2. Login (interactive — Telegram will SMS a code)
tgmcp init --label main
# 3. Start the daemon
tgmcp daemon start --account main
tgmcp daemon status
Wire up Claude Code
~/.claude.json (or per-project .mcp.json):
{
"mcpServers": {
"telegram": {
"command": "tgmcp-mcp"
}
}
}
~/.claude/settings.json:
{
"skillsDirs": [
"/absolute/path/to/slim-tg-mcp/skills"
]
}
CLI cheat sheet
tgmcp init [--label X] [--passphrase] # First-time login
tgmcp account list # Saved accounts (* = active)
tgmcp account add <label> # Add another account
tgmcp account use <label> [--passphrase] # Switch the running daemon
tgmcp account remove <label>
tgmcp daemon start [--account X] [--passphrase] [--foreground]
tgmcp daemon status
tgmcp daemon stop
Multi-account
Sessions are loaded lazily on first switch and cached server-side, so
subsequent switches are instant. The passphrase for --passphrase
accounts is read via hidden prompt or --passphrase-stdin — never on
argv. Per-label asyncio.Lock serializes concurrent switches against the
same cold label so two parallel callers can never end up with two live
TGSession objects pointing at the same account.
Real-account end-to-end test
TGMCP_E2E_CONFIRM=yes python scripts/e2e_smoke.py
This walks daemon-up → list → send to Saved Messages → search → edit →
delete (verified via post-delete search) → daemon-down. It refuses to run
without the explicit TGMCP_E2E_CONFIRM=yes so you don't fire it by
accident; use a burner account, not your main.
Development
pip install -e ".[dev]"
pytest tests/ # 365 unit tests at v0.4.0
ruff check src/ tests/
CI runs lint and the full suite on Python 3.10 / 3.11 / 3.12 on every
push to main and on every PR.
Project layout
slim-tg-mcp/
├── src/tgmcp/
│ ├── client.py # Daemon Unix-socket client
│ ├── daemon/
│ │ ├── server.py # FastAPI on Unix socket
│ │ ├── telegram.py # Telethon wrapper (TGSession)
│ │ ├── auth.py # Encrypted session storage
│ │ ├── paths.py # Runtime/persistent path resolution
│ │ ├── sanitizer.py # Prompt-injection defense
│ │ └── audit.py # Append-only audit log
│ ├── mcp_server/server.py # 8 MCP tools (stdio)
│ └── cli/main.py # `tgmcp` CLI
├── skills/ # 11 lazy-load Skills
│ ├── tg-messaging/
│ ├── tg-group-admin/
│ ├── tg-contacts/
│ ├── tg-media-upload/
│ ├── tg-polls/
│ ├── tg-scheduling/
│ ├── tg-profile/
│ ├── tg-export/
│ ├── tg-privacy/
│ ├── tg-folders/
│ └── tg-stickers-gifs/
├── docs/
│ └── security.md # Full threat model
├── scripts/
│ ├── smoke.sh # Local sanity check (no real account)
│ └── e2e_smoke.py # Real-account end-to-end
└── tests/ # 365 unit tests at v0.4.0
License
Apache-2.0. The Phase 1 MCP-tool surface was inspired by
chigwell/telegram-mcp (also Apache-2.0); the architecture, security
model, skill split, and implementation are independent.
中文
为什么是 slim-tg-mcp?
社区里热门的 chigwell/telegram-mcp 暴露 73 个工具——也就是说每次
对话都会常驻消耗约 11 000 token。本项目只把 8 个搜索/读取工具做
成 MCP,把所有写入/管理操作做成 按需加载的 Skill。v0.4.0 起共 11
个 Skill,能力与 chigwell 全面对齐;上下文开销减少约 87%,并且从
第一天起就按"防 prompt injection"思路设计。
架构总览
┌───────────────────────┐
│ Claude Code / Desktop │
└───────────┬───────────┘
│
┌────────┴─────────┐
▼ ▼
[Slim MCP - 8 工具] [Skills - 11 个,按需加载]
搜索/读取 messaging / group-admin (13 个子命令)
下载 contacts / media-upload
解析 polls (含 edit) / scheduling (含 edit)
profile (含 2FA) / export
privacy / folders / stickers-gifs
│ │
└─────────┬────────┘
▼
[TG Daemon - Unix socket]
├─ 多账号 session pool
├─ AES-GCM 会话存储 + Keychain
├─ Prompt-injection 清洗器
├─ flock 单例锁 + 审计日志
└─ Telethon (MTProto)
▼
Telegram
功能清单(v0.4.0)
常驻 MCP 工具(约 1.5k tokens):
| 工具 | 用途 |
|---|---|
tg_search_global |
跨所有对话关键字搜索 |
tg_search_in_chat |
单对话内搜索(按 sender / 日期过滤) |
tg_list_dialogs |
发现 chat ID |
tg_get_messages |
读最近消息 |
tg_get_message_context |
命中前后 N 条 |
tg_resolve_entity |
username / 链接 → marked peer ID |
tg_chat_info |
标题、类型、人数 |
tg_download_media |
下载媒体到 daemon 的安全目录 |
按需加载 Skill(仅描述常驻,触发时才载入):
tg-messaging— 发 / 编辑 / 删 / 转发 / 置顶 / 反应 / 已读tg-group-admin— 建群、加/踢/封禁/解除、邀请链接、改名、退群;频道高级管理(成员分页、署名开关、慢速模式、绑定讨论组、admin-log)tg-contacts— 加(强校验 E.164)/ 删 / 屏蔽 / 解除 / 搜索tg-media-upload— 上传本地文件(图片 / 视频 / 文档 / 语音)tg-polls— 创建 / 关闭 / 看结果 / 编辑(匿名、公开、多选、Quiz)tg-scheduling— 定时消息(含编辑)+ 草稿管理tg-profile— 改名字 / 简介 / 用户名 / 头像 / 在线状态 / 2FA(设置 / 修改 / 移除)tg-export— 导出聊天历史为 JSON + 媒体tg-privacy— 读取 / 修改全部 10 个隐私键(6 条规则的完整语法)tg-folders— 列出 / 新增或更新 / 删除 对话文件夹(dialog filters)tg-stickers-gifs— 收藏 GIF list+send,贴纸包 list / 解析 / send
安全特性
完整威胁模型见 docs/security.md,要点如下:
- 会话加密:AES-GCM 落盘,数据密钥存 OS Keychain(macOS Keychain / libsecret / Windows DPAPI),keychain 不可用时降级到 scrypt 加密的 passphrase 模式。
- Prompt-injection 防御:每条 TG 消息都先清洗(剥零宽字符、中性化常
见注入标记),再用
<tg_msg trust="high|medium|low">包裹并附 sender / chat 元数据。转发自动降权——攻击者无法借"让用户帮我转发一下"达 到 high trust。 - TOCTOU 防御:所有调用者传入的文件路径
(
tg-media-upload/tg-export/tg-profile photo)都过lstat → O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC open → fstat (st_dev, st_ino) 对比。Telethon 收到的是 fd,不再二次按名字解析。导出全 程用dir_fd相对操作,导出根目录不会被重新解析。 - 单例 daemon:用
fcntl.flock作为权威活性信号(内核在进程退出时 自动释放)。停止 daemon 不再依赖 SIGTERM;改用 daemon 自带/shutdownRPC 并绑定每进程随机instance_id,SIGTERM 仅作 transport 失败兜底。 - 路径安全:运行时产物(socket / lock / pid)放
$XDG_RUNTIME_DIR(检查 owner / 0700)或/tmp/tgmcp-<uid>。持久路径用pwd.getpwuid(getuid()),忽略$HOME(防注入)。 - 统一 400 校验面:所有 Pydantic / model-validator 失败统一返回
400 {"error":"ValidationError", ...},替代 FastAPI 默认的 400/422 混合。CLI / Skill /DaemonClient一个状态码全搞定。 - 审计日志:每个写操作 append 到
~/.config/tgmcp/audit.log。敏感 内容(passphrase、绝对路径、消息正文、电话全号、2FA 密码材料、隐私规 则的 user_id 白名单)从不明文记录。
安装
PyPI(推荐):
pip install slim-tg-mcp
源码(如果想改代码):
git clone https://github.com/haoyu-haoyu/slim-tg-mcp.git
cd slim-tg-mcp
pip install -e .
首次配置
# 1. 去 https://my.telegram.org 申请 api_id / api_hash
export TG_API_ID=123456
export TG_API_HASH=abcdef0123456789...
# 2. 登录(交互式,Telegram 会发短信验证码)
tgmcp init --label main
# 3. 启动 daemon
tgmcp daemon start --account main
tgmcp daemon status
接到 Claude Code
~/.claude.json(或项目级 .mcp.json):
{
"mcpServers": {
"telegram": {
"command": "tgmcp-mcp"
}
}
}
~/.claude/settings.json:
{
"skillsDirs": [
"/absolute/path/to/slim-tg-mcp/skills"
]
}
CLI 速查表
tgmcp init [--label X] [--passphrase] # 首次登录
tgmcp account list # 列已存账号(*=活跃)
tgmcp account add <label> # 加另一个账号
tgmcp account use <label> [--passphrase] # 切换运行中 daemon 的账号
tgmcp account remove <label>
tgmcp daemon start [--account X] [--passphrase] [--foreground]
tgmcp daemon status
tgmcp daemon stop
多账号
切换时 daemon 才懒加载对应 session 并缓存,后续切换是常数时间。
--passphrase 账号通过隐藏 prompt 或 --passphrase-stdin 读密码——永
不进 argv。每个 label 一把 asyncio.Lock,并发切换不会让同一账号被起
两份 TGSession。
真实账号 e2e 验证
TGMCP_E2E_CONFIRM=yes python scripts/e2e_smoke.py
该脚本:启 daemon → 列对话 → 发到「我的消息」→ 搜回来 → 编辑 → 删除
(删后重搜确认)→ 关 daemon。强制要 TGMCP_E2E_CONFIRM=yes 防误触;
强烈建议用小号,不要用主账号。
开发
pip install -e ".[dev]"
pytest tests/ # v0.4.0 共 365 个单元测试
ruff check src/ tests/
CI 在 main 推送和 PR 时跑 lint + 全测试,覆盖 Python 3.10 / 3.11 / 3.12。
项目结构
slim-tg-mcp/
├── src/tgmcp/
│ ├── client.py # Daemon Unix socket 客户端
│ ├── daemon/
│ │ ├── server.py # FastAPI on Unix socket
│ │ ├── telegram.py # Telethon 包装(TGSession)
│ │ ├── auth.py # 加密会话存储
│ │ ├── paths.py # 运行时/持久路径解析
│ │ ├── sanitizer.py # 注入防御
│ │ └── audit.py # 仅追加审计日志
│ ├── mcp_server/server.py # 8 个 MCP 工具(stdio)
│ └── cli/main.py # `tgmcp` CLI
├── skills/ # 11 个按需加载 Skill
├── docs/
│ └── security.md # 完整威胁模型
├── scripts/
│ ├── smoke.sh # 本地 sanity check(不联网)
│ └── e2e_smoke.py # 真账号端到端
└── tests/ # v0.4.0 共 365 个单元测试
协议
Apache-2.0。Phase 1 的 MCP 工具面(搜索/读取 8 件套)参考了
chigwell/telegram-mcp(同样 Apache-2.0),其余架构、安全模型、Skill 拆
分、所有实现均为独立完成。
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file slim_tg_mcp-0.5.1.tar.gz.
File metadata
- Download URL: slim_tg_mcp-0.5.1.tar.gz
- Upload date:
- Size: 203.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bb8527102f379716d8a5e934d521a18560a86192c5a313fddff15832fd98c51d
|
|
| MD5 |
71b1392bec380af34612bc1ef814b1b5
|
|
| BLAKE2b-256 |
acc66d3badaa0a376775ee0a3ef9213dfe037048b461c272b0a9fabef00c2807
|
File details
Details for the file slim_tg_mcp-0.5.1-py3-none-any.whl.
File metadata
- Download URL: slim_tg_mcp-0.5.1-py3-none-any.whl
- Upload date:
- Size: 83.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
26bbe2ea25f458bc762ad131e506c33947086d22f52799a11846d3b57efe69b2
|
|
| MD5 |
3795e25ba5ef928759a8e14fd02e0b49
|
|
| BLAKE2b-256 |
1b532405b8b5b32006b04a145798b1e2275f17dc86b2f128bccfb802d6b11e21
|