Architecture

150 outils pour un agent — et la bascule qui a tout allégé

Mes connecteurs MCP exposent près de 150 outils à mon agent. Un jour, l'un d'eux m'a renvoyé 124 815 caractères pour 3 champs que je voulais. J'ai testé l'autre voie — passer par du code — et je l'ai mesuré : 55 fois moins de contexte, pour le même travail.

Coffret de velours ouvert sur un établi d'atelier : une carte blanche imprimée « execute(...) », devant un mur d'outils anciens ; plaque gravée « UN SEUL OUTIL ».
Cent cinquante outils au mur ; un seul dans le coffret.

En écrivant cet article, j'ai demandé à mon agent la liste de mes propres articles de blog. Il a appelé l'outil qui va bien, et la réponse est tombée : 124 815 caractères d'un seul bloc. 26 articles entiers, corps compris — alors que je voulais 3 champs de 3 d'entre eux. Le retour était si gros que l'outil l'a écrit dans un fichier au lieu de me le rendre, en me suggérant poliment d'aller y pêcher ce qui m'intéressait.

Voilà, en une capture, le problème que je veux raconter.

Mon assistant parle à mes services via MCP — le protocole qui laisse un agent appeler du vrai code : mes API, mes bases, mes outils. J'ai plusieurs connecteurs branchés dessus. L'un, semantic-hub, lui ouvre ma knowledge, mes projets, la publication de ce blog, l'analytics. Les autres couvrent le reste de la maison. À eux tous, près de 150 outils.

Le réflexe, quand on branche un agent sur ses propres services, c'est d'exposer un outil par endpoint. Créer, lire, lister, mettre à jour, supprimer. Chaque verbe devient un outil. C'est propre, c'est symétrique, ça ressemble à un bon design d'API. Et c'est un piège.

Le vrai gouffre n'est pas où on croit

On imagine que le coût, c'est d'avoir trop d'outils. C'est vrai à moitié seulement. En réalité il y a 3 postes de dépense — et le plus visible est le plus petit des 3.

1. Les gros retours qui traversent le modèle en entier. Le premier poste, et de loin le plus lourd. La liste d'articles de tout à l'heure en est un cas d'école : 124 815 caractères avalés pour en garder 3. Mais ce n'est pas isolé. Quand je demande l'état de mes projets, hub-projects me rend un tableau qui agrège GitHub, Sonar, Harbor et le cluster — je n'en voulais que les 2 dépôts au rouge. Une recherche dans ma knowledge me ramène de longs passages quand une phrase suffirait. Un relevé de frais dans ledger, les pages vues que hub-plausible agrège : à chaque fois, le service crache tout, et le modèle avale tout — même la part dont il n'a que faire. Les gros retours, c'est là qu'est le budget.

2. Les enchaînements. Lire une décision dans ma knowledge pour la reporter dans un article, c'est le même texte qui transite 2 fois : une fois en sortie de la knowledge, une fois en entrée de la publication. 3 opérations chaînées, ce sont 3 allers-retours et 2 résultats intermédiaires qui polluent le contexte au passage, pour rien.

3. Les définitions d'outils. Le plus petit des 3. Oui, 150 descriptions d'outils occupent de la place en tête de conversation. Mais mon harnais est déjà malin là-dessus : il ne charge la définition d'un outil qu'au moment où l'agent la réclame. Ce poste-là, on sait à peu près le maîtriser. Les 2 autres, non.

Anthropic a mis des chiffres sur ce virage, sur un cas type : leur agent, en explorant les outils comme des fichiers au lieu de charger 150 000 tokens de définitions d'avance, retombe à 2 000 tokens — 98,7 % en moins. Leur chiffre mesure surtout le 3e poste ; les 2 premiers, restait à les mesurer chez moi. Le détail est dans leur article ; venons-en à ce qui m'intéresse : appliquer ça à ma propre maison.

La bascule

L'idée tient en une phrase : au lieu d'exposer 150 outils que l'agent appelle un par un, on lui présente ses opérations comme une bibliothèque de code, et on ne lui donne qu'un seul outil — « exécute ce script ».

Reprenons ma liste d'articles. Avant, il n'y avait pas de code du tout : l'agent appelait l'outil MCP directement — une ligne — et le retour entier lui revenait dans le contexte :

→ publishing_article_list()
← 124 815 caractères, 26 articles entiers, droit dans mon contexte

Maintenant, il écrit 3 lignes, et le tri se fait dans le code avant que quoi que ce soit ne me parvienne :

articles = publishing.list_articles()
derniers = [{"titre": a["title"], "date": a["created_at"]} for a in articles[:3]]
print(derniers)
# ← seul ceci revient dans mon contexte : 3 titres, 3 dates

Les 124 815 caractères restent dans le bac à sable où le script s'exécute ; moi, je ne vois passer que le strict nécessaire. Le tri se fait avant que la donnée touche le modèle, pas après.

Et cette bibliothèque, à quoi ressemble-t-elle ? À n'importe quel code source. Un fichier par service — publication, knowledge, projets… — et dedans des fonctions documentées. Celle du dessus, la vraie, ressemble à ça :

def list_articles(*, status=None, limit=None, offset=None):
    """Liste les articles du blog (lecture seule).

    status : filtre optionnel ("draft", "published"…).
    Renvoie une liste d'articles — chacun porte "title",
    "created_at", "slug"…
    """

L'agent l'ouvre comme un développeur ouvre un dépôt : il lit la signature au moment de s'en servir, y découvre la forme du retour — c'est de là qu'il tire title, pas d'une devinette — et écrit son script contre ça.

C'est toute la différence. On arrête de faire transiter les données par le cerveau du modèle ; on les fait transiter par du code, et le modèle ne voit que ce que le script décide de lui montrer.

Schéma animé du MCP en mode code : l'agent lit la bibliothèque, écrit 3 lignes de script, l'unique outil execute les envoie au bac à sable, et seul le retour filtré touche le modèle Le seul outil : « exécute ce script ». La bibliothèque se lit fichier par fichier, le script s'exécute dans le bac à sable, et seul le retour filtré touche le modèle.

Ce que ça déverrouille

Une fois qu'on voit les outils comme du code plutôt que comme une liste, 3 choses changent.

L'agent filtre avant de me montrer quoi que ce soit — le gros tableau de projets devient les 2 dépôts au rouge, rien d'autre. Il compose : la décision lue dans la knowledge et reportée dans l'article, c'est une seule exécution au lieu de 3 tours, sans les intermédiaires qui encombrent. Et il ne charge la connaissance d'un outil qu'au moment de s'en servir.

Il y a un bénéfice que je n'attendais pas et qui, chez moi, pèse lourd : la confidentialité. Ce qui circule dans le code ne remonte pas au modèle par défaut. Une donnée sensible peut passer d'un service à l'autre sans jamais entrer dans le contexte. Quand on manipule des secrets et des identités toute la journée, ce n'est pas un détail — c'est une raison à soi seule.

Mise en marche

Écrire un article sur une bonne idée, c'est confortable. La philosophie de ce site, c'est d'aller plus loin : mettre les idées en marche, proprement, professionnellement — tout en apprenant. Et accepter de casser en route : une erreur qu'on a payée soi-même, on s'en souvient, et on fait mieux le lendemain.

J'ai une chance, ici : les bases sont déjà bâties. Le Kubernetes de la maison, les connecteurs, la supervision — tout ce qu'il faut pour démarrer un chantier proprement, simplement et vite. Alors le bac à sable a suivi : un environnement isolé, non privilégié, coupé du reste, avec ses limites de mémoire et de temps, et un seul droit accordé : lire. Dedans, une poignée de mes opérations à gros retour, présentées comme des fonctions qu'un script peut appeler. L'agent n'y accède que par un unique outil, « exécute ce script ». Et parce que c'est ajouté à côté plutôt que greffé dans l'existant, je peux le débrancher d'un geste.

En moins d'une heure, c'était en place — et testé pour la première fois sur nos articles. Le script liste les 26 articles à l'intérieur du bac à sable, n'en garde que les derniers réduits à leur titre et leur date, et ne me renvoie que ça.

Le résultat, mesuré : 124 815 caractères en entrée, 2 272 en sortie. 55 fois moins — 98,2 % du contexte évaporé, pour exactement le même travail.

Ce ne sont pas des chiffres de laboratoire : c'est ce qui s'est passé chez moi, sur mes vraies données. Un seul test, sur un seul outil — ça se prend avec des pincettes, comme toute première mesure. Mais la direction, elle, est claire : c'est celle qu'on prend.

Petit aveu au passage : ma toute première mesure m'a menti. Elle annonçait un gain irréel — le script s'était trompé de fonction et ne vérifiait même pas s'il avait réussi. Une preuve ne vaut que si on la regarde de près ; celle-ci, je l'ai reprise jusqu'à ce qu'elle tienne debout. Casser, se tromper, s'en souvenir : c'est comme ça qu'on apprend.

Là où ça se paie

Rien n'est gratuit : faire exécuter du code écrit par un agent réclame ce bac à sable. Un environnement isolé, avec ses limites de ressources et sa surveillance — bref, une infrastructure et des questions de sécurité que le simple appel d'outil, lui, n'a pas besoin de poser.

Autrement dit, on ne fait pas disparaître le coût : on le déplace. D'un poste qu'on paie en tokens à chaque tour, indéfiniment, vers un bac à sable qu'on sécurise une bonne fois. C'est un bien meilleur endroit où le mettre — et maintenant qu'il tourne, c'est un coût payé, pas un coût récurrent.

Quand ne pas le faire

Soyons justes : pour un appel unique et simple — un seul « cherche-moi ça » — écrire un script est plus lourd qu'appeler l'outil directement. Le gain apparaît sur le volume et sur la composition, pas sur le geste isolé.

La bonne stratégie est donc hybride. Garder à portée directe les 2 ou 3 outils qu'on utilise tout le temps, et basculer tout le reste derrière une surface de code. On prend le meilleur des 2 mondes plutôt que de troquer un dogme contre un autre.

Le nombre d'outils cesse d'être un problème. On peut en avoir 1 000 : ce n'est plus une liste qui écrase le contexte, c'est une bibliothèque que l'agent ouvre à la page qu'il lui faut.

À très vite.

— Damien