Conception de surfaces d’outil à efficacité en tokens pour des serveurs MCP agents
Apprenez à regrouper des dizaines de définitions d’outils MCP en un petit nombre d’outils axés sur un domaine et orientés vers des actions, sans perdre aucune fonctionnalité de base.
Sources: **From 30 Tools to 3: Designing a Token-Efficient MCP Tool Surface**
Le problème typique avec les systèmes d’agents modernes n’est pas un manque d’outils.
C’est plutôt l’existence de trop nombreux outils.
Au fur et à mesure que les capacités de l’agent s’étendent, il est courant de le connecter à Jira, GitLab, Confluence, Sentry, Elasticsearch, Jaeger, diverses bases de données, des tableaux de bord de surveillance, des API internes, des pipelines de déploiement et d’innombrables autres systèmes.
Chacune de ces intégrations expose généralement une longue liste d’opérations :
Jira
├── searchIssues
├── getIssue
├── createIssue
├── updateIssue
├── addComment
├── transitionIssue
├── getTransitions
├── assignIssue
└── ...
GitLab
├── listProjects
├── getProject
├── listIssues
├── createIssue
├── updateIssue
├── listMergeRequests
├── getMergeRequest
├── createComment
└── ...
Confluence
├── searchPages
├── getPage
├── createPage
├── updatePage
└── ...
Il ne faut pas longtemps pour devoir gérer 30, 50 ou même des centaines de définitions d’outils.
À première vue, cela semble être un problème de capacités brutes.
En réalité, il s’agit d’un problème de surface d’outils.
L’agent n’a pas besoin de pouvoir faire moins de choses.
Ce dont il a besoin, c’est de moins de outils de niveau supérieur parmi lesquels choisir.
Cet article présente une méthode pour réduire les interfaces MCP ou les outils surchargés à un petit nombre d’outils axés sur un domaine donné, sans perdre aucune des fonctionnalités de base.
L’idée principale est simple :
Réduisez l’interface de l’outil, pas l’ensemble des capacités.
Au lieu de mettre à disposition quelque chose comme :
30 MCP tools
on peut mettre à disposition :
3–5 domain tools
et s’appuyer sur un champ action pour diriger chaque appel en interne.
Ainsi, un ensemble comme :
jira_search
jira_get_issue
jira_create_issue
jira_update_issue
jira_add_comment
jira_transition_issue
...
se transforme en quelque chose comme :
jira({
action: "search",
...
})
jira({
action: "getIssue",
...
})
jira({
action: "createIssue",
...
})
Le backend conserve toutes les capacités originales.
Cependant, du point de vue du modèle, l’interface de l’outil est considérablement plus petite.
1. Le problème des interfaces d’outils trop grandes
Un serveur MCP n’est pas uniquement un mécanisme d’exécution.
Ce dernier fait également partie de ce que le modèle doit conserver en contexte.
Chaque fois qu’un agent se connecte à un serveur MCP, le modèle doit généralement traiter :
- les noms des outils
- leurs descriptions
- les schémas d’entrée
- les paramètres
- les ensembles de valeurs possibles
- les contraintes
- parfois des métadonnées supplémentaires
Imaginez un agent relié à 40 outils différents.
Même si chaque schéma individuel est compact, l’ensemble peut rapidement devenir volumineux.
De plus, le modèle doit maintenant faire face à un éventail de décisions plus important :
User request
│
▼
Which tool?
│
┌───┼────┬────┬────┬────┐
▼ ▼ ▼ ▼ ▼ ▼
T1 T2 T3 T4 T5 ...
Il doit distinguer de nombreuses opérations qui sont conceptuellement proches.
Prenons cet exemple :
jira_search_issues
jira_search_projects
jira_get_issue
jira_get_issue_comments
jira_get_issue_transitions
jira_get_issue_worklogs
Tous ces éléments appartiennent au même domaine conceptuel.
Considérer chacun d’eux comme une capacité de niveau supérieur entièrement indépendante n’apporte que peu de valeur.
2. L’idée principale : le multiplexage des outils
Le truc consiste à ajouter une couche intermédiaire, un champ de discrimination :
{
"action": "search",
"query": "authentication bug"
}
Plutôt que de définir :
jira_search
on ne montre que :
jira
L’outil lui-même devient un routeur léger.
Structuralement, cela ressemble à :
jira
│
action field
│
┌──────────────┼──────────────┐
▼ ▼ ▼
search getIssue createIssue
│ │ │
▼ ▼ ▼
searchHandler issueHandler createHandler
L’idée clé est que l’outil n’est pas la même chose que la capacité.
L’outil n’est qu’une interface orientée vers l’extérieur.
C’est le champ d’action qui transporte réellement la capacité.
Résultat :
30 capabilities
↓
3 domain-oriented tools
sans perte de fonctionnalités au cours du processus.
3. Utiliser des limites de domaine, pas des regroupements arbitraires
Une erreur fréquente consiste à raisonner comme suit :
"J’ai 30 outils, donc je regrouperai 10 opérations dans chacun d’eux."
Cela n’est pas le but.
Le regroupement doit être guidé par des domaines sémantiques, et non par des tailles de catégories arbitraires.
Par exemple :
jira
├── search
├── issue
├── comment
├── transition
└── project
gitlab
├── project
├── issue
├── mergeRequest
└── pipeline
observability
├── search
├── trace
├── log
└── error
Le regroupement idéal varie en fonction de l’intégration.
Pour une base de données, quelque chose comme :
db_tables
db_query
db_advanced
peut fonctionner très bien.
Pour Jira, vous préférerez peut-être :
jira_issues
jira_projects
jira_search
Pour GitLab, un agencement tel que :
gitlab_repository
gitlab_issues
gitlab_mergeRequests
pourrait être plus adapté.
Il n’existe pas de nombre fixe qui fonctionne partout.
L’objectif est de trouver la surface d’outil la plus réduite qui respecte néanmoins des limites sémantiques claires et significatives.
4. Un exemple de base de données
Un connecteur de base de données constitue une bonne illustration, car les API de bases de données ont tendance à exposer une longue liste d’opérations individuelles.
Au lieu de définir des entrées séparées telles que :
listTables
getTableSchema
getSampleData
getTableSize
executeQuery
getDatabaseInfo
listRelationships
getIndexes
profileColumn
searchSchema
listProcedures
getTriggers
compareSchemas
vous pouvez les regrouper en :
db_tables
db_query
db_advanced
Le premier de ces outils peut s’appuyer sur un schéma tel que :
const tablesSchema = z.discriminatedUnion("action", [
z.object({
action: z.literal("list"),
schema: schemaField,
}),
z.object({
action: z.literal("schema"),
tableName: z.string(),
schema: schemaField,
}),
z.object({
action: z.literal("sampleData"),
tableName: z.string(),
schema: schemaField,
rowCount: z.number().optional().default(10),
}),
z.object({
action: z.literal("size"),
tableName: z.string(),
schema: schemaField,
}),
]);
Du point de vue du modèle, il n’y a qu’un seul outil :
db_tables
appuyé par un ensemble clairement défini d’actions possibles :
list
schema
sampleData
size
Au niveau du runtime, les appels sont tout de même acheminés vers quatre gestionnaires distincts :
switch (args.action) {
case "list":
return handleListTables(args.schema);
case "schema":
return handleGetTableSchema(
args.tableName,
args.schema
);
case "sampleData":
return handleGetSampleData(
args.tableName,
args.schema,
args.rowCount
);
case "size":
return handleGetTableSize(
args.tableName,
args.schema
);
}
Cette séparation est importante :
La fusion a lieu au niveau de l’interface MCP, et non au sein de la logique métier sous-jacente.
Chaque gestionnaire interne reste testable de manière indépendante et facile à maintenir.
5. Les unions discriminées de Zod sont une solution idéale
Dans les bases de code TypeScript, z.discriminatedUnion() offre un moyen simple de modéliser ce schéma.
Par exemple :
const querySchema = z.discriminatedUnion("action", [
z.object({
action: z.literal("execute"),
query: z.string(),
params: z.record(z.string()).optional(),
limit: z.number().optional().default(100),
}),
z.object({
action: z.literal("info"),
}),
]);
À partir de là, le type peut être dérivé directement :
type QueryInput = z.infer<typeof querySchema>;
Cette approche vous offre trois avantages pratiques :
- Un seul outil MCP.
- Un routage explicite basé sur le champ d’action.
- Une validation stricte appliquée en temps de exécution.
La structure globale de l’architecture ressemble à ceci :
MCP Tool
│
▼
Discriminated Union
│
┌─────┴─────┐
│ action │
└─────┬─────┘
│
┌────────┼────────┐
▼ ▼ ▼
Handler A Handler B Handler C
│ │ │
└────────┼────────┘
▼
Backend
C’est bien plus simple pour un modèle de naviguer ainsi que d’utiliser des dizaines d’outils de niveau supérieur non liés entre eux.
6. Le nombre d’outils n’est pas égal au nombre de capacités
C’est sans doute l’idée la plus importante de cette approche.
Supposons que vous obteniez :
3 tools
13 actions
Cela ne signifie pas que dix capacités ont disparu. Ce que vous avez réellement, c’est :
3 external interfaces
13 internal capabilities
En bref :
Tool count ≠ capability count
Cette distinction gagne en importance à mesure que les systèmes d’agents s’agrandissent. L’ensemble des outils d’une grande organisation pourrait ressembler à ceci :
Jira 20 operations
GitLab 25 operations
Confluence 15 operations
Sentry 8 operations
ELK 10 operations
Jaeger 6 operations
Database 20 operations
Cela peut facilement entraîner plus de 100 opérations individuelles. Exposer chacune d’elles directement au modèle crée une interface trop encombrée. Au lieu de cela, on peut les regrouper en quelque chose comme :
jira
gitlab
confluence
sentry
observability
database
tout en laissant le nombre total d’opérations sous-jacentes inchangé.
7. Pourquoi cela peut réduire l’utilisation de tokens
Le principal avantage réside dans la diminution du volume de métadonnées des outils que le modèle doit analyser. Au lieu de lister :
Tool 1
Tool 2
Tool 3
Tool 4
...
Tool 30
on présente :
Tool A
Tool B
Tool C
Cela peut réduire :
- les descriptions des outils
- les métadonnées de paramètres répétées
- les explications sémantiques redondantes
- la complexité du choix parmi les outils de niveau supérieur
- l’espace de contexte occupé par les définitions des outils
Cela dit, cela ne doit pas être présenté comme une économie proportionnelle garantie. Passer de :
30 tools → 3 tools
Ce n’est pas automatiquement traduit en :
90% lower token cost
car les schémas fusionnés peuvent eux-mêmes devenir plus volumineux. Une manière plus précise de le formuler :
Regrouper plusieurs opérations en un seul outil a tendance à réduire la quantité de métadonnées que l’agent doit lire, bien que l’économie réelle en termes de tokens ou de temps de réponse dépende de facteurs tels que la taille du schéma combiné, le degré de détail des descriptions des champs, les particularités du traitement des définitions d’outils par le fournisseur de modèles, ainsi que la manière spécifique dont votre framework d’agent gère les appels aux outils.
Cette nuance mérite d’être prise en compte.
8. Regrouper tout ensemble n’est pas automatiquement mieux
Il existe un compromis qu’il convient de souligner ici.
Imaginons un seul outil qui expose 40 actions différentes :
enterprise({
action: ...
})
avec des actions telles que :
createCustomer
deleteCustomer
searchInvoice
rotateCredentials
deployService
createRepository
getTrace
searchLogs
...
Cela est techniquement réalisable.
Cependant, cela constitue un mauvais design.
Le modèle est désormais bloqué dans l’analyse d’un schéma massif.
30 tools
à :
1 giant tool
La meilleure approche est la consolidation selon les lignes de domaine.
Par exemple :
customer
billing
repository
observability
deployment
Cinq outils pourraient être le bon objectif, et non un seul.
Cela mène à un principe directeur :
Réduisez la surface des outils, mais ne le faites pas de manière négligente.
9. Le schéma fait également partie de l’interface de l’agent
Lorsqu’un seul outil couvre plusieurs fonctionnalités, le paramètre action prend une grande importance.
Une version insuffisante :
action: z.string()
Une amélioration :
action: z.enum([
"search",
"get",
"create",
"update"
])
C’est l’option la plus fiable lorsque chaque action nécessite des paramètres distincts :
z.discriminatedUnion("action", [
searchSchema,
getSchema,
createSchema,
updateSchema,
])
Ici, l’action choisie et ses arguments attendus sont liés grâce à une véritable sécurité de type.
Par exemple :
action = "search"
→ query required
action = "get"
→ issueId requiredaction = "create"
→ title + description required
Cela est bien plus précis qu’un schéma à type lâche rempli de champs optionnels pour chaque action possible.
10. Complications des schémas JSON sous MCP
Il y a une complication supplémentaire dans la pratique.
Les schémas des outils MCP sont finalement compilés en schémas JSON.
En fonction du convertisseur utilisé, une union discriminée Zod peut générer :
{
"anyOf": [...]
}
ou bien :
{
"oneOf": [...]
}
Certaines parties du pipeline de schéma MCP partent du principe que la racine du schéma est un objet simple.
Ce principe n’est pas toujours valable.
Dans au moins une implémentation réelle, la couche de compatibilité Zod intégrée au SDK MCP a été conçue autour des formes d’objets et n’a pas réussi à gérer correctement une union discriminée.
La solution consiste à ajouter une étape de normalisation pour le schéma généré.
Conceptuellement, cela se présente comme suit :
Zod discriminated union
│
▼
JSON Schema
│
▼
MCP normalization
│
▼
MCP-compatible object schema
La leçon architecturale est la suivante :
Réorganisez le schéma que vous montrez au modèle sans toucher au niveau de strictesse de votre validateur réel.
Votre schéma Zod original doit rester la seule source de vérité pour la validation.
11. Deux schémas différents : un pour le modèle, un pour l’exécution
Cela souligne une distinction plus large qui mérite d’être clarifiée.
Il s’agit en réalité de deux tâches distinctes :
LLM-facing representation
│
▼
Tool selection and argument generation
Runtime representation
│
▼
Validation and execution
Le schéma affiché à l’LLM doit être :
- facile à lire
Le schéma utilisé en temps de exécution doit être :
- strict
- sûr
- prévisible
- final
Ces deux représentations n’ont pas besoin de correspondre exactement. Une forme d’objet simplifiée et aplatie peut aider un modèle à choisir l’action appropriée plus facilement, tandis que l’union discriminée sous-jacente assure toujours la validation en arrière-plan.
Cela se généralise en une règle plus large pour les systèmes d’agents :
Ajustez les schémas en fonction de la manière dont les modèles les lisent, mais ne comptez jamais uniquement sur le schéma destiné au modèle comme unique ligne de défense.
12. Les descriptions ont plus d’importance une fois les outils fusionnés
Avec 30 outils distincts, chacun peut se permettre une description étroite et spécifique.
Avec 3 outils, la description doit communiquer l’ensemble des actions possibles.
Par exemple :
Unified tool for advanced database analysis.
ACTIONS:
relationships
List foreign key relationships.
indexes
Get indexes for a table.
profileColumn
Analyze nullability, cardinality and top values.
searchSchema
Search tables and columns.
listProcedures
List stored procedures.
triggers
Get triggers.
compareSchemas
Compare two table definitions.
Ce n’est pas seulement de la documentation destinée aux humains.
Celui-ci fait partie du mécanisme par lequel le modèle dirige ses appels.
Une façon utile de l’envisager :
Tool name
+
Description
+
Action enum
+
Parameter schema
↓
Agent routing signal
Ainsi, la consolidation des outils devrait vous inciter à rédiger des descriptions plus minutieuses, et non moins nombreuses.
13. Ce schéma n’est pas limité aux bases de données
La même approche est encore plus efficace lorsqu’on l’applique à un ensemble plus large de systèmes d’ingénierie.
Jira
Au lieu d’exposer :
searchIssues
getIssue
createIssue
updateIssue
addComment
transitionIssue
assignIssue
getTransitions
exposer :
jira_issues
avec un ensemble d’actions tel que :
search
get
create
update
comment
transition
assign
GitLab
Au lieu de présenter des outils distincts pour les répertoires, les problèmes, les demandes de fusion et les pipelines :
gitlab_repository
gitlab_issues
gitlab_mergeRequests
gitlab_pipelines
On peut les regrouper en un seul point d’entrée, chacun disposant de son propre ensemble d’actions ciblées.
Confluence / Wiki
Au lieu de disperser les opérations sur plusieurs outils :
searchPages
getPage
createPage
updatePage
deletePage
getChildren
On les fusionne en un seul :
wiki
Guidé par une liste d’actions telle que :
search
get
create
update
children
Sentry
Une structure similaire fonctionne également ici :
sentry
Soutenu par :
searchIssues
getIssue
events
releases
projects
ELK
La même logique s’applique à la recherche de logs :
logs
Avec :
search
aggregate
fields
indices
Jaeger
Et pour le traçage distribué :
tracing
Avec :
search
getTrace
services
operations
Aucun de ces exemples ne dépend du nom spécifique choisi. Ce qui compte, c’est l’approche architecturale sous-jacente, et non les étiquettes exactes utilisées pour chaque action.
14. Vous pouvez envelopper des serveurs MCP existants au lieu de les reconstruire
L’un des avantages pratiques de cette conception est que l’on n’a presque jamais besoin de réimplémenter une intégration à partir de zéro. Des serveurs MCP prêts à l’emploi existent déjà pour de nombreuses plateformes courantes. Le problème, c’est que leur interface d’outils par défaut n’est souvent pas adaptée aux besoins d’un agent donné.
Au lieu de connecter directement un agent à un ensemble complexe d’outils :
Agent
│
├── 20 Jira tools
├── 20 GitLab tools
└── 15 Confluence tools
on peut insérer une couche légère et conçue spécifiquement à cet effet entre eux :
Agent
│
Custom MCP Layer
│
┌──────────┼──────────┐
▼ ▼ ▼
Jira GitLab Confluence
│ │ │
existing existing existing
APIs MCP MCP
Cette couche personnalisée agit comme une facade destinée aux agents. Elle est particulièrement utile lorsque l’intégration de base fonctionne correctement mais expose simplement des opérations bien plus détaillées que celles dont l’agent devrait avoir besoin pour raisonner directement.
15. Considérer MCP comme une couche d’adaptation
Cela mène à une idée architecturale plus générale. Une configuration conventionnelle ressemble à ceci :
Agent → MCP → Service
Une configuration personnalisée ressemble plutôt à ceci :
Agent
↓
Agent-oriented MCP facade
↓
Service-specific MCP/API
↓
Service
La façade intermédiaire peut assumer des responsabilités telles que :
- consolider les outils
- appliquer des vérifications de permissions
- normaliser les entrées
- normaliser les sorties
- journaliser
- traçer
- mettre en cache
- limiter le débit
- appliquer des règles de politique spécifiques à l’organisation
Sous cet angle, la couche MCP cesse d’être simplement un mécanisme de transport pour devenir une véritable couche d’interface pour l’agent.
16. Le contrôle d’accès reste au niveau de l’action
Il existe une conséquence en matière de sécurité qui mérite d’être soulignée. Si vous fusionnez :
jira_create
jira_update
jira_delete
en une seule appelation de type :
jira({
action: ...
})
vous ne pouvez pas simplement accorder une permission générale telle que :
permission: jira
Et supposer que chaque opération sous-jacente présente un risque égal. Les vérifications de permissions doivent continuer à s’effectuer au niveau de chaque action individuelle, par exemple :
jira.search → allowed
jira.get → allowed
jira.create → allowed
jira.update → approval required
jira.delete → denied
Le outil consolidé n’est qu’une simplification au niveau de l’interface — il ne doit jamais devenir un raccourci qui accorde silencieusement un accès plus large que prévu.
17. Les journaux doivent enregistrer l’action, et non seulement l’outil
Lorsque les outils sont fusionnés, enregistrer uniquement le nom de l’outil dans les journaux cesse d’être utile. Une entrée de journal faible ressemble à ceci :
tool = jira
Une meilleure version inclut l’action :
{
"tool": "jira",
"action": "search",
"duration_ms": 142
}
Et une version encore plus détaillée ajoute des informations supplémentaires sur l’exécution :
{
"tool": "jira",
"action": "search",
"status": "success",
"duration_ms": 142,
"request_id": "...",
"agent_id": "...",
"user_id": "..."
}
La véritable unité d’exécution est désormais le couple :
tool + action
plutôt que l’outil seul :
tool
Cette distinction est très importante lorsque vous essayez de déboguer ce qu’un agent a réellement fait.
18. Conservez les gestionnaires sous-jacents séparés
La consolidation doit avoir lieu à la frontière exposée à l’agent, et non à l’intérieur de votre implémentation. Évitez de regrouper tout le contenu dans un seul gestionnaire massif :
async function jira(args) {
// everything
}
Préférez plutôt envoyer les requêtes vers des fonctions distinctes :
async function jira(args: JiraInput) {
switch (args.action) {
case "search":
return searchIssues(args);
case "get":
return getIssue(args);
case "create":
return createIssue(args);
case "update":
return updateIssue(args);
}
}
Chaque gestionnaire reste testable de manière indépendante :
jira()
│
├── searchIssues()
├── getIssue()
├── createIssue()
└── updateIssue()
Cela permet de maintenir votre base de code même si le nombre d’outils externes diminue.
19. Une règle pratique pour décider de ce à fusionner
Une bonne heuristique pour déterminer si deux opérations appartiennent au même outil est :
Un ingénieur regrouperait-il naturellement ces opérations sous une seule capacité ?
Par exemple, celles-ci appartiennent clairement ensemble :
Jira search
Jira get issue
Jira update issue
Mais ceux-ci ne le font pas :
Jira update issue
Elasticsearch search logs
Create AWS infrastructure
Une deuxième vérification utile :
La décision entre ces opérations nécessite-t-elle généralement les mêmes informations contextuelles ?
Si la réponse est oui, les fusionner est généralement avantageux. Sinon, il vaut mieux les conserver en outils séparés.
20. Lorsque fusionner des outils est une mauvaise idée
Parfois, conserver des outils séparés est simplement la meilleure solution.
Domaines non liés
database
payments
infrastructure
email
ne devrait pas être simplifié en quelque chose comme :
enterprise({
action: ...
})
Ensembles d’actions trop volumineux
Si un outil unique finit par couvrir cinquante actions distinctes, le schéma résultant peut devenir encombré et difficile à gérer pour le modèle, annulant ainsi l’objectif de la consolidation.
Opérations avec des privilèges élevés
Des actions telles que :
deleteProductionData
rotateCredentials
deployProduction
pourraient nécessiter leurs propres limites de permissions, même si elles se trouvent techniquement dans le même domaine que des opérations à moindre risque.
Opérations avec des garanties de cycle de vie différentes
Les appels en lecture seule et les appels destructeurs fonctionnent parfois mieux en tant qu’interfaces séparées :
jira_read
jira_write
plutôt que d’être regroupés dans un seul outil trop volumineux.
21. Moins d’outils n’est pas l’objectif réel
L’objectif n’est pas de réduire tout cela à :
30 → 1
La véritable cible est :
30 → smallest useful semantic surface
Dans un système, le nombre approprié peut ressembler à ceci :
30 → 3
Dans un autre :
30 → 7
Et dans un autre encore :
30 → 12
Ce que vous optimisez réellement, c’est :
minimize unnecessary tool metadata
sans sacrifier :
semantic clarity
validation
security
maintainability
22. Une structure architecturale réutilisable
Une configuration scalable suit généralement cette structure :
Agent
│
▼
┌──────────────────┐
│ Agent MCP Layer │
└────────┬─────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
jira gitlab wiki
│ │ │
┌────┼────┐ ┌────┼────┐ ┌────┼────┐
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
search get create issues MR pipeline search get update
│ │ │
└──────────────┼──────────────┘
▼
Existing APIs/MCPs
Le modèle interagit avec une interface simple et claire. En dessous, la couche d’intégration conserve toujours accès à l’ensemble des opérations possibles.
23. Repenser l’usage de MCP
Un modèle mental courant pour MCP est :
"Transformer chaque appel API en son propre outil."
C’est un bon point de départ.
Mais pour les systèmes en production, la question plus utile devient :
Que le modèle doit-il vraiment voir ?
Ces deux approches ne conduisent pas toujours au même design.
Considérons une API REST qui expose :
GET /issues
GET /issues/:id
POST /issues
PATCH /issues/:id
POST /issues/:id/comments
POST /issues/:id/transition
Il n’y a pas de nécessité de créer un outil distinct pour chaque route HTTP. La couche MCP peut plutôt servir d’enveloppe sémantique autour de ces points d’accès — tout comme on conçoit une API en fonction des besoins spécifiques d’un utilisateur, plutôt que de simplement exposer directement le service sous-jacent ou le schéma de base de données.
24. Considérer la conception des outils comme un problème d’UX pour les agents
La conception classique d’une API se pose cette question :
Quels points d’accès existent ?
La conception destinée aux agents pose une question différente :
Quelle décision le modèle tente-t-il réellement de prendre ?
Un ingénieur humain qui intègre une API pourrait recourir à :
getIssue
getIssueComments
getIssueTransitions
getIssueWorklogs
car chaque appel correspond clairement à une opération.
D’un autre côté, un agent peut fonctionner mieux avec :
jira_issue({
action: "get",
...
})
en combinaison avec :
jira_issue({
action: "comments",
...
})
car il choisit d’abord le domaine, puis affine le choix vers l’action spécifique — un processus de routage en deux étapes :
Domain
↓
Action
↓
Arguments
au lieu de forcer le modèle à résoudre :
One large global tool-selection problem
25. Un modèle minimal réutilisable
Cette approche se traduit facilement en TypeScript :
const toolSchema = z.discriminatedUnion("action", [
searchSchema,
getSchema,
createSchema,
updateSchema,
]);
server.registerTool(
"jira",
{
description: "...",
inputSchema: toolSchema,
},
async (args) => {
switch (args.action) {
case "search":
return search(args);
case "get":
return get(args);
case "create":
return create(args);
case "update":
return update(args);
}
}
);
Le code lui-même reste assez simple. Le véritable travail de conception consiste à répondre aux questions suivantes :
- Quelles opérations sont logiquement regroupées ?
- Quelle convention de nommage les actions doivent-elles suivre ?
- Qu’est-ce qui doit figurer dans la description de l’outil ?
- Quels paramètres sont obligatoires pour chaque action ?
- Quelles actions nécessitent des vérifications de permissions supplémentaires ?
- Comment les réponses doivent-elles être structurées de manière cohérente ?
- Comment chaque action doit-elle être enregistrée et traçée ?
26. Suivez l’impact avec des chiffres réels
La consolidation des outils ne doit pas reposer sur la foi — vous devez le vérifier à l’aide de données.
Au préalable du changement, vous pourriez enregistrer quelque chose comme :
Tools: 32
Tool schema tokens: X
Average tool-selection latency: Y
Tool-selection errors: Z
Après l’introduction du design multiplexé, vous mesurez à nouveau les mêmes indicateurs :
Tools: 5
Tool schema tokens: X'
Average tool-selection latency: Y'
Tool-selection errors: Z'
Parmi les signaux les plus utiles à suivre, on trouve :
- le nombre d’outils mis à disposition
- la taille totale des schémas
- le nombre de tokens consommés par la requête ou le contexte
- le degré de précision avec lequel l’agent choisit l’outil approprié
- la fréquence à laquelle des appels d’outils invalides sont effectués
- la fréquence à laquelle la validation des arguments échoue
- le temps de réponse
- la fréquence à laquelle les exécutions réussissent réellement
- le fait que l’agent termine la tâche dans son ensemble
L’objectif n’est pas simplement de poursuivre :
fewer tools
mais plutôt d’optimiser pour :
better agent performance per unit of context
27. Un modèle plus général pour les API destinées aux agents
Cette idée dépasse de loin le cadre du MCP lui-même.
Vous constaterez que ce même principe s’applique à :
- l’appel de fonctions
- les définitions d’outils au style OpenAI
- les SDK
- les environnements d’exécution personnalisés pour les agents
- les moteurs de flux de travail
- les architectures de plugins
- les adaptateurs REST vers les agents
- les ponts GraphQL vers les agents
En son essence, ce modèle se présente comme suit :
Traditional API
↓
Many low-level operations
↓
Agent-oriented facade
↓
Small semantic tool surface
↓
Action routing
↓
Strict execution layer
Ce n’est pas une astuce spécifique aux bases de données.
C’est un modèle général pour concevoir des interfaces consommées par les agents.
28. Le principe fondamental
Le modèle mental à retenir est simple :
┌──────────────────────┐
│ 30 APIs │
│ capabilities │
└──────────┬───────────┘
│
Consolidation
│
▼
┌──────────────────────┐
│ 3–5 tools │
│ semantic domains │
└──────────┬───────────┘
│
Action routing
│
▼
┌──────────────────────┐
│ 30 handlers │
│ original abilities │
└──────────────────────┘
Le changement significatif n’est pas :
30 capabilities → 3 capabilities
En réalité, il s’agit de :
30 exposed tools → 3 exposed interfaces
Rien dans les capacités du système ne change.
Ce qui change, c’est la partie de cette surface que le modèle doit traiter directement.
Conclusion
À mesure que les systèmes agents s’intègrent de plus en plus à votre infrastructure, le nombre d’outils à leur disposition ne cesse d’augmenter.
Des systèmes tels que Jira, GitLab, Confluence, Sentry, ELK, Jaeger, diverses bases de données, des fournisseurs cloud et des services internes peuvent chacun contribuer avec des dizaines ou des centaines d’opérations distinctes.
Confier chacune de ces opérations au modèle en tant qu’outil séparé n’est que rarement l’architecture appropriée.
Une stratégie plus efficace consiste à créer une couche d’outils destinée aux agents au-dessus d’eux :
Many low-level operations
↓
Semantic domain grouping
↓
Discriminated action schemas
↓
Small MCP tool surface
↓
Strict runtime validation
↓
Original capabilities
La règle directrice est :
Réduisez le nombre d’outils dont le modèle doit tenir compte — et non le nombre de fonctionnalités que votre système offre réellement.
L’intégration de trente opérations sous-jacentes n’implique pas nécessairement la présence de trente outils visibles pour le modèle.
Il peut suffire de trois interfaces soigneusement conçues, chacune disposant d’un champ d’action bien défini.
Cela devient encore plus précieux lorsque l’on cesse de considérer les serveurs MCP existants comme des contrats fixes et intouchables, pour les traiter plutôt comme des éléments de construction que l’on peut envelopper, remodeler et ajuster afin qu’ils répondent aux besoins réels de l’agent.
À l’avenir, une bonne conception MCP consistera moins à mettre en évidence chaque opération prise en charge par une API, et plus à créer l’interface la plus petite et la plus pratique dont un agent a besoin pour accomplir sa tâche.