Pedir un dato al usuario a mitad de la llamada: MRTR (InputRequiredResult)
Reservar, pagar, borrar: una acción con consecuencias pide el sí de una persona. En la revisión 2026-07-28 el servidor ya no abre una solicitud propia para preguntar. Devuelve input_required, el cliente le pregunta al usuario y repite la llamada con la respuesta. En esta lección ves las dos vueltas y el camino del no.
1Las dos vueltas: input_required y la llamada repetida con la respuesta
2acceptedContent e inputResponse: leer el sí, el no y la ausencia
3El cliente debe declarar que sabe preguntar (elicitation)
4requestState para flujos de varios pasos, firmado
Objetivo: hacer que la tool con consecuencias pida confirmación a mitad de la llamada con input_required y actuar solo después del sí.
01 · Dos vueltas, una reserva
Haz clic en cada paso. Tu handler se ejecuta en las dos vueltas.
Haz clic en cada bloque del diagrama para ver qué hace.
Antes (2025): el servidor iniciaba elicitation/create, sampling y roots por su cuenta. En la 2026-07-28 eso pasó a ser MRTR, y Sampling y Roots quedaron descontinuados.
02 · El handler de reservar_vaga
Fragmento de kit/servidor.mjs. Está escrito para correr en las dos vueltas: lee lo que ya llegó y pide solo lo que falta.
El script muestra la 1.ª vuelta cruda (sin que el cliente la complete solo) y después el flujo normal.
Terminal · el usuario dice sí
cd ~/projetos/formacao-webmcp-mcp-v72/kit
node teste-confirmacao.mjs sim; echo "saída: $?"
Terminal · el usuario dice no
cd ~/projetos/formacao-webmcp-mcp-v72/kit
node teste-confirmacao.mjs nao; echo "saída: $?"
Archivo · kit/teste-confirmacao.mjs
// teste-confirmacao.mjs — muestra el MRTR (revisión 2026-07-28): la tool reservar_vaga
// devuelve resultType "input_required"; el cliente le pregunta al usuario y repite la llamada.
// Uso: node teste-confirmacao.mjs sim (el "usuario" acepta)
// node teste-confirmacao.mjs nao (el "usuario" rechaza)
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.ª vuelta "a mano": lo que llega del servidor antes de que el usuario responda.
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();
// Flujo normal: el SDK del cliente llama a tu handler de elicitation y repite la llamada solo.
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;
Ejemplo del repositorio
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
Un rechazo se vuelve isError y salida 1: la plaza sigue ahí.
04 · Error común: un cliente que no sabe preguntar
Sin capabilities.elicitation, el servidor no tiene cómo pedir la confirmación.
Terminal · cliente sin capacidad
cd ~/projetos/formacao-webmcp-mcp-v72/kit
node teste-sem-capacidade.mjs; echo "saída: $?"
Ejemplo del repositorio
recusado: -32021 Cannot request input 'confirmar' (elicitation/create): the request's client capabilities do not declare the re
saída: 1
El propio script corta el mensaje a 110 caracteres (slice). El SDK rechaza con -32021 antes de enviar la solicitud. En un host real, esto significa que ese client no puede usar la tool con confirmación: avísalo en la descripción u ofrece otro camino.
05 · Tres cuidados
1. La respuesta no es confiable
inputResponses viene del cliente. Valídala con esquema (acceptedContent lo hace) antes de actuar.
2. Nada sobrevive entre vueltas
El servidor es sin estado: la 2.ª vuelta es otro pedido. Un flujo de varios pasos guarda lo que ya se comprobó en requestState, firmado con createRequestStateCodec (HMAC). Está firmado, no cifrado: nada de secretos adentro.
3. Cliente antiguo
En una conexión 2025, el SDK del servidor convierte el input_required en un elicitation/create a la antigua (shim activado por defecto). El mismo handler sirve a las dos eras.
06 · Práctica
El sí, el no y una confirmación tuya.
E12 · Sí y no
5 min
Corre los dos bloques.
Listo cuando:node teste-confirmacao.mjs nao → reserva cancelada pelo usuário · isError=true y saída: 1; con sim → reservado: html-01 y saída: 0. En los dos, la primera línea es resultType=input_required.
E13 · Sin capacidad
3 min
Corre el cliente sin capacidad.
Listo cuando:recusado: -32021 y saída: 1.
E14 · Confirmación en tu tool
20 min
En la tool con consecuencias de tu servicio (lección 3), pide confirmación con inputRequired.elicit y maneja el no. Copia el teste-confirmacao.mjs cambiando nombre y argumentos.
Listo cuando: tu prueba con nao termina en saída: 1 sin cambiar nada; con sim, en saída: 0.
07 · Prueba de la clase
Pregunta 1 de 4
En la revisión 2026-07-28, ¿cómo le pide el servidor una confirmación al usuario?competencia
Correcto
El servidor ya no inicia solicitudes. Responde input_required y el cliente pregunta y repite la llamada (MRTR).
No es esa
El servidor ya no inicia solicitudes. Responde input_required y el cliente pregunta y repite la llamada (MRTR).Repasar: Dos vueltas, una reserva
Pregunta 2 de 4
El usuario rechazó. ¿Qué hace el handler del kit?competencia
Correcto
inputResponse muestra un action distinto de accept; el handler devuelve isError y la plaza sigue ahí.
Un flujo pide confirmación y después el scope. ¿Dónde se guarda que la confirmación ya ocurrió?
Correcto
Sin estado, el servidor no recuerda la vuelta anterior. requestState vuelve idéntico desde el cliente; fírmalo con createRequestStateCodec y guarda solo lo que ya se probó.
No es esa
Sin estado, el servidor no recuerda la vuelta anterior. requestState vuelve idéntico desde el cliente; fírmalo con createRequestStateCodec y guarda solo lo que ya se probó.Repasar: Tres cuidados
Pregunta 4 de 4
El cliente no declaró capabilities.elicitation y llama a reservar_vaga. ¿Resultado?
Correcto
Cada solicitud embebida se compara con las capacidades del cliente. Sin elicitation, se rechaza con -32021.