- Home
- Documentation
- MCP
- Protocollo MCP e meccanismi interni di trasporto
Protocollo MCP e meccanismi interni di trasporto
Questo documento descrive come coding-agent implementa la messaggistica MCP JSON-RPC e come le problematiche di protocollo sono separate dalle problematiche di trasporto.
Tratta:
- Flusso di richiesta/risposta e notifiche JSON-RPC
- Correlazione delle richieste e ciclo di vita per i trasporti stdio e HTTP/SSE
- Comportamento di timeout e cancellazione
- Propagazione degli errori e gestione dei payload malformati
- Limiti di selezione del trasporto (
stdiovshttp/sse) - Quali responsabilità di riconnessione/ripetizione sono a livello di trasporto rispetto a livello di manager
Non tratta l’UX di creazione delle estensioni o l’interfaccia utente dei comandi.
File di implementazione
Sezione intitolata “File di implementazione”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
Confini dei livelli
Sezione intitolata “Confini dei livelli”Livello di protocollo (JSON-RPC + metodi MCP)
Sezione intitolata “Livello di protocollo (JSON-RPC + metodi MCP)”- Le strutture dei messaggi sono definite in
types.ts(JsonRpcRequest,JsonRpcNotification,JsonRpcResponse,JsonRpcMessage). - La logica del client MCP (
client.ts) determina l’ordine dei metodi e l’handshake di sessione:- Richiesta
initialize - Notifica
notifications/initialized - Chiamate a metodi come
tools/list,tools/call
- Richiesta
Livello di trasporto (MCPTransport)
Sezione intitolata “Livello di trasporto (MCPTransport)”MCPTransport astrae la consegna e il ciclo di vita:
request(method, params, options?) -> Promise<T>notify(method, params?) -> Promise<void>close()connected- callback opzionali:
onClose,onError,onNotification
Le implementazioni di trasporto gestiscono il framing e i dettagli di I/O:
StdioTransport: JSON delimitato da newline su stdio del sottoprocessoHttpTransport: JSON-RPC su HTTP POST, con risposte/ascolto SSE opzionali
Avvertenza importante attuale
Sezione intitolata “Avvertenza importante attuale”Le callback di trasporto (onClose, onError, onNotification) sono implementate, ma i flussi correnti di MCPClient/MCPManager non collegano la logica di riconnessione a queste callback. Le notifiche vengono consumate solo se il chiamante registra i relativi handler.
Selezione del trasporto
Sezione intitolata “Selezione del trasporto”client.ts:createTransport() sceglie il trasporto dalla configurazione:
typeomesso o"stdio"->createStdioTransport"http"o"sse"->createHttpTransport
"sse" è trattato come una variante del trasporto HTTP (stessa classe), non come un’implementazione di trasporto separata.
Flusso dei messaggi JSON-RPC e correlazione
Sezione intitolata “Flusso dei messaggi JSON-RPC e correlazione”ID di richiesta
Sezione intitolata “ID di richiesta”Ogni trasporto genera ID per richiesta (Math.random + stringa timestamp). Gli ID sono token di correlazione locali al trasporto.
Percorso di correlazione stdio
Sezione intitolata “Percorso di correlazione stdio”- La richiesta in uscita viene serializzata come un oggetto JSON +
\n. #pendingRequests: Map<id, {resolve,reject}>memorizza le richieste in corso.- Il ciclo di lettura analizza il JSONL dallo stdout e chiama
#handleMessage. - Se il messaggio in entrata ha un
idcorrispondente, la richiesta viene risolta/rifiutata. - Se il messaggio in entrata ha
methode nessunid, viene trattato come notifica e inviato aonNotification.
Gli ID sconosciuti vengono ignorati (nessun rifiuto, nessuna callback di errore).
Percorso di correlazione HTTP
Sezione intitolata “Percorso di correlazione HTTP”- La richiesta in uscita è un HTTP
POSTcon body JSON eidgenerato. - Percorso di risposta non SSE: analizza una risposta JSON-RPC e restituisce
result/lancia un’eccezione suerror. - Percorso di risposta SSE (
Content-Type: text/event-stream): trasmette eventi in streaming, restituisce il primo messaggio il cuiidcorrisponde all’ID di richiesta atteso e contieneresultoerror. - I messaggi SSE con
methode senzaidvengono trattati come notifiche.
Se lo stream SSE termina prima della risposta corrispondente, la richiesta fallisce con No response received for request ID ....
Notifiche
Sezione intitolata “Notifiche”Il client emette notifiche JSON-RPC tramite transport.notify(...).
- Stdio: scrive il frame di notifica su stdin (
jsonrpc,method,paramsopzionali) più newline. - HTTP: invia il body POST senza
id; il successo accetta2xxo202 Accepted.
Le notifiche avviate dal server vengono esposte solo attraverso onNotification del trasporto; non esiste un sottoscrittore globale predefinito nel manager/client.
Meccanismi interni del trasporto stdio
Sezione intitolata “Meccanismi interni del trasporto stdio”Ciclo di vita e transizioni di stato
Sezione intitolata “Ciclo di vita e transizioni di stato”- Iniziale:
connected=false,process=null, mappa pending vuota connect():- avvia il sottoprocesso con comando/args/env/cwd configurati
- contrassegna come connesso
- avvia il ciclo di lettura stdout (
readJsonl) - avvia il ciclo stderr (legge/scarta; attualmente silenzioso)
close():- contrassegna come disconnesso
- rifiuta tutte le richieste pending (
Transport closed) - termina il sottoprocesso
- attende la chiusura del ciclo di lettura
- emette
onClose
Se il ciclo di lettura termina inaspettatamente, finally attiva #handleClose() che esegue lo stesso rifiuto delle richieste pending e la callback di chiusura.
Timeout e cancellazione
Sezione intitolata “Timeout e cancellazione”Per ogni richiesta:
- il timeout predefinito è
config.timeout ?? 30000 AbortSignalopzionale dal chiamante- abort e timeout rifiutano entrambi la promise pending e puliscono la voce nella mappa
La cancellazione è solo locale: il trasporto non invia notifiche di cancellazione a livello di protocollo al server.
Gestione dei payload malformati
Sezione intitolata “Gestione dei payload malformati”Nel ciclo di lettura:
- ogni riga JSONL analizzata viene passata a
#handleMessagein un bloccotry/catch - le eccezioni nella gestione dei messaggi malformati/non validi vengono scartate (commento
Skip malformed lines) - il ciclo continua, quindi un messaggio errato non interrompe la connessione
Se il parser dello stream sottostante lancia un’eccezione, viene invocato onError (quando ancora connesso), poi la connessione si chiude.
Comportamento in caso di disconnessione/errore
Sezione intitolata “Comportamento in caso di disconnessione/errore”Quando il processo termina o lo stream si chiude:
- tutte le richieste in corso vengono rifiutate con
Transport closed - nessun riavvio o riconnessione automatica
- i livelli superiori devono riconnettersi creando un nuovo trasporto
Note su backpressure/streaming
Sezione intitolata “Note su backpressure/streaming”- Le scritture in uscita utilizzano
stdin.write()+flush()senza attendere la semantica di drain. - Non esiste una gestione esplicita della coda o del high-watermark nel trasporto.
- L’elaborazione in entrata è guidata dallo stream (
for awaitsureadJsonl), un messaggio analizzato alla volta.
Meccanismi interni del trasporto HTTP/SSE
Sezione intitolata “Meccanismi interni del trasporto HTTP/SSE”Ciclo di vita e semantica della connessione
Sezione intitolata “Ciclo di vita e semantica della connessione”Il trasporto HTTP ha uno stato di connessione logico, ma il percorso delle richieste è stateless per ogni chiamata HTTP:
connect()impostaconnected=true(nessun handshake socket/sessione)- tracciamento opzionale della sessione server tramite header
Mcp-Session-Id close()invia opzionalmenteDELETEconMcp-Session-Id, interrompe il listener SSE, emetteonClose
Quindi connected significa “trasporto utilizzabile”, non “stream persistente stabilito”.
Comportamento dell’header di sessione
Sezione intitolata “Comportamento dell’header di sessione”- Alla risposta POST, se l’header
Mcp-Session-Idè presente, il trasporto lo memorizza. - Le richieste/notifiche successive includono
Mcp-Session-Id. close()tenta di terminare la sessione server con HTTP DELETE; i fallimenti di terminazione vengono ignorati.
Timeout e cancellazione
Sezione intitolata “Timeout e cancellazione”Per request() e notify():
- il timeout utilizza
AbortController(config.timeout ?? 30000) - il segnale esterno, se fornito, viene unito tramite
AbortSignal.any([...]) - la gestione di AbortError distingue l’abort del chiamante dal timeout
Errori generati:
- timeout:
Request timeout after ...ms(oSSE response timeout ...,Notify timeout ...) - abort del chiamante: l’AbortError originale viene rilancato quando il segnale esterno è già abortito
Propagazione degli errori HTTP
Sezione intitolata “Propagazione degli errori HTTP”Su risposta non OK:
- il testo della risposta è incluso nell’errore generato (
HTTP <status>: <text>) - se presenti, gli hint di autenticazione da
WWW-AuthenticateeMcp-Auth-Servervengono aggiunti
Su oggetto di errore JSON-RPC:
- lancia
MCP error <code>: <message>
Il fallimento del parsing del body JSON (response.json()) si propaga come eccezione di analisi.
Comportamento SSE e modalità
Sezione intitolata “Comportamento SSE e modalità”Esistono due percorsi SSE:
-
Risposta SSE per richiesta (
#parseSSEResponse)- utilizzato quando il content type della risposta POST è
text/event-stream - consuma lo stream fino a trovare l’id di risposta corrispondente
- può elaborare notifiche interleaved durante lo stesso stream
- utilizzato quando il content type della risposta POST è
-
Listener SSE in background (
startSSEListener())- listener GET opzionale per le notifiche avviate dal server
- attualmente non avviato automaticamente da MCP manager/client
- se GET restituisce
405, il listener si disabilita silenziosamente (il server non supporta questa modalità)
Gestione di payload malformati e disconnessione
Sezione intitolata “Gestione di payload malformati e disconnessione”Gli errori di parsing JSON SSE emergono da readSseJson e rifiutano la richiesta/il listener.
- Gli errori di parsing SSE delle richieste rifiutano la richiesta attiva.
- Gli errori del listener in background attivano
onError(eccetto AbortError). - Nessuna riconnessione automatica per il listener in background.
Utilità json-rpc.ts vs astrazione di trasporto
Sezione intitolata “Utilità json-rpc.ts vs astrazione di trasporto”src/mcp/json-rpc.ts fornisce gli helper callMCP() e parseSSE() per chiamate HTTP MCP dirette (utilizzate dall’integrazione Exa), non l’astrazione MCPTransport usata da MCPClient/MCPManager.
Differenze rilevanti rispetto a HttpTransport:
- analizza prima l’intero testo della risposta, poi estrae la prima riga
data:(parseSSE), con fallback JSON - nessuna gestione del timeout di richiesta, nessuna API di abort, nessuna gestione di session-id, nessun ciclo di vita del trasporto
- restituisce l’oggetto envelope JSON-RPC grezzo
Questo percorso è leggero ma meno robusto rispetto all’implementazione completa del trasporto.
Responsabilità di ripetizione/riconnessione
Sezione intitolata “Responsabilità di ripetizione/riconnessione”A livello di trasporto
Sezione intitolata “A livello di trasporto”Le implementazioni di trasporto attuali non:
- ripetono le richieste fallite
- si riconnettono dopo la chiusura del processo stdio
- riconnettono i listener SSE
- reinviano le richieste in corso dopo la disconnessione
Falliscono rapidamente e propagano gli errori.
A livello di manager/client
Sezione intitolata “A livello di manager/client”MCPManager gestisce l’orchestrazione di discovery/connessione iniziale e può riconnettersi solo eseguendo nuovamente i flussi di connessione (connectToServer/percorsi discoverAndConnect). Non ripristina automaticamente un trasporto già connesso in caso di errori a runtime tramite le callback.
MCPManager ha un comportamento di fallback all’avvio per i server lenti (strumenti differiti dalla cache), ma si tratta di un fallback sulla disponibilità degli strumenti, non di un ripetizione del trasporto.
Riepilogo degli scenari di errore
Sezione intitolata “Riepilogo degli scenari di errore”- Riga del messaggio stdio malformata: scartata; lo stream continua.
- Stream/processo stdio termina: il trasporto si chiude; le richieste pending vengono rifiutate con
Transport closed. - HTTP non-2xx: la richiesta/notifica genera un errore HTTP.
- Risposta JSON non valida: l’eccezione di parsing viene propagata.
- SSE termina senza id corrispondente: la richiesta fallisce con
No response received for request ID .... - Timeout: errore di timeout specifico del trasporto.
- Abort del chiamante: AbortError/motivo propagato dal segnale del chiamante.
Regola pratica sui confini
Sezione intitolata “Regola pratica sui confini”Se la problematica riguarda la struttura dei messaggi, la correlazione degli id o l’ordine dei metodi MCP, appartiene alla logica di protocollo/client.
Se la problematica riguarda il framing (JSONL vs HTTP/SSE), il parsing dello stream, il ciclo di vita di fetch/spawn, i clock di timeout o la chiusura della connessione, appartiene all’implementazione del trasporto.