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

Nesta aula você vai

  1. 1As duas voltas: input_required e a chamada repetida com a resposta
  2. 2acceptedContent e inputResponse: ler o sim, o não e a ausência
  3. 3O cliente precisa declarar que sabe perguntar (elicitation)
  4. 4requestState para fluxo em várias etapas, assinado

Objetivo: fazer a ferramenta com consequência pedir confirmação no meio da chamada com input_required e só agir depois do sim.

01Duas voltas, uma reserva

Clique em cada etapa. O seu handler roda nas duas voltas.

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

Antes (2025): o servidor iniciava elicitation/create, sampling e roots por conta própria. Na 2026-07-28 isso virou MRTR, e Sampling e Roots estão descontinuados.

02O handler de reservar_vaga

Trecho do kit/servidor.mjs. Ele é escrito para rodar nas duas voltas: lê o que já chegou e pede só o que falta.

Trecho · reservar_vaga (handler)
async ({ turma_id, email }, ctx) => {
  const turma = TURMAS.find(x => x.id === turma_id);
  if (!turma) return { content: [{ type: 'text', text: `turma ${turma_id} não existe` }], isError: true };
  if (turma.vagas < 1) return { content: [{ type: 'text', text: `turma ${turma_id} sem vagas` }], isError: true };

  const resposta = inputResponse(ctx.mcpReq.inputResponses, 'confirmar');
  if (resposta.kind === 'elicit' && resposta.action !== 'accept') {
    return { content: [{ type: 'text', text: 'reserva cancelada pelo usuário' }], isError: true };
  }
  const ok = acceptedContent(ctx.mcpReq.inputResponses, 'confirmar', Confirma);
  if (ok?.confirmar !== true) {
    return inputRequired({
      inputRequests: {
        confirmar: inputRequired.elicit({ message: `Reservar 1 vaga em "${turma.titulo}" para ${email}?`, requestedSchema: Confirma })
      }
    });
  }
  turma.vagas -= 1;
  return { content: [{ type: 'text', text: `reservado: ${turma_id} para ${email} (restam ${turma.vagas})` }] };
}

03Veja as duas voltas: sim e não

O script mostra a 1ª volta crua (sem o cliente completar sozinho) e depois o fluxo normal.

Terminal · usuário diz sim
cd ~/projetos/formacao-webmcp-mcp-v72/kit
node teste-confirmacao.mjs sim; echo "saída: $?"
Terminal · usuário diz não
cd ~/projetos/formacao-webmcp-mcp-v72/kit
node teste-confirmacao.mjs nao; echo "saída: $?"
Arquivo · kit/teste-confirmacao.mjs
// teste-confirmacao.mjs — mostra o MRTR (revisão 2026-07-28): a ferramenta reservar_vaga
// devolve resultType "input_required"; o cliente pergunta ao usuário e repete a chamada.
// Uso: node teste-confirmacao.mjs sim   (o "usuário" aceita)
//      node teste-confirmacao.mjs nao   (o "usuário" recusa)
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
import { createMcpHandler } from '@modelcontextprotocol/server';
import { createServer } from './servidor.mjs';

const resposta = process.argv[2] === 'nao' ? 'nao' : 'sim';
const handler = createMcpHandler(createServer);
const ligar = async extra => {
  const c = new Client({ name: 'teste-confirmacao', version: '1.0.0' },
    { versionNegotiation: { mode: 'auto' }, capabilities: { elicitation: { form: {} } }, ...extra });
  await c.connect(new StreamableHTTPClientTransport(new URL('http://teste.local/mcp'),
    { fetch: (url, init) => handler.fetch(new Request(url, init)) }));
  return c;
};
const args = { turma_id: 'html-01', email: 'aluno@exemplo.com' };

// 1ª volta "na mão": o que chega do servidor antes de o usuário responder.
const cru = await ligar({ inputRequired: { autoFulfill: false } });
const volta1 = await cru.request({ method: 'tools/call', params: { name: 'reservar_vaga', arguments: args } }, { allowInputRequired: true });
console.log(`1ª volta: resultType=${volta1.resultType} · pede: ${Object.keys(volta1.inputRequests ?? {}).join(', ')}`);
await cru.close();

// Fluxo normal: o SDK do cliente chama o seu handler de elicitation e repete a chamada sozinho.
const cliente = await ligar({});
cliente.setRequestHandler('elicitation/create', async req => {
  console.log(`[cliente] pergunta ao usuário: ${req.params.message} → ${resposta}`);
  return resposta === 'sim' ? { action: 'accept', content: { confirmar: true } } : { action: 'decline' };
});
const fim = await cliente.callTool({ name: 'reservar_vaga', arguments: args });
console.log(`resultado: ${fim.content[0].text} · isError=${fim.isError === true}`);
await cliente.close();
await handler.close();
process.exitCode = fim.isError ? 1 : 0;
Exemplo do repositório
1ª volta: resultType=input_required · pede: confirmar
[cliente] pergunta ao usuário: Reservar 1 vaga em "HTML semântico para agentes" para aluno@exemplo.com? → sim
resultado: reservado: html-01 para aluno@exemplo.com (restam 2) · isError=false
saída: 0

04Erro comum: cliente que não sabe perguntar

Sem capabilities.elicitation, o servidor não tem como pedir a confirmação.

Terminal · cliente sem capacidade
cd ~/projetos/formacao-webmcp-mcp-v72/kit
node teste-sem-capacidade.mjs; echo "saída: $?"
Exemplo do repositório
recusado: -32021 Cannot request input 'confirmar' (elicitation/create): the request's client capabilities do not declare the re
saída: 1

A mensagem sai cortada em 110 caracteres pelo próprio script (slice). O SDK recusa com -32021 antes de mandar o pedido. Num host real, isso significa que aquele cliente não pode usar a ferramenta com confirmação: avise na descrição ou ofereça outro caminho.

05Três cuidados

1. Resposta não é confiável

inputResponses vem do cliente. Valide com esquema (acceptedContent faz isso) antes de agir.

06Prática

O sim, o não e uma confirmação sua.

E12 · Sim e não

5 min

Rode os dois blocos.

Pronto quando: node teste-confirmacao.mjs nao → reserva cancelada pelo usuário · isError=true e saída: 1; com sim → reservado: html-01 e saída: 0. Nos dois, a 1ª linha é resultType=input_required.

E13 · Sem capacidade

3 min

Rode o cliente sem capacidade.

Pronto quando: recusado: -32021 e saída: 1.

E14 · Confirmação na sua ferramenta

20 min

Na ferramenta com consequência do seu serviço (aula 3), peça confirmação com inputRequired.elicit e trate o não. Copie o teste-confirmacao.mjs trocando nome e argumentos.

Pronto quando: o seu teste com nao termina em saída: 1 sem mudar nada; com sim, em saída: 0.

07Teste da aula

Pergunta 1 de 4

Na revisão 2026-07-28, como o servidor pede uma confirmação ao usuário?competência

08Em três frases

MRTRinput_required na 1ª volta; o cliente pergunta e repete.
HandlerRoda nas duas voltas; age só com o sim validado.
EstadoEntre voltas, só requestState assinado.

Para ir além

Aula anteriorPróxima aula