PullMD Teil 7: Spec-Compliant ist nicht genug – Einfach erklärt
Die nächste Ausbaustufe meines Markdown-Service stand an: anbinden an claude.ai im Webbrowser. Damit Claude den Service direkt nutzen kann, ohne dass ich Inhalte selbst rüberkopieren muss.
Dafür brauchte es eine sichere Anmeldung — den Anschluss-Mechanismus, mit dem Claude sich bei meinem Server ausweisen kann. Im Fachjargon nennt sich das OAuth. Es ist im Grunde dasselbe wie „mit Google anmelden“ — ein standardisiertes Verfahren, das überall gleich funktioniert.
Diesen Anmelde-Mechanismus habe ich zusammen mit Claude Code gebaut, getestet, hochgeladen. Dann wollte ich den Server in claude.ai eintragen. Und dort fing das Problem an.
Die Fehlermeldung, die keine war
claude.ai-Web, Einstellungen, „Add custom connector“. Adresse meines Servers eingegeben. Erwartung: ein Browserfenster öffnet sich, ich bestätige den Zugriff, fertig.
Stattdessen kam: „Couldn’t reach the MCP server“ — „der Server ist nicht erreichbar“. Mehr nicht. Kein Detail, kein Code, kein Hinweis worauf.
Ich habe es ein paarmal wiederholt. Vielleicht ein Hänger. Vielleicht ein kurzer Aussetzer auf meiner Seite.
Was nicht passierte, war das eigentliche Rätsel
Ich habe in die Logs geschaut — also in die Protokolle, die mein Server über jeden Besuch führt. Da steht normalerweise jede Anfrage drin, jeder Versuch, jeder Klopfer an der Tür.
Da stand: nichts.
Kein einziger Zugriff während all der Versuche. Niemand klopfte auch nur an die Tür.
Zur Kontrolle habe ich von verschiedenen Stellen aus selbst an die Tür geklopft: vom eigenen Netzwerk, vom Handy, von einem fremden Rechner irgendwo im Internet. Überall kam ich problemlos durch. Das Problem lag nicht bei mir.
Alle Pflichtprüfungen bestanden
Der Anmelde-Mechanismus folgt einem öffentlichen Regelwerk — einer „Spec“. Das ist eine Art Bauplan, an den sich alle halten, damit verschiedene Systeme zusammenarbeiten können. Wer spec-konform ist, hat sich an alle Regeln gehalten.
Ich habe alle acht relevanten Prüfungen einzeln durchgespielt, von einem dritten Standort aus. Jeder einzelne Endpunkt — das sind die Stellen, an denen mein Server antwortet — gab genau das zurück, was die Spec vorschreibt.
Acht von acht grün. Ab da habe ich aufgehört, im eigenen Code nach Fehlern zu suchen. Etwas anderes musste falsch sein.
Jemand anderes hatte das Problem schon
Eine GitHub-Suche später bin ich auf eine Diskussion gestoßen, die jemand eine Woche vor mir gestartet hatte. Ein Startup im Beschleuniger-Programm, mit Launch-Termin in den nächsten Wochen, mit Kunden, die warteten.
Die Symptome: exakt identisch. Server-konform, Logs leer, „Couldn’t reach the MCP server“. Der andere Entwickler hatte sogar live mitprotokolliert, während er es versuchte — null eingehende Anfragen.
Jemand anderes hatte schon eine Woche mit demselben Problem gerungen, mit deutlich höherem Druck.
Seine Diagnose: Anthropic — der Anbieter hinter Claude — startet den Anmelde-Vorgang gar nicht erst. Irgendetwas auf deren Seite weist meinen Server ab, bevor überhaupt eine Anfrage rausgeht.
Datenpunkt hinzufügen
Ich habe in dieser Diskussion einen Kommentar hinterlassen. Kurz und sachlich:
“Same symptom on a different server — adding a data point. Setup: Custom MCP server, OAuth 2.1 spec-compliant. […] Server-side evidence: Traefik access log shows ZERO requests from claudeai-proxy during repeated ‘Add custom connector’ attempts.” —
syswave-dev(Issue-Kommentar)„Gleiches Symptom auf einem anderen Server — füge einen Datenpunkt hinzu. Setup: Eigener Server, spec-konform aufgesetzt. […] Beleg auf meiner Seite: Mein Server-Protokoll zeigt NULL Anfragen vom Claude-Proxy bei wiederholten ‚Add custom connector’-Versuchen.“ —
syswave-dev
Ich habe auch die Referenz-ID dazugeschrieben, die claude.ai mir nach dem Fehlschlag angezeigt hatte. Eine Art Quittungsnummer — falls Anthropic in den eigenen Logs nachschauen will, was bei diesem Versuch passiert ist.
Ein zweiter Server als Beweis
Am nächsten Morgen habe ich zusammen mit Claude Code einen zweiten Server gebaut. Komplett neu: andere Programmiersprache, andere Datenbank, andere Werkzeuge. Aber dieselben Spec-Regeln, dieselbe Sorgfalt.
Diesen zweiten Server wollte ich ohnehin bauen — und er war zugleich das beste Argument in der Diskussion: Wenn zwei völlig unabhängige Server auf verschiedenen Grundlagen exakt gleich scheitern, kann der Fehler nicht an meinem Code liegen.
Auch der zweite Server: dieselbe Symptomatik. Logs leer, dieselbe nichtssagende Fehlermeldung.
Zweiter Kommentar in der Diskussion:
“Built a second independent OAuth-MCP server (different codebase, different stack — TypeScript/PostgreSQL instead of JavaScript/SQLite). Same exhaustive spec compliance, same symptom on ‘Add custom connector’ in claude.ai web UI. […] Two independent codebases failing identically rules out implementation-specific bugs on the user side.” —
syswave-dev(Issue-Kommentar)„Einen zweiten unabhängigen Server gebaut (andere Codebase, andere Werkzeuge). Gleiche Spec-Sorgfalt, gleiches Symptom in der claude.ai-Web-Oberfläche. […] Zwei unabhängige Server, die identisch fehlschlagen, schließen Bugs in meinem Code aus.“ —
syswave-dev
Damit war die Frage „was habe ich falsch gemacht?“ beantwortet: nichts.
Die Auflösung
An dem Morgen, an dem ich diesen Artikel schreiben wollte, gab es in der Diskussion eine neue Antwort. Von einem Mitarbeiter bei Anthropic. Das Ticket wurde geschlossen:
“#256 captured an
x-deny-reason: host_not_allowedheader from Anthropic’s egress proxy, which is a strong signal that the underlying cause is a host allowlist on our side rather than anything in your server.” — @localden (Anthropic, Issue-Close)„In einem anderen Ticket hat jemand einen Hinweis-Header von unserem ausgehenden Proxy abgefangen, der ‚host_not_allowed’ sagt — ‚dieser Host steht nicht auf der erlaubten Liste’. Das deutet stark darauf hin, dass die Ursache eine Freigabe-Liste auf unserer Seite ist, nicht etwas in deinem Server.“ — @localden (Anthropic)
In Klartext: Anthropic führt eine Liste erlaubter Server. Wer nicht draufsteht, kommt nicht durch — und zwar so früh, dass der eigene Server gar nichts davon mitbekommt.
Es war kein Bug in meinem Code. Es war eine Tür, die ich von meiner Seite aus nicht öffnen kann.
Was ich nicht in der Hand hatte
Über die letzten drei Teile zog sich ein roter Faden: Dinge, die anders liefen als gedacht.
In Teil 5 war es die Resonanz auf einen Reddit-Post, mit der ich nicht gerechnet hatte: mehr Aufmerksamkeit, mehr Nachfragen, mehr Anregungen als geplant.
In Teil 6 war es ein Umweg in der Architektur, den ich nicht eingeplant hatte. Anfragen von außen haben die Reihenfolge meiner Aufgaben verändert.
Und hier, in Teil 7, ist es eine Hürde, die ich gar nicht beeinflussen kann: eine Liste auf der anderen Seite, auf der mein Server (noch) nicht steht. Bei den ersten beiden hätte ich anders planen oder priorisieren können. Hier nicht — der Code ist fertig, der Rest liegt bei jemand anderem.
Wer auf der Infrastruktur eines anderen aufbaut, muss solche Wartezeiten akzeptieren. Das ist keine Beschwerde, sondern gehört einfach dazu. Ich habe deshalb dokumentiert statt mich zu ärgern: was gebaut wurde, was ich geprüft habe, welche Referenz-IDs ich habe, was die Diskussion ergeben hat. Wer dasselbe Problem hat, findet das beim Suchen.
Die neue Version meines Tools liegt jetzt einsatzbereit im Regal — fertig, aber zurückgehalten, bis die Tür auf der anderen Seite aufgeht.
Teil 7 von 9 der PullMD Serie.
← PullMD Teil 6: Multi-User-Pivot | PullMD Teil 8: Eine alte Regel →