Servidor MCP de tu servicio|INEMA.CLUB-PRO

Clase 3 · 20 min

Diseñar una buena tool: nombre, descripción, JSON Schema, idempotencia, solo lectura × con consecuencia

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
Banco oscuro con herramientas de precisión en fila, cada una con una etiqueta en blanco, una con un seguro rojo.

En esta lección vas a

  1. 1Una intención por tool, con un nombre y una descripción que la expresen
  2. 2Esquema de entrada que rechaza el argumento equivocado antes de tu código
  3. 3Las cuatro anotaciones y el valor por defecto cuando no dices nada
  4. 4Resultado en texto y estructurado; un error que el modelo pueda leer

Objetivo: escribir nombre, descripción, esquema y anotaciones que dejen al agente elegir bien y al cliente pedir confirmación cuando hay consecuencia.

01Lo que leen el modelo y el cliente

Haz clic en cada parte del registro de una tool.

Haz clic en cada bloque del diagrama para ver qué hace.

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).

02Anotaciones: qué significa no decir nada

Los valores por defecto de la especificación valen cuando omites la anotación.

1. readOnlyHint

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.

03Las dos tools lado a lado

Fragmento de kit/servidor.mjs. Fíjate en la descripción que avisa la consecuencia y en las anotaciones distintas.

Fragmento · buscar_turmas
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 }) => { /* … */ }
);
Fragmento · reservar_vaga
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 … */ }
);

04Comprueba el diseño con un script

Lista las tools, comprueba la descripción y la anotación, y llama con un argumento bueno y con uno malo.

Terminal · comprobar tools
cd ~/projetos/formacao-webmcp-mcp-v72/kit
node teste-ferramentas.mjs; echo "saída: $?"
Archivo · kit/teste-ferramentas.mjs
// 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;
Ejemplo del repositorio
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.

05Práctica

Ensayo y la primera tool de tu servicio.

E6 · Comprobar el kit

5 min

Corre el bloque comprobar tools.

Listo cuando: node teste-ferramentas.mjs → 0 falhas y saída: 0.

E7 · Haz que falle a propósito

5 min

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.

E8 · Una tool de tu servicio

20 min

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.

06Prueba de la clase

Pregunta 1 de 4

Registraste una tool que borra registros y olvidaste las anotaciones. ¿Cómo debe tratarla el cliente?competencia

07En tres frases

IntenciónUna por tool; nombre con verbo; descripción que avisa de la consecuencia.
EsquemaLímites en Zod: el SDK rechaza antes de tu código.
AnotacionesDi las cuatro; omitir = destructiva para el cliente.

Para ir más allá

Clase anteriorSiguiente clase