Exemplo de elicitation MCP: suporte ao cliente e configuração no Claude Code
Um exemplo funcional de elicitation MCP para suporte ao cliente: um servidor em TypeScript que pausa a escalada de um chamado, pede ao agente a prioridade, a área do produto e o status da interrupção por meio de um formulário e depois retoma o processo. Inclui a configuração do Claude Code, o tratamento de recusa e cancelamento e uma lista de testes.
Seu agente de suporte digita "escalate SUP-1042" no Claude Code e a ferramenta começa a rodar. Aí ela trava, porque ninguém informou a prioridade, a área do produto nem se os clientes estão sem acesso. Uma configuração fraca adivinha e aciona a equipe errada. Uma configuração melhor pergunta. Essa pergunta, enviada do servidor de volta para a pessoa no terminal, é o que a elicitation MCP faz. Este exemplo de elicitation MCP para uma central de suporte ao cliente mostra o ciclo completo: o código do servidor, o schema do formulário, a configuração do Claude Code e o que fazer quando o agente disser não.
Tudo abaixo segue a revisão 2025-11-25 do Model Context Protocol e o SDK para TypeScript. O servidor é pequeno o bastante para ser lido de uma vez, e cada parte dele corresponde a uma regra da especificação.
O que a elicitation MCP realmente faz
Normalmente, um cliente MCP chama uma ferramenta, o servidor trabalha e um resultado volta. A elicitation adiciona uma etapa no meio. Enquanto a ferramenta está rodando, o servidor envia uma requisição elicitation/create ao cliente. O cliente mostra uma caixa de diálogo para a pessoa, coleta a resposta e a devolve. Daí a ferramenta continua com dados reais, em vez de um palpite.
Isso torna a elicitation uma primitiva de humano no loop. O servidor continua no comando do que precisa saber. O cliente continua no comando de como a pergunta aparece, quais servidores podem perguntar e se a pessoa tem permissão para recusar.
Modo formulário e modo URL
A especificação define dois modos:
Modo formulário coleta dados estruturados dentro do próprio protocolo. O servidor envia uma mensagem curta com um requestedSchema, e o cliente monta um formulário a partir dele.
Modo URL envia a pessoa para um endereço externo em casos sensíveis, como um login ou um pagamento. Os dados nunca passam pelo cliente. A revisão 2025-11-25 introduziu esse modo.
Os schemas de formulário são propositalmente simples. São objetos planos, com apenas propriedades primitivas:
Tipo no schema
Opções úteis
Uso típico
string
minLength, maxLength, pattern, format (email, uri, date, date-time)
E-mail de contato, nota curta
number ou integer
minimum, maximum, default
Clientes afetados
boolean
default
"Isto é uma interrupção?"
enum de seleção única
enum, ou oneOf com títulos
Prioridade, área do produto
enum de seleção múltipla
array com minItems e maxItems
Plataformas afetadas
Objetos aninhados e arrays de objetos ficam de fora de propósito, para que qualquer cliente consiga desenhar o formulário sem adivinhar.
Três respostas possíveis
Toda resposta traz uma action:
accept: a pessoa enviou o formulário, e content contém os valores.
decline: a pessoa disse não de propósito.
cancel: a pessoa fechou a caixa de diálogo sem escolher nada.
Seu servidor precisa tratar as três como resultados normais. A maioria dos bugs de elicitation vem de tratar apenas o primeiro.
O cenário de suporte ao cliente
Imagine uma equipe de suporte com uma central de chamados cheia e uma pequena escala de plantão. Os agentes trabalham dentro do Claude Code, e um servidor MCP chamado support-desk dá ao modelo uma única ação de escrita: escalate_ticket. Ela recebe um ID de chamado e entrega o caso aos engenheiros certos.
Por que chutar falha aqui
O modelo consegue ler um chamado e inferir uma prioridade. Muitas vezes vai acertar. Quando erra, uma dúvida de cobrança cai no canal de incidentes, ou uma interrupção de login fica numa fila lenta durante a noite. Você poderia ampliar o schema de entrada da ferramenta e torcer para o modelo preencher todos os campos corretamente, mas uma chamada com valores inventados parece idêntica a uma com valores reais.
A elicitation transfere a decisão para a pessoa que é responsável por ela. O modelo fornece o ID do chamado. O ser humano fornece o julgamento.
Os campos que o servidor pede
Cinco campos bastam. Mais do que isso e os agentes começam a fechar o diálogo sem responder.
Campo
Tipo
Por que está aqui
priority
enum de seleção única
Encaminha para a fila de plantão certa
area
enum de seleção única
Escolhe a equipe responsável
affectedCustomers
integer, de 1 a 10000
Separa um usuário de um incidente amplo
customerEmail
string, format email
Permite que o engenheiro faça o acompanhamento
outage
boolean
Abre o canal de incidentes
Somente priority e area são obrigatórios. Os outros têm valores padrão, então um agente com pressa pode aceitar e seguir em frente.
💡 Mantenha o formulário curto. Cada campo extra é um motivo para o agente pressionar cancelar.
Crie o servidor de suporte
O servidor é um único arquivo TypeScript, um transporte stdio e duas pequenas dependências.
Definir type como module permite que o arquivo use await no nível superior e imports ES.
A ferramenta de escalada
Salve isto 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());
Repare que mode não aparece na chamada de elicitInput. O modo formulário é o padrão, o que mantém a requisição legível para clientes mais antigos.
Verifique primeiro as capacidades do cliente
As primeiras linhas do handler importam mais do que parecem. Um cliente que suporta elicitation declara uma capacidade elicitation durante a inicialização. Um objeto elicitation vazio conta apenas como modo formulário, e um servidor nunca deve enviar um modo que o cliente não declarou.
Se a capacidade estiver ausente, o servidor devolve um erro em texto simples em vez de travar. O modelo lê essa mensagem e pergunta ao agente no chat. Uma ferramenta que falha com educação vale mais do que uma que congela.
💡 Em um servidor stdio, nunca imprima na saída padrão. Linhas soltas de console.log corrompem o fluxo do protocolo. Envie a saída de depuração com console.error.
Configuração do Claude Code passo a passo
Com o servidor escrito, o Claude Code precisa saber que ele existe.
Registre o servidor
Em qualquer pasta, adicione-o com a CLI. Tudo depois dos dois traços é o comando que inicia o servidor:
claude mcp add support-desk -- npx -y tsx /absolute/path/to/support-desk-mcp/src/server.ts
Para compartilhar a configuração com a equipe, adicione --scope project. Então o Claude Code grava um arquivo .mcp.json na raiz do repositório:
💡 No Windows nativo, envolva o launcher: claude mcp add support-desk -- cmd /c npx -y tsx C:\path\to\src\server.ts.
Confirme a conexão
Duas verificações mostram que o servidor está ativo:
claude --version
claude mcp list
Dentro de uma sessão, /mcp abre o painel de servidores com o status da conexão. Você deve ver support-desk listado como conectado.
Se um servidor parar de conectar após uma atualização, confira o changelog do Claude Code. As notas da versão 2.1.287, que incluiu prompts de URL vindos de servidores no protocolo 2025-11-25, recomendam adicionar "bareElicitationCapability": true à entrada de configuração desse servidor quando ele deixar de conectar.
Execute o fluxo de escalada
Inicie uma sessão no seu projeto e digite um pedido simples:
Escalate ticket SUP-1042 using the support-desk tool.
O Claude chama mcp__support-desk__escalate_ticket. A ferramenta pausa, o Claude Code mostra o formulário, e o agente preenche prioridade, área, clientes afetados, e-mail e o indicador de interrupção.
Depois que o agente envia, a ferramenta retoma e devolve algo como Escalated SUP-1042 as p1 in login. Reference ESC-LQ3F9A2. O modelo pode citar essa referência diretamente na resposta.
O Claude Code também expõe os hooks Elicitation e ElicitationResult, filtrados pelo nome do servidor. Um script pode responder a um formulário conhecido por conta própria, ou registrar cada resposta antes de ela chegar ao servidor. Use isso em formulários de baixo risco na automação, e mantenha uma pessoa supervisionando qualquer coisa que afete clientes.
Trate recusa, cancelamento e entrada inválida
Demonstrações do caminho feliz escondem a parte que decide se os agentes confiam na ferramenta. As pessoas fecham diálogos, mudam de ideia e digitam valores errados.
O que cada ação deve disparar
Ação
O que a pessoa fez
O que a ferramenta deve fazer
accept
Enviou o formulário
Validar de novo e criar a escalada
decline
Disse não de propósito
Deixar o chamado como está, registrar e oferecer um caminho manual
cancel
Fechou o diálogo
Não alterar nada, permitir nova tentativa depois
Repare na chamada safeParse do servidor. Os clientes devem validar as respostas contra o schema, e os servidores devem fazer isso de novo. Valores padrão e formatos são dicas para a interface, não garantias sobre os dados que chegam.
Devolva um resultado isError para entrada inválida, em vez de lançar uma exceção. O modelo vê a mensagem e pode pedir ao agente que execute a ferramenta novamente.
Erros que quebram a elicitation
A maioria das falhas vem de uma pequena lista de hábitos.
Dados sensíveis em formulários
A especificação é direta: servidores não devem pedir senhas, tokens de API ou credenciais de pagamento pelo modo formulário. As respostas do formulário passam pelo cliente, então podem acabar em logs e transcrições. Qualquer coisa secreta pertence ao modo URL, em que a pessoa digita a informação em uma página que o cliente não consegue ler.
Um nome ou um endereço de e-mail é diferente. O servidor pode pedir esses dados, e a pessoa pode revisar e recusar.
Schemas aninhados
Um requestedSchema com um objeto aninhado ou uma lista de objetos será rejeitado ou renderizado mal. Achate a estrutura. Se você precisar de uma lista de itens, faça várias elicitations pequenas ou aceite um enum de seleção múltipla.
Outros hábitos que causam problemas:
Tratar cancel como decline, para que um diálogo fechado pareça uma recusa.
Confiar em uma identidade digitada em um formulário. Identifique os usuários por autorização, não por um campo de texto.
Pedir os mesmos dados duas vezes em uma sessão.
Esquecer que um servidor remoto precisa vincular o estado ao usuário, e não apenas a um ID de sessão.
Uma lista curta de testes
Execute estes cinco casos antes que chamados reais toquem na ferramenta:
Aceite só com os valores padrão e confirme que affectedCustomers vira 1.
Aceite com um e-mail que tenha erro de digitação e confirme que o servidor o rejeita.
Recuse e confirme que o chamado não mudou.
Cancele com a tecla Esc e confirme que nada foi gravado.
Conecte a partir de um cliente sem elicitation e confirme que aparece o fallback em texto simples.
Você também pode executar o servidor no MCP Inspector, a partir da pasta do projeto, com npx @modelcontextprotocol/inspector npx tsx src/server.ts para acompanhar as mensagens elicitation/create brutas passando.
Rascunhe respostas com o Claude Sonnet 5
Depois que a escalada devolve uma referência, o agente ainda deve uma resposta ao cliente. O Claude Sonnet 5 no PicassoIA cuida dessa etapa de redação, e ele também lê capturas de tela, o que ajuda quando um cliente anexa uma imagem de erro.
Abra a página do Claude Sonnet 5 e cole o resumo do chamado mais a referência da escalada em Prompt.
Defina System Prompt uma vez: "Você escreve respostas de suporte curtas e calmas. Nunca prometa um prazo de correção."
Deixe Effort em low para rascunhos rápidos. Aumente para medium ou high quando o chamado exigir raciocínio de verdade. Segundo a página do modelo, low desativa o pensamento para as respostas mais rápidas e baratas.
Reduza Max Tokens do padrão 8192 para cerca de 600, para que as respostas fiquem curtas.
Anexe uma captura de tela em Image se o cliente tiver enviado uma. Max Image Resolution tem como padrão 0,5 megapixel, o que basta para uma caixa de diálogo de erro.
Parâmetro
Padrão
Dica para respostas de suporte
Effort
low
Aumente só para bugs complicados
Max Tokens
8192
Defina perto de 600
System Prompt
vazio
Fixe o tom e os limites uma vez
Max Image Resolution
0,5 MP
Mantenha como está para capturas de tela
Para raciocínios mais difíceis, o Claude Opus 4.7 está na mesma coleção. Comece com o Sonnet 5 e suba de modelo só quando um rascunho não acertar o ponto.
Crie suas próprias imagens com o Picasso IA
Documentações de suporte e artigos de central de ajuda ficam melhores com imagens reais. As mesmas cenas de mesa que você viu acima, um headset sobre uma mesa ou uma prancheta sob luz de janela, levam um único prompt para serem criadas.
Experimente o PicassoIA Image para uma primeira versão rápida, ou o Flux 2 Pro quando quiser uma textura mais fina. Um prompt que funciona bem:
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.
Escolha um modelo, cole o prompt, mude um detalhe por execução e compare os resultados. Dez minutos de experimentos vão ensinar mais sobre a redação de prompts do que qualquer lista de regras. Abra o Picasso IA, crie sua primeira imagem de cabeçalho e coloque-a no seu próximo artigo de suporte.