An MCP Server for Your Service|INEMA.CLUB-PRO

In this lesson you will

  1. 1The two rounds: input_required and the call repeated with the answer
  2. 2acceptedContent and inputResponse: reading the yes, the no, and the absence
  3. 3The client must declare that it knows how to ask (elicitation)
  4. 4requestState for multi-step flows, signed

Goal: make the tool with consequences ask for confirmation mid-call with input_required and act only after the yes.

01Two rounds, one booking

Click each step. Your handler runs in both rounds.

Click each block in the diagram to see what it does.

Before (2025): the server started elicitation/create, sampling and roots on its own. In 2026-07-28 this became MRTR, and Sampling and Roots are deprecated.

02The reservar_vaga handler

Excerpt from kit/servidor.mjs. It is written to run on both rounds: it reads what has already arrived and asks only for what is missing.

Excerpt · 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})` }] };
}

03See both rounds: yes and no

The script shows the raw 1st round (without the client completing it on its own) and then the normal flow.

Terminal · user says yes
cd ~/projetos/formacao-webmcp-mcp-v72/kit
node teste-confirmacao.mjs sim; echo "saída: $?"
Terminal · user says no
cd ~/projetos/formacao-webmcp-mcp-v72/kit
node teste-confirmacao.mjs nao; echo "saída: $?"
File · kit/teste-confirmacao.mjs
// teste-confirmacao.mjs — shows the MRTR (revision 2026-07-28): the reservar_vaga tool
// returns resultType "input_required"; the client asks the user and repeats the call.
// Usage: node teste-confirmacao.mjs sim   (the "user" accepts)
//        node teste-confirmacao.mjs nao   (the "user" declines)
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' };

// 1st round "by hand": what arrives from the server before the user answers.
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();

// Normal flow: the client SDK calls your elicitation handler and repeats the call on its own.
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;
Example from the repository
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

04Common mistake: a client that can't ask

Without capabilities.elicitation, the server has no way to ask for the confirmation.

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

The message is cut off at 110 characters by the script itself (slice). The SDK refuses with -32021 before sending the request. In a real host, this means that client can't use the tool with confirmation: say so in the description or offer another path.

05Three things to watch

1. The answer is not trustworthy

inputResponses comes from the client. Validate it with a schema (acceptedContent does this) before acting.

06Practice

The yes, the no and a confirmation of your own.

E12 · Yes and no

5 min

Run both blocks.

Done when: node teste-confirmacao.mjs nao → reserva cancelada pelo usuário · isError=true and saída: 1; with sim → reservado: html-01 and saída: 0. In both, the 1st line is resultType=input_required.

E13 · No capability

3 min

Run the client without the capability.

Done when: recusado: -32021 and saída: 1.

E14 · Confirmation in your tool

20 min

On your service's tool with consequences (lesson 3), ask for confirmation with inputRequired.elicit and handle the no. Copy teste-confirmacao.mjs, changing the name and arguments.

Done when: your test with nao ends in saída: 1 without changing anything; with sim, in saída: 0.

07Lesson quiz

Question 1 of 4

In the 2026-07-28 revision, how does the server ask the user for confirmation?skill

08In three sentences

MRTRinput_required on the 1st round; the client asks and repeats.
HandlerRuns in both rounds; acts only on the validated yes.
StateBetween rounds, only signed requestState.

Going further

Previous lessonNext lesson