---
title: "MCP-Transporte: stdio gegen HTTP, und was sich gerade geändert hat"
description: "Die meisten Antworten auf diese Frage beschreiben eine Spezifikation, die es nicht mehr gibt. Sessions, GET-Stream und fortsetzbares SSE sind weg. Der aktuelle Stand."
author: "Matthias Meyer"
published: 2026-08-03
updated: 2026-08-03
language: de
tags: ["mcp", "stdio", "http", "protokoll", "transporte", "spec"]
canonical: "https://studiomeyer.io/de/blog/mcp-stdio-vs-http"
markdown_versions: ["https://studiomeyer.io/de/blog/mcp-stdio-vs-http.md", "https://studiomeyer.io/en/blog/mcp-stdio-vs-http.md", "https://studiomeyer.io/es/blog/mcp-stdio-vs-http.md"]
publisher: "StudioMeyer, https://studiomeyer.io (llms.txt: https://studiomeyer.io/llms.txt)"
---

# MCP-Transporte: stdio gegen HTTP, und was sich gerade geändert hat

Unsere eigenen Suchdaten sagen mir seit Monaten, dass ich diesen Text schreiben soll, und ich habe es ignoriert. Einunddreißig Schreibweisen derselben Frage, Dinge wie „stdio vs http mcp" und „mcp server stdio vs http", knapp dreihundert Impressionen, mehrere davon auf Seite eins. Null Klicks, weil wir dafür versehentlich aus anderen Artikeln ranken und nie eine Seite hatten, die die Frage tatsächlich beantwortet.

Hier ist die Seite. Und beim Schreiben kam etwas heraus, womit ich nicht gerechnet hatte: Das meiste, was man zu dieser Frage findet, auch die vordersten Treffer, beschreibt eine Fassung des Protokolls, die ersetzt wurde.

## Die kurze Antwort

Wenn ein einzelner Client den Server auf derselben Maschine startet und dieser lokale Dateien, lokale Datenbanken oder lokale Werkzeuge anfasst, nimm stdio. Wenn der Server über ein Netzwerk erreichbar sein muss, mehr als einen Client bedient oder irgendwo läuft, wohin du deployst statt dich davorzusetzen, nimm Streamable HTTP.

Wenn du gerade auf HTTP+SSE bauen willst, dem Zwei-Endpunkt-Transport aus der Revision 2024-11-05: lass es. Er gilt seit 2025-03-26 als veraltet, neue Implementierungen sollen ihn nicht mehr aufgreifen, und er kann in einer künftigen Revision entfernt werden.

Damit sind die meisten Entscheidungen erledigt. Der Rest dieses Textes handelt von dem, was danach beißt.

## stdio: eine Leitung, und ein Fehler, den alle schreiben

Der Client startet deinen Server als Subprozess und spricht über die Standard-Streams mit ihm. Der Server liest JSON-RPC von `stdin` und schreibt JSON-RPC nach `stdout`, eine Nachricht pro Zeile, per Zeilenumbruch getrennt, und Nachrichten dürfen keine eingebetteten Zeilenumbrüche enthalten.

Jetzt die Regel, die mehr stdio-Server zerlegt als alles andere, und sie ist es wert, zitiert zu werden, weil man beim Überfliegen darüber hinwegliest: Der Server **DARF NICHT** irgendetwas nach `stdout` schreiben, das keine gültige MCP-Nachricht ist.

Das heißt: Jedes `print()`, jedes `console.log()`, jede verirrte Debug-Zeile aus einer Bibliothek, die du eingebunden hast, landet direkt im Nachrichtenkanal und beschädigt das Protokoll. Der Client sieht kaputtes JSON, wo er eine Antwort erwartet hat. Die Symptome reichen von einem Werkzeug, das nie zurückkommt, bis zu einem Server, der verbunden aussieht und beim ersten Aufruf stirbt.

Die Lösung steht im selben Absatz der Spezifikation: `stderr` gehört dir. Der Server darf dorthin UTF-8 für jeden Protokollierungszweck schreiben, und der Client darf das mitschneiden, weiterreichen oder ignorieren. Die Spezifikation sagt ausdrücklich, dass Ausgaben auf `stderr` vom Client nicht als Fehlerzustand gewertet werden sollen. Also leite dein Logging dorthin und lass `stdout` in Ruhe.

Zwei weitere Dinge lohnen sich zu wissen. Das Herunterfahren beginnt damit, dass der Client deinen Eingabe-Stream schließt, ein Server sollte also zügig beenden, wenn `stdin` das Dateiende erreicht. Das ist das primäre Signal für einen geordneten Abbruch und laut Spezifikation das einzige portable. Und wenn dein Prozess unerwartet stirbt, sollte der Client ihn neu starten. Weil das Protokoll zustandslos ist, gehen laufende Anfragen schlicht verloren, und der Client kann sie gegen den frischen Prozess wiederholen. Auf die Formulierung kommt es an: Sie gehen verloren, sie werden nicht automatisch wiederholt. Ob ein neuer Versuch stattfindet, entscheidet dein Aufrufer, nicht der Transport.

## Streamable HTTP: ein Endpunkt, ein POST je Nachricht

Der Server stellt einen einzigen HTTP-Endpunkt bereit, der POST annimmt. Jede JSON-RPC-Anfrage und jede Benachrichtigung ist ein eigener POST. Auf eine Anfrage antwortet der Server entweder mit einem einzelnen JSON-Objekt oder mit einem SSE-Stream, der genau zu dieser Anfrage gehört und Fortschritts-Benachrichtigungen und dann die finale Antwort trägt. Auf eine Benachrichtigung gibt es gar keine JSON-RPC-Antwort: Eine angenommene Benachrichtigung bekommt HTTP `202 Accepted` ohne Body.

Drei Anforderungen auf Client-Seite übersieht man leicht. Der `Accept`-Header muss sowohl `application/json` als auch `text/event-stream` nennen, weil der Server je Anfrage entscheidet, was er schickt, und der Client beides beherrschen muss. Der Body muss eine einzelne JSON-RPC-Anfrage oder -Benachrichtigung sein, niemals eine Antwort. Und die Metadaten-Header, zu denen ich gleich komme, sind Pflicht.

Für langlebige Benachrichtigungen vom Server zum Client gibt es jetzt einen eigenen Weg: Du schickst eine `subscriptions/listen`-Anfrage, und deren Antwort-Stream bleibt offen und trägt genau die Benachrichtigungstypen, die du abonniert hast. Anfrage-bezogene Benachrichtigungen wie Fortschritt laufen nicht über diesen Stream, sondern über den Antwort-Stream der Anfrage, zu der sie gehören.

## Was sich in der Revision 2026-07-28 geändert hat

Das ist der Teil, der die meisten vorhandenen Darstellungen falsch macht, auch etliche Anleitungen aus diesem Jahr.

**Sessions auf Protokollebene sind weg.** Frühere Revisionen ließen den Server über einen `Mcp-Session-Id`-Header eine Sitzung vergeben, beendet per HTTP DELETE. Dieser Mechanismus ist nicht Teil der aktuellen Revision. Ein Server, der nur die neue Revision umsetzt, sollte einen eingehenden `Mcp-Session-Id`-Header vollständig ignorieren und darf keine Sitzungs-IDs vergeben oder zurückspiegeln.

**Der eigenständige GET-Stream ist weg.** Clients öffneten früher per GET einen separaten SSE-Stream, um vom Server ausgehende Nachrichten zu empfangen. Entfernt. Ein Server, der nur diese Revision unterstützt, sollte GET oder DELETE auf dem MCP-Endpunkt mit `405 Method Not Allowed` beantworten; Server, die daneben noch die älteren Revisionen sprechen, bedienen sie natürlich weiter.

**Fortsetzbare Streams sind weg.** `Last-Event-ID` wird nicht unterstützt. Ein Server, der nur für diese Revision gebaut ist, sollte diesen Header ignorieren, ebenso jede eingehende `Mcp-Session-Id`.

**Server schicken keine eigenen Anfragen mehr.** Wenn ein Server etwas vom Client braucht, also Sampling, Elicitation oder Roots, schickte er früher eine JSON-RPC-Anfrage durch einen SSE-Stream. Jetzt liefert er ein `InputRequiredResult` zurück, und der Client wiederholt den ursprünglichen Aufruf mit den Antworten im Gepäck. Das heißt Multi Round-Trip Requests und ist eine echte Änderung im Kontrollfluss, keine Umbenennung.

**Und der `initialize`-Handshake selbst ist jetzt der Altweg.** Moderne Revisionen tragen Protokollversion, Client-Fähigkeiten und Client-Identität pro Anfrage in `_meta.io.modelcontextprotocol/*`-Feldern, statt sie einmalig in einem verbindungsgebundenen Handshake festzulegen.

Wenn du etwas davon in einer Anleitung gelesen hast, die noch `Mcp-Session-Id`-Buchhaltung und einen GET-Stream zeigt, beschreibt diese Anleitung die Form von 2025-03-26 bis 2025-11-25. Das ist keine falsche Geschichte, und viele laufende Server sprechen sie weiterhin, aber es ist nicht das, wogegen man heute baut.

## Drei Header, die jetzt Pflicht sind

Streamable HTTP spiegelt ausgewählte Body-Felder in HTTP-Header, damit Load Balancer, Gateways und Observability-Werkzeuge Anfragen routen und inspizieren können, ohne den Body zu parsen.

Jeder POST an den MCP-Endpunkt muss `MCP-Protocol-Version` tragen, zum Beispiel `MCP-Protocol-Version: 2026-07-28`. `Mcp-Method` ist bei allen Anfragen Pflicht, `Mcp-Name` bei `tools/call`, `resources/read` und `prompts/get`, und trägt dort den Werkzeugnamen, die Ressourcen-URI beziehungsweise den Prompt-Namen.

Und hier die Falle: Der Header-Wert muss zum entsprechenden Wert im Body passen. Tut er das nicht, muss der Server die Anfrage mit `400 Bad Request` und dem JSON-RPC-Fehler `-32020` namens `HeaderMismatch` zurückweisen. Die Begründung ist ein echtes Sicherheitsanliegen und keine Pedanterie. Wenn ein Load Balancer nach dem Header routet, während der Server nach dem Body ausführt, und beide sich widersprechen, hast du eine Anfrage, die zur Infrastruktur eines Mandanten geht und den Aufruf eines anderen ausführt.

Werte, die sich nicht sicher als reines ASCII schreiben lassen, also alles mit Nicht-ASCII-Zeichen, Steuerzeichen oder führenden und nachlaufenden Leerzeichen, müssen Base64-kodiert werden, im Sentinel-Format `=?base64?WERT?=`. Server dekodieren das vor dem Abgleich mit dem Body.

## Die Sicherheitsregeln für HTTP, die nicht optional sind

Drei Anforderungen, und die erste ist die, die übersprungen wird.

Server **MÜSSEN** den `Origin`-Header bei allen eingehenden Verbindungen prüfen, um DNS-Rebinding-Angriffe zu verhindern, und müssen einen ungültigen Origin mit `403 Forbidden` beantworten. Ohne das kann eine Website, die deine Nutzerin im Browser besucht, einen lokalen MCP-Server auf ihrer Maschine erreichen und steuern. Der Angriff funktioniert genau deshalb, weil der Server lokal und vertrauenswürdig ist.

Im lokalen Betrieb **SOLLEN** Server nur an `127.0.0.1` binden statt an `0.0.0.0`. An alle Schnittstellen zu binden setzt deinen Werkzeug-Server auf jedes Netz, dem dieser Laptop beitritt, Hotel-WLAN eingeschlossen.

Und Server **SOLLEN** eine ordentliche Authentifizierung für alle Verbindungen umsetzen. Der Transport selbst schreibt kein Verfahren vor, welches du nimmst, ist also eine bewusste Entscheidung und keine, die dir der Transport abnimmt. In der Praxis heißt das die Autorisierung, die die MCP-Spezifikation getrennt definiert, oder eine Bearer-Anmeldung, die du kontrollierst.

Nichts davon gilt für stdio, und das ist ein großer Teil des Grundes, warum stdio für lokale Arbeit die richtige Wahl bleibt. Es gibt keinen Port, keinen Origin und keine lauschende Netzwerkfläche, die Prozessgrenze ist also die Sicherheitsgrenze des Transports.

Eine Sache gibt dir stdio allerdings nicht, und das gehört gesagt, weil es breit angenommen wird: Es ist keine Garantie, dass deine Daten auf der Maschine bleiben. Der Transport ist lokal, das Verhalten des Servers ist es nicht. Ein stdio-Server ist ein ganz normaler Prozess, der jede ausgehende Verbindung öffnen kann, die ihm gefällt, und etliche existieren genau dafür, in deinem Namen eine entfernte API aufzurufen. stdio sagt dir, wie der Client mit dem Server spricht. Es sagt dir nichts darüber, wohin der Server die Dinge danach schickt.

## Die Entscheidung als Tabelle

| | stdio | Streamable HTTP |
|---|---|---|
| Wer startet den Server | der Client, als Subprozess | du, unabhängig |
| Erreichbar für | genau diesen einen Client | alles, was die URL erreicht |
| Netzwerkfläche | keine | ein Port, mit allem was folgt |
| Abbruch | `notifications/cancelled` | den Stream der Anfrage schließen |
| Authentifizierung | Prozessgrenze | eigenes Verfahren plus Origin-Prüfung |
| Zeitgesteuert und kopflos | braucht einen Prozess, der es startet | natürliche Passform |
| Typischer Fall | lokale Dateien, lokale Datenbank, IDE-Werkzeuge | gehostete API, Team-Server, SaaS |

In der Praxis ist die Entscheidung selten knapp. Wirklich überlegenswert ist der Fall, in dem ein Anbieter beides anbietet, was zunehmend vorkommt: ein lokales Paket zum Installieren und einen gehosteten Endpunkt zum Draufzeigen. Dort ist die entscheidende Frage nicht technische Eleganz, sondern wer die Zugangsdaten hält und was der Server damit macht. Ein lokales Paket hält die Zugangsdaten auf deiner Maschine, das ist ein echter Unterschied. Es hält, wie der vorige Abschnitt sagt, nicht deine Daten dort. Wenn dieser Unterschied für deinen Fall zählt, liegt die Antwort im Verhalten des Servers und in seinen Datenschutzbedingungen, nicht in seinem Transport.

## Zwei Dinge, die ich vor dem Bauen prüfen würde

Prüfe, welche Revision dein Client tatsächlich spricht, nicht welche die Dokumentation beschreibt. Die Spezifikation trägt ein ausdrückliches Verfahren für Versions-Aushandlung und Rückfall, gerade weil das Feld im Moment über mehrere Revisionen zerfällt, und sie empfiehlt ein Vorab-Abtasten selbst für Clients, die nur moderne Versionen können. Der Gewinn: Ein Versatz scheitert vorhersagbar, statt dass ein alter Server deinen Aufruf still unter anderer Semantik verarbeitet.

Und wenn du einen stdio-Server schreibst, setz eine Lint-Regel oder einen Test darauf, der fehlschlägt, sobald irgendetwas außerhalb des Nachrichten-Schreibers nach `stdout` gelangt. Das ist der billigste Schutz gegen den einen Fehler, der jeden einen Nachmittag kostet, und er ist schneller eingebaut als die erste Debugging-Sitzung dauert, die er verhindert.

Die Transporte selbst sind nicht kompliziert. Was dieses Thema verwirrend macht, ist dass sich der Boden kürzlich bewegt hat und das meiste Geschriebene darüber noch nicht nachgezogen ist. Wenn du eine Sache aus diesem Text mitnimmst, dann die Gewohnheit, bei allem was du liest auf das Datum der Revision zu schauen.

## Weiterlesen, Claude + Claude Code

- [Ahrefs MCP Server: Einrichtung für Claude, Codex und den Rest](https://studiomeyer.io/de/blog/ahrefs-mcp-server-setup.md)
- [Dokumente mit Claude lesen und analysieren](https://studiomeyer.io/de/blog/analyze-documents-with-claude.md)
- [Die sich wiederholenden Teile deiner Woche mit Claude automatisieren](https://studiomeyer.io/de/blog/automating-tasks-with-claude.md)
- [Claude 2026: Modelle, Apps, Claude Code und die API](https://studiomeyer.io/de/blog/claude-guide-2026.md)
