Ejemplo de elicitation en MCP: soporte al cliente y configuración de Claude Code
Un ejemplo funcional de elicitation en MCP para soporte al cliente: un servidor en TypeScript que pausa la escalación de un ticket, pide al agente mediante un formulario la prioridad, el área del producto y el estado de la caída, y luego continúa. Incluye la configuración de Claude Code, el manejo de rechazos y cancelaciones, y una lista de pruebas.
Tu agente de soporte escribe "escalate SUP-1042" en Claude Code y la herramienta empieza a ejecutarse. Entonces se detiene, porque nadie le ha indicado la prioridad, el área del producto ni si los clientes están sin acceso. Una configuración débil adivina y avisa al equipo equivocado. Una mejor, en cambio, pregunta. Esa pregunta, enviada desde el servidor de vuelta a la persona que está en la terminal, es lo que hace la elicitation de MCP. Este ejemplo de elicitation en MCP para un servicio de soporte al cliente muestra el ciclo completo: el código del servidor, el esquema del formulario, la configuración de Claude Code y qué hacer cuando el agente dice que no.
Todo lo que sigue se refiere a la revisión 2025-11-25 del Model Context Protocol y al SDK de TypeScript. El servidor es lo bastante pequeño como para leerlo de una sentada, y cada parte corresponde a una regla de la especificación.
Qué hace realmente la elicitation en MCP
Normalmente un cliente MCP llama a una herramienta, el servidor trabaja y devuelve un resultado. La elicitation añade un paso en medio. Mientras una herramienta se ejecuta, el servidor envía una solicitud elicitation/create al cliente. El cliente muestra a la persona un diálogo, recoge una respuesta y la devuelve. Entonces la herramienta continúa con datos reales en lugar de con una suposición.
Eso convierte la elicitation en una primitiva de humano en el bucle. El servidor sigue decidiendo qué necesita. El cliente sigue decidiendo cómo se ve la pregunta, qué servidores pueden hacerla y si la persona tiene derecho a negarse.
Modo formulario y modo URL
La especificación define dos modos:
Modo formulario recoge datos estructurados dentro del propio protocolo. El servidor envía un mensaje breve junto con un requestedSchema, y el cliente genera un formulario a partir de él.
Modo URL envía a la persona a una dirección externa para cualquier dato sensible, como un inicio de sesión o un pago. Los datos nunca pasan por el cliente. Lo introdujo la revisión 2025-11-25.
Los esquemas de formulario son deliberadamente pequeños. Son objetos planos con propiedades primitivas únicamente:
Tipo de esquema
Opciones útiles
Uso típico
string
minLength, maxLength, pattern, format (email, uri, date, date-time)
Correo de contacto, nota breve
number o integer
minimum, maximum, default
Clientes afectados
boolean
default
"¿Es una caída?"
enum de selección única
enum, o oneOf con títulos
Prioridad, área del producto
enum de selección múltiple
array con minItems y maxItems
Plataformas afectadas
Los objetos anidados y los arrays de objetos se dejan fuera a propósito, para que cualquier cliente pueda dibujar el formulario sin tener que adivinar.
Tres posibles respuestas
Cada respuesta lleva una action:
accept: la persona envió el formulario y content contiene los valores.
decline: la persona dijo que no a propósito.
cancel: la persona cerró el diálogo sin elegir nada.
Tu servidor debe tratar las tres como resultados normales. La mayoría de los errores de elicitation vienen de gestionar solo la primera.
El escenario de soporte al cliente
Imagina un equipo de soporte con una mesa de ayuda llena de tickets y un pequeño turno de guardia. Los agentes trabajan dentro de Claude Code, y un servidor MCP llamado support-desk da al modelo una única acción de escritura: escalate_ticket. Recibe un ID de ticket y entrega el caso a los ingenieros adecuados.
Por qué adivinar falla aquí
El modelo puede leer un ticket e inferir una prioridad. Acertará a menudo. Cuando se equivoca, una pregunta de facturación llega al canal de incidencias, o una caída del inicio de sesión se queda en una cola lenta toda la noche. Podrías ampliar el esquema de entrada de la herramienta y confiar en que el modelo rellene bien cada campo, pero una llamada con valores inventados se ve igual que una con valores reales.
La elicitation traslada la decisión a la persona que es responsable de ella. El modelo aporta el ID del ticket. El ser humano aporta el criterio.
Los campos que pide el servidor
Cinco campos bastan. Si son más, los agentes empiezan a descartar el diálogo.
Campo
Tipo
Por qué está ahí
priority
enum de selección única
Dirige a la cola de guardia adecuada
area
enum de selección única
Elige el equipo responsable
affectedCustomers
entero, de 1 a 10000
Distingue a un usuario de una incidencia amplia
customerEmail
string, formato email
Permite al ingeniero hacer seguimiento
outage
boolean
Abre el canal de incidencias
Solo priority y area son obligatorios. Los demás tienen valores por defecto, así que un agente con prisa puede aceptar y seguir.
💡 Mantén el formulario corto. Cada campo extra es un motivo para que el agente pulse cancelar.
Construye el servidor de soporte
El servidor es un único archivo TypeScript, un transporte stdio y dos dependencias pequeñas.
Al poner type en module, el archivo puede usar await de nivel superior e importaciones ES.
La herramienta de escalación
Guarda esto como src/server.ts:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({ name: "support-desk", version: "1.0.0" });
const EscalationForm = z.object({
priority: z.enum(["p1", "p2", "p3"]),
area: z.enum(["billing", "login", "api", "mobile-app"]),
affectedCustomers: z.number().int().min(1).max(10000).default(1),
customerEmail: z.string().email().optional(),
outage: z.boolean().default(false),
});
// Stub: swap in your helpdesk API call.
async function createEscalation(ticketId: string, data: z.infer<typeof EscalationForm>) {
return `ESC-${Date.now().toString(36).toUpperCase()}`;
}
const reply = (text: string, isError = false) => ({
content: [{ type: "text" as const, text }],
isError,
});
server.registerTool(
"escalate_ticket",
{
description: "Escalate a support ticket to the on-call team. Asks the agent for missing details.",
inputSchema: { ticketId: z.string().describe("Ticket ID, for example SUP-1042") },
},
async ({ ticketId }) => {
if (!server.server.getClientCapabilities()?.elicitation) {
return reply("This client cannot show forms. Ask the agent for priority and area, then retry.", true);
}
const answer = await server.server.elicitInput({
message: `Ticket ${ticketId} needs a few details before it reaches the on-call team.`,
requestedSchema: {
type: "object",
properties: {
priority: {
type: "string",
title: "Priority",
oneOf: [
{ const: "p1", title: "P1 Service down" },
{ const: "p2", title: "P2 Major feature broken" },
{ const: "p3", title: "P3 Minor issue" },
],
},
area: {
type: "string",
title: "Product area",
enum: ["billing", "login", "api", "mobile-app"],
},
affectedCustomers: {
type: "integer",
title: "Customers affected",
minimum: 1,
maximum: 10000,
default: 1,
},
customerEmail: { type: "string", format: "email", title: "Customer email" },
outage: { type: "boolean", title: "Is this an outage?", default: false },
},
required: ["priority", "area"],
},
});
if (answer.action === "decline") {
return reply(`The agent declined to escalate ${ticketId}. The ticket is unchanged.`);
}
if (answer.action === "cancel") {
return reply(`Escalation of ${ticketId} was cancelled. Nothing was changed.`);
}
const parsed = EscalationForm.safeParse(answer.content);
if (!parsed.success) {
return reply("The form answers were invalid. Ask again.", true);
}
const escalationId = await createEscalation(ticketId, parsed.data);
return reply(`Escalated ${ticketId} as ${parsed.data.priority} in ${parsed.data.area}. Reference ${escalationId}.`);
}
);
await server.connect(new StdioServerTransport());
Fíjate en que mode no aparece en la llamada a elicitInput. El modo formulario es el predeterminado, lo que mantiene la solicitud legible para clientes más antiguos.
Comprueba antes las capacidades del cliente
Las primeras líneas del manejador importan más de lo que parecen. Un cliente que admite elicitation declara una capacidad elicitation durante la inicialización. Un objeto elicitation vacío cuenta solo como modo formulario, y un servidor nunca debe enviar un modo que el cliente no haya declarado.
Si falta la capacidad, el servidor devuelve un error en texto plano en lugar de quedarse colgado. El modelo lee ese mensaje y pregunta al agente en el chat. Una herramienta que falla con educación es mejor que una que se congela.
💡 En un servidor stdio, nunca imprimas por la salida estándar. Las líneas console.log perdidas corrompen el flujo del protocolo. Envía la salida de depuración con console.error.
Configuración de Claude Code paso a paso
Con el servidor escrito, Claude Code necesita saber que existe.
Registra el servidor
Desde cualquier carpeta, añádelo con la CLI. Todo lo que va después de los dos guiones es el comando que lanza el servidor:
claude mcp add support-desk -- npx -y tsx /absolute/path/to/support-desk-mcp/src/server.ts
Para compartir la configuración con tu equipo, añade --scope project. Claude Code entonces escribe un archivo .mcp.json en la raíz del repositorio:
💡 En Windows nativo, envuelve el lanzador: claude mcp add support-desk -- cmd /c npx -y tsx C:\path\to\src\server.ts.
Confirma la conexión
Dos comprobaciones te dicen que el servidor está activo:
claude --version
claude mcp list
Dentro de una sesión, /mcp abre el panel del servidor con el estado de la conexión. Deberías ver support-desk listado como conectado.
Si un servidor deja de conectarse tras una actualización, revisa el registro de cambios de Claude Code. Las notas de la versión 2.1.287, que añadió los prompts de URL desde servidores del protocolo 2025-11-25, indican que añadas "bareElicitationCapability": true a la entrada de configuración de ese servidor cuando deje de conectarse.
Ejecuta el flujo de escalación
Inicia una sesión en tu proyecto y escribe una petición sencilla:
Escalate ticket SUP-1042 using the support-desk tool.
Claude llama a mcp__support-desk__escalate_ticket. La herramienta se pausa, Claude Code muestra el formulario y el agente rellena la prioridad, el área, los clientes afectados, el correo y el indicador de caída.
Cuando el agente envía el formulario, la herramienta continúa y devuelve algo como Escalated SUP-1042 as p1 in login. Reference ESC-LQ3F9A2. El modelo puede citar esa referencia directamente en su respuesta.
Claude Code también expone los hooks Elicitation y ElicitationResult, que se filtran por el nombre del servidor. Un script puede responder por su cuenta a un formulario conocido, o registrar cada respuesta antes de que llegue al servidor. Úsalo para formularios de bajo riesgo en automatizaciones, y mantén a una persona delante de cualquier cosa que afecte a los clientes.
Gestiona el rechazo, la cancelación y los datos incorrectos
Las demos del camino feliz ocultan la parte que decide si los agentes confían en la herramienta. La gente cierra diálogos, cambia de opinión y escribe mal los valores.
Qué debe provocar cada acción
Acción
Lo que hizo la persona
Lo que debe hacer la herramienta
accept
Envió el formulario
Validar de nuevo y luego crear la escalación
decline
Dijo que no a propósito
No tocar el ticket, informar y ofrecer una vía manual
cancel
Cerró el diálogo
No cambiar nada, permitir reintentar más tarde
Fíjate en la llamada safeParse del servidor. Los clientes deben validar las respuestas contra el esquema, y los servidores deben hacerlo de nuevo. Los valores por defecto y los formatos son pistas para la interfaz, no garantías sobre los datos que llegan.
Devuelve un resultado isError ante datos incorrectos en lugar de lanzar una excepción. El modelo ve el mensaje y puede pedir al agente que vuelva a ejecutar la herramienta.
Errores que rompen la elicitation
La mayoría de los fallos vienen de una lista corta de malos hábitos.
Datos sensibles en formularios
La especificación es tajante: los servidores no deben pedir contraseñas, tokens de API ni credenciales de pago mediante el modo formulario. Las respuestas del formulario pasan por el cliente, así que pueden acabar en registros y transcripciones. Todo lo secreto pertenece al modo URL, donde la persona escribe el dato en una página que el cliente no puede leer.
Un nombre o una dirección de correo es distinto. El servidor puede pedir esos datos, y la persona puede revisarlos y negarse.
Esquemas anidados
Un requestedSchema con un objeto anidado o una lista de objetos será rechazado o se mostrará mal. Aplánalo. Si necesitas una lista de líneas de pedido, haz varias elicitations pequeñas o acepta un enum de selección múltiple.
Otros hábitos que causan problemas:
Tratar cancel como decline, de modo que un diálogo cerrado parezca un rechazo.
Confiar en una identidad escrita en un formulario. Identifica a los usuarios mediante autorización, no mediante un campo de texto.
Pedir los mismos datos dos veces en una misma sesión.
Olvidar que un servidor remoto debe vincular su estado al usuario, no solo a un ID de sesión.
Una lista corta de pruebas
Ejecuta estos cinco casos antes de que los tickets reales toquen la herramienta:
Aceptar solo con los valores por defecto, y confirmar que affectedCustomers pasa a ser 1.
Aceptar con un correo con una errata, y confirmar que el servidor lo rechaza.
Rechazar, y confirmar que el ticket no cambia.
Cancelar con la tecla Escape, y confirmar que no se escribe nada.
Conectar desde un cliente sin elicitation, y confirmar que aparece el respaldo en texto plano.
También puedes ejecutar el servidor con MCP Inspector desde la carpeta del proyecto con npx @modelcontextprotocol/inspector npx tsx src/server.ts para ver pasar los mensajes elicitation/create sin procesar.
Redacta respuestas con Claude Sonnet 5
Una vez que la escalación devuelve una referencia, el agente aún debe una respuesta al cliente. Claude Sonnet 5 en PicassoIA se encarga de ese paso de redacción, y también lee capturas de pantalla, lo que ayuda cuando un cliente adjunta una imagen de error.
Abre la página de Claude Sonnet 5 y pega el resumen del ticket junto con la referencia de la escalación en Prompt.
Configura System Prompt una sola vez: "Escribes respuestas de soporte breves y tranquilas. Nunca prometas un plazo de solución."
Deja Effort en low para borradores rápidos. Súbelo a medium o high cuando el ticket necesite un razonamiento real. Según la página del modelo, low desactiva el pensamiento para las respuestas más rápidas y baratas.
Baja Max Tokens de los 8192 por defecto a unos 600 para que las respuestas sean breves.
Adjunta una captura en Image si el cliente la envió. Max Image Resolution tiene 0,5 megapíxeles por defecto, lo que basta para un diálogo de error.
Parámetro
Valor por defecto
Consejo para respuestas de soporte
Effort
low
Súbelo solo para errores complicados
Max Tokens
8192
Ajústalo a unos 600
System Prompt
vacío
Fija el tono y los límites una sola vez
Max Image Resolution
0,5 MP
Déjalo así para capturas de pantalla
Para un razonamiento más difícil, Claude Opus 4.7 figura en la misma colección. Empieza con Sonnet 5 y pasa a un modelo superior solo cuando un borrador no dé en el punto.
Crea tus propias imágenes con Picasso IA
La documentación de soporte y los artículos del centro de ayuda se leen mejor con imágenes reales. Las mismas escenas de escritorio que viste arriba, unos auriculares sobre una mesa o un portapapeles bajo la luz de una ventana, se crean con un solo prompt.
Prueba PicassoIA Image para una primera versión rápida, o Flux 2 Pro cuando quieras una textura más fina. Un prompt que funciona bien:
A support engineer at a wooden desk, 35mm lens, soft window light from the left, shallow depth of field, Kodak Portra 400 film grain, no text.
Elige un modelo, pega el prompt, cambia un detalle en cada ejecución y compara los resultados. Diez minutos de pruebas te enseñarán más sobre la redacción de prompts que cualquier lista de reglas. Abre Picasso IA, crea tu primera imagen de cabecera y colócala en tu próximo artículo de soporte.