MCP se vuelve stateless: adiós a las sesiones y al Sampling

El draft 2026-07-28 del Model Context Protocol no es un bump de versión. Elimina el handshake de inicialización, borra las sesiones del transporte, retira las peticiones iniciadas por el servidor y deja a Roots, Sampling y Logging marcados para morir.
Antes de seguir, la aclaración que cambia cómo leer todo esto: es un draft. La página de versioning sigue marcando 2025-11-25 como la versión vigente, y el changelog se refiere a sí mismo como "this draft". Nada de lo que viene aquí está en producción todavía, y parte puede cambiar antes de cerrarse.
Dicho eso, la dirección es lo bastante clara y lo bastante drástica como para que valga la pena mirarla ahora, sobre todo si mantienes un servidor MCP.
Una sola decisión explica casi todo
Si lees el changelog de arriba abajo parece una lista inconexa de retiradas. No lo es. Casi cada punto se deduce de una misma decisión: MCP pasa a ser un protocolo sin estado.
El cambio literal, del changelog:
Make MCP stateless: remove the
initialize/notifications/initializedhandshake. Every request now carries its protocol version and client capabilities in_meta.
Y el que lo acompaña:
Remove protocol-level sessions and the
Mcp-Session-Idheader from the Streamable HTTP transport.
El motivo es de despliegue, no de elegancia. Un protocolo con estado obliga a que la conexión número dos de un cliente aterrice en el mismo proceso que la número uno. En la práctica eso significa sticky sessions en el balanceador, o una capa de almacenamiento compartido entre instancias. Ambas cosas son un impuesto que pagas para poder escalar horizontalmente algo que, en el fondo, son llamadas a funciones.
Sin estado, cualquier instancia puede atender cualquier petición. Eso es lo que se está comprando, y el resto del changelog es el precio.
Lo que desaparece
Con el estado se van varias cosas que hoy das por sentadas:
- El handshake
initialize/notifications/initialized. Ahora cada petición lleva su versión de protocolo y las capacidades del cliente en_meta, bajo claves comoio.modelcontextprotocol/protocolVersionyio.modelcontextprotocol/clientCapabilities. - La cabecera
Mcp-Session-Id. Los servidores que necesiten estado entre llamadas deben emitir handles explícitos y pasarlos como argumentos normales de una tool. ping,logging/setLevelynotifications/roots/list_changed. El nivel de log pasa a ser por petición, víaio.modelcontextprotocol/logLevelen_meta.- La resumabilidad del stream SSE. Se van la cabecera
Last-Event-IDy los IDs de evento. Si el stream se corta, la petición en vuelo se pierde y el cliente MUST reintentarla como una petición nueva con un ID nuevo.
Ese último punto merece un momento. La resumabilidad existía justamente para sobrevivir a redes malas sin repetir trabajo. Cambiarla por "reintenta desde cero" es una simplificación real del transporte, pero traslada el coste a operaciones largas: si tu tool tarda 40 segundos y el túnel se cae en el segundo 38, empiezas de nuevo.
MRTR: el servidor deja de poder llamarte
Este es el cambio con más consecuencias prácticas, y el propio spec lo etiqueta sin rodeos como breaking change.
Hasta ahora, un servidor que necesitaba algo del cliente a mitad de una operación (pedirle un dato al usuario con elicitation/create, pedirle al modelo del cliente que genere algo con sampling/createMessage, consultar los directorios con roots/list) simplemente abría una petición en sentido contrario. Eso requiere una conexión viva y con estado, así que es incompatible con lo anterior.
El reemplazo se llama Multi Round-Trip Requests, y le da la vuelta al flujo: en vez de que el servidor te llame, te devuelve un resultado a medias pidiéndote lo que le falta, y tú reintentas la petición original con la respuesta incluida.
El servidor responde algo así:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "input_required",
"inputRequests": {
"github_login": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please provide your GitHub username",
"requestedSchema": {
"type": "object",
"properties": { "name": { "type": "string" } },
"required": ["name"]
}
}
}
},
"requestState": "AEAD-protected blob"
}
}El cliente recoge el dato, y reintenta la llamada original añadiendo inputResponses y devolviendo el requestState tal cual lo recibió. Ese requestState es la pieza que hace que el truco funcione: es un blob opaco donde el servidor mete todo el contexto que necesitaría recordar, para no tener que recordarlo. El cliente MUST NOT inspeccionarlo ni modificarlo.
Aquí hay un detalle de seguridad que conviene no pasar por alto, porque el spec es explícito: el requestState viaja por el cliente, así que el servidor debe tratarlo como entrada controlada por un atacante. Si influye en autorización o en lógica de negocio, hay que protegerlo con HMAC o AEAD y rechazar lo que no verifique. Y para evitar replay, el spec recomienda meter dentro del blob el principal autenticado, un TTL corto y un identificador de la petición de origen.
Traducido: acabas de mudar tu estado de sesión a un token firmado que va y viene por la red. Eso escala mucho mejor, pero es criptografía que antes no tenías que escribir.
También cambia una cosa transversal: todos los resultados llevan ahora un campo resultType obligatorio, con valor "complete" o "input_required". Los clientes deben tratar como "complete" los resultados de servidores en versiones anteriores que no lo incluyan.
Roots, Sampling y Logging quedan deprecados
Esta es la parte que me parece más discutible, y la que más va a doler.
SEP-2577 deja las tres funcionalidades en estado Deprecated. Siguen funcionando durante la ventana de deprecación, pero las implementaciones nuevas no deberían adoptarlas. Las migraciones sugeridas por el propio spec:
| Funcionalidad | Migración sugerida |
|---|---|
| Roots | Pasar directorios o ficheros como parámetros de tool, URIs de recurso, o configuración del servidor |
| Sampling | Integrar directamente con las APIs del proveedor de LLM |
| Logging | Escribir a stderr en stdio, o usar OpenTelemetry |
Lo de Sampling es un retroceso conceptual, y vale la pena decirlo claro. Sampling era la idea de que un servidor pudiera pedir prestado el modelo del cliente: tú no necesitabas tu propia API key ni elegir proveedor, porque el host ya tenía uno y te lo prestaba. Era de las pocas cosas que MCP hacía y que no podías replicar con una API REST normal.
Decir "intégrate directamente con las APIs del proveedor" es decirle a cada servidor que se busque la vida: su propia clave, su propia facturación, su propio proveedor. Se gana simplicidad de protocolo y se pierde la parte donde el host era un intermediario útil.
Que sea la decisión correcta o no, dependerá de cuánta gente estuviera usando Sampling de verdad. Mi sospecha es que poca, y que ese es exactamente el argumento que ganó.
Lo que llega
No todo es retirada. Entra bastante:
server/discover, un RPC que los servidores MUST implementar y que devuelve versiones soportadas, capacidades e identidad en una sola llamada. Llamarlo es opcional para el cliente: puedes lanzar cualquier petición directamente y manejar el error de versión si llega.subscriptions/listen, que sustituye al endpoint GET y aresources/subscribecon un único stream long-lived sobre una respuesta POST. El cliente se suscribe explícitamente a los tipos que le interesan.- Un sistema de extensiones fuera del núcleo, siempre opt-in. Ahí van Tasks (que sale del core), MCP Apps para UI interactiva, y Skills over MCP.
- Caché de verdad. Los resultados de
tools/list,prompts/list,resources/listy compañía pasan a requerirttlMsycacheScope. Es un cambio pequeño con impacto real: menos polling y mejores tasas de acierto en la caché de prompts. - Orden determinista en
tools/list, recomendado precisamente para que esa caché funcione.
Lo de Skills over MCP como extensión oficial tiene su gracia si seguiste el debate del año pasado, cuando media internet declaró que Skills había matado a los MCP. Escribí entonces que no habían muerto sino cambiado de rol; que Skills acabe siendo una extensión del propio protocolo es más o menos el final más aburrido y más razonable posible.
La parte de gobernanza que nadie va a comentar
Junto a los cambios técnicos entra una política de ciclo de vida y deprecación: estados Active, Deprecated y Removed, una ventana mínima de doce meses antes de que algo pueda eliminarse, y un registro público de funcionalidades deprecadas.
Es lo menos vistoso del changelog y probablemente lo más importante a largo plazo. Un protocolo que rompe cosas sin una política de deprecación es un protocolo en el que no puedes construir. Que aparezca justo en la revisión que más cosas retira no es casualidad: es lo que hace que retirarlas sea aceptable.
En la misma línea, la Dynamic Client Registration de OAuth 2.0 queda deprecada en favor de los Client ID Metadata Documents, y el viejo transporte HTTP+SSE pasa formalmente a Deprecated.
Qué hacer si mantienes un MCP hoy
Nada urgente, y esa es la respuesta honesta. Es un draft, la versión vigente sigue siendo 2025-11-25, y hay una ventana de doce meses para lo deprecado.
Lo que sí haría desde ya:
- No construir nada nuevo sobre Roots, Sampling o Logging. La ventana es de doce meses, pero adoptar hoy algo marcado para salir es trabajo que ya sabes que vas a tirar.
- Mirar cuánto estado de sesión tienes. Si tu servidor guarda cosas entre llamadas apoyándose en la sesión del protocolo, ese es tu trabajo de migración. Cuanto antes sepas el tamaño, mejor.
- Si dependes de Sampling, empezar a pensar en el plan B. Es la deprecación sin sustituto equivalente dentro del protocolo.
- No reescribir nada todavía. Es un draft. Reaccionar a un draft como si fuera final es la forma más rápida de escribir código dos veces.
Mi lectura
MCP se está reescribiendo para ser desplegable, no para ser más capaz. Todo el movimiento va en la dirección de "esto tiene que poder correr detrás de un balanceador sin pensarlo", y para llegar ahí se está desprendiendo de lo que le estorba: sesiones, handshake, peticiones bidireccionales, resumabilidad.
Es un intercambio defendible. La complejidad de operar servidores MCP era real, y buena parte venía del estado. Pero conviene ser claro sobre qué se paga: el protocolo se vuelve más simple de servir y más aburrido de usar. Las piezas que lo hacían distinto a "una API REST con descubrimiento" son justo las que se están yendo.
Queda por ver si la ganancia en despliegue trae la adopción que compense. Y queda por ver, sobre todo, qué sobrevive del draft cuando se cierre.