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.

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_searchyjarroba_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:
- Ya no hay saludo. Antes, el cliente empezaba con
initializey ahí se acordaba la versión. Ahora cada petición lleva su versión y las capacidades del cliente enparams._meta, con las clavesio.modelcontextprotocol/protocolVersionyio.modelcontextprotocol/clientCapabilities. - La versión va también en una cabecera HTTP,
MCP-Protocol-Version, y tiene que coincidir con la del cuerpo. - Dos cabeceras más:
Mcp-Method, con el método, en todas las peticiones; yMcp-Name, con el nombre de la herramienta, entools/call. Sirven para que un intermediario (un balanceador, una pasarela o una herramienta de monitorización) sepa qué se pide sin leer el cuerpo. - Hay que implementar
server/discover, que dice qué versiones y capacidades tiene el servidor. - 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). - Las listas se pueden guardar en caché:
tools/listllevattlMs, cuánto tiempo vale la respuesta, ycacheScope,"public"si no tiene nada de un usuario concreto. - Desaparece
ping, y también las sesiones (Mcp-Session-Id) y el canal de eventos por GET. - 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-32022con 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.
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:
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'"}}
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
- Especificación MCP 2026-07-28: registro de cambios: la lista completa de lo que cambia.
- Especificación MCP 2026-07-28: versiones: cómo se negocia la versión y cómo convivir con los clientes antiguos.
- Especificación MCP 2026-07-28: transporte HTTP: las cabeceras y cuándo responder 400 o 404.
- SEP-2575: la propuesta que quitó el saludo y las sesiones.
- Cliente oficial de MCP para Python: el que usamos para probar.
«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.

Te puede interesar
«Lo que funciona no se toca» y otras frases que ya no cuelan
Cuatro frases-excusa de empresa, por qué el cálculo ha cambiado con la IA, y qué hacer si estás atrapado en una organización inmovilista.
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.



Deja un comentario