Asking the user for input in the middle of the call: MRTR (InputRequiredResult)
Book, pay, delete: an action with consequences needs a yes from a person. In the 2026-07-28 revision, the server no longer opens its own request to ask. It returns input_required, the client asks the user, and repeats the call with the answer. In this lesson you see both rounds and the path of the no.
1The two rounds: input_required and the call repeated with the answer
2acceptedContent and inputResponse: reading the yes, the no, and the absence
3The client must declare that it knows how to ask (elicitation)
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.
01 · Two 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.
02 · The 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.
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
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? → nao
resultado: reserva cancelada pelo usuário · isError=true
saída: 1
A refusal becomes isError and exit code 1: the seat is still there.
04 · Common 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.
05 · Three things to watch
1. The answer is not trustworthy
inputResponses comes from the client. Validate it with a schema (acceptedContent does this) before acting.
2. Nothing survives between rounds
The server is stateless: the 2nd round is a separate request. A multi-step flow keeps what has already been proven in requestState, signed with createRequestStateCodec (HMAC). It is signed, not encrypted: no secrets inside.
3. Old client
On a 2025 connection, the server SDK converts the input_required into an old-style elicitation/create (shim on by default). The same handler serves both eras.
06 · Practice
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.
07 · Lesson quiz
Question 1 of 4
In the 2026-07-28 revision, how does the server ask the user for confirmation?skill
Correct
The server no longer starts requests. It responds with input_required, and the client asks and repeats the call (MRTR).
Not this one
The server no longer starts requests. It responds with input_required, and the client asks and repeats the call (MRTR).Review: Two rounds, one booking
Question 2 of 4
The user declined. What does the kit's handler do?skill
Correct
inputResponse shows an action other than accept; the handler returns isError and the seat is still there.
Not this one
inputResponse shows an action other than accept; the handler returns isError and the seat is still there.Review: The reservar_vaga handler
Question 3 of 4
A flow asks for confirmation and then for the scope. Where do you store the fact that the confirmation already happened?
Correct
Stateless, the server doesn't remember the previous round. requestState comes back identical from the client; sign it with createRequestStateCodec and store only what has already been proven.
Not this one
Stateless, the server doesn't remember the previous round. requestState comes back identical from the client; sign it with createRequestStateCodec and store only what has already been proven.Review: Three things to watch
Question 4 of 4
The client did not declare capabilities.elicitation and calls reservar_vaga. Result?
Correct
Each embedded request is checked against the client's capabilities. Without elicitation, it is refused with -32021.
Not this one
Each embedded request is checked against the client's capabilities. Without elicitation, it is refused with -32021.Review: The reservar_vaga handler
Quiz complete
You got {a} of {n} right. Get them all right and the lesson is marked as complete.
Pick an option. The explanation appears after you try.
08 · In three sentences
MRTRinput_required on the 1st round; the client asks and repeats.
HandlerRuns in both rounds; acts only on the validated yes.