Hinter den Kulissen des CaptainDNS-MCP

Von CaptainDNS
Veröffentlicht am 27. November 2025

  • #MCP
  • #Architektur
  • #DNS
  • #E-Mail
  • #KI-Integrationen
TL;DR

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/api bleibt die einzige Quelle der Wahrheit (DNS, Propagation, Mail).
  • services/mcp-server ist 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.
  • CaptainDNS-API (services/api):
    • Endpunkte /resolve, /resolve/propagation, mail/domain-check;
    • Datenbank und Resolver;
    • Logs, Scoring, Nutzerprofil.

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: insbesondere tools: { 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 wie captaindns: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 (Enum A, 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:

  1. validiert und normalisiert die Argumente (Domains, DNS-Typen, etc.);
  2. ruft den passenden Backend-Endpoint (/resolve, /resolve/propagation, /mail/domain-check) über einen internen Client auf;
  3. packt die Antwort in ein CallToolResult mit:
    • structuredContent: die strukturierte CaptainDNS-Antwort (DNS-Responses, Mail-Score, SPF/DKIM/DMARC/BIMI-Details, etc.);
    • optional content mit 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.

CaptainDNS-MCP-Transportdiagramm

Einstiegspunkt: POST /stream

Der Haupteinstieg des MCP-Servers ist ein HTTP-Endpoint:

  • POST /stream mit einem JSON-RPC-2.0-Body.

Moderne Clients nutzen ihn, um:

  • die Sitzung zu öffnen;
  • initialize zu senden;
  • tools/list und tools/call anzuketten.

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 /stream mit Accept: 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_lookup kürzer als email_auth_audit oder dns_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.1 lauschte, 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.1 statt 0.0.0.0 hinter 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 /stream mit 405 (Method not allowed);
  • einen Fallback auf GET /stream mit Accept: 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 /stream für alles JSON-RPC (initialize, tools/list, tools/call);
  • GET /stream SSE 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 initialize mit protocolVersion und clientInfo;
  • eine Antwort, die bereits die Tool-Liste enthielt und manchmal ein falsch getyptes capabilities.tools.

Gefundene Probleme:

  • fehlendes protocolVersion in result;
  • fehlendes serverInfo;
  • capabilities.tools als Boolean (true) statt Objekt ({listChanged:false});
  • nicht standardisierte Felder (Tool-Liste, Custom-Endpoints) in der initialize-Antwort.

Fix:

  • die initialize-Antwort normalisieren:
    • protocolVersion spiegeln;
    • capabilities.tools = { listChanged: false };
    • minimales serverInfo (name, version);
  • die Tool-Liste nach tools/list verschieben.

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 id hat (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 nutzte input_schema im snake_case statt camelCase inputSchema.

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/call hinzufügen;
  • anhand von params.name zum richtigen Tool routen;
  • Parameter mit dem passenden inputSchema validieren;
  • ein strukturiertes CallToolResult zurü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 structuredContent verschieben;
  • content für einen optionalen Text-Summary reservieren (leicht für Nutzer anzeigbar);
  • isError nutzen, 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 localhost oder eine private IP.
  • POST /stream antwortet (kein 405) und du siehst ein initialize-Log auf Serverseite.
  • Der Server lauscht auf 0.0.0.0 hinter dem Reverse Proxy, nicht nur auf 127.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 structuredContent und nutze content nur für eine optionale Textzusammenfassung;
  • antworte nie auf eine Notification (notifications/initialized hat kein id);
  • liefere genau ein CallToolResult mit klarem isError statt eigener Blöcke type: \"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/call implementieren und zu dns_lookup, dns_propagation, email_auth_audit routen, validiert gegen das passende inputSchema;
  • Clientseitig sicherstellen, dass params.name exakt einem name aus tools/list entspricht.

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_lookup kürzer als dns_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.

Ähnliche Artikel

CaptainDNS · 21. November 2025

Ein MCP für CaptainDNS?

Bevor CaptainDNS an IAs angebunden wird, muss klar sein, was das Model Context Protocol (MCP) ist und was es wirklich ermöglicht. Ein MCP-ABC und erste Ansätze für CaptainDNS.

  • #MCP
  • #KI
  • #DNS
  • #E-Mail
  • #Architektur