Concevoir des outils MCP que les LLM utilisent réellement correctement
Avancé12 min de lectureIA pour les entreprises

Concevoir des outils MCP que les LLM utilisent réellement correctement

La plupart des outils MCP que nous rencontrons sont techniquement corrects, mais pratiquement inutiles. Les LLM les ignorent, les utilisent mal ou les appellent sans discernement. Voici les principes permettant de concevoir des outils qu’ils adoptent naturellement, avec des exemples d’erreurs courantes et leurs corrections.

Ce que vous saurez faire

La conception d’outils pour les LLM s’apparente davantage à la conception UX qu’à celle d’une API. Le LLM est votre utilisateur et la description de l’outil constitue votre interface. Les bons outils sont souvent employés correctement ; les mauvais sont ignorés, détournés ou mal enchaînés. Voici les principes essentiels.

AI Expert TeamPublié: 15 mai 2026
Enregistré uniquement dans ce navigateur.
Dans cet article

Vous avez construit un serveur MCP. Ses outils fonctionnent. Vous le connectez à un agent LLM et, en observant son comportement, vous constatez qu’il ignore vos outils, les appelle avec des paramètres étranges, hésite sur celui à utiliser ou les enchaîne de manière incohérente.

C’est l’écart entre les « outils qui existent » et les « outils que les LLM utilisent correctement ». Une grande partie du travail consacré aux serveurs MCP est ainsi gaspillée : les entreprises créent des capacités puissantes, les exposent sous forme d’outils, puis voient les LLM échouer à les exploiter efficacement.

Le changement de perspective utile consiste à aborder la conception des outils comme une démarche UX dans laquelle le LLM est votre utilisateur. La description de l’outil est l’interface, son schéma est le formulaire et ses messages d’erreur constituent le retour d’information. Concevez-les correctement et les LLM travailleront efficacement ; négligez-les et votre système dorsal sophistiqué restera invisible pour l’agent.

Cet article présente ces principes à l’aide d’exemples concrets de ce qui fonctionne et de ce qui échoue.

Principe 1 : les noms des outils communiquent l’intention

Le nom de l’outil est le premier élément que voit le LLM. Il doit en décrire la fonction au moyen d’un verbe d’action.

Mauvais :

  • customers (nom, pas d’action)
  • process_customer (vague)
  • do_x (sans signification)

Mieux :

  • search_customers (action claire)
  • get_customer_by_id (opération spécifique)
  • update_customer_email (modification spécifique)

Pourquoi est-ce important ? Les LLM parcourent les listes d’outils à la recherche de ceux qui sont pertinents. Un nom descriptif leur permet de trouver rapidement l’outil approprié. Un nom vague les contraint à lire attentivement sa description, ce qu’ils ne font pas toujours.

Une convention utile consiste à employer des préfixes verbaux standard :

  • list_*, search_*, get_* pour les lectures.
  • create_*, update_*, delete_* pour les écritures.
  • analyze_*, summarize_* pour les calculs.

La cohérence à l’échelle du serveur aide le LLM à se construire un modèle mental.

Principe 2 : les descriptions sont des prompts

La description d’un outil est le texte le plus important de votre serveur. Le LLM s’en sert pour décider s’il doit utiliser l’outil et de quelle manière.

Mauvaise description :

search_customers: Search the customer database.

Meilleure description :

search_customers: Find customers by name, email, or company. Returns up to 10 matching customers with their basic info. Use this when you need to identify a customer the user is referring to. For exact lookups by ID, use get_customer_by_id instead.

Observez ce que fait la version améliorée :

  • Elle décrit les entrées (« par nom, adresse e-mail ou entreprise »).
  • Elle décrit les sorties (« jusqu’à 10 clients correspondants avec leurs informations de base »).
  • Elle indique quand utiliser l’outil (« lorsque vous devez identifier un client auquel l’utilisateur fait référence »).
  • Elle indique quand ne pas l’utiliser (« pour une recherche exacte par identifiant, utilisez plutôt get_customer_by_id »).

La partie « quand ne pas l’utiliser » est essentielle. Sans elle, le LLM pourrait appeler search_customers alors que get_customer_by_id conviendrait mieux.

Principe 3 : les descriptions des paramètres sont importantes

Chaque paramètre doit posséder une description. Ne vous fiez pas uniquement à son nom.

Mauvais :

{
  customer_id: string,
  fields: string[]
}

Meilleur :

{
  customer_id: string,  // "The customer's unique identifier. Get this from search_customers or from explicit user input."
  fields: string[]      // "Specific fields to return. Available: name, email, phone, tier, created_at, last_active. If not specified, returns name and email."
}

Les descriptions :

  • Indiquent au LLM comment obtenir la valeur.
  • Spécifient les valeurs autorisées si nécessaire.
  • Indiquent les valeurs par défaut.

Principe 4 : les erreurs guident la reprise

Lorsqu’un outil génère une erreur, son message guide l’action suivante du LLM. Un message vague désoriente l’agent.

Mauvaise erreur :

{ "error": "Invalid input" }

Meilleure erreur :

{
  "error": "validation_error",
  "message": "The email '...' is not in a valid format. It must be like 'name@example.com'.",
  "field": "email",
  "suggestion": "Ask the user for a valid email address."
}

Le LLM sait maintenant :

  • Ce qui s’est passé (erreur de validation sur le champ e-mail).
  • Comment la corriger (utiliser un format d’e-mail valide).
  • Que faire ensuite (demander à l’utilisateur).

Comparez le comportement de l’agent face à ces deux erreurs. Avec la première, il peut répéter le même appel, abandonner ou inventer une entrée valide. La seconde conduit à une interaction claire avec l’utilisateur.

Principe 5 : la sortie détermine l’action suivante

La sortie de l’outil détermine ce que le LLM fera ensuite. Sa conception influence donc le comportement de l’agent.

Mauvaise sortie pour une recherche :

[
  {"id": "c1", "n": "John", "e": "john@..."},
  {"id": "c2", "n": "Jane", "e": "jane@..."}
]

Meilleure sortie :

{
  "customers": [
    {"id": "c1", "name": "John Smith", "email": "john@example.com", "tier": "pro"},
    {"id": "c2", "name": "Jane Doe", "email": "jane@example.com", "tier": "free"}
  ],
  "total_found": 2,
  "summary": "Found 2 customers matching 'john'. Note that one is named 'Jane Doe' but has 'john' in their email."
}

La meilleure sortie :

  • Utilise des noms de champs lisibles.
  • Fournit un contexte complémentaire (total_found).
  • Comprend un résumé en langage naturel qui aide le LLM à formuler sa prochaine réponse.

Le champ summary est particulièrement utile : il fournit au LLM une indication complémentaire sur la manière d’interpréter le résultat.

Principe 6 : un outil, une fonction

Les outils multifonctions désorientent les LLM. Le modèle doit décider à la fois s’il doit utiliser l’outil et dans quel mode.

Confus :

manage_customer:
  - mode: "search" | "get" | "update" | "delete"
  - params: depends on mode

Le LLM doit choisir un mode et se trompe souvent. Plus grave encore, le schéma des paramètres devient complexe puisqu’il varie selon le mode.

Mieux : des outils distincts.

search_customers: search by name/email/company
get_customer: get details by ID
update_customer: update specific fields
delete_customer: archive a customer

Chaque outil est dépourvu d’ambiguïté. Le LLM choisit celui qui correspond à l’intention, et les schémas restent simples.

Cela augmente le nombre d’outils, mais chacun devient plus clair. Le LLM gère mieux 10 outils explicites que 3 outils comportant plusieurs modes.

Principe 7 : contraindre les entrées

Dans la mesure du possible, restreignez les valeurs d’entrée. Les énumérations et la validation empêchent le LLM d’en inventer.

Sans contrainte :

{
  status: string  // could be anything
}

Avec contrainte :

{
  status: "active" | "trial" | "churned" | "suspended"
}

La contrainte est appliquée au niveau du schéma ; la génération contrainte empêche le LLM de produire des valeurs non valides.

Le même principe s’applique aux énumérations d’opérations, de niveaux de gravité et de types, ainsi qu’à tout paramètre dont les valeurs valides sont connues.

Pour les dates, utilisez le format ISO 8601 et précisez-le dans la description (« Date au format ISO 8601, par exemple 2026-05-15 »). À défaut, les LLM produisent des dates dans des formats variables.

Principe 8 : les valeurs par défaut réduisent les hallucinations

Lorsque des paramètres possèdent des valeurs par défaut judicieuses, rendez-les facultatifs et appliquez ces valeurs côté serveur.

Mauvais :

{
  query: string,
  limit: number,  // LLM has to provide some value
  include_archived: boolean,
  sort_by: string
}

Le LLM doit choisir une valeur pour chacun de ces paramètres, au risque de se tromper.

Meilleur :

{
  query: string,
  limit: number = 10,            // sensible default
  include_archived: boolean = false,  // safe default
  sort_by: "relevance" | "name" | "created_at" = "relevance"  // most common
}

Le LLM ne renseigne alors que les paramètres pertinents pour la requête concernée. Moins il doit en fournir, moins le risque de confusion est élevé.

Documentez les valeurs par défaut dans la description : « Limite : nombre de résultats à renvoyer. Valeur par défaut : 10 ; maximum : 50. »

Principe 9 : la composabilité est importante

Les outils doivent pouvoir s’assembler en flux de travail que le LLM sait construire. Une granularité appropriée simplifie les tâches complexes.

Prenons la tâche suivante : « Présentez-moi tous les tickets ouverts de nos trois principaux clients. »

Mauvais ensemble d’outils :

get_customer_summary(customer_id): returns customer + tickets + activity all in one

Le LLM ne peut pas facilement appliquer le filtre des trois principaux clients : cet outil renvoie toutes les données d’un seul client à la fois. Pour accomplir la tâche, le modèle doit d’abord déterminer quels sont les principaux clients, puis appeler cet outil trois fois.

Meilleur ensemble d’outils :

list_customers(sort_by="value", limit=N): returns customer summaries with priority info
list_tickets(customer_id, status): returns tickets for a customer

Le LLM peut composer le flux naturellement : répertorier les principaux clients, puis lister les tickets ouverts de chacun.

Le principe consiste à raisonner en termes de flux de travail faisant appel à plusieurs outils. Les outils qui se combinent bien sont utilisables ; ceux qui se combinent mal ne le sont souvent pas.

Principe 10 : communiquer l’idempotence

Pour les outils d’écriture, précisez les exigences d’idempotence dans la description :

create_invoice: Create a new invoice for a customer.
IMPORTANT: Pass an idempotency_key (a UUID you generate). If you retry this operation, use the same UUID to prevent duplicate invoices.

Parameters:
- amount: ...
- customer_id: ...
- idempotency_key: UUID to prevent duplicate creation on retry. Generate once per logical operation.

Le LLM sait désormais qu’il doit générer un UUID et réutiliser le même en cas de nouvelle tentative.

Sans cette indication, le LLM pourrait omettre la clé, supprimant ainsi l’idempotence, ou générer un nouvel UUID à chaque tentative, ce qui en annulerait l’intérêt.

Principe 11 : mentionner les préconditions et les postconditions

Pour les outils assortis de préconditions ou d’effets secondaires importants, documentez-les :

delete_customer: Archive a customer record. This is reversible within 30 days; after 30 days, the data is permanently deleted.

PRECONDITIONS:
- Customer must have no active subscriptions.
- Customer must have no open tickets.

If preconditions are not met, this tool returns an error indicating what to resolve first.

SIDE EFFECTS:
- All customer's contacts are also archived.
- Customer is removed from active reports.
- An audit log entry is created.

Le LLM sait ainsi ce qu’il doit vérifier avant l’appel et à quoi s’attendre ensuite. Il peut correctement planifier des flux de travail en plusieurs étapes, par exemple « fermer d’abord les tickets, puis supprimer le client ».

Principe 12 : en cas de doute, fournir des exemples

Pour les outils complexes, l’ajout d’un exemple dans la description est utile :

analyze_funnel: Analyze a conversion funnel from event data.

Parameters:
- start_date: ISO 8601 date
- end_date: ISO 8601 date  
- steps: array of step definitions, each {event_name: string, filters?: object}

Example:
{
  "start_date": "2026-01-01",
  "end_date": "2026-01-31",
  "steps": [
    {"event_name": "signup"},
    {"event_name": "first_login"},
    {"event_name": "first_action", "filters": {"action_type": "create_project"}},
    {"event_name": "subscription_started"}
  ]
}

Les exemples enseignent mieux la structure au LLM que les seuls schémas.

Principe 13 : ne pas exposer les détails internes

Le LLM n’a pas besoin de connaître la structure de votre base de données ni ses identifiants internes. Exposez un modèle conceptuel clair.

Mauvais :

get_user_by_pk(pk: number)

Le LLM doit alors savoir qu’il faut utiliser la « clé primaire », un concept propre à la base de données.

Meilleur :

get_user(user_id: string)

Masquez le concept propre à la base de données. Le LLM utilise un user_id, notion pertinente pour l’utilisateur.

De même, n’exposez ni champs obsolètes, ni indicateurs internes, ni paramètres de débogage, ni aucun élément qui relève de votre implémentation plutôt que du concept présenté à l’utilisateur.

Principe 14 : éviter les chaînes magiques

Certains outils exigent des chaînes qui ressemblent à des commandes ou à des codes. Elles sont sources d’erreurs.

Mauvais :

modify_record(record_id: string, change_string: string)
// where change_string is like "field1=value1;field2=value2"

Le LLM doit encoder les modifications dans un format de chaîne précis, ce qui l’expose à des erreurs.

Meilleur :

update_record(record_id: string, updates: { field1?: any; field2?: any; ... })

Structurez plutôt les mises à jour sous forme d’objet. Le LLM peut alors utiliser directement chaque champ.

Principe 15 : tester avec de véritables LLM

Les descriptions d’outils peuvent sembler claires aux humains tout en désorientant les LLM. Le seul moyen de le savoir consiste à les tester.

Voici un flux de travail utile :

  1. Construisez l’outil.
  2. Demandez à un agent LLM de réaliser plusieurs tâches réalistes en utilisant uniquement vos outils.
  3. Observez les échecs.
  4. Ajustez les descriptions en fonction des échecs.
  5. Répétez.

Vous observerez notamment les schémas suivants :

  • Le LLM utilise le mauvais outil → le nom ou la description de l’outil manque de clarté.
  • Le LLM transmet des valeurs de paramètres incorrectes → la description ou le schéma des paramètres doit être amélioré.
  • Le LLM abandonne après une erreur → les messages d’erreur doivent être améliorés.
  • Le LLM n’essaie pas un outil qui pourrait l’aider → l’outil est mal présenté ou mal nommé.

Chaque problème suggère une correction spécifique.

Diagnostic : signes d’une mauvaise conception des outils

Voici quelques comportements révélateurs de problèmes de conception :

Le LLM utilise fréquemment le mauvais outil. Vous le verrez appeler search_customers alors qu’il devrait appeler get_customer_by_id. Solution : précisez quel outil convient à chaque situation.

Le LLM appelle de nombreux outils pour accomplir une seule tâche. Il enchaîne 5 appels alors qu’un seul devrait suffire. Solution : envisagez un outil composite de niveau supérieur ou augmentez la granularité.

Le LLM abandonne après une erreur. Il essaie une fois, reçoit une erreur, puis informe l’utilisateur qu’il ne peut pas l’aider. Solution : améliorez les messages d’erreur afin qu’ils suggèrent les prochaines étapes.

Le LLM invente des valeurs de paramètres. Il fabrique des user_ids, des dates ou des identifiants. Solution : précisez comment obtenir des valeurs valides, ajoutez des contraintes et gérez les erreurs de manière à détecter et expliquer le problème.

Le LLM répète le même appel en échec. La même erreur se reproduit. Solution : le message d’erreur n’indique pas assez précisément la cause au LLM.

Le LLM n’utilise pas un outil puissant. Vous avez créé un excellent outil, mais le LLM ne l’appelle jamais. Solution : facilitez sa découverte grâce à un nom plus clair, une meilleure description et une indication du type « utilisez cet outil lorsque… ».

Taxonomie des outils

Un exercice utile consiste à organiser les outils selon une taxonomie.

Read tools (safe, idempotent):
- search_customers
- get_customer_by_id
- list_tickets
- list_orders

Compute tools (no state changes):
- summarize_account_activity
- analyze_funnel
- calculate_lifetime_value

Write tools (state changes, need idempotency):
- create_customer
- update_customer_email
- create_ticket
- send_email

Destructive tools (require careful authorization):
- delete_customer
- cancel_subscription
- archive_record

La taxonomie vous aide à :

  • Appliquer les garde-fous appropriés, tels que l’idempotence et la confirmation pour les outils destructifs.
  • Documenter les catégories dans les prompts système du LLM.
  • Détecter les outils manquants : si une catégorie est vide, faut-il en créer un ?

Voici un ajout utile au prompt système :

Tool categories available:
- READ tools (safe to call): search_customers, get_customer_by_id, ...
- COMPUTE tools (no side effects): summarize_account_activity, ...
- WRITE tools (side effects, include idempotency_key): create_customer, ...
- DESTRUCTIVE tools (require human confirmation): delete_customer, ...

Before calling a WRITE or DESTRUCTIVE tool, confirm with the user.

Cette consigne façonne l’utilisation des outils par le LLM à l’échelle du flux de travail, et non uniquement de chaque appel.

Exemples d’améliorations courantes

Voici des exemples avant-après qui concrétisent ces principes :

Exemple 1 : un outil de recherche

Avant :

// search documents
{
  name: "documents",
  description: "Search documents",
  inputSchema: { query: "string" }
}

Après :

{
  name: "search_documents",
  description: `Search internal documents (knowledge base, wiki pages, policies). 
  Returns matching documents with title, excerpt, and link. Use when the user asks about company policies, procedures, or internal documentation. Returns up to 10 most relevant matches by semantic similarity.`,
  inputSchema: {
    query: {
      type: "string",
      description: "Search query. Be specific. Good: 'remote work policy 2026'. Bad: 'documents about work'."
    },
    document_type: {
      type: "string",
      enum: ["policy", "procedure", "guide", "faq", "any"],
      default: "any",
      description: "Filter to a specific type of document."
    },
    limit: {
      type: "number",
      default: 5,
      maximum: 10,
      description: "Number of results."
    }
  }
}

Exemple 2 : un outil d’action

Avant :

{
  name: "send_email",
  description: "Send an email",
  inputSchema: {
    to: "string",
    subject: "string",
    body: "string"
  }
}

Après :

{
  name: "draft_email_to_customer",
  description: `Draft an email to a customer based on a recent interaction. The email is saved as a draft for human review before sending — it is NOT sent automatically. The user must approve drafts in their inbox.

  Use when:
  - You've identified an action requiring follow-up with the customer.
  - You have a specific reason and content for the email.
  
  Do NOT use:
  - To send marketing or promotional content.
  - Without explicit user request.
  - To respond to refund or cancellation requests (escalate to human instead).`,
  inputSchema: {
    customer_id: {
      type: "string",
      description: "Customer ID from search_customers or get_customer."
    },
    subject: {
      type: "string",
      description: "Email subject, 4-8 words, specific. Avoid generic subjects like 'Following up'."
    },
    body: {
      type: "string",
      description: "Email body, plain text. 3-5 sentences. Personal, specific, not template-y."
    },
    tone: {
      type: "string",
      enum: ["professional", "friendly", "apologetic", "urgent"],
      default: "professional",
      description: "Tone of the email."
    },
    idempotency_key: {
      type: "string",
      description: "UUID for this draft. Use the same UUID if retrying to avoid duplicates."
    }
  }
}

Les versions « Après » guident le LLM beaucoup plus efficacement. Elles peuvent sembler verbeuses, mais cette précision est utile.

À retenir

La conception d’outils pour les LLM est une discipline à part entière. Ses principes ne sont pas intuitifs : ils exigent de considérer le LLM comme votre utilisateur et de concevoir l’interface en conséquence.

Les principes essentiels sont les suivants :

  • Noms d’outils fondés sur des verbes d’action.
  • Descriptions détaillées précisant la fonction de l’outil, quand l’utiliser et quand ne pas le faire.
  • Descriptions de chaque paramètre accompagnées d’exemples et de contraintes.
  • Messages d’erreur structurés et exploitables.
  • Sorties qui guident l’action suivante.
  • Un outil par concept.
  • Valeurs par défaut judicieuses.
  • Une granularité composable.
  • Idempotence explicitement documentée.
  • Préconditions et postconditions documentées.
  • Exemples pour les outils complexes.
  • Détails internes masqués.
  • Tests avec de véritables LLM.

La plupart des serveurs MCP échouent non parce que le protocole est difficile, mais parce que leurs outils n’ont pas été conçus en tenant compte du LLM. Une bonne conception rend le serveur efficace ; une mauvaise réduit à néant les efforts investis dans un système dorsal sophistiqué.

Traitez le LLM comme votre utilisateur et concevez vos outils en conséquence. Cet investissement améliore considérablement la manière dont ils sont réellement utilisés.

À lire ensuite

Continuez sur le même parcours d'apprentissage avec les prochains articles pratiques.