- Início
- Documentation
- MCP
- Protocolo MCP e Internos de Transporte
Protocolo MCP e Internos de Transporte
Este documento descreve como o coding-agent implementa a mensageria JSON-RPC do MCP e como as responsabilidades de protocolo são separadas das responsabilidades de transporte.
Abrange:
- Fluxo de requisição/resposta e notificação JSON-RPC
- Correlação de requisições e ciclo de vida para transportes stdio e HTTP/SSE
- Comportamento de timeout e cancelamento
- Propagação de erros e tratamento de payloads malformados
- Limites de seleção de transporte (
stdiovshttp/sse) - Quais responsabilidades de reconexão/retry são do nível de transporte vs nível de gerenciador
Não abrange UX de criação de extensões ou UI de comandos.
Arquivos de implementação
Seção intitulada “Arquivos de implementação”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
Limites entre camadas
Seção intitulada “Limites entre camadas”Camada de protocolo (JSON-RPC + métodos MCP)
Seção intitulada “Camada de protocolo (JSON-RPC + métodos MCP)”- Os formatos de mensagem são definidos em
types.ts(JsonRpcRequest,JsonRpcNotification,JsonRpcResponse,JsonRpcMessage). - A lógica do cliente MCP (
client.ts) decide a ordem dos métodos e o handshake de sessão:- Requisição
initialize - Notificação
notifications/initialized - Chamadas de métodos como
tools/list,tools/call
- Requisição
Camada de transporte (MCPTransport)
Seção intitulada “Camada de transporte (MCPTransport)”MCPTransport abstrai a entrega e o ciclo de vida:
request(method, params, options?) -> Promise<T>notify(method, params?) -> Promise<void>close()connected- callbacks opcionais:
onClose,onError,onNotification
As implementações de transporte são responsáveis pelo enquadramento e detalhes de I/O:
StdioTransport: JSON delimitado por nova linha sobre stdio de subprocessoHttpTransport: JSON-RPC sobre HTTP POST, com respostas/escuta SSE opcionais
Ressalva importante atual
Seção intitulada “Ressalva importante atual”Os callbacks de transporte (onClose, onError, onNotification) estão implementados, mas os fluxos atuais de MCPClient/MCPManager não conectam a lógica de reconexão a esses callbacks. Notificações só são consumidas se o chamador registrar handlers.
Seleção de transporte
Seção intitulada “Seleção de transporte”client.ts:createTransport() escolhe o transporte a partir da configuração:
typeomitido ou"stdio"->createStdioTransport"http"ou"sse"->createHttpTransport
"sse" é tratado como uma variante de transporte HTTP (mesma classe), não como uma implementação de transporte separada.
Fluxo de mensagens JSON-RPC e correlação
Seção intitulada “Fluxo de mensagens JSON-RPC e correlação”IDs de requisição
Seção intitulada “IDs de requisição”Cada transporte gera IDs por requisição (string de Math.random + timestamp). Os IDs são tokens de correlação locais ao transporte.
Caminho de correlação stdio
Seção intitulada “Caminho de correlação stdio”- A requisição de saída é serializada como um objeto JSON +
\n. #pendingRequests: Map<id, {resolve,reject}>armazena as requisições em andamento.- O loop de leitura analisa JSONL do stdout e chama
#handleMessage. - Se a mensagem de entrada possui
idcorrespondente, a requisição resolve/rejeita. - Se a mensagem de entrada possui
methode não possuiid, é tratada como notificação e enviada paraonNotification.
IDs desconhecidos são ignorados (sem rejeição, sem callback de erro).
Caminho de correlação HTTP
Seção intitulada “Caminho de correlação HTTP”- A requisição de saída é um HTTP
POSTcom corpo JSON eidgerado. - Caminho de resposta não-SSE: analisa uma resposta JSON-RPC e retorna
result/lança exceção emerror. - Caminho de resposta SSE (
Content-Type: text/event-stream): transmite eventos, retorna a primeira mensagem cujoidcorresponde ao ID esperado da requisição e possuiresultouerror. - Mensagens SSE com
methode semidsão tratadas como notificações.
Se o stream SSE encerrar antes da resposta correspondente, a requisição falha com No response received for request ID ....
Notificações
Seção intitulada “Notificações”O cliente emite notificações JSON-RPC via transport.notify(...).
- Stdio: escreve o frame de notificação no stdin (
jsonrpc,method,paramsopcional) mais nova linha. - HTTP: envia corpo POST sem
id; sucesso aceita2xxou202 Accepted.
Notificações iniciadas pelo servidor são expostas apenas através do onNotification do transporte; não há assinante global padrão no gerenciador/cliente.
Internos do transporte stdio
Seção intitulada “Internos do transporte stdio”Ciclo de vida e transições de estado
Seção intitulada “Ciclo de vida e transições de estado”- Inicial:
connected=false,process=null, mapa de pendentes vazio connect():- cria subprocesso com comando/args/env/cwd configurados
- marca como conectado
- inicia loop de leitura do stdout (
readJsonl) - inicia loop do stderr (leitura/descarte; atualmente silencioso)
close():- marca como desconectado
- rejeita todas as requisições pendentes (
Transport closed) - encerra o subprocesso
- aguarda encerramento do loop de leitura
- emite
onClose
Se o loop de leitura encerrar inesperadamente, o finally aciona #handleClose() que realiza a mesma rejeição de requisições pendentes e callback de fechamento.
Timeout e cancelamento
Seção intitulada “Timeout e cancelamento”Por requisição:
- timeout padrão de
config.timeout ?? 30000 AbortSignalopcional do chamador- tanto abort quanto timeout rejeitam a promise pendente e limpam a entrada no mapa
O cancelamento é apenas local: o transporte não envia notificação de cancelamento em nível de protocolo para o servidor.
Tratamento de payload malformado
Seção intitulada “Tratamento de payload malformado”No loop de leitura:
- cada linha JSONL analisada é passada para
#handleMessageemtry/catch - exceções de tratamento de mensagens malformadas/inválidas são descartadas (comentário
Skip malformed lines) - o loop continua, então uma mensagem ruim não encerra a conexão
Se o parser de stream subjacente lançar exceção, onError é invocado (quando ainda conectado), então a conexão é fechada.
Comportamento de desconexão/falha
Seção intitulada “Comportamento de desconexão/falha”Quando o processo encerra ou o stream fecha:
- todas as requisições em andamento são rejeitadas com
Transport closed - sem reinício ou reconexão automática
- camadas superiores devem reconectar criando um novo transporte
Notas sobre backpressure/streaming
Seção intitulada “Notas sobre backpressure/streaming”- Escritas de saída usam
stdin.write()+flush()sem aguardar semântica de drain. - Não há fila explícita ou gerenciamento de high-watermark no transporte.
- O processamento de entrada é orientado por stream (
for awaitsobrereadJsonl), uma mensagem analisada por vez.
Internos do transporte HTTP/SSE
Seção intitulada “Internos do transporte HTTP/SSE”Ciclo de vida e semântica de conexão
Seção intitulada “Ciclo de vida e semântica de conexão”O transporte HTTP possui estado de conexão lógico, mas o caminho de requisição é stateless por chamada HTTP:
connect()defineconnected=true(sem handshake de socket/sessão)- rastreamento opcional de sessão do servidor via header
Mcp-Session-Id close()opcionalmente enviaDELETEcomMcp-Session-Id, aborta o listener SSE, emiteonClose
Portanto, connected significa “transporte utilizável”, não “stream persistente estabelecido”.
Comportamento do header de sessão
Seção intitulada “Comportamento do header de sessão”- Na resposta do POST, se o header
Mcp-Session-Idestiver presente, o transporte o armazena. - Requisições/notificações subsequentes incluem
Mcp-Session-Id. close()tenta encerrar a sessão do servidor com HTTP DELETE; falhas de encerramento são ignoradas.
Timeout e cancelamento
Seção intitulada “Timeout e cancelamento”Para ambos request() e notify():
- timeout usa
AbortController(config.timeout ?? 30000) - sinal externo, se fornecido, é mesclado via
AbortSignal.any([...]) - tratamento de AbortError distingue abort do chamador vs timeout
Erros lançados:
- timeout:
Request timeout after ...ms(ouSSE response timeout ...,Notify timeout ...) - abort do chamador: AbortError original é relançado quando o sinal externo já está abortado
Propagação de erros HTTP
Seção intitulada “Propagação de erros HTTP”Em resposta não-OK:
- texto da resposta é incluído no erro lançado (
HTTP <status>: <text>) - se presente, dicas de autenticação de
WWW-AuthenticateeMcp-Auth-Serversão adicionadas
Em objeto de erro JSON-RPC:
- lança
MCP error <code>: <message>
Corpo JSON malformado (falha em response.json()) propaga como exceção de parse.
Comportamento e modos SSE
Seção intitulada “Comportamento e modos SSE”Existem dois caminhos SSE:
-
Resposta SSE por requisição (
#parseSSEResponse)- usado quando o content type da resposta POST é
text/event-stream - consome o stream até encontrar o ID de resposta correspondente
- pode processar notificações intercaladas durante o mesmo stream
- usado quando o content type da resposta POST é
-
Listener SSE em background (
startSSEListener())- listener GET opcional para notificações iniciadas pelo servidor
- atualmente não é iniciado automaticamente pelo gerenciador/cliente MCP
- se GET retorna
405, o listener se desabilita silenciosamente (servidor não suporta este modo)
Tratamento de payload malformado e desconexão
Seção intitulada “Tratamento de payload malformado e desconexão”Erros de parse JSON no SSE propagam de readSseJson e rejeitam requisição/listener.
- Erros de parse SSE de requisição rejeitam a requisição ativa.
- Erros do listener em background acionam
onError(exceto AbortError). - Sem reconexão automática para o listener em background.
Utilitário json-rpc.ts vs abstração de transporte
Seção intitulada “Utilitário json-rpc.ts vs abstração de transporte”src/mcp/json-rpc.ts fornece os helpers callMCP() e parseSSE() para chamadas HTTP MCP diretas (usado pela integração Exa), não a abstração MCPTransport usada por MCPClient/MCPManager.
Diferenças notáveis do HttpTransport:
- analisa o texto completo da resposta primeiro, depois extrai a primeira linha
data:(parseSSE), com fallback para JSON - sem gerenciamento de timeout de requisição, sem API de abort, sem tratamento de session-id, sem ciclo de vida de transporte
- retorna o envelope JSON-RPC bruto
Este caminho é leve, mas menos robusto que a implementação completa de transporte.
Responsabilidades de retry/reconexão
Seção intitulada “Responsabilidades de retry/reconexão”Nível de transporte
Seção intitulada “Nível de transporte”As implementações atuais de transporte não:
- tentam novamente requisições com falha
- reconectam após saída do processo stdio
- reconectam listeners SSE
- reenviam requisições em andamento após desconexão
Elas falham rapidamente e propagam erros.
Nível de gerenciador/cliente
Seção intitulada “Nível de gerenciador/cliente”MCPManager lida com a orquestração de descoberta/conexão inicial e pode reconectar apenas executando os fluxos de conexão novamente (caminhos connectToServer/discoverAndConnect). Ele não repara automaticamente um transporte já conectado em callbacks de falha em tempo de execução.
MCPManager possui comportamento de fallback na inicialização para servidores lentos (ferramentas adiadas do cache), mas isso é fallback de disponibilidade de ferramentas, não retry de transporte.
Resumo de cenários de falha
Seção intitulada “Resumo de cenários de falha”- Linha de mensagem stdio malformada: descartada; stream continua.
- Stream/processo stdio encerra: transporte fecha; requisições pendentes rejeitadas como
Transport closed. - HTTP não-2xx: requisição/notificação lança erro HTTP.
- Resposta JSON inválida: exceção de parse propagada.
- SSE encerra sem ID correspondente: requisição falha com
No response received for request ID .... - Timeout: erro de timeout específico do transporte.
- Abort do chamador: AbortError/razão propagado do sinal do chamador.
Regra prática de limite
Seção intitulada “Regra prática de limite”Se a preocupação é formato de mensagem, correlação de ID ou ordenação de métodos MCP, pertence à lógica de protocolo/cliente.
Se a preocupação é enquadramento (JSONL vs HTTP/SSE), parse de stream, ciclo de vida de fetch/spawn, relógios de timeout ou encerramento de conexão, pertence à implementação de transporte.