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
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Saludo inicial | el primer intercambio que abre una sesión | |
| Versión del protocolo | la fecha que da nombre a una versión de las reglas | 2024-11-05 |
| Capacidad | un grupo de funciones que el servidor dice tener | tools |
| Estado | lo que recuerda el servidor: new, waiting o ready | |
| Negociar | acordar 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 pide | El servidor habla | El servidor responde |
|---|---|---|
| 2024-11-05 | 2025-03-26, 2024-11-05 | Un resultado con 2024-11-05 |
| 2025-03-26 | 2025-03-26, 2024-11-05 | Un resultado con 2025-03-26 |
| 1999-01-01 | 2025-03-26, 2024-11-05 | Error -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
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
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
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
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
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
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
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
docker exec -w /home/node/project agent node client2.jsSe 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ón | Respuesta |
|---|---|
| 1 tools/list | Error -32600, Not initialised |
| 2 initialize, 1999-01-01 | Error -32602, con las dos versiones admitidas |
| 3 initialize, 2024-11-05 | Un resultado con tools anunciadas y el nombre del servidor |
| 4 tools/list | Error -32600, aún no está listo |
| 5 tools/list | Un resultado con una lista vacía |
| 6 resources/list | Error -32601, nunca se anunció |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.