Hinter den Kulissen des CaptainDNS-MCP
Von CaptainDNS
Veröffentlicht am 27. November 2025
- #MCP
- #Architektur
- #DNS
- #KI-Integrationen
TL;DR - 🔨 Bevor wir CaptainDNS via MCP an ChatGPT angeschlossen haben, brauchten wir ein klares Setup: einen dedizierten MCP-Server zwischen den Hosts (ChatGPT, interne Tools) und der bestehenden CaptainDNS-API.
- Der MCP-Server enthält keine DNS- oder Mail-Logik: er delegiert alles an das CaptainDNS-Backend über eine abgesicherte interne API.
- Der MCP-Transport basiert auf HTTP + JSON-RPC mit einem einzigen Einstiegspunkt
/stream, passend zum modernen HTTP+SSE-Modell. - Die exponierten Tools (
dns_lookup,dns_propagation,email_auth_audit) sind getypt, damit die KI sie selbst entdecken und aufrufen kann. - Frühe Tests brachten Timeouts, 424-Fehler und Protokoll-Details (notifications, tools/list, tools/call) zutage, die den Vertrag gehärtet haben.
- Dieser Beitrag zeigt, wie wir die Architektur aufgebaut, die Kommunikation abgesichert und die gesamte Kette debuggt haben.
Warum ein dedizierter MCP-Server für CaptainDNS?
Aus Produktsicht ist CaptainDNS weiterhin ein klassisches SaaS: eine Next.js-Weboberfläche (frontend) und eine Go-API (services/api), die die Fachlogik trägt (DNS, Mail, Resolver, Logging, Scoring).
MCP hinzufügen heißt nicht, die API direkt an ein Modell zu geben: wir haben einen dedizierten MCP-Server (services/mcp-server) dazwischen gesetzt, der als Adapter fungiert.
Konkret:
services/apibleibt die einzige Quelle der Wahrheit (DNS, Propagation, Mail).services/mcp-serverist ein starker Client dieser API:- er authentifiziert sich mit einem Service-Token, um seine Herkunft aus der CaptainDNS-Infrastruktur zu belegen;
- er leitet einen Auth0-User-Token weiter, wenn vorhanden, damit Quoten, Logging und Berechtigungen greifen.
- Hosts (ChatGPT, interne Tools, Agents) sehen nur den MCP-Server und sprechen MCP/JSON-RPC, nie die rohe API.
Diese Trennung ermöglicht:
- die API-Evolution vom MCP-Vertrag zu entkoppeln;
- KI-spezifische Schutzmaßnahmen hinzuzufügen (Rate Limits, Format-Checks, aggressive Timeouts); und
- eine saubere Architektur zu behalten.
Architektur-Übersicht
Auf hoher Ebene sieht die Architektur so aus:
- MCP-Hosts: ChatGPT (Connectors), interne Tools, andere MCP-Clients.
- CaptainDNS-MCP-Server:
- stellt MCP-Tools (
dns_lookup,dns_propagation,email_auth_audit, etc.) via JSON-RPC bereit; - validiert und normalisiert Eingaben (Domains, Record-Typen, DKIM-Selectoren);
- setzt Timeouts, Quoten und Fehlerklassifizierung durch.
- stellt MCP-Tools (
- CaptainDNS-API (
services/api):- Endpunkte
/resolve,/resolve/propagation,mail/domain-check; - Datenbank und Resolver;
- Logs, Scoring, Nutzerprofil.
- Endpunkte
Der MCP-Server ist damit eine Protokoll-Brücke: er übersetzt MCP-Aufrufe (tools/list, tools/call) in interne HTTP-Aufrufe und verpackt die Antwort in ein Format, das der MCP-Client und das Modell verstehen.
MCP-Vertrag: initialize, tools/list, tools/call
Das Protokoll nutzt JSON-RPC 2.0 und drei zentrale Methoden auf Serverseite:
initialize: Aushandlung der Protokollversion und Fähigkeiten.tools/list: Entdeckung der verfügbaren Tools.tools/call: Ausführung eines benannten Tools mit getypten Argumenten.
initialize: sagen, wer man ist und was man kann
Wenn ein Host (z. B. ChatGPT) eine Sitzung mit dem CaptainDNS-MCP-Server öffnet, startet er mit:
method: "initialize";params.protocolVersion: eine Protokollversion (z. B."2025-06-18");params.clientInfo: Name und Version des Clients (openai-mcp,1.0.0, etc.).
Der MCP-Server antwortet mit:
protocolVersion: der akzeptierten Version (oft gespiegelt zum Client);capabilities: insbesonderetools: { listChanged: false }für eine stabile Tool-Liste;serverInfo: Name ("captaindns-mcp-server") und Version ("0.1.0") des Servers.
An diesem Punkt ist noch kein Business-Call an die CaptainDNS-API gegangen: es geht nur um Protokollversion und Basis-Fähigkeiten.
tools/list: CaptainDNS-Tools ankündigen
Nach erfolgreichem initialize sendet der Client tools/list. Der CaptainDNS-MCP liefert ein Array von Tool-Definitionen, jeweils mit:
name: Tool-Identifikator, z. B.dns_lookup,dns_propagation,email_auth_audit;description: was das Tool tut, in natürlicher Sprache;inputSchema: ein JSON-Schema, das die erwarteten Argumente beschreibt;annotations: Metadaten (Tags, empfohlene Scopes wiecaptaindns:dns:read).
CaptainDNS-Tools sind bewusst read-only: sie fragen DNS oder Mail-Konfiguration ab, ändern aber nichts.
Beispiele für getypte Parameter:
dns_lookup:domain(pflicht);record_type(EnumA,AAAA,TXT,MX, etc.);resolver_preset(optional);trace(bool), um eine iterative Trace zu verlangen.
dns_propagation:- gleiche Logik, angewandt auf mehrere Resolver.
email_auth_audit:domain(pflicht);rp_domain(optional, für Reports/Policies);dkim_selectors(optionale Liste zu prüfender Selector).
tools/call: ein Business-Tool ausführen
Wenn das Modell ein Tool nutzen will, ruft es nicht direkt dns_lookup auf: es sendet tools/call mit:
params.name: Name des Tools ("dns_lookup","dns_propagation","email_auth_audit");params.arguments: ein JSON-Objekt gemäßinputSchema.
Der MCP-Server:
- validiert und normalisiert die Argumente (Domains, DNS-Typen, etc.);
- ruft den passenden Backend-Endpoint (
/resolve,/resolve/propagation,/mail/domain-check) über einen internen Client auf; - packt die Antwort in ein
CallToolResultmit:structuredContent: die strukturierte CaptainDNS-Antwort (DNS-Responses, Mail-Score, SPF/DKIM/DMARC/BIMI-Details, etc.);- optional
contentmit einer Textzusammenfassung für die KI; isError:false, wenn alles lief,true, wenn das Tool lief, aber ein Business-Fehler zurückgab (z. B. DNS-Timeout).
Strukturelle Fehler (unbekanntes Tool, ungültige Parameter, fehlerhaftes JSON) bleiben klassische JSON-RPC-Fehler (-32601, -32602, etc.), wodurch der MCP-Client Protokoll- von Business-Problemen trennen kann.
MCP-Transport: HTTP + JSON-RPC und SSE
Für die erste Version haben wir uns für den modernen Transport HTTP + JSON-RPC entschieden, mit optionalem SSE-Support.
Einstiegspunkt: POST /stream
Der Haupteinstieg des MCP-Servers ist ein HTTP-Endpoint:
POST /streammit einem JSON-RPC-2.0-Body.
Moderne Clients nutzen ihn, um:
- die Sitzung zu öffnen;
initializezu senden;tools/listundtools/callanzuketten.
Der MCP-Server:
- liest die JSON-RPC-Anfrage (
jsonrpc,id,method,params); - routet zum passenden Handler (
initialize,tools/list,tools/call); - liefert eine strukturierte JSON-RPC-Antwort:
result(Erfolg);- oder
error(Protokollfehler).
SSE: Kompatibilität und Introspektion
Für ältere Clients oder spezielle Fälle stellt der Server außerdem bereit:
GET /streammitAccept: text/event-stream;
Dieser SSE-Stream:
- kündigt Basis-Metadaten an (HTTP-Endpoint für Requests);
- kann eine vereinfachte Tool-Liste ausgeben;
- sendet regelmäßige Pings, um Verbindungen unter Kontrolle zu halten.
In der Praxis stützt sich die ChatGPT-Integration vor allem auf POST /stream mit JSON-RPC. Frühe Ausfälle (Timeouts, 424, etc.) kamen von einem unvollständigen SSE-Handshake; ein klarer, einziger JSON-RPC-Stream hat sie behoben.
Interaktionen MCP-Server ↔ CaptainDNS-API
Für jedes tools/call agiert der MCP-Server als authentifizierter Client der CaptainDNS-API.
Authentifizierung und Identität
Der MCP sendet immer:
- ein Service-Token, damit die API eine vertrauenswürdige interne Quelle erkennt;
- ein User-Token, wenn der MCP-Host eines liefert, damit die API:
- Requests einem Profil zuordnen kann (für Logs und Historie);
- Quoten oder Berechtigungen durchsetzen kann.
Der MCP-Server speichert keine Nutzerdaten langfristig: er leitet die Identität nur an die API weiter, die Autorität für Profile und Logs bleibt dort.
Interner Client und Normalisierung
Alle ausgehenden Requests laufen über einen einzigen internen Client, der:
- Domains normalisiert (
example.com, ohne abschließenden Punkt, lowercase); - Record-Typen validiert (
A,AAAA,TXT, etc.); - den Scope der Tools einschränkt (DKIM-Selectoren, Resolver-Presets);
- pro Tool abgestimmte Timeouts setzt (
dns_lookupkürzer alsemail_auth_auditoderdns_propagation).
Der MCP-Server macht keine rohen HTTP-Calls außerhalb dieses Clients: das erleichtert Wartung und Sicherheit.
Fehlerklassifizierung
Fehler fallen in drei Gruppen:
input: ungültige oder fehlende Parameter (falsch formatierte Domain, nicht unterstützter Record-Typ, fehlender Token, etc.);business: fachlicher Fehler (DNS-Timeout, Resolver nicht erreichbar, langsamer Mail-Upstream, etc.);internal: interner Ausfall (Bug, fehlende Konfiguration, API-5xx).
Auf /stream (JSON-RPC) spiegeln sich die Fehler als Standardcodes (-32602, -32001, -32603) mit Details in error.data. Auf Tool-Ergebnissen kann ein Business-Fehler als isError: true mit spezifischem structuredContent erscheinen.
Feldnotizen: Timeouts, 424 und andere Überraschungen
Die MCP-Theorie ist elegant; in der Praxis gab es Bugs und Überraschungen. Hier die wichtigsten Probleme und ihre Lösungen.
1. Der stille Timeout beim Hinzufügen des Servers
Erster Schritt: den MCP-Server in ChatGPT eintragen. Anfangs zeigte die konfigurierte URL auf:
- einen Server, der nur auf
127.0.0.1lauschte, oder - einen HTTP-Endpoint, der MCP gar nicht implementierte.
Ergebnis:
- kein
initialize-Log auf MCP-Seite; - ChatGPT zeigte schließlich nur einen Timeout: "unable to connect to the MCP server".
Ursachen:
- Server lauschten nur auf
127.0.0.1statt0.0.0.0hinter dem Reverse Proxy; - Verwendung von localhost/privaten IPs in der Konfiguration (aus der ChatGPT-Cloud nicht erreichbar).
Lesson Learned: vor Protokolldetails die Basics prüfen: öffentlicher DNS, HTTPS, auf erreichbarer Adresse lauschen und sofort Logs auf MCP-Seite sehen.
2. SSE ohne vollständigen Handshake: Verbindung offen... und bricht dann ab
Als die MCP-URL erreichbar war, zeigten die ersten Logs:
- ein
POST /streammit405(Method not allowed); - einen Fallback auf
GET /streammitAccept: text/event-stream; - eine SSE-Verbindung, die akzeptiert und nach ~2 Minuten geschlossen wurde.
Auf dem Papier „funktionierte“ es (es gab eine SSE-Verbindung), aber ChatGPT konnte die MCP-Session nie initialisieren. Grund:
- der SSE-Stream sendete Pings, aber nicht den erwarteten Handshake (kein
endpoint-Event, keine Info über die JSON-RPC-URL).
Fix: Transport vereinfachen mit einem klaren Haupteinstieg:
POST /streamfür alles JSON-RPC (initialize, tools/list, tools/call);GET /streamSSE als sekundärer Introspektionskanal, für ChatGPT aber nicht nötig.
Ein einziger, robuster JSON-RPC-Einstieg beseitigte die meisten mysteriösen Timeouts.
3. Unvollständiges initialize: wenn der Client mehr Metadaten erwartet
Nächste Version: die Verbindung stand, aber der MCP-Client stürzte bei initialize ab. Logs zeigten:
- ein eingehendes
initializemitprotocolVersionundclientInfo; - eine Antwort, die bereits die Tool-Liste enthielt und manchmal ein falsch getyptes
capabilities.tools.
Gefundene Probleme:
- fehlendes
protocolVersioninresult; - fehlendes
serverInfo; capabilities.toolsals Boolean (true) statt Objekt ({listChanged:false});- nicht standardisierte Felder (Tool-Liste, Custom-Endpoints) in der
initialize-Antwort.
Fix:
- die
initialize-Antwort normalisieren:protocolVersionspiegeln;capabilities.tools = { listChanged: false };- minimales
serverInfo(name,version);
- die Tool-Liste nach
tools/listverschieben.
Mit dem eingehaltenen Vertrag konnte der Client notifications/initialized und danach tools/list ohne Fehler abarbeiten.
4. Auf eine Notification antworten: eine sichere Art, den Client zu crashen
Weitere Überraschung: notifications/initialized ist eine JSON-RPC-Notification:
- kein
id; - der Client erwartet keine Antwort.
Eine frühe Serverversion antwortete trotzdem mit:
- einer pseudo JSON-RPC-Antwort mit
id: null.
Für einen strikten Client wirkt das wie eine Antwort auf eine nicht existierende Anfrage, was Fehler wie "unhandled errors in a TaskGroup" auslöste.
Fix: einfache Regel anwenden:
- wenn die Anfrage kein
idhat (Notification):- loggen;
- ggf. internen Zustand aktualisieren;
- aber nichts auf dem Stream zurückschicken.
Dieses Detail hat eine ganze Fehlerklasse auf Client-Seite verschwinden lassen.
5. tools/list und inputSchema vs input_schema
In frühen Tests kam ChatGPT bis tools/list, scheiterte aber beim Aufbau der internen Tools. Ursache:
- die
tools/list-Antwort nutzteinput_schemaim snake_case statt camelCaseinputSchema.
Selbst mit korrekt definiertem JSON-Schema erwartet ein getypter Client strikt inputSchema. Mit input_schema galt das Feld als fehlend: keine Formulare, keine Parameter-Validierung.
Fix: den Schlüssel konsequent auf inputSchema umbenennen.
6. tools/call: das scheinbar unbekannte Tool
Next Step: nach stabilisiertem tools/list versuchten Tool-Aufrufe tools/call... und erhielten durchgehend:
- ein JSON-RPC
method not found; - einen internen
ERR_UNKNOWN_TOOL.
Der Server suchte weiter nach Methoden wie dns_lookup oder dns_propagation, obwohl MCP eine einzige Methode tools/call mit Parameter name vorschreibt.
Fix:
- dedizierten Handler
tools/callhinzufügen; - anhand von
params.namezum richtigen Tool routen; - Parameter mit dem passenden
inputSchemavalidieren; - ein strukturiertes
CallToolResultzurückgeben.
Ab dann liefen die CaptainDNS-Tools endlich über MCP.
7. content type "json" vs structuredContent: Details, die Integrationen brechen
In einer Zwischenversion lieferte der MCP-Server Ergebnisse so zurück:
result.content[0].type = "json";result.content[0].json = { ... }.
Serverseitig praktisch, aber kein Standard-Blocktyp im Protokoll (das vor allem text, image, resource etc. definiert). Manche toleranten Clients können es interpretieren, andere nicht.
Bei ChatGPT zeigte sich das als interne Fehler wie:
http_error 424;unhandled errors in a TaskGroup (1 sub-exception).
Fix:
- die strukturierte Payload in
structuredContentverschieben; contentfür einen optionalen Text-Summary reservieren (leicht für Nutzer anzeigbar);isErrornutzen, um klar Erfolg vs. fachlichen Fehler zu markieren.
Mit diesem Schema verschwanden die 424er durch Content-Mapping-Probleme.
FAQ: Timeouts, 424 und MCP Best Practices
Häufige Fragen zum CaptainDNS-MCP
Welche Voraussetzungen prüfen, wenn das Hinzufügen des MCP-Servers scheitert?
Starte mit den Basics:
- Die URL zeigt auf einen öffentlich per HTTPS erreichbaren Endpoint, nicht auf
localhostoder eine private IP. POST /streamantwortet (kein405) und du siehst eininitialize-Log auf Serverseite.- Der Server lauscht auf
0.0.0.0hinter dem Reverse Proxy, nicht nur auf127.0.0.1. - SSE (
GET /stream) ist optional – blockiere dich nicht daran, wenn JSON-RPC läuft.
Taucht kein initialize in den Logs auf, ist es ein Netzwerkproblem (DNS, TLS, Firewall), kein Protokollthema.
Wie gehe ich mit `http_error 424` oder `unhandled errors in a TaskGroup` um?
Das deutet meist auf eine Antwort hin, die den MCP-Vertrag bricht:
- lege strukturierte Payloads in
structuredContentund nutzecontentnur für eine optionale Textzusammenfassung; - antworte nie auf eine Notification (
notifications/initializedhat keinid); - liefere genau ein
CallToolResultmit klaremisErrorstatt eigener Blöcketype: \"json\".
Wenn Logs ein erfolgreiches tools/call zeigen, der Client aber scheitert, prüfe zuerst diese Punkte.
Warum alle Tools über `tools/call` routen?
MCP verlangt eine einzige Business-Methode (tools/call) mit params.name:
- Serverseitig
tools/callimplementieren und zudns_lookup,dns_propagation,email_auth_auditrouten, validiert gegen das passendeinputSchema; - Clientseitig sicherstellen, dass
params.nameexakt einemnameaustools/listentspricht.
Pro-Tool-Methoden oder falsche Namen führen zu method not found oder ERR_UNKNOWN_TOOL.
Wie einen Timeout oder ungewöhnliche Latenz diagnostizieren?
Timeouts kommen oft eher vom Netzwerkpfad als vom Protokoll:
- prüfe Tool-spezifische Timeouts im MCP (
dns_lookupkürzer alsdns_propagation); - verkleinere für Tests einen Resolver-Sweep und erweitere danach;
- sieh dir Backend-Logs an (langsames DNS, Mail-Upstream) und prüfe das Service-Token.
Ein Timeout ohne geloggtes initialize bleibt ein Erreichbarkeitsproblem.
Reicht HTTP JSON-RPC oder brauche ich SSE?
Standardmäßig genügt ein einziger JSON-RPC-Einstieg (POST /stream):
- er ist der stabilste Pfad für ChatGPT und moderne Clients;
- Tool-Discovery und -Aufrufe laufen darüber.
Füge SSE (GET /stream) nur hinzu, wenn du Introspektion brauchst; dann sollte der Stream das endpoint liefern und regelmäßig Pings senden.
Glossar MCP und CaptainDNS
MCP (Model Context Protocol)
Offenes Protokoll, das standardisiert, wie ein KI-Modell mit externen Tools spricht: Datenbanken, APIs, Dienste wie CaptainDNS. Es definiert Konzepte wie initialize, tools/list, tools/call und getypte Antwortformate (CallToolResult, ContentBlock, structuredContent).
Host
Applikation, die ein KI-Modell und einen MCP-Client einbettet: ChatGPT, eine IDE, ein interner Agent. Der Host entscheidet, wann ein CaptainDNS-Tool via MCP aufgerufen wird.
MCP-Server
Service, der MCP-Tools für Hosts bereitstellt. Hier: captaindns-mcp-server, ein Go-Service, der die CaptainDNS-API kennt und ins MCP-Format übersetzt.
JSON-RPC 2.0
Leichtgewichtiges, JSON-basiertes Protokoll, das MCP nutzt, um Requests (method, params, id) und Responses (result oder error) zu beschreiben. MCP verwendet es fokussiert (initialize, tools/list, tools/call, notifications).
tools/list
MCP-Methode, die die verfügbaren Tools eines MCP-Servers mit inputSchema und Metadaten liefert. Damit erfährt das Modell, was es mit CaptainDNS tun kann.
tools/call
MCP-Methode, die ein Host nutzt, um ein bestimmtes Tool auszuführen. Der MCP-Server liest params.name, validiert params.arguments, ruft die Backend-API auf und liefert ein CallToolResult für das strukturierte Ergebnis (oder den Business-Fehler).
structuredContent
Optionales Feld im CallToolResult, in dem ein MCP-Server strukturierte Daten (JSON) aus der Tool-Ausführung ablegen kann. CaptainDNS platziert dort z. B. DNS-Antworten, Mail-Scores, SPF/DKIM/DMARC/BIMI-Details.
TaskGroup
Begriff aus asynchronen Runtimes (Python, etc.): eine Gruppe parallel ausgeführter Tasks. Eine Meldung wie "unhandled errors in a TaskGroup" zeigt eine unbehandelte Exception in einer oder mehreren Tasks an, oft durch subtile Format-Inkompatibilitäten oder Bugs in der Kette.