Exemple d’elicitation MCP : support client et configuration de Claude Code
Un exemple d’elicitation MCP fonctionnel pour le support client : un serveur TypeScript qui met en pause l’escalade d’un ticket, demande à l’agent la priorité, le domaine produit et l’état de la panne via un formulaire, puis reprend. Inclut la configuration de Claude Code, la gestion du refus et de l’annulation, et une checklist de tests.
Votre agent de support tape « escalate SUP-1042 » dans Claude Code et l’outil se lance. Puis il se bloque, car personne ne lui a indiqué la priorité, le domaine produit, ni si les clients sont privés d’accès. Une configuration faible devine et alerte la mauvaise équipe. Une meilleure configuration pose la question. Cette question, envoyée depuis le serveur vers la personne devant le terminal, c’est ce que fait l’elicitation MCP. Cet exemple d’elicitation MCP pour un service de support client présente toute la boucle : le code du serveur, le schéma du formulaire, la configuration de Claude Code, et la conduite à tenir quand l’agent refuse.
Tout ce qui suit cible la révision 2025-11-25 du Model Context Protocol et le SDK TypeScript. Le serveur est assez court pour être lu d’une traite, et chaque partie correspond à une règle de la spécification.
Ce que fait réellement l’elicitation MCP
Normalement, un client MCP appelle un outil, le serveur travaille, et un résultat revient. L’elicitation ajoute une étape au milieu. Pendant que l’outil s’exécute, le serveur envoie une requête elicitation/create au client. Le client affiche à la personne une boîte de dialogue, recueille une réponse et la renvoie. L’outil poursuit alors avec des données réelles au lieu d’une supposition.
Cela fait de l’elicitation une primitive de human in the loop. Le serveur reste maître de ce dont il a besoin. Le client reste maître de la manière dont la question s’affiche, des serveurs autorisés à poser des questions, et de la possibilité pour la personne de refuser.
Mode formulaire et mode URL
La spécification définit deux modes :
Le mode formulaire recueille des données structurées directement dans le protocole. Le serveur envoie un court message accompagné d’un requestedSchema, et le client en tire un formulaire.
Le mode URL envoie la personne vers une adresse externe pour tout ce qui est sensible, comme une connexion ou un paiement. Les données ne transitent jamais par le client. La révision 2025-11-25 l’a introduit.
Les schémas de formulaire sont volontairement simples. Ce sont des objets plats, avec uniquement des propriétés primitives :
Type de schéma
Options utiles
Usage typique
string
minLength, maxLength, pattern, format (email, uri, date, date-time)
Adresse e-mail de contact, courte note
number ou integer
minimum, maximum, default
Clients concernés
boolean
default
« S’agit-il d’une panne ? »
enum à sélection unique
enum, ou oneOf avec titres
Priorité, domaine produit
enum à sélection multiple
array avec minItems et maxItems
Plateformes concernées
Les objets imbriqués et les tableaux d’objets sont volontairement exclus, afin que n’importe quel client puisse dessiner le formulaire sans rien deviner.
Trois réponses possibles
Chaque réponse porte un action :
accept : la personne a soumis le formulaire, et content contient les valeurs.
decline : la personne a refusé délibérément.
cancel : la personne a fermé la boîte de dialogue sans rien choisir.
Votre serveur doit traiter ces trois issues comme des résultats normaux. La plupart des bugs d’elicitation viennent du fait de ne gérer que la première.
Le scénario du support client
Imaginez une équipe support avec un helpdesk plein de tickets et une petite astreinte. Les agents travaillent dans Claude Code, et un serveur MCP appelé support-desk donne au modèle une seule action d’écriture : escalate_ticket. Il prend un identifiant de ticket et confie le dossier aux bons ingénieurs.
Pourquoi deviner échoue ici
Le modèle peut lire un ticket et en déduire une priorité. Il aura souvent raison. Quand il se trompe, une question de facturation atterrit dans le canal des incidents, ou une panne de connexion reste dans une file lente pendant la nuit. Vous pourriez élargir le schéma d’entrée de l’outil en espérant que le modèle remplisse correctement chaque champ, mais un appel d’outil avec des valeurs inventées ressemble exactement à un appel avec des valeurs réelles.
L’elicitation confie la décision à la personne qui en est responsable. Le modèle fournit l’identifiant du ticket. L’humain apporte le jugement.
Les champs que demande le serveur
Cinq champs suffisent. Au-delà, les agents commencent à fermer la boîte de dialogue sans la lire.
Champ
Type
Raison de sa présence
priority
enum à sélection unique
Oriente vers la bonne file d’astreinte
area
enum à sélection unique
Désigne l’équipe responsable
affectedCustomers
entier, de 1 à 10 000
Distingue un utilisateur isolé d’un incident massif
customerEmail
string, format email
Permet à l’ingénieur de faire le suivi
outage
boolean
Ouvre le canal de l’incident
Seuls priority et area sont obligatoires. Les autres ont des valeurs par défaut, si bien qu’un agent pressé peut valider et passer à la suite.
💡 Gardez le formulaire court. Chaque champ supplémentaire est une raison pour l’agent d’appuyer sur annuler.
Construire le serveur de support
Le serveur tient dans un seul fichier TypeScript, un transport stdio et deux petites dépendances.
Régler type sur module permet au fichier d’utiliser await au niveau supérieur et les imports ES.
L’outil d’escalade
Enregistrez ceci sous 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());
Remarquez que mode est absent de l’appel elicitInput. Le mode formulaire est celui par défaut, ce qui garde la requête lisible pour les anciens clients.
Vérifier d’abord les capacités du client
Les premières lignes du gestionnaire comptent plus qu’il n’y paraît. Un client qui prend en charge l’elicitation déclare une capacité elicitation lors de l’initialisation. Un objet elicitation vide compte uniquement comme mode formulaire, et un serveur ne doit jamais envoyer un mode que le client n’a pas déclaré.
Si la capacité est absente, le serveur renvoie un message d’erreur en texte brut au lieu de se bloquer. Le modèle lit ce message et demande à l’agent dans le chat. Un outil qui échoue poliment vaut mieux qu’un outil qui gèle.
💡 Dans un serveur stdio, n’écrivez jamais sur la sortie standard. Les lignes console.log parasites corrompent le flux du protocole. Envoyez les messages de débogage avec console.error.
Configurer Claude Code pas à pas
Une fois le serveur écrit, Claude Code doit savoir qu’il existe.
Enregistrer le serveur
Depuis n’importe quel dossier, ajoutez-le avec la CLI. Tout ce qui suit le double tiret est la commande qui lance le serveur :
claude mcp add support-desk -- npx -y tsx /absolute/path/to/support-desk-mcp/src/server.ts
Pour partager la configuration avec vos coéquipiers, ajoutez --scope project. Claude Code écrit alors un fichier .mcp.json à la racine du dépôt :
💡 Sous Windows natif, encapsulez le lanceur : claude mcp add support-desk -- cmd /c npx -y tsx C:\path\to\src\server.ts.
Confirmer la connexion
Deux vérifications vous indiquent que le serveur fonctionne :
claude --version
claude mcp list
Dans une session, /mcp ouvre le panneau des serveurs avec l’état de connexion. Vous devriez voir support-desk listé comme connecté.
Si un serveur cesse de se connecter après une mise à jour, consultez le journal des modifications de Claude Code. Les notes de la version 2.1.287, qui a ajouté les invites d’URL venant des serveurs sur le protocole 2025-11-25, indiquent d’ajouter "bareElicitationCapability": true à l’entrée de configuration de ce serveur lorsqu’il ne se connecte plus.
Lancer le flux d’escalade
Démarrez une session dans votre projet et tapez une demande simple :
Escalate ticket SUP-1042 using the support-desk tool.
Claude appelle mcp__support-desk__escalate_ticket. L’outil se met en pause, Claude Code affiche le formulaire, et l’agent renseigne la priorité, le domaine, le nombre de clients concernés, l’e-mail et l’indicateur de panne.
Une fois que l’agent a validé, l’outil reprend et renvoie quelque chose comme Escalated SUP-1042 as p1 in login. Reference ESC-LQ3F9A2. Le modèle peut citer cette référence directement dans sa réponse.
Claude Code expose aussi les hooks Elicitation et ElicitationResult, filtrés sur le nom du serveur. Un script peut répondre seul à un formulaire connu, ou journaliser chaque réponse avant qu’elle n’atteigne le serveur. Réservez cela aux formulaires à faible risque dans l’automatisation, et gardez une personne devant tout ce qui touche aux clients.
Gérer le refus, l’annulation et les mauvaises saisies
Les démos du cas idéal masquent la partie qui détermine si les agents font confiance à l’outil. Les gens ferment les boîtes de dialogue, changent d’avis et se trompent dans les valeurs.
Ce que chaque action doit déclencher
Action
Ce que la personne a fait
Ce que l’outil doit faire
accept
A soumis le formulaire
Revalider, puis créer l’escalade
decline
A refusé délibérément
Ne pas toucher au ticket, le signaler, proposer une voie manuelle
cancel
A fermé la boîte de dialogue
Ne rien modifier, autoriser une nouvelle tentative plus tard
Remarquez l’appel safeParse dans le serveur. Les clients doivent valider les réponses par rapport au schéma, et les serveurs doivent le refaire. Les valeurs par défaut et les formats ne sont que des indications pour l’interface, pas des garanties sur les données reçues.
Renvoyez un résultat isError en cas de mauvaise saisie au lieu de lever une exception. Le modèle voit le message et peut demander à l’agent de relancer l’outil.
Les erreurs qui cassent l’elicitation
La plupart des échecs proviennent d’une courte liste de mauvaises habitudes.
Données sensibles dans les formulaires
La spécification est claire : les serveurs ne doivent pas demander de mots de passe, de jetons API ni d’identifiants de paiement via le mode formulaire. Les réponses du formulaire transitent par le client, elles peuvent donc finir dans des journaux et des transcriptions. Tout élément secret relève du mode URL, où la personne le saisit sur une page que le client ne peut pas lire.
Un nom ou une adresse e-mail, c’est différent. Le serveur peut les demander, et la personne peut les relire et refuser.
Schémas imbriqués
Un requestedSchema avec un objet imbriqué ou une liste d’objets sera rejeté ou mal affiché. Aplatissez-le. Si vous avez besoin d’une liste d’éléments, lancez plusieurs petites elicitations ou acceptez un enum à sélection multiple.
Autres habitudes qui posent problème :
Traiter cancel comme decline, si bien qu’une boîte fermée ressemble à un refus.
Faire confiance à une identité saisie dans un formulaire. Identifiez les utilisateurs par l’autorisation, pas par un champ de texte.
Demander deux fois les mêmes détails dans une même session.
Oublier qu’un serveur distant doit lier son état à l’utilisateur, et pas seulement à un identifiant de session.
Une courte liste de tests
Lancez ces cinq cas avant que de vrais tickets ne touchent l’outil :
Valider avec les seules valeurs par défaut, et vérifier que affectedCustomers passe à 1.
Valider avec une adresse e-mail mal orthographiée, et vérifier que le serveur la rejette.
Refuser, et vérifier que le ticket n’a pas changé.
Annuler avec la touche Échap, et vérifier que rien n’est écrit.
Se connecter depuis un client sans elicitation, et vérifier que le message de repli en texte brut apparaît.
Vous pouvez aussi lancer le serveur sous le MCP Inspector depuis le dossier du projet avec npx @modelcontextprotocol/inspector npx tsx src/server.ts pour voir défiler les messages elicitation/create bruts.
Rédiger des réponses avec Claude Sonnet 5
Une fois que l’escalade renvoie une référence, l’agent doit encore répondre au client. Claude Sonnet 5 sur PicassoIA prend en charge cette étape de rédaction, et il lit aussi les captures d’écran, ce qui est utile quand un client joint une image d’erreur.
Ouvrez la page de Claude Sonnet 5 et collez le résumé du ticket ainsi que la référence de l’escalade dans Prompt.
Renseignez System Prompt une seule fois : « Vous rédigez des réponses de support courtes et calmes. Ne promettez jamais de délai de correction. »
Laissez Effort sur low pour les brouillons rapides. Montez-le à medium ou high quand le ticket demande un vrai raisonnement. Selon la page du modèle, low désactive la réflexion pour des réponses plus rapides et moins coûteuses.
Baissez Max Tokens depuis la valeur par défaut de 8 192 à environ 600 pour que les réponses restent courtes.
Joignez une capture d’écran dans Image si le client en a envoyé une. Max Image Resolution vaut 0,5 mégapixel par défaut, ce qui suffit largement pour une boîte de dialogue d’erreur.
Paramètre
Valeur par défaut
Conseil pour les réponses de support
Effort
faible
À monter uniquement pour les bugs complexes
Max Tokens
8 192
Régler vers 600
System Prompt
vide
Fixez le ton et les limites une seule fois
Max Image Resolution
0,5 MP
Gardez tel quel pour les captures d’écran
Pour un raisonnement plus poussé, Claude Opus 4.7 figure dans la même collection. Commencez par Sonnet 5 et ne passez à un modèle supérieur que lorsqu’un brouillon passe à côté du sujet.
Créer vos propres images avec Picasso IA
Les documentations de support et les articles du centre d’aide gagnent à être illustrés de vraies images. Les scènes de bureau que vous avez vues plus haut, un casque posé sur une table ou un presse-papiers sous une lumière de fenêtre, se créent avec un seul prompt.
Essayez PicassoIA Image pour un premier jet rapide, ou Flux 2 Pro si vous voulez une texture plus fine. Un prompt qui fonctionne 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.
Choisissez un modèle, collez le prompt, modifiez un seul détail à chaque essai et comparez les résultats. Dix minutes d’expérimentation vous apprendront plus sur la formulation des prompts que n’importe quelle liste de règles. Ouvrez Picasso IA, créez votre première image d’en-tête et insérez-la dans votre prochain article de support.