Files
mcp-wms-api/docs/handoff-lot5.md
T
Arthur Ria 90b2c89ff1 Passation lot 5 : clore la famille D24 (fenêtre workflow, garde requêtes, champ tool)
Deux mesures nouvelles du 25/08/2026 aggravent le dossier : une requête
Reading ordinaire query_wms_entities(Products, limit 200) pèse 957 234
caractères et search_wms_data(PAL) 847 543 — le dépassement du seuil de
rejet client (~70 000, D24) ne se limite donc pas au Writing ni aux
workflows. Les points ouverts correspondants deviennent un lot 5 planifié
dans la ROADMAP (L5.1 fenêtre verbatim sur le blob data, L5.2 garde de
taille commune aux trois outils de requête avec le cas limite d'une
ligne seule au-dessus du plafond, L5.3 balayage du champ tool).

Pas de nouveau numéro de décision : le lot étend D24. D27 réservable si
besoin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 11:57:39 +02:00

12 KiB

Passation — lot 5 (clore la famille D24)

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 — en particulier D24, le contrat de troncature que ce lot généralise — avant de toucher au code.

Mission : éliminer les derniers cas connus de réponses d'outils dépassant le seuil de rejet des clients MCP (~70 000 caractères, D24) — lot 5 de ../ROADMAP.md : pagination du blob data de get_workflow_details (L5.1), garde de taille sur les trois outils de requête (L5.2), champ tool manquant dans les enveloppes d'erreur locales (L5.3).

Hors périmètre : tout le reste de la roadmap (L4.3, L4.4, L4.5, exploration Metrics). QueryExecuteStream est explicitement interdit — piste long terme consignée en L4.5, pas ce lot. Aucun nouvel outil : le compte reste à 23. 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.
  • Le profil AD est cassé et c'est diagnostiqué — ne le réinvestigue pas (tenant introuvable côté STS, point ouvert de la ROADMAP). 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 (la mesure qui fait foi est la longueur de content[0].text via le protocole) :

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 (D6).
  2. Contrat d'erreur : { success: false, error, tool }, isError: true — c'est précisément l'objet de L5.3.
  3. Messages et hints actionnables.
  4. Aucun accès base de données (D1).
  5. D23 : le wrapper valide les noms de paramètres et les requis contre les schémas — tout paramètre ajouté (fenêtre de L5.1…) doit être déclaré dans l'inputSchema. Le wrapper ne valide pas les valeurs : les gardes de valeur vivent dans le code de l'outil.
  6. D24 : signal commun — truncated: true uniquement quand la réponse est coupée, hint actionnable, returned vs total avant coupe. omitted en plus quand des éléments entiers sont écartés. Réutilise ce vocabulaire exactement ; ne crée pas un second dialecte.
  7. Pas de nouveau numéro de décision attendu : ce lot étend D24 (complète son texte : les outils de requête et get_workflow_details entrent dans son périmètre). Si une décision réellement nouvelle s'impose, vérifie le dernier numéro (D26 à ce jour) et réserve D27 dans le commit qui l'acte.

Phase 0 — Confirmer les mesures

Toutes rejouées le 25/08/2026 (protocole, LIMAGRAIN) avec la commande de mesure ci-dessus. À confirmer, pas à réinvestiguer :

Appel Taille constatée
query_wms_entities {"entity_type":"Products","limit":200} (Reading) 957 234
search_wms_data {"keyword":"PAL"} 847 543
call_query_api {"entity_type":"Products","query_type":1,"limit":1} — une seule ligne Writing 95 288
get_workflow_details sur CST_SendRejectContainersToPK (application: "CustomApp") ~101 800
get_workflow_details {"workflow_id":"2a320000-0642-47df-aa52-3b89c27c016f"} (StackerCrane, EasyWMS) 79 092 (dont blob data : 71 512)

Attention aux noms de paramètres (D23 les fait respecter) : search_workflows prend query, search_wms_data et search_logs prennent keyword. Pour trouver l'id du workflow CST : search_workflows {"query":"CST_SendRejectContainersToPK","application":"CustomApp"}.


L5.1 — Paginer le blob data de get_workflow_details

Problème. La réponse embarque la définition complète du workflow (blob data, XML/JSON EasyBuilder) : 71 512 caractères sur le StackerCrane mesuré, davantage sur les gros CST_* — la réponse dépasse le seuil client.

À faire.

  • Deux paramètres de fenêtre sur le blob data (au schéma, D23) : une taille max de tranche (défaut de l'ordre de 20 000 caractères, cohérent avec D24) et un offset (défaut 0). Nommage à ta main (max_data_chars / data_offset ou équivalent), documenté dans les descriptions.
  • La réponse porte toujours la taille totale du blob ; quand la fenêtre tronque : truncated: true + hint donnant l'offset suivant.
  • La tranche est verbatim : découpe de chaîne, rien d'autre.
  • Les métadonnées du workflow (id, name, commonInfo…) restent complètes dans chaque réponse ; seule data est fenêtrée.

Pente naturelle interdite : ne résume pas, ne reformule pas, ne « parse » pas le blob pour n'en renvoyer que des morceaux jugés utiles — la définition EasyBuilder doit rester reconstituable à l'octet près en concaténant les tranches.

Vérification attendue (protocole, LIMAGRAIN) :

  • get_workflow_details sur le StackerCrane (2a320000-0642-47df-aa52-3b89c27c016f) sans paramètre de fenêtre → réponse < ~30 000 caractères, truncated: true, taille totale annoncée = 71 512, hint avec l'offset suivant.
  • En enchaînant les tranches (offset 0, 20 000, 40 000, 60 000) : la somme des longueurs des tranches = 71 512, et la concaténation est identique au blob d'origine (compare au moins les longueurs et les 100 premiers/derniers caractères).
  • Un workflow à petit blob (< défaut) → réponse strictement inchangée, pas de truncated.

L5.2 — Garde de taille sur les outils de requête

Problème. Aucune borne de volume sur query_wms_entities, call_query_api et search_wms_data : 957 234 caractères pour 200 lignes Reading, 847 543 pour une recherche, et 95 288 pour une seule ligne Writing (le modèle Writing sérialise l'agrégat complet — navigations, $id… — là où la même ligne Reading fait ~4 500).

À faire.

  • Garde de taille commune aux trois outils : plafond en variable d'environnement avec défaut (style MAX_LOG_SEARCH_CHARS, 25 000 — même ordre de grandeur, nom à ta main, documenté dans CLAUDE.md).
  • Au-delà du plafond : écarter des lignes entières (pour search_wms_data : des résultats entiers, par entité), et signaler D24 : truncated: true, returned, omitted, hint (réduire limit, ajouter un filter ; et pour query_type != 0 : rappeler que les lignes Writing sont des agrégats complets et suggérer Reading si l'usage le permet).
  • Cas limite à traiter explicitement : une seule ligne dépasse le plafond (réel en Writing). La réponse est alors returned: 0, omitted: <n>, truncated: true, avec un hint qui explique pourquoi et quoi faire — c'est moins bon qu'un résultat, mais c'est mieux qu'un rejet client opaque.
  • Ne change ni MAX_QUERY_ROWS, ni les limites par défaut des outils, ni le comportement sous le plafond : une requête qui tient aujourd'hui doit renvoyer exactement la même réponse.
  • L'avertissement de volume mérite une phrase dans la description du paramètre query_type (D25).

Vérification attendue (protocole, LIMAGRAIN) :

  • query_wms_entities {"entity_type":"Products","limit":200} → réponse sous le plafond (+ marge d'enveloppe), truncated: true, returned < 200, omitted cohérent, hint présent.
  • search_wms_data {"keyword":"PAL"} → borné et signalé de même.
  • call_query_api {"entity_type":"Products","query_type":1,"limit":1}returned: 0, omitted: 1, truncated: true, hint expliquant le volume Writing.
  • query_wms_entities {"entity_type":"Container","limit":1} → réponse strictement identique à aujourd'hui (~4 500 caractères, pas de truncated).
  • count_wms_entities → non concerné, inchangé.

L5.3 — Champ tool dans les enveloppes d'erreur locales

Problème. Les catch locaux d'api-tools.js (deux blocs, vers les lignes 145-162 et 191-208) renvoient { success: false, error } sans le champ tool du contrat. Preuve : call_query_api {"entity_type":"Products","query_type":7} répond aujourd'hui {"success": false, "error": "query_type invalide : …"} — pas de tool.

À faire. Balayer tous les modules de src/tools/ à la recherche d'enveloppes success: false construites localement : soit y ajouter tool, soit laisser l'erreur remonter au wrapper de src/index.js (qui l'ajoute) — au choix selon le cas, mais le résultat observable est uniforme. Attention à ne pas perdre les champs additionnels utiles des enveloppes locales (le warning de résolution d'api-tools, les listes de profils de profile-tools…).

Vérification attendue : call_query_api avec query_type: 7 → l'enveloppe contient "tool": "call_query_api" ; un grep sur src/tools/ ne montre plus d'enveloppe success: false sans tool (colle le résultat du grep dans le compte-rendu).


Méthode

  1. Phase 0 d'abord (cinq mesures, rejouées telles quelles).
  2. L5.1, puis L5.2, puis L5.3 — chaque bloc vérifié en exécution via le protocole avant de passer au suivant.
  3. Rappel : le serveur traite les tools/call en concurrence (point ouvert de la ROADMAP) — pour les vérifications qui comparent des réponses successives, envoie les requêtes séquentiellement.
  4. Les schémas changent : reboucler sur les 23 noms de tools/list (aucun Unknown tool ; execute_command vérifié statiquement, non appelé) et vérifier qu'un paramètre inconnu est toujours rejeté (D23).
  5. Baseline avant/après : handshake (23/6) + npm test (4/4, exit 0).
  6. « 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 bloc (L5.1, L5.2, L5.3), messages expliquant le pourquoi, mesures avant/après dans le corps du message (tailles en caractères).
  • Documentation dans les mêmes commits : compléter D24 (périmètre étendu aux outils de requête et à get_workflow_details) ; CLAUDE.md — la nouvelle variable d'environnement dans les réglages partagés, la fenêtre de get_workflow_details si elle change l'usage documenté ; ROADMAP.md — retirer le lot 5.
  • 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 bloc, la vérification attendue rejouée et son résultat mesuré (colle les tailles et les sorties), plus la baseline finale. Laisse handoff-lot5.md en place pour la révision.