Donnez une CLI à vos agents IA, pas un serveur MCP
Si vous avez déjà connecté plusieurs serveurs MCP à un agent, la scène vous est familière. Vous branchez GitHub, puis une base de données, puis un outil de tickets, et avant même d’avoir posé une question, une partie de la fenêtre de contexte est déjà consommée. Non pas par du travail utile, mais par des définitions d’outils que l’agent n’utilisera peut-être jamais.
C’est le problème que je veux raconter ici, comment l’écosystème est en train de le corriger, et la petite solution que j’ai fini par coder. Spoiler: c’est une CLI, et elle tient en quelques fichiers Python.
La taxe du MCP, et ce qui est en train de changer
Le Model Context Protocol fait une chose simple et, longtemps, coûteuse. À la connexion, il charge dans le contexte le schéma des outils qu’il expose, qu’ils servent ou non. Anthropic a elle-même documenté un montage à cinq serveurs, 58 outils au total, qui engloutissait environ 55 000 tokens avant le premier message. Le serveur MCP de GitHub, avec l’ensemble de ses outils activés, en a déjà consommé autour de 64 600 d’après GitHub. Sur une fenêtre de 200 000 tokens, c’est un tiers du budget parti avant la première question.
Et ce n’est pas qu’une question de prix. Chaque token de schéma inutile est un token de moins pour le raisonnement, et la fiabilité en pâtit aussi. Un benchmark de Scalekit a mesuré qu’une question toute bête coûtait 1 365 tokens en CLI contre 44 026 en MCP, avec en prime des échecs de connexion côté serveur distant que la CLI locale n’a pas. À l’autre bout du spectre, Cloudflare a calculé qu’exposer ses 2 500 endpoints comme autant d’outils MCP demanderait 1,17 million de tokens, soit plus que la fenêtre de la plupart des modèles.
Maintenant, jouons cartes sur table, parce que le lecteur d’un blog tech connaît le sujet. Cette taxe est en train d’être corrigée. Depuis janvier 2026, Claude Code défère par défaut les outils MCP grâce à une fonction de Tool Search: au lieu de tout précharger, il ne charge la définition d’un outil que lorsqu’il en a besoin. Concrètement, quand les définitions dépassent environ 10 % du contexte, il bascule sur un index de recherche léger, et Anthropic annonce une réduction de la surcharge d’environ 85 %. Le serveur de GitHub, de son côté, n’active plus tous ses outils par défaut et propose une découverte dynamique de ses toolsets. Donc non, dans un Claude Code à jour, GitHub n’avale plus des dizaines de milliers de tokens d’un coup. Et c’est important de le souligner.
Sauf que ces correctifs vivent côté client, ce ne sont pas des garanties du protocole. Justin le souligne d’ailleurs: si vous publiez un serveur MCP, vous ne pouvez pas supposer que le client gérera bien le contexte. Hors de Claude Code, sous le seuil de déclenchement, ou avec un autre client, la taxe revient. Et surtout, les arguments de fond en faveur de la CLI ne dépendent d’aucun correctif. On y vient.
L’idée que je reprends à Justin Poehnelt
Avant de coder quoi que ce soit, je suis tombé sur deux articles de Justin Poehnelt, ingénieur chez Google, qui ont structuré ma réflexion. Le premier s’intitule You Need to Rewrite Your CLI for AI Agents, le second The MCP Abstraction Tax.
Sa distinction centrale m’a servi de fil rouge: l’expérience développeur pensée pour les humains optimise la découvrabilité et le pardon des erreurs, alors que celle pensée pour les agents optimise la prévisibilité et la défense en profondeur. Ce sont deux objectifs différents, et essayer de rétro-adapter une CLI pensée humain pour des agents est souvent un mauvais pari.
Il insiste aussi sur un point que je trouve sous-estimé: le durcissement des entrées. L’idée est simple. Un humain qui se trompe au clavier fait une faute de frappe, vite repérée. Un agent, lui, n’a pas de doigts: il génère du texte, et il peut inventer des valeurs aberrantes avec un aplomb total. Le rôle de l’outil est donc de se méfier de ce que l’agent lui envoie, exactement comme une API web se méfie de ce que tape un internaute.
Deux exemples concrets. Un agent peut produire un chemin comme ../../.ssh, qui tente de remonter hors du dossier prévu pour aller fouiller des fichiers sensibles. Ou il peut coller un bout de requête à l’intérieur d’un identifiant, par exemple fichier123?champs=nom, et casser l’appel parce que ce qui devait être un simple id contient soudain des caractères qui n’ont rien à y faire. Un humain ne commet quasiment jamais ces erreurs. Un agent, si. D’où la formule de Justin, qui résume tout: “Agents hallucinate. Build like it.” Autrement dit, partez du principe que l’agent va halluciner, et construisez en conséquence.
Son second article ajoute la nuance qui m’évite le manichéisme. Entre la donnée brute et l’agent, il y a des couches successives: la base de données, l’API qui l’expose, puis parfois le MCP par-dessus. Et chaque couche en montre un peu moins que celle d’en dessous, un peu comme un résumé qui laisse forcément des détails de côté. Justin le dit clairement: même l’API REST n’est qu’une projection imparfaite du modèle de données, parce que la représentation interne est plus riche que ce que l’API laisse passer. Des champs, des relations, des métadonnées restent en coulisses, et le MCP rajoute encore une couche au-dessus. C’est ce qu’il appelle la taxe d’abstraction. Sa conclusion, que je fais mienne: MCP et CLI n’optimisent pas pour la même chose, et comprendre ce que chacun vous coûte est plus utile que de chercher à couronner un gagnant. Je garde ça en tête pour la fin de l’article.
Pourquoi une CLI inverse le problème
Une ligne de commande renverse la logique de coût. L’agent ne paie rien tant qu’il n’a pas lancé une commande. Il découvre ce dont il a besoin au moment où il en a besoin, via --help ou un petit fichier de skill, puis lance exactement l’appel utile. On perd un peu de la découvrabilité structurée du MCP, mais pour un workflow de lecture et d’écriture sur une API qui publie déjà un spec OpenAPI, l’économie de tokens est énorme et la perte est minime.
Il y a aussi une raison plus profonde, qu’on lit rarement. Les modèles ont été entraînés sur des milliards d’interactions en terminal: réponses Stack Overflow, dépôts GitHub, tutoriels. La CLI, elle, joue à domicile. Un schéma MCP, lui, ne bénéficie d’aucun avantage d’entraînement comparable. Le modèle connaît déjà la grammaire commande sous-commande --option valeur.
L’outil: spec2cli
De là est né spec2cli, un petit outil open source que j’ai mis en ligne. Le pari de conception est le suivant: au lieu de générer un binaire figé par API, il lit le spec OpenAPI à l’exécution et expose chaque opération comme une sous-commande. Zéro génération de code, aucune ligne spécifique à une API donnée. C’est volontairement simple, presque rudimentaire, et c’est tout l’intérêt.
Un même spec, deux chemins. La CLI ne charge la définition d’une opération que lorsqu’elle est appelée.
Quatre commandes suffisent à comprendre la philosophie:
listdécouvre les opérations et renvoie un tableau JSON plat, donc peu coûteux à charger.describeexpose le schéma complet d’une seule opération, références$refrésolues, pour que l’agent n’ait jamais besoin de doc statique préchargée.callexécute une opération, avec un--dry-runqui compose et affiche la requête exacte sans l’envoyer.skillgénère un fichier SKILL.md adapté à l’API visée, pour que Claude Code sache s’en servir.
Le mieux est de le voir tourner sur une vraie API. Open-Meteo est parfaite pour ça: gratuite, sans clé, sans inscription, en simple GET JSON. Voici l’outil branché directement sur un spec communautaire récupéré depuis GitHub. Ce spec ne définit pas d’operationId, donc spec2cli en génère des stables à partir de la méthode et du chemin.
SPEC=https://raw.githubusercontent.com/cmer81/open-meteo-mcp/main/openapi.yml
# 1. Découvrir: 16 opérations, un tableau plat
spec2cli list "$SPEC"
# 2. Inspecter une seule opération, à la demande
spec2cli describe "$SPEC" getV1Elevation
# -> latitude, longitude (paramètres de requête requis)
# 3. L'appeler: l'altitude du sol à Nice
spec2cli call "$SPEC" getV1Elevation \
--params '{"latitude":43.7102,"longitude":7.2620}'
# -> {"ok": true, "status": 200, "data": {"elevation": [29.0]}}
À aucun moment l’agent n’a eu besoin d’avoir les 16 opérations en contexte. Il a listé, il a regardé celle qui l’intéressait, il a appelé. Le coût en tokens suit la tâche, pas la taille de l’API.
L’outil gère aussi les cas un peu tordus du monde réel. Open-Meteo éclate certains services sur des hôtes voisins alors que le spec n’en déclare qu’un seul, donc un simple --base-url permet de rediriger l’appel:
spec2cli call "$SPEC" getV1Archive \
--base-url https://archive-api.open-meteo.com \
--params '{"latitude":43.7102,"longitude":7.2620,"start_date":"2024-07-01","end_date":"2024-07-01","daily":"temperature_2m_max,temperature_2m_min"}'
# -> {"ok": true, "status": 200, "data": {"daily": {"temperature_2m_max": [24.5], "temperature_2m_min": [18.3]}}}
Les garde-fous qui en font autre chose qu’un wrapper curl
Si je m’étais arrêté à “lis le spec et envoie la requête”, j’aurais juste écrit un curl déguisé. Les principes de Justin sont justement ce qui transforme ça en interface pour agents. spec2cli les embarque par défaut.
La sortie est en JSON sur la sortie standard, pour chaque commande, pour que l’agent la parse directement et la passe au besoin dans jq. Le durcissement des entrées rejette toute valeur destinée à un segment d’URL qui contiendrait ?, #, %, .. ou un caractère de contrôle. Ce sont exactement les fautes que commettent les agents: une chaîne de requête collée à un identifiant, une chaîne pré-encodée qui se réencode, une traversée de chemin hallucinée. Le --dry-run montre la requête exacte avec les secrets masqués, à utiliser avant toute écriture. L’authentification ne passe que par des variables d’environnement, jamais par un flag. Et chaque chemin d’erreur renvoie quand même un JSON parsable avec un code de sortie non nul, pour que l’agent comprenne ce qui s’est passé.
Rien de spectaculaire pris isolément. Mais mises bout à bout, ces règles font la différence entre un outil que l’agent utilise sans supervision et un outil qui déraille à la première hallucination.
Alors, CLI ou MCP ?
Honnêtement, il n’y a pas de vainqueur, juste un choix qui dépend du contexte. Quand vous bossez en local et que vous maîtrisez ce qui tourne, la CLI est presque toujours le bon réflexe : elle consomme peu de tokens, elle se combine avec vos autres outils en ligne de commande, et elle ne vous lâche pas au milieu d’une tâche. Pour le quotidien d’un développeur, c’est dur à battre.
Le MCP reprend la main dès que vous sortez de votre machine. Un agent qui agit pour le compte de vos clients, des accès à gérer, un historique à garder de qui a fait quoi : là, ses schémas explicites et son authentification stricte deviennent un atout, pas un poids. Et puis beaucoup d’outils métier n’ont tout bonnement pas de CLI, donc la question est vite réglée.
Reste un point que je ne veux pas balayer : les fenêtres de contexte grossissent et coûtent de moins en moins cher. Une partie du coût du MCP finira par fondre. L’argument des tokens a donc une date de péremption, même si ce n’est pas pour demain.
Mon avis, aujourd’hui, est simple. Si vous contrôlez l’exécution et que vous cherchez l’efficacité, donnez une CLI à vos agents. Et si vous avez déjà une API avec un spec OpenAPI, spec2cli vous la transforme en CLI en une ligne. C’est petit, c’est ouvert, et ça règle un vrai problème.
Sources
- Justin Poehnelt, You Need to Rewrite Your CLI for AI Agents, justin.poehnelt.com
- Justin Poehnelt, The MCP Abstraction Tax, justin.poehnelt.com
- Anthropic, Introducing advanced tool use (Tool Search Tool), anthropic.com
- Claude Code, documentation MCP (tool search activé par défaut), code.claude.com
- GitHub MCP Server, toolsets par défaut et découverte dynamique, github.com
- Scalekit, benchmark MCP vs CLI, scalekit.com
- Firecrawl, MCP vs CLI, firecrawl.dev
- spec2cli, dépôt GitHub, github.com/gothub97/spec2cli
Lead internal product & AI engineer chez Flowdesk. Je développe avec Claude Code au quotidien et j'écris à ce sujet ici.