# Transport und Resilienz: Grenzen unter Störung

> Entscheide zwischen lokalem und geteiltem Betrieb, sichere HTTP-Grenzen ab und behandle Streamabbrüche, Fehlerkanäle, Wiederholungen und Teilausfälle ohne Doppelwirkung.

Track: [MCP Engineering](https://physar.tech/learn/mcp-engineering)  
Kanonische Fassung: https://physar.tech/learn/mcp-engineering/transport-resilience  
Stand: 2026-09-05  
Interaktiver Teil: 8 Checks (nur im Browser)

## Transport und Resilienz: Korrekt bleiben, wenn Verbindungen versagen

### Transport ist eine Vertrauens- und Fehlergrenze

MCP trennt fachliche Nachrichten von ihrem Transport. Für die Implementierung ist die Transportwahl trotzdem folgenreich: Sie bestimmt, wer den Server startet, wie Identität nachgewiesen wird, welche Netzkomponenten zwischen Client und Server liegen und welche Fehler unabhängig voneinander auftreten können. Ein lokaler Kindprozess über `stdio` erbt einen Betriebskontext. Ein geteilter HTTP-Dienst muss diesen Kontext ausdrücklich herstellen und gegen andere Nutzer abschirmen.

Resilienz heißt nicht, jede Störung mit Wiederholung zu beantworten. Zuerst muss das System unterscheiden: Ist die Nachricht technisch ungültig, ist die Fachoperation gescheitert, kam nur die Antwort nicht an, oder ist eine ganze Fähigkeit derzeit nicht verfügbar? Diese Zustände verlangen andere Reaktionen. Wer sie in „Fehler“ zusammenfasst, erzeugt Schleifen, Doppelwirkungen und irreführende Nutzertexte. Der wichtigste Entwurfsgegenstand ist deshalb die **Bedeutung unter Unterbrechung**.

> **Leitfrage:** Was weiß jede Seite nach Verbindungsabbruch sicher — und welchen Fachzustand muss sie erst wieder feststellen?

### stdio begrenzt Reichweite, nicht Prozessrechte

Bei `stdio` startet der Host den MCP-Server gewöhnlich als Kindprozess und tauscht JSON-RPC-Nachrichten über Standard-Ein- und -Ausgabe aus. Es gibt keinen lauschenden Netzwerkport. Das vereinfacht Distribution für persönliche Werkzeuge und kann die erreichbare Angriffsfläche verkleinern. Der Prozess läuft aber typischerweise mit den Rechten des angemeldeten Nutzers. Er kann lokale Dateien, Umgebungswerte und erreichbare Dienste so weit sehen, wie Betriebssystem und Sandbox es erlauben.

|  |  |
| --- | --- |
| Eigenschaft | Betriebliche Folge |
| Host startet Prozess | Lebensdauer und Versionierung hängen an der lokalen Installation |
| Nutzerrechte geerbt | Jeder Server ist ausgeführter Code im persönlichen Vertrauensbereich |
| Kein Listener | Keine eingehende Netzfreigabe, aber ausgehender Zugriff bleibt möglich |
| Lokale Credentials | Widerruf, Rotation und Audit müssen auf vielen Geräten funktionieren |

Für einen Entwicklerprototyp kann dieses Modell passend sein. Für Hunderte Nutzer ist es nicht automatisch die einfachste Lösung: Verteilung, Updates, Provenienz, lokale Secrets und Diagnose vervielfachen sich. Entscheide anhand des Besitzmodells. Ein persönliches Dateisystemwerkzeug kann lokal bleiben; eine zentrale Buchungsfunktion mit organisationsweiter Policy braucht meist einen geteilten Dienst und explizite Nutzeridentität.

> **Merksatz:** „Lokal“ beantwortet die Frage nach Erreichbarkeit, nicht nach Vertrauenswürdigkeit oder minimalen Rechten.

### HTTP macht Identität und Zustandslosigkeit explizit

Ein HTTP-Server ist unabhängig vom Host betreibbar, zentral aktualisierbar und horizontal skalierbar. Dafür braucht er Authentifizierung, Autorisierung, TLS-Terminierung, Rate Limits und beobachtbare Betriebsgrenzen. Die Revision `2026-07-28` ist im Core stateless: Es gibt keinen Initialisierungs-Handshake und keine Protokollsession. Version und Client-Capabilities stehen pro Request in `_meta`; der Server verarbeitet den Request aus diesen Angaben, seiner Autorisierung und expliziten Fachparametern.

```json
{
  "jsonrpc": "2.0",
  "id": 42,
  "method": "tools/call",
  "params": {
    "_meta": {
      "protocolVersion": "2026-07-28",
      "clientCapabilities": {}
    },
    "name": "reindex_catalog",
    "arguments": { "catalogId": "eu" }
  }
}
```

Speichert ein Worker trotzdem „die letzte Version“ oder „den aktuellen Tenant“, kollidiert das mit freiem Routing und parallelen Clients. Sticky Sessions kaschieren diesen Fehler nur und machen Knotenausfall sichtbar. Fachzustand über mehrere Requests bekommt einen opaken Handle; Zugriff wird bei jedem Call erneut autorisiert. Dann darf der Load-Balancer jeden gesunden Worker wählen.

> **Trade-off:** Zustandslose Protokollverarbeitung vereinfacht Skalierung, verlangt aber explizite Persistenz für jeden wirklich benötigten Fachzustand.

### Lokales HTTP bleibt ein echter Netzwerkendpunkt

Desktop-Anwendungen starten manchmal einen HTTP-Server auf demselben Rechner. Bindet er an `0.0.0.0`, ist er nicht nur lokal, sondern über alle passenden Interfaces erreichbar. Bindung an Loopback reduziert diese Reichweite, schützt jedoch nicht automatisch vor anderem lokalem Code oder einer Webseite, die der Nutzer im Browser geöffnet hat. Der Server muss die erwartete Herkunft prüfen und einen echten Authentifizierungsmechanismus verlangen.

Nur an Loopback binden → Erwartete `Origin`-Werte prüfen → **Client authentifizieren** → Jeden fachlichen Zugriff autorisieren → Fehlversuche begrenzen und protokollieren

Ein zufälliger Port ist kein Secret. Portscans und Browseranfragen können ihn finden; auch der Prozesskontext des Nutzers beweist nicht, wer einen Request ausgelöst hat. Vermeide weitreichende Langzeittokens in URL, Log oder frei lesbarer Konfiguration. Wenn der lokale HTTP-Modus keinen klaren Nutzen gegenüber `stdio` bietet, ist der kleinere Transport oft leichter zu härten. Wenn Browser- oder Mehrprozessintegration ihn verlangt, behandle ihn wie jede andere privilegierte API.

> **Prüfregel:** Netzwerkposition ist keine Identität, und Loopback ist keine Autorisierung.

### Proxys verändern Streams, Fristen und Beobachtbarkeit

Zwischen Client und Server liegen häufig Reverse-Proxy, Load-Balancer, WAF oder Service Mesh. Jede Schicht kann Antworten puffern, Idle-Verbindungen schließen, maximale Requestdauer erzwingen oder Header verändern. Ein lokal erfolgreicher Stream beweist daher wenig über Produktion. Prüfe die gesamte Strecke mit realistischen Dauern und Datenmengen. Konfiguriere Timeouts bewusst von außen nach innen und beobachte, welche Komponente tatsächlich beendet hat.

|  |  |
| --- | --- |
| Symptom | Mögliche Grenze |
| Erste Daten kommen erst am Ende | Response-Buffering im Proxy |
| Abbruch immer nach 60 Sekunden | Idle- oder Request-Timeout einer Zwischeninstanz |
| Nur große Ergebnisse scheitern | Body-, Buffer- oder Speichergrenze |
| Einzelne Replikate verlieren Events | Prozesslokaler Event-Bus bei verteilten Streams |

Keep-alives oder kleine Fortschrittsdaten können Idle-Timeouts vermeiden, machen die Fachoperation aber nicht transaktional. Auch ein korrekt konfigurierter Stream kann durch Netzwechsel, Deploy oder Clientabbruch enden. Plane deshalb immer den Zustand **nach** dem Streamverlust: Kann der Client erneut lesen, einen Status abfragen oder den Subscription-Stream neu öffnen? Gibt es keine Replay-Garantie, darf er keine lückenlose Historie vortäuschen.

> **Merksatz:** Eine stabile Verbindung verbessert Latenz; Korrektheit darf trotzdem nicht von ihrer Unsterblichkeit abhängen.

Lege für jede Grenze einen **Besitzer der Frist** fest. Der Client begrenzt seine Wartezeit aus Sicht der Nutzerinteraktion. Der Gateway schützt Verbindungen und Kapazität. Der Server begrenzt interne Arbeit, und ein Downstream hat nochmals eigene Fristen. Sind alle Werte zufällig gleich, bleibt bei einem Abbruch keine Zeit für eine kontrollierte Fehlerantwort oder Aufräumarbeit. Üblicherweise endet die innerste Operation zuerst, dann der Server und zuletzt die äußere Nutzerfrist. Die konkreten Zahlen hängen vom Dienst ab; entscheidend ist ihre begründete Staffelung. Übertrage außerdem die verbleibende Deadline, nicht bei jedem Hop eine neue volle Frist. Sonst kann eine kurze Nutzerfrist hinter mehreren Retries zu minutenlanger Hintergrundarbeit wachsen. Metriken sollten die beendende Schicht und die verbrauchte Zeit ausweisen. So erkennst du, ob eine Optimierung am Tool, am Proxy oder am Retry-Verhalten nötig ist, statt überall pauschal größere Timeouts einzutragen.

### Langläufer werden zu adressierbaren Vorgängen

Dauert eine Reindizierung mehrere Minuten, ist eine einzige offene Response ein fragiler Eigentümer ihres Ergebnisses. Besser ist häufig ein zweistufiger Vertrag: Das wirkende Tool akzeptiert einen Vorgangsschlüssel, startet oder erkennt die Arbeit und liefert einen opaken Jobhandle. Ein lesender Folgeaufruf berichtet Status und Ergebnis. Abbruch, Ablauf und Aufbewahrung sind dann Eigenschaften des Vorgangs, nicht des TCP-Flows.

Absicht mit Idempotency-Key senden → **Vorgang erstellen oder bestehenden erkennen** → Jobhandle zurückgeben → Status mit Handle lesen → Ergebnis oder kompensierbaren Fehler abschließen

Diese Form kostet Persistenz und einen zusätzlichen Aufruf. Dafür kann ein Client nach Unterbrechung feststellen, was geschah, statt blind neu zu starten. Der Handle ist keine Berechtigung: Authentifizierte Server binden ihn an Principal und Tenant. Ohne Authentifizierung wirkt er als Bearer Token und braucht ausreichend Entropie, kurze Lebensdauer und geringe Offenlegung. Definiere außerdem, ob Cancellation nur das Warten beendet oder den Hintergrundjob fachlich abbrechen soll.

> **Entwurfsziel:** Der Vorgang überlebt die Verbindung; seine Wirkung bleibt einmalig und sein Zustand später feststellbar.

### Fehlerkanäle richten sich nach dem nächsten Akteur

JSON-RPC-Fehler und Tool-Ausführungsfehler haben verschiedene Adressaten. Eine unbekannte Methode, syntaktisch ungültige Nachricht oder Schemaabweichung muss der Client technisch korrigieren. Ein belegter Raum, überschrittenes Fachlimit oder nicht lieferbarer Artikel ist dagegen eine gültig angeforderte Operation mit fachlichem Fehlschlag. Diese Information gehört in das Tool-Ergebnis, damit Modell oder Nutzer eine Alternative wählen kann.

|  |  |
| --- | --- |
| JSON-RPC-Fehler | Nachricht oder Aufruf technisch nicht verarbeitbar; Client reagiert |
| Tool-Ausführungsfehler | Aufruf gültig, Fachziel nicht erreicht; Modell oder Nutzer reagiert |
| Transportabbruch | Ausgang möglicherweise unbekannt; Status- und Wiederholungslogik reagiert |

Ein fachlicher Fehler soll handlungsleitend sein: Was scheiterte, welche zulässige Änderung hilft, und ist Wiederholung sinnvoll? Er darf keine Stacktraces, internen Hostnamen oder Daten fremder Mandanten offenlegen. Markiere einen Fehlschlag nicht als Erfolg mit `booked: false`, wenn Auswertungen und Hosts Erfolg und Fehler unterscheiden sollen. Ebenso wenig gehört eine korrigierbare Fachentscheidung in einen generischen Protokollfehler, den das Modell nie sinnvoll sieht.

**Kurzcheck:** Ein gültiger Tool-Aufruf findet den Raum, aber er ist belegt. Wer kann den nächsten Schritt wählen?

- [x] Modell oder Nutzer; der Fehlschlag gehört handlungsleitend in das Tool-Ergebnis.
- [ ] Nur der JSON-RPC-Parser; er muss die Nachricht als ungültig ablehnen.
- [ ] Der Transport; er soll dieselbe Nachricht bis zum Erfolg wiederholen.

> Richtig. Der Vertrag war technisch gültig, nur das Fachziel ist gescheitert.

### Timeout und Cancellation lassen Fachwirkung offen

Ein Client-Timeout beweist nur, dass innerhalb seiner Frist keine Antwort ankam. Der Server kann den Request nie gesehen, gerade verarbeitet oder bereits vollständig ausgeführt haben. Sendet der Client anschließend `notifications/cancelled`, erhält der Server ein kooperatives Stoppsignal. Der Empfänger soll Arbeit beenden, Ressourcen freigeben und für diesen Request keine weitere Nachricht senden. Das Signal rollt bereits angenommene Zahlungen, Uploads oder Migrationen jedoch nicht automatisch zurück.

Schreibe deshalb nach Timeout nicht „wurde nicht ausgeführt“ in Modellkontext oder UI. Der ehrliche Zustand lautet „Ausgang unbekannt“, ergänzt um Vorgangsschlüssel und Statuspfad. Diese Formulierung wirkt weniger bequem, verhindert aber gefährliche Folgeschlüsse. Kann eine Außenschnittstelle nicht abgefragt werden, braucht der interne Prozess einen Abgleich oder eine Kompensation. Eine fehlende Response bleibt sonst dauerhaft mehrdeutig.

> **Korrektheitsgrenze:** Cancellation beendet die zulässige Nachrichtenfolge; Idempotenz und Status klären die bereits mögliche Fachwirkung.

Teste besonders die Race-Grenzen: Cancellation unmittelbar vor und nach dem Commit, Antwortverlust nach erfolgreichem Commit und doppelter Request während der ersten Ausführung. Logs müssen unterscheiden, ob der Server den Auftrag annahm, eine Wirkung bestätigte und ein Result senden konnte. Nur dann lässt sich ein Incident rekonstruieren, ohne aus Transporttelemetrie eine fachliche Wahrheit abzuleiten.

### Wiederholung braucht Schlüssel, Budget und Klassifikation

Retries sind bei kurzzeitigen Transportfehlern nützlich, aber nur begrenzt. Ein wirkendes Tool erhält einen vom Aufrufer stabil wiederverwendeten Idempotency-Key. Der Server speichert dazu Status und Ergebnis atomar genug, dass zwei parallele Zustellungen nicht zweimal wirken. Der Schlüssel bezeichnet dieselbe Absicht; er darf nicht für veränderte Parameter wiederverwendet werden. Der Server sollte solche Konflikte sichtbar ablehnen.

- **Wiederholbar:** kurzzeitiger Verbindungsfehler vor bekannter Annahme, mit Backoff und Jitter.
- **Status zuerst:** Timeout nach möglicher Annahme einer wirkenden Operation.
- **Nicht wiederholen:** ungültiges Schema, fehlende Berechtigung oder stabiler Fachkonflikt.
- **Budget erschöpft:** Fähigkeit vorübergehend aus der aktiven Auswahl nehmen und im Hintergrund prüfen.

Ein Retry-Budget begrenzt Versuche pro Request, Server und Zeitfenster. Exponentieller Backoff mit Jitter verhindert, dass viele Hosts einen erholenden Dienst gleichzeitig überlasten. Budgets gelten auch für MRTR-Runden und Statusabfragen: Endlosschleifen verbrauchen Kontext und Geld, selbst wenn jeder einzelne Aufruf billig ist. Metriken sollten Erstversuch, Retry, deduplizierte Wiederholung und endgültigen Fehlschlag getrennt zählen.

> **Preis:** Sichere Wiederholung erfordert Speicher und Ablaufregeln; unsichere Wiederholung verlagert den Preis in reale Doppelwirkung.

Der Idempotency-Speicher braucht selbst einen Vertrag. Speichere neben dem Schlüssel einen Fingerabdruck der normalisierten Argumente, den Bearbeitungsstatus und das kanonische Ergebnis. Trifft derselbe Schlüssel mit anderen Argumenten ein, ist das ein Konflikt und kein Cachetreffer. Läuft die erste Ausführung noch, dürfen parallele Requests nicht beide die Wirkung starten; sie warten begrenzt, erhalten den aktuellen Status oder denselben Jobhandle. Die Aufbewahrungsfrist muss mindestens das realistische Wiederholungsfenster abdecken und zu den Fachregeln passen. Bei Zahlungen kann sie deutlich länger sein als bei einer temporären Analyse. Lösche den Eintrag nicht unmittelbar nach dem ersten erfolgreichen Response, denn gerade ein verloren gegangenes Resultat löst den späteren Retry aus. Achte zugleich auf Mandantentrennung: Derselbe vom Client gewählte Schlüssel zweier Nutzer darf niemals denselben Vorgang bezeichnen. Gute Telemetrie zählt neue, laufende, deduplizierte und konfligierende Schlüssel getrennt, ohne den Schlüssel selbst als frei durchsuchbares Secret zu behandeln.

### Teilausfälle bleiben lokal und sichtbar

Ein Host kann Fähigkeiten mehrerer Server aggregieren. Fällt einer davon aus, sollen die übrigen weiter funktionieren. Ein dauerhaft angebotenes, aber totes Tool lockt das Modell in Timeouts und wiederholte Fehlentscheidungen. Entferne seine Fähigkeiten nach einer definierten Schwelle aus der aktiven Auswahl oder markiere sie eindeutig als nicht verfügbar. Prüfe die Erholung mit begrenzten Hintergrundversuchen und füge die Fähigkeiten erst nach belastbarem Erfolg wieder hinzu.

_[Abbildung: Ein ausgefallenes Tool in der Auswahl erzeugt bei jedem Planungsschritt neue Kosten und weitere Fehlversuche.]_

`server/discover` hilft, aktuelle Serverfähigkeiten explizit zu erfassen; es ersetzt keine Health-Policy. Discovery-Erfolg bedeutet, dass der Server antwortete, nicht dass jedes Backend gesund ist. Umgekehrt darf ein einzelner Toolfehler nicht sofort den ganzen Server sperren. Definiere Zustände wie verfügbar, degradiert, offen und prüfbereit sowie Schwellen für Übergänge. Zeige Nutzern die konkrete Lücke, statt die gesamte Sitzung zu beenden oder still eine andere, wirkende Fähigkeit zu wählen.

> **Betriebsregel:** Entferne erwiesen tote Optionen aus der Modellentscheidung, aber automatisiere ihre kontrollierte Rückkehr.

Eine Circuit-Breaker-Policy braucht genug Kontext, um nicht gesunde Fähigkeiten mitzusperren. Gruppiere nach Server, Backend oder konkretem Tool entsprechend der gemeinsamen Fehlerursache. Fünf Timeouts eines einzelnen langsamen Exports dürfen nicht zwingend schnelle Lese-Tools desselben Servers entfernen. Authentifizierungsfehler eines Tenants sind wiederum kein globaler Serverausfall. Verwende Mindeststichprobe und Zeitfenster, damit ein einzelner Zufallsfehler keinen Zustandswechsel auslöst. Im offenen Zustand wird die Fähigkeit nicht dem Modell angeboten; ein kleiner, kontrollierter Prüfverkehr entscheidet über die Rückkehr. Diese Zustände gehören in Metriken und Nutzerhinweise. Ohne Sichtbarkeit wirkt das Entfernen wie eine zufällig verschwundene Funktion. Ohne Begrenzung wirkt die Erholung wie eine neue Lastspitze. Der Host muss schließlich auch seinen Kontext bereinigen: Eine veraltete Toolbeschreibung im bereits aufgebauten Prompt darf das Modell nicht weiter zu einer Fähigkeit planen lassen, die die aktive Policy gerade ausgeschlossen hat.

### Die Produktionsprobe verbindet alle Grenzen

Transport und Identität festlegen → Stateless Routing unter mehreren Replikaten prüfen → Proxy- und Streamabbrüche injizieren → **Status und Idempotenz bei Wirkung testen** → Fehlerkanäle und Retry-Budget beobachten → Teilausfall und automatische Erholung nachweisen

Eine belastbare Abnahme enthält keine reine Happy-Path-Demo. Starte zwei Nutzer parallel, verteile ihre Requests auf verschiedene Replikate und kappe die Verbindung an den gefährlichsten Stellen. Wiederhole denselben wirkenden Auftrag gleichzeitig. Lass einen Backenddienst langsam und einen ganzen MCP-Server unerreichbar werden. Prüfe danach Fachzustand, sichtbare Toolmenge, Logs, Metriken und Nutzertext. Jede Schicht soll die Unsicherheit benennen, die sie wirklich kennt.

- **Transport:** minimale Reichweite, authentifizierte Gegenstelle und dokumentierte Proxygrenzen.
- **Zustand:** kein impliziter Connection- oder Workerbesitz.
- **Wirkung:** Idempotency-Key, Status und gegebenenfalls Kompensation.
- **Fehler:** technischer und fachlicher Kanal mit richtigen Adressaten.
- **Resilienz:** begrenzte Retries, lokale Degradation und automatische Erholung.

> **Ausblick:** Das nächste Modul betrachtet den Host und Client als Orchestrator dieser Verträge: Discovery, Policy, Nutzerinteraktion und Kontextaufbereitung. Nimm dafür besonders die ehrliche Unterscheidung zwischen technischem Fehlschlag, fachlichem Ergebnis und unbekannter Wirkung mit.

## Quellen

- modelcontextprotocol.io/specification/2026-07-28/architecture — https://modelcontextprotocol.io/specification/2026-07-28/architecture
- modelcontextprotocol.io/specification/2026-07-28/basic/index — https://modelcontextprotocol.io/specification/2026-07-28/basic/index
- modelcontextprotocol.io/specification/2026-07-28/basic/versioning — https://modelcontextprotocol.io/specification/2026-07-28/basic/versioning
- modelcontextprotocol.io/specification/2026-07-…ilities/cancellation — https://modelcontextprotocol.io/specification/2026-07-28/basic/utilities/cancellation
- modelcontextprotocol.io/specification/2026-07-28/server/discover — https://modelcontextprotocol.io/specification/2026-07-28/server/discover
- modelcontextprotocol.io/specification/2026-07-28/server/tools — https://modelcontextprotocol.io/specification/2026-07-28/server/tools
- jsonrpc.org/specification — https://www.jsonrpc.org/specification
