Ida-Telegram
Ida-Telegram
Ein eigenständiger MCP-Server (Model Context Protocol), getrennt von Ida-Untis und ohne Verbindung dazu. Zwei Dinge in einem Container:
Zwei MCP-Werkzeuge für Claude: einer fest konfigurierten Person eine Telegram-Nachricht schicken (
nachricht_senden), und lesen, was sie gerade geschrieben hat (neue_nachrichten_abrufen) -- Fotos kommen dabei als echter Bildinhalt mit, den die Routine wirklich "sehen" kann.Ein Hintergrund-Loop, der neue Telegram-Nachrichten erkennt (Text, Fotos, Sprachnachrichten) und dafür eine claude.ai Routine triggert -- die Routine liest die Nachricht dann selbst über die MCP-Tools oben und antwortet.
Läuft als Docker-Container und wird über einen bestehenden Cloudflare Tunnel unter einer eigenen Domain erreichbar gemacht.
Architektur
claude.ai Routine (Cloud-Agent)
| ^
(per API-Trigger) (MCP: nachricht_senden,
| neue_nachrichten_abrufen)
| |
| v
Claude (MCP-Client) --https--> Cloudflare Tunnel (öffentliche Domain)
|
v
127.0.0.1:4567 auf deinem Server
|
v
Docker-Container "ida-telegram-mcp"
^
| (long-polling, ausgehend)
Telegram Bot HTTP-API
^
Telegram-Nutzer schreibt dem BotWichtig: Dieser Container ruft selbst nie eine Claude-API auf. Er macht nur zwei Dinge -- Telegram per Long-Polling nach neuen Nachrichten fragen, und bei neuen Nachrichten eine claude.ai Routine über deren eigenen API-Trigger anstoßen. Die eigentliche "Intelligenz" (Nachricht lesen, Antwort formulieren) läuft komplett in der Routine bei Anthropic, die sich dafür ganz normal als MCP-Client mit diesem Server verbindet -- genau wie Claude Code oder claude.ai es auch tun.
Der Container published seinen Port nur auf 127.0.0.1 -- von außen
nicht direkt erreichbar, nur über den bereits laufenden cloudflared-Prozess.
Zusätzlich verlangt der Server bei jeder Anfrage ein geheimes Token
(MCP_AUTH_TOKEN).
Wichtigste Design-Entscheidung: Es gibt keinen Empfänger-Parameter.
TELEGRAM_CHAT_ID in der .env legt die einzige Person fest, an die dieser
Server jemals schreiben kann -- weder Claude noch sonst jemand kann über die
Tools eine andere chat_id angeben. Eine Nachricht erreicht eine echte Person
und lässt sich nicht zurückholen, deshalb ist der Empfänger bewusst fest
verdrahtet statt frei wählbar.
Voraussetzungen
Docker + Docker Compose auf dem Server
Ein bereits eingerichteter und verbundener Cloudflare Tunnel auf diesem Server
Ein Telegram-Bot (in wenigen Minuten selbst erstellt, siehe unten)
Ein claude.ai-Account, um die Routine anzulegen
1. Telegram-Bot erstellen
In Telegram den Chat mit @BotFather öffnen.
/newbotsenden, Namen vergeben -- du bekommst einen Token wie123456789:ABC-DEF1234ghIkl-zyx57W2v1u123ew11.Die Person, die die Nachrichten empfangen soll, schreibt dem neuen Bot einmal eine beliebige Nachricht (z.B. "hi"). Wichtig: Ein Bot kann niemanden anschreiben, der ihm nicht vorher selbst geschrieben hat.
Im Browser aufrufen (mit echtem Token):
https://api.telegram.org/bot<TOKEN>/getUpdatesIm JSON nach"chat":{"id": ...}suchen -- das ist diechat_id.
2. Einrichten, bauen, starten
git clone https://github.com/<dein-user>/Ida-Telegram.git
cd Ida-Telegram
cp .env.example .env.env erstmal mit TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID und
MCP_AUTH_TOKEN ausfüllen (siehe Tabelle unten) -- ROUTINE_ID
und ROUTINE_API_KEY folgen in Schritt 5, dafür muss der Server erst
erreichbar sein.
Image bauen lassen: Bei jedem Push auf main baut
.github/workflows/docker-publish.yml das Image automatisch nach
ghcr.io/<dein-user>/ida-telegram:latest. Einmalig auf öffentlich stellen
(GitHub -> Profil -> Packages -> ida-telegram -> Package settings ->
Change visibility -> Public), damit docker compose es ohne Login ziehen
kann.
docker compose pull
docker compose up -d
docker compose logs -f(Mit AUTOREPLY_ENABLED=true als Standard startet der Container zunächst
mit Fehler, weil ROUTINE_ID/ROUTINE_API_KEY noch fehlen --
das ist normal, kommt in Schritt 5. Alternativ jetzt schon AUTOREPLY_ENABLED=false
setzen und später wieder auf true.)
3. An den bestehenden Cloudflare Tunnel anbinden
Analog zu Ida-Untis, nur mit eigenem Hostname und Port 4567:
ingress:
- hostname: telegram.deine-domain.de
service: http://localhost:4567
- service: http_status:404(Bzw. im Zero-Trust-Dashboard unter Public Hostname eintragen.) Danach
cloudflared neu laden.
4. Als claude.ai Connector hinzufügen
Genau wie bei Ida-Untis: claude.ai -> Einstellungen -> Connectors -> Add
custom connector -> als URL
https://telegram.deine-domain.de/mcp?token=<MCP_AUTH_TOKEN> eintragen.
5. Routine anlegen (der eigentliche Auto-Antwort-Teil)
Auf claude.ai/code/routines -> Neue Routine.
Name: z.B. "Ida Telegram Autoreply".
Anweisungen: (setzt voraus, dass die Routine sowohl den Ida-Telegram- als auch den Ida-Memory-Connector hat, siehe Schritt 5)
Du bist der Telegram-Assistent fuer die eine fest konfigurierte Person.
Neue Nachricht lesen: Ruf ueber den Ida-Telegram-Connector
neue_nachrichten_abrufenauf. Das koennen Text, Fotos (die du dir direkt anschauen kannst) oder Hinweise auf Sprachnachrichten sein. Liefert das Tool nichts, mach nichts und brich ab.Kontext holen: Ruf zusaetzlich
chat_verlaufauf, um die letzten Nachrichten (beide Richtungen) zu sehen -- hilft z.B. zu verstehen, worauf sich eine kurze Nachricht bezieht.Gedaechtnis pruefen, BEVOR du antwortest -- aber nur wenn noetig: Wenn du die Antwort schon aus der Nachricht selbst oder aus
chat_verlaufkennst, ueberspring diesen Schritt. Sonst nutze den Ida-Memory-Connector:search_nodesmit den wichtigsten Stichworten aus der Nachricht (Namen, Projekte, Themen). Bei relevanten Treffern mitopen_nodesdie genauen Eintraege nachladen. Keinread_graphbenutzen -- das laedt unnoetig den kompletten Bestand. Findest du nichts und weisst es auch sonst nicht -- sag ehrlich, dass du es nicht weisst, statt etwas zu erfinden.Antworten: Kurze, freundliche, hilfreiche Antwort auf Deutsch ueber
nachricht_senden. Relevante Infos aus Schritt 2/3 einbauen, wenn sie zur Nachricht passen. Bei Sprachnachrichten zuerstsprachnachricht_transkribieren(voice_id)mit der id ausneue_nachrichten_abrufenaufrufen, um den Inhalt zu verstehen.Essensplan-Sonderfall: Wenn ein Foto ein Essensplan fuer die Woche ist, merke dir NUR das Mittagessen pro Tag (z.B. als Entity "Essensplan KW" mit einer observation pro Tag, "Montag: Spaghetti Bolognese"). Fruehstueck und Abendessen auf demselben Bild ignorierst du, auch wenn sie draufstehen. Wird spaeter nach dem Essen an einem bestimmten Tag gefragt: wie in Schritt 3 im Gedaechtnis nachschauen.
Gedaechtnis aktualisieren, NACH der Antwort: Enthaelt die Nachricht sonst einen dauerhaft nuetzlichen Fakt (Vorliebe, laufendes Projekt, wiederkehrende Info, Korrektur zu etwas Gespeichertem) -- ueber Ida-Memory speichern:
create_entitiesnur fuer neue Personen/Projekte/ Themen,add_observationsfuer neue Fakten zu bestehenden Entities (nur an die, zu der sie wirklich gehoeren),create_relationsfuer dauerhafte Zusammenhaenge. NICHT jede Kleinigkeit speichern -- im Zweifel lieber nichts speichern als zu viel.
Trigger: "API" auswählen (nicht Zeitplan). claude.ai zeigt dir danach einmalig einen API-Token an (
sk-ant-oat01-...) -- sofort notieren, er wird danach nicht mehr im Klartext angezeigt.Bei Konnektoren den gerade hinzugefügten
Ida-Telegram-Connector auswählen.Routine speichern.
Die Routine-ID aus der URL ablesen, die claude.ai beim Bearbeiten der Routine anzeigt (
claude.ai/code/routines/trig_...-- der Teil abtrig_ist die ID), und zusammen mit dem Token in.enveintragen:
ROUTINE_ID=<trig_...>
ROUTINE_API_KEY=<der-notierte-api-token>Technischer Hintergrund: der Server ruft dafür
POST https://api.anthropic.com/v1/claude_code/routines/<ROUTINE_ID>/fire
auf (offizieller Endpunkt für Routinen-Trigger, siehe
Doku) --
ROUTINE_ID und ROUTINE_API_KEY sind alles, was dafür gebraucht wird.
Neu starten:
docker compose up -d
Ab jetzt: schreibt die konfigurierte Person dem Telegram-Bot, triggert der Container die Routine, die Routine liest die Nachricht über MCP und antwortet über MCP zurück auf Telegram.
Verfügbare MCP-Tools
Tool | Zweck |
| Schickt |
| Gibt zurück, was den aktuellen Routine-Lauf ausgelöst hat (jeweils nur einmal): Text als String, Fotos als echten Bildinhalt, Bildunterschriften als eigener Text, Sprachnachrichten als Hinweistext mit |
| Transkribiert eine zwischengespeicherte Sprachnachricht zu Text -- läuft lokal in diesem Container (faster-whisper), keine Audiodaten verlassen die eigene Infrastruktur |
| Letzte |
| Prüft nur, ob Token/Bot erreichbar sind (sendet nichts) |
Während ein Routine-Lauf auf eine Antwort wartet, zeigt der Bot in Telegram automatisch "tippt..." an (aktualisiert alle 4s, damit es nicht ausblendet) -- kein eigenes Tool dafür nötig, das läuft im Hintergrund mit.
Persistentes Gedächtnis (über einzelne Routine-Läufe hinweg, gemeinsam nutzbar von mehreren KIs/Connectors) liegt bewusst nicht hier, sondern im separaten Ida-Memory-Projekt -- der Routine dafür zusätzlich diesen Connector geben.
Unterstützte Nachrichtentypen:
Typ | Verhalten |
Text (auch formatiert, z.B. fett) | Wird 1:1 als Text an die Routine weitergegeben |
Foto | Wird heruntergeladen und als echter Bildinhalt weitergegeben -- die Routine kann es tatsächlich "sehen" (Claude-Vision über MCP-Bildinhalte) |
Sprachnachricht | Wird heruntergeladen und zwischengespeichert (Hinweis mit |
Sticker, Videos, Dokumente | Werden aktuell ignoriert |
Lokale Sprachnachrichten-Transkription (Whisper)
sprachnachricht_transkribieren läuft direkt in diesem Container --
kein externer Dienst, keine Audiodaten verlassen die eigene Infrastruktur.
Technisch: faster-whisper
(CTranslate2-Engine statt des originalen openai-whisper/PyTorch-Stacks --
deutlich schnellere und speicherschonendere CPU-Inferenz, wichtig auf einem
VPS ohne GPU).
Auf Abruf, nicht automatisch: Eine Sprachnachricht wird beim Empfang nur heruntergeladen und mit einer
voice_idzwischengespeichert (die letzten 20, älteste fällt raus). Transkribiert wird erst, wenn die Routine das Tool tatsächlich aufruft -- spart Rechenzeit für Sprachnachrichten, die z.B. gar nicht beantwortet werden.Modell wird lazy geladen: Nicht beim Containerstart, sondern beim ersten tatsächlichen Transkriptions-Aufruf -- der ist dadurch spürbar langsamer (Download + Laden), jeder weitere Aufruf nutzt das bereits geladene Modell und ist deutlich schneller.
Ressourcen:
WHISPER_MODEL=base(Standard) ist ein Kompromiss aus Geschwindigkeit/Genauigkeit für eine CPU-VPS ohne GPU. Bei sehr begrenztem RAMtinyprobieren, bei Bedarf an Genauigkeitsmall. Das Modell wird nach dem ersten Download im/data-Volume gecacht.Bekanntes Whisper-Verhalten: Auf sehr kurzen/leisen/inhaltsleeren Aufnahmen "halluziniert" das Modell manchmal plausibel klingenden, aber falschen Text (ein dokumentiertes Verhalten aller Whisper-Modelle, kein Bug dieses Servers) -- bei zweifelhaften Ergebnissen im Zweifel nachfragen.
Wie der Auto-Antwort-Loop funktioniert
Wenn AUTOREPLY_ENABLED=true (Standard) läuft im Container ein
Hintergrund-Thread:
Fragt Telegram per Long-Polling nach neuen Nachrichten der konfigurierten Person (
TELEGRAM_CHAT_ID) -- Nachrichten von anderen werden ignoriert.Kommen mehrere Nachrichten schnell hintereinander, wartet der Server
AUTOREPLY_DEBOUNCE_SECONDSauf weiteren Nachschub und bündelt alles -- die Routine wird dann einmal getriggert statt einmal pro Nachricht.Schickt einen
POSTmitAuthorization: Bearer $ROUTINE_API_KEYan den Routinen-Endpunkt (ROUTINE_IDin der URL) -- der gebündelte Text geht alstext-Feld direkt mit (sofortiger Kontext für die Routine), zusätzlich liefertneue_nachrichten_abrufendenselben Text noch einmal ab, falls die Routine ihn lieber über MCP nachlesen will.
Kein Doppelt-Antworten: Telegrams getUpdates-Offset-Mechanismus sorgt von
selbst dafür, dass jede Nachricht genau einmal in den Zwischenspeicher
wandert, auch nach einem Neustart des Containers; neue_nachrichten_abrufen
liefert jede Nachricht ebenfalls nur einmal aus.
Kosten: Jeder Routine-Lauf verbraucht claude.ai-Nutzung auf deinem Account (Cloud-Agent-Sitzung), nicht eine separate API-Rechnung.
Lokal testen ohne Cloudflare
docker compose up -d
curl -H "Authorization: Bearer $MCP_AUTH_TOKEN" http://127.0.0.1:4567/healthzTroubleshooting
Container startet nicht:
docker compose logs-- meist fehlt eine Pflicht-Variable in.env(z.B.ROUTINE_ID/ROUTINE_API_KEYfehlen, obwohlAUTOREPLY_ENABLED=trueist).Telegram-API-Fehler: chat not found: Die Zielperson hat dem Bot noch nie geschrieben (siehe Schritt 1.3), oder diechat_idist falsch.Telegram-API-Fehler: Unauthorized:TELEGRAM_BOT_TOKENfalsch/abgelaufen.Claude bekommt 401: Token in Client-Konfiguration und
.envvergleichen.Routine wird nicht getriggert:
docker compose logs -fprüfen -- Zeile "Telegram-Autoreply-Loop gestartet" sollte beim Start erscheinen, und bei neuer Nachricht "Neue Nachricht(en) erhalten, triggere Routine...". Bei einem HTTP-Fehler danach:ROUTINE_ID/ROUTINE_API_KEYprüfen (401 = Token falsch/gehört nicht zu dieser Routine, 404 =ROUTINE_IDfalsch).Routine läuft, antwortet aber nicht: In claude.ai unter Routinen die letzte Sitzung öffnen und den Verlauf prüfen -- meist fehlt der Ida-Telegram-Connector bei den Konnektoren der Routine, oder
neue_nachrichten_abrufenliefert eine leere Liste (Race Condition sehr unwahrscheinlich, aber möglich bei extrem kurzemAUTOREPLY_DEBOUNCE_SECONDS).Conflict: terminated by other getUpdates request: Der Bot-Token wird gleichzeitig noch woanders pergetUpdatesabgefragt (oder es ist ein Webhook für den Bot gesetzt) -- ein Telegram-Bot-Token kann immer nur von einem Prozess gleichzeitig per Long-Polling abgefragt werden.sprachnachricht_transkribierendauert beim ersten Aufruf sehr lange: normal -- das Modell wird dann erst heruntergeladen/geladen. Danach deutlich schneller. Bei dauerhaft sehr langsamer Transkription ein kleineresWHISPER_MODEL(z.B.tiny) probieren."Keine zwischengespeicherte Sprachnachricht ... gefunden": entweder eine falsche/erfundene
voice_id, oder es sind seither mehr als 20 neue Sprachnachrichten eingegangen (älteste fallen aus dem Zwischenspeicher).Container braucht spürbar mehr RAM als vorher: durch
faster-whisper+ geladenes Modell erwartet -- bei sehr begrenztem RAMWHISPER_MODEL=tinysetzen oderWHISPER_ENABLED=false.