Saltar al contenido

MCP sin estado: qué cambia en la especificación 2026-07-28

La especificación MCP 2026-07-28 quita el saludo y las sesiones. Qué cambia en un servidor HTTP y cómo el de jarroba.com habla las dos versiones.

Ramón Invarato
Un núcleo de IA envía un sobre cerrado por un solo conector a una base de datos, un documento y una llave inglesa

El 28 de julio de 2026 salió una revisión del Model Context Protocol (MCP) que quita el saludo inicial y las sesiones: cada petición viaja sola, con su versión dentro. Para un servidor MCP por HTTP, eso cambia cómo se elige la versión, qué cabeceras se comprueban y qué errores se devuelven. El de jarroba.com ya habla la versión nueva y las anteriores a la vez.

El servidor MCP de jarroba.com

MCP es el protocolo con el que un asistente de IA (Claude, ChatGPT y otros) usa herramientas externas: el asistente pide la lista de herramientas de un servidor y las llama con JSON-RPC. jarroba.com tiene uno en https://jarroba.com/wp-json/jarroba/v1/mcp, para que un asistente pueda buscar artículos y leer un resumen de cada uno. Es pequeño:

  • dos herramientas, jarroba_search y jarroba_article, de solo lectura;
  • va por HTTP, dentro del propio WordPress, con una petición POST por llamada;
  • no guarda nada entre peticiones: ni sesiones, ni canal de eventos (SSE).

El cambio grande de la especificación nueva es dejar de guardar estado, y nuestro servidor ya no lo guardaba; aun así, hubo que tocar bastante.

Qué cambia en la 2026-07-28

La lista completa está en el registro de cambios de la especificación. Lo que afecta a un servidor de herramientas por HTTP:

  1. Ya no hay saludo. Antes, el cliente empezaba con initialize y ahí se acordaba la versión. Ahora cada petición lleva su versión y las capacidades del cliente en params._meta, con las claves io.modelcontextprotocol/protocolVersion y io.modelcontextprotocol/clientCapabilities.
  2. La versión va también en una cabecera HTTP, MCP-Protocol-Version, y tiene que coincidir con la del cuerpo.
  3. Dos cabeceras más: Mcp-Method, con el método, en todas las peticiones; y Mcp-Name, con el nombre de la herramienta, en tools/call. Sirven para que un intermediario (un balanceador, una pasarela o una herramienta de monitorización) sepa qué se pide sin leer el cuerpo.
  4. Hay que implementar server/discover, que dice qué versiones y capacidades tiene el servidor.
  5. Cada resultado lleva resultType ("complete" en los normales), y se recomienda que lleve también el nombre del servidor en su _meta (io.modelcontextprotocol/serverInfo).
  6. Las listas se pueden guardar en caché: tools/list lleva ttlMs, cuánto tiempo vale la respuesta, y cacheScope, "public" si no tiene nada de un usuario concreto.
  7. Desaparece ping, y también las sesiones (Mcp-Session-Id) y el canal de eventos por GET.
  8. Errores nuevos: si las cabeceras no cuadran con el cuerpo, HTTP 400 y el código -32020; si el servidor no admite la versión, HTTP 400 y -32022 con la lista de las que sí admite; un método que no existe, HTTP 404 y -32601.

Nota

La razón de fondo, según la propuesta que lo cambió: con sesiones, un servidor MCP no puede ir detrás de un balanceador normal sin atar cada cliente a una misma máquina. Sin estado, cualquier copia del servidor puede atender cualquier petición, como en cualquier otro servicio web.

Dos paneles. Hasta la 2025-11-25: el cliente saluda con initialize y manda Mcp-Session-Id en cada petición, y el balanceador tiene que mandarlo siempre al servidor B, el que guarda su sesión; los servidores A y C no le sirven. En la 2026-07-28: cada petición lleva su versión en _meta y en MCP-Protocol-Version, y el balanceador la puede mandar a cualquiera de los tres servidores

Un servidor de dos épocas

Los clientes no se actualizan todos a la vez, así que el servidor tiene que entender a los dos. La especificación lo permite y lo llama dual-era (de las dos épocas). La pregunta es cómo saber de qué época es cada petición, porque ya no hay un saludo que lo diga:

Diagrama: si la petición trae la versión en _meta, o una cabecera con una versión que no es de las antiguas, se trata como moderna; si no, como antigua, con initialize

La cabecera sola no basta para distinguirlas: los clientes de la 2025-06-18 en adelante ya mandaban MCP-Protocol-Version, después de initialize. Una petición es moderna si la versión viene en el _meta, o si la cabecera trae una versión que no es de las antiguas. La parte que decide, en PHP y simplificada, escrita como un endpoint (punto de acceso) de la API REST de WordPress:

const VERSIONES_ANTIGUAS = array( '2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05' );

$meta   = is_array( $params['_meta'] ?? null ) ? $params['_meta'] : array();
$v_meta = $meta['io.modelcontextprotocol/protocolVersion'] ?? null;
$v_cab  = $peticion->get_header( 'mcp_protocol_version' );

if ( null !== $v_meta || ( null !== $v_cab && ! in_array( $v_cab, VERSIONES_ANTIGUAS, true ) ) ) {
    return responder_moderna( $peticion, $mensaje, $v_meta, $v_cab );
}
// Si no, la de siempre: initialize, ping, tools/list y tools/call como antes.

Las herramientas son las mismas para las dos épocas; solo cambian el sobre de la respuesta y las comprobaciones.

Comprobar que las cabeceras cuadran con el cuerpo

La especificación obliga a rechazar una petición cuyas cabeceras no coincidan con el cuerpo: si un balanceador decide por la cabecera y el servidor ejecuta lo que dice el cuerpo, alguien podría colar una llamada distinta de la que el balanceador cree estar dejando pasar.

$cab_metodo = $peticion->get_header( 'mcp_method' );
if ( $cab_metodo !== $metodo ) {
    return error_de_cabeceras( null === $cab_metodo ? 'missing Mcp-Method header'
        : "Mcp-Method header '$cab_metodo' does not match body value '$metodo'" );
}

error_de_cabeceras() responde con HTTP 400 y el código -32020. Si un nombre no cabe en una cabecera HTTP (con tildes, por ejemplo), se manda codificado como =?base64?…?=, y hay que decodificarlo antes de comparar.

Así responde nuestro servidor si la cabecera dice una herramienta y el cuerpo otra:

$ curl -s -X POST https://jarroba.com/wp-json/jarroba/v1/mcp \
    -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
    -H 'MCP-Protocol-Version: 2026-07-28' \
    -H 'Mcp-Method: tools/call' -H 'Mcp-Name: jarroba_article' \
    -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"jarroba_search","arguments":{"query":"docker"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'
{"jsonrpc":"2.0","id":2,"error":{"code":-32020,"message":"Header mismatch: Mcp-Name header 'jarroba_article' does not match body value 'jarroba_search'"}}
La misma petición, partida en cabeceras y cuerpo. MCP-Protocol-Version coincide con la versión de _meta y Mcp-Method con method, marcadas en verde; Mcp-Name dice jarroba_article y el name del cuerpo jarroba_search, marcadas en rojo. El balanceador solo mira las cabeceras y el servidor ejecuta el cuerpo, así que responde HTTP 400 con el error -32020 y no ejecuta la llamada

Peligro

Estas comprobaciones solo se aplican a las peticiones modernas. Un cliente antiguo no manda Mcp-Method: si se le exigiera, dejaría de funcionar de un día para otro.

Cuando el cliente pide una versión que no tienes

En vez de fallar sin más, el servidor dice qué versiones tiene, y el cliente puede repetir la petición con una de ellas:

{
  "jsonrpc": "2.0",
  "id": 3,
  "error": {
    "code": -32022,
    "message": "Unsupported protocol version",
    "data": {
      "supported": ["2026-07-28", "2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"],
      "requested": "2099-01-01"
    }
  }
}

La misma lista sale en server/discover, que un cliente puede llamar antes de nada para elegir versión:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "supportedVersions": ["2026-07-28", "2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"],
    "capabilities": { "tools": {} },
    "instructions": "Search and read computing articles from jarroba.com…",
    "ttlMs": 86400000,
    "cacheScope": "public",
    "resultType": "complete",
    "_meta": {
      "io.modelcontextprotocol/serverInfo": { "name": "jarroba", "title": "Jarroba.com", "version": "1.1.0" }
    }
  }
}

El ttlMs de 86 400 000 son 24 horas: el cliente puede guardar esta respuesta un día entero sin volver a pedirla. tools/list lleva su propio ttlMs; el nuestro también es de 24 horas, porque las herramientas casi nunca cambian.

Probarlo con un cliente de verdad

Comprobar el servidor con nuestras propias peticiones demuestra poco: las escribimos con la misma idea de la especificación que el código. Por eso lo probamos también con el cliente oficial de MCP para Python (el paquete mcp, versión 2.3.0, que ya habla la 2026-07-28):

import asyncio
from mcp import Client

async def main():
    async with Client("https://jarroba.com/wp-json/jarroba/v1/mcp") as c:
        print("versión:", c.protocol_version)
        herramientas = await c.list_tools()
        print([t.name for t in herramientas.tools])
        r = await c.call_tool("jarroba_search", {"query": "cómo guardo pares clave valor en java", "limit": 2})
        print(r.content[0].text.splitlines()[0])

asyncio.run(main())
versión: 2026-07-28
['jarroba_search', 'jarroba_article']
1. [Map en Java, con ejemplos](https://jarroba.com/map-en-java-con-ejemplos/) — 2014-03-01 · Lenguajes de Programación

El cliente eligió la versión nueva por su cuenta, y la pregunta escrita con otras palabras encontró el artículo bueno.

Lo que aprendimos

Si tu servidor ya no guardaba estado, el cambio es pequeño, pero no es cero. A nosotros nos tocó la detección de época, las cabeceras, server/discover, el sobre de las respuestas y los errores nuevos; las herramientas no cambiaron. Lo antiguo conviene dejarlo: un servidor que solo entienda la versión nueva deja fuera a los clientes que aún no se han actualizado, como los hechos con la serie 1 del SDK de TypeScript.

Antes de empezar, mira qué versión habla tu SDK. El oficial de Python ya habla la 2026-07-28. El de TypeScript (@modelcontextprotocol/sdk) se queda en la 2025-11-25 en su última versión de la serie 1, la 1.32.2 (de octubre de 2026); la nueva viene en paquetes aparte (@modelcontextprotocol/server, en la 2.4.0), con una API distinta y zod 4: hay que migrar el código, y no basta con subir la versión del paquete. Para un servidor local (stdio), la prisa es menor: no hay balanceador ni sesiones que quitar, y los clientes siguen hablando las versiones anteriores. Se puede esperar a que la serie 1 del SDK deje de mantenerse o a que un cliente importante deje de aceptar la 2025-11-25.

Herramienta

Si quieres probar nuestro servidor desde tu asistente, la dirección es https://jarroba.com/wp-json/jarroba/v1/mcp. Es de solo lectura y no pide cuenta.

Bibliografía

«MCP sin estado: qué cambia en la especificación 2026-07-28», de Ramón Invarato en jarroba.com, con licencia Creative Commons Reconocimiento-NoComercial-CompartirIgual 3.0. Obra registrada en Safe Creative.

Ramón Invarato

Diseño y construyo plataformas de datos y sistemas inteligentes, y me motiva compartir esa experiencia aquí.

Te puede interesar

Trucos para usar la IA bien

Patrones con respaldo empírico para usar la IA: cuándo aplicar CoT, la frontera dentada de BCG, lost in the middle y cómo filtrar el ruido.

10 Jul 2026

Comentarios

Deja un comentario


Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.