¿Te resulta más fácil leer con fondo crema, un tono de color, más contraste o un texto más grande? Elige tus colores y tu texto una sola vez, para todas las páginas.

Lección gratuita

MCP a mano: JSON-RPC sobre stdio

Después de esta lección: Puedo leer y escribir los mensajes que se intercambian un servidor de herramientas y su cliente.

Lección de 70 min6 comandosLaboratorio de agente de programación con IA: usa opencode para explicar y corregir código, y comprueba su trabajo

Lo que envía un servidor de herramientas

El Model Context Protocol (MCP) es una forma estándar de que un agente encuentre y llame a las herramientas de otro programa. Sus mensajes son JSON sencillo, uno por línea, por la entrada y la salida estándar. Importa porque, si sabes leer los mensajes, puedes depurar cualquier servidor de herramientas.

Un servidor de herramientas que funciona solo pero no con tu agente suele enviar un mensaje con la forma equivocada.

Al final de esta lección

Nota

Puedo leer y escribir los mensajes que se intercambian un servidor de herramientas y su cliente.

Palabras clave

PalabraQué significaEjemplo
JSON-RPCun formato de mensaje para llamar a métodos, en JSON
Peticiónun mensaje con id que espera respuesta
Notificaciónun mensaje sin id y sin respuesta
stdiola entrada y la salida estándar de un proceso
Código de errorun número que dice qué tipo de fallo es-32601

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

JSON-RPC tiene tres clases de mensaje

Una petición tiene un id, un nombre de método y parámetros opcionales. Una respuesta copia ese id y trae un resultado o un error. Una notificación no tiene id, así que nada la responde.

Por stdio, cada mensaje es una línea de JSON. El servidor escribe solo mensajes en su salida y manda sus registros por el flujo de errores.

Ejemplo resuelto: tres mensajes

ClaseEjemploRespuesta
Petición{"jsonrpc":"2.0","id":2,"method":"tools/list"}Una respuesta con id 2
Respuesta{"jsonrpc":"2.0","id":2,"result":{"tools":[]}}Ninguna
Notificación{"jsonrpc":"2.0","method":"notifications/initialized"}Ninguna

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Es la forma, no el protocolo entero

Importante

El servidor de abajo tiene la forma del protocolo real, en pequeño. Un servidor real usa el SDK oficial, que también gestiona versiones, cancelación, más tipos de contenido y muchos más métodos. Aquí no se instala nada.

Escribir el servidor de herramientas, parte 1

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const PRICES = { pan: 90, leche: 110 };' 'const TOOLS = [{ name: \"get_price\", description: \"Precio de un producto de la tienda en céntimos\", inputSchema: { type: \"object\", properties: { item: { type: \"string\" } }, required: [\"item\"] } }];' 'function send(message) { process.stdout.write(JSON.stringify(Object.assign({ jsonrpc: \"2.0\" }, message)) + \"\n\"); }' 'function reply(id, result) { send({ id: id, result: result }); }' 'function fail(id, code, text) { send({ id: id, error: { code: code, message: text } }); }' 'function text(words, isError) { return { content: [{ type: \"text\", text: words }], isError: isError }; }' > server.js"

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Escribir el servidor de herramientas, parte 2

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function call(id, params) {' '  if (params.name === \"get_price\") {' '    const price = PRICES[(params.arguments || {}).item];' '    if (price === undefined) return reply(id, text(\"Producto desconocido\", true));' '    return reply(id, text(price + \" céntimos\", false));' '  }' '  return fail(id, -32602, \"Herramienta desconocida: \" + params.name);' '}' 'function handle(msg) {' '  if (msg.id === undefined) return;' '  if (msg.method === \"initialize\") return reply(msg.id, { protocolVersion: \"2024-11-05\", capabilities: { tools: {} }, serverInfo: { name: \"tienda\", version: \"0.1\" } });' '  if (msg.method === \"tools/list\") return reply(msg.id, { tools: TOOLS });' '  if (msg.method === \"tools/call\") return call(msg.id, msg.params || {});' '  return fail(msg.id, -32601, \"Método no encontrado: \" + msg.method);' '}' >> server.js"

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Escribir el servidor de herramientas, parte 3

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'require(\"readline\").createInterface({ input: process.stdin }).on(\"line\", function (line) {' '  try { handle(JSON.parse(line)); } catch (error) { fail(null, -32700, \"Error de análisis\"); }' '});' >> server.js"

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Escribir el cliente con guion, parte 1

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const server = require(\"child_process\").spawn(\"node\", [\"server.js\"]);' 'const call = function (n, name, args) { return { id: n, method: \"tools/call\", params: { name: name, arguments: args } }; };' 'const script = [' '  { id: 1, method: \"initialize\", params: { protocolVersion: \"2024-11-05\", capabilities: {}, clientInfo: { name: \"demo\", version: \"0.1\" } } },' '  { method: \"notifications/initialized\" },' '  { id: 2, method: \"tools/list\" },' '  call(3, \"get_price\", { item: \"leche\" }),' '  call(4, \"get_price\", { item: \"kiwi\" }),' '  call(5, \"delete_all\", {}),' '  { id: 6, method: \"resources/list\" },' '];' > client.js"

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Escribir el cliente con guion, parte 2

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'let waiting = 6;' 'require(\"readline\").createInterface({ input: server.stdout }).on(\"line\", function (line) {' '  console.log(\"<- \" + line);' '  waiting -= 1;' '  if (waiting === 0) server.stdin.end();' '});' 'script.forEach(function (m) {' '  const full = Object.assign({ jsonrpc: \"2.0\" }, m);' '  console.log(\"-> \" + JSON.stringify(full));' '  server.stdin.write(JSON.stringify(full) + \"\n\");' '});' >> client.js"

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Ejecutar el cliente contra el servidor

Comando
docker exec -w /home/node/project agent node client.js

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Una salida posible

PeticiónRespuesta
initializeUn resultado con la versión del protocolo y el nombre del servidor
tools/listUn resultado que lista get_price
get_price para la lecheUn resultado con el texto 110 céntimos
get_price para un kiwiUn resultado con isError true y Producto desconocido
delete_allUn error con código -32602
resources/listUn error con código -32601

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Una sesión sigue un orden

El cliente envía primero initialize y el servidor responde con lo que admite. Después el cliente envía la notificación de que está listo. Solo entonces pide tools/list y usa tools/call.

El agente enseña al modelo las herramientas listadas. Cuando el modelo pide una, el cliente envía tools/call.

Dos clases de fallo

Un error de protocolo es una respuesta de error JSON-RPC. La petición misma estaba mal: método desconocido, herramienta desconocida o JSON ilegible. Cada uno tiene un código, como -32601 para un método que falta.

Un error de herramienta es un resultado normal con isError en true. La llamada era correcta, pero la herramienta no pudo hacerlo. El modelo lee el texto y puede probar otra cosa.

Un error frecuente

Advertencia

Mucha gente cree que todo fallo debe ser un error JSON-RPC. Una respuesta de error dice que la petición está mal formada. Una herramienta que se ejecutó y no encontró el producto debe dar un resultado con isError en true. Así el modelo ve el motivo y puede recuperarse.

Sobre este laboratorio y su código: Laboratorio de agente de programación con IA: usa opencode para explicar y corregir código, y comprueba su trabajo

¿Enseñas a una clase? Asigna este módulo a tus alumnos con guía del profesor, tareas con corrección automática y un laboratorio para cada alumno.