Skip to main content
Glama

Dos servidores MCP, un producto, dos revisiones de protocolo

El mismo carrito de la compra, implementado dos veces: una con la antigua especificación MCP con estado (2025-11-25), otra con la nueva sin estado (2026-07-28). Ejecútalos en paralelo y observa cómo uno de ellos se cae.

El objetivo no es código funcional, sino entender por qué cambió la especificación. Cada experimento aquí está diseñado para que el fallo sea ruidoso y la razón sea visible en la red.

¿Qué es MCP? (tres frases)

MCP — el Model Context Protocol — es una forma estándar de que una aplicación de IA llame a herramientas escritas por otra persona. Sustituye N aplicaciones × M integraciones por N + M, de la misma manera que el Language Server Protocol sustituyó a cada editor que escribía su propio soporte de TypeScript. Concretamente, son mensajes JSON-RPC 2.0 con nombres de método acordados, enviados a través de stdio o HTTP.

Versión más larga, si eso pasó demasiado rápido: docs/01 — por qué existe MCP.

Related MCP server: Online Boutique AI Assistant MCP Server

Qué demuestra este repositorio

Cinco herramientas — catalog_list, cart_create, cart_add_item, cart_view, cart_checkout — con nombres idénticos en ambos servidores y lógica de negocio idéntica en un paquete compartido cart-core que no sabe nada de MCP. La única diferencia entre los dos servidores es la capa de protocolo, que es exactamente lo que se estudia.

Cuatro cosas que puedes ver ocurrir:

  1. server-old no puede completar su propio handshake detrás de un balanceador de carga round-robin simple. server-new ni siquiera nota que el balanceador de carga existe.

  2. Reinicia server-old a mitad de conversación y el carrito desaparece, permanentemente, sin ninguna petición que el cliente pueda enviar para recuperarlo.

  3. Preguntar "¿confirmas este total?" le cuesta a server-old un socket abierto durante todo el tiempo de pensamiento humano (1522ms medidos). server-new lo hace en dos peticiones independientes, 4ms + 15ms, y puede terminar en una máquina distinta de la que empezó.

  4. Orden estable de listas y pistas de caché — y la aritmética que muestra por qué un .sort() que falta vale unos $4,200/año.

Los servidores están deliberadamente no refactorizados para compartir código de protocolo. Hay duplicación entre ellos a propósito, para que puedas leer cada uno de principio a fin y compararlos.

Asegúrate de ver la red

Ambos servidores imprimen cada petición a nivel HTTP: método, ruta, todas las cabeceras MCP, el método y los parámetros JSON-RPC, qué instancia la manejó, y la respuesta incluyendo resultType. Nada queda oculto tras abstracciones del SDK. Si solo lees una cosa mientras se ejecuta un experimento, lee las líneas de registro de colores.

Requisitos previos

  • Node.js 20 o superior (desarrollado en 25.5). node -v para comprobar.

  • Un terminal que muestre color ANSI — los registros dependen mucho de ello.

  • Puertos 3000–3002, 3011, 3012 libres.

  • Sin base de datos, sin Docker, sin cuenta en la nube. El estado compartido es un archivo JSON.

Cero Python en cualquier parte de este repositorio.

Instalación

git clone <this repo>
cd mcp-server
npm install
npm run typecheck    # should print nothing and exit 0

npm install configura un workspace de npm que contiene dos generaciones del SDK de MCP a la vez. Tienen nombres de paquete diferentes, por lo que coexisten sin trucos de alias:

Paquete

Versión

Usado por

@modelcontextprotocol/sdk

1.30.0

server-old, el cliente antiguo

@modelcontextprotocol/{core,server,client,node}

2.0.0

server-new, el cliente nuevo

Los cuatro experimentos, en orden

Cada uno es un comando. Cada uno inicia y detiene sus propios servidores — no se necesita una segunda terminal. Lee el artículo enlazado después de ejecutarlo; cada uno explica lo que acabas de ver y por qué.

Orden

Comando

Qué enseña

1

npm run exp:01

Dos instancias detrás de un balanceador de carga — el servidor antiguo ni siquiera puede terminar de saludar; el nuevo no se inmuta. Empieza aquí.

2

npm run exp:02

Reinicio a mitad de conversación — dónde vivía realmente el carrito, y por qué "solo añade Redis" funciona a medias.

3

npm run exp:03

Confirmar antes del pago — 1522ms de socket retenido frente a dos peticiones de 4ms, y por qué la forma antigua nunca puede ejecutarse en serverless.

4

npm run exp:04

Pistas de caché y orden estable — demostrando aciertos de caché por contador en lugar de por cronómetro, y el argumento económico para .sort().

Luego lee los documentos de arquitectura, que unen los cuatro:

Conduciéndolo a mano

Vale la pena hacerlo al menos una vez, porque eliges el ritmo y puedes leer cada línea de registro a medida que aparece.

# Old server (port 3001)
npm run old:server
npm run client -- --target old --scenario basic
npm run client -- --target old --scenario checkout
npm run client -- --target old --scenario checkout --decline

# New server (port 3002)
npm run new:server
npm run client -- --target new --scenario basic
npm run client -- --target new --scenario checkout
npm run client -- --target new --scenario discover    # server/discover — new spec only

Dos instancias más un balanceador de carga, manualmente:

PORT=3002 INSTANCE_ID=A npm run new:server
PORT=3012 INSTANCE_ID=B npm run new:server
PORT=3000 TARGETS=http://localhost:3002,http://localhost:3012 npm run lb
npm run client -- --target new --url http://localhost:3000/mcp --scenario basic

Observa ambos terminales de servidor: el mismo id de carrito aparece en peticiones manejadas por cada uno, y a ninguno le importa.

Probándolo con curl

La forma más directa de sentir lo autodescriptiva que es una petición de la nueva especificación. Inicia npm run new:server, luego:

# A complete, valid request — note how much has to be in it
curl -s -X POST http://localhost:3002/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: catalog_list' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{
        "name":"catalog_list","arguments":{},
        "_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28",
                 "io.modelcontextprotocol/clientInfo":{"name":"curl","version":"1"},
                 "io.modelcontextprotocol/clientCapabilities":{}}}}'

Ahora rómpelo pieza a pieza y observa cómo cambia el error:

Cambio

Esperado

-H 'mcp-method: tools/list' (el cuerpo sigue siendo tools/call)

-32020 HeaderMismatch

-H 'mcp-name: cart_view'

-32020, nombrando el desacuerdo

quita la cabecera mcp-method

-32020, "la cabecera Mcp-Method requerida está ausente"

quita el bloque _meta

-32602, enumerando las claves de sobre que faltan

mcp-protocol-version: 2099-01-01 tanto en la cabecera como en _meta

-32022 Versión de protocolo no soportada

curl http://localhost:3002/mcp (un GET)

405 — el endpoint GET ha desaparecido

Haz lo mismo contra el servidor antiguo y te dirá que initialize primero.

Estructura del repositorio

packages/
  cart-core/     the actual product. zero MCP knowledge. shared by both servers.
  server-old/    MCP 2025-11-25. sessions, handshake, held-open streams.
  server-new/    MCP 2026-07-28. stateless, handles, MRTR, cache hints.
  client-demo/   both clients — one per SDK generation.
  round-robin/   ~50-line load balancer. no stickiness, on purpose.
experiments/     four runnable scripts + a write-up each.
docs/            the four architecture notes.
.cart-store/     server-new's shared state. a JSON file. delete it freely.

Orden de lectura para el código: cart-core/src/cart.ts (lo que hace el producto) → server-old/src/index.tsserver-new/src/index.ts. El código a nivel de protocolo en ambos servidores está comentado línea por línea; la fontanería no.

npm run clean elimina la salida de compilación y .cart-store.


Glosario

Términos usados a lo largo del documento, en el orden en que te morderán.

Balanceador de carga — una caja delante de varias copias idénticas de tu servidor que reparte las peticiones entrantes entre ellas. La política por defecto es round-robin: enviar cada petición a la siguiente copia de la lista. Asume que cualquier copia puede responder a cualquier petición, que es exactamente la suposición que la antigua especificación MCP rompió. Aquí es packages/round-robin, unas 50 líneas.

Sesión — una memoria del lado del servidor de un cliente, que abarca múltiples peticiones. En 2025-11-25 el servidor acuñaba un Mcp-Session-Id durante el handshake, el cliente lo repetía en cada petición, y el servidor lo usaba como clave en un mapa en memoria. El id de sesión es un puntero al heap de un proceso, que es de donde viene todo el problema.

Sin estado — el servidor no guarda nada entre peticiones. Cada petición lleva todo lo necesario para atenderla. Observa lo que esto no significa: todavía hay un carrito, y todavía se almacena. Lo que desaparece es el estado mantenido en un proceso concreto, implícitamente, claveado por conexión. El estado de la aplicación en una base de datos compartida es perfectamente compatible con un protocolo sin estado.

Sesión fija (afinidad de sesión) — configurar el balanceador de carga para que todas las peticiones de un cliente vuelvan a la misma copia del servidor, normalmente haciendo hash de una cookie o una cabecera. El workaround estándar para un protocolo con estado. Funciona, y te cuesta una distribución uniforme de la carga, despliegues sin dolor, autoescalado útil, y un balanceador de carga que no necesita entender tu protocolo de aplicación. Lista de costes en docs/02.

Elicitación — un servidor que hace una pregunta al usuario final a mitad de operación ("el total es $180.36, ¿confirmas?"). En la antigua especificación, el servidor enviaba su propia petición al cliente a través de un stream abierto y se bloqueaba dentro del manejador de la herramienta mientras un humano pensaba en ello. Esa única característica requería un proceso vivo, un socket abierto, y enrutamiento garantizado de vuelta a la misma caja.

MRTR (Multi Round-Trip Requests) — cómo hace 2026-07-28 la elicitación en su lugar. El servidor devuelve un 200 normal con resultType: "input_required", las preguntas en inputRequests, y un requestState opaco firmado. Esa petición ha terminado — no se retiene nada. El cliente reúne las respuestas y envía una nueva petición (nuevo id JSON-RPC) que lleva inputResponses y el mismo requestState. El estado en vuelo viajó a través del cliente en lugar de residir en un proceso, por lo que la segunda ronda puede ser atendida por una máquina completamente distinta.

Handle — un identificador acuñado por el servidor que se devuelve como salida normal de herramienta, y luego se pasa de vuelta como argumento normal. cart_create devuelve cartId; cart_add_item lo recibe. Así es como 2026-07-28 reemplaza el estado de sesión, y la diferencia con el diseño antiguo es quién tiene la clave: el transporte, invisiblemente, frente al cliente, en un valor que el modelo puede leer y pasar. Advertencia: un handle por sí solo es un token de portador — necesita estar limitado a un usuario autenticado, lo cual docs/04 cubre honestamente.

Caché de prompt — los proveedores de LLM cachean el prefijo de un prompt: si envías los mismos bytes iniciales de nuevo, el proveedor reutiliza su estado calculado en lugar de reprocesar esos tokens, a aproximadamente una décima del precio de entrada. Dos propiedades lo hacen frágil: la coincidencia es en bytes exactos, y es posicional desde el principio. Así que si tu lista de herramientas o catálogo está en el prefijo y dos entradas intercambian posiciones, pierdes el descuento en cada token después del intercambio. Por eso 2026-07-28 dice que los servidores DEBERÍAN devolver listas en un orden determinista, y por qué listProducts() ordena por un id único en lugar de por nombre o precio — una clave única da un orden total sin empates que una implementación de ordenación pueda resolver de forma diferente. Ejemplo trabajado, con precios: experimento 04.


Si solo recuerdas tres cosas

  1. "Sin estado" no significa "sin estado" — significa sin estado anclado a un proceso. El carrito sigue existiendo. Se movió a un lugar al que cualquier instancia puede llegar.

  2. Las sesiones fijas fueron una solución real con costes reales, y uno de esos costes fue hacer que tu infraestructura analizara tu protocolo de aplicación.

  3. MRTR, no la ausencia de estado, es lo que desbloqueó serverless. La ausencia de estado llevó a MCP detrás de un balanceador de carga. La elicitación aún necesitaba que un proceso siguiera vivo mientras un humano leía un diálogo — y eso es precisamente lo que serverless eliminó.

Maintenance

ActivityMaintained
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ritik913553/mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server