telegram-mcp-server
# telegram-mcp-server
MCP-Server für Telegram über einen **User-Account** (MTProto/Telethon).
Liest Chats/Nachrichten/Medien und sendet Nachrichten/Dateien. Läuft lokal über stdio.
## Warum User-Account statt Bot?
Ein Bot kann keine Gruppen-*Historie* und keine vor seinem Beitritt geposteten
Medien lesen (und hat ein 20-MB-Download-Limit). Für „hol mir die Bilder aus der
Gruppe" braucht es einen User-Client. Session-Datei = voller Account-Zugriff →
lokal halten, `chmod 600`, nie committen.
## Setup
1. API-Credentials auf https://my.telegram.org (API development tools) erzeugen.
2. Repo installieren (venv + editierbar; Python ≥3.10):
```bash
python3 -m venv .venv
.venv/bin/pip install -e .
```
3. Environment setzen:
```bash
export TELEGRAM_API_ID=...
export TELEGRAM_API_HASH=...
export TELEGRAM_PHONE=+49... # nur für den Login nötig
# optional:
export TELEGRAM_SESSION="$HOME/.telegram-mcp/telegram.session"
export TELEGRAM_DOWNLOAD_DIR="$HOME/Downloads/telegram-mcp"
```
4. Einmalig einloggen (erzeugt die Session-Datei, fragt Code + ggf. 2FA):
```bash
.venv/bin/python -m telegram_mcp_server.login
```
## In Claude Code registrieren
```bash
claude mcp add telegram \
-e TELEGRAM_API_ID=... \
-e TELEGRAM_API_HASH=... \
-e TELEGRAM_SESSION="$HOME/.telegram-mcp/telegram.session" \
-- /ABSOLUTER/PFAD/telegram-mcp-server/.venv/bin/telegram-mcp-server
```
Die `-e`-Flags sind nötig, weil ein von Claude Code gestarteter MCP-Server die
Shell-`export`s aus dem Setup NICHT erbt. Alternativ die `env`-Sektion in der
MCP-Konfiguration nutzen. `TELEGRAM_DOWNLOAD_DIR` optional per weiterem `-e` setzen.
Nach dem Registrieren eine neue Claude-Code-Session starten, damit der Server erscheint.
## Tools
| Tool | Zweck |
|---|---|
| `get_me` | Whoami / Session-Check |
| `list_chats(query?, limit)` | Chats auflisten (Gruppe/Kanal/DM) |
| `get_messages(chat, limit, before_id?, search?)` | Nachrichten eines Chats lesen |
| `search_messages(query, chat?, limit)` | Nachrichten suchen (global oder im Chat) |
| `download_media(chat, message_ids[], dest_dir?)` | Medien gezielter Nachrichten laden |
| `get_recent_media(chat, limit, kind?, dest_dir?)` | Letzte N Medien eines Chats laden |
| `send_message(chat, text)` | Textnachricht senden |
| `send_file(chat, file_path, caption?)` | Datei/Bild senden |
`chat` akzeptiert Chat-id, `@username` oder (Teil-)Titel; bei mehrdeutigem Titel
kommt ein Fehler mit den Treffern statt einer Rate-Auflösung.
`get_messages`/`search_messages` liefern pro Nachricht auch `chat_id` mit —
Treffer aus der globalen Suche sind damit direkt in weiteren Tool-Aufrufen
(z.B. `download_media`) verwendbar.
## Mehrere gleichzeitige Sessions
Jeder MCP-Client startet einen **eigenen Serverprozess**. Alle zeigen auf
dieselbe Session-Datei, und Telethon legt darin veraenderlichen Zustand ab —
vor allem `update_state`. `SQLiteSession.set_update_state` schreibt mit
`insert or replace`, also ein **blindes Ueberschreiben ohne Vergleich**.
Gemessen am 14.09.2026 an einer Kopie einer echten Session: Zwei Schreiber
verwerfen gegenseitig ihren Stand — **4997 Update-Positionen still verloren,
kein Fehler**. Dazu nutzt die Datei `journal_mode=delete` (kein WAL), ein
Schreiber sperrt also die ganze Datei; ein konkurrierender Schreibvorgang
scheitert nach dem 5-s-Standardtimeout mit `database is locked`.
**Deshalb liest der Server aus einer prozesseigenen Kopie und schreibt nie
zurueck** (`_private_session_copy`). Kopiert wird ueber SQLites Backup-API,
nicht per Dateikopie: Ein anderer Prozess kann mitten im Schreiben sein, und
eine einfache Kopie kann eine halb geschriebene Seite erwischen.
Was das kostet: Der Entitaeten-Cache waechst nicht mehr ueber Prozesse hinweg,
es gibt also etwas mehr Aufloese-Aufrufe. Fuer Lesen und Senden wird nur der
`auth_key` gebraucht; `update_state` und der Cache sind Beschleuniger.
`login.py` ist davon nicht betroffen und schreibt weiterhin die echte Datei.
⚠️ Der naheliegende Fix `receive_updates=False` traegt **nicht**: Er schaltet
nur den Zufluss ab. Der `set_update_state`-Aufruf in Telethons
`telegrambaseclient.py` laeuft beim **Disconnect** und ist nicht von
`_no_updates` geschuetzt — geschrieben wird trotzdem.
## Entwicklung / Tests
```bash
python3 -m venv .venv && .venv/bin/pip install -e ".[test]"
.venv/bin/python -m pytest tests/ -v
```
Tests laufen ohne Live-Account (Client wird gemockt).
## Smoke-Test (einmalig nach Login, mit echtem Account)
```bash
.venv/bin/python -m telegram_mcp_server.login # 1) Session erzeugen
# 2) get_me / list_chats / get_messages / download_media manuell über den
# MCP-Client (Claude Code) prüfen:
# - get_me -> zeigt deinen Account
# - list_chats query="<gruppe>" -> findet eine deiner Gruppen
# - get_messages chat=<id> limit=5 -> liefert Nachrichten
# - search_messages query="..." -> globale Suche (übt _search_global live aus;
# Treffer müssen chat_id tragen)
# - get_recent_media chat=<id> kind="photo" -> lädt Fotos nach $TELEGRAM_DOWNLOAD_DIR
```
Erster echter Integrationstest = ein Medienabruf aus einer deiner Gruppen. Die globale `search_messages`
(ohne `chat`) ist der einzige Pfad, der `_search_global`/`SearchGlobalRequest` real ausführt —
im Smoke-Test nicht überspringen.
## Maintainer
Schimmi — https://schimmilab.de
Issues und Pull Requests willkommen.
TDQS
Scored across 8 tools
Tools mostly have distinct purposes. download_media and get_recent_media both handle media downloads but are differentiated by targeted vs. bulk recent retrieval. All other tools are clearly distinct.
All tool names follow a consistent verb_noun pattern in snake_case (e.g., download_media, list_chats, send_message), making the set predictable and easy to navigate.
8 tools is well-scoped for a Telegram messaging interface, covering essential operations like reading messages, searching, listing chats, sending messages/files, and downloading media without being overwhelming.
Core functionality is covered: read, search, send, download. Minor gaps such as no delete, edit, or forward tools, but these are optional for many use cases and the set feels complete for basic messaging automation.