Servidor MCP do seu serviço|INEMA.CLUB-PRO

Aula 3 · 20 min

Desenhar boa ferramenta: nome, descrição, JSON Schema, idempotência, só-leitura × com consequência

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
Bancada escura com ferramentas de precisão enfileiradas, cada uma com etiqueta em branco, uma com trava vermelha.

Nesta aula você vai

  1. 1Uma intenção por ferramenta, com nome e descrição que a dizem
  2. 2Esquema de entrada que recusa o argumento errado antes do seu código
  3. 3As quatro anotações e o padrão quando você não diz nada
  4. 4Resultado em texto e estruturado; erro que o modelo consegue ler

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.

01O que o modelo e o cliente leem

Clique em cada parte do registro de uma ferramenta.

Clique em cada bloco do diagrama para ver o que ele faz.

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

02Anotações: o que significa não dizer nada

Os padrões da especificação valem quando você omite a anotação.

1. readOnlyHint

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.

03As duas ferramentas lado a lado

Trecho do kit/servidor.mjs. Repare na descrição que avisa a consequência e nas anotações diferentes.

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

04Confira o desenho com um script

Lista as ferramentas, confere descrição e anotação, chama com argumento bom e com argumento ruim.

Terminal · conferir ferramentas
cd ~/projetos/formacao-webmcp-mcp-v72/kit
node teste-ferramentas.mjs; echo "saída: $?"
Arquivo · kit/teste-ferramentas.mjs
// 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;
Exemplo do repositório
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.

05Prática

Ensaio e a primeira ferramenta do seu serviço.

E6 · Conferir o kit

5 min

Rode o bloco conferir ferramentas.

Pronto quando: node teste-ferramentas.mjs → 0 falhas e saída: 0.

E7 · Faça falhar de propósito

5 min

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.

E8 · Uma ferramenta do seu serviço

20 min

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.

06Teste da aula

Pergunta 1 de 4

Você registrou uma ferramenta que apaga registros e esqueceu as anotações. Como o cliente deve tratá-la?competência

07Em três frases

IntençãoUma por ferramenta; nome com verbo; descrição que avisa a consequência.
EsquemaLimites no Zod: o SDK recusa antes do seu código.
AnotaçõesDiga as quatro; omitir = destrutiva para o cliente.

Para ir além

Aula anteriorPróxima aula