---
title: "MCP por dentro: cómo funciona el protocolo que conecta agentes y herramientas"
excerpt: "Ya sabes qué es MCP. Ahora, cómo funciona por dentro: el modelo host-cliente-servidor, el protocolo JSON-RPC 2.0 que viaja por el cable, la negociación de versión por petición y los dos transportes (stdio y HTTP). Un análisis a fondo desde la especificación oficial."
date: "2026-07-17T11:00:00.000Z"
lastModified: "2026-08-11T15:00:00.000Z"
category: "Inteligencia Artificial"
tech_article: true
seo_title: "MCP por dentro: arquitectura, JSON-RPC y negociación de versión"
seo_description: "Cómo funciona MCP por dentro: modelo host-cliente-servidor, JSON-RPC 2.0, la negociación por petición que sustituyó al handshake y los transportes stdio y HTTP. Desde la spec oficial."
author:
  name: "angel cruz"
  picture: "/images/me/angel-cruz.png"
ogImage:
  url: "/images/open-graph/og-image.png"
---

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](/post/introduccion-a-mcp-model-context-protocol); esto es el nivel de abajo. Todo lo que sigue sale de la [especificación oficial](https://modelcontextprotocol.io/).

> **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 handshake `initialize` desapareció 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](/post/mcp-stateless-adios-sesiones-y-sampling).

## 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:

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

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

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

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

```json
{ "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 en `2026-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 `UnsupportedProtocolVersionError` mientras 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](/post/mejores-servidores-mcp) cambien sus tools sobre la marcha.

Cuando escribes el tuyo con la [guía para crear un servidor MCP](/post/como-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](/post/introduccion-a-mcp-model-context-protocol); si quieres pasar del concepto al código, sigue con [cómo crear tu primer servidor MCP](/post/como-crear-un-servidor-mcp).

## Fuentes

- [Especificación MCP, revisión `2026-07-28`](https://modelcontextprotocol.io/specification/2026-07-28) (versión vigente): protocolo base stateless, negociación por petición y extensiones.
- [Versioning](https://modelcontextprotocol.io/specification/versioning): el estado de cada revisión y cómo se negocia la versión.
- [`server/discover`](https://modelcontextprotocol.io/specification/2026-07-28/server/discover): petición y respuesta de descubrimiento, con los ejemplos JSON de la propia spec.
- [Registro de features deprecadas](https://modelcontextprotocol.io/specification/2026-07-28/deprecated): Roots, Sampling, Logging y registro dinámico de clientes, con su ruta de migración y fecha más temprana de retirada.

---

## Sitemap

Índice completo del sitio: [/sitemap.md](https://www.angelcruz.dev/sitemap.md)

Canónico HTML: [https://www.angelcruz.dev/post/mcp-por-dentro](https://www.angelcruz.dev/post/mcp-por-dentro)
