¿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

El saludo inicial de MCP

Después de esta lección: Puedo responder a una petición initialize y decir qué sabe hacer mi servidor.

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

Cómo responde un servidor MCP a initialize

El saludo inicial es el primer intercambio entre un cliente MCP y un servidor. El cliente envía initialize con la versión que quiere. El servidor responde con la versión que usará y lo que sabe hacer.

Importa porque un cliente solo usa lo que el servidor dice que ofrece. Un servidor que promete de más rompe al cliente.

Al final de esta lección

Nota

Puedo responder a una petición initialize y decir qué sabe hacer mi servidor.

Palabras clave

PalabraQué significaEjemplo
Saludo inicialel primer intercambio que abre una sesión
Versión del protocolola fecha que da nombre a una versión de las reglas2024-11-05
Capacidadun grupo de funciones que el servidor dice tenertools
Estadolo que recuerda el servidor: new, waiting o ready
Negociaracordar una versión que hablan los dos lados

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

El servidor elige una versión que conoce

El cliente nombra una versión. El servidor guarda una lista de las versiones que habla. Si la pedida está en la lista, el servidor responde con ella.

Si no, este servidor la rechaza con el código -32602. Pone en el error la versión pedida y su propia lista, para que el cliente pueda intentarlo de nuevo.

Ejemplo resuelto: tres clientes piden

El cliente pideEl servidor hablaEl servidor responde
2024-11-052025-03-26, 2024-11-05Un resultado con 2024-11-05
2025-03-262025-03-26, 2024-11-05Un resultado con 2025-03-26
1999-01-012025-03-26, 2024-11-05Error -32602 con las dos listas

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

Un servidor anuncia solo lo que hace

El resultado de initialize lleva las capacidades. Cada una es un grupo de métodos que el servidor tiene: tools, resources, prompts o logging.

Una capacidad puede llevar indicadores. listChanged dice que el servidor avisará al cliente cuando cambie su lista. Este servidor nunca lo hace, así que dice false. Un método de un grupo que no anunció se responde con -32601.

Un error frecuente

Advertencia

Mucha gente anuncia todas las capacidades «por si acaso». Entonces el cliente llama a métodos que el servidor no tiene. Anuncia solo lo que has escrito y probado. Anunciar algo es prometerlo.

Escribe el ayudante del modelo

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' 'async function ask(messages, sample, max) {' '  try {' '    const res = await fetch(base + \"/v1/chat/completions\", { method: \"POST\", headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" }, body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 80 }), signal: AbortSignal.timeout(30000) });' '    if (res.ok === false) throw new Error(\"estado \" + res.status);' '    const data = await res.json();' '    return { text: data.choices[0].message.content, recorded: false };' '  } catch (error) {' '    console.log(\"Muestra grabada: ningún modelo respondió (\" + error.message + \").\");' '    return { text: sample, recorded: true };' '  }' '}' 'module.exports = { ask };' > model.js"

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

Escribe el servidor, parte 1

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const SUPPORTED = [\"2025-03-26\", \"2024-11-05\"];' 'let state = \"new\";' 'function send(m) { process.stdout.write(JSON.stringify(Object.assign({ jsonrpc: \"2.0\" }, m)) + \"\n\"); }' 'function reply(id, result) { send({ id: id, result: result }); }' 'function fail(id, code, text, data) { send({ id: id, error: { code: code, message: text, data: data } }); }' > server2.js"

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

Escribe el servidor, parte 2

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function initialise(msg) {' '  const asked = (msg.params || {}).protocolVersion;' '  if (SUPPORTED.indexOf(asked) < 0) return fail(msg.id, -32602, \"Unsupported protocol version\", { requested: asked, supported: SUPPORTED });' '  state = \"waiting\";' '  reply(msg.id, { protocolVersion: asked, capabilities: { tools: { listChanged: false } }, serverInfo: { name: \"comedor\", version: \"0.2\" } });' '}' >> server2.js"

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

Escribe el servidor, parte 3

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function handle(msg) {' '  if (msg.id === undefined) {' '    if (msg.method === \"notifications/initialized\" && state === \"waiting\") state = \"ready\";' '    return;' '  }' '  if (msg.method === \"ping\") return reply(msg.id, {});' '  if (msg.method === \"initialize\") return state === \"new\" ? initialise(msg) : fail(msg.id, -32600, \"Already initialised\");' '  if (state === \"new\" || state === \"waiting\") return fail(msg.id, -32600, \"Not initialised\");' '  if (msg.method === \"tools/list\") return reply(msg.id, { tools: [] });' '  fail(msg.id, -32601, \"Method not found: \" + msg.method);' '}' 'require(\"readline\").createInterface({ input: process.stdin }).on(\"line\", function (line) {' '  let msg;' '  try { msg = JSON.parse(line); } catch (error) { return fail(null, -32700, \"Parse error\"); }' '  handle(msg);' '});' >> server2.js"

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

Escribe el cliente con guion, parte 1

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

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

Escribe el cliente con guion, parte 2

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const seen = [];' 'require(\"readline\").createInterface({ input: server.stdout }).on(\"line\", function (line) {' '  console.log(\"<- \" + line);' '  seen.push(JSON.parse(line));' '  if (seen.length === 6) 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\");' '});' >> client2.js"

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

Escribe el cliente con guion, parte 3

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'server.on(\"close\", async function () {' '  const caps = JSON.stringify(seen[2].result.capabilities);' '  const r = await ask([{ role: \"user\", content: \"En una frase, di qué puede hacer un servidor con estas capacidades: \" + caps }], \"Puede listar herramientas, y no avisa de que su lista vaya a cambiar.\", 60);' '  console.log(\"el modelo dijo: \" + r.text);' '});' >> client2.js"

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

Ejecuta el cliente contra el servidor

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

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

Qué envía el guion

Siete mensajes entran en un orden fijo. Primero una lista demasiado pronto y una versión mala. Luego una versión buena y una segunda lista demasiado pronto.

Después la notificación de listo, una lista que funciona y un método que el servidor nunca anunció.

Una llamada corta al modelo al final redacta qué significan las capacidades. Sin modelo, imprime una Muestra grabada.

Las seis respuestas

PeticiónRespuesta
1 tools/listError -32600, Not initialised
2 initialize, 1999-01-01Error -32602, con las dos versiones admitidas
3 initialize, 2024-11-05Un resultado con tools anunciadas y el nombre del servidor
4 tools/listError -32600, aún no está listo
5 tools/listUn resultado con una lista vacía
6 resources/listError -32601, nunca se anunció

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

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.