KI & Automatisierung

Model Context Protocol auf einem kleinen Server — Was die Spezifikation standardisiert und was sie dir überlässt

Model Context Protocol auf einem kleinen Server — Was die Spezifikation standardisiert und was sie dir überlässt

Es gibt ein Wort, das in fast jeder Unterhaltung über die Verbindung von KI-Assistenten mit echter Software auftaucht: MCP. Das Model Context Protocol gilt als Standardweg, um Modellen Zugriff auf Werkzeuge und Daten zu geben, und das zu Recht. Hinter dem Satz "es ist ein Standard" steckt aber die Frage, die ein kleiner Server tatsächlich beantworten muss: Standardisiert genug, damit zwei Programme von fremden Autoren miteinander sprechen? Oder standardisiert genug, damit ich aufhören kann, über Sicherheit nachzudenken? Bei einem Protokoll, dessen Version jünger ist als zwei Monate vor diesem Artikel, lohnt sich die genaue Antwort.

Ich möchte hier drei Dinge trennen: was die Spezifikation tatsächlich sagt, was daraus für einen Server folgt, den ich selbst betreibe, und was ich nicht überprüfen konnte. Alle versionsbezogenen Aussagen unten beziehen sich auf die Revision 2026-07-28, die heute neueste verfügbare.

Was das Protokoll tatsächlich standardisiert

Die Spezifikation beschreibt MCP als offenes Protokoll, das LLM-Anwendungen mit externen Datenquellen und Werkzeugen verbindet. Die Kommunikation läuft über JSON-RPC 2.0-Nachrichten zwischen drei Rollen: dem Host, also der KI-Anwendung, die die Verbindung initiiert; dem Client, dem Konnektor innerhalb des Hosts; und dem Server, dem Dienst, der Kontext und Fähigkeiten bereitstellt. Als Vorbild nennt die Spezifikation das Language Server Protocol, das für Programmiersprachen ein ähnliches Problem gelöst hat: Jeder Editor integriert jede Sprache einzeln.

Die Architekturseite ergänzt den betrieblich relevanten Teil: Ein Host betreibt viele Clients, und jeder Client spricht mit genau einem Server. Eine KI-Anwendung kann deshalb einen Dateiserver, eine Datenbank und eine entfernte API erreichen, ohne dass diese etwas voneinander wissen. Eines der vier Designprinzipien dort ist keine Funktion, sondern eine Grenze: Server "should not be able to read the whole conversation, nor 'see into' other servers".

Was ein Server anbieten kann, ist klein und konkret: Resources (Kontext und Daten), Prompts (Textvorlagen) und Tools (Funktionen, die ein Modell ausführen kann). Was ein Client in dieser Revision anbietet, ist schmaler: Elicitation, also die Bitte des Servers an den Nutzer um weitere Informationen.

Ein Tool ist ein Name, eine Beschreibung und ein Schema

Die Tools-Spezifikation ist auf eine angenehme Weise langweilig. Ein Tool hat einen eindeutigen name, eine menschenlesbare description und ein inputSchema als JSON-Schema-Objekt, das auf JSON Schema 2020-12 zurückfällt, wenn kein $schema angegeben ist. Hier ist die Tool-Definition aus dem Beispiel der Spezifikation:

{
  "name": "get_weather",
  "description": "Get current weather information for a location",
  "inputSchema": {
    "type": "object",
    "properties": {
      "location": { "type": "string", "description": "City name or zip code" }
    },
    "required": ["location"]
  }
}

Der Aufruf ist eine tools/call-Anfrage; das Ergebnis kommt mit content, optional structuredContent passend zu einem outputSchema, sowie einem isError-Kennzeichen zurück. Dieses Kennzeichen wiegt schwerer, als es aussieht. Die Spezifikation trennt Fehler in zwei Arten: Protokollfehler (unbekanntes Tool, fehlerhafte Anfrage) kommen als JSON-RPC-Fehler, Ausführungsfehler (Datum im falschen Format, Wert außerhalb des Bereichs, fehlgeschlagener API-Aufruf) kommen als normales Ergebnis mit isError: true. Clients sollen dem Modell die zweite Art weiterreichen, damit es sich korrigieren kann, und mit der ersten Art vorsichtiger umgehen, weil ein Modell sie meist nicht beheben kann.

Zwei Details sind einen Blick wert. Erstens gelten Tools als model-controlled: Das Modell entscheidet, ob es ein Tool aufruft, und das Protokoll schreibt keine bestimmte Oberfläche dafür vor, wie der Nutzer das sehen soll. Die Empfehlung der Spezifikation lautet, dass immer ein Mensch im Ablauf sein sollte, der Tool-Aufrufe ablehnen kann. Zweitens darf eine Tool-Definition annotations über ihr Verhalten enthalten, und sowohl die Tools-Seite als auch der Sicherheitsabschnitt auf oberster Ebene warnen davor, diese Annotations als nicht vertrauenswürdig zu behandeln, sofern sie nicht von einem bereits vertrauenswürdigen Server stammen. Eine Tool-Beschreibung ist Inhalt von einem Fremden, kein Versprechen aus eigenem Code.

Zwei Transportwege, zwei Risikoprofile

Dieselbe Werkzeugliste bewegt sich völlig unterschiedlich, je nachdem, wie der Server erreicht wird, und die Spezifikation behandelt beide Fälle als unterschiedliche Sicherheitssituationen.

stdio ist der lokale Fall. Der Client startet den Server als Subprozess und spricht Zeile für Zeile JSON-RPC über dessen Standard-Streams. Nachrichten dürfen keine eingebetteten Zeilenumbrüche enthalten, stderr ist für Logs vorgesehen, und der Server "MUST NOT write anything to its stdout that is not a valid MCP message". Diese Regel existiert, weil ein versehentliches print() in einem Skript genau den Stream zerstört, den der Client gerade liest. Server sollten beenden, wenn ihre Eingabe schließt, und Clients sollen einen unerwartet beendeten Prozess neu starten — das ist inzwischen günstig, weil es keinen Sitzungszustand zu retten gibt.

Streamable HTTP ist der entfernte Fall. Der Server stellt einen einzigen Endpunkt bereit, der POST annimmt; jede Anfrage ist ein eigener POST und wird entweder mit einem JSON-Objekt oder mit einem auf diese Anfrage bezogenen Server-Sent-Events-Stream beantwortet. Der Sicherheitsabschnitt der Transportspezifikation ist ungewöhnlich direkt und eignet sich besser als Checkliste als als Theorie:

  • Server MUST den Origin-Header jeder eingehenden Verbindung prüfen und bei vorhandenem, aber ungültigem Wert mit 403 Forbidden antworten — ohne diese Prüfung kann ein Angreifer per DNS-Rebinding einen lokalen MCP-Server ausgehend von einer Webseite erreichen.
  • Lokal betriebene Server SHOULD nur an 127.0.0.1 binden, nicht an 0.0.0.0.
  • Server SHOULD für alle Verbindungen eine echte Authentifizierung umsetzen.
  • Hinter einem Reverse Proxy SHOULD Server den Header X-Accel-Buffering: no senden, damit nginx den Event-Stream nicht puffert.

Anfragen tragen außerdem Pflicht-Metadaten. Jeder POST muss die Header MCP-Protocol-Version und Mcp-Method enthalten sowie Mcp-Name für tools/call, resources/read und prompts/get. Weicht ein Header vom Wert im Body ab, muss der Server die Anfrage mit 400 Bad Request und einem HeaderMismatch-Fehler ablehnen — die Regel verhindert, dass Load Balancer und Backend Entscheidungen aus zwei verschiedenen Quellen der Wahrheit ableiten. Als Shell-Befehl statt als Client formuliert sieht so ein Werkzeugabruf aus:

curl -i -X POST https://mcp.example.lan/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2026-07-28' \
  -H 'Mcp-Method: tools/list' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/list",
    "params": {
      "_meta": {
        "io.modelcontextprotocol/protocolVersion": "2026-07-28",
        "io.modelcontextprotocol/clientCapabilities": {}
      }
    }
  }'

Diese Anfrage ist aus den Anforderungen der Spezifikation zusammengesetzt, nicht aus einer laufenden Sitzung kopiert — ich habe keinen MCP-Server aufgesetzt, um sie erfolgreich zu beobachten. Der _meta-Block ist keine Dekoration: io.modelcontextprotocol/protocolVersion und io.modelcontextprotocol/clientCapabilities sind in jeder Anfrage erforderlich; eine Anfrage ohne eines dieser Felder ist fehlerhaft und muss mit JSON-RPC -32602 sowie über HTTP mit 400 Bad Request abgelehnt werden.

Die neueste Revision hat den Handshake abgeschafft

Hier liegt der Grund, die Spezifikation statt eines verbreiteten Artikels zu lesen. In Revision 2026-07-28 ist MCP auf Protokollebene zustandslos. Der Handshake initialize / notifications/initialized, der Sitzungen früherer Versionen definierte, ist entfernt, ebenso Protokollsitzungen und der Header Mcp-Session-Id; jede Anfrage trägt Version und Fähigkeiten selbst. Server, die Zustand über Aufrufe hinweg brauchen, sollen explizite Handle erzeugen und sie als gewöhnliches Tool-Argument zurückerhalten — eine Warenkorb-ID ist einfach eine Zeichenkette, und das Modell ist dafür verantwortlich, sie weiterzutragen.

Die Versionsaushandlung ist mitgezogen. Es gibt keinen Aushandlungs-Handshake: Der Client trägt seine bevorzugte Version in die Anfrage ein, und ein Server, der sie nicht implementiert, antwortet mit UnsupportedProtocolVersionError, dem JSON-RPC-Code -32022 und der Liste der tatsächlich unterstützten Versionen. Server MUST die Methode server/discover implementieren, die unterstützte Versionen, Fähigkeiten und Identität nennt; Clients MAY sie zuerst aufrufen, und die Seite zur Versionsverwaltung nutzt sie als Sonde, um einen modernen von einem älteren Server zu unterscheiden.

Für alle, die Software betreiben, die mit anderen interoperieren muss, ist das Changelog die ehrliche Zusammenfassung darüber, wie wenig gefestigt dieser Zustand noch ist. Streamable HTTP verlor den GET-Endpunkt, den Sitzungsheader und fortsetzbare Event-Streams; Abonnements wurden um eine einzige subscriptions/listen-Anfrage herum neu gebaut; experimentelle Aufgaben wanderten aus dem Kernprotokoll in eine optionale Extension; und Roots, Sampling sowie Logging — die Funktionen, die viele Tutorials von 2024 und 2025 benutzen — sind jetzt deprecated, ebenso der alte HTTP+SSE-Transport. Das Projekt hat außerdem eine Deprecation-Richtlinie mit einem Mindestfenster von zwölf Monaten übernommen, was immerhin bedeutet, dass eine funktionierende Funktion nicht über Nacht verschwinden sollte.

Was das Protokoll nicht für Sie erledigt

Der zitierfähigste Satz der gesamten Spezifikation ist zugleich der für den Betrieb wichtigste. Nach den Grundsätzen zu Einwilligung, Datenschutz, Tool-Sicherheit und Sampling-Kontrolle stellt die Spezifikation nüchtern fest: "MCP itself cannot enforce these security principles at the protocol level", und listet dann auf, was Implementierende tun sollen — Zustimmungs- und Autorisierungsflüsse bauen, Sicherheitsauswirkungen dokumentieren, Zugriffskontrollen umsetzen, bewährte Sicherheitsverfahren einhalten und Datenschutz beim Feature-Entwurf bedenken.

Praktisch heißt das: Die Sicherheitsgrenze ist Ihr Server und Ihre Host-Anwendung, nicht das Übertragungsformat. Die Tools-Seite fasst die Pflichten des Servers als MUST-Liste zusammen: alle Tool-Eingaben validieren, ordentliche Zugriffskontrolle umsetzen, Tool-Aufrufe begrenzen und Ausgaben bereinigen. Alles rund um die Frage, ob ein Mensch einen Aufruf genehmigt hat, ist eine Oberflächenentscheidung, die das Protokoll offenlässt — und genau deshalb empfiehlt die Spezifikation Human-in-the-Loop-Verhalten, statt es zu verlangen.

Wenn der Server nicht auf Ihrem Rechner läuft

Autorisierung ist die Stelle, an der ein kleiner Server am ehesten eine Ecke abschneidet, deshalb kurz, was die Spezifikation tatsächlich verlangt. Autorisierung ist für MCP-Implementierungen optional. Implementierungen mit HTTP-Transport SHOULD der Autorisierungsspezifikation folgen; Implementierungen mit stdio SHOULD NOT ihr folgen und stattdessen Zugangsdaten aus der Umgebung beziehen. Diese Unterscheidung ist praktisch: Ein Subprozess, den Ihre Desktop-Anwendung selbst startet, hat ein anderes Vertrauensverhältnis als eine URL, die aus dem Internet erreichbar ist.

Für HTTP-Server baut die Spezifikation auf bestehender OAuth-Arbeit auf, statt ein eigenes Verfahren zu erfinden. Server müssen OAuth 2.0 Protected Resource Metadata (RFC 9728, April 2025) implementieren, Clients müssen Resource Indicators (RFC 8707, Februar 2020) verwenden, also den Mechanismus, der ein Access-Token an die Ressource bindet, für die es tatsächlich ausgestellt wurde. Die Seite zu den Sicherheitsüberlegungen ergänzt, dass ein Server den vom MCP-Client erhaltenen Token beim Aufruf einer vorgelagerten API nicht weiterreichen darf ("MUST NOT pass through the token it received from the MCP client"), und das Dokument Security Best Practices erklärt warum: Ein Server, der Token weiterreicht, ohne sie für sich selbst zu prüfen, wird zum confused deputy — einem Mittelsmann, dessen Anfragen aussehen, als kämen sie von jemand anderem.

Ein weiteres Risiko entsteht erst dadurch, dass das Protokoll zustandslos wurde. Ohne Protokollsitzung ist jeder Zustand über Aufrufe hinweg ein Handle, den der Server selbst erzeugt hat — und das Sicherheitsdokument behandelt den Besitz eines solchen Handles als Besitz des Zustands einer anderen Person, sofern Sie nicht selbst etwas dagegen tun. Empfohlen wird, Handles mit einem sicheren Zufallsgenerator zu erzeugen, sie zu befristen und serverseitig an den authentifizierten Nutzer zu binden, statt dem String zu vertrauen, der in einem Tool-Ergebnis zurückkommt.

Eine Checkliste, die ich selbst verwenden würde

Dieser Abschnitt ist meine eigene Interpretation, kein Spezifikationstext, und er ist auf einen kleinen selbst gehosteten Aufbau zugeschnitten:

  • Mit stdio beginnen. Ein lokaler Subprozess vermeidet die gesamte HTTP-Angriffsfläche, und die offizielle Empfehlung für Zugangsdaten bei stdio lautet schlicht: Umgebung.
  • Bevor Sie an etwas anderem als localhost binden, schreiben Sie die Origin-Prüfung und die Authentifizierung, statt sie zu planen. Beides sind MUST- beziehungsweise SHOULD-Punkte der Spezifikation, und beides ist billig, solange der Server noch geschrieben wird.
  • Lesen Sie die Version, die der Client tatsächlich sendet, nicht die Version, die er in einer README nennt. Die Revision, auf der dieser Artikel beruht, hat das Handshake-Modell vollständig verändert; eine verschwundene Werkzeugliste ist oft nur ein Versionskonflikt.
  • Behandeln Sie Werkzeugbeschreibungen und Annotations eines Servers, den Sie nicht geschrieben haben, als nicht vertrauenswürdige Eingabe — in derselben Kategorie wie eine Webseite, die Sie nicht in einen Prompt einfügen würden.
  • Wenn Sie Handles für Zustand erzeugen, binden Sie sie an den Aufrufer. Wenn Sie Anfragen an eine andere API weiterleiten, tauschen Sie das Token aus, statt es durchzureichen.

Was ich nicht überprüfen konnte

Ich habe nichts davon an einem laufenden MCP-Server ausprobiert. Deshalb kann ich nicht sagen, wie gut aktuelle Clients mit einer zustandslosen Revision umgehen, die erst vor zwei Monaten veröffentlicht wurde, und ich behaupte bewusst nichts darüber, welche Anwendung welche Revision unterstützt — die Spezifikation selbst garantiert nur das Verhalten konformer Implementierungen. Eine Bewertung des Extension-Ökosystems, der Registry oder der Liste deprecated Funktionen habe ich ebenfalls nicht vorgenommen, und ich habe keine konkrete Serverimplementierung auf Sicherheitsprobleme untersucht. Wo dieser Artikel das Protokoll beschreibt, beschreibt er das Dokument; wo er Risiken beschreibt, ist das meine Lesart dessen, was das Dokument den Implementierenden als Verantwortung aufgibt.

Die Zusammenfassung ist eng, aber sie ist brauchbar. MCP standardisiert die Unterhaltung zwischen einer KI-Anwendung und Ihren Werkzeugen: ein Drahtformat, dessen Spezifikation aus dem Jahr 2010 stammt, eine Werkzeugliste in JSON Schema und eine versionierte Möglichkeit zu sagen "ich spreche nicht deinen Dialekt". Nicht standardisiert ist die Frage, ob überhaupt jemand Ihr Werkzeug hätte aufrufen sollen — und die Spezifikation sagt das ganz offen. Für einen kleinen Server ist das Protokoll der einfache Teil. Der schwierige Teil ist genau der, den die Spezifikation Ihnen bewusst überlässt.

References