MCP 2026-07-28: Das Protokoll wird endlich stateless

Die neue MCP-Spezifikation entfernt Sessions und Handshakes und bringt Extensions, Tasks, MCP Apps, Caching und OpenTelemetry.

14 Min. Lesezeit

Bei MCP geht es in der öffentlichen Diskussion meistens um Tools, Resources und Prompts. Also um die Frage, was ein Modell über einen MCP-Server alles tun kann. Die wichtigste Änderung der neuen Spezifikation ist deutlich unspektakulärer: MCP schafft seine Protokoll-Sessions ab.

Mit der Revision 2026-07-28 verschwinden der initialize-Handshake, die anschliessende initialized-Notification und der Header Mcp-Session-Id. Statt einmal zu Beginn einer Verbindung Fähigkeiten und Protokollversion auszuhandeln, enthält nun jeder Request alle Informationen, die der Server zu seiner Verarbeitung benötigt.

Das klingt zunächst nach einer technischen Detailänderung. Tatsächlich ist es der Schritt, mit dem MCP von einem praktischen Protokoll für lokale Tools zu einer deutlich besseren Grundlage für verteilte und horizontal skalierbare AI-Systeme wird.

Die Maintainer bezeichnen 2026-07-28 als die grösste Überarbeitung seit der Einführung von MCP. Neben dem stateless Protokollkern kommen ein offizielles Extension-Modell, Multi Round-Trip Requests, MCP Apps, die neue Tasks Extension, Caching-Metadaten, OpenTelemetry-Konventionen und mehrere Änderungen an der OAuth-Integration hinzu. Gleichzeitig werden Roots, Sampling und Logging als veraltet markiert.

Es heisst nicht MCP 2.0

MCP verwendet für seine Protokollversionen keine semantische Versionierung, sondern ein Datum. Die neue Revision heisst daher:

2026-07-28

Bezeichnungen wie „MCP 2.0“ sind missverständlich. Es gibt zwar inzwischen eine Version 2 des offiziellen TypeScript- und Python-SDKs, diese SDK-Versionen sind aber unabhängig von der Protokollversion.

Das ist besonders bei TypeScript relevant: Ein Update auf die v2-Pakete aktiviert nicht automatisch das neue Protokoll auf dem Wire. Die Unterstützung für 2026-07-28 muss explizit über die neuen Server- und Client-Einstiegspunkte eingeschaltet werden. Damit können bestehende Anwendungen das SDK aktualisieren, ohne gleichzeitig ihre Kommunikation mit allen Clients umzustellen.

Das Problem mit den bisherigen Sessions

In der vorherigen Spezifikation 2025-11-25 begann eine Verbindung mit einem initialize-Request. Darin teilte der Client dem Server seine Protokollversion, seine Fähigkeiten und einige Informationen über sich selbst mit.

Der Server antwortete unter anderem mit einem Mcp-Session-Id:

Client
  |
  | initialize
  v
MCP-Server A
  |
  | Mcp-Session-Id: 1868a90c-3a3f-4f5b
  v
Client

Alle folgenden Requests mussten diese Session-ID mitsenden:

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": {
      "query": "stateless MCP"
    }
  }
}

Bei einem lokalen MCP-Server, der über stdio gestartet wird, ist das kaum ein Problem. Es gibt genau einen Prozess und einen Client. Sobald ein MCP-Server aber über HTTP bereitgestellt und horizontal skaliert wird, entstehen die üblichen Probleme zustandsbehafteter Systeme.

Entweder benötigt der Load Balancer Sticky Sessions, damit ein Client immer wieder auf derselben Instanz landet, oder alle Instanzen müssen ihren Session-State in Redis, einer Datenbank oder einem anderen gemeinsamen Speicher ablegen.

Damit sah ein MCP-Deployment schnell so aus:

                         +----------------+
                    +--->| MCP-Server A   |
                    |    +----------------+
Client -> Load Balancer -+                 +--> gemeinsamer Session Store
                    |    +----------------+
                    +--->| MCP-Server B   |
                         +----------------+

Für ein Protokoll, das eigentlich nur Tools und Datenquellen standardisieren soll, ist das ein erheblicher infrastruktureller Ballast.

Jeder Request ist jetzt vollständig

Mit 2026-07-28 wird jeder Request unabhängig verarbeitet. Die Protokollversion und die Fähigkeiten des Clients befinden sich in _meta. Bei HTTP steht die Protokollversion zusätzlich im Header MCP-Protocol-Version.

Ein Tool-Aufruf sieht damit schematisch so aus:

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

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search",
    "arguments": {
      "query": "stateless MCP"
    },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {},
      "io.modelcontextprotocol/clientInfo": {
        "name": "my-client",
        "version": "1.0.0"
      }
    }
  }
}

protocolVersion und clientCapabilities müssen bei jedem Request vorhanden sein. clientInfo wird empfohlen, ist aber nicht verpflichtend.

Damit kann jeder Request bei jeder Instanz landen:

                         +----------------+
                    +--->| MCP-Server A   |
                    |
Client -> Load Balancer -+---> MCP-Server B
                    |
                    +--->| MCP-Server C   |
                         +----------------+

Es gibt auf Protokollebene keine Session mehr, keinen gemeinsamen Session Store und keinen Grund für Sticky Sessions. Der Server darf nicht einmal davon ausgehen, dass zwei Requests auf derselben Verbindung zur selben Unterhaltung oder zum selben Agentenlauf gehören.

Versionserkennung ohne Handshake

Ganz ohne Aushandlung kommt auch das neue MCP nicht aus. Statt eines zwingenden Handshakes gibt es nun den RPC-Aufruf server/discover.

Ein Server muss darüber unter anderem seine unterstützten Protokollversionen und Fähigkeiten bekannt geben. Ein Client kann server/discover vor seinem ersten eigentlichen Aufruf verwenden, muss es aber nicht. Alternativ sendet er direkt einen Request mit seiner bevorzugten Version.

Unterstützt der Server diese Version nicht, antwortet er mit dem neuen Fehlercode -32022:

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

Der Client kann daraufhin eine gemeinsame Version auswählen und den Request wiederholen. Moderne Clients können damit neue und alte Server ansprechen, ohne dass jeder Server am selben Tag migriert werden muss. Die Spezifikation unterscheidet ausdrücklich zwischen modernen, legacy und sogenannten Dual-Era-Implementierungen.

Stateless bedeutet nicht, dass Anwendungen keinen Zustand haben dürfen

Der Wegfall der MCP-Session bedeutet nicht, dass jeder Tool-Aufruf komplett isoliert sein muss. Der Zustand wird lediglich nicht mehr implizit an eine Verbindung gekoppelt.

Ein Browser-Tool könnte beispielsweise weiterhin mehrere zusammengehörige Aufrufe anbieten:

create_browser()
    -> browser_id: "browser_7d91"

navigate(
    browser_id = "browser_7d91",
    url = "https://example.com"
)

take_screenshot(
    browser_id = "browser_7d91"
)

close_browser(
    browser_id = "browser_7d91"
)

Dasselbe Muster funktioniert für Warenkörbe, Datenbanktransaktionen, Sandbox-Umgebungen, Suchindizes oder temporäre Arbeitsverzeichnisse.

Der Unterschied ist, dass der Zustand explizit wird. Das Modell sieht den Bezeichner, kann ihn in späteren Tool-Aufrufen einsetzen und sogar mehrere Instanzen parallel verwalten. Der Server kann den eigentlichen Zustand weiterhin intern speichern. Er darf ihn nur nicht mehr an eine bestimmte HTTP-Verbindung oder einen bestimmten MCP-Prozess binden.

Ein solcher Handle sollte möglichst:

  • undurchsichtig sein und keine internen Informationen preisgeben
  • an den authentifizierten Benutzer gebunden sein
  • eine begrenzte Lebensdauer haben
  • nach Ablauf oder Abschluss zuverlässig ungültig werden
  • bei schreibenden Operationen nicht allein als Autorisierungsnachweis dienen

Die neue Architektur entfernt den Zustand also nicht. Sie zwingt Entwickler lediglich dazu, ihn bewusst als Teil des Anwendungsmodells zu behandeln. Das ist zunächst mehr Arbeit, führt aber zu besser testbaren und besser skalierbaren Schnittstellen.

Multi Round-Trip Requests ersetzen den Rückkanal

Bisher konnte ein MCP-Server während eines laufenden Requests seinerseits einen JSON-RPC-Request an den Client senden. Das wurde etwa für Elicitation, Sampling oder roots/list verwendet.

Diese Richtung existiert in der neuen Revision nicht mehr. Ein Server darf keine eigenen JSON-RPC-Requests initiieren. Benötigt er zusätzliche Informationen, antwortet er mit einem InputRequiredResult.

Ein Tool, das vor dem Löschen von Dateien eine Bestätigung benötigt, könnte so reagieren:

{
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "resultType": "input_required",
    "inputRequests": {
      "confirm_deletion": {
        "method": "elicitation/create",
        "params": {
          "mode": "form",
          "message": "Sollen die drei ausgewählten Dateien gelöscht werden?",
          "requestedSchema": {
            "type": "object",
            "properties": {
              "confirmed": {
                "type": "boolean"
              }
            },
            "required": [
              "confirmed"
            ]
          }
        }
      }
    },
    "requestState": "AEAD-protected-state"
  }
}

Der Client holt die benötigte Eingabe ein und wiederholt anschliessend den ursprünglichen Tool-Aufruf. Dabei sendet er die inputResponses sowie den unveränderten requestState zurück.

Der zweite Aufruf ist kein Fortsetzen derselben Verbindung, sondern ein neuer Request mit einer neuen JSON-RPC-ID. Jede Serverinstanz kann ihn verarbeiten, solange alle benötigten Informationen im Request enthalten sind.

requestState ist dabei ein interessantes Detail. Der Client soll den Wert weder interpretieren noch verändern. Aus Sicht des Servers handelt es sich trotzdem um nicht vertrauenswürdige Eingabe, schliesslich kommt der Wert über den Client zurück.

Sobald requestState Autorisierungsentscheidungen oder Geschäftslogik beeinflusst, muss der Server seine Integrität schützen. Die Spezifikation nennt dafür HMAC oder AEAD und empfiehlt, mindestens folgende Informationen einzubinden:

  • den authentifizierten Benutzer
  • eine kurze Ablaufzeit
  • den ursprünglichen RPC-Aufruf
  • einen Hash relevanter Parameter

Für echte Einmalaktionen reicht ein signierter Token alleine nicht aus. Ob der Zustand bereits verbraucht wurde, muss weiterhin serverseitig geprüft werden.

Architektonisch sind Multi Round-Trip Requests nichts anderes als explizite, wiederaufnehmbare Zustandsautomaten. Das ist weniger bequem als ein dauerhaft geöffneter bidirektionaler Kanal, dafür lässt es sich wesentlich besser über gewöhnliche HTTP-Infrastruktur verteilen.

Routable: Gateways müssen nicht mehr JSON parsen

Streamable HTTP verlangt nun standardisierte Header wie Mcp-Method und Mcp-Name.

Ein Gateway erkennt dadurch bereits anhand der Header, ob es sich beispielsweise um tools/list, tools/call oder resources/read handelt. Es muss nicht erst den JSON-RPC-Body parsen.

Damit werden Konfigurationen wie diese möglich:

Mcp-Method = tools/list
    -> hoher Cache-Anteil
    -> grosszügiges Rate Limit

Mcp-Method = tools/call
Mcp-Name   = delete_document
    -> separates Rate Limit
    -> zusätzliches Audit Logging

Mcp-Method = resources/read
    -> anderer Backend-Pool

Server müssen prüfen, ob Header und JSON-RPC-Body übereinstimmen. Ein Client kann daher nicht einfach Mcp-Name: harmless_search angeben und im Body ein anderes Tool aufrufen.

Trotzdem sind diese Header keine Sicherheitsgrenze. Autorisierung muss weiterhin anhand des authentifizierten Benutzers, der tatsächlichen Methode und der validierten Request-Daten erfolgen. Die Header sind in erster Linie für Routing, Rate Limiting, Metriken und Traffic-Analyse gedacht.

Cacheable: Tool- und Ressourcenlisten bekommen eine Lebensdauer

Antworten von tools/list, prompts/list, resources/list, resources/read und resources/templates/list enthalten nun ttlMs und cacheScope.

Eine Antwort kann beispielsweise so aussehen:

{
  "resultType": "complete",
  "tools": [
    {
      "name": "search_documents",
      "description": "Durchsucht das Dokumentenarchiv"
    }
  ],
  "ttlMs": 300000,
  "cacheScope": "private"
}

ttlMs gibt an, wie lange die Antwort als aktuell betrachtet werden kann. cacheScope entscheidet, ob sie benutzerübergreifend zwischengespeichert werden darf:

public
    Die Antwort darf von gemeinsam genutzten Zwischenstellen gecacht werden.

private
    Die Antwort gehört zum aktuellen Benutzer- oder Autorisierungskontext.

Gerade tools/list wird von Clients häufig erneut abgefragt. Dabei können umfangreiche Tool-Schemas übertragen und anschliessend erneut in den Modellkontext aufgenommen werden. Ein sinnvoller Cache reduziert Netzwerkverkehr und kann zugleich die Trefferquote von Prompt Caches verbessern.

Die Spezifikation empfiehlt zusätzlich eine deterministische Reihenfolge der Tools. Ein Server sollte dieselben Tools also nicht bei jedem Request in einer anderen Reihenfolge zurückgeben.

cacheScope: public sollte trotzdem sehr vorsichtig verwendet werden. Sobald eine Tool- oder Ressourcenliste von Rollen, Mandanten, Feature Flags oder Benutzerrechten abhängt, ist sie privat. Ein falsch gesetzter Scope kann sonst Informationen über Funktionen oder Ressourcen anderer Benutzer offenlegen.

Traceable: OpenTelemetry wird Teil der Konvention

MCP definiert jetzt die Weitergabe des W3C Trace Context über _meta. Dazu gehören die bekannten Felder:

{
  "_meta": {
    "traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
    "tracestate": "vendor=value",
    "baggage": "tenant=example"
  }
}

Dadurch kann ein Trace vom Host über den MCP-Client und den MCP-Server bis zu nachgelagerten APIs, Datenbanken oder Modellaufrufen verfolgt werden.

Für produktive Agentensysteme ist das wichtiger, als es zunächst klingt. Ohne durchgehenden Trace sieht man häufig nur, dass eine Antwort lange gedauert hat. Mit einem gemeinsamen Trace lässt sich unterscheiden, ob die Zeit im Modell, im MCP-Transport, bei der Tool-Ausführung oder bei einem externen Dienst verbraucht wurde.

Die Spezifikation standardisiert dafür nur die Namen und die Weitergabe. Welche Daten tatsächlich in baggage landen dürfen, bleibt eine Sicherheitsfrage. Zugangsdaten, vollständige Prompts oder personenbezogene Inhalte gehören dort nicht hinein.

Subscriptions werden zu einem normalen Request

Die bisherige HTTP-GET-Verbindung sowie resources/subscribe und resources/unsubscribe werden durch subscriptions/listen ersetzt.

Der Client startet dabei einen normalen POST-Request, dessen Response als langfristig geöffneter Stream bestehen bleibt. Er kann angeben, für welche Ereignisse er sich interessiert, beispielsweise:

  • Änderungen an der Tool-Liste
  • Änderungen an Prompts
  • Änderungen an Ressourcen
  • Updates bestimmter abonnierter Ressourcen

Auch hier ist der Zustand an den konkreten Request gebunden und nicht an die darunterliegende HTTP-Verbindung. Bricht die Verbindung ab, stellt der Client einen neuen subscriptions/listen-Request.

Die bisherige Wiederaufnahme über Last-Event-ID und SSE Event IDs entfällt. Auch ein normaler Request, dessen Response-Stream abbricht, gilt als verloren und muss mit einer neuen JSON-RPC-ID wiederholt werden.

Daraus folgt eine wichtige Konsequenz für schreibende Tools: Sie sollten entweder idempotent sein oder über einen eigenen Idempotency Key beziehungsweise eine Duplikaterkennung verfügen. Sonst kann ein erfolgreich ausgeführter, aber vor der Antwort unterbrochener Request beim erneuten Aufruf dieselbe Aktion ein zweites Mal ausführen.

Das ist kein neues Problem verteilter Systeme. Durch den bewussten Verzicht auf Transport-Sessions wird es bei MCP aber sichtbarer.

Extensions werden zu einem offiziellen Bestandteil

MCP Extensions gab es bereits vorher, allerdings ohne vollständig definierten Lebenszyklus. Die neue Revision legt nun fest, wie Extensions benannt, ausgehandelt und weiterentwickelt werden.

Identifiers verwenden Reverse-DNS-Namen:

{
  "capabilities": {
    "extensions": {
      "io.modelcontextprotocol/ui": {
        "mimeTypes": [
          "text/html;profile=mcp-app"
        ]
      },
      "io.modelcontextprotocol/tasks": {}
    }
  }
}

Extensions können unabhängig von der Kernspezifikation versioniert und von eigenen Maintainern betreut werden. Neue Funktionen müssen dadurch nicht mehr sofort Teil des Protokollkerns werden.

Die ersten beiden offiziellen Extensions zeigen, wofür dieses Modell gedacht ist.

MCP Apps: Ein Tool kann eine Benutzeroberfläche mitbringen

MCP Apps erlauben es einem Server, neben gewöhnlichen Text- oder JSON-Ergebnissen auch interaktive HTML-Oberflächen bereitzustellen.

Der Host rendert diese Oberfläche in einem isolierten iframe. Ein Tool kann damit beispielsweise Folgendes zurückgeben:

  • ein Dashboard mit Messwerten
  • eine editierbare Tabelle
  • ein Formular mit Validierung
  • eine Kartenansicht
  • eine Vorschau mit Freigabe-Button
  • einen mehrstufigen Konfigurationsdialog

Die UI-Templates werden bereits in der Tool-Beschreibung deklariert. Der Host kann sie dadurch vorab laden, cachen und sicherheitstechnisch prüfen. Aktionen aus der Oberfläche laufen anschliessend wieder über MCP und damit über dieselben Autorisierungs-, Zustimmungs- und Audit-Pfade wie normale Tool-Aufrufe.

Das ist ein sinnvoller Ansatz. Nicht jede Interaktion lässt sich gut über natürliche Sprache abbilden. Bei einer Tabelle mit 50 Zeilen, einem Datumsbereich oder einer grafischen Auswahl ist eine kleine, kontrollierte Oberfläche oft genauer und effizienter als zehn weitere Chatnachrichten.

Tasks für lang laufende Operationen

Tasks waren in 2025-11-25 noch ein experimenteller Bestandteil des Protokollkerns. Sie werden nun in die offizielle Extension io.modelcontextprotocol/tasks verschoben und an das stateless Modell angepasst.

Ein Server kann bei einem Tool-Aufruf statt eines fertigen Ergebnisses einen Task-Handle zurückgeben. Der Client steuert den weiteren Ablauf über:

tasks/get
tasks/update
tasks/cancel

tasks/list wurde entfernt. Ohne Protokoll-Session lässt sich eine globale Liste von Tasks nur schwer sicher einem Benutzer, Client oder Agentenlauf zuordnen. Der Client muss daher den konkreten Task-Handle kennen.

Das passt gut zu Operationen wie:

  • dem Verarbeiten grosser Dokumentenmengen
  • längeren Code-Analysen
  • Datenexporten
  • Video- oder Bildverarbeitung
  • umfangreichen Rechercheaufträgen
  • Jobs auf externen Systemen

Wichtig ist, dass nicht der Client im Voraus entscheidet, ob ein Aufruf als Task laufen soll. Der Server trifft diese Entscheidung und gibt bei Bedarf einen Task-Handle zurück.

OAuth und OpenID Connect werden präziser

Ein grosser Teil der Änderungen betrifft weniger auffällige, aber für produktive Systeme wichtige Details der Autorisierung.

Clients müssen einen vorhandenen iss-Parameter aus der Authorization Response gegen den erwarteten Issuer prüfen. Das reduziert das Risiko sogenannter Mix-up-Angriffe, bei denen Antworten verschiedener Authorization Server verwechselt werden.

Bei Dynamic Client Registration muss der Client seinen passenden OpenID-Connect-application_type angeben. Das verhindert unter anderem, dass ein Desktop- oder CLI-Client fälschlicherweise als Webanwendung registriert wird und anschliessend seine lokale Redirect URI nicht verwenden darf.

Gespeicherte Client Credentials müssen zudem an den Issuer gebunden werden, der sie ausgestellt hat. Wechselt eine Ressource zu einem anderen Authorization Server, dürfen die vorhandenen Credentials nicht einfach wiederverwendet werden.

Dynamic Client Registration selbst wird inzwischen zugunsten von Client ID Metadata Documents als veraltet eingestuft. Es bleibt vorerst aus Kompatibilitätsgründen bestehen, neue Implementierungen sollen sich aber am neuen Verfahren orientieren.

Roots, Sampling und Logging werden deprecated

Die neue Revision führt erstmals einen formalen Lebenszyklus für Protokollfunktionen ein:

Active -> Deprecated -> Removed

Zwischen der Einstufung als deprecated und einer möglichen Entfernung müssen mindestens zwölf Monate liegen. Bestehende Funktionen verschwinden daher nicht plötzlich.

Mit 2026-07-28 betrifft das mehrere bekannte Bestandteile:

FunktionEmpfohlener Ersatz
RootsTool-Parameter, Resource URIs oder Server-Konfiguration
SamplingDirekte Integration mit einer LLM-Provider-API
Loggingstderr bei stdio oder OpenTelemetry
HTTP+SSEStreamable HTTP
Dynamic Client RegistrationClient ID Metadata Documents

Roots und Logging werde ich kaum vermissen. Verzeichnisse und Dateien explizit als Tool-Parameter oder Resource URI zu übergeben, ist verständlicher als ein impliziter Satz von Roots. Für produktives Logging ist OpenTelemetry ohnehin die passendere Abstraktion.

Bei Sampling bin ich skeptischer. Sampling erlaubte es einem MCP-Server, den Host um einen Modellaufruf zu bitten. Der Host konnte dabei Modellzugang, Kostenkontrolle, Berechtigungen und Benutzerinteraktion verwalten.

Die empfohlene direkte Integration mit einer Provider-API vereinfacht das MCP-Protokoll, koppelt einen Server aber wieder stärker an konkrete Modellanbieter und zwingt ihn gegebenenfalls zur eigenen Verwaltung von Zugangsdaten und Kosten. Sampling funktioniert während der Deprecation-Phase weiterhin und kann über Multi Round-Trip Requests transportiert werden. Für neue Systeme sollte man sich trotzdem überlegen, ob ein MCP-Server tatsächlich selbst zum LLM-Client werden soll.

Vollständiges JSON Schema 2020-12 für Tools

Tool-Schemas dürfen nun den vollständigen Funktionsumfang von JSON Schema 2020-12 verwenden.

Dazu gehören unter anderem:

oneOf
anyOf
allOf
if / then / else
$ref
$defs

Ein Tool kann damit beispielsweise zwei alternative Arten der Kontoadressierung definieren:

{
  "type": "object",
  "oneOf": [
    {
      "properties": {
        "iban": {
          "type": "string"
        },
        "amount": {
          "type": "number",
          "exclusiveMinimum": 0
        }
      },
      "required": [
        "iban",
        "amount"
      ]
    },
    {
      "properties": {
        "accountId": {
          "type": "string"
        },
        "amount": {
          "type": "number",
          "exclusiveMinimum": 0
        }
      },
      "required": [
        "accountId",
        "amount"
      ]
    }
  ]
}

Output-Schemas müssen nicht mehr zwingend ein Objekt beschreiben. Auch Arrays, Strings, Zahlen oder andere gültige JSON-Werte sind möglich. structuredContent kann entsprechend jeden JSON-Wert enthalten.

Die grössere Ausdrucksstärke bringt allerdings neue Risiken mit sich. Externe $ref-URLs dürfen standardmässig nicht automatisch abgerufen werden. Sonst könnten manipulierte Schemas Zugriffe auf interne Netzwerke oder lokale Dienste auslösen. Validatoren sollen ausserdem Tiefe, Anzahl der Subschemas und Validierungszeit begrenzen, damit ein absichtlich komplexes Schema nicht zum Denial-of-Service-Angriff wird.

Nur weil ein Schema formal sehr komplex sein darf, muss man diese Möglichkeiten nicht vollständig ausreizen. LLMs kommen mit einfachen, klar benannten Feldern häufig besser zurecht als mit mehreren verschachtelten oneOf- und allOf-Konstruktionen. Das Schema sollte so komplex wie nötig, aber so einfach wie möglich bleiben.

TypeScript v2 in der Praxis

Das offizielle TypeScript-SDK wurde in mehrere Pakete aufgeteilt. Für einen Server wird nun @modelcontextprotocol/server verwendet.

Der neue HTTP-Einstiegspunkt ist createMcpHandler:

import {
  createMcpHandler,
  McpServer,
} from '@modelcontextprotocol/server';
import * as z from 'zod/v4';

const handler = createMcpHandler(() => {
  const server = new McpServer(
    {
      name: 'document-server',
      version: '1.0.0',
    },
    {
      capabilities: {
        tools: {},
      },
    },
  );

  server.registerTool(
    'search_documents',
    {
      description: 'Durchsucht das Dokumentenarchiv',
      inputSchema: z.object({
        query: z.string().min(1),
        limit: z.number().int().min(1).max(100).default(10),
      }),
    },
    async ({ query, limit }) => {
      const results = await searchDocuments(query, limit);

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(results),
          },
        ],
      };
    },
  );

  return server;
});

export default handler;

createMcpHandler erzeugt für jeden Request eine passende Serverinstanz und unterstützt standardmässig sowohl die neue Revision als auch stateless Requests aus der 2025er Protokollgeneration.

In einer Node-Anwendung kann der Web-Standard-Handler über toNodeHandler eingebunden werden:

import { toNodeHandler } from '@modelcontextprotocol/node';

app.all('/mcp', toNodeHandler(handler));

Ein Client aktiviert die automatische Aushandlung explizit:

import { Client } from '@modelcontextprotocol/client';

const client = new Client(
  {
    name: 'document-client',
    version: '1.0.0',
  },
  {
    versionNegotiation: {
      mode: 'auto',
    },
  },
);

await client.connect(transport);

console.log(client.getProtocolEra());
// "modern" oder "legacy"

Im Modus auto versucht der Client zunächst server/discover. Erkennt er einen älteren Server, fällt er auf den bisherigen initialize-Handshake zurück.

Wer ausschliesslich das neue Protokoll zulassen möchte, kann die Revision fest vorgeben:

const client = new Client(
  {
    name: 'document-client',
    version: '1.0.0',
  },
  {
    versionNegotiation: {
      mode: {
        pin: '2026-07-28',
      },
    },
  },
);

Damit schlägt die Verbindung zu einem reinen 2025-11-25-Server fehl, statt still auf das alte Protokoll zurückzufallen.

Was bei einer Migration zu tun ist

Für einen einfachen lokalen MCP-Server besteht kein Grund, alles sofort umzubauen. Alte Clients und Server werden nicht am Release-Tag abgeschaltet. Die neue Version ist eine zusätzliche Protokollrevision, kein zentral erzwungenes Upgrade.

Bei einem über HTTP bereitgestellten MCP-Server würde ich die Migration in dieser Reihenfolge angehen:

  1. SDK aktualisieren, ohne sofort das Wire-Protokoll umzustellen.
    Damit lassen sich API-Änderungen und Paketaufteilung getrennt von der Protokollmigration behandeln.

  2. Dual-Era-Betrieb einführen.
    Der Server sollte zunächst sowohl 2025-11-25 als auch 2026-07-28 unterstützen. So können Clients schrittweise migriert werden.

  3. Session-Abhängigkeiten suchen.
    Dazu gehören Mcp-Session-Id, Sticky Sessions, Maps mit Session-IDs und Zustand, der an einen Prozess oder eine Verbindung gekoppelt ist.

  4. Anwendungszustand durch explizite Handles ersetzen.
    Handles benötigen eine klare Lebensdauer, Benutzerbindung und Autorisierungsprüfung.

  5. Server-initiierte Requests auf MRTR umstellen.
    Elicitation, Sampling und Roots müssen über InputRequiredResult, inputResponses und gegebenenfalls requestState laufen.

  6. Schreibende Tools auf Wiederholbarkeit prüfen.
    Bei abgebrochenen Streams können Requests erneut gesendet werden. Seiteneffekte brauchen Idempotenz oder Duplikaterkennung.

  7. Caching-Metadaten bewusst setzen.
    Besonders cacheScope darf nicht pauschal auf public gesetzt werden.

  8. Tracing durchgängig anbinden.
    traceparent, tracestate und baggage sollten bis zu nachgelagerten APIs weitergegeben werden.

  9. OAuth-Konfiguration überprüfen.
    Dazu gehören Issuer-Prüfung, Credential-Bindung, Scope-Step-up und die zukünftige Ablösung von Dynamic Client Registration.

  10. Deprecated Features nicht mehr neu einführen.
    Bestehende Roots-, Sampling- und Logging-Implementierungen können weiterlaufen. Neue Funktionen sollten jedoch bereits auf den empfohlenen Alternativen aufbauen.

Meine Einschätzung

2026-07-28 ist die bisher wichtigste MCP-Version, obwohl viele Änderungen für einen Benutzer kaum sichtbar sind.

MCP Apps sind anschaulicher. Eine interaktive Tabelle direkt im Chat lässt sich besser vorführen als ein entfernter Session-Header. Für den langfristigen Erfolg des Protokolls ist der stateless Kern trotzdem wichtiger.

Die bisherige Architektur funktionierte gut für lokale Prozesse und einfache Integrationen. Für zentral betriebene MCP-Plattformen mit mehreren Instanzen, Gateways, Mandanten und Autorisierung war sie unnötig kompliziert. Die neue Revision orientiert sich deutlich stärker an den Eigenschaften, die man von gewöhnlichen HTTP-Diensten erwartet:

  • Requests lassen sich unabhängig routen.
  • Zustand wird explizit referenziert.
  • Antworten können kontrolliert gecacht werden.
  • Traces funktionieren über Systemgrenzen hinweg.
  • optionale Funktionen werden als Extensions entwickelt
  • veraltete Funktionen haben einen vorhersehbaren Lebenszyklus

Die Komplexität verschwindet dadurch nicht. Ein Server, der früher seinen Zustand in einer Session-Map abgelegt hat, benötigt jetzt ein sauberes Handle-Modell. Ein bidirektionaler Ablauf wird zu einem expliziten Zustandsautomaten. Schreibende Requests müssen mit Wiederholungen umgehen können.

Das sind aber Probleme der Anwendung und nicht des Transports. Genau dort gehören sie hin.

MCP wirkt mit dieser Revision weniger wie ein bequemer JSON-RPC-Wrapper für lokale Tools und mehr wie ein Protokoll, auf dem sich tatsächlich verteilte Agentensysteme betreiben lassen. Für lokale Hobbyprojekte ist ein sofortiges Upgrade nicht zwingend. Wer MCP-Server als gemeinsame Infrastruktur bereitstellt, sollte sich 2026-07-28 dagegen sehr genau ansehen.