SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-28

Servidores MCP stateless: o que mudou na prática

A revisão MCP 2026-07-28 removeu sessões e o handshake initialize. Veja os impactos no proxy reverso, health checks, timeouts e autenticação.

O que é um servidor MCP stateless

Um servidor MCP stateless não mantém estado por cliente entre pedidos. Cada pedido inclui a versão do protocolo, as capacidades do cliente e as credenciais de que o servidor precisa para responder. Assim, qualquer processo em qualquer máquina pode responder a qualquer pedido. O MCP (Model Context Protocol, o formato de comunicação que os agentes usam para aceder a ferramentas) tornou isto uma regra na revisão 2026-07-28. Essa revisão removeu o handshake initialize e a sessão HTTP subjacente. Tudo aqui diz respeito ao lado do servidor dessa comunicação. Se o lado do agente ainda for novo para si, um percurso faseado para aprender agentes de IA explica o ciclo que decide chamar uma ferramenta antes de ser necessário compreender estes detalhes HTTP.

Esse é o objetivo operacional. Um servidor que não mantém dados por cliente pode ficar atrás de um balanceador de carga comum, sem afinidade de sessão. Também pode ser reiniciado durante uma implementação sem interromper os clientes. Além disso, pode ser executado como quatro processos idênticos em vez de um. Um servidor orientado a sessões não oferece nenhuma destas características sem componentes adicionais.

O Model Context Protocol é um protocolo stateless: todas as informações necessárias para processar um pedido estão contidas no próprio pedido. O servidor processa cada pedido de forma independente. Não deve inferir estado a partir de pedidos anteriores, mesmo que tenham sido feitos na mesma ligação ou stream.

Stateless não significa que o servidor não armazena nada. A sua base de dados, a sua fila e a sua cache continuam a existir. Significa que o protocolo não mantém estado na ligação. Por isso, o servidor não deve tratar uma ligação, um processo ou um socket aberto como substituto de «este cliente a meio de uma conversa». A separação é mais fácil de ver numa aplicação que já é proprietária dos seus dados: servidor MCP somente de leitura do openGym responde a perguntas sobre o histórico de treinos armazenado na própria base de dados da aplicação, e nada desse armazenamento depende da ligação através da qual um determinado pedido chegou.

O que foi removido na revisão de 2026-07-28

2026-07-28 é a revisão atual da especificação em agosto de 2026. Em comparação com 2025-11-25, remove cinco elementos que existiam para dar suporte a sessões.

  • O pedido initialize e a notificação notifications/initialized. Não existe qualquer handshake (SEP-2575).
  • O cabeçalho Mcp-Session-Id e o encerramento da sessão com HTTP DELETE (SEP-2567).
  • O stream HTTP GET autónomo, no qual os servidores enviavam notificações. Foi substituído por subscriptions/listen, um POST normal cuja resposta é um stream de longa duração.
  • A possibilidade de retomar streams SSE (server-sent events). O cabeçalho Last-Event-ID e os IDs por evento foram removidos. Assim, quando um stream é interrompido, o pedido em curso é perdido, e o cliente tem de o reenviar como um novo pedido com um novo ID de pedido.
  • ping, logging/setLevel e notifications/roots/list_changed. O nível de log é agora um campo por pedido, io.modelcontextprotocol/logLevel em _meta.

Foi adicionado um método, que todos os servidores têm de implementar. server/discover devolve, numa única chamada, as versões de protocolo suportadas pelo servidor, as capacidades e a identidade. É o elemento mais próximo de um handshake que resta, e a sua chamada é opcional para os clientes.

Por que o transporte baseado em sessão era difícil de executar em produção

Em 2025-11-25 e versões anteriores, um servidor podia gerar um ID de sessão durante a inicialização e devolvê-lo no cabeçalho Mcp-Session-Id na InitializeResult. Depois, o cliente tinha de enviar esse cabeçalho em todos os pedidos seguintes. A versão de protocolo negociada e as capacidades do cliente ficavam na memória do servidor, associadas a esse ID. Cada uma dessas decisões tinha um custo operacional.

  • Um reinício eliminava a tabela de sessões. A especificação exigia que o servidor respondesse com 404 Not Found a qualquer pedido que transportasse um ID de sessão expirado. Também exigia que o cliente recomeçasse com um novo InitializeRequest. Cada implementação passava a ser um evento de reconexão para todos os clientes ligados.
  • Uma segunda réplica não conhecia as sessões da primeira. Escalar horizontalmente exigia encaminhamento persistente no balanceador de carga ou um armazenamento de sessões partilhado, que todas as réplicas consultariam em cada pedido.
  • A tabela de sessões ocupava memória e crescia com os clientes inativos. DELETE era opcional, e os clientes que fechavam a ligação sem o enviar deixavam entradas para trás.
  • Os resultados das listas podiam variar por ligação, pelo que era inseguro colocar uma cache à frente do servidor.

Remover as sessões elimina os quatro problemas de uma só vez. Esta é a alteração que deve compreender antes de alterar qualquer configuração.

O que cada pedido transporta agora

Cada pedido POST para o endpoint MCP é independente. A versão do protocolo e as capacidades do cliente são enviadas no corpo do pedido, em _meta, e alguns campos são replicados nos cabeçalhos HTTP para que um intermediário possa encaminhar o tráfego sem analisar JSON.

POST /mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather
Authorization: Bearer <access token>

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {"location": "Seattle, WA"},
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": {"name": "ExampleClient", "version": "1.0.0"},
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}

io.modelcontextprotocol/protocolVersion e io.modelcontextprotocol/clientCapabilities são obrigatórios em todos os pedidos. clientInfo não é obrigatório, embora os clientes devam enviá-lo. Um pedido sem um campo obrigatório está malformado. O servidor deve rejeitá-lo com o erro JSON-RPC -32602 e HTTP 400 Bad Request.

O cabeçalho Mcp-Method é obrigatório em todos os pedidos. Mcp-Name é obrigatório em tools/call, resources/read e prompts/get. O valor do cabeçalho tem de corresponder ao corpo. Um servidor que processe o corpo deve rejeitar uma divergência com 400 Bad Request e os códigos de erro -32020, HeaderMismatch. Esta regra existe porque um balanceador de carga que encaminha com base no cabeçalho e um servidor que executa com base no corpo estão a usar duas fontes de verdade diferentes. Se encaminhar tráfego ou aplicar limites de pedidos com base nestes cabeçalhos, verifique primeiro MCP-Protocol-Version: as revisões anteriores nunca validavam o cabeçalho contra o corpo. Nessas versões, o valor do cabeçalho não é fiável.

Uma divergência de versões é agora um erro normal por pedido, e não uma falha no handshake. Um servidor que não implemente a versão solicitada responde com 400 Bad Request e os erros -32022, UnsupportedProtocolVersion, além de indicar as versões suportadas em data.supported. O cliente escolhe uma versão dessa lista e tenta novamente.

Onde ficou o estado: tokens, cursores, subscrições

O estado não desapareceu. Passou para locais que pode consultar e registar.

As credenciais passam a estar em cada pedido. Não existe uma sessão à qual associar uma identidade, por isso o token de acesso acompanha cada chamada HTTP e é validado em todas elas. Os detalhes estão na secção de autenticação abaixo.

Os cursores têm de transportar a sua própria posição. A paginação em tools/list, resources/list, prompts/list e resources/templates/list usa uma cadeia de cursor opaca, e os clientes não a devem analisar nem modificar. Num servidor com um único processo, era comum manter o offset em memória, associado à sessão. Sem sessão, o cursor tem de ser suficiente para qualquer réplica retomar a listagem. Para isso, codifique a posição no cursor e assine-o, ou mantenha-a num armazenamento partilhado por todas as réplicas. Um cursor inválido deve devolver -32602. Assine-o porque um cursor opaco continua a ser uma entrada fornecida pelo cliente que o seu código descodifica e em que confia.

As subscrições pertencem a um pedido, não a uma ligação. Um cliente que pretenda receber notificações de alterações envia subscriptions/listen com um filtro que identifica os tipos pretendidos: toolsListChanged, promptsListChanged, resourcesListChanged e resourceSubscriptions. O servidor responde com notifications/subscriptions/acknowledged e mantém esse fluxo de resposta aberto. Se o fluxo cair, o servidor não guarda nada, e o cliente envia novamente subscriptions/listen para o restabelecer.

O estado da aplicação entre chamadas passa a ser um identificador explícito. Quando um servidor tem efetivamente de guardar algo entre chamadas, a resposta da especificação é um identificador criado pelo servidor e devolvido como um argumento normal da ferramenta. O identificador aparece no esquema da ferramenta, pode ser registado e nunca fica implícito na ligação. Um servidor com dados reais por utilizador, como um servidor MCP de email alojado localmente, usa este padrão em vez de uma sessão: o identificador da caixa de correio ou do rascunho é um argumento da ferramenta, por isso qualquer réplica pode tratar a chamada seguinte. Muitas ferramentas não precisam de qualquer identificador: uma ferramenta de pesquisa suportada pela sua própria instância SearXNG recebe uma consulta e devolve os resultados, sem nada que permita retomar a chamada seguinte e sem motivo para saber qual foi a réplica que respondeu.

Implementação: reverse proxy, timeouts e verificações de integridade

O endpoint MCP é um caminho que aceita POST. A maior parte do tráfego é composta por um pedido curto e uma resposta JSON, que qualquer proxy consegue processar. A exceção é a resposta em streaming, em que os valores predefinidos do proxy podem causar problemas. É esta a parte que muda quando passa de uma demonstração num portátil para um servidor MCP executado num VPS.

location /mcp {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_buffering off;
    proxy_read_timeout 1h;
    proxy_send_timeout 1h;
}

proxy_buffering off é importante porque o nginx armazena em buffer as respostas encaminhadas por predefinição. Isto retém os eventos SSE até o buffer ficar cheio ou a resposta terminar. A especificação também pede que os servidores enviem X-Accel-Buffering: no nas respostas SSE, e o nginx respeita esse cabeçalho. Assim, um servidor correto informa o proxy da configuração adequada. Defina também a diretiva, porque essa é a parte que controla.

proxy_read_timeout tem o valor predefinido de 60 segundos. Um stream subscriptions/listen que permaneça inativo durante mais tempo é fechado pelo nginx, não pelo seu servidor. Por isso, os logs mostram um processo saudável, mas o cliente mostra um stream interrompido. Aumente o valor apenas na localização MCP, não em todo o servidor. Também é recomendado que os servidores enviem uma linha de comentário SSE (uma linha iniciada por dois-pontos) como keep-alive durante períodos de inatividade. Isto impede que os intermediários terminem o stream por timeout.

O Caddy exige menos configuração. Por predefinição, armazena parcialmente as respostas em buffer para melhorar a eficiência da transmissão e faz flush imediato quando a resposta contém Content-Type: text/event-stream. Por isso, o streaming funciona sem diretivas adicionais.

mcp.example.com {
	reverse_proxy 127.0.0.1:8080 {
		health_uri /healthz
		health_interval 10s
	}
}

Observe o destino dessa verificação de integridade. Não aponte uma verificação ativa para o endpoint MCP com GET, porque um servidor que implemente apenas esta revisão responde 405 Method Not Allowed a GET e DELETE, e o método de verificação predefinido do Caddy é GET. O proxy marcaria então um backend perfeitamente saudável como indisponível. Disponibilize um caminho simples, como /healthz, para o proxy e verifique o protocolo separadamente com um POST.

curl -sS https://mcp.example.com/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2026-07-28' \
  -H 'Mcp-Method: server/discover' \
  -d '{"jsonrpc":"2.0","id":"health-1","method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'

Um 200 que contenha uma lista supportedVersions significa que o processo está ativo e a comunicar através do protocolo. Um 404 com o erro JSON-RPC -32601 significa que o processo está ativo, mas não disponibiliza server/discover, que todos os servidores 2026-07-28 têm de implementar. Um 400 com -32022 significa que o verificador pediu uma versão que esta compilação não suporta. É exatamente isto que pretende detetar depois de uma atualização de dependência. O nginx de código aberto não tem verificações de integridade ativas. Use, por isso, max_fails e fail_timeout passivos no upstream e execute a verificação do protocolo a partir do seu sistema de monitorização.

Um rolling restart passa a interromper apenas os pedidos em curso. Faça o drain, aguarde que os POST abertos terminem, inicie o novo processo e permita que os clientes repitam os pedidos que falharam. A única ligação que continua a ser perdida é qualquer stream subscriptions/listen aberto, porque esse stream é uma ligação ativa a um processo específico. A ausência de estado removeu a afinidade de sessão. Não removeu a afinidade de ligação de um stream que esteja aberto naquele momento, e nenhuma regra de encaminhamento resolve isso. O cliente consegue distinguir os casos: um stream que termina com o resultado subscriptions/listen vazio foi fechado corretamente; um stream que termina sem esse resultado foi interrompido, e o cliente pode interpretar isso como motivo para voltar a ligar-se.

A colocação em cache passa a ser possível pela primeira vez. Os resultados dos métodos de listagem passam agora a incluir ttlMs e cacheScope, e cacheScope: "public" informa os intermediários partilhados de que podem colocar a resposta em cache. Isto só é seguro porque os resultados das listagens já não variam por ligação, uma consequência direta da remoção das sessões.

Por que a autenticação muda quando não existe uma sessão

Com uma sessão, era tentador autenticar uma vez em initialize e depois tratar o ID da sessão como prova para tudo o que viesse a seguir. Um ID de sessão usado dessa forma é uma credencial de portador sem audiência, expiração ou mecanismo de revogação, criada pelo seu próprio servidor. Remover as sessões elimina esse atalho, e a alternativa é mais rigorosa.

Um servidor MCP protegido funciona como um servidor de recursos OAuth 2.1. Cada pedido HTTP do cliente deve incluir Authorization: Bearer <access token>, e o servidor valida o token em cada pedido. A validação inclui a audiência: o servidor deve confirmar que o token foi emitido especificamente para ele, de acordo com a RFC 8707 (Resource Indicators for OAuth 2.0), e não deve aceitar nem encaminhar tokens destinados a qualquer outro serviço. Os clientes solicitam a audiência correta enviando o parâmetro resource com o URI canónico do servidor.

A descoberta começa com um desafio. Quando recebe um pedido sem um token utilizável, o servidor responde com 401 Unauthorized.

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
                         scope="files:read"

O cliente lê resource_metadata, obtém esse documento (RFC 9728, OAuth 2.0 Protected Resource Metadata, que os servidores MCP devem implementar), identifica o servidor de autorização e executa o fluxo. Um token válido com permissões insuficientes recebe 403 Forbidden com error="insufficient_scope" e os scopes necessários para essa operação.

Isto tem duas consequências para a forma como executa o serviço. A validação do token ocorre agora em cada pedido, em vez de uma vez por sessão. Por isso, uma viagem de rede até um endpoint de introspeção por chamada terá impacto na latência. Prefira tokens que possa verificar localmente com base numa assinatura, numa audiência e numa expiração, ou coloque em cache o resultado da validação durante um período curto, indexado pelo token. Além disso, como não existe uma sessão que mantenha uma identidade, a autorização deve ser calculada a partir do token em cada chamada. Isto é mais transparente do que o modelo de sessão e combina com a prática mais ampla de manter as credenciais fora do processo do agente, descrita em manter segredos fora de um agente de IA. Os scopes apenas limitam o que um token pode fazer depois de o pedido chegar ao servidor. Na máquina onde o agente é executado, plugins do harness que adicionam regras de permissões para ferramentas e limites de orçamento determinam quais as chamadas que são feitas.

O que é verdadeiro nesta revisão e o que não é

Tudo o que foi descrito acima aplica-se à revisão 2026-07-28. Não descreve o MCP para sempre nem descreve o servidor que implementou no ano passado.

Os clientes e servidores em 2025-11-25 e anteriores continuam a utilizar o modelo de handshake. A especificação chama essas revisões de legadas e chama as revisões com metadados por pedido de modernas. Um servidor que suporte apenas esta revisão, ao comunicar com um cliente mais antigo, deve responder 405 Method Not Allowed a GET ou DELETE no endpoint do MCP, ignorar qualquer cabeçalho Mcp-Session-Id sem gerar nem devolver um, e ignorar Last-Event-ID porque os streams não podem ser retomados. Um servidor compatível com as duas eras pode disponibilizar ambas no mesmo endpoint: um pedido que contenha _meta é servido sem estado, e um pedido initialize seleciona a semântica de sessão mais antiga.

Por isso, verifique a string da revisão antes de confiar em qualquer parte deste conteúdo. Se o seu SDK ainda enviar initialize, as sessões continuam a existir na sua implementação e os problemas relacionados com sessões descritos acima continuam a ser da sua responsabilidade. O mesmo se aplica ao lado do cliente: um processo de agente no seu próprio sistema, como a configuração descrita em executar um agente de programação numa VPS, só é sem estado neste sentido se a biblioteca utilizada falar uma revisão moderna. Leia a versão que o seu runtime negoceia e, em seguida, leia a revisão correspondente da especificação. Considere esta página como a descrição de uma revisão identificada, não do protocolo em geral.

FAQ

Um servidor MCP sem estado significa que não posso armazenar nada?

Não. Sem estado descreve o protocolo, não a sua aplicação. Bases de dados, filas e caches continuam a funcionar exatamente como antes. O que muda é que o estado que abrange várias chamadas tem de ser referenciado por um identificador explícito, enviado pelo cliente em cada pedido, como um identificador gerado pelo servidor num argumento de ferramenta. O que não pode fazer é inferir o contexto a partir da ligação: a especificação diz que um servidor não pode depender de pedidos anteriores na mesma ligação para estabelecer capacidades, versão do protocolo ou identidade do cliente, porque cada pedido fornece esses dados em _meta.

Continuo a precisar de sessões persistentes no balanceador de carga?

Não para pedidos normais. Na revisão 2026-07-28, cada POST inclui a sua própria versão do protocolo, capacidades e credenciais. Assim, qualquer réplica pode responder a qualquer pedido e o round-robin é suficiente. O único elemento de longa duração que resta é o fluxo de resposta subscriptions/listen, que é uma ligação aberta única para um único processo. Termina quando esse processo termina, e o cliente envia novamente subscriptions/listen para o restabelecer. Isto corresponde ao tempo de vida da ligação, não à afinidade de sessão, e nenhuma regra de encaminhamento o impede.

O que aconteceu a Mcp-Session-Id e ao fluxo HTTP GET?

Ambos foram removidos na revisão 2026-07-28, ao abrigo de SEP-2567 e SEP-2575. Um servidor que implemente apenas esta revisão deve responder com 405 Method Not Allowed a GET e DELETE no endpoint MCP, e deve ignorar um cabeçalho Mcp-Session-Id em vez de o devolver. As notificações de alteração iniciadas pelo servidor seguem agora no fluxo de resposta de um pedido subscriptions/listen, em vez de um fluxo GET autónomo. Os servidores que têm de continuar a servir clientes mais antigos implementam o comportamento da revisão anterior em conjunto com este.

Como faço um health check a um servidor MCP sem handshake?

Use dois níveis. Aponte o check ativo do proxy para um caminho HTTP simples servido pela aplicação, porque um GET ao endpoint MCP devolve corretamente 405 e faria o backend saudável parecer indisponível. Depois, verifique o próprio protocolo enviando server/discover por POST. Todos os servidores 2026-07-28 têm de o implementar. Confirme que a resposta é HTTP 200 e que apresenta uma versão do protocolo usada pelos seus clientes. Um 404 com o erro JSON-RPC -32601 significa que o processo está em execução, mas não disponibiliza esse método. Um 400 com -32022 significa que a versão solicitada não é suportada por essa compilação.