Blog/KI-Agenten
MCP 2026-07-28: Migrationsleitfaden für zustandslose MCP-Server
MCP 2026-07-28 entfernt Sessions und den initialize-Handshake. Was sich für Serverautoren ändert: _meta, server/discover, MRTR, Autorisierung und eine Checkliste.
Balázs Csorba··9 Min. Lesezeit
- MCP
- Protocol migration
- Stateless APIs
- OAuth
- Agents

Das Wichtigste in Kürze
- MCP 2026-07-28, veröffentlicht am 28. Juli 2026, entfernt den initialize-Handshake und den Header Mcp-Session-Id; jeder Request trägt damit Version und Fähigkeiten selbst.
- Server müssen server/discover implementieren; Clients dürfen es zuerst aufrufen oder einfach einen Request schicken und UnsupportedProtocolVersionError behandeln.
- Multi Round-Trip Requests ersetzen serverinitiierte Elicitation, Sampling und Roots: Der Server gibt input_required zurück, der Client wiederholt den Aufruf mit inputResponses.
- Zustand zwischen Aufrufen wandert in explizite Handles, die als Tool-Argumente durchgereicht werden; requestState muss integritätsgeschützt sein, weil es angreiferkontrolliert ist.
- Roots, Sampling, Logging und Dynamic Client Registration sind deprecated, die früheste Entfernung ist für die erste Revision am oder nach dem 28. Juli 2027 angesetzt.
MCP 2026-07-28 ist die am 28. Juli 2026 veröffentlichte Revision des Model Context Protocol und macht aus einem zustandsbehafteten, sessionbasierten Protokoll ein zustandsloses Request/Response-Protokoll. Der initialize-Handshake und der Header Mcp-Session-Id sind weg, jede Anfrage beschreibt sich selbst, und Server können den Client mitten in einem Aufruf keine Requests mehr schicken. Für alle, die einen MCP-Server betreiben, ist das die größte inkompatible Änderung seit es Remote-MCP gibt.
Dieser Leitfaden erklärt, was sich für einen Serverautor tatsächlich ändert, Stand September 2026: wie eine zustandslose Anfrage aufgebaut ist, was die serverinitiierten Requests ersetzt, wohin der Zustand zwischen Aufrufen wandert, welche Härtungen die Autorisierung erfährt, wann die Deprecations wirksam werden, und eine Migrations-Checkliste mit Testplan. Alles Weitere stammt aus dem offiziellen Changelog und dem Release-Post; wo der Spec-Text die Quelle der Wahrheit ist, verlinke ich ihn.
Warum hat MCP die Sessions abgeschafft?
MCP hat die Sessions auf Protokollebene abgeschafft, weil sie Server schwer skalierbar machten: Eine Session lebte auf einer Serverinstanz, also musste jeder spätere Request genau diese Instanz erreichen. Ohne sie kann jeder Request auf jeder Instanz hinter einem gewöhnlichen Load Balancer landen.
Die Roadmap 2026 (9. März 2026) stellt „Transport Evolution and Scalability“ an erster von vier Prioritäten und benennt das Problem direkt: Zustandsbehaftete Sessions vertragen sich schlecht mit Load Balancern, und horizontales Skalieren brauchte Workarounds. In der Praxis bedeutete das Sticky Routing, einen geteilten Session-Store oder beides. Serverless-Plattformen hatten es schlimmer, weil es dort überhaupt keinen langlebigen Prozess gibt, der eine Session halten könnte.
Der Release-Post nennt das Ziel in einem Satz: Jeder Request kann jetzt auf jeder Serverinstanz hinter einem einfachen Round-Robin-Load-Balancer landen, ohne gemeinsamen Storage. Das Changelog listet 9 große und 12 kleine Änderungen gegenüber der vorherigen Revision 2025-11-25. Die meisten davon sind Folgen dieser einen Entscheidung.
Wie funktioniert eine zustandslose MCP-Anfrage?
Eine zustandslose MCP-Anfrage trägt alles, was der Server braucht, bereits im Request selbst: Protokollversion und Fähigkeiten des Clients reisen bei jedem Aufruf in _meta, und HTTP-Requests wiederholen Methode und Tool-Namen in Headern. Es gibt keinen Handshake, den man sich merken müsste.
Unter 2025-11-25 hat der Client initialize gesendet, Fähigkeiten und eine Session-ID zurückbekommen, mit notifications/initialized bestätigt und die Session-ID danach an jeden Aufruf gehängt. Unter 2026-07-28 gelten die Versionierungsregeln pro Request:
io.modelcontextprotocol/protocolVersionundio.modelcontextprotocol/clientCapabilitiessind im_metajeder Anfrage Pflicht. Eine Anfrage ohne sie ist fehlerhaft und bekommt-32602(Invalid params) mit HTTP 400.io.modelcontextprotocol/clientInfosollte bei jeder Anfrage mitgeschickt werden, und Server solltenio.modelcontextprotocol/serverInfoim_metajedes Ergebnisses zurückgeben. Beide Angaben sind selbst gemeldet und dürfen keine Sicherheitsentscheidungen steuern.- Unterstützt der Server die angeforderte Version nicht, gibt er
UnsupportedProtocolVersionError(-32022) mit einersupported-Liste zurück, und der Client versucht es mit einer Version aus dieser Liste erneut. - Server müssen die neue RPC
server/discoverimplementieren, die Versionen, Fähigkeiten und Identität ausweist. Clients dürfen sie zuerst aufrufen, müssen es aber nicht. - Auf Streamable HTTP müssen POST-Requests
MCP-Protocol-Version,Mcp-Methodund beitools/call,resources/readundprompts/getzusätzlichMcp-Namemitschicken (SEP-2243). Widerspricht ein Header dem Body, antwortet der Server mit 400 und einemHeaderMismatch-Fehler (-32020). Gateways und WAFs können jetzt anhand der Header routen und Raten begrenzen, ohne JSON zu parsen.
So sieht ein tools/call-Request unter der neuen Revision aus (illustrativ, an den Feldnamen der Spec):
POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search_issues
{"jsonrpc": "2.0", "id": 7, "method": "tools/call",
"params": {"name": "search_issues", "arguments": {"query": "status = Open"},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {"elicitation": {}},
"io.modelcontextprotocol/clientInfo": {"name": "my-agent", "version": "1.4.0"}
}}}Was ersetzt die serverinitiierten Requests? Multi Round-Trip Requests
Multi Round-Trip Requests (MRTR, SEP-2322) ersetzen die Requests vom Server zum Client elicitation/create, sampling/createMessage und roots/list. Statt den Client mitten im Aufruf über einen offengehaltenen Stream aufzurufen, beendet der Server den Request mit einem „input required“-Ergebnis, und der Client wiederholt den ursprünglichen Request mit den angehängten Antworten.
Die MRTR-Seite der Spec definiert den Ablauf. Der Server gibt ein InputRequiredResult mit resultType: "input_required" zurück, eine inputRequests-Map (die Schlüssel sind serverseitig gewählte IDs, die Werte Elicitation-, Sampling- oder Roots-Requests) und ein optionales opakes requestState. Der Client sammelt die Antworten ein und schickt den ursprünglichen Aufruf mit inputResponses unter denselben Schlüsseln erneut, echot requestState zurück und verwendet eine neue JSON-RPC-ID. Damit ist der erste Request beendet; der zweite ist ein unabhängiger Request, den jede Instanz bearbeiten kann.
So sieht ein gekürztes Zwischenergebnis für ein Tool aus, das eine Bestätigung braucht:
{"jsonrpc": "2.0", "id": 1, "result": {
"resultType": "input_required",
"inputRequests": {
"confirm_transition": {
"method": "elicitation/create",
"params": {"mode": "form", "message": "Move PROJ-42 to Done?",
"requestedSchema": {"type": "object",
"properties": {"confirm": {"type": "boolean"}}, "required": ["confirm"]}}
}
},
"requestState": "<HMAC-protected blob>"
}}Drei Regeln aus der Spec ändern, wie man das schreibt:
requestStateist angreiferkontrollierte Eingabe. Fließt er in Autorisierung, Ressourcenzugriff oder Geschäftslogik ein, musst du seine Integrität schützen (HMAC oder AEAD) und jeden State ablehnen, der die Prüfung nicht besteht. Die Spec empfiehlt, darin den authentifizierten Principal, eine kurze Ablaufzeit und einen Digest des ursprünglichen Requests zu binden. Muss ein State höchstens einmal verwendet werden, erzwinge das serverseitig.- Nur
tools/call,prompts/getundresources/readdürfen einInputRequiredResultzurückgeben, und nur mit Request-Typen, die der Client in seinen Fähigkeiten deklariert hat. - Jedes Ergebnis braucht jetzt
resultType:"complete"für normale Ergebnisse. Clients behandeln ein fehlendes Feld von älteren Servern als „complete“, dein neuer Server sollte es aber immer mitschicken.
MRTR ändert auch das Timing. Der Client versucht es vielleicht nie erneut, also darf ein Tool keine halbfertige Arbeit liegen lassen, während es auf eine Antwort wartet. Den Seiteneffekt setzt du nach der Bestätigung um, nicht davor.
Wohin geht der Zustand jetzt? Handles, Tasks und Subscriptions
Zustand zwischen Aufrufen wandert aus dem Transport in die Tools: Ein Tool erzeugt einen expliziten Handle, gibt ihn zurück, und das Modell reicht ihn bei späteren Aufrufen als normales Argument weiter. Lang laufende Arbeit nutzt die Tasks-Erweiterung, Änderungsbenachrichtigungen einen neuen subscriptions/listen-Stream.
Explizite Handles
Die Tools-Spec nutzt einen Warenkorb als Beispiel: create_basket gibt bsk_a1b2c3 zurück, und add_item nimmt basket_id als Parameter. Der Release-Post begründet, warum das besser funktioniert als versteckter Session-State: Das Modell sieht den Handle und reicht ihn von Tool zu Tool weiter. Die Designhinweise der Spec gehören in deine Review-Checkliste: Bei einem authentifizierten Server ist ein Handle ein Name, keine Berechtigung – prüfe die Autorisierung des Aufrufers also bei jedem Aufruf; halte Handles opak; schreibe die Aufbewahrungsfrist in die Beschreibung des erzeugenden Tools; und gib für einen abgelaufenen Handle einen Tool-Ausführungsfehler zurück, damit sich das Modell erholen kann.
Tasks und Subscriptions
Tasks sind aus dem experimentellen Kern in die offizielle Erweiterung io.modelcontextprotocol/tasks (SEP-2663) gewandert. Das blockierende tasks/result wird durch Polling mit tasks/get ersetzt, ein neues tasks/update transportiert Eingaben vom Client zum Server, und tasks/list ist gestrichen. Erweiterungen werden über ein neues extensions-Feld in den Client- und Serverfähigkeiten ausgehandelt.
Der alte HTTP-GET-Endpunkt und resources/subscribe werden durch subscriptions/listen ersetzt, also durch einen einzigen langlebigen POST-Response-Stream, in dem der Client Benachrichtigungstypen wie toolsListChanged abonniert. Auch die Wiederaufnahme abgebrochener Streams ist weg: Bricht ein Response-Stream ab, ist der laufende Request verloren, und der Client muss ihn mit einer neuen ID erneut senden. Damit ist Idempotenz dein Problem. Kann ein Tool-Aufruf doppelt ankommen, muss er zweimal laufen können, ohne Schaden anzurichten, oder er braucht einen Schlüssel, mit dem du das Duplikat erkennst.
Listenergebnisse lassen sich günstiger cachen. tools/list, prompts/list, resources/list, resources/read und resources/templates/list müssen ttlMs und cacheScope ("public" oder "private") mitliefern, und Server sollten die Tools in einer deterministischen Reihenfolge zurückgeben. Eine stabile Tool-Liste hält den Prompt-Cache des Clients warm, und das kostet Geld (siehe Prompt-Caching und Routing).
Was hat sich an der MCP-Autorisierung geändert?
Die Autorisierungsänderungen in 2026-07-28 schließen ein Verwechslungsloch zwischen Autorisierungsservern, binden Client-Credentials an den Server, der sie ausgestellt hat, und kennzeichnen Dynamic Client Registration formal als deprecated zugunsten von Client ID Metadata Documents (CIMD).
- Issuer-Validierung (SEP-2468). Autorisierungsserver sollten den Parameter
issgemäß RFC 9207 in die Autorisierungsantwort aufnehmen, und Clients müssen ein vorhandenesissgegen den hinterlegten Issuer prüfen, bevor sie den Code einlösen. - Credentials nach Issuer geschlüsselt (SEP-2352). Clients müssen dauerhaft gespeicherte Credentials nach Issuer-Kennung indizieren, dürfen sie nicht mit einem anderen Autorisierungsserver wiederverwenden und müssen sich neu registrieren, wenn der Autorisierungsserver wechselt.
application_typebei der Registrierung (SEP-837). Deshalb sahen manche Desktop- und CLI-Clientsredirect_uri-Fehler für Callbacks auf localhost.- CIMD statt DCR. Bei CIMD ist die Client-ID eine HTTPS-URL, die auf ein JSON-Dokument mit mindestens
client_id,client_nameundredirect_uriszeigt. Autorisierungsserver kündigen die Unterstützung mitclient_id_metadata_document_supportedan. DCR funktioniert aus Gründen der Abwärtskompatibilität weiter.
Wenn dein Server nur Tokens validiert, landet der größte Teil davon beim Client und beim Autorisierungsserver. Die Aufgabe deines Servers bleibt unverändert und weiterhin streng: Nimm nur Tokens an, die für dich ausgestellt wurden, und reiche sie nie an Upstream-APIs durch. Die MCP-Server-Sicherheits-Checkliste deckt diese Seite ab.
Was ist deprecated, und wann sollte man die Migration nicht beschleunigen?
Roots, Sampling und Logging sind deprecated (SEP-2577), ebenso Dynamic Client Registration und der alte HTTP+SSE-Transport. Bisher wurde nichts entfernt, der eigentliche Trade-off ist also nicht „migrieren oder brechen“, sondern wie lange du einen Server für beide Epochen betreibst.
Das Registry für deprecated Features nennt als früheste Entfernung für Roots, Sampling, Logging und DCR die erste Revision, die am oder nach dem 28. Juli 2027 erscheint. Die vorgeschlagenen Migrationen sind konkret: Verzeichnisse über Tool-Parameter oder Konfiguration übergeben statt über Roots, den LLM-Anbieter direkt aufrufen statt Sampling, und nach stderr oder OpenTelemetry loggen statt über Logging. ping und logging/setLevel sind schon aus dem Protokoll entfernt; das Log-Level wandert nun pro Request als io.modelcontextprotocol/logLevel.
Die Clients da draußen wechseln nicht alle gleichzeitig, deshalb definiert die Spec einen Abwärtskompatibilitätspfad. Ein moderner Client versucht es zuerst mit einem modernen Request und fällt nur dann auf initialize zurück, wenn der Body einer 400-Antwort kein moderner JSON-RPC-Fehler ist. Ein Server, der nur die neue Revision spricht, sollte HTTP GET oder DELETE mit 405 beantworten, Mcp-Session-Id ignorieren, ohne eine auszustellen, und Last-Event-ID ignorieren.
Meine Meinung: Sind deine Clients alle SDK-basiert und kontrollierst du sie, migriere in einem Schritt. Servierst du Drittanbieter-Hosts, die du nicht kontrollierst, betreibst du eine Weile einen Server für beide Epochen und beobachtest MCP-Protocol-Version in deinen Access Logs, um zu entscheiden, wann der Legacy-Pfad rausfliegt. Die vier Tier-1-SDKs (TypeScript, Python, Go und C#) unterstützen die Revision, Rust ist in Beta – die Migration ist also im Wesentlichen ein SDK-Upgrade plus die Designänderungen von oben.
| Mechanismus in 2025-11-25 | 2026-07-28 | Was du an deinem Server änderst |
|---|---|---|
initialize-Handshake | Entfernt; Version und Fähigkeiten in jedem _meta | Pro Request lesen; -32022 mit unterstützten Versionen zurückgeben |
| Nichts | server/discover (Server müssen implementieren) | Versionen, Fähigkeiten und Identität ausweisen |
Mcp-Session-Id | Entfernt | Zustand in explizite, autorisierte Handles verschieben |
| Serverinitiierte Elicitation, Sampling, Roots | MRTR: input_required + Wiederholung | InputRequiredResult zurückgeben; requestState signieren |
GET-Stream, resources/subscribe | subscriptions/listen | GET und DELETE mit 405 beantworten |
Wiederaufnahme über Last-Event-ID | Entfernt | Tool-Aufrufe idempotent machen |
Experimentelle Tasks, tasks/result | Tasks-Erweiterung, Polling mit tasks/get | Polling; tasks/list streichen |
| Nur JSON-Body | Header Mcp-Method / Mcp-Name | Abweichungen zwischen Header und Body ablehnen (-32020) |
Ressource nicht gefunden -32002 | -32602 | Fehlermapping und Tests anpassen |
MCP 2026-07-28: Migrations-Checkliste und Testplan
Die Migration eines MCP-Servers auf 2026-07-28 läuft darauf hinaus, das SDK upzugraden, jede Annahme über Sessions zu entfernen und zu beweisen, dass zwei aufeinanderfolgende Requests auf zwei verschiedenen Instanzen landen können. In dieser Reihenfolge würde ich vorgehen:
- Auf ein Tier-1-SDK-Release umsteigen, das 2026-07-28 spricht, und zuerst dessen Migrationshinweise lesen; die SDKs übernehmen die meisten Transportänderungen.
- Suche im Code nach Session-IDs und pro Verbindung aufgebauten Caches. Ersetze jeden durch einen expliziten Handle oder durch Daten im Request.
server/discoverimplementieren undserverInfoim_metades Ergebnisses zurückgeben.- Jeden serverinitiierten Request als MRTR umschreiben, mit einem integritätsgeschützten
requestState, das an Nutzer, Ablaufzeit und Request gebunden ist. resultType,ttlMsundcacheScopeergänzen undtools/listdeterministisch sortieren.- Die Header
Mcp-MethodundMcp-Namegegen den Body validieren und die Fehlercodes anpassen (-32602,-32020zu-32022). - Roots, Sampling und Logging ersetzen durch Tool-Parameter, direkte Anbieter-Aufrufe und OpenTelemetry. Trace-Kontext hat jetzt dokumentierte
_meta-Keys (traceparent, SEP-414). - Festlegen, wie lange beide Epochen parallel laufen, und die Protokollversion jedes Requests loggen.
Testplan
- Zwei Instanzen hinter Round-Robin betreiben und jeden Schritt eines Ablaufs mit mehreren Aufrufen an eine andere schicken.
- Einen Response-Stream mitten im Aufruf abbrechen und prüfen, ob ein erneut gesendeter Aufruf keine Doppelarbeit tut.
- Einen
requestStatemanipulieren, erneut einspielen und ablaufen lassen; alle drei müssen abgelehnt werden. - Den Handle des einen Nutzers mit dem Token eines anderen vorlegen; das muss scheitern.
- Einen alten 2025-11-25-Client gegen den Server testen und den gewählten Fallback bestätigen.
Der zustandslose Transport ändert auch, wie man über Tool-Design nachdenken sollte, denn Handles und Bestätigungen liegen jetzt in den Tool-Schemas. Einen Artikel dazu habe ich unter MCP-Tools, die Agenten richtig auswählen geschrieben. Wenn du so eine Migration für deine eigenen Server planst, ist das ein Teil dessen, was ich als KI-Engineer mache.
Quellen
- The 2026-07-28 Specification – MCP-Blog, 28. Juli 2026
- MCP 2026-07-28 Key Changes (changelog)
- MCP 2026-07-28: Versioning and Compatibility
- MCP 2026-07-28: Streamable HTTP transport
- MCP 2026-07-28: Multi Round-Trip Requests
- MCP 2026-07-28: Tools (state handles)
- MCP 2026-07-28: Client registration (CIMD)
- MCP 2026-07-28: Deprecated features registry
- The 2026 MCP Roadmap – MCP-Blog, 9. März 2026
- RFC 9207: OAuth 2.0 Authorization Server Issuer Identification
Häufige Fragen
Unterstützt MCP 2026-07-28 den initialize-Handshake noch?
Nein. Die Revision 2026-07-28 entfernt initialize und notifications/initialized. Jeder Request trägt stattdessen seine Protokollversion und die Client-Fähigkeiten in _meta. Ein Server kann ältere Clients weiter bedienen, indem er zusätzlich das Verhalten von 2025-11-25 implementiert, und moderne Clients fallen auf initialize zurück, wenn der Body einer 400-Antwort kein erkannter moderner JSON-RPC-Fehler ist.
Wie halte ich ohne Sessions Zustand zwischen MCP-Tool-Aufrufen?
Erzeuge in einem Tool einen expliziten Handle, etwa eine Warenkorb- oder Workflow-ID, gib ihn im Ergebnis zurück und nimm ihn bei späteren Aufrufen als normales Argument an. Speichere den Zustand serverseitig unter diesem Schlüssel, prüfe bei jedem Aufruf die Autorisierung des Aufrufers gegen den Handle, halte Handles opak und gib einen klaren Fehler zurück, wenn ein Handle abgelaufen ist.
Was ist requestState in MCP Multi Round-Trip Requests?
requestState ist ein opaker String, den der Server mit einem input_required-Ergebnis zurückgibt und den der Client beim Wiederholen zurückschickt. Er erlaubt einem zustandslosen Server, seine Arbeit fortzusetzen. Die Spec behandelt ihn als angreiferkontrollierte Eingabe: Wenn er die Autorisierung oder die Geschäftslogik beeinflusst, schütze ihn mit einem HMAC oder AEAD, binde ihn an den Nutzer, eine kurze Ablaufzeit und den ursprünglichen Request, und lehne alles ab, was die Prüfung nicht besteht.
Wann werden Roots, Sampling und Logging aus MCP entfernt?
Sie sind in 2026-07-28 deprecated, aber noch voll funktionsfähig. Das Registry für deprecated Features nennt als früheste Entfernung die erste Spec-Revision, die am oder nach dem 28. Juli 2027 erscheint; die tatsächliche Entfernung entscheidet der Maintainer. Vorgeschlagen sind Tool-Parameter oder Konfiguration für Roots, direkte LLM-Anbieter-Aufrufe für Sampling und stderr oder OpenTelemetry für Logging.