Servidor MCP de tu servicio|INEMA.CLUB-PRO

Clase 5 · 20 min

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.

Comenzar la lección
Una mano desenfocada a punto de presionar un botón de confirmación, y al lado un robot industrial detenido esperando.

En esta lección vas a

  1. 1Las dos vueltas: input_required y la llamada repetida con la respuesta
  2. 2acceptedContent e inputResponse: leer el sí, el no y la ausencia
  3. 3El cliente debe declarar que sabe preguntar (elicitation)
  4. 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í.

01Dos 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.

02El 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.

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

03Mira las dos vueltas: sí y no

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

04Error 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.

05Tres cuidados

1. La respuesta no es confiable

inputResponses viene del cliente. Valídala con esquema (acceptedContent lo hace) antes de actuar.

06Prá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.

07Prueba 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

08En tres frases

MRTRinput_required en la 1.ª vuelta; el cliente pregunta y repite.
HandlerSe ejecuta en las dos vueltas; actúa solo con el sí validado.
EstadoEntre vueltas, solo requestState firmado.

Para ir más allá

Clase anteriorSiguiente clase