E6 · Conferir o kit
5 minRode o bloco conferir ferramentas.
Pronto quando: node teste-ferramentas.mjs → 0 falhas e saída: 0.
Aula 3 · 20 min
O modelo escolhe a ferramenta lendo nome, descrição e esquema; o cliente decide se pede aprovação lendo as anotações. Ferramenta mal desenhada é chamada na hora errada ou com argumento errado. Nesta aula você confere, com um script, se as suas ferramentas dizem o que fazem e o que mudam.
Começar a aula
Objetivo: escrever nome, descrição, esquema e anotações que deixam o agente escolher certo e o cliente pedir confirmação quando há consequência.
Clique em cada parte do registro de uma ferramenta.
Semântica e padrões das anotações conforme o tipo ToolAnnotations da especificação 2026-07-28 (página Tools da especificação; tipos no repositório do SDK oficial, lidos em 19/10/2026).
Os padrões da especificação valem quando você omite a anotação.
true = a ferramenta não muda o ambiente. Um host pode aprovar sozinho uma ferramenta só de leitura.
Sem a anotação, o cliente deve supor que ela muda algo.
Só faz sentido quando não é só leitura. true = pode apagar ou sobrescrever; false = só acrescenta (como uma reserva nova).
Omitir numa ferramenta que muda algo = o cliente trata como destrutiva.
true = repetir com os mesmos argumentos não tem efeito extra. Importa porque clientes repetem chamadas (rede caiu, MRTR na aula 5).
reservar_vaga não é idempotente: duas chamadas, duas vagas.
true = fala com o mundo externo (busca na web, API de terceiro). false = domínio fechado, como o catálogo em memória do kit.
Ferramenta de mundo aberto é porta de entrada de conteúdo não confiável: o curso 4 trata disso.
Trecho do kit/servidor.mjs. Repare na descrição que avisa a consequência e nas anotações diferentes.
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 as ferramentas, confere descrição e anotação, chama com argumento bom e com argumento ruim.
cd ~/projetos/formacao-webmcp-mcp-v72/kit node teste-ferramentas.mjs; echo "saída: $?"
// teste-ferramentas.mjs — confere o desenho das ferramentas: anotações, argumento válido,
// argumento inválido (o SDK recusa antes do seu código rodar) e resultado estruturado.
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
O termo "m" tem 1 caractere e o esquema pede 2: o SDK recusou e devolveu a mensagem como resultado com isError, que o modelo lê e corrige.
Ensaio e a primeira ferramenta do seu serviço.
Rode o bloco conferir ferramentas.
Pronto quando: node teste-ferramentas.mjs → 0 falhas e saída: 0.
Apague a linha annotations de buscar_turmas e rode de novo. Depois devolva a linha.
Pronto quando: sem a linha: FALHA buscar_turmas: diz se é só leitura e saída: 1; com a linha de volta: 0 falhas.
Escolha uma ferramenta do seu mapa-mcp.md (aula 1) e registre no servidor.mjs com descrição, esquema com limites e as quatro anotações.
Pronto quando: node teste-ferramentas.mjs → 0 falhas com a sua ferramenta listada nas linhas ok.
Pergunta 1 de 4
Você registrou uma ferramenta que apaga registros e esqueceu as anotações. Como o cliente deve tratá-la?competência
Pergunta 2 de 4
O modelo chama buscar_turmas com termo "m". O que acontece?competência
isError, que o modelo lê.isError, que o modelo lê.Rever: As duas ferramentas lado a ladoPergunta 3 de 4
Por que reservar_vaga tem idempotentHint false?
Pergunta 4 de 4
Qual destas é a melhor descrição para uma ferramenta?
Escolha uma alternativa. A explicação aparece depois da sua tentativa.