Especificación MCP 2026-07-28: adiós a las sesiones — guía de migración a stateless

El 28 de julio de 2026 se publicó una nueva revisión de la especificación de Model Context Protocol
que pasó a ser la versión actual (Current) del protocolo. Han transcurrido ocho meses desde la
revisión anterior, 2025-11-25. David Soria Parra, cocreador de MCP, describió esta entrega en el
anuncio oficial como lo “más importante que le ha pasado a MCP desde que apareció remote MCP hace
más de un año”.
Resumido en una línea: MCP se ha vuelto stateless. El handshake initialize ha desaparecido y
la cabecera Mcp-Session-Id también. Si tienes un servidor o un cliente MCP en producción, esto no
es una nota de versión que puedas leer “algún día”: es un breaking change que te obliga a tocar
código.
Las ondas expansivas ya se notan. El 6 de agosto, Cloudflare marcó McpAgent —la pieza que se usaba
para construir servidores MCP en Workers— como deprecated y feature-frozen, y recomendó pasarse a
un handler stateless. Si desaparecen las sesiones, tampoco hace falta un Durable Object que las
sostenga. De esto hablamos aparte en la sección 12.
Este artículo no se limita a copiar la lista de cambios de la especificación: se centra en qué código hay que modificar realmente. Cada punto lleva su número de SEP, así que si necesitas la fuente puedes abrir directamente la PR correspondiente.
1. Las sesiones han desaparecido (SEP-2567)
Este es el cambio grande. En el transporte Streamable HTTP se han eliminado las sesiones a nivel de
protocolo y la cabecera Mcp-Session-Id.
De ahí se deriva una condición adicional: tools/list, resources/list y prompts/list
ya no pueden devolver valores distintos según la conexión. Antes era viable implementar cosas del
estilo “esta sesión pertenece a un usuario autenticado, así que también le mostramos las herramientas
de administración”; ahora la respuesta de un listado no depende de la conexión.
¿Y qué hace entonces un servidor que necesita mantener estado entre llamadas? La respuesta de la especificación es clara: que el servidor emita un handle explícito y lo intercambie como un argumento más de la herramienta.
// Before — estructura que dependía de la cabecera de sesión
app.post('/mcp', async (c) => {
const sessionId = c.req.header('Mcp-Session-Id');
const state = await sessions.get(sessionId); // ya no llega esta cabecera
// ...
});
// After — el estado viaja explícitamente como argumento, en un handle emitido por el servidor
server.tool('open_workspace', {}, async () => ({
resultType: 'complete',
content: [{ type: 'text', text: 'Se ha abierto el workspace' }],
structuredContent: { workspaceHandle: 'ws_01J8ZQ...' }, // lo emite el servidor
}));
server.tool('write_file', {
workspaceHandle: z.string(), // el cliente lo reenvía tal cual en la siguiente llamada
path: z.string(),
body: z.string(),
}, async ({ workspaceHandle, path, body }) => { /* ... */ });
El estado implícito de la conexión se convierte en dato explícito. Eso simplifica muchísimo tener varias instancias detrás de un balanceador de carga o desplegar en serverless, porque da igual qué instancia atienda cada petición.
2. El handshake initialize ha desaparecido (SEP-2575)
initialize y notifications/initialized se han eliminado por completo. En su lugar, cada
petición transporta en _meta la información que necesita por sí misma.
Clave de _meta |
Dirección | Obligatorio | Contenido |
|---|---|---|---|
io.modelcontextprotocol/protocolVersion |
Cliente → Servidor | Sí | Versión del protocolo ("2026-07-28") |
io.modelcontextprotocol/clientCapabilities |
Cliente → Servidor | Sí | Capacidades del cliente relevantes para esta petición |
io.modelcontextprotocol/clientInfo |
Cliente → Servidor | No (SHOULD) | Nombre y versión del cliente |
io.modelcontextprotocol/logLevel |
Cliente → Servidor | No | Nivel mínimo de log para esta petición |
io.modelcontextprotocol/serverInfo |
Servidor → Cliente (en el _meta del resultado) |
No (SHOULD) | Nombre y versión del servidor |
// esta es la forma que adopta ahora cualquier petición
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_file",
"arguments": { "path": "a.txt", "body": "hi" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": { },
"io.modelcontextprotocol/clientInfo": { "name": "my-client", "version": "1.0.0" }
}
}
}
Una petición a la que le falte un campo obligatorio está malformada. El servidor debe (MUST)
rechazarla con el error JSON-RPC -32602 (Invalid params) y, si es HTTP, el estado de respuesta debe
ser 400 Bad Request. Si la versión no encaja, se devuelve UnsupportedProtocolVersionError
(-32022).
La declaración de capacidades también incorpora una regla: el servidor no debe (MUST NOT) asumir
capacidades que el cliente no haya declarado. Si para atender la petición hace falta una capacidad
no declarada, hay que devolver MissingRequiredClientCapabilityError (-32021) y enumerar en
data.requiredCapabilities las capacidades que faltan.
La especificación deja muy claro qué significa aquí “stateless”: el servidor no debe (MUST NOT) construir contexto apoyándose en peticiones anteriores de la misma conexión. Incluso indica de forma explícita que un proceso stdio no es una conversación ni una sesión: el cliente puede mezclar en el mismo transporte peticiones sin relación entre sí, y el servidor no debe tratar la identidad de la conexión o del proceso como sustituto de la continuidad de sesión.
Como se elimina un ida y vuelta completo del handshake, la ganancia se nota en entornos con arranques en frío frecuentes: runtimes edge o serverless. En el lado contrario, todo el código que dependía de información “recibida una vez en la inicialización y reutilizada después” hay que rehacerlo.
3. server/discover: ahora es de implementación obligatoria (SEP-2575)
Es el RPC que ocupa el hueco que deja el handshake. El servidor debe (MUST) implementar
server/discover, cuya función es anunciar las versiones de protocolo soportadas, las capacidades
y la identidad.
Desde el lado del cliente se puede usar de dos maneras:
- Llamarlo antes de enviar otras peticiones para elegir la versión (MAY)
- En STDIO, como sonda de compatibilidad hacia atrás: si responde, es moderno; si no, es legacy
Así se ve un DiscoverResult. Cuidado con los nombres de los campos: la lista de versiones
soportadas es supportedVersions, y la identidad del servidor no va en la raíz del resultado, sino
dentro de _meta.
{
"resultType": "complete",
"supportedVersions": ["2026-07-28", "2025-11-25"],
"capabilities": { "tools": {}, "resources": {} },
"_meta": {
"io.modelcontextprotocol/serverInfo": { "name": "my-mcp-server", "version": "2.0.0" }
}
}
El error que devuelve el servidor cuando la versión no encaja también está definido. Hay que incluir la lista de versiones soportadas para que el cliente pueda volver a elegir.
{
"jsonrpc": "2.0", "id": 1,
"error": {
"code": -32022,
"message": "Unsupported protocol version",
"data": { "supported": ["2026-07-28", "2025-11-25"], "requested": "1900-01-01" }
}
}
Si estás construyendo un servidor, este es el primer trozo de código que debes añadir en esta migración. Sin él, incumples la especificación.
Cómo atender a clientes antiguos y nuevos a la vez
La especificación clasifica las implementaciones en tres categorías.
| Término | Significado |
|---|---|
| Modern | Envía versión, identidad y capacidades en el _meta de cada petición (2026-07-28 o posterior) |
| Legacy | Establece una sesión con el handshake initialize (2025-11-25 o anterior) |
| Dual-era | Implementación que soporta ambos modos |
Un servidor dual-era decide según cómo le hable el cliente. Si la petición llega con el _meta
moderno, la procesa como stateless; si llega un initialize, la procesa a la manera legacy. Se
permite (MAY) servir ambos modos desde un mismo endpoint.
La detección del lado cliente varía según el transporte.
- stdio — sondear con
server/discover; si no se recibe un error moderno reconocible, se asume legacy - Streamable HTTP — enviar una petición moderna y hacer fallback después de inspeccionar el cuerpo del
400 Bad Request
El resultado de esa detección es una propiedad del servidor, no de cada petición concreta. Lo correcto es cachearlo (SHOULD) durante la vida del proceso en stdio, y por origen en HTTP.
Hay una combinación que conviene tener presente: cliente legacy → servidor moderno falla. Un
cliente legacy no tiene ningún mecanismo para subir a una versión superior. Por eso, en un servidor
exclusivamente moderno conviene (SHOULD) indicar las versiones soportadas en el mensaje de error
de la petición initialize: ese mensaje puede ser la única pista que llegue a ver el usuario del
cliente legacy.
4. Cambia el modelo de suscripciones (SEP-2575)
Desaparecen el endpoint HTTP GET y resources/subscribe/resources/unsubscribe, y todo queda
unificado en subscriptions/listen: un único stream de respuesta POST de larga duración.
El cliente hace un opt-in explícito a los tipos de notificación que quiere recibir.
toolsListChangedpromptsListChangedresourcesListChangedresourceSubscriptions
El servidor lo confirma y etiqueta las notificaciones que envía con
io.modelcontextprotocol/subscriptionId.
Aquí hay un punto que se presta a confusión: las notificaciones asociadas a una petición, como
notifications/progress y notifications/message, no viajan por el stream de
subscriptions/listen. Esas notificaciones siguen fluyendo por el stream de respuesta de la
petición original. Si de repente deja de aparecer el indicador de progreso, sospecha de aquí.
5. MRTR — cómo pregunta ahora el servidor al cliente (SEP-2322)
Antes existía un canal propio para que el servidor lanzara peticiones al cliente: roots/list,
sampling/createMessage, elicitation/create y similares. Trasladando literalmente lo que dice la
especificación: el servidor debe (MUST) enviar ahora ese tipo de peticiones mediante el patrón
MRTR, y el antiguo patrón de peticiones iniciadas por el servidor ya no está soportado. Es un
breaking change.
El nuevo flujo es este.
- El cliente envía una petición (
id: 1) - Si el servidor necesita más información, devuelve un
InputRequiredResultconresultType: "input_required" - El cliente pregunta al usuario y vuelve a enviar la petición original con un
idnuevo, incorporando la respuesta
Y aquí está el error más habitual: inputRequests e inputResponses no son arrays, son mapas.
La clave es el identificador que asigna el servidor, y el valor es un objeto de petición completo
(method + params) que puede ser ElicitRequest, CreateMessageRequest o ListRootsRequest.
// paso 2 — respuesta del servidor
{
"jsonrpc": "2.0", "id": 1,
"result": {
"resultType": "input_required",
"inputRequests": {
"github_login": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Introduce tu nombre de usuario de GitHub",
"requestedSchema": {
"type": "object",
"properties": { "name": { "type": "string" } },
"required": ["name"]
}
}
}
},
"requestState": "AEAD-protected blob"
}
}
La respuesta es un mapa con las mismas claves, y sus valores son los objetos de resultado de cada petición.
// paso 3 — se reintenta con un id nuevo, reenviando la respuesta y el requestState tal cual
{
"jsonrpc": "2.0", "id": 2,
"method": "tools/call",
"params": {
"name": "delete_file",
"arguments": { "path": "a.txt" },
"inputResponses": {
"github_login": { "action": "accept", "content": { "name": "octocat" } }
},
"requestState": "AEAD-protected blob"
}
}
requestState — el mecanismo que hace stateless a MRTR
requestState es una cadena opaca que solo tiene sentido para el servidor. El servidor codifica
ahí el contexto que necesita y lo envía; el cliente se lo devuelve tal cual. Esa es justamente la
razón por la que el servidor no necesita un almacén de estado.
Las reglas son bastante estrictas.
- El servidor debe (MUST) incluir en todo
InputRequiredResultal menos uno de estos dos campos:inputRequestsorequestState - El cliente debe (MUST) devolver el valor exactamente como lo recibió, y no debe (MUST NOT) inspeccionarlo, parsearlo ni modificarlo. Si no lo recibió, tampoco debe incluirlo en el reintento
- El
idJSON-RPC del reintento debe (MUST) ser distinto al de la petición original, porque se trata de una petición independiente
El requisito de seguridad es especialmente importante. Como este valor pasa por el cliente, es una entrada manipulable por un atacante (la especificación indica con MUST que debe tratarse así). Si influye en la autorización, en el acceso a recursos o en la lógica de negocio, el servidor debe proteger obligatoriamente su integridad (por ejemplo con HMAC o AEAD) y rechazar cualquier estado que no supere la verificación. Solo se puede omitir cuando manipularlo no provoque nada más allá de que la petición falle.
Para prevenir replay, se recomienda (SHOULD) incluir lo siguiente dentro del payload protegido en integridad y verificarlo en cada recepción.
- El sujeto autenticado — rechazar si lo presenta otro sujeto
- Una caducidad corta (TTL) — rechazar si se presenta pasada
- Un identificador de la petición original (nombre del método + digest de los parámetros principales) — rechazar si se presenta en una petición que no corresponde
Ahora bien, estas medidas solo estrechan la ventana de replay; no garantizan un uso único. Si de verdad necesitas un solo uso, el servidor debe (MUST) imponerlo por su cuenta.
Solo tres peticiones admiten MRTR
| Petición del cliente | ¿Admite InputRequiredResult? |
|---|---|
prompts/get |
Sí |
resources/read |
Sí |
tools/call |
Sí |
En el resto de peticiones no debe (MUST NOT) enviarse. Tampoco se pueden incluir en
inputRequests peticiones de un tipo que el cliente no haya declarado como capacidad: si el cliente
no declaró elicitation, no puedes meter ahí un elicitation/create.
Las peticiones bidireccionales se han convertido en reintentos unidireccionales. Para el transporte todo es mucho más simple, pero el cliente tiene que implementar el bucle de reintento por su cuenta. Todos los callbacks que gestionaban peticiones iniciadas por el servidor hay que trasladarlos a esta estructura.
6. resultType pasa a ser obligatorio en todos los resultados (SEP-2322)
Todos los resultados llevan de forma obligatoria el campo resultType.
"complete"— resultado normal"input_required"— resultado intermedio de MRTR
Hay una regla explícita sobre compatibilidad hacia atrás: si un servidor de un protocolo anterior
omite este campo, el cliente debe (MUST) tratarlo como "complete". Si estás escribiendo un
cliente, no te olvides de esta rama.
7. Lo que ha desaparecido
ping
logging/setLevel
notifications/roots/list_changed
tasks/result (→ sustituido por polling con tasks/get)
tasks/list
notifications/elicitation/complete
El nivel de log se indica ahora en cada petición mediante io.modelcontextprotocol/logLevel dentro
de _meta. Y el servidor no debe (MUST NOT) enviar notifications/message para peticiones que no
lleven ese campo. Si tu servidor volcaba logs sin condiciones, tendrás que añadir esa comprobación.
También desaparece la reanudación del stream SSE. Al eliminarse de Streamable HTTP la cabecera
Last-Event-ID y los IDs de evento SSE, si se corta el stream de respuesta, la petición en curso se
pierde sin más. El cliente debe (MUST) reenviarla con un nuevo ID de petición. Si tienes código
escrito esperando que al reconectar se retome donde iba, aquí se romperá en silencio.
8. Tasks sale del core y pasa a ser una extensión (SEP-2663)
Tasks, que era una función experimental, se ha sacado del protocolo core y se ha trasladado a la
extensión oficial io.modelcontextprotocol/tasks. No es una simple mudanza: el diseño se ha
rehecho.
| Cambio | Detalle |
|---|---|
Se elimina tasks/result |
Era bloqueante. Se sustituye por polling con tasks/get |
Se añade tasks/update |
Para enviar entrada del cliente al servidor |
Se elimina tasks/list |
— |
| Devolución de handles | El servidor puede devolver un handle de tarea por iniciativa propia, sin opt-in por petición |
Las extensiones se anuncian en el mapa extensions de las capacidades (capabilities). El
identificador sigue la convención de nombres de las claves _meta, con prefijo obligatorio.
{
"capabilities": {
"tools": {},
"extensions": { "io.modelcontextprotocol/tasks": {} }
}
}
También está definido qué ocurre cuando solo una de las partes soporta la extensión: quien la soporta debe (MUST) recurrir al comportamiento del core o rechazar con un error apropiado.
9. Deprecated — Roots, Sampling y Logging (SEP-2577)
Tres funcionalidades quedan marcadas como obsoletas. Durante el periodo de deprecación siguen funcionando por completo, pero la idea es que las implementaciones nuevas no las usen. Estas son las alternativas que recomienda la especificación.
| Funcionalidad obsoleta | Alternativa |
|---|---|
| Roots | Pasar directorios y ficheros como parámetros de herramienta, URIs de recurso o configuración del servidor |
| Sampling | Integrarse directamente con la API del proveedor de LLM |
| Logging | stderr en stdio; OpenTelemetry en el resto de casos |
Hay más elementos reclasificados como obsoletos.
- Transporte HTTP+SSE — ya estaba deprecated desde
2025-03-26y ahora pasa oficialmente al estado Deprecated. Hay que migrar a Streamable HTTP (SEP-2596) "thisServer"y"allServers"deincludeContext— omite el campo o usa"none"- OAuth 2.0 Dynamic Client Registration (RFC 7591) — se sustituye por Client ID Metadata Documents. Se mantiene por compatibilidad hacia atrás para servidores de autorización que no soporten lo nuevo
Lo relevante aquí es la política de ciclo de vida de funcionalidades que se introduce junto con todo esto. Define tres estados —Active / Deprecated / Removed— y establece que una funcionalidad obsoleta permanece en la especificación un mínimo de 12 meses antes de ser candidata a eliminación. Pero no es incondicional: si se aplica la excepción de eliminación acelerada (expedited-removal) de la política, ese plazo baja a un mínimo de 90 días, por ejemplo cuando existe un riesgo de seguridad activo. Es decir, estas funcionalidades no van a desaparecer en la próxima revisión, pero no hay que leerlo como “tengo un año garantizado”.
10. Caché y enrutado — cambios discretos con mucho impacto práctico
No aparecen en la lista de cambios mayores, pero afectan de inmediato a la implementación real.
Los resultados de listado pasan a ser cacheables (SEP-2549). Los resultados de tools/list,
prompts/list, resources/list, resources/read y resources/templates/list incorporan de forma
obligatoria ttlMs y cacheScope. ttlMs es una pista de frescura (en milisegundos) y
cacheScope toma los valores "public" / "private" para determinar si una caché intermedia puede
compartir el resultado. Sirve para reducir el polling.
Ciertas cabeceras de petición pasan a ser obligatorias (SEP-2243). En un POST de Streamable HTTP
hay que enviar obligatoriamente Mcp-Method y Mcp-Name. El objetivo es poder enrutar sin parsear el
cuerpo. Si te tocaba distinguir tráfico MCP delante de un reverse proxy o un WAF, ahora basta con
mirar las cabeceras.
tools/list debería (SHOULD) devolverse en un orden determinista. El motivo es el caché del
cliente y la tasa de aciertos del caché de prompts del LLM. Si estabas sacando la lista de
herramientas de un mapa y emitiéndola en un orden distinto cada vez, añade una ordenación: es muy
probable que estuvieras invalidando el caché de prompts en cada llamada.
Se estandariza el contexto de trazas de OpenTelemetry (SEP-414). Se propagan traceparent,
tracestate y baggage dentro de _meta.
11. Renumeración de los códigos de error
Se ha establecido una política de asignación de códigos de error que divide así el rango de errores de servidor de JSON-RPC.
-32000~-32019— Zona definida por la implementación (se respeta lo que ya usan los SDK)-32020~-32099— Reservado por la especificación MCP
En consecuencia, algunos códigos han cambiado.
| Error | Antes | Ahora |
|---|---|---|
HeaderMismatch |
-32001 |
-32020 |
MissingRequiredClientCapability |
-32003 |
-32021 |
UnsupportedProtocolVersion |
-32004 |
-32022 |
| Recurso no encontrado | -32002 |
-32602 (Invalid Params) |
Hay un matiz que se malinterpreta con facilidad: esos tres primeros códigos nacieron en la fase de borrador de esta misma revisión, así que no existen en ningún código que implemente una versión publicada anterior. Lo único que puede estar realmente incrustado en código existente es la última fila.
El cambio de “recurso no encontrado” de -32002 a -32602 (Invalid Params) responde al ajuste con
la especificación JSON-RPC. Quien implemente esta versión no debe (MUST NOT) emitir -32002, pero el
cliente debería (SHOULD) seguir aceptando el -32002 que envíen servidores de versiones
anteriores.
12. Si usas Cloudflare Workers — McpAgent queda obsoleto
El mejor ejemplo del alcance real de este cambio de especificación es Cloudflare. El 6 de agosto de
2026, con el anuncio “The next generation of MCP”, Cloudflare retiró McpAgent.
Si has leído las secciones 1 y 2, ya intuirás por qué era el desenlace natural. Hasta ahora, ejecutar
un servidor MCP en Workers hacía prácticamente imprescindibles los Durable Objects: había que
mantener la sesión identificada por Mcp-Session-Id y el stream SSE asociado a ella, así que hacía
falta algo que fijara las peticiones a la misma instancia. Al desaparecer la sesión, desaparece esa
premisa.
Cloudflare lo expresa así:
This release of the MCP 2026-07-28 specification … removes the need for
McpAgent. While Durable Objects remain the right primitive when an application itself needs state, MCP itself no longer requires a Durable Object to speak the protocol.
Según la documentación, el estado actual de McpAgent es “deprecated and feature-frozen”. No es
que hoy mismo deje de funcionar, pero no recibirá funcionalidades nuevas. El reemplazo es
createMcpHandler, descrito oficialmente como algo que “crea un handler stateless de peticiones
MCP invocable a partir de una factoría de servidores del MCP SDK v2”.
export default {
fetch(request, env, ctx) {
return createMcpHandler(createServer)(request, env, ctx);
},
} satisfies ExportedHandler;
Ni clase de Durable Object ni enrutado de sesiones. En cada petición se crea el servidor, se atiende y se acabó.
Aquí hay otro punto fácil de malinterpretar: no es que los Durable Objects hayan dejado de ser necesarios. Lo que ha dejado de ser necesario es el DO “para hablar el protocolo MCP”. Si tu propia aplicación necesita estado, el DO sigue siendo la herramienta adecuada. La guía oficial señala exactamente esa distinción:
Store cross-request data behind an authenticated handle in a Durable Object, D1, KV, or R2 rather than an MCP session ID.
Es justo el “handle explícito emitido por el servidor” de la sección 1. El estado sigue viviendo en DO, D1, KV o R2; lo que cambia es que la llave que apunta a ese estado deja de ser el ID de sesión MCP y pasa a ser un handle autenticado.
La carga de migración tampoco cae toda de golpe. Cloudflare aclara que el endpoint /mcp “acepta
tanto el nuevo protocolo como las peticiones stateless de clientes Streamable HTTP de 2025”. Aunque
te queden usuarios con clientes antiguos, puedes migrar el servidor primero.
Con los costes conviene ser prudente. Cloudflare solo empleó una expresión cualitativa —el ahorro que corresponde a tener menos piezas móviles— y no ofreció ninguna cifra concreta de reducción. Que quitar instancias de DO va en la dirección de abaratar la factura es evidente, pero no es algo a lo que acercarse esperando un porcentaje.
13. Checklist de migración
Si tienes un servidor en producción, repásalo en orden.
- Implementar
server/discover— obligatorio; sin él, incumples la especificación - Eliminar el código que depende de
Mcp-Session-Id→ handles emitidos por el servidor como argumentos de herramienta - Eliminar los handlers de
initialize/notifications/initialized - Leer la versión de protocolo y las capacidades del cliente desde
_metaen cada petición - Añadir
resultType: "complete"a todos los resultados -
resources/subscribeyunsubscribe→subscriptions/listen - Peticiones iniciadas por el servidor (
roots/list,sampling/createMessage,elicitation/create) → MRTR - Eliminar los handlers de
pingylogging/setLevel - Añadir una guarda para no enviar
notifications/messageen peticiones sinlogLevel - Añadir
ttlMsycacheScopea los resultados de listado - Ordenar
tools/list(tasa de aciertos del caché de prompts) - Revisar las constantes de códigos de error
- Si usas el transporte HTTP+SSE, migrar a Streamable HTTP
- Si usas tasks, migrar a la extensión oficial —
tasks/result→ polling contasks/get, nuevotasks/update, se eliminatasks/list - Autorización: validar (MUST) el
issde la respuesta de autorización, y keyear las credenciales por issuer sin reutilizarlas (MUST NOT) en otro servidor de autorización - Si usas DCR, especificar
application_typey, mejor aún, plantear la migración a Client ID Metadata Documents - En Cloudflare Workers,
McpAgent→createMcpHandler, con el estado detrás de un handle autenticado
Si desarrollas un cliente, añade además esto.
- Tratar como
"complete"las respuestas sinresultType(compatibilidad con servidores antiguos, MUST) - Implementar el bucle de reintento MRTR — devolver
requestStatetal cual, y usar en el reintento uniddistinto al de la petición original - Dejar de esperar reanudación SSE — si se corta el stream, reemitir con un nuevo ID de petición
Conclusión
Los cuatro SDK de Tier 1 —TypeScript, Python, Go y C#— ya hablan 2026-07-28 desde el momento del
lanzamiento. El SDK de Rust lo soporta en beta. El propio anuncio oficial reconoce que “para quienes
dependían de identificadores de sesión hay cierto coste de migración”.
Aun así, la dirección es inequívoca: eliminar el estado implícito de la conexión y poner todo de forma explícita dentro de cada petición. Si ejecutas servidores MCP en entornos donde las instancias aparecen y desaparecen —edge o serverless—, este cambio no es una pérdida, es una ganancia: ya no hay motivo para seguir arrastrando un almacén de sesiones.
Como el periodo de deprecación es de 12 meses como mínimo, no hace falta reescribirlo todo hoy mismo.
Pero server/discover es la excepción: no es una deprecación, sino un nuevo requisito
obligatorio, así que si quieres decir que soportas la nueva especificación, tienes que añadirlo ya.