Skip to main content
Glama
L8teNever

Ida-Telegram

by L8teNever

Ida-Telegram

Ein eigenständiger MCP-Server (Model Context Protocol), getrennt von Ida-Untis und ohne Verbindung dazu. Zwei Dinge in einem Container:

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

  2. 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 Bot

Wichtig: 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

  1. In Telegram den Chat mit @BotFather öffnen.

  2. /newbot senden, Namen vergeben -- du bekommst einen Token wie 123456789:ABC-DEF1234ghIkl-zyx57W2v1u123ew11.

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

  4. Im Browser aufrufen (mit echtem Token): https://api.telegram.org/bot<TOKEN>/getUpdates Im JSON nach "chat":{"id": ...} suchen -- das ist die chat_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)

  1. Auf claude.ai/code/routines -> Neue Routine.

  2. Name: z.B. "Ida Telegram Autoreply".

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

    1. Neue Nachricht lesen: Ruf ueber den Ida-Telegram-Connector neue_nachrichten_abrufen auf. Das koennen Text, Fotos (die du dir direkt anschauen kannst) oder Hinweise auf Sprachnachrichten sein. Liefert das Tool nichts, mach nichts und brich ab.

    2. Kontext holen: Ruf zusaetzlich chat_verlauf auf, um die letzten Nachrichten (beide Richtungen) zu sehen -- hilft z.B. zu verstehen, worauf sich eine kurze Nachricht bezieht.

    3. Gedaechtnis pruefen, BEVOR du antwortest -- aber nur wenn noetig: Wenn du die Antwort schon aus der Nachricht selbst oder aus chat_verlauf kennst, ueberspring diesen Schritt. Sonst nutze den Ida-Memory-Connector: search_nodes mit den wichtigsten Stichworten aus der Nachricht (Namen, Projekte, Themen). Bei relevanten Treffern mit open_nodes die genauen Eintraege nachladen. Kein read_graph benutzen -- 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.

    4. Antworten: Kurze, freundliche, hilfreiche Antwort auf Deutsch ueber nachricht_senden. Relevante Infos aus Schritt 2/3 einbauen, wenn sie zur Nachricht passen. Bei Sprachnachrichten zuerst sprachnachricht_transkribieren(voice_id) mit der id aus neue_nachrichten_abrufen aufrufen, um den Inhalt zu verstehen.

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

    6. 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_entities nur fuer neue Personen/Projekte/ Themen, add_observations fuer neue Fakten zu bestehenden Entities (nur an die, zu der sie wirklich gehoeren), create_relations fuer dauerhafte Zusammenhaenge. NICHT jede Kleinigkeit speichern -- im Zweifel lieber nichts speichern als zu viel.

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

  5. Bei Konnektoren den gerade hinzugefügten Ida-Telegram-Connector auswählen.

  6. Routine speichern.

  7. Die Routine-ID aus der URL ablesen, die claude.ai beim Bearbeiten der Routine anzeigt (claude.ai/code/routines/trig_... -- der Teil ab trig_ ist die ID), und zusammen mit dem Token in .env eintragen:

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.

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

nachricht_senden(text)

Schickt text an die fest konfigurierte Person. Stoppt dabei automatisch die "tippt..."-Anzeige und trägt die Antwort in chat_verlauf ein

neue_nachrichten_abrufen()

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 voice_id

sprachnachricht_transkribieren(voice_id)

Transkribiert eine zwischengespeicherte Sprachnachricht zu Text -- läuft lokal in diesem Container (faster-whisper), keine Audiodaten verlassen die eigene Infrastruktur

chat_verlauf()

Letzte CHAT_HISTORY_LENGTH Nachrichten (Standard 5, beide Richtungen) als leichtgewichtiger Text -- fürs Gesprächsgedächtnis über den aktuellen Lauf hinaus. Nicht destruktiv, beliebig oft abrufbar. Fotos nur als [Foto]-Platzhalter, keine Bilddaten

bot_status()

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 voice_id). Keine automatische Transkription -- die Routine ruft bei Bedarf gezielt sprachnachricht_transkribieren(voice_id) auf (lokal per Whisper, siehe unten)

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_id zwischengespeichert (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 RAM tiny probieren, bei Bedarf an Genauigkeit small. 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:

  1. Fragt Telegram per Long-Polling nach neuen Nachrichten der konfigurierten Person (TELEGRAM_CHAT_ID) -- Nachrichten von anderen werden ignoriert.

  2. Kommen mehrere Nachrichten schnell hintereinander, wartet der Server AUTOREPLY_DEBOUNCE_SECONDS auf weiteren Nachschub und bündelt alles -- die Routine wird dann einmal getriggert statt einmal pro Nachricht.

  3. Schickt einen POST mit Authorization: Bearer $ROUTINE_API_KEY an den Routinen-Endpunkt (ROUTINE_ID in der URL) -- der gebündelte Text geht als text-Feld direkt mit (sofortiger Kontext für die Routine), zusätzlich liefert neue_nachrichten_abrufen denselben 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/healthz

Troubleshooting

  • Container startet nicht: docker compose logs -- meist fehlt eine Pflicht-Variable in .env (z.B. ROUTINE_ID/ROUTINE_API_KEY fehlen, obwohl AUTOREPLY_ENABLED=true ist).

  • Telegram-API-Fehler: chat not found: Die Zielperson hat dem Bot noch nie geschrieben (siehe Schritt 1.3), oder die chat_id ist falsch.

  • Telegram-API-Fehler: Unauthorized: TELEGRAM_BOT_TOKEN falsch/abgelaufen.

  • Claude bekommt 401: Token in Client-Konfiguration und .env vergleichen.

  • Routine wird nicht getriggert: docker compose logs -f prü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_KEY prüfen (401 = Token falsch/gehört nicht zu dieser Routine, 404 = ROUTINE_ID falsch).

  • 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_abrufen liefert eine leere Liste (Race Condition sehr unwahrscheinlich, aber möglich bei extrem kurzem AUTOREPLY_DEBOUNCE_SECONDS).

  • Conflict: terminated by other getUpdates request: Der Bot-Token wird gleichzeitig noch woanders per getUpdates abgefragt (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_transkribieren dauert beim ersten Aufruf sehr lange: normal -- das Modell wird dann erst heruntergeladen/geladen. Danach deutlich schneller. Bei dauerhaft sehr langsamer Transkription ein kleineres WHISPER_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 RAM WHISPER_MODEL=tiny setzen oder WHISPER_ENABLED=false.