Mesures rafraîchies du 25/08/2026 via le protocole (LIMAGRAIN) :
get_system_parameters {} -> 70 141 caractères pour 168 paramètres,
search_logs Error avec les défauts -> 55 954 caractères pour 50
résultats. Trois blocs : pagination limit/offset, garde-fou de taille
cumulée, cohérence du signal truncated (D24 si contrat commun).
QueryExecuteStream explicitement hors périmètre (piste L4.5).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9.3 KiB
Passation — lot 3 (L3.1, bornage des sorties volumineuses)
Tu travailles sur mcp-wms-api : un serveur MCP (Node.js, CommonJS, stdio) qui
donne à Claude un accès en lecture à un WMS EasyWMS (Mecalux) via ses API REST.
Lis ../CLAUDE.md et ../DECISIONS.md avant de
toucher au code.
Mission : borner les sorties des outils qui produisent aujourd'hui des
réponses de 50 000 à 70 000 caractères, rejetées par les clients MCP —
get_system_parameters et search_logs — avec un signal truncated: true
explicite plutôt qu'un rejet silencieux côté client.
Hors périmètre : tout le reste de la roadmap (lot 4 en entier). En
particulier, n'explore pas QueryExecuteStream — c'est une piste long
terme consignée en L4.5, pas ce lot. Ne touche pas aux limites des outils de
requête (MAX_QUERY_ROWS fait déjà le travail). Ne pousse rien
(git push interdit), ne touche pas au .env, n'appelle jamais
execute_command (il écrit dans le WMS).
Contexte matériel
- Profil de travail :
LIMAGRAIN(par défaut), host10.255.255.2, tenantLIMAGRAI2512,saas=false— les outils de logs y fonctionnent. - Le profil
ADest cassé et c'est diagnostiqué — ne le réinvestigue pas (tenant introuvable côté STS, point ouvert de la ROADMAP). Conséquence :npm test -- --alléchoue sur AD ; la baseline se mesure avecnpm test(profil par défaut), attendu 4/4, code de sortie 0. - Baseline protocolaire à préserver : 23 outils, 6 resources, aucune écriture sur stdout hors JSON-RPC.
Handshake + comptages :
printf '%s\n%s\n%s\n%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}' '{"jsonrpc":"2.0","method":"notifications/initialized"}' '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' '{"jsonrpc":"2.0","id":3,"method":"resources/list"}' | node src/index.js 2>/dev/null | node -e "let b='';process.stdin.on('data',d=>b+=d).on('end',()=>{for(const l of b.split('\n').filter(Boolean)){const m=JSON.parse(l);if(m.id===2)console.log('tools:',m.result.tools.length);if(m.id===3)console.log('resources:',m.result.resources.length);}});"
Mesurer la taille d'une réponse d'outil via le protocole (c'est la mesure qui fait foi, pas une estimation) :
printf '%s\n%s\n%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}' '{"jsonrpc":"2.0","method":"notifications/initialized"}' '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"NOM","arguments":{}}}' | node src/index.js 2>/dev/null | node -e "let b='';process.stdin.on('data',d=>b+=d).on('end',()=>{for(const l of b.split('\n').filter(Boolean)){const m=JSON.parse(l);if(m.id===2)console.log('chars:',m.result.content[0].text.length);}});"
Contraintes non négociables
console.error()uniquement — une écriture sur stdout casse Claude Desktop (D6).- Un outil ne plante jamais le serveur : erreurs en réponse structurée
{ success: false, error, tool },isError: true(wrapper desrc/index.js). - Messages d'erreur et hints actionnables : dire quoi faire ensuite.
- Aucun accès base de données (D1).
- D23 : le wrapper
tools/callvalide les arguments contre les schémas. Tout paramètre que tu ajoutes (limit,offset…) doit être déclaré dans l'inputSchemade l'outil, sinon le wrapper le rejettera comme inconnu. C'est voulu — ne contourne pas la validation. - Les schémas portent
additionalProperties: false— conserve-le.
Numéro de décision réservé : D24 = contrat de troncature (si tu actes un contrat commun — voir L3.1c). Vérifie que D23 est bien la dernière décision avant d'écrire.
Phase 0 — Confirmer les deux mesures
Mesures du 25/08/2026 (profil LIMAGRAIN, via le protocole) à confirmer
avec la commande de mesure ci-dessus, pas à réinvestiguer :
| Appel | Constaté |
|---|---|
get_system_parameters {} |
70 141 caractères, 168 paramètres, aucun limit/offset au schéma |
search_logs {"keyword":"Error"} (défauts : max_results 50, context_lines 2) |
55 954 caractères, 50 résultats |
Contexte : une sortie de ~70 000 caractères a déjà été rejetée par le client MCP (constat du 24/08/2026 qui a motivé ce lot). L'ordre de grandeur cible est ~20 000–25 000 caractères par réponse ; c'est un ordre de grandeur, pas un chiffre sacré — ce qui compte est le comportement (borné + signalé), mesuré via le protocole.
L3.1a — get_system_parameters : pagination
Problème. L'outil (src/tools/config-tools.js) renvoie les 168 paramètres
fusionnés d'un bloc : 70 141 caractères sans filtre. Les filtres existants
(warehouse, param_class, search, only_overridden) réduisent la sortie
mais rien ne borne le cas sans filtre.
À faire. Ajouter limit (défaut raisonnable, ~50 — à ce volume la réponse
tient vers 21 000 caractères) et offset (défaut 0), déclarés au schéma
(contrainte 5). La réponse annonce toujours totalParameters (le total avant
pagination), le nombre renvoyé, et — quand la pagination a tronqué —
truncated: true avec un hint indiquant comment continuer (offset suivant)
ou réduire (param_class, search).
Vérification attendue (protocole, LIMAGRAIN) :
{}→ taille < 25 000 caractères, 50 paramètres renvoyés,totalParameters: 168,truncated: true, hint présent.{"limit": 200}→ les 168,truncated: false(ou champ absent — mais alors cohérent partout).{"offset": 160}→ 8 paramètres, pas detruncated.{"search": "CROSSDOCK"}→ comportement inchangé sur petit résultat.{"lines": 5}→ toujours rejeté par le wrapper (D23 intact).
L3.1b — search_logs : garde-fou de taille
Problème. max_results existe (défaut 50) mais ne borne pas le volume :
les context_lines multiplient la taille des résultats. Mesuré : 55 954
caractères avec les seuls défauts.
À faire. Un garde-fou sur la taille cumulée de la réponse construite
(src/tools/log-tools.js / src/services/log-service.js) : au-delà du
plafond, couper la liste des résultats — des résultats entiers, ne coupe
pas un résultat au milieu de ses lignes de contexte — et poser
truncated: true + un compte des résultats retenus/écartés + un hint
(réduire context_lines, affiner keyword, baisser max_results).
Plafond : constante ou variable d'environnement avec défaut (cohérent avec le
style MAX_QUERY_ROWS/QUERY_TIMEOUT dans le code existant) — documente le
choix dans le commit.
Pente naturelle interdite : ne réduis pas silencieusement les défauts
(max_results 50, context_lines 2 restent tels quels) — le correctif est le
bornage signalé, pas un changement de comportement par défaut qui casserait
les usages existants.
Vérification attendue (protocole, LIMAGRAIN) :
{"keyword":"Error"}→ taille sous le plafond choisi,truncated: true, compte écarté + hint présents.{"keyword":"Error","max_results":3}→ petit, pas de troncature signalée.- Un mot-clé sans occurrence → comportement inchangé (0 résultat, pas de
truncated).
L3.1c — Cohérence du signal truncated
Si tu factorises un helper de troncature commun aux deux outils, actes le
contrat en D24 dans DECISIONS.md (forme du signal : truncated: true,
compte total vs renvoyé, hint actionnable). Si les deux implémentations restent
locales et divergentes, harmonise au moins les noms de champs — deux
vocabulaires pour le même concept est exactement le genre de dérive qu'on
traque en révision. read_recent_logs peut bénéficier du même helper si c'est
gratuit ; ne le complexifie pas pour ça.
Méthode
- Phase 0 d'abord (deux mesures, rejouées telles quelles).
- L3.1a puis L3.1b puis L3.1c. Chaque correctif vérifié en exécution via le
protocole avant de passer au suivant — la taille en caractères de
content[0].textest la mesure qui fait foi. - Les schémas changent (nouveaux paramètres) : reboucler sur les 23 noms de
tools/listet vérifier qu'aucun ne répondUnknown tool(n'appelle pasexecute_command— vérifie sa branche statiquement), et vérifier qu'un paramètre inconnu est toujours rejeté (D23). - Baseline avant/après : handshake (23/6) +
npm test(4/4, exit 0). - « Non résolu » est une réponse acceptable pour une investigation time-boxée ; une hypothèse présentée comme solution ne l'est pas.
Livraison
- Un commit par correctif (L3.1a, L3.1b, L3.1c si D24), messages expliquant le pourquoi, mesures avant/après dans le corps du message (tailles en caractères, rejouées via le protocole).
- Documentation dans les mêmes commits : D24 si actée ; ROADMAP.md — retirer L3.1 (le lot 3 devient vide : retire la section) ; CLAUDE.md ne change que si tu ajoutes une variable d'environnement (la documenter dans la section des réglages partagés).
- Ne pousse pas.
.envintact. Toute anomalie hors périmètre découverte en route : dans ROADMAP.md, pas dans le code. - Compte-rendu final : pour chaque correctif, la vérification attendue rejouée et son résultat mesuré (colle les tailles et les sorties), plus la baseline finale.