E6 · Comprobar el kit
5 minCorre el bloque comprobar tools.
Listo cuando: node teste-ferramentas.mjs → 0 falhas y saída: 0.
Clase 3 · 20 min
El modelo elige la tool leyendo el nombre, la descripción y el esquema; el cliente decide si pide aprobación leyendo las anotaciones. Una tool mal diseñada se llama en el momento equivocado o con el argumento equivocado. En esta clase compruebas, con un script, si tus tools dicen lo que hacen y lo que cambian.
Comenzar la lección
Objetivo: escribir nombre, descripción, esquema y anotaciones que dejen al agente elegir bien y al cliente pedir confirmación cuando hay consecuencia.
Haz clic en cada parte del registro de una tool.
Semántica y valores por defecto de las anotaciones según el tipo ToolAnnotations de la especificación 2026-07-28 (página Tools de la especificación; tipos en el repositorio del SDK oficial, leídos el 19/10/2026).
Los valores por defecto de la especificación valen cuando omites la anotación.
true = la tool no cambia el entorno. Un host puede aprobar solo una tool de solo lectura.
Sin la anotación, el cliente debe suponer que cambia algo.
Solo tiene sentido cuando no es de solo lectura. true = puede borrar o sobrescribir; false = solo agrega (como una reserva nueva).
Omitirlo en una tool que cambia algo = el cliente la trata como destructiva.
true = repetir con los mismos argumentos no tiene efecto extra. Importa porque los clientes repiten llamadas (se cayó la red, MRTR en la clase 5).
reservar_vaga no es idempotente: dos llamadas, dos plazas.
true = habla con el mundo externo (búsqueda en la web, API de terceros). false = dominio cerrado, como el catálogo en memoria del kit.
Una tool de mundo abierto es una puerta de entrada de contenido no confiable: el curso 4 trata ese tema.
Fragmento de kit/servidor.mjs. Fíjate en la descripción que avisa la consecuencia y en las anotaciones distintas.
server.registerTool(
'buscar_turmas',
{
title: 'Buscar turmas',
description: 'Lista turmas cujo título contém o termo. Só leitura: não reserva nada.',
inputSchema: z.object({
termo: z.string().min(2).describe('Parte do título, ex.: "mcp"'),
limite: z.int().min(1).max(20).optional().describe('Máximo de turmas (padrão 5)')
}),
outputSchema: z.object({ turmas: z.array(Turma) }),
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
},
async ({ termo, limite }) => { /* … */ }
);server.registerTool(
'reservar_vaga',
{
title: 'Reservar vaga',
description: 'Reserva uma vaga na turma para o e-mail informado. Tem consequência: pede confirmação antes.',
inputSchema: z.object({
turma_id: z.string().describe('Id da turma, ex.: "mcp-01"'),
email: z.email().describe('E-mail de quem vai fazer a turma')
}),
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
},
async ({ turma_id, email }, ctx) => { /* … aula 5 … */ }
);Lista las tools, comprueba la descripción y la anotación, y llama con un argumento bueno y con uno malo.
cd ~/projetos/formacao-webmcp-mcp-v72/kit node teste-ferramentas.mjs; echo "saída: $?"
// teste-ferramentas.mjs — comprueba el diseño de las tools: anotaciones, argumento válido,
// argumento inválido (el SDK lo rechaza antes de que corra tu código) y resultado estructurado.
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
import { createMcpHandler } from '@modelcontextprotocol/server';
import { createServer } from './servidor.mjs';
const handler = createMcpHandler(createServer);
const cliente = new Client({ name: 'teste-ferramentas', version: '1.0.0' }, { versionNegotiation: { mode: 'auto' } });
await cliente.connect(new StreamableHTTPClientTransport(new URL('http://teste.local/mcp'),
{ fetch: (url, init) => handler.fetch(new Request(url, init)) }));
let falhas = 0;
const confere = (ok, msg) => { console.log(`${ok ? 'ok ' : 'FALHA'} ${msg}`); if (!ok) falhas++; };
const { tools } = await cliente.listTools();
for (const t of tools) {
confere(t.description?.length >= 20, `${t.name}: descrição com 20+ caracteres`);
confere(typeof t.annotations?.readOnlyHint === 'boolean', `${t.name}: diz se é só leitura`);
}
const ok = await cliente.callTool({ name: 'buscar_turmas', arguments: { termo: 'mcp', limite: 1 } });
confere(ok.structuredContent?.turmas?.length === 1, 'buscar_turmas devolve structuredContent com 1 turma');
const ruim = await cliente.callTool({ name: 'buscar_turmas', arguments: { termo: 'm' } });
confere(ruim.isError === true, `argumento inválido vira isError: ${ruim.content[0].text.slice(0, 70)}…`);
console.log(`${falhas} falhas`);
await cliente.close();
await handler.close();
process.exitCode = falhas ? 1 : 0;ok buscar_turmas: descrição com 20+ caracteres ok buscar_turmas: diz se é só leitura ok reservar_vaga: descrição com 20+ caracteres ok reservar_vaga: diz se é só leitura ok buscar_turmas devolve structuredContent com 1 turma ok argumento inválido vira isError: Input validation error: Invalid arguments for tool buscar_turmas: term… 0 falhas saída: 0
El termo "m" tiene 1 carácter y el esquema pide 2: el SDK lo rechazó y devolvió el mensaje como resultado con isError, que el modelo lee y corrige.
Ensayo y la primera tool de tu servicio.
Corre el bloque comprobar tools.
Listo cuando: node teste-ferramentas.mjs → 0 falhas y saída: 0.
Borra la línea annotations de buscar_turmas y corre de nuevo. Después devuelve la línea.
Listo cuando: sin la línea: FALHA buscar_turmas: diz se é só leitura y saída: 1; con la línea de vuelta: 0 falhas.
Elige una tool de tu mapa-mcp.md (clase 1) y regístrala en el servidor.mjs con descripción, esquema con límites y las cuatro anotaciones.
Listo cuando: node teste-ferramentas.mjs → 0 falhas con tu tool listada en las líneas ok.
Pregunta 1 de 4
Registraste una tool que borra registros y olvidaste las anotaciones. ¿Cómo debe tratarla el cliente?competencia
Pregunta 2 de 4
El modelo llama a buscar_turmas con termo "m". ¿Qué pasa?competencia
isError, que el modelo lee.isError, que el modelo lee.Repasar: Las dos tools lado a ladoPregunta 3 de 4
¿Por qué reservar_vaga tiene idempotentHint false?
Pregunta 4 de 4
¿Cuál de estas es la mejor descripción para una tool?
Elige una alternativa. La explicación aparece después de tu intento.