JSON-RPC — das Wire-Format

MCP ist nicht selbst ein Protokoll — es ist eine Reihe von Methoden und Datentypen, die über JSON-RPC 2.0 transportiert werden. JSON-RPC ist alt, simpel, und tut nur eins: strukturierte Anfragen und Antworten mit IDs durch einen beliebigen Kanal schicken.

Vier Message-Typen: Request (mit id, erwartet Antwort) · Response (Erfolg, gleiche id) · Error (statt result, gleiche id) · Notification (kein id, keine Antwort). Mehr nicht.

Message-Player

{
  "jsonrpc": "2.0",
  "id": 42,
  "method": "tools/call",
  "params": {
    "name": "create_issue",
    "arguments": {
      "title": "Bug in Slider"
    }
  }
}
Eine Anfrage. Drei Pflichtfelder: jsonrpc, id, method. Die id wird gebraucht, damit die Antwort zugeordnet werden kann — Requests können async und out-of-order beantwortet werden.

Die ID — der Sortierungs-Trick

Eine TCP-Verbindung ist ein Stream: Bytes fließen rein, Bytes fließen raus. Ohne IDs hätte der Client keine Chance, zwei Antworten den richtigen Requests zuzuordnen — denn JSON-RPC erlaubt async und out-of-order. Mit IDs ist die Zuordnung trivial:

Client →  { id: 1, method: "tools/list" }
Client →  { id: 2, method: "resources/list" }
Server ←  { id: 2, result: [...] }        ← Antwort auf #2 zuerst!
Server ←  { id: 1, result: [...] }        ← Antwort auf #1 später

Standardisierte Fehler-Codes

Code
Name
Bedeutung
-32700
Parse error
JSON war kaputt.
-32600
Invalid request
Nicht-valide Request-Struktur.
-32601
Method not found
Methode existiert nicht.
-32602
Invalid params
Parameter passen nicht zum Schema.
-32603
Internal error
Server-interner Fehler.

Eigene Codes vergibst du in -32000 bis -32099 — das ist der Server-Error-Range, den JSON-RPC für Anwendungs-Fehler offenlässt.

Batch — mehrere Requests gleichzeitig

JSON-RPC erlaubt ein Array statt eines Objekts als Payload. Der Server verarbeitet alle und gibt ein Array von Responses zurück. Praktisch für Setup-Phase, wenn der Client zur gleichen Zeit nach Tools, Resources und Prompts fragt:

[
  { "jsonrpc": "2.0", "id": 1, "method": "tools/list" },
  { "jsonrpc": "2.0", "id": 2, "method": "resources/list" },
  { "jsonrpc": "2.0", "id": 3, "method": "prompts/list" }
]
Warum eigentlich?Warum JSON-RPC und nicht REST oder gRPC?
REST braucht URLs, das passt schlecht zu stdio-Transport (keine URLs). gRPC hätte typsicheres Schema, aber binäre Encoding macht es für Menschen schwer lesbar und Debug-Tools komplex. JSON-RPC ist ein Kompromiss: simpel genug für Hand-Debug, mächtig genug für bidirektionalen async Verkehr, transport-agnostisch.
Häufiger Denkfehlerid=0 vergessen
0 ist eine gültige ID. Wenn dein Client mit if (msg.id) filtert, übersieht er Request mit id=0. Stattdessen: if (msg.id !== undefined). Klassischer Bug, der nur in 1 von 10000 Sessions auftritt — und dann stundenlang unverständlich ist.
Tiefer reinString- vs. Number-IDs
JSON-RPC erlaubt sowohl Strings als auch Numbers als ID. SDKs sind unterschiedlich: das offizielle MCP-Python-SDK nutzt Numbers, das TypeScript-SDK Strings (UUIDs). Beim Mischen aufpassen: ein Client, der nur Numbers parst, kann ein Server-Result mit String-ID nicht mehr zuordnen.