Files
mcp-wms-api/docs/handoff-lot3.md
T
Arthur Ria 7b25e79e98 Passation lot 3 : bornage des sorties volumineuses (L3.1), D24 réservée
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>
2026-08-25 10:14:37 +02:00

9.3 KiB
Raw Blame History

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), host 10.255.255.2, tenant LIMAGRAI2512, saas=false — les outils de logs y fonctionnent.
  • Le profil AD est 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 avec npm 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

  1. console.error() uniquement — une écriture sur stdout casse Claude Desktop (D6).
  2. Un outil ne plante jamais le serveur : erreurs en réponse structurée { success: false, error, tool }, isError: true (wrapper de src/index.js).
  3. Messages d'erreur et hints actionnables : dire quoi faire ensuite.
  4. Aucun accès base de données (D1).
  5. D23 : le wrapper tools/call valide les arguments contre les schémas. Tout paramètre que tu ajoutes (limit, offset…) doit être déclaré dans l'inputSchema de l'outil, sinon le wrapper le rejettera comme inconnu. C'est voulu — ne contourne pas la validation.
  6. 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 00025 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 de truncated.
  • {"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

  1. Phase 0 d'abord (deux mesures, rejouées telles quelles).
  2. 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].text est la mesure qui fait foi.
  3. Les schémas changent (nouveaux paramètres) : reboucler sur les 23 noms de tools/list et vérifier qu'aucun ne répond Unknown tool (n'appelle pas execute_command — vérifie sa branche statiquement), et vérifier qu'un paramètre inconnu est toujours rejeté (D23).
  4. Baseline avant/après : handshake (23/6) + npm test (4/4, exit 0).
  5. « 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. .env intact. 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.