---
title: MCP sin estado: qué cambia en la especificación 2026-07-28
url: https://jarroba.com/mcp-sin-estado-especificacion-2026-07-28/
author: Ramón Invarato
date: 2026-10-27
modified: 2026-10-27
language: es
license: CC BY-NC-SA 3.0 (https://creativecommons.org/licenses/by-nc-sa/3.0/) — attribution: "MCP sin estado: qué cambia en la especificación 2026-07-28", Ramón Invarato, jarroba.com
categories: Inteligencia Artificial, Tecnologias Web
tags: agentes, API REST, IA, JSON-RPC, MCP, Model Context Protocol, php, WordPress
---

# 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_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](https://modelcontextprotocol.io/specification/2026-07-28/changelog). 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ó](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575): 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](https://jarroba.com/wp-content/uploads/2026/10/mcp-antes-y-ahora-www.jarroba.com_.svg)](https://jarroba.com/wp-content/uploads/2026/10/mcp-antes-y-ahora-www.jarroba.com_.svg)

 

## 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](https://jarroba.com/wp-content/uploads/2026/10/dos-epocas-www.jarroba.com_.svg)](https://jarroba.com/wp-content/uploads/2026/10/dos-epocas-www.jarroba.com_.svg)

 

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:

 

```php
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.

 

```php
$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:

 

```console
$ 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](https://jarroba.com/wp-content/uploads/2026/10/mcp-cabeceras-y-cuerpo-www.jarroba.com_-1.svg)](https://jarroba.com/wp-content/uploads/2026/10/mcp-cabeceras-y-cuerpo-www.jarroba.com_-1.svg)

 

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:

 

```json
{
  "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:

 

```json
{
  "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):

 

```python
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())
```

 

```text
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](https://modelcontextprotocol.io/specification/2026-07-28/changelog): la lista completa de lo que cambia.
- [Especificación MCP 2026-07-28: versiones](https://modelcontextprotocol.io/specification/2026-07-28/basic/versioning): cómo se negocia la versión y cómo convivir con los clientes antiguos.
- [Especificación MCP 2026-07-28: transporte HTTP](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http): las cabeceras y cuándo responder 400 o 404.
- [SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575): la propuesta que quitó el saludo y las sesiones.
- [Cliente oficial de MCP para Python](https://github.com/modelcontextprotocol/python-sdk): el que usamos para probar.
