---
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, el handshake que negocia capacidades y los dos transportes (stdio y HTTP). Un análisis a fondo desde la especificación oficial."
date: "2026-07-17T11:00:00.000Z"
category: "Inteligencia Artificial"
tech_article: true
seo_title: "MCP por dentro: arquitectura, JSON-RPC y handshake"
seo_description: "Cómo funciona MCP por dentro: modelo host-cliente-servidor, JSON-RPC 2.0, el handshake de capacidades y los transportes stdio y HTTP. Explicado desde la spec oficial."
author:
  name: "angel cruz"
  picture: "https://angelcruzdevcdn.nyc3.cdn.digitaloceanspaces.com/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, después de un handshake donde cliente y servidor negocian qué sabe hacer cada uno.** 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/).

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

## El handshake: negociar capacidades

Toda conexión arranca con un `initialize`. El cliente se presenta, dice qué versión del protocolo habla y qué sabe hacer:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-11-25",
    "capabilities": { "elicitation": {} },
    "clientInfo": { "name": "example-client", "version": "1.0.0" }
  }
}
```

El servidor responde con su propia versión y sus capacidades:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-11-25",
    "capabilities": {
      "tools": { "listChanged": true },
      "resources": {}
    },
    "serverInfo": { "name": "example-server", "version": "1.0.0" }
  }
}
```

Y el cliente cierra el handshake con una notificación de que está listo:

```json
{ "jsonrpc": "2.0", "method": "notifications/initialized" }
```

Ese intercambio hace tres cosas: **negocia una versión compatible** del protocolo (el campo `protocolVersion`, una cadena de texto con la fecha), **descubre capacidades** (cada lado declara qué primitivas soporta, para no intentar operaciones que el otro no entiende) e **intercambia identidad** (nombre y versión, para depurar). Si no hay una versión compatible, la conexión se corta.

## 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` en el handshake. 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**, y son las que permiten servidores más ricos:

- **Sampling:** el servidor puede pedirle al host una completion del modelo (`sampling/createMessage`). Así el autor del servidor usa un LLM sin meter el SDK de ningún proveedor: se mantiene independiente del modelo.
- **Elicitation:** el servidor pide información o confirmación al usuario (`elicitation/create`).

## 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.
- **El handshake de capacidades** explica el bug más común: si una tool "no aparece", muchas veces es que no se declaró o no se negoció en el `initialize`.
- **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.

### ¿Qué se negocia en el handshake de MCP?

En el `initialize` se negocian tres cosas: la versión del protocolo (para que sean compatibles), las capacidades (qué primitivas soporta cada lado) y la identidad de cliente y servidor. Si no hay versión común, la conexión se cierra.

### ¿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. Gracias a la primitiva de sampling, incluso un servidor puede pedir completions sin acoplarse a ningún proveedor.

## Cierre

MCP no es magia: es JSON-RPC 2.0 sobre un transporte, con un handshake que negocia qué sabe hacer cada lado y unas pocas primitivas bien definidas. Esa simpleza es justo lo que lo hace universal. 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).

## Fuente

- [Model Context Protocol, Architecture overview](https://modelcontextprotocol.io/docs/concepts/architecture) (especificación oficial): capas de datos y transporte, participantes, ciclo de vida y primitivas. Ejemplos JSON-RPC de la propia spec.

---

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