Transporte padrão do Model Context Protocol (MCP) para conexões remotas, substituindo o antigo HTTP+SSE. Introduzido na spec 2025-03-26, evoluiu em 2025-06-18 e 2025-11-25.

Importante: “Streamable HTTP” não é um protocolo independente — é uma combinação específica de HTTP (POST/GET/DELETE) + Server Sent Events (SSE) definida pela spec do MCP, com regras próprias de sessão e resumability. Não existe um RFC/padrão web com esse nome fora do contexto MCP.

Como funciona

  • Endpoint único (ex: /mcp) para tudo, em vez de endpoints separados para request e streaming
  • POST: cliente envia mensagens JSON-RPC
  • Servidor responde com JSON direto (resposta simples) ou abre um stream SSE (text/event-stream) quando precisa mandar múltiplas mensagens/notificações
  • GET: abre canal SSE para o servidor mandar mensagens assíncronas
  • DELETE: encerra a sessão explicitamente

Sessões e resiliência

  • Servidor pode atribuir Mcp-Session-Id no handshake inicial, mantendo estado entre chamadas
  • Resumability: se a conexão cair, cliente reconecta com header Last-Event-ID e o servidor reenvia o que faltou (mesmo mecanismo do SSE puro)
  • Servidores também podem ser stateless (sem sessão), request-response puro

Por que substituiu o SSE+HTTP antigo

O modelo anterior exigia dois canais (um pra eventos, outro pra mensagens), complicando escalonamento, load balancers e firewalls. Streamable HTTP consolida tudo em HTTP padrão, facilitando rodar atrás de proxies/gateways comuns (ex: gateway MCP do LiteLLM, servidor Obsidian MCP exposto via OAuth).

Server-Sent Events (SSE) — o protocolo por trás

Server Sent Events (SSE) é um padrão do WHATWG (parte da spec HTML), unidirecional (server→client), sobre HTTP de longa duração. É a peça real de padrão web que o Streamable HTTP usa por baixo.

Formato de wire: Content-Type: text/event-stream, corpo texto plano, eventos separados por linha em branco:

data: primeira parte da resposta

data: segunda parte

event: done
data: {"tokens": 42}

Campos: data: (payload, pode ter múltiplas linhas concatenadas), event: (tipo, default message), id: (id do evento, usado pra resumability), retry: (ms antes de reconectar).

Reconexão automática: diferencial do SSE nativo (EventSource) — se a conexão cai, o browser reconecta sozinho e reenvia Last-Event-ID; servidor decide se faz replay ou continua do zero.

Limitações:

  • Unidirecional — cliente não manda nada pela mesma conexão
  • HTTP/1.1 limita ~6 conexões simultâneas por origin (some com HTTP/2, que multiplexa)
  • Só texto (binário precisa base64)
  • EventSource nativo não suporta headers customizados nem POST (só GET)

SSE para chatbot estilo ChatGPT (streaming de resposta)

Recomendado: SSE, não WebSocket. É o que OpenAI/Anthropic usam.

Motivos:

  • Unidirecional server→client é exatamente o caso de uso (prompt vai, resposta stream volta)
  • Roda sobre HTTP puro — passa por load balancers/CDN/proxy corporativo sem drama (diferente de WebSocket, que exige upgrade de conexão)
  • Modelo mental bate 1:1 com geração token-by-token — cada data: é um chunk/delta, evento final (event: done ou data: [DONE]) sinaliza fim
  • Já é o formato que sai do LiteLLM/OpenAI-compatible — backend só repassa o stream

WebSocket só compensa se precisar de: interromper geração + mandar novo input simultâneo com baixa latência bidirecional, voice/real-time multimodal, ou múltiplos participantes vendo o mesmo stream (colaborativo). Pra chat de texto simples, cancelamento dá pra fazer com AbortController + endpoint de cancel separado, sem full duplex.

Problema do EventSource nativo com POST

EventSource só faz GET, não aceita headers customizados nem body — inviável pra mandar prompt + histórico. Duas opções:

A) fetch + ReadableStream manual — parsing próprio do formato data: ...\n\n, controle de buffer, AbortController pra cancelamento. Baixo nível, mais código pra manter.

B) Lib fetch-event-source (@microsoft/fetch-event-source) — mesma API/comportamento do EventSource (parsing, reconexão com backoff, Last-Event-ID), mas usa fetch por baixo, então aceita POST/headers/body. Recomendado — parsing manual de SSE tem várias arestas já resolvidas pela lib (buffer truncado, encoding, reconexão).

import { fetchEventSource } from '@microsoft/fetch-event-source';
 
await fetchEventSource('/chat', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ prompt, history }),
  onmessage(ev) {
    if (ev.data === '[DONE]') return;
    const chunk = JSON.parse(ev.data);
  },
  onerror(err) { throw err; },
  signal: abortController.signal
});

Ver também