MCP se vuelve stateless: se va el handshake initialize y el header Mcp-Session-Id

MCP se vuelve stateless: se va el handshake initialize y el header Mcp-Session-Id

Abre el código de tu MCP server y busca dos cosas: el handler de initialize y cualquier lectura del header Mcp-Session-Id. En la especificación que salió ayer, los dos dejan de ser parte del protocolo.

La spec 2026-07-28 se publicó el 28 de julio y es el cambio más grande en MCP desde que llegaron los remote servers, hace aproximadamente dieciocho meses. El titular es una frase del anuncio: MCP pasa “de un protocolo bidireccional stateful a un protocolo request/response stateless”.

No es un cambio cosmético. Reescribe cómo un request se identifica a sí mismo, cómo despliegas tus servers y de qué capabilities puedes seguir dependiendo.

Qué se eliminó realmente

Tres cosas, y están conectadas:

  • El intercambio initialize/initialized. Ya no hay una ronda de negociación inicial antes de poder llamar a una tool.
  • El header Mcp-Session-Id. No existe en la nueva spec.
  • El requisito de sesión a nivel de protocolo, retirado por completo.

Todo lo que ese handshake establecía —versión del protocolo, identidad del client, capabilities del client— ahora viaja en cada request en lugar de acordarse una vez y recordarse.

Cómo se ve un request ahora

La identidad del client se mueve a _meta, bajo una key con namespace:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search",
    "arguments": { "q": "otters" },
    "_meta": {
      "io.modelcontextprotocol/clientInfo": { "name": "my-app", "version": "1.0" }
    }
  }
}

La versión del protocolo no va en _meta: viaja en el header MCP-Protocol-Version. _meta también carga W3C Trace Context (traceparent, tracestate, baggage), lo que significa que el distributed tracing entre las tool calls de un agente deja de ser algo que tienes que montar por tu cuenta.

Hay dos headers más que importan sobre Streamable HTTP: Mcp-Method y Mcp-Name. Le permiten a un proxy o a un load balancer rutear un request sin parsear el body JSON. Los servers deben rechazar los requests donde headers y body no coinciden, así que no puedes usarlos para colar una llamada distinta por delante del router.

Por qué lo hicieron

La razón declarada es el deployment, y vale la pena citarla directo: “Cualquier request puede ahora aterrizar en cualquier instancia del server detrás de un load balancer round-robin simple, sin necesidad de storage compartido”.

Si has corrido un MCP server en producción, sabes qué te está comprando esa frase. La session affinity implicaba o bien sticky routing o bien un Redis en el medio cuyo único trabajo era recordar qué client había dicho hola. Las dos son costo operacional, y las dos son un modo de falla: si pierdes el session store, todos los clients conectados tienen que volver a hacer el handshake.

La spec suma dos cosas que apuntan a la misma idea. Los resultados de listados y resources ahora pueden llevar ttlMs y cacheScope, modelados sobre el Cache-Control de HTTP: respuestas cacheables en lugar de estado sostenido por conexión. Y para las llamadas que sí necesitan más de un round trip, existe InputRequiredResult, con inputRequests, requestState y los inputResponses que provee el client. El estado viaja en el request en vez de vivir en el server.

Qué queda deprecado y qué lo reemplaza

Roots, Sampling y Logging quedan deprecados, igual que el transporte legacy HTTP+SSE. La spec nombra un reemplazo para cada uno:

Deprecado Reemplazo
Roots Parámetros de tool, resource URIs o configuración del server
Sampling Integración directa con la API de tu proveedor de LLM
Logging stderr para transportes stdio; OpenTelemetry para observabilidad estructurada
Transporte HTTP+SSE Streamable HTTP

Sampling es el que vale la pena mirar dos veces. Permitía que un server le pidiera al client correr una inferencia en su nombre, que es exactamente el tipo de cosa bidireccional y stateful que el nuevo core está diseñado para no hacer. Si tu server lo usaba, no estás cambiando el nombre de un campo: estás asumiendo una integración con un proveedor de LLM que antes no tenías.

Junto con las deprecaciones, la spec introduce una política formal de deprecación: “una ventana mínima de doce meses para que puedas planificar las actualizaciones en lugar de reaccionar a ellas”. Esa es la parte con la cola más larga. Hasta ahora, quien consumía MCP no tenía una respuesta contractual a “¿cuánto tiempo tengo?”. Ahora la tiene, y es al menos un año.

Checklist de migración

Los SDKs de TypeScript, Python, Go y C# —todos Tier 1— ya soportan 2026-07-28. Un orden de trabajo práctico:

  1. Actualiza el SDK y lee las notas de migración de tu framework. La mayor parte del cambio de wire format se resuelve por ti.
  2. Haz grep de Mcp-Session-Id y de cualquier cosa apoyada en un identificador de sesión: caches por sesión, auth por sesión, working directories por sesión. Cada una tiene que convertirse en un parámetro del request o en un lookup del lado del server con una key duradera.
  3. Elimina los supuestos del handler de initialize. Si definías defaults durante la inicialización y los leías después, esos defaults ahora tienen que derivarse por request o venir de la configuración del server.
  4. Reemplaza Roots con parámetros de tool explícitos o resource URIs. Suele ser el más fácil de los tres, y tiende a dejar tus tool schemas más honestos sobre lo que realmente necesitan.
  5. Reemplaza Sampling con una llamada directa al proveedor, o elimina la funcionalidad.
  6. Mueve Logging a stderr o a OpenTelemetry.
  7. Sal de HTTP+SSE hacia Streamable HTTP, y agrega Mcp-Method/Mcp-Name si algo delante de tu server rutea por contenido.
  8. Recién ahí, quita la infraestructura de sesiones: sticky sessions, session store. Y confirma que puedes hacer round-robin entre instancias.

El paso 8 al final, a propósito. Es el payoff, y también es el paso que te dice si los pasos 2 y 3 quedaron completos.

La parte honesta

El anuncio no pretende que esto sea gratis: “habrá algún costo de migración, especialmente para desarrolladores que sí dependían de los identificadores de sesión”. Eso calza con la forma del cambio. Si tu server ya era efectivamente stateless —recibe argumentos, devuelve un resultado—, la migración es un bump de SDK y una pasada de limpieza. Si construiste workflows stateful sobre la identidad de sesión, estás refactorizando, no actualizando.

Doce meses alcanzan para hacerlo con calma. No alcanzan para no hacerlo nunca.


¿Tu MCP server depende de estado por sesión, o migrar a stateless te queda en un bump de SDK y limpiar dos handlers? Cuéntanos qué se te rompe.