Ir al contenido

MCP 2026-07-28: qué cambia y por qué

Alessandro Mengoli
Series: Model Context Protocol · 3

Desde su introducción a finales de 2024, Model Context Protocol ha tenido una adopción rápida y constante. Como parte de su evolución, y en respuesta a las peticiones recibidas, la versión 2026-07-28 introdujo cambios importantes.

En los artículos anteriores de la serie vimos qué es MCP y cómo construir un servidor en .NET (ambos en italiano). Aquí veremos qué ha cambiado y por qué.

El paso de un modelo con estado a uno sin estado

El cambio más importante es el paso de un modelo con estado a uno sin estado. Todos los demás cambios, grandes y pequeños, se derivan de él.

Al principio, el protocolo se diseñó principalmente para conectar procesos locales mediante una tubería, donde cliente y servidor pueden escribir en cualquier momento. Esa simetría no existe en HTTP: el cliente habla y el servidor responde. Para permitir también la comunicación del servidor al cliente, se introdujeron mecanismos específicos: una sesión que permanecía abierta, identificada mediante Mcp-Session-Id, por la que el servidor podía enviar mensajes cuando los necesitara.

Esto tiene dos costes. El primero es la necesidad de mantener el estado. El segundo, más molesto, es que la solicitud del cliente queda vinculada a la instancia concreta del servidor que abrió la sesión. Esto dificulta el escalado: el balanceador de carga debe enviar siempre al mismo cliente a la misma instancia y, si esta falla, la sesión desaparece con ella.

La nueva revisión cambia este flujo para adaptarse mejor a HTTP y, sobre todo, mejorar la escalabilidad y la fiabilidad.

MRTR: Multi Round-Trip Requests

Si el servidor necesita información del cliente, simplemente responde: en su respuesta pide lo que necesita y termina el intercambio. Una vez obtenida la información, el cliente vuelve a llamar al servidor con la misma solicitud, ampliada con la respuesta.

A la izquierda, una llamada HTTP permanece abierta mientras el servidor consulta al usuario; a la derecha, dos llamadas independientes están vinculadas mediante requestState

Veamos un ejemplo concreto. La herramienta close_ticket cierra un ticket de soporte, pero primero pide confirmar el motivo. Es el caso más sencillo que requiere una interacción a mitad de la llamada, y precisamente donde divergen las dos revisiones.

En la ronda 1, el cliente llama a tools/call. El servidor todavía no conoce el motivo del cierre, así que no puede terminar el trabajo. Responde con resultType: input_required, la pregunta que debe trasladarse al usuario y un requestState. La llamada HTTP termina aquí: el servidor libera la conexión, el controlador finaliza y no queda nada en memoria. El requestState es el único vínculo entre las dos rondas. Lo genera el servidor y el cliente lo devuelve sin modificarlo.

En la ronda 2, el cliente repite la misma llamada tools/call, añadiendo el requestState recibido y la respuesta del usuario dentro de inputResponses. Esta vez el servidor tiene todo lo necesario y termina con resultType: complete.

El campo clave es resultType: input_required significa «me falta algo, vuelve a llamarme»; complete indica que el intercambio ha terminado.

La diferencia entre los dos modelos se aprecia enseguida en las trazas de la misma herramienta, ejecutada primero con una sesión y después con MRTR.

Traza del modelo antiguo con sesión: initialize, canal GET abierto, elicitation anidada y DELETE final

Con sesión: 26 spans, profundidad 8. Se ven initialize, elicitation/create anidada dentro de tools/call y el DELETE final que cierra la sesión. Entre medias hay un GET / que dura por sí solo 0,15 segundos en una traza de 0,37 segundos: es el canal del servidor al cliente, abierto durante toda la sesión.

Traza de MRTR: server/discover, tools/list y dos llamadas tools/call al mismo nivel, sin GET ni DELETE

Con MRTR: 19 spans, profundidad 4. Las dos llamadas tools/call están al mismo nivel, no una dentro de la otra. No hay barra GET ni DELETE.

La diferencia de profundidad muestra el cambio de enfoque: antes una llamada contenía una conversación; ahora hay dos llamadas independientes.

Adiós al intercambio inicial

Como parte del paso a un modelo sin estado, también se ha eliminado el mecanismo de inicialización: el intercambio initialize/notifications/initialized y Mcp-Session-Id dejan paso a solicitudes independientes.

El intercambio inicial cumplía dos funciones, y cada una ha seguido un camino distinto.

La información del cliente — versión del protocolo, capacidades e identidad — servía para que el servidor supiera con quién estaba hablando. Ahora viaja en cada solicitud dentro de _meta, bajo tres claves:

io.modelcontextprotocol/protocolVersion
io.modelcontextprotocol/clientCapabilities
io.modelcontextprotocol/clientInfo

Se trata, literalmente, del contenido de initialize repetido en cada solicitud. Y estos campos son obligatorios: si falta alguno, el servidor rechaza la solicitud.

{"error":{"code":-32602,"message":
  "Requests using protocol version '2026-07-28' must include
   '_meta/io.modelcontextprotocol/protocolVersion'."}}

La información del servidor — serverInfo y serverCapabilities — permitía al cliente saber qué podía hacer el servidor. Ahora es una solicitud ordinaria, server/discover, y es opcional: un cliente que ya sabe qué herramienta necesita puede omitirla e ir directamente a tools/call.

Se pasa así de una negociación, en la que ambas partes llegaban a un acuerdo y lo recordaban, a una declaración repetida en cada solicitud.

👉 Un consejo práctico: con el SDK de .NET, una solicitud GET al endpoint de un servidor sin estado devuelve 405 Method Not Allowed, porque en ese modo ni siquiera se registran las rutas GET y DELETE. La especificación no garantiza este comportamiento, pero es una forma rápida de averiguar desde fuera cómo funciona un servidor.

Los demás cambios

  • Enrutamiento basado en cabeceras. Se introducen Mcp-Method y Mcp-Name. Como todas las solicitudes son POST al mismo endpoint, antes un proxy tenía que leer y analizar el cuerpo JSON para enrutarlas. Ahora le bastan dos cabeceras.
  • Listas que se pueden almacenar en caché. Las respuestas de tools/list, prompts/list, resources/list y resources/read incluyen ahora ttlMs y cacheScope: el cliente sabe durante cuánto tiempo puede guardarlas y con qué alcance. Sin depender de una sesión, la caché resulta posible.
  • Roots y Sampling en desuso. Eran solicitudes iniciadas por el servidor hacia el cliente. Seguirán funcionando durante al menos doce meses, pero las nuevas implementaciones deberían evitar adoptarlas.
  • Logging en desuso. Era una notificación, no una pregunta a la espera de respuesta, así que ni siquiera puede utilizar MRTR. La solución pasa a ser OpenTelemetry; si quieres profundizar, consulta la serie dedicada en este blog.
  • Elicitation sigue vigente. No se ha dejado de usar: solo cambia el mecanismo de comunicación y se convierte en el principal caso de uso de MRTR.
  • Autorización. También hay novedades aquí, pero merecen un artículo propio.

Ventajas, inconvenientes y aspectos a vigilar

Como ocurre con cualquier cambio, hay ventajas, inconvenientes y detalles que requieren atención.

El sistema escala mejor, pero las rondas adicionales aumentan la latencia: una interacción que antes cabía en una llamada ahora necesita dos. En una red local pueden ser milisegundos; en una conexión lenta, bastante más.

Sobre todo, debemos prestar atención a cómo escribimos el servidor, porque ahora hay que gestionar la idempotencia.

⚠️
Atención: las rondas 1 y 2 son dos llamadas a la misma herramienta con los mismos argumentos. Todo lo que hace la herramienta antes de pedir la información que le falta se ejecuta dos veces. Los efectos secundarios deben trasladarse al punto en que ya dispone de todo lo necesario, o protegerse con una clave de deduplicación.

Conclusión

MCP ha pasado de «el servidor puede volver a llamar al cliente en mitad de una solicitud» a «el servidor responde y se olvida de ti». El fin de las sesiones, las nuevas cabeceras, las listas que se pueden almacenar en caché y las funciones en desuso se derivan de esta decisión.

El precio son algunas rondas adicionales y la responsabilidad de gestionar la idempotencia. A cambio, el servidor puede situarse detrás de un balanceador de carga corriente sin una configuración especial y reiniciarse sin efectos drásticos sobre las conversaciones en curso.

En el próximo artículo pasaremos a la práctica: cómo escribir en .NET un servidor para la nueva revisión y migrar los antiguos (en italiano). Actualizar el paquete compila sin avisos, pero después falla en tiempo de ejecución.

Recursos útiles

Este artículo se ha escrito o traducido con ayuda de inteligencia artificial.