MCP ohne Sessions: Was die Spezifikation vom 28. Juli 2026 ändert

MCP ohne Sessions: Was die Spezifikation vom 28. Juli 2026 ändert

MCP ohne Sessions: Was die Spezifikation vom 28. Juli 2026 ändert

Artikel-Übersicht

Am 28. Juli 2026 ist eine neue Version des Model Context Protocol erschienen, und die Maintainer nennen sie selbst „die größte Revision des Protokolls seit dem Start". Die Kernbotschaft in einem Satz: MCP wird zustandslos – keine Sessions mehr, kein Handshake mehr, jede Anfrage steht für sich. David Soria Parra, einer der beiden Lead Maintainer, sagte dazu gegenüber The Register: „A lot of things that made MCP are gone." Das ist keine Übertreibung. Wer einen eigenen MCP-Server oder Client gebaut hat, muss sich mit dieser Version beschäftigen.

Dieser Artikel geht die Änderungen der Reihe nach durch: was wegfällt, was an seine Stelle tritt und warum. Er zeigt die Unterschiede an konkreten HTTP- und JSON-RPC-Beispielen aus der Spezifikation, erklärt die Kompatibilitätsregeln zwischen alter und neuer Protokollgeneration, fasst den Stand der offiziellen SDKs zusammen und endet mit einer Migrations-Checkliste. Grundlage sind der offizielle Changelog, die zugehörigen Spec Enhancement Proposals (SEPs), die Blogposts der Maintainer und die Release-Notes der SDKs.

Wenn du MCP noch nicht kennst, lies zuerst Das Model Context Protocol (MCP) verständlich erklärt. Dort beschreibe ich unter anderem den Initialisierungs-Handshake, den es jetzt nicht mehr gibt – der Artikel bleibt für das Grundverständnis richtig, die Abschnitte zu Handshake und Sampling gelten aber nur noch für die ältere Protokollgeneration. Wie man fertige Server einbindet, zeigt MCP-Server einrichten.

Die Revision auf einen Blick

Die Version 2026-07-28 entfernt alles, was einen MCP-Server an eine bestimmte Verbindung gebunden hat, und ersetzt es durch Informationen, die jede Anfrage selbst mitbringt. Die wichtigsten Punkte:

Bereich Bis 2025-11-25 Ab 2026-07-28
Sitzung Mcp-Session-Id-Header, Server hält Zustand je Verbindung keine Sessions; Zustand in expliziten Handles
Verbindungsaufbau initialize + notifications/initialized entfällt; jede Anfrage trägt Version, Fähigkeiten, Client-Info in _meta
Fähigkeiten abfragen Antwort auf initialize neue Methode server/discover
Rückfragen des Servers Server schickt eigene Requests (Sampling, Elicitation, Roots) Multi Round-Trip Requests: Antwort input_required, Client wiederholt
Benachrichtigungen HTTP-GET-Stream, resources/subscribe ein langlebiger POST: subscriptions/listen
Ergebnisse ohne Typkennung Pflichtfeld resultType (complete / input_required)
Tasks experimentell im Kern Extension io.modelcontextprotocol/tasks
Abgekündigt – Roots, Sampling, Logging, HTTP+SSE-Transport, Dynamic Client Registration
Entfernt – ping, logging/setLevel, SSE-Wiederaufnahme per Last-Event-ID

Dazu kommen kleinere Änderungen: neue Pflicht-Header für Methode und Name, Cache-Angaben an Listen, OpenTelemetry-Felder, strengere OAuth-Regeln und neue Fehlercodes. Wir gehen sie gleich durch.

Wie es dazu kam

MCP hat sich schnell weiterentwickelt. Die wichtigsten Stationen:

Datum Ereignis
18.06.2025 Version 2025-06-18: strukturierte Tool-Ausgaben, Elicitation, MCP-Server als OAuth Resource Server
25.11.2025 Version 2025-11-25: experimentelle Tasks, URL-Elicitation, Client ID Metadata Documents, formale Governance
09.12.2025 Anthropic übergibt MCP an die Agentic AI Foundation unter dem Dach der Linux Foundation
21.05.2026 Release Candidate von 2026-07-28, zehn Wochen Testphase
28.07.2026 finale Version 2026-07-28, alle vier Tier-1-SDKs unterstützen sie zum Start
22.08.2026 neue Roadmap der Maintainer

Warum Sessions weg mussten

Ein MCP-Server mit Sitzungszustand lässt sich nicht hinter einen gewöhnlichen Load Balancer stellen – und genau das wollten immer mehr Betreiber tun. Die Begründung steht ausführlich in den beiden zentralen Vorschlägen SEP-2575 („Stateless MCP") und SEP-2567 („Sessionless MCP").

Das Problem in der alten Welt sieht so aus:

Alt (2025-11-25): Sitzung bindet Client an eine Instanz

  Client ──initialize──►  Load Balancer ──► Instanz A  (Session 1868a90c gespeichert)
  Client ──tools/call──►  Load Balancer ──► Instanz B  ✗ kennt Session 1868a90c nicht
                                │
                 Abhilfe nur über Sticky Sessions oder
                 einen gemeinsamen Session-Speicher

Neu (2026-07-28): jede Anfrage ist vollständig

  Client ──tools/call + _meta──► Load Balancer ──► Instanz A ✓
  Client ──tools/call + _meta──► Load Balancer ──► Instanz B ✓
  Client ──tools/call + _meta──► Load Balancer ──► Instanz C ✓

SEP-2575 nennt Sticky Sessions ausdrücklich „complex and fragile" und führt dazu schlechte Ausfallsicherheit, das Aufräumen verwaister Sitzungen und Speicherlecks an. Das Leitprinzip heißt „pay as you go": standardmäßig zustandslos, Zustand nur dort, wo eine Anwendung ihn wirklich braucht.

SEP-2567 liefert die empirische Seite, und die ist entlarvend. Sessions hatten in der Praxis nie eine einheitliche Bedeutung: Laut dem Proposal legte ChatGPT für jeden einzelnen Tool-Aufruf eine neue Session an, Claude.ai tat bis vor Kurzem dasselbe, Desktop- und IDE-Clients hielten dagegen eine Session pro Programmstart. Kaum ein Client nahm eine Session je wieder auf. Eine Auswertung von 1.000 Open-Source-Servern ergab:

Nutzung der Session-ID Anteil
gar nicht 90 %
nur Routing-Boilerplate aus dem TypeScript-SDK 3,5 %
nur im Transport eingerichtet 2,8 %
Anwendungszustand an die Session gebunden 2,5 %
Gateways mit Sticky Routing 0,7 %
Auth-Artefakte an die Session gebunden 0,5 %

Nur rund drei Prozent der Server nutzten Sessions also tatsächlich für Zustand oder Authentifizierung. Gleichzeitig verhinderten Sessions, dass Listen wie tools/list gecacht werden konnten – mit der Folge, dass Agenten mit vielen Subagenten dieselbe Werkzeugliste immer wieder abfragten. Und eine Session bietet genau einen Gültigkeitsbereich: Man kann damit nicht ausdrücken, dass sich mehrere Subagenten einen Warenkorb teilen, aber jeder seinen eigenen Browser hat.

Kein Handshake mehr: Jede Anfrage bringt alles mit

Statt einmal am Anfang Version und Fähigkeiten auszuhandeln, schickt der Client diese Angaben jetzt mit jeder einzelnen Anfrage im Feld _meta. Das ist die zweite große Änderung (SEP-2575).

So sah ein Werkzeugaufruf über HTTP bisher aus – zwei Schritte, verbunden durch die Session-ID:

POST /mcp HTTP/1.1
Content-Type: application/json

{"jsonrpc":"2.0","id":1,"method":"initialize",
 "params":{"protocolVersion":"2025-11-25","capabilities":{},
           "clientInfo":{"name":"my-app","version":"1.0"}}}
POST /mcp HTTP/1.1
Mcp-Session-Id: 1868a90c-3a3f-4f5b
Content-Type: application/json

{"jsonrpc":"2.0","id":2,"method":"tools/call",
 "params":{"name":"search","arguments":{"q":"otters"}}}

Und so sieht derselbe Aufruf jetzt aus – eine Anfrage, vollständig in sich:

POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather

{"jsonrpc":"2.0","id":1,"method":"tools/call",
 "params":{"name":"get_weather","arguments":{"location":"Seattle, WA"},
  "_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28",
           "io.modelcontextprotocol/clientInfo":{"name":"ExampleClient","version":"1.0.0"},
           "io.modelcontextprotocol/clientCapabilities":{}}}}

Drei Dinge fallen auf:

  1. Die _meta-Felder tragen Protokollversion, Fähigkeiten des Clients und (empfohlen, nicht verpflichtend) Client-Informationen. Der Server antwortet umgekehrt mit io.modelcontextprotocol/serverInfo im _meta des Ergebnisses.
  2. Die neuen Header Mcp-Method und Mcp-Name (SEP-2243) wiederholen Methode und Werkzeugname außerhalb des Bodys. Damit können Proxys, Gateways und Firewalls routen und filtern, ohne JSON zu parsen. Stimmen Header und Body nicht überein, antwortet der Server mit 400 und dem Fehlercode -32020 (HeaderMismatch). Werte, die kein reines ASCII sind, werden als =?base64?…?= kodiert.
  3. Es gibt keine Session-ID mehr. Ein Server, der nur die neue Version spricht, ignoriert einen mitgeschickten Mcp-Session-Id-Header und erzeugt selbst nie einen.

Wer die Fähigkeiten eines Servers vorab kennen will, ruft die neue Methode server/discover auf. Jeder Server muss sie implementieren, Clients dürfen sie nutzen:

{"jsonrpc":"2.0","id":"discover-1","result":{"resultType":"complete",
 "supportedVersions":["2026-07-28"],"capabilities":{"tools":{},"resources":{}},
 "_meta":{"io.modelcontextprotocol/serverInfo":{"name":"ExampleServer","version":"1.0.0"}},
 "instructions":"…","ttlMs":3600000,"cacheScope":"public"}}

Passt die angefragte Version nicht, gibt es einen klaren Fehler mit der Liste der unterstützten Versionen:

{"jsonrpc":"2.0","id":1,"error":{"code":-32022,"message":"Unsupported protocol version",
 "data":{"supported":["2026-07-28","2025-11-25"],"requested":"1900-01-01"}}}

Was sich am HTTP-Transport noch ändert

Der Streamable-HTTP-Transport wird deutlich einfacher:

  • Jede Nachricht ist ein eigener POST. Der Accept-Header muss application/json und text/event-stream enthalten. Die Antwort ist entweder JSON oder ein SSE-Stream, der nur zu dieser einen Anfrage gehört.
  • Stream schließen heißt abbrechen. Bricht die Verbindung ab, ist die Anfrage verloren; der Client schickt sie mit neuer ID erneut. Die Wiederaufnahme per Last-Event-ID gibt es nicht mehr.
  • GET und DELETE auf den Endpunkt beantwortet ein reiner Neu-Server mit 405.
  • Für SSE-Antworten sollte der Server X-Accel-Buffering: no setzen, damit Reverse Proxys wie nginx nicht puffern.

Für stdio ändert sich weniger, weil dort die Verbindung ohnehin genau einem Prozess gehört. Die Nachrichten sehen aber genauso aus wie über HTTP.

Zustand über Handles statt über die Verbindung

Wo ein Server wirklich Zustand über mehrere Aufrufe braucht, gibt er dem Client ein Handle zurück, das bei den folgenden Aufrufen als ganz normales Argument mitkommt. SEP-2567 betont, dass das kein neues Protokollelement ist, sondern ein Muster für das Design von Werkzeugen:

// → tools/call
{ "name": "create_basket", "arguments": {} }
// ← { "structuredContent": { "basket_id": "bsk_a1b2c3" }, ... }

// → tools/call
{ "name": "add_item", "arguments": { "basket_id": "bsk_a1b2c3", "sku": "shoes" } }

Der Zustand liegt dann dort, wo er hingehört: in einer Datenbank oder einem Cache, auf den alle Server-Instanzen zugreifen können. Das Modell sieht die ID, reicht sie weiter, und der Server braucht nicht zu wissen, über welche Verbindung die Anfrage kommt. Das hat einen angenehmen Nebeneffekt: Mehrere Subagenten können bewusst dasselbe Handle teilen oder bewusst jeder ein eigenes bekommen – genau das, was mit einer Session nicht ging.

Die Spezifikation gibt dazu klare Regeln vor, und die sollte man ernst nehmen:

  • Handles sind opak. Keine sprechenden IDs wie user_42_cart.
  • Besitz ist keine Berechtigung. Bei jedem Aufruf prüft der Server die Kombination aus Handle und Authentifizierungskontext. Ein Handle, das in einem Chat-Log auftaucht, darf einem anderen Nutzer nichts nützen.
  • Ohne Authentifizierung braucht ein Handle mindestens 128 Bit Zufall und eine begrenzte Lebensdauer.
  • Die Lebensdauer steht in der Werkzeugbeschreibung, etwa „Warenkörbe verfallen nach 24 Stunden Inaktivität". Das Modell soll wissen, womit es rechnen muss.
  • Abgelaufene Handles liefern einen klaren Fehler, damit das Modell ein neues anlegen kann.

Illustration eines großen orangefarbenen Gepäckanhängers mit abgerissenem Abschnitt, der leicht versetzt daneben liegt; am Anhänger hängt an einer kurzen Schlaufe ein kleines Vorhängeschloss

Illustration eines großen orangefarbenen Gepäckanhängers mit abgerissenem Abschnitt, der leicht versetzt daneben liegt; am Anhänger hängt an einer kurzen Schlaufe ein kleines Vorhängeschloss

Ein Handle funktioniert wie ein Gepäckschein: Der Server gibt den Abschnitt heraus, der Client legt ihn beim nächsten Aufruf vor – aber erst die Prüfung gegen den angemeldeten Nutzer, das Schloss, gibt den Zustand frei.

In Code übersetzt heißt das: Die Prüfung gehört in jedes Werkzeug, nicht in eine Verbindungsschicht.

async function addItem(args: { basket_id: string; sku: string }, auth: AuthContext) {
  const basket = await baskets.find(args.basket_id);
  if (!basket || basket.expiresAt < Date.now()) {
    throw new ToolError("basket_id unbekannt oder abgelaufen – create_basket erneut aufrufen");
  }
  if (basket.owner !== auth.subject) {
    throw new ToolError("kein Zugriff auf diesen Warenkorb");
  }
  await baskets.addItem(basket.id, args.sku);
  return { structuredContent: { basket_id: basket.id, items: basket.items.length + 1 } };
}

Rückfragen ohne Rückkanal: Multi Round-Trip Requests

Bisher konnte ein Server mitten in einer Anfrage selbst Anfragen an den Client schicken – etwa um den Nutzer etwas zu fragen oder das Sprachmodell des Clients zu nutzen. Das ersetzt die neue Version durch Multi Round-Trip Requests (MRTR, SEP-2322). Der Server antwortet nicht mit einer eigenen Anfrage, sondern mit einem Ergebnis vom Typ input_required, und der Client wiederholt die ursprüngliche Anfrage mit den gewünschten Antworten.

Alt:   Client ──tools/call──► Server
       Client ◄──elicitation/create── Server   (Server fragt aktiv zurück)
       Client ──Antwort──► Server
       Client ◄──Ergebnis── Server

Neu:   Client ──tools/call──► Server
       Client ◄──resultType: input_required + requestState── Server
       Client ──tools/call (neue id) + inputResponses + requestState──► Server
       Client ◄──resultType: complete── Server

Die Antwort des Servers sieht so aus:

{"jsonrpc":"2.0","id":1,"result":{"resultType":"input_required",
 "inputRequests":{"github_login":{"method":"elicitation/create","params":{"mode":"form",
   "message":"Please provide your GitHub username",
   "requestedSchema":{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]}}}},
 "requestState":"AEAD-protected blob"}}

Der Trick steckt im Feld requestState: Der Server packt alles, was er für die Fortsetzung braucht, in diesen Blob. Der Client schickt ihn unverändert zurück. So muss sich keine Server-Instanz merken, dass da noch eine Anfrage offen ist.

Damit wird requestState aber auch zum Angriffspunkt. Die Spezifikation ist hier unmissverständlich: Der Server muss den Blob als vom Angreifer kontrollierte Eingabe behandeln. Beeinflusst er Berechtigungen oder Geschäftslogik, muss er per HMAC oder AEAD geschützt sein; er sollte an den Nutzer, eine Ablaufzeit und die ursprüngliche Anfrage gebunden sein. Wer hier einfach JSON base64-kodiert zurückschickt, baut sich eine Lücke, durch die ein Client beliebigen Server-Zustand einschleusen kann. Warum solche Eingaben bei Agenten besonders heikel sind, beschreibe ich in KI-Sicherheit: Prompt Injection, Datenabfluss und Werkzeugrechte.

MRTR ist nur bei tools/call, resources/read und prompts/get erlaubt. Jedes Ergebnis trägt dafür jetzt das Pflichtfeld resultType mit complete oder input_required. Fehlt es, weil ein älterer Server antwortet, wertet der Client das als complete.

Was abgekündigt oder entfernt ist

Roots, Sampling und Logging sind abgekündigt, funktionieren aber noch mindestens bis zur ersten Revision ab dem 28. Juli 2027. Seit dieser Version gilt eine formale Lebenszyklus-Regel (SEP-2596): Abgekündigte Funktionen bleiben mindestens zwölf Monate erhalten.

Funktion Status Ersatz
Sampling (Server nutzt das LLM des Clients) abgekündigt keiner im Kern; Server braucht eigenen Modellzugang
Roots (Client nennt Arbeitsverzeichnisse) abgekündigt Angaben als Werkzeugargumente
Logging abgekündigt, logging/setLevel entfernt Log-Level je Anfrage über io.modelcontextprotocol/logLevel in _meta; ohne das Feld sendet der Server keine Log-Nachrichten
HTTP+SSE-Transport (der alte, vor Streamable HTTP) abgekündigt Streamable HTTP
Dynamic Client Registration abgekündigt Client ID Metadata Documents
ping entfernt –
notifications/roots/list_changed entfernt –
SSE-Wiederaufnahme (Last-Event-ID) entfernt Anfrage neu senden
notifications/elicitation/complete entfernt –

Der Verlust von Sampling ist der Punkt, der in der Community am meisten diskutiert wird. Sampling erlaubte es einem Server, eine eigene Agentenschleife auf dem Modell des Clients laufen zu lassen, ohne selbst einen API-Schlüssel zu besitzen. Diese Möglichkeit verschwindet mittelfristig.

Eine Ungereimtheit bleibt bei Logging: Es gilt als abgekündigt mit Zwölf-Monats-Frist, die Methode logging/setLevel ist in der neuen Version aber schon entfernt. Übrig bleiben die Log-Nachrichten selbst, gesteuert über das Feld in _meta.

Benachrichtigungen, Tasks und die kleineren Änderungen

Benachrichtigungen über einen Abo-Stream

Bisher hielten Clients einen HTTP-GET-Stream offen und abonnierten Ressourcen mit resources/subscribe. Beides ersetzt subscriptions/listen: ein langlebiger POST, in dem der Client angibt, welche Benachrichtigungen er haben will.

{"jsonrpc":"2.0","id":7,"method":"subscriptions/listen",
 "params":{"notifications":{"toolsListChanged":true,
   "resourceSubscriptions":["file:///project/config.json"]}}}

Die erste Nachricht im Stream muss notifications/subscriptions/acknowledged sein, und jede folgende Benachrichtigung trägt eine subscriptionId, die der JSON-RPC-ID der Listen-Anfrage entspricht. Wählbar sind toolsListChanged, promptsListChanged, resourcesListChanged und resourceSubscriptions.

Tasks werden eine Extension

Die in 2025-11-25 eingeführten, langlaufenden Tasks wandern aus dem Kern in die Extension io.modelcontextprotocol/tasks (SEP-2663). Statt blockierend auf tasks/result zu warten, fragt der Client per tasks/get den Stand ab; neu ist tasks/update, tasks/list entfällt. Möglich wird das durch einen allgemeinen Extension-Mechanismus: Fähigkeiten haben jetzt ein Feld extensions. Die erste offizielle Extension war schon im Januar 2026 MCP Apps für interaktive Oberflächen.

Kleinere, aber spürbare Änderungen

  • Caching: Listen-Ergebnisse und resources/read müssen ttlMs und cacheScope (public oder private) angeben (SEP-2549). Weil Werkzeuglisten nicht mehr je Verbindung variieren dürfen, können Clients sie endlich zwischenspeichern. tools/list soll Werkzeuge außerdem in fester Reihenfolge liefern – gut für Prompt-Caching auf Modellseite.
  • Observability: traceparent, tracestate und baggage in _meta erlauben verteiltes Tracing mit OpenTelemetry über Client, Gateway und Server hinweg (SEP-414).
  • Parameter in Headern: Über x-mcp-header kann ein Werkzeug ausgewählte Parameter als Mcp-Param-{Name} in Header spiegeln, etwa für Routing nach Mandant.
  • JSON Schema: inputSchema und outputSchema unterstützen volles JSON Schema 2020-12, structuredContent darf jeder JSON-Wert sein (SEP-2106).
  • OAuth: Validierung des iss-Parameters nach RFC 9207, an den Aussteller gebundene Credentials, application_type bei der Client-Registrierung, Refresh Tokens und schrittweise Erweiterung von Scopes.
  • Fehlercodes: Der Bereich -32020 bis -32099 ist für MCP reserviert. Neu sind -32020 (HeaderMismatch), -32021 (MissingRequiredClientCapability) und -32022 (UnsupportedProtocolVersion). „Resource not found" wechselt von -32002 zu -32602. Achtung: Artikel, die auf dem Release Candidate beruhen, zeigen teils noch ältere Codes.

Alt und neu nebeneinander: die Kompatibilitätsregeln

Die Spezifikation unterscheidet jetzt zwei Protokollgenerationen: „Legacy" (2025-11-25 und älter) und „Modern" (ab 2026-07-28). Ein Server, der beides spricht, heißt „Dual-era". Wer mit wem reden kann:

Client Server Ergebnis
Modern Modern ✓
Modern Legacy ✓, wenn der Client zurückfällt (siehe unten)
Modern Dual-era ✓
Legacy Dual-era ✓
Legacy Modern-only ✗ – alte Clients können nicht „nach vorn" wechseln

Die letzte Zeile ist die wichtigste für Serverbetreiber: Wer seinen Server nur noch modern betreibt, sperrt alle Clients aus, die noch nicht aktualisiert sind. Für öffentliche Server ist Dual-era deshalb für eine Übergangszeit Pflicht. Ein Dual-era-Server kann beide Generationen sogar gleichzeitig am selben Endpunkt bedienen.

Clients erkennen alte Server so:

stdio:  zuerst server/discover senden
        └─ unbekannter Fehler  → zurückfallen auf initialize (Legacy)

HTTP:   moderne Anfrage senden
        └─ 400 mit modernem JSON-RPC-Fehler → mit unterstützter Version wiederholen
        └─ alles andere                    → zurückfallen auf initialize (Legacy)

Ergebnis zwischenspeichern: je Prozess (stdio) bzw. je Origin (HTTP)

Eine Besonderheit betrifft die Sessions selbst: Ihre Abschaffung hat keine Übergangsfrist. SEP-2567 nennt das ausdrücklich einen „clean break". Server, die Anwendungszustand an Sessions hängen, bleiben bis zum Umbau auf der alten Version.

Der Stand der SDKs

Alle vier Tier-1-SDKs – TypeScript, Python, Go und C# – haben die neue Version zum Erscheinungstermin unterstützt, aber nicht alle schalten sie standardmäßig ein.

SDK Version mit 2026-07-28 Aktuell (Anfang Oktober 2026) Standardverhalten
TypeScript 2.0.0 (27.07.2026) 2.3.1 Client standardmäßig legacy, modern nur auf Wunsch
Python (mcp) 2.0.0 (28.07.2026) 2.3.0 Client handelt die Version automatisch aus
Go v1.7.0 (28.07.2026) v1.8.0 über HTTP nur mit Stateless = true, sonst Rückfall auf 2025-11-25
C# v2.0.0 (28.07.2026) v2.2.0 –

Das Rust-SDK hatte die Version laut Release-Post zum Start im Beta-Stadium.

TypeScript

Das TypeScript-SDK ist mit Version 2 in mehrere Pakete aufgeteilt (@modelcontextprotocol/server, client, node und Adapter für Hono und Fastify). Ein Server entsteht über eine Fabrikfunktion:

import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';

const handler = createMcpHandler(() => {
  const server = new McpServer(
    { name: 'my-server', version: '1.0.0' },
    { capabilities: { tools: {} } },
  );
  return server;
});
// Node/Express: app.all('/mcp', toNodeHandler(handler))   aus @modelcontextprotocol/node

Die Fabrik ist kein Zufall: Weil es keine Session gibt, an der eine Server-Instanz hängt, kann der Handler für jede Anfrage eine frische Instanz erzeugen. Auf der Client-Seite steuert die Option versionNegotiation, welche Generation gesprochen wird:

const client = new Client(
  { name: 'my-client', version: '1.0.0' },
  { versionNegotiation: { mode: 'auto' } },   // 'legacy' (Default) | 'auto' | { pin: '2026-07-28' }
);
await client.connect(transport);
client.getProtocolEra();                       // 'modern' | 'legacy'

Die Migrationsdoku sagt es selbst: „Nothing in v2 puts a 2026-07-28 byte on the wire by default." Wer modern sprechen will, muss das aktiv einschalten. Für den Umstieg von Version 1 gibt es einen Codemod (npx @modelcontextprotocol/codemod v1-to-v2), der allerdings nur die API-Änderungen umschreibt – den Umbau auf zustandslose Werkzeuge nimmt er dir nicht ab.

Python

Im Python-SDK 2.0 heißt die Server-Klasse MCPServer statt FastMCP, und ein einzelnes Client-Objekt ersetzt die Kombination aus ClientSession und initialize(). Derselbe Server bedient auch ältere Clients. Lücken laut Release-Notes: Die Tasks-Extension fehlt noch. Wer vorerst bei der alten Generation bleiben will, pinnt mit mcp>=1.28,<2; die 1.x-Reihe bekommt nur noch Sicherheitskorrekturen.

Migrations-Checkliste

Für die meisten Server ist die Migration kleiner, als die Liste der Änderungen vermuten lässt – laut SEP-2567 nutzen 90 Prozent die Session-ID gar nicht. Aufwendig wird es nur dort, wo Zustand, Rückfragen oder Sampling im Spiel sind.

Für Server-Entwickler

  • SDK auf die neue Hauptversion heben und prüfen, ob der Server im Dual-era-Modus läuft.
  • Session-Zustand suchen: Alles, was an Mcp-Session-Id oder an der Verbindung hängt, in Handles mit gemeinsamem Speicher umbauen.
  • Handles absichern: opak, an den Nutzer gebunden, mit Ablaufzeit, Lebensdauer in der Werkzeugbeschreibung.
  • Werkzeuglisten vereinheitlichen: tools/list darf nicht mehr je Verbindung variieren, Reihenfolge fest.
  • Rückfragen umbauen: Elicitation über MRTR, requestState per HMAC oder AEAD schützen.
  • Sampling ersetzen: eigenen Modellzugang einplanen, falls der Server bisher das Modell des Clients nutzt.
  • server/discover implementieren (bei SDK-Servern automatisch).
  • ttlMs und cacheScope an Listen und Ressourcen sinnvoll setzen.
  • Reverse Proxy prüfen: X-Accel-Buffering: no, keine Sticky Sessions mehr nötig, GET/DELETE dürfen 405 liefern.
  • Header-Logging prüfen: Mcp-Name und gespiegelte Mcp-Param-*-Header können sensible Werte in Proxy-Logs tragen.

Für Client- und Gateway-Entwickler

  • Versionsaushandlung einschalten (auto) und das Ergebnis je Origin zwischenspeichern.
  • Abgebrochene Streams durch erneutes Senden mit neuer ID behandeln.
  • resultType: input_required verarbeiten und requestState unverändert zurückgeben.
  • Benachrichtigungen über subscriptions/listen abonnieren statt GET-Stream.
  • Werkzeuglisten cachen gemäß ttlMs – bei vielen Subagenten spart das spürbar Anfragen.
  • Gateways: Sticky Routing abbauen, Routing über Mcp-Method/Mcp-Name statt Body-Parsing.

Kritik und offene Fragen

Die Richtung wird weitgehend begrüßt, der Preis aber offen benannt. Craig McLuckie (Stacklok) nannte die Zustandsbehaftung gegenüber The Register „really a by-product of its origin" – MCP kam aus der Welt lokaler stdio-Prozesse. Soria Parra selbst räumte ein, dass die Umstellung für alle mit eigener Implementierung „a lot of uplift" bedeute, und kommentierte die größeren Nachrichten mit „hope that bandwidth is for free".

Die wichtigsten Kritikpunkte:

  • Verlust von Sampling. Server-seitige Agentenschleifen auf dem Modell des Clients waren ein Alleinstellungsmerkmal von MCP.
  • Sicherheit wandert zu den Entwicklern. Ein Fehler bei requestState oder bei der Handle-Prüfung kann direkt zu Berechtigungsumgehungen führen. Früher steckte mehr davon im Transport.
  • Header als Datenleck. Routing-Header landen in Proxy- und Load-Balancer-Logs.
  • Mehr Bytes pro Anfrage. Version, Fähigkeiten und Client-Info reisen jetzt mit jeder Nachricht mit. SEP-2575 führt den Overhead selbst als offene Frage.
  • Handles im Kontext. Das Modell muss IDs mitführen; bei Kontext-Kompaktierung können sie verloren gehen, und sie tauchen in Chat-Verläufen auf.
  • Keine Übergangsfrist für Sessions, obwohl für alles andere zwölf Monate gelten. Stacklok wies zudem darauf hin, dass auch die Zwölf-Monats-Frist keine Interoperabilität zwischen den Generationen garantiert.

Was als Nächstes kommt

Die Roadmap vom 22. August 2026 nennt fünf Schwerpunkte, aber keinen Termin für die nächste Version:

  1. Primitive für agentische Kommunikation – vom Server ausgelöste Ereignisse, Tasks zurück in Richtung Kern.
  2. Vereinheitlichter HTTP-Transport – auch für lokale Server, die bisher stdio nutzen.
  3. Agenten-Identität und Enterprise-Sicherheit – standardisierte Identitäten statt API-Schlüsseln und langlebiger Tokens.
  4. Verbesserte Primitive – standardisierte Werkzeugergebnisse und schrittweise Entdeckung großer Werkzeugkataloge.
  5. Bessere SDK-Erfahrung – Ergonomie, Konformitätstests, Dokumentation.

Häufige Fragen

Muss ich meinen MCP-Server sofort migrieren? Nein. Alte Server funktionieren weiter, weil moderne Clients auf die alte Generation zurückfallen. Dringend wird es erst, wenn deine Clients nur noch modern sprechen oder wenn du den Server horizontal skalieren willst. Planen solltest du die Migration aber, denn abgekündigte Funktionen fallen frühestens ab Juli 2027 weg.

Was ersetzt die Mcp-Session-Id? Nichts auf Protokollebene. Zustand, der über mehrere Aufrufe reicht, wird als explizites Handle vom Server erzeugt und vom Client als normales Werkzeugargument zurückgegeben. Der Server speichert den Zustand in einem Speicher, den alle Instanzen erreichen.

Gibt es den initialize-Handshake noch? Nur für die alte Protokollgeneration. Ab 2026-07-28 trägt jede Anfrage Version und Fähigkeiten in _meta. Wer die Fähigkeiten eines Servers vorab wissen will, ruft server/discover auf.

Funktioniert Sampling noch? Ja, aber es ist abgekündigt und kann frühestens mit der ersten Revision ab dem 28. Juli 2027 entfallen. Neue Server sollten nicht mehr darauf bauen und einen eigenen Modellzugang vorsehen.

Kann ein Server alte und neue Clients gleichzeitig bedienen? Ja, als sogenannter Dual-era-Server, sogar am selben Endpunkt. Die offiziellen SDKs in TypeScript, Python, Go und C# unterstützen das. Ein Server, der nur die neue Version spricht, schließt alte Clients dagegen aus.

Ändert sich etwas für lokale stdio-Server? Weniger als für HTTP-Server, weil dort ohnehin ein Prozess genau einem Client gehört. Das Nachrichtenformat ändert sich aber genauso: kein Handshake, _meta in jeder Anfrage, server/discover, resultType in jedem Ergebnis.

Wo finde ich die offizielle Änderungsliste? Im Changelog unter modelcontextprotocol.io/specification/2026-07-28/changelog. Die Begründungen stehen in SEP-2575 und SEP-2567, die Migrationshinweise in der Doku der jeweiligen SDKs.

Zusammenfassung und praktische Tipps

  • Zustandslos ist der neue Standard. Jede MCP-Anfrage ab 2026-07-28 trägt Version, Fähigkeiten und Client-Info selbst; Sessions und Handshake entfallen.
  • Handles statt Sessions. Echter Zustand wird als opake ID zurückgegeben und bei jedem Aufruf gegen den Nutzer geprüft.
  • MRTR statt Rückrufen. Rückfragen an den Nutzer laufen über input_required und requestState; Letzteres ist Angreifer-Eingabe und gehört signiert oder verschlüsselt.
  • Sampling, Roots, Logging sind abgekündigt. Zwölf Monate Frist, aber neue Server sollten sie nicht mehr nutzen.
  • server/discover ersetzt das Abfragen der Fähigkeiten beim Verbindungsaufbau.
  • Caching wird möglich. ttlMs, cacheScope und feste Reihenfolge machen Werkzeuglisten cachebar.
  • Neue Header Mcp-Method und Mcp-Name erleichtern Routing, können aber sensible Werte in Logs tragen.
  • Dual-era für öffentliche Server. Wer nur modern spricht, sperrt alte Clients aus.
  • SDK-Standards unterscheiden sich. TypeScript spricht standardmäßig weiter legacy, Python handelt automatisch aus, Go braucht Stateless = true.
  • Codemods lösen nur die API-Seite. Den Umbau von Zustand und Rückfragen musst du selbst machen.

Mein Rat: Nimm die Revision als Anlass, deine Werkzeuge einmal konsequent zustandslos zu denken – auch wenn du die neue Version noch nicht einschaltest. Ein Werkzeug, das alles Nötige in seinen Argumenten bekommt und jede ID gegen den Nutzer prüft, ist leichter zu testen, leichter zu skalieren und schwerer zu missbrauchen. Das ist dieselbe Lektion, die REST-APIs vor fünfzehn Jahren gelernt haben, und sie gilt hier genauso – mehr dazu in API-Design: Schnittstellen entwerfen, die man nicht bereut. Wer einen öffentlichen Server betreibt, sollte jetzt auf Dual-era umstellen und die modernen Clients schon mitnehmen. Und wer Sampling nutzt, sollte heute anfangen, eine Alternative zu bauen, nicht im Juni 2027.

Ressourcen