Six outils, et l'endroit où vivent les règles
Quand un agent tient le suivi de projet, il faut bien lui dire comment. Reste à savoir où écrire la consigne : la description d'un outil est le dernier texte qu'il lit avant d'agir, et ça change tout.
Le premier billet racontait le renversement : un suivi de projet que l’agent tient seul, sans validation. Restait une question que je croyais secondaire et qui s’est révélée être la principale — où écrit-on les règles pour qu’elles soient suivies ?
Un ticket bien formulé, une résolution honnête, un blocage expliqué : rien de tout ça ne s’obtient par du code. Ce sont des consignes. Et une consigne, ça se range quelque part.
Les endroits qui ne marchent pas
Un README, personne ne le lit au bon moment. Un fichier de consignes projet, l’agent le lit au démarrage de la session — utile, mais c’est loin : entre ce moment-là et l’appel d’outil, il s’est passé une heure de travail et vingt décisions. Un commentaire dans le code, il ne le voit jamais. Une validation côté serveur arrive après coup : elle refuse l’appel, elle ne l’oriente pas.
Il reste un endroit, un seul : la description de l’outil. C’est le texte que l’agent a sous les yeux à l’instant où il choisit quoi appeler.
Une règle écrite ailleurs est une règle lue trop tard.
Ce que ça change à ce qu'on écrit dedans
Une fois qu’on accepte ça, les descriptions arrêtent de décrire. Elles ne disent plus « ce paramètre attend un titre », elles disent quoi faire et surtout quoi ne pas faire. Celle de create_ticket passe l’essentiel de sa longueur sur des contre-exemples :
Un ticket est une fonctionnalité ou un correctif que quelqu'un reconnaîtrait
trois mois plus tard — « export CSV des relevés », « TVA fausse sur les avoirs
partiels ». Jamais une étape technique : pas « refactoriser le repository »,
pas « ajouter la migration », pas « corriger le test qui échoue ». C'est la
façon dont le travail se fait, pas le travail.
Trois exemples négatifs pour une règle positive. Lu comme de la doc, c’est disproportionné. Lu pour ce que c’est — la dernière chose qui peut encore changer l’appel qui va partir — c’est le bon dosage.
Une consigne devient du code déployé. La corriger demande de toucher au serveur et de relancer la session, là où une ligne de README se change en trois secondes. C’est le prix de l’endroit qui marche.
Le serveur ne décide de rien
Le serveur MCP est un pont, rien de plus : il traduit sept appels d’outils en sept appels HTTP. Aucune règle métier n’y vit — ni les statuts, ni les résolutions, ni la numérotation des tickets. Tout ça reste dans l’application.
La tentation inverse est réelle : valider dans le pont fait gagner un aller-retour et donne de plus jolis messages d’erreur. Ça donne surtout deux exemplaires de la même règle, et deux exemplaires finissent toujours par diverger. Le jour où ils ne disent plus la même chose, c’est celui qui est le plus près du modèle qui gagne — donc le mauvais.
La frontière n’est pas dogmatique pour autant : le pont fait ce que l’application ne peut pas faire. Résoudre un lien symbolique, par exemple — l’application ne tourne pas forcément sur la machine où vivent les projets, le pont si. Chaque moitié fait ce dont elle seule est capable.
Le sixième outil qui n'a pas tenu
La règle affichée était « six outils, pas un de plus ». Elle a cédé sur un usage tout bête : list_tickets renvoie des résultats de recherche, et un résultat porte le titre et le statut, jamais le texte. L’agent voyait donc qu’un ticket existait sans jamais lire ce qu’on lui avait demandé dedans.
get_ticket a été ajouté, et sa description dit pourquoi il existe : « list_tickets trouve un ticket, celui-ci le lit ». Puis la phrase qui nomme le défaut corrigé — un commentaire que personne n’a lu est une instruction manquée.
Je garde la règle quand même, parce qu’elle a fait son travail : elle a rendu l’ajout coûteux, donc argumenté. Un compte rond qu’on défend vaut mieux qu’un compte rond qu’on respecte.
Deux pièges qui se paient cher
Effacer un champ n'est pas la même chose que ne pas y toucher
Dans un appel d’outil, une valeur vide et une clé qu’on n’a pas envoyée arrivent pareil. Or « vide la description » et « laisse la description tranquille », ce sont deux intentions opposées, et rien ne permet de les distinguer techniquement.
D’où une convention : une chaîne vide efface, une clé absente laisse en place. Une convention ne vaut que si elle est lue — elle est donc écrite dans la description du paramètre, au seul endroit qui compte.
Le symptôme peut être très loin de la cause
Celui-là fait perdre une soirée. Le serveur MCP démarre sans shell de connexion, donc sans le profil qui met node dans le PATH quand il vient de nvm. À l’écran, on voit un serveur « déconnecté », sans un mot de plus. Rien n’oriente vers le PATH.
La commande déclarée pointe donc sur un petit script de lancement qui va chercher node au démarrage. Une ligne de plus, et le problème ne revient jamais, même après un changement de version.
github.com/beeraw/tsutsumbi — le dossier mcp/ tient en quelques centaines de lignes, dont la moitié sont les descriptions des outils. C’est la partie la plus intéressante à lire.
- La description d’un outil est le seul endroit où une consigne arrive à temps. Écrite ailleurs, elle est lue trop tôt, trop tard, ou pas du tout.
- Une description utile dit quoi ne pas faire. Les contre-exemples travaillent plus que la règle qu’ils illustrent.
- Un pont qui valide devient un second exemplaire de la règle, et deux exemplaires divergent toujours.
- Une règle de conception vaut par le coût qu’elle impose, pas par le fait qu’elle tienne. « Six outils, pas un de plus » a bien travaillé le jour où elle a cédé.