Tutorial

Remote-MCP-Server in Produktion: zustandsloses MCP 2026 + HTTP-Load-Balancer + JSON-RPC

Protokoll-Zustandslosigkeit zahlt sich nur aus, wenn Sie wirklich einen normalen HTTP-Load-Balancer nutzen. Produktion heißt Replikas skalieren, Proxys die SSE nicht puffern, Drains die lange Streams nicht kappen — nicht eine neue Session erfinden.

Nach 2026-07-28 nagelt Remote-MCP einen Client nicht mehr mit initialize plus Mcp-Session-Id an einen Prozess. Jede JSON-RPC-Anfrage trägt Protokollversion und Client-Fähigkeiten in _meta; Streamable HTTP spiegelt die nützlichen Felder in HTTP-Header, damit Load Balancer und Gateway ohne Body-Parse routen, limitieren und messen können. Die harte Produktionsarbeit ist nicht tools/call zu schreiben. Es ist, Replikas zu skalieren, eine Balancer-Politik zu wählen, nginx am Puffern von SSE-Fortschritt zu hindern, subscriptions/listen beim Rollout zu leeren und wo Anwendungszustand lebt. Warum das Protokoll zustandslos ist, steht in Zustandsloses MCP; wer rufen darf, in OAuth 2.1. Dieser Text ergänzt nur „wie es hinter einem HTTP-Load-Balancer hängt“. Transport: Streamable HTTP. Umschlag: JSON-RPC 2.0. Zwingen Sie diese öffentliche Topologie keinem lokalen stdio-Kindprozess auf.

Produktionsfalle: Session-Affinität macht Horizontal Scale zum Single Point

Viele hören „zustandslos“ und liefern einen Container. Der Gewinn erscheint nur mit einem normalen HTTP-Load-Balancer — Round-Robin, Least-Conn, CPU-gewichtet — nicht mit Session-Affinität. Ältere Revisionen nutzten eine verbindungsgebundene Session: nach dem Handshake mussten spätere tools/call den Prozess mit Mcp-Session-Id treffen. Mehr Replikas bedeuteten Cookie-Stickiness oder IP-Hash auf ALB oder nginx; eine tote Box tötete die ganze Agent-Session. 2026-07-28 hat Protokoll-Sessions entfernt. Jede Replika soll jeden POST allein beenden.

Cursor, Claude Desktop, OpenAI Remote-MCP und selbstgebaute Agent-Hosts serialisieren nicht höflich auf einen Endpunkt. Dieselbe Sekunde kann tools/list, tools/call und ein langlebiges subscriptions/listen tragen. Diese drei Hops müssen nicht auf derselben Instanz landen. Affinität nach Client-IP macht Horizontal Scale zum Single Point und friert Limits und Canaries an eine Box. Protokolleinstieg: Was ist MCP. Agent-Schleife: Was ist ein KI-Agent.

Gegen unsere anderen Schichten: Zustandslosheit entfernte die Protokoll-Session, nicht Geschäftsdaten. Rechnungsentwürfe, Warenkörbe, unfertige Mehr-Runden-Tool-Aufrufe gehen weiter nach Redis oder eine Datenbank und kommen über eine draftId in arguments zurück. Eine prozesslokale Map ist kein Produktionszustand. Auth ist auch nicht diese Schicht — Bearer für öffentliches Remote-MCP lebt in HTTP-Headern; siehe den OAuth-Artikel. Hier sind die drei Schichten schon getrennt; es geht nur um Topologie und den Betriebskontrakt.

Ansatz Was der Load Balancer sieht Produktionsfolge
Cookie-/IP-Affinität + Session im ProzessMuss denselben Client an dasselbe Upstream klebenSchwer zu skalieren, Rollouts brechen, Single Point
Zustandslose Replikas + gewöhnlicher HTTP-LBJede Replika kann jedes JSON-RPC-POST nehmenHorizontale Skalierung, Canaries, Limits pro Tool
Serverless-Kaltstart + ein POSTKein langlebiger Prozess, der einen Handshake „merkt“Gut für kurzes RPC; langes SSE braucht eigene Timeouts

Zielarchitektur: Client → HTTP-LB → N zustandslose Replikas

Halten Sie die Topologie dünn: öffentliches DNS → TLS-Terminierung (ALB, NLB plus Sidecar, nginx, Caddy, Cloud-LB) → ein Satz MCP-Replikas mit gleichem Image und gleicher Config. Hängen Sie keinen „MCP-Session-Store“ davor, um das Protokoll zu kitten. Healthchecks nutzen ein getrenntes GET /healthz: Prozess oben, Abhängigkeiten erreichbar. POSTen Sie keinen leeren Body an /mcp und nutzen Sie kein lebendes tools/list als Sonde — das trifft die echte Registry und kann 401 liefern, die Replika fälschlich leeren.

Jede Replika muss einen Hop allein beenden: Origin prüfen (DNS-Rebinding → 403), MCP-Protocol-Version / Mcp-Method / Mcp-Name lesen, Bearer prüfen falls die Ressource geschützt ist, den JSON-RPC-Umschlag parsen, das Tool ausführen, ein einzelnes JSON-Objekt oder einen anfragegebundenen SSE-Stream zurückgeben. Die Spezifikation will einen MCP-Endpunkt, der POST annimmt, etwa https://mcp.example.com/mcp. GET-Streams und Protokoll-Sessions sind in 2026-07-28 weg. Öffnen Sie GET /sse nicht „für alte Healthchecks“ wieder.

Was Replikas teilen, sind Anwendungs-Abhängigkeiten: Datenbank, Object Storage, Drittanbieter-APIs, optionales Redis. Sie teilen nicht „welche MCP-Verbindungen offen sind“. Cloud Run, Cloud Functions, Knative mit Skalierung pro Anfrage passen zu diesem Modell. Was Sie noch justieren, ist Idle-Timeout langer SSE, nicht Session-Klebrigkeit. Lassen Sie den Prozess als unprivilegierten User hinter dem Proxy laufen; binden Sie MCP nicht auf 0.0.0.0:80 ins öffentliche Netz.

Wie JSON-RPC 2.0 den Load Balancer kreuzt

MCP kodiert Nachrichten als JSON-RPC 2.0, UTF-8 Pflicht. Auf Streamable HTTP ist jede Client-Anfrage oder -Notification ein neues HTTP-POST; Server starten keine JSON-RPC-Anfragen. Der Load Balancer braucht die fachliche Bedeutung von method nicht — er leitet Bytes weiter. Der Transport 2026 spiegelt method nach Mcp-Method und Tool-/Resource-/Prompt-Namen nach Mcp-Name, damit Zwischengeräte ohne Body-Parse pro Tool limitieren, nach Methode sharden und nach Version canaryen können.

Der Body bleibt die Wahrheit. MCP-Protocol-Version im Header muss bytegenau zu params._meta.io.modelcontextprotocol/protocolVersion passen, sonst MUSS der Server 400 mit HeaderMismatch liefern. Clients müssen auch Accept: application/json, text/event-stream senden. Ein akzeptiertes Notification-POST liefert 202 Accepted ohne Body; eine Anfrage liefert ein JSON-Objekt oder einen SSE-Stream. Die JSON-RPC-id paart Anfrage und Antwort dieses Hops. Sie ist kein Session-Schlüssel und darf nicht genutzt werden, um Kontext zwischen Replikas „wiederzufinden“.

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 42,
  "error": {
    "code": -32600,
    "message": "HeaderMismatch: MCP-Protocol-Version does not match params._meta"
  }
}

Unten ein produktionsförmiges tools/call: Auth am HTTP-Header, Umschlag im Body, Routing-Schlüssel die das Gateway sieht ebenfalls am Header. Ein Access Token in params oder _meta zu stopfen ist kein Remote-MCP 2026. Die Argumentform braucht weiter einen Schema-Check: MCP und JSON Schema.

POST /mcp HTTP/1.1
Host: mcp.example.com
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: searchInvoices
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

{
  "jsonrpc": "2.0",
  "id": 42,
  "method": "tools/call",
  "params": {
    "name": "searchInvoices",
    "arguments": {
      "startDate": "2026-01-01",
      "endDate": "2026-01-31",
      "status": "paid"
    },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {},
      "io.modelcontextprotocol/clientInfo": {
        "name": "jsonvue-demo",
        "version": "1.0.0"
      }
    }
  }
}

Streamable HTTP: Puffer, Timeouts und SSE

Kurze Tool-Aufrufe sollen Content-Type: application/json liefern. Lange Arbeit darf text/event-stream sein: zuerst anfragebezogene notifications/progress, dann eine finale JSON-RPC-Antwort die den Stream beendet. Die meisten Produktionsvorfälle sitzen im Reverse-Proxy, nicht im MCP-SDK. nginx proxy_buffering hält Fortschrittsereignisse standardmäßig und spült sie als einen Brocken; der Agent wirkt festgefahren. Die Spezifikation sagt Servern, X-Accel-Buffering: no für Zwischengeräte zu senden. Siehe nginx proxy_buffering.

Auf Streamable HTTP ist das Abbruchsignal, dass der Client dieses SSE schließt, nicht ein folgendes notifications/cancelled-POST (das ist die stdio-Bindung). Der LB muss Backend-Trennungen zum Client und Client-Trennungen zur Replika durchreichen, damit der Worker stoppt. Setzen Sie kein Gateway dazwischen das „POST erneut versucht“: JSON-RPC-Anfragen sind standardmäßig nicht idempotent, und tools/call kann die Datenbank schon geschrieben haben.

subscriptions/listen ist ein anderer langer Stream: die Antwort bleibt offen und trägt Änderungen wie tools/list_changed, nicht Fortschritt eines Aufrufs. Die Spezifikation empfiehlt periodische SSE-Kommentarzeilen (eine Zeile die mit einem Doppelpunkt beginnt) als Keep-Alive, damit idle Zwischengeräte nicht auflegen. Wiederaufnehmbares SSE über Last-Event-ID wird nicht unterstützt. Legen Sie Idle-/Read-Timeouts des LB über Ihr Keep-Alive-Intervall; die 60s-Defaults von Cloudflare, ALB und nginx sind oft zu kurz. Der Schnipsel unten ist eine minimale Reverse-Proxy-Skizze — keine Sicherheitsbasis. TLS, Limits und WAF kommen extra.

upstream mcp_replicas {
  least_conn;
  server 10.0.1.11:8080;
  server 10.0.1.12:8080;
  server 10.0.1.13:8080;
}

server {
  listen 443 ssl;
  server_name mcp.example.com;

  location /healthz {
    proxy_pass http://mcp_replicas;
    proxy_connect_timeout 2s;
    proxy_read_timeout 3s;
  }

  location /mcp {
    proxy_pass http://mcp_replicas;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header Connection "";
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
  }
}

Rolling Deploy, Drain und subscriptions/listen

Zustandslose Replikas machen Rollouts leichter; SSE ist weiter eine fliegende HTTP-Anfrage. Die Reihenfolge: Instanz aus der Zielgruppe nehmen → neuen /mcp-Verkehr stoppen → auf offene JSON-Antworten und SSE warten oder das veröffentlichte Drain-Timeout treffen → dann SIGTERM an den Worker. SIGKILL nicht einen Prozess der noch Fortschritt schiebt. Erst zurücknehmen, wenn Healthchecks grün sind.

Die neue Version muss die alte Anfrageform weiter akzeptieren. Es gibt keinen Protokollschritt der „den Handshake upgraded, dann den Traffic schaltet“. Ein Canary ist ein Prozentsatz der POSTs zum neuen Image. MCP-Protocol-Version kann Routing-Schlüssel sein: nur Clients die 2026-07-28 erklären, kommen in den neuen Pool; ältere bleiben im Kompatibilitätspool. Bei Versionsmismatch 400 plus UnsupportedProtocolVersionError. Nicht still herunterstufen und Tools weiterlaufen lassen.

subscriptions/listen bricht in einem Publish-Fenster fast immer ab. Clients sollen listen neu öffnen und nicht annehmen, Ereignisse seien nicht verloren. Server sollen undeliverte list_changed nicht im Prozessspeicher stapeln. Brauchen Sie zuverlässige Zustellung, schreiben Sie eine externe Queue; listen ist nur der Abo-Mund. MRTR (Mehr-Runden-Eingabe) sind ebenfalls unabhängige POSTs: parken Sie das Zwischenergebnis in geteiltem Speicher, damit der nächste Hop auf einer anderen Replika landen kann.

Gateway-Limits, Auth, Telemetrie und JSONVue

Sieht das Gateway Mcp-Method und Mcp-Name, setzen Sie QPS pro Tool, nicht nur pro Quell-IP. Teure Tools (Writes, Zahlungen, langes SQL) bekommen ein eigenes Kontingent; tools/list darf lockerer sein. Im öffentlichen Netz Origin prüfen und OAuth 2.1 als Resource Server fahren — 401, Protected Resource Metadata, Bearer auf jedem Hop; siehe MCP OAuth 2.1. Auth auf dem Transport, Limits auf dem Gateway, Schema in der Fachschicht. Die drei nicht in eine Middleware kneten.

Telemetriefeld Woher es kommt Wofür Sie es nutzen
MCP-Protocol-Version / Mcp-Method / Mcp-NameRequest-Header (am Body-_meta ausgerichtet)Limits pro Tool, Canaries, Dashboards
JSON-RPC-idUmschlag dieses HopsClient-Retries mit Replika-Logs verbinden
HTTP-Status + JSON-RPC error.codeTransport- vs. Methodenschicht401 / HeaderMismatch / Fachfehler trennen

Access-Logs sollten mindestens diese drei Gruppen halten. Replika-Logs ergänzen, ob jsonrpc 2.0 ist und ob das Tool vor einer Nebenwirkung lief. Header/Body-Mismatch, fehlendes Accept und schlechtes Origin sterben am Gateway oder Replika-Rand, nicht in der Tool-Funktion. Halten Sie je ein Sample: HeaderMismatch, 401, fehlende Argumentfelder, gepuffertes SSE (Client sah nur den letzten Brocken).

Sie können das Labor im Browser beenden:JSON-Formatierer, um zu sehen ob der Umschlag parst;JSON-Schema-Validator für params.arguments;JSON Diff, um zwei Fehlerantworten zu vergleichen;JWT-Decoder für das Bearer-aud eines öffentlichen Deploys. Daten bleiben auf dieser Maschine.

Weiterlesen: Was ist MCP, Zustandsloses MCP, OAuth 2.1, MCP und JSON Schema, A2A vs MCP.

FAQ

Brauche ich in Produktion noch Sticky Sessions?

Nicht auf der Protokollebene für 2026-07-28. Sticky Sessions lassen Sie nur glauben, Sie hätten noch eine Session. Muss die App an einer Region oder einem Datenshard kleben, routen Sie über Mcp-Param-* oder einen Mandantenschlüssel in arguments — Anwendungs-Routing — nicht Cookie-Affinität an einen MCP-Prozess.

Kann ich den Load Balancer mit GET /mcp healthchecken?

Nein. Der moderne MCP-Endpunkt nimmt nur POST; GET-Streams sind weg. Zufällige GETs auf /mcp liefern 405 oder verwirren alte Kompatibilitätslogik. Sonden Sie ein getrenntes /healthz, das Prozess und Abhängigkeiten prüft und keine Tools ausführt.

Wenn SSE abbricht, soll der Client mit Last-Event-ID fortsetzen?

Die Spezifikation unterstützt kein wiederaufnehmbares SSE. Clients öffnen die passende Anfrage neu (listen → ein neues subscriptions/listen; ein langes Tool → Retry nur wenn das Geschäft idempotent ist). Keep-Alive-Kommentarzeilen plus ein ungepufferter Proxy passen besser zum Vertrag als ein eigener Event-ID-Cache.

Sitzt ein lokales stdio-MCP auch hinter einem Load Balancer?

Nein. stdio ist ein Kindprozess den der Client startete; Bytes reiten auf stdin/stdout ohne HTTP-Hop. Lastverteilung, Origin-Checks, Bearer und X-Accel-Buffering gehören zu Streamable HTTP / Remote. Dieselben Tools können beide Transports zeigen; die Produktionstopologie umhüllt nur die HTTP-Seite.

Fazit und nächste Schritte

Die Produktionsarchitektur von Remote-MCP ist ein Satz: eine zustandslose JSON-RPC-Anfrage kreuzt einen gewöhnlichen HTTP-Load-Balancer und landet auf einer beliebigen identischen Replika; lange Streams öffnen im Anfrage-Scope, nicht im Verbindungs-Scope. Header sind für das Gateway, der Body ist die Wahrheit, Anwendungszustand lebt in externem Speicher.

Liefern Sie in dieser Reihenfolge: jede Replika beendet ein tools/call allein, dann Puffer aus, Timeouts hoch, Drain dazu, dann Limits pro Tool und Canaries. Halten Sie einen Erfolgsumschlag, einen HeaderMismatch und einen 401 in JSONVue auf dieser Maschine. Protokollsemantik: zustandsloses MCP. Wer rufen darf: OAuth 2.1.