- Startseite
- Documentation
- MCP
- MCP-Protokoll und Transport-Interna
MCP-Protokoll und Transport-Interna
Dieses Dokument beschreibt, wie coding-agent MCP-JSON-RPC-Messaging implementiert und wie Protokollbelange von Transportbelangen getrennt werden.
Geltungsbereich
Abschnitt betitelt „Geltungsbereich“Behandelt:
- JSON-RPC-Request/Response- und Notification-Ablauf
- Request-Korrelation und Lebenszyklus für stdio- und HTTP/SSE-Transporte
- Timeout- und Abbruchverhalten
- Fehlerweitergabe und Behandlung fehlerhafter Payloads
- Transportauswahl-Grenzen (
stdiovshttp/sse) - Welche Reconnect-/Retry-Verantwortlichkeiten auf Transport-Ebene vs. Manager-Ebene liegen
Behandelt nicht die Extension-Authoring-UX oder die Befehlsoberfläche.
Implementierungsdateien
Abschnitt betitelt „Implementierungsdateien“src/mcp/types.tssrc/mcp/transports/stdio.tssrc/mcp/transports/http.tssrc/mcp/transports/index.tssrc/mcp/json-rpc.tssrc/mcp/client.tssrc/mcp/manager.ts
Schichtgrenzen
Abschnitt betitelt „Schichtgrenzen“Protokollschicht (JSON-RPC + MCP-Methoden)
Abschnitt betitelt „Protokollschicht (JSON-RPC + MCP-Methoden)“- Nachrichtenstrukturen sind in
types.tsdefiniert (JsonRpcRequest,JsonRpcNotification,JsonRpcResponse,JsonRpcMessage). - Die MCP-Client-Logik (
client.ts) bestimmt die Methodenreihenfolge und den Session-Handshake:initialize-Requestnotifications/initialized-Notification- Methodenaufrufe wie
tools/list,tools/call
Transportschicht (MCPTransport)
Abschnitt betitelt „Transportschicht (MCPTransport)“MCPTransport abstrahiert Zustellung und Lebenszyklus:
request(method, params, options?) -> Promise<T>notify(method, params?) -> Promise<void>close()connected- Optionale Callbacks:
onClose,onError,onNotification
Transport-Implementierungen verwalten Framing- und I/O-Details:
StdioTransport: Newline-delimitiertes JSON über Subprocess-stdioHttpTransport: JSON-RPC über HTTP POST, mit optionalen SSE-Responses/Listening
Wichtiger aktueller Vorbehalt
Abschnitt betitelt „Wichtiger aktueller Vorbehalt“Transport-Callbacks (onClose, onError, onNotification) sind implementiert, aber die aktuellen MCPClient/MCPManager-Abläufe verdrahten keine Reconnection-Logik mit diesen Callbacks. Notifications werden nur konsumiert, wenn der Aufrufer Handler registriert.
Transportauswahl
Abschnitt betitelt „Transportauswahl“client.ts:createTransport() wählt den Transport basierend auf der Konfiguration:
typeweggelassen oder"stdio"->createStdioTransport"http"oder"sse"->createHttpTransport
"sse" wird als HTTP-Transport-Variante behandelt (gleiche Klasse), nicht als separate Transport-Implementierung.
JSON-RPC-Nachrichtenfluss und Korrelation
Abschnitt betitelt „JSON-RPC-Nachrichtenfluss und Korrelation“Request-IDs
Abschnitt betitelt „Request-IDs“Jeder Transport generiert pro Request IDs (Math.random + Timestamp-String). IDs sind transport-lokale Korrelations-Token.
Stdio-Korrelationspfad
Abschnitt betitelt „Stdio-Korrelationspfad“- Ausgehende Requests werden als ein JSON-Objekt +
\nserialisiert. #pendingRequests: Map<id, {resolve,reject}>speichert laufende Requests.- Die Lese-Schleife parst JSONL von stdout und ruft
#handleMessageauf. - Wenn eine eingehende Nachricht eine passende
idhat, wird der Request aufgelöst/abgelehnt. - Wenn eine eingehende Nachricht
methodhat und keineid, wird sie als Notification behandelt und anonNotificationgesendet.
Unbekannte IDs werden ignoriert (keine Ablehnung, kein Error-Callback).
HTTP-Korrelationspfad
Abschnitt betitelt „HTTP-Korrelationspfad“- Ausgehender Request ist ein HTTP
POSTmit JSON-Body und generierterid. - Nicht-SSE-Response-Pfad: Eine JSON-RPC-Response parsen und
resultzurückgeben / beierrorwerfen. - SSE-Response-Pfad (
Content-Type: text/event-stream): Events streamen, erste Nachricht zurückgeben, derenidder erwarteten Request-ID entspricht undresultodererrorenthält. - SSE-Nachrichten mit
methodund ohneidwerden als Notifications behandelt.
Wenn der SSE-Stream vor einer passenden Response endet, schlägt der Request fehl mit No response received for request ID ....
Notifications
Abschnitt betitelt „Notifications“Der Client sendet JSON-RPC-Notifications über transport.notify(...).
- Stdio: Schreibt den Notification-Frame auf stdin (
jsonrpc,method, optionaleparams) plus Newline. - HTTP: Sendet POST-Body ohne
id; Erfolg akzeptiert2xxoder202 Accepted.
Server-initiierte Notifications werden nur über den Transport-onNotification-Callback verfügbar gemacht; es gibt keinen globalen Standard-Subscriber im Manager/Client.
Stdio-Transport-Interna
Abschnitt betitelt „Stdio-Transport-Interna“Lebenszyklus und Zustandsübergänge
Abschnitt betitelt „Lebenszyklus und Zustandsübergänge“- Initial:
connected=false,process=null, Pending-Map leer connect():- Subprocess mit konfiguriertem Command/Args/Env/Cwd starten
- Als verbunden markieren
- Stdout-Lese-Schleife starten (
readJsonl) - Stderr-Schleife starten (lesen/verwerfen; derzeit lautlos)
close():- Als getrennt markieren
- Alle ausstehenden Requests ablehnen (
Transport closed) - Subprocess beenden
- Auf Shutdown der Lese-Schleife warten
onCloseauslösen
Wenn die Lese-Schleife unerwartet endet, löst finally #handleClose() aus, das die gleiche Ablehnung ausstehender Requests und den Close-Callback durchführt.
Timeout und Abbruch
Abschnitt betitelt „Timeout und Abbruch“Pro Request:
- Timeout standardmäßig
config.timeout ?? 30000 - Optionales
AbortSignalvom Aufrufer - Abbruch und Timeout lehnen beide das ausstehende Promise ab und bereinigen den Map-Eintrag
Der Abbruch ist nur lokal: Der Transport sendet keine Abbruch-Notification auf Protokollebene an den Server.
Behandlung fehlerhafter Payloads
Abschnitt betitelt „Behandlung fehlerhafter Payloads“In der Lese-Schleife:
- Jede geparste JSONL-Zeile wird im
try/catchan#handleMessageübergeben - Ausnahmen bei der Behandlung fehlerhafter/ungültiger Nachrichten werden verworfen (Kommentar
Skip malformed lines) - Die Schleife fährt fort, sodass eine fehlerhafte Nachricht die Verbindung nicht beendet
Wenn der zugrundeliegende Stream-Parser wirft, wird onError aufgerufen (wenn noch verbunden), dann wird die Verbindung geschlossen.
Disconnect-/Fehlerverhalten
Abschnitt betitelt „Disconnect-/Fehlerverhalten“Wenn der Prozess endet oder der Stream geschlossen wird:
- Alle laufenden Requests werden mit
Transport closedabgelehnt - Kein automatischer Neustart oder Reconnect
- Höhere Schichten müssen durch Erstellen eines neuen Transports reconnecten
Backpressure-/Streaming-Hinweise
Abschnitt betitelt „Backpressure-/Streaming-Hinweise“- Ausgehende Schreibvorgänge verwenden
stdin.write()+flush()ohne Warten auf Drain-Semantik. - Es gibt kein explizites Queue- oder High-Watermark-Management im Transport.
- Die eingehende Verarbeitung ist stream-gesteuert (
for awaitüberreadJsonl), jeweils eine geparste Nachricht.
HTTP/SSE-Transport-Interna
Abschnitt betitelt „HTTP/SSE-Transport-Interna“Lebenszyklus und Verbindungssemantik
Abschnitt betitelt „Lebenszyklus und Verbindungssemantik“Der HTTP-Transport hat einen logischen Verbindungszustand, aber der Request-Pfad ist zustandslos pro HTTP-Aufruf:
connect()setztconnected=true(kein Socket-/Session-Handshake)- Optionales Server-Session-Tracking über den
Mcp-Session-Id-Header close()sendet optionalDELETEmitMcp-Session-Id, bricht den SSE-Listener ab, löstonCloseaus
connected bedeutet also “Transport nutzbar”, nicht “persistenter Stream aufgebaut”.
Session-Header-Verhalten
Abschnitt betitelt „Session-Header-Verhalten“- Bei der POST-Response wird, wenn der
Mcp-Session-Id-Header vorhanden ist, dieser vom Transport gespeichert. - Nachfolgende Requests/Notifications enthalten
Mcp-Session-Id. close()versucht, die Server-Session mit HTTP DELETE zu beenden; Beendigungsfehler werden ignoriert.
Timeout und Abbruch
Abschnitt betitelt „Timeout und Abbruch“Für sowohl request() als auch notify():
- Timeout verwendet
AbortController(config.timeout ?? 30000) - Externes Signal wird, falls vorhanden, über
AbortSignal.any([...])zusammengeführt - AbortError-Behandlung unterscheidet zwischen Aufrufer-Abbruch und Timeout
Geworfene Fehler:
- Timeout:
Request timeout after ...ms(oderSSE response timeout ...,Notify timeout ...) - Aufrufer-Abbruch: Ursprünglicher AbortError wird erneut geworfen, wenn das externe Signal bereits abgebrochen ist
HTTP-Fehlerweitergabe
Abschnitt betitelt „HTTP-Fehlerweitergabe“Bei nicht-OK-Response:
- Response-Text wird in den geworfenen Fehler aufgenommen (
HTTP <status>: <text>) - Falls vorhanden, werden Auth-Hinweise aus
WWW-AuthenticateundMcp-Auth-Serverangehängt
Bei JSON-RPC-Fehlerobjekt:
- Wirft
MCP error <code>: <message>
Fehlerhafter JSON-Body (Fehler bei response.json()) wird als Parse-Exception weitergegeben.
SSE-Verhalten und Modi
Abschnitt betitelt „SSE-Verhalten und Modi“Es existieren zwei SSE-Pfade:
-
Per-Request-SSE-Response (
#parseSSEResponse)- Wird verwendet, wenn der POST-Response-Content-Type
text/event-streamist - Konsumiert den Stream, bis eine passende Response-ID gefunden wird
- Kann verschachtelte Notifications während desselben Streams verarbeiten
- Wird verwendet, wenn der POST-Response-Content-Type
-
Hintergrund-SSE-Listener (
startSSEListener())- Optionaler GET-Listener für server-initiierte Notifications
- Wird derzeit nicht automatisch vom MCP-Manager/Client gestartet
- Wenn GET
405zurückgibt, deaktiviert sich der Listener stillschweigend (Server unterstützt diesen Modus nicht)
Behandlung fehlerhafter Payloads und Disconnect
Abschnitt betitelt „Behandlung fehlerhafter Payloads und Disconnect“SSE-JSON-Parsing-Fehler propagieren aus readSseJson und lehnen Request/Listener ab.
- Request-SSE-Parse-Fehler lehnen den aktiven Request ab.
- Hintergrund-Listener-Fehler lösen
onErroraus (außer AbortError). - Kein Auto-Reconnect für den Hintergrund-Listener.
json-rpc.ts-Utility vs. Transport-Abstraktion
Abschnitt betitelt „json-rpc.ts-Utility vs. Transport-Abstraktion“src/mcp/json-rpc.ts stellt callMCP() und parseSSE()-Hilfsfunktionen für direkte HTTP-MCP-Aufrufe bereit (verwendet von der Exa-Integration), nicht die MCPTransport-Abstraktion, die von MCPClient/MCPManager verwendet wird.
Bemerkenswerte Unterschiede zu HttpTransport:
- Parst zuerst den gesamten Response-Text, extrahiert dann die erste
data:-Zeile (parseSSE), mit JSON-Fallback - Kein Request-Timeout-Management, keine Abort-API, kein Session-ID-Handling, kein Transport-Lebenszyklus
- Gibt das rohe JSON-RPC-Envelope-Objekt zurück
Dieser Pfad ist leichtgewichtig, aber weniger robust als die vollständige Transport-Implementierung.
Retry-/Reconnect-Verantwortlichkeiten
Abschnitt betitelt „Retry-/Reconnect-Verantwortlichkeiten“Transport-Ebene
Abschnitt betitelt „Transport-Ebene“Aktuelle Transport-Implementierungen führen nicht durch:
- Retry fehlgeschlagener Requests
- Reconnect nach stdio-Prozess-Ende
- Reconnect von SSE-Listenern
- Erneutes Senden laufender Requests nach Disconnect
Sie schlagen schnell fehl und propagieren Fehler.
Manager-/Client-Ebene
Abschnitt betitelt „Manager-/Client-Ebene“MCPManager übernimmt die Discovery-/Initial-Connection-Orchestrierung und kann nur durch erneutes Ausführen der Connect-Abläufe reconnecten (connectToServer/discoverAndConnect-Pfade). Es heilt einen bereits verbundenen Transport bei Laufzeit-Fehler-Callbacks nicht automatisch.
MCPManager verfügt über Startup-Fallback-Verhalten für langsame Server (verzögerte Tools aus dem Cache), aber das ist Tool-Verfügbarkeits-Fallback, kein Transport-Retry.
Zusammenfassung der Fehlerszenarien
Abschnitt betitelt „Zusammenfassung der Fehlerszenarien“- Fehlerhafte stdio-Nachrichtenzeile: Wird verworfen; Stream fährt fort.
- Stdio-Stream/Prozess endet: Transport schließt; ausstehende Requests werden als
Transport closedabgelehnt. - HTTP non-2xx: Request/Notify wirft HTTP-Fehler.
- Ungültige JSON-Response: Parse-Exception wird weitergegeben.
- SSE endet ohne passende ID: Request schlägt fehl mit
No response received for request ID .... - Timeout: Transport-spezifischer Timeout-Fehler.
- Aufrufer-Abbruch: AbortError/Reason wird vom Aufrufer-Signal weitergegeben.
Praktische Grenzregel
Abschnitt betitelt „Praktische Grenzregel“Wenn es um Nachrichtenstruktur, ID-Korrelation oder MCP-Methodenreihenfolge geht, gehört es zur Protokoll-/Client-Logik.
Wenn es um Framing (JSONL vs HTTP/SSE), Stream-Parsing, Fetch-/Spawn-Lebenszyklus, Timeout-Timer oder Verbindungsabbau geht, gehört es zur Transport-Implementierung.