Módulo 2: Arquitectura Host-Client-Server

El Client: El Conector de MCP

El Client: El Conector de MCP

Descripción de la cápsula

Ya conoces al Host — Claude Code como orquestador que decide qué servers usar y coordina todo. Pero entre el Host que decide y el Server que ejecuta hay un componente invisible: el MCP Client. Es el mesero del restaurante — no cocina ni decide el menú, pero sin él los pedidos no llegan a la cocina y los platos no llegan a la mesa.

En esta cápsula vas a entender qué es el MCP Client, cómo maneja el ciclo de vida de una conexión (desde el handshake inicial hasta la desconexión), qué protocolo usa para comunicarse, y cómo negocia capabilities con el Server. Aunque normalmente no construyes Clients (vienen integrados en el Host), entender cómo funcionan te permite diagnosticar problemas de conexión y escribir Servers que se comporten correctamente.

El Client es la capa que más se da por sentada — todo funciona bien hasta que falla. Entenderlo te prepara para esos momentos donde necesitas saber exactamente qué pasa entre el Host y tu Server.


¿Qué es un MCP Client?

Definición

Un MCP Client es el componente dentro del Host que establece y mantiene una conexión con un MCP Server usando el protocolo MCP (basado en JSON-RPC 2.0).

En términos concretos:

Claude Code (Host)
├── MCP Client 1 ←→ Filesystem Server
├── MCP Client 2 ←→ GitHub Server
└── MCP Client 3 ←→ PostgreSQL Server

Cada Client es una conexión independiente.
Cada Client mantiene su propio estado.
Cada Client negocia capabilities con SU Server.

Relación 1:1

Un detalle arquitectural importante: la relación Client-Server es 1:1. No hay un Client que habla con múltiples Servers. Cada conexión Server tiene su propio Client dedicado:

✅ Correcto:
Host
├── Client A → Server A
├── Client B → Server B
└── Client C → Server C

❌ Incorrecto (esto NO es cómo funciona):
Host
└── Client único → Server A
                 → Server B
                 → Server C

Esto garantiza aislamiento: si la conexión con Server B falla, Client A y Client C siguen funcionando normalmente.


El protocolo: JSON-RPC 2.0

Por qué JSON-RPC

MCP usa JSON-RPC 2.0 como formato de mensajes. Si has trabajado con APIs REST, JSON-RPC te parecerá familiar pero con estructura diferente:

REST:
  GET /api/files/readme.md
  → { "content": "# Mi README..." }

JSON-RPC:
  → { "jsonrpc": "2.0", "method": "tools/call", "params": {...}, "id": 1 }
  ← { "jsonrpc": "2.0", "result": {...}, "id": 1 }

Anatomía de un mensaje JSON-RPC

Cada mensaje que el Client envía al Server tiene esta estructura:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "read_file",
    "arguments": {
      "path": "/Users/dev/projects/README.md"
    }
  }
}
CampoDescripción
jsonrpcSiempre "2.0" — versión del protocolo
idIdentificador único del request (para emparejar con la respuesta)
methodQué operación ejecutar
paramsParámetros de la operación

Y la respuesta del Server:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "# Mi README\n\nEste es mi proyecto..."
      }
    ]
  }
}

El id: 1 en la respuesta coincide con el id: 1 del request — así el Client sabe qué respuesta corresponde a qué petición.


Tipos de mensajes

El protocolo MCP maneja 3 tipos de mensajes:

Tipos de mensajes MCP:
│
├── Request (Client → Server)
│   Tiene: method, params, id
│   Espera: Response
│   Ejemplo: "Ejecuta el tool read_file"
│
├── Response (Server → Client)
│   Tiene: result (éxito) o error (fallo), id
│   Responde a: un Request específico (mismo id)
│   Ejemplo: "Aquí está el contenido del archivo"
│
└── Notification (cualquier dirección, sin id)
    Tiene: method, params (NO tiene id)
    No espera respuesta
    Ejemplo: "La lista de resources cambió" (Server → Client)

La diferencia entre Request y Notification es clave: un Request espera respuesta (tiene id), una Notification es fire-and-forget (sin id).

// Request (espera respuesta)
{
  "jsonrpc": "2.0",
  "id": 42,
  "method": "tools/call",
  "params": { "name": "read_file", "arguments": { "path": "README.md" } }
}

// Notification (no espera respuesta)
{
  "jsonrpc": "2.0",
  "method": "notifications/resources/updated",
  "params": { "uri": "file:///Users/dev/data.json" }
}

Lifecycle de una conexión MCP

Vista general

El lifecycle de una conexión MCP tiene 4 fases:

Lifecycle de conexión:
│
├── 1. INITIALIZE
│   Client y Server negocian versión y capabilities
│
├── 2. INITIALIZED (notification)
│   Client confirma que la inicialización fue exitosa
│
├── 3. OPERATION (fase principal)
│   Client envía requests, Server responde
│   Puede durar minutos, horas, o toda la sesión
│
└── 4. SHUTDOWN
    Client cierra la conexión

Cada fase tiene mensajes específicos. Veámoslas en detalle.


Fase 1: Initialize

Cuando Claude Code arranca un MCP server, lo primero que hace el Client es enviar un mensaje initialize:

// Client → Server: "Hola, quiero conectarme"
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-03-26",
    "capabilities": {
      "roots": {
        "listChanged": true
      }
    },
    "clientInfo": {
      "name": "claude-code",
      "version": "1.0.0"
    }
  }
}

Este mensaje dice:

  • protocolVersion: Qué versión del protocolo MCP habla el Client
  • capabilities: Qué features soporta el Client
  • clientInfo: Quién es el Client (nombre y versión)

El Server responde con su propia información:

// Server → Client: "Hola, acepto la conexión"
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-03-26",
    "capabilities": {
      "tools": {
        "listChanged": true
      },
      "resources": {
        "subscribe": true,
        "listChanged": true
      }
    },
    "serverInfo": {
      "name": "filesystem-server",
      "version": "0.5.0"
    }
  }
}

Este response dice:

  • protocolVersion: Versión del protocolo que el Server acepta
  • capabilities: Qué features expone el Server (tools, resources, etc.)
  • serverInfo: Quién es el Server

Negociación de capabilities

La fase de initialize es una negociación. No todos los Servers exponen las mismas capabilities:

Server A (filesystem):
  capabilities:
    tools: ✅ (tiene tools como read_file, write_file)
    resources: ❌ (no expone resources)
    prompts: ❌ (no expone prompts)

Server B (database):
  capabilities:
    tools: ✅ (tiene tools como query, list_tables)
    resources: ✅ (expone resources como db://schema)
    prompts: ✅ (tiene prompt templates como analyze_table)

Server C (weather):
  capabilities:
    tools: ✅ (tiene tools como get_forecast)
    resources: ❌
    prompts: ❌

El Client registra las capabilities de su Server y le dice al Host qué puede hacer. Por eso el Host sabe qué tools están disponibles — porque cada Client le reportó las capabilities de su Server.


Fase 2: Initialized (Notification)

Después de recibir la respuesta de initialize, el Client envía una notification confirmando que todo está listo:

// Client → Server: "Todo listo, empecemos"
{
  "jsonrpc": "2.0",
  "method": "notifications/initialized"
}

Esta es una notification (sin id), no espera respuesta. A partir de este momento, la conexión está activa y el Client puede enviar requests operacionales.


Fase 3: Operation

Esta es la fase donde el Client envía requests reales al Server. Los tipos más comunes:

Operaciones disponibles según capabilities:
│
├── Tools
│   ├── tools/list → Listar todos los tools disponibles
│   └── tools/call → Ejecutar un tool específico
│
├── Resources
│   ├── resources/list → Listar resources disponibles
│   ├── resources/read → Leer un resource específico
│   └── resources/subscribe → Suscribirse a cambios
│
└── Prompts
    ├── prompts/list → Listar prompts disponibles
    └── prompts/get → Obtener un prompt específico

Ejemplo: Listar tools disponibles

// Client → Server
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list"
}

// Server → Client
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "read_file",
        "description": "Lee el contenido completo de un archivo",
        "inputSchema": {
          "type": "object",
          "properties": {
            "path": {
              "type": "string",
              "description": "Ruta al archivo"
            }
          },
          "required": ["path"]
        }
      },
      {
        "name": "write_file",
        "description": "Escribe contenido a un archivo",
        "inputSchema": {
          "type": "object",
          "properties": {
            "path": { "type": "string" },
            "content": { "type": "string" }
          },
          "required": ["path", "content"]
        }
      }
    ]
  }
}

Nota cómo cada tool tiene un inputSchema — un JSON Schema que define exactamente qué parámetros acepta. Esto permite al Host saber cómo invocar cada tool correctamente.

Ejemplo: Ejecutar un tool

// Client → Server: "Ejecuta read_file"
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "read_file",
    "arguments": {
      "path": "/Users/dev/projects/README.md"
    }
  }
}

// Server → Client: "Aquí tienes el resultado"
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "# Mi Proyecto\n\nEste proyecto es..."
      }
    ],
    "isError": false
  }
}

Ejemplo: Error en un tool

// Server → Client: "Hubo un error"
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Error: File not found: /Users/dev/nonexistent.md"
      }
    ],
    "isError": true
  }
}

El Server retorna errores dentro de result con isError: true, no como un error JSON-RPC. Esto permite que el Host presente el error de forma amigable al usuario.


Fase 4: Shutdown

Cuando Claude Code cierra sesión o remueve un MCP server, el Client cierra la conexión de forma ordenada:

Shutdown del Client:
├── Client deja de enviar requests
├── Client cierra el canal de comunicación
└── El proceso del Server termina

En la práctica con Claude Code, el shutdown ocurre cuando:

  • Cierras la sesión de Claude Code (Ctrl+C)
  • Remueves un server (claude mcp remove)
  • Claude Code detecta que el proceso del server crasheó

Transports: cómo viajan los mensajes

¿Qué es un transport?

El protocolo MCP define qué mensajes se envían. El transport define cómo viajan esos mensajes. Piensa en el protocolo como el idioma y el transport como el medio de comunicación (teléfono, email, presencial):

Transports MCP:
│
├── stdio (Standard I/O)
│   ├── Los mensajes viajan por stdin/stdout del proceso
│   ├── El server es un proceso local
│   ├── Usado por: Claude Code, Claude Desktop
│   └── El más común para desarrollo local
│
├── HTTP con SSE (Server-Sent Events)
│   ├── Los mensajes viajan por HTTP
│   ├── El server puede estar en una máquina remota
│   ├── Client → Server: HTTP POST
│   ├── Server → Client: SSE stream
│   └── Usado para: servers remotos, cloud deployments
│
└── Streamable HTTP
    ├── Evolución del transport HTTP
    ├── Soporta streaming bidireccional
    └── Usado para: comunicación avanzada

stdio en detalle

Cuando configuras un MCP server en Claude Code con npx, usas stdio:

claude mcp add filesystem -s user -- npx -y @modelcontextprotocol/server-filesystem /dir

Lo que pasa internamente:

Claude Code
│
├── Lanza proceso: npx server-filesystem /dir
│   └── Este proceso tiene stdin y stdout
│
├── Client escribe en stdin del proceso
│   → {"jsonrpc":"2.0","method":"initialize",...}\n
│
├── Server lee de stdin, procesa, escribe en stdout
│   → {"jsonrpc":"2.0","result":{...}}\n
│
└── Client lee de stdout del proceso
    → Parsea el JSON y entrega al Host
Flujo de datos con stdio:

Client  ──stdin──→  Server Process
Client  ←─stdout──  Server Process
Client  ←─stderr──  Server Process (logs/errores, no MCP)

Cada mensaje JSON va en una línea, terminada con \n. El Client y el Server parsean JSON línea por línea.

¿Cuándo usar cada transport?

stdio:
├── ✅ Server local (tu máquina)
├── ✅ Desarrollo y testing
├── ✅ Claude Code, Claude Desktop
└── ❌ Server en la nube

HTTP/SSE:
├── ✅ Server remoto (en un servidor)
├── ✅ Múltiples Hosts conectados al mismo Server
├── ✅ Deployments en producción
└── ❌ Más complejo de configurar

Para esta guía, trabajarás casi siempre con stdio. HTTP/SSE se explorará cuando llegues a deployments en producción.


Capabilities del Client

¿Qué puede hacer un Client?

El Client también declara capabilities durante la inicialización. Estas son menos conocidas pero importantes:

{
  "capabilities": {
    "roots": {
      "listChanged": true
    },
    "sampling": {}
  }
}
Capabilities del Client:
│
├── roots
│   ├── Le dice al Server qué directorios/URIs base tiene acceso
│   └── listChanged: el Client puede notificar si las roots cambian
│
└── sampling
    └── El Client puede pedir al Server que genere texto
        (usado en flujos avanzados donde el Server necesita
         que el modelo genere algo)

Roots: los directorios base

Cuando el Client envía sus roots al Server, le dice "estos son los directorios a los que tengo acceso":

// Client informa al Server sus roots
{
  "roots": [
    {
      "uri": "file:///Users/dev/projects/my-app",
      "name": "My App"
    }
  ]
}

Esto permite al Server contextualizar sus operaciones. Un Filesystem server puede saber que solo debe operar dentro de esos directorios.


Manejo de errores en el protocolo

Errores JSON-RPC

Cuando algo falla a nivel de protocolo (no a nivel de operación), el Server retorna un error JSON-RPC:

// Error de protocolo: método no encontrado
{
  "jsonrpc": "2.0",
  "id": 5,
  "error": {
    "code": -32601,
    "message": "Method not found: tools/nonexistent"
  }
}

Códigos de error estándar JSON-RPC:

Códigos de error:
├── -32700  Parse error (JSON inválido)
├── -32600  Invalid request (estructura incorrecta)
├── -32601  Method not found (método no existe)
├── -32602  Invalid params (parámetros incorrectos)
└── -32603  Internal error (error interno del server)

Errores vs resultados con isError

Es importante distinguir entre errores de protocolo y errores de operación:

Error de protocolo (error JSON-RPC):
→ El Client no pudo comunicarse con el Server
→ Ejemplo: método no existe, JSON inválido
→ Se retorna como "error" en JSON-RPC

Error de operación (result con isError):
→ El Client se comunicó bien, pero la operación falló
→ Ejemplo: archivo no encontrado, query SQL inválida
→ Se retorna como "result" con isError: true

Troubleshooting

"El server se conecta pero no lista tools"

Causa probable: El Server no declara tools en sus capabilities durante initialize.

Solución: Verifica que el Server retorne capabilities correctas:

{
  "capabilities": {
    "tools": {}
  }
}

Si falta "tools": {}, el Client no pedirá la lista de tools.

"Los mensajes no llegan al server"

Causa probable: Problema de transport (stdio). El Server no está leyendo de stdin correctamente.

Solución:

# Prueba enviar un mensaje manualmente al server
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | npx -y @modelcontextprotocol/server-filesystem /tmp

Si no obtienes respuesta, el Server no está procesando stdin correctamente.

"Error: Protocol versión mismatch"

Causa: El Client y el Server hablan versiones diferentes del protocolo MCP.

Solución: Actualiza el Server (o el Client) a una versión compatible:

# Actualizar el server
npm update -g @modelcontextprotocol/server-filesystem

# O reinstalar la última versión
npm install -g @modelcontextprotocol/server-filesystem@latest

"Connection timeout durante initialize"

Causa: El Server tarda demasiado en responder al initialize.

Solución:

# Verificar que el server arranca correctamente
npx -y @modelcontextprotocol/server-filesystem /tu/dir

# Si usa npx, instalar globalmente para arranque más rápido
npm install -g @modelcontextprotocol/server-filesystem

"El server se desconecta después de un rato"

Causa probable: El proceso del Server crashea. Puede ser por un error no manejado, memory leak, o timeout.

Solución: Revisa los logs del Server (stderr):

# Los errores del server van a stderr
# Claude Code puede mostrar estos errores en su output
# Busca mensajes de error en la sesión de Claude Code

Ejercicios

Ejercicio 1: Identificar mensajes MCP (Fácil)

Clasifica cada mensaje como Request, Response, o Notification:

// Mensaje A
{ "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "read_file" } }

// Mensaje B
{ "jsonrpc": "2.0", "id": 3, "result": { "content": [{ "type": "text", "text": "hola" }] } }

// Mensaje C
{ "jsonrpc": "2.0", "method": "notifications/resources/updated" }

// Mensaje D
{ "jsonrpc": "2.0", "id": 7, "error": { "code": -32601, "message": "Method not found" } }
Ver solución
  • Mensaje A: Request — Tiene method, params, y id. Espera una respuesta.
  • Mensaje B: Response (éxito) — Tiene result e id. Es la respuesta al Request con id 3.
  • Mensaje C: Notification — Tiene method pero NO tiene id. No espera respuesta.
  • Mensaje D: Response (error) — Tiene error e id. Es una respuesta de error al Request con id 7.

Regla rápida: Si tiene id + method → Request. Si tiene id + result/error → Response. Si tiene method pero no id → Notification.

Ejercicio 2: Escribir un mensaje initialize (Medio)

Escribe el mensaje initialize que un Client enviaría, y la respuesta que un Server de database retornaría. El Server expone tools y resources, pero no prompts.

Ver solución

Client → Server (initialize request):

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-03-26",
    "capabilities": {
      "roots": {
        "listChanged": true
      }
    },
    "clientInfo": {
      "name": "claude-code",
      "version": "1.0.0"
    }
  }
}

Server → Client (initialize response):

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-03-26",
    "capabilities": {
      "tools": {
        "listChanged": true
      },
      "resources": {
        "subscribe": true,
        "listChanged": true
      }
    },
    "serverInfo": {
      "name": "database-server",
      "version": "1.0.0"
    }
  }
}

Puntos clave:

  • El Server NO incluye prompts en capabilities (no las soporta)
  • Ambos usan la misma protocolVersion
  • El id: 1 coincide en request y response

Ejercicio 3: Trazar el lifecycle completo (Medio)

Dibuja la secuencia completa de mensajes cuando Claude Code conecta con un Filesystem MCP server y el usuario pide "lee el archivo package.json". Incluye desde initialize hasta la respuesta final.

Ver solución
Fase 1: Initialize
───────────────────
Client → Server:  initialize (id:1)
                  { protocolVersion, capabilities, clientInfo }

Server → Client:  response (id:1)
                  { protocolVersion, capabilities: {tools: {}}, serverInfo }

Client → Server:  notifications/initialized
                  (sin id, notification)

Fase 2: Discover
───────────────────
Client → Server:  tools/list (id:2)

Server → Client:  response (id:2)
                  { tools: [read_file, write_file, ...] }

→ Host registra los tools disponibles

Fase 3: Operation (usuario pide "lee package.json")
───────────────────
Host decide: necesita read_file → Filesystem server

Client → Server:  tools/call (id:3)
                  { name: "read_file", arguments: { path: "package.json" } }

Server → Client:  response (id:3)
                  { content: [{ type: "text", text: "{ \"name\": \"my-app\"... }" }] }

→ Host presenta el contenido al usuario

Total: 6 mensajes (3 del Client, 3 del Server)

Ejercicio 4: Diagnosticar errores por el mensaje (Medio)

Dado este intercambio de mensajes, identifica qué salió mal y en qué fase del lifecycle:

// Client → Server
{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-03-26", "capabilities": {}, "clientInfo": { "name": "claude-code", "version": "1.0.0" } } }

// Server → Client
{ "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2024-11-05", "capabilities": { "tools": {} }, "serverInfo": { "name": "old-server", "version": "0.1.0" } } }
Ver solución

Problema: Posible mismatch de versión de protocolo.

El Client envía protocolVersion: "2025-03-26" pero el Server responde con protocolVersion: "2024-11-05".

Análisis:

  • El Client quiere usar la versión más reciente del protocolo
  • El Server solo soporta una versión anterior
  • Dependiendo de la implementación, esto puede:
    • Funcionar si las versiones son backward-compatible
    • Fallar si hay breaking changes entre versiones
    • Causar comportamiento inesperado si el Client usa features que el Server no soporta

Fase: Initialize (Fase 1 del lifecycle)

Solución: Actualizar el Server a una versión que soporte el protocolo más reciente:

npm update -g @modelcontextprotocol/server-old-server

Ejercicio 5: Comparar stdio vs HTTP/SSE (Difícil)

Describe 3 escenarios donde usarías stdio y 3 donde usarías HTTP/SSE para el transport de un MCP server. Justifica cada elección.

Ver solución

Escenarios para stdio:

  1. Server de filesystem local

    • Accede a archivos en tu máquina
    • No necesita red
    • stdio es directo y rápido
    • Justificación: la operación es local, no hay beneficio en HTTP
  2. Server de development/testing

    • Estás desarrollando un MCP server nuevo
    • Necesitas ciclo rápido de desarrollo
    • stdio no requiere puerto ni configuración de red
    • Justificación: simplicidad para desarrollo
  3. Server con datos sensibles

    • Accede a secrets locales o archivos privados
    • No quieres exponer un endpoint HTTP
    • stdio mantiene todo dentro de la máquina
    • Justificación: seguridad — sin superficie de ataque de red

Escenarios para HTTP/SSE:

  1. Server de database en la nube

    • La database está en AWS/GCP/Azure
    • El server necesita correr cerca de la database (menos latencia)
    • Múltiples developers quieren usar el mismo server
    • Justificación: el server no puede correr localmente
  2. Server compartido por equipo

    • Un MCP server de Jira que todo el equipo usa
    • Un server centralizado evita que cada developer configure el suyo
    • Justificación: compartir un server entre múltiples Hosts
  3. Server con CI/CD integration

    • El server es parte de un pipeline de deployment
    • Se despliega como un servicio web
    • Otros sistemas (no solo Claude Code) necesitan accederlo
    • Justificación: integración con infraestructura existente

Ejercicio 6: Escribir una secuencia de tools/call (Difícil)

Escribe los mensajes JSON-RPC completos para esta secuencia: el usuario quiere "buscar archivos .py y leer el más grande". Necesitas 2 calls: primero search_files, luego read_file con el resultado.

Ver solución
// Call 1: Buscar archivos .py
// Client → Server
{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "tools/call",
  "params": {
    "name": "search_files",
    "arguments": {
      "path": "/Users/dev/project",
      "pattern": "*.py"
    }
  }
}

// Server → Client
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Found 3 files:\n- main.py (2.4 KB)\n- utils.py (5.1 KB)\n- test_main.py (1.8 KB)"
      }
    ],
    "isError": false
  }
}

// Host procesa: utils.py es el más grande (5.1 KB)

// Call 2: Leer el archivo más grande
// Client → Server
{
  "jsonrpc": "2.0",
  "id": 11,
  "method": "tools/call",
  "params": {
    "name": "read_file",
    "arguments": {
      "path": "/Users/dev/project/utils.py"
    }
  }
}

// Server → Client
{
  "jsonrpc": "2.0",
  "id": 11,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "import os\nimport json\n\ndef parse_config(path):\n    ..."
      }
    ],
    "isError": false
  }
}

Puntos clave:

  • Los id son secuenciales (10, 11) para distinguir cada request
  • El Host (no el Client) decide que utils.py es el más grande
  • El segundo call usa información del resultado del primero
  • Ambos calls usan el mismo Client → mismo Server

Resumen

En esta cápsula aprendiste:

  • El MCP Client es el componente dentro del Host que maneja la comunicación con un Server específico
  • La relación Client-Server es 1:1 — cada Server tiene su Client dedicado
  • MCP usa JSON-RPC 2.0 como formato de mensajes, con 3 tipos: Request, Response, Notification
  • El lifecycle tiene 4 fases: Initialize → Initialized → Operation → Shutdown
  • Durante initialize, Client y Server negocian versión del protocolo y capabilities
  • Los transports definen cómo viajan los mensajes: stdio (local) o HTTP/SSE (remoto)
  • Los errores pueden ser de protocolo (error JSON-RPC) o de operación (result con isError)
  • Entender el Client te permite diagnosticar problemas de conexión entre Host y Server

Próxima cápsula: El Server: el proveedor — la capa que tú vas a construir. Cómo un Server expone capabilities, responde requests, y maneja estado.


Recursos adicionales

  1. MCP Protocol Specification - Especificación completa del protocolo incluyendo todos los mensajes
  2. JSON-RPC 2.0 Specification - El protocolo base que MCP usa
  3. MCP Transports - Documentación oficial de transports (stdio, HTTP/SSE)
  4. MCP Connection Lifecycle - Documentación del lifecycle de conexión
  5. MCP TypeScript SDK — Client - Código fuente del Client en TypeScript
  6. MCP Inspector - Herramienta para visualizar mensajes MCP en tiempo real

Siguiente cápsula: El Server: el proveedor — cómo un MCP Server expone capabilities, responde a requests, y maneja su estado interno. Esta es la capa que construirás en los módulos 4-6.