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.

Ejemplo de elicitation en MCP: soporte al cliente y configuración de Claude Code
Cristian Da Conceicao
Fundador de Picasso IA

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.

Manos sujetando un formulario de papel y una pluma estilográfica sobre un escritorio de roble

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 esquemaOpciones útilesUso típico
stringminLength, maxLength, pattern, format (email, uri, date, date-time)Correo de contacto, nota breve
number o integerminimum, maximum, defaultClientes afectados
booleandefault"¿Es una caída?"
enum de selección únicaenum, o oneOf con títulosPrioridad, área del producto
enum de selección múltiplearray con minItems y maxItemsPlataformas 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:

  1. accept: la persona envió el formulario y content contiene los valores.
  2. decline: la persona dijo que no a propósito.
  3. 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.

Agente de atención al cliente con auriculares en un escritorio con dos monitores

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.

CampoTipoPor qué está ahí
priorityenum de selección únicaDirige a la cola de guardia adecuada
areaenum de selección únicaElige el equipo responsable
affectedCustomersentero, de 1 a 10000Distingue a un usuario de una incidencia amplia
customerEmailstring, formato emailPermite al ingeniero hacer seguimiento
outagebooleanAbre el canal de incidencias

Vista cenital de un escritorio con un equipo portátil, flechas en una libreta y notas adhesivas en secuencia

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.

Desarrollador de software escribiendo en un equipo portátil en una oficina tranquila tipo loft

Archivos del proyecto y dependencias

mkdir support-desk-mcp && cd support-desk-mcp
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/sdk zod
npm install -D typescript tsx @types/node
mkdir src

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.

Manos escribiendo en un equipo portátil con una ventana de terminal en pantalla

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:

{
  "mcpServers": {
    "support-desk": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "tsx", "/absolute/path/to/support-desk-mcp/src/server.ts"]
    }
  }
}
ÁmbitoSe carga enCompartido con el equipoSe guarda en
local (predeterminado)Solo el proyecto actualNo~/.claude.json
projectSolo el proyecto actualSí, mediante control de versiones.mcp.json
userTodos tus proyectosNo~/.claude.json

💡 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.

Pantalla de un equipo portátil con un diálogo de formulario sencillo y un dedo sobre el panel táctil

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.

Dedo sobre la tecla Escape de un equipo portátil

Qué debe provocar cada acción

AcciónLo que hizo la personaLo que debe hacer la herramienta
acceptEnvió el formularioValidar de nuevo y luego crear la escalación
declineDijo que no a propósitoNo tocar el ticket, informar y ofrecer una vía manual
cancelCerró el diálogoNo 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

Candado de latón en una puerta de madera verde desgastada

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

Ingeniero de control de calidad con un portapapeles de lista de comprobación y dos equipos portátiles

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.

  1. Abre la página de Claude Sonnet 5 y pega el resumen del ticket junto con la referencia de la escalación en Prompt.
  2. Configura System Prompt una sola vez: "Escribes respuestas de soporte breves y tranquilas. Nunca prometas un plazo de solución."
  3. 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.
  4. Baja Max Tokens de los 8192 por defecto a unos 600 para que las respuestas sean breves.
  5. 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ámetroValor por defectoConsejo para respuestas de soporte
EffortlowSúbelo solo para errores complicados
Max Tokens8192Ajústalo a unos 600
System PromptvacíoFija el tono y los límites una sola vez
Max Image Resolution0,5 MPDé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.

Compartir este artículo

Elige tu idioma