MCP por dentro: cómo funciona el protocolo que conecta agentes y herramientas

Le conectas un servidor MCP a Claude Code y, de pronto, el agente consulta tu base de datos o lee tus docs. Parece magia, pero no lo es. Por debajo hay un protocolo sorprendentemente simple: mensajes JSON-RPC 2.0 que viajan por un transporte, donde cada petición declara qué versión del protocolo habla y el servidor la acepta o la rechaza. Si todavía no lo tienes claro, empieza por qué es MCP; esto es el nivel de abajo. Todo lo que sigue sale de la especificación oficial.
Actualizado a la revisión
2026-07-28, que desde julio de 2026 es la versión vigente del protocolo. Cambia lo más básico que tenía MCP: el handshakeinitializedesapareció y el núcleo pasó a ser stateless. Si vienes de tutoriales escritos antes, lo que sabías del ciclo de vida ya no aplica. Cubrí el porqué del cambio y a quién rompe en MCP se vuelve stateless.
Dos capas: datos y transporte
MCP se divide en dos capas, y entenderlas separadas aclara casi todo lo demás:
- Capa de datos: el protocolo JSON-RPC 2.0. Define los mensajes, el ciclo de vida de la conexión y las primitivas (tools, resources, prompts).
- Capa de transporte: cómo viajan esos mensajes (por procesos locales o por HTTP).
La capa de datos es la interna; la de transporte, la externa. La ventaja de separarlas: el mismo formato de mensaje funciona igual sin importar el transporte. Cambias de local a remoto y el JSON-RPC no cambia.
Los participantes: host, cliente y servidor
MCP sigue una arquitectura cliente-servidor con tres roles:
- Host: la aplicación de IA que coordina todo (Claude Code, Claude Desktop, VS Code).
- Cliente: por cada servidor que conectas, el host crea un cliente con una conexión dedicada.
- Servidor: el programa que entrega el contexto (tus tools, resources y prompts).
El detalle que casi nadie menciona: la relación es uno a uno. Si conectas tres servidores, el host levanta tres clientes, cada uno con su conexión aislada. Eso importa (lo retomo al final).
El transporte: local contra remoto
La spec define dos transportes, y la elección determina si el servidor es "local" o "remoto":
- stdio: comunicación por entrada/salida estándar entre procesos en la misma máquina. Sin red, sin sobrecarga. Un servidor local con stdio suele servir a un solo cliente. Es lo que usa, por ejemplo, el servidor de filesystem que Claude Desktop lanza en tu equipo.
- Streamable HTTP: POST para los mensajes cliente a servidor, con Server-Sent Events opcionales para streaming. Es para servidores remotos que atienden a muchos clientes, con autenticación por bearer token o API key (la spec recomienda OAuth). Es lo que usa un servidor alojado como el de Sentry.
La negociación: ya no hay handshake
Hasta la revisión 2025-11-25, toda conexión arrancaba con un initialize, el servidor respondía con sus capacidades y el cliente cerraba con notifications/initialized. Ese baile ya no existe.
Ahora cada petición se negocia sola. El cliente declara la versión que habla en el campo _meta, dentro de la petición que iba a hacer de todas formas:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28"
}
}
}El servidor acepta o rechaza cada petición de forma independiente. Si no soporta esa versión, responde con un UnsupportedProtocolVersionError que lista las que sí habla, y el cliente reintenta con una en común. Sobre Streamable HTTP el mismo valor viaja además en la cabecera MCP-Protocol-Version.
Esa es la consecuencia de fondo: el servidor ya no guarda estado de tu conexión. No hay sesión que establecer, ni que mantener viva, ni que perder. Cada petición se basta a sí misma.
server/discover: preguntar antes, si quieres
Sigue existiendo una forma de preguntarle a un servidor qué sabe hacer, pero es opcional para el cliente y obligatoria de implementar para el servidor:
{
"jsonrpc": "2.0",
"id": "discover-1",
"method": "server/discover",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": { "name": "ExampleClient", "version": "1.0.0" },
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}La respuesta trae supportedVersions, capabilities, el serverInfo dentro de _meta, unas instructions opcionales en lenguaje natural para el modelo, y datos de caché (ttlMs, cacheScope).
La diferencia con el viejo initialize es de obligación, no de forma: un cliente puede lanzar tools/call en frío y manejar el error de versión si aparece. Llamar a server/discover sirve para dos cosas concretas: mostrar la identidad y capacidades del servidor en una sola petición en vez de sondear con tres listados, y detectar servidores antiguos sobre stdio, donde no hay código de estado HTTP que guíe el fallback.
Un detalle que la spec subraya y conviene no olvidar: serverInfo lo declara el propio servidor y nadie lo verifica. Sirve para mostrar y depurar, no para decidir nada de seguridad.
El ciclo de una herramienta: descubrir y ejecutar
Con la conexión lista, el cliente descubre las tools con tools/list:
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }La respuesta trae un array donde cada tool tiene name (identificador único), description y un inputSchema en JSON Schema (los parámetros que espera). Con eso, el host arma un registro unificado de tools de todos los servidores y se lo ofrece al modelo.
Cuando el modelo decide usar una, el cliente la ejecuta con tools/call:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "weather_current",
"arguments": { "location": "San Francisco", "units": "imperial" }
}
}El servidor devuelve un array content (texto, imágenes, recursos) que el host inyecta de vuelta en la conversación. Ese patrón de listar y luego llamar es lo que permite catálogos dinámicos: el cliente no necesita saber de antemano qué tools existen.
Notificaciones: cambios en tiempo real
MCP no es solo pregunta-respuesta. Un servidor puede avisar cuando sus tools cambian:
{ "jsonrpc": "2.0", "method": "notifications/tools/list_changed" }Fíjate que no tiene id: es una notificación JSON-RPC, no espera respuesta. Y solo la envían los servidores que declararon "listChanged": true entre sus capacidades. Al recibirla, el cliente vuelve a pedir tools/list y actualiza lo que el modelo tiene disponible. Por eso las herramientas pueden aparecer o desaparecer en vivo, sin reiniciar nada.
No solo el servidor habla: primitivas del cliente
La spec también define primitivas que expone el cliente. Aquí es donde más se nota el recorte de la revisión 2026-07-28:
- Elicitation: el servidor pide información o confirmación al usuario (
elicitation/create). Es la que sobrevive, y hoy la única que la spec lista como capacidad del cliente. - Sampling (deprecada): el servidor podía pedirle al host una completion del modelo (
sampling/createMessage), lo que permitía escribir servidores que usan un LLM sin acoplarse a ningún proveedor. Quedó deprecada en2026-07-28, y la migración que propone la spec es cruda: integra directamente con la API del proveedor. - Roots (deprecada): pasar directorios o ficheros ahora se hace por parámetros de la tool, URIs de recurso o configuración del servidor.
También quedó deprecado Logging (a stderr en stdio, u OpenTelemetry para observabilidad) y el registro dinámico de clientes en la parte de autorización.
Deprecado no es borrado: la política de ciclo de vida garantiza al menos doce meses, así que estas piezas no pueden desaparecer antes del 28 de julio de 2027. Pero un servidor nuevo no debería adoptarlas.
Por qué esto te importa en la práctica
Saber cómo funciona por dentro te cambia la forma de depurar:
- Un cliente por servidor, con conexión dedicada: un servidor que se cae no tumba a los demás. Los aíslas mentalmente.
- La negociación por petición cambia dónde buscar cuando algo falla. Antes, un fallo de versión mataba la conexión entera al arrancar y lo veías enseguida. Ahora una petición puede fallar con
UnsupportedProtocolVersionErrormientras el resto funciona, así que el síntoma es parcial y más difícil de leer. - El núcleo stateless explica por qué los servidores MCP encajan hoy en entornos serverless, donde una sesión pegada a un proceso era justamente el problema.
- stdio contra HTTP explica por qué los servidores locales son instantáneos y los remotos necesitan autenticación y toleran latencia de red.
- El ciclo listar/llamar dinámico es lo que permite que los mejores servidores MCP cambien sus tools sobre la marcha.
Cuando escribes el tuyo con la guía para crear un servidor MCP, el SDK te esconde casi todo esto. Pero cuando algo no conecta, saber qué mensaje falta es la diferencia entre adivinar y arreglarlo.
Preguntas frecuentes
¿Qué protocolo usa MCP por debajo?
JSON-RPC 2.0. Cliente y servidor se mandan requests (con id), responses y notifications (sin id, no esperan respuesta). Ese mismo formato viaja igual por cualquier transporte.
¿Cuál es la diferencia entre los transportes stdio y HTTP?
stdio comunica procesos locales por entrada/salida estándar, sin red, y suele servir a un cliente (servidor "local"). Streamable HTTP usa POST más SSE opcional, sirve a muchos clientes y soporta autenticación (servidor "remoto"). La spec recomienda OAuth para los remotos.
¿Sigue existiendo el handshake initialize en MCP?
No. La revisión 2026-07-28, vigente desde julio de 2026, lo eliminó. Cada petición declara su versión en _meta y el servidor la acepta o la rechaza por separado; si no la soporta, devuelve UnsupportedProtocolVersionError con las versiones que sí habla. Para hablar con servidores de 2025-11-25 o anteriores, la spec define un modo de compatibilidad hacia atrás.
¿Cómo sabe el agente qué herramientas tiene un servidor?
Las descubre con tools/list, que devuelve el nombre, la descripción y el esquema de entrada de cada tool. Luego las ejecuta con tools/call. Si las tools cambian, el servidor puede avisar con una notificación y el cliente vuelve a listar.
¿MCP depende de un modelo de IA concreto?
No. MCP solo define el protocolo de intercambio de contexto; no dicta qué modelo usa la aplicación ni cómo. Ojo con un matiz reciente: la primitiva de sampling, que dejaba a un servidor pedir completions sin acoplarse a ningún proveedor, quedó deprecada en 2026-07-28. La migración oficial es integrar directamente con la API del proveedor, así que esa independencia hay que construirla por tu cuenta.
Cierre
MCP no es magia: es JSON-RPC 2.0 sobre un transporte, con peticiones que se bastan a sí mismas y unas pocas primitivas bien definidas. Esa simpleza es justo lo que lo hace universal, y la revisión 2026-07-28 la llevó más lejos quitando el handshake y el estado. Si quieres el panorama de entrada, lee qué es MCP; si quieres pasar del concepto al código, sigue con cómo crear tu primer servidor MCP.
Fuentes
- Especificación MCP, revisión
2026-07-28(versión vigente): protocolo base stateless, negociación por petición y extensiones. - Versioning: el estado de cada revisión y cómo se negocia la versión.
server/discover: petición y respuesta de descubrimiento, con los ejemplos JSON de la propia spec.- Registro de features deprecadas: Roots, Sampling, Logging y registro dinámico de clientes, con su ruta de migración y fecha más temprana de retirada.