Files
mcp-wms-api/docs/handoff-lot1.md
T
Arthur Ria 7c722dae91 Passation : prompt autoportant pour le lot 1
Ajoute docs/handoff-lot1.md, le prompt à donner à une nouvelle session Claude
Code pour implémenter le lot 1 de la roadmap. Il vivait jusqu'ici dans un
dossier temporaire de session, donc perdable.

Contient la phase 0 de vérifications préalables (les quatre contextes de
requête, le périmètre applications, le champ ClientModule), les trois
correctifs avec leurs preuves et leurs vérifications attendues, et les
consignes de livraison.

Document à usage unique : à supprimer une fois le lot 1 livré, la référence
durable restant ROADMAP.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 16:33:11 +02:00

16 KiB

Passation — lot 1

Comment s'en servir. Ouvrir une nouvelle session Claude Code dans D:\GIT\_PERSO\mcp-wms-api et lui donner tout ce qui suit, ou simplement : « Lis docs/handoff-lot1.md et exécute-le. »

Rédigé le 24/08/2026. Toutes les mesures citées ont été prises contre le WMS réel (profil LIMAGRAIN, tenant LIMAGRAI2512).


Tu interviens sur le dépôt D:\GIT\_PERSO\mcp-wms-api : un serveur MCP (Node.js, CommonJS) qui donne à Claude un accès en lecture à un WMS EasyWMS de Mecalux, via ses API REST uniquement. Lis CLAUDE.md, DECISIONS.md et ROADMAP.md avant de coder — ils contiennent les conventions et les pièges déjà constatés en production.

Ta mission : implémenter le lot 1 décrit dans ROADMAP.md. Trois correctifs indépendants, tous reproduits contre le WMS réel le 24/08/2026. Ne fais rien du lot 2 ni du lot 3.

Contexte matériel

Le profil LIMAGRAIN du .env local est fonctionnel et pointe sur un WMS joignable. npm test passe 4/4 aujourd'hui — c'est ta baseline, il doit continuer à passer.

Deux commandes de vérification que tu réutiliseras :

npm test
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/list"}' | node src/index.js

Le serveur parle JSON-RPC sur stdin/stdout. Pour appeler un outil, ajoute une ligne {"jsonrpc":"2.0","id":N,"method":"tools/call","params":{"name":"...","arguments":{...}}} après la notification initialized.

Contraintes non négociables

  1. console.error() uniquement, jamais console.log(). stdout est réservé au JSON du protocole MCP ; une seule écriture parasite casse la session Claude Desktop. Voir D6.
  2. Un outil ne lève jamais d'exception hors du wrapper. Toute erreur revient en { success: false, error, tool } avec isError: true.
  3. Les messages d'erreur sont lus par un agent, pas par un humain : ils doivent dire quoi faire ensuite.
  4. Aucun accès base de données (D1). Tout passe par les API REST.
  5. Préfixer les logs par composant : [Server], [API], [Workflow], [WorkflowTools]
  6. Ne touche pas à .env (ignoré par git, contient des credentials réels).

Phase 0 — Vérifications avant de coder

À faire en premier, avant toute modification de code. Ces deux points conditionnent des évolutions du lot 4 ; ils ne sont pas à implémenter maintenant, mais tu dois les vérifier pour ne pas coder le lot 1 d'une manière qui les bloquerait, et pour confirmer ou infirmer les mesures ci-dessous.

Aucune modification de code dans cette phase. Uniquement des appels en lecture.

V1 — Modèle de données : Reading ou Writing ?

QueryType est figé à 0 (Reading) en dur dans src/services/api-service.js (executeQuery ~ligne 249, executeScalarQuery ~ligne 286). Aucun outil n'expose de bascule.

Or QueryContextType a quatre valeurs, pas deux. Mesures déjà faites sur LIMAGRAI2512 — confirme-les :

Valeur Contexte Résultat mesuré
0 Reading (ApplicationReadingContext) opérationnel, seul utilisé
1 Writing (ApplicationWritingRepository) opérationnelContext.Products répond
2 DataWarehouse non configuré : Could not resolve serviceType 'IDataWarehouse…'
3 Metrics (ApplicationMetricDataContext) contexte présent, modèle non exploré

Astuce de diagnostic à retenir : le message d'erreur nomme le contexte utilisé. C'est le moyen le plus rapide de savoir quel QueryType a réellement servi.

Rappel D3 : en QueryType: 1 les champs de statut sont des énumérations, donc == "Release" échoue. Le défaut devra rester 0.

V2 — Applications : le MCP ne voit-il que EasyWMS ?

Constat à vérifier : Application vient de WMS_APPLICATION dans .env, partagé par tous les profils, sans surcharge par appel ni paramètre d'outil.

POST /AD/api/Application/GetAll déclare 9 applications sur le tenant LIMAGRAI2512. Mesures à confirmer :

Application Workflows Queries Entities
EasyWMS 4012 2239 338
CustomApp 153 54 11
AGV 71 14 5
Notifications 26 35 24
GalileoFaults 9 20 24
Common 1 7 25

CustomApp porte le spécifique client (workflows et entités préfixés CST_) — exactement ce qu'on cherche en debug, et invisible aujourd'hui.

Deux sous-questions, de difficulté très différente :

a) API AD. L'application est un simple champ du payload [application, tenant, pageSize, offset]. Mesuré : ["CustomApp", tenant, 5, 0] sur /Workflow/GetByApplication renvoie bien des workflows CST_. Confirme-le, et note que les caches de ad-service et workflow-service sont aujourd'hui indexés sans l'application : les rendre multi-applications imposera une clé de cache incluant l'application, sinon les résultats se mélangeront.

b) QueryExecute — déjà tranché, ne le réinvestigue pas. Le champ Application ne partitionne pas le contexte de lecture : Context.AgvTasks (entité de l'application AGV) répond aussi bien avec Application: "AGV" qu'avec Application: "EasyWMS". Le contexte est commun au tenant.

Et les entités CustomApp ne sont interrogeables dans aucun contexte : les 11 entités CST_ testées sous les quatre QueryType, au singulier et au pluriel, échouent toutes ; Metadata/Entities et Metadata/EntitiesAll renvoient 0 entité pour CustomApp ; aucune n'est marquée isDataWarehouse. Ce sont des définitions EasyBuilder sans projection requêtable.

Deux conséquences à retenir, elles portent sur le lot 2 :

  • la table de résolution de L2.1 devra agréger le Metadata de toutes les applications (?applicationName= par application : 232 + 20 + 6 + …), pas du seul EasyWMS ;
  • inutile d'ajouter un paramètre application à QueryExecute, il ne changerait rien. Le paramètre n'a de sens que sur les outils AD et workflow.

Contente-toi de confirmer ces mesures si tu veux, mais ne relance pas d'investigation dessus : c'est du temps perdu.

V3 — Champs de QueryExecute que le MCP n'envoie pas

La référence officielle de l'API (https://<host>/ApplicationService/Help) documente des champs inexploités. Un seul te concerne pour le lot 1 :

ClientModule. Le MCP ne l'envoie pas, donc ses requêtes apparaissent dans les logs du WMS sous Execute error. Client: GNA — le client OAuth partagé, indistinguable du vrai client GNA. Le champ est accepté (vérifié).

Tu peux le renseigner dans le cadre de L1.1, c'est cohérent avec l'objectif de traçabilité et ça tient en une ligne : envoyer ClientModule: "MCP-WMS" dans les corps de QueryExecute et QueryScalarExecute. Vérifie ensuite dans les logs du WMS (search_logs) que les requêtes du MCP apparaissent bien sous ce nom. Si la vérification n'est pas concluante, retire le changement et signale-le plutôt que de le laisser non vérifié.

Les autres champs (Parameters, CommandTimeout, QueryId, QueryExecuteStream) sont consignés en L4.5 : n'y touche pas.

Ce que tu fais de ces vérifications

  • Rapporte les résultats dans ton compte-rendu final, en distinguant ce que tu as mesuré de ce que tu supposes.
  • Mets à jour la section Lot 4 de ROADMAP.md si tes mesures diffèrent, ou si tu as tranché la question (b).
  • N'implémente ni V1 ni V2. C'est le lot 4. Seul ClientModule (V3) entre dans ton périmètre, et seulement s'il est vérifiable.
  • Deux incidences concrètes sur ta façon de coder le lot 1, et elles seules :
    • L1.1 : inclure Application et QueryType dans le payload rapporté par l'erreur enrichie. Sans ça, un futur bug multi-applications sera aussi opaque que les 500 d'aujourd'hui.
    • L1.2 : la table de routage ne doit rien présumer de la signature des outils — ajouter demain un paramètre application ou query_type ne doit pas la toucher.

L1.1 — Remonter le détail des erreurs HTTP

Problème. Toute erreur d'API se résume aujourd'hui à Request failed with status code 500. Or le WMS renvoie déjà le diagnostic complet dans le corps de la réponse. Vérifié :

POST /QueryExecute  Expression: "Context.Container.OrderBy(z => z.Id)"
→ 500, content-type: application/json
→ {"ClassName":"System.AggregateException","Message":"Compile Error: ...
   'ApplicationReadingContext' ne contient pas de définition pour 'Container' ..."}

Ce corps est jeté par le catch. Une session Cowork a passé une heure sur des 500 dont la cause était écrite noir sur blanc dans la réponse HTTP.

À faire. Dans src/services/api-service.js, aux blocs catch de post() (ligne ~164) et get() (ligne ~212), enrichir l'erreur propagée avec :

  • le statut HTTP,
  • l'URL complète et le verbe,
  • le payload envoyé,
  • le corps de la réponse, tronqué à ~2000 caractères,
  • le message d'origine.

Points d'attention :

  • Le corps peut être un objet ou une chaîne. S'il est structuré, Message (et InnerException.Message) portent l'essentiel — privilégie-les au JSON.stringify intégral, qui noie l'information dans du bruit WatsonBuckets / HResult.
  • Ne casse pas le retry 401 existant : le rejeu après refresh de token doit rester intact, et une erreur pendant le rejeu doit elle aussi être enrichie.
  • Le point de passage est unique : ne duplique pas cette logique dans les modules tools/.
  • N'inclus jamais le token Bearer ni un mot de passe dans le message enrichi.

Vérification attendue. Appeler query_wms_entities avec entity_type: "Container" (singulier — volontairement faux) doit faire apparaître ApplicationReadingContext ne contient pas de définition pour 'Container' dans la réponse de l'outil.


L1.2 — Fiabiliser le routage des outils

Problème. src/index.js route les appels d'outils par préfixe de nom dans une cascade de else if (handler tools/call, ~ligne 167). Deux outils sont listés dans tools/list mais n'atteignent jamais leur module :

Outil Cause Erreur reproduite
get_entity_metadata startsWith('get_entity_') le capte pour wmsQueryTools, dont la branche vient avant la sienne Unknown WMS query tool: get_entity_metadata
list_log_files la branche logs teste includes('_logs'), or le nom contient _log_files Unknown tool: list_log_files

À faire. Remplacer la cascade par une table explicite nom d'outil → module, construite au démarrage en parcourant les listTools() des 8 modules de src/tools/. Le dispatch devient un simple lookup.

Exigences :

  • Si deux modules déclarent le même nom d'outil, échouer au démarrage avec un message nommant les deux modules — c'est un bug de développement, pas un cas d'exécution.
  • Un outil listé est un outil routé, par construction : c'est tout l'intérêt du changement.
  • Le wrapper d'erreur existant du handler tools/call doit être conservé tel quel.
  • Les 23 outils doivent rester exposés, aux mêmes noms. Ce n'est pas une refonte de l'API : aucun renommage.

Vérification attendue. get_entity_metadata et list_log_files répondent tous les deux, et un tools/list renvoie toujours 23 outils. Vérifie aussi que rien n'a régressé sur les 21 autres : chaque nom listé doit résoudre vers un module.


L1.3 — Corriger les projections de champs des workflows

Problème. L'API Application Dictionary renvoie ses champs en minuscules. Les clés réelles d'un objet workflow, relevées en direct :

$id, validFrom, validTo, versionId, version, lockInfo, id, name,
commonInfo, applicationName, dataType, data

Deux endroits supposent autre chose :

  1. src/tools/workflow-tools.js, fonction searchWorkflows (~ligne 110) : projette w.Id, w.Code, w.Name, w.Category, w.Description, w.Version, w.Created, w.Modified. Tous valent undefined, JSON.stringify les supprime — d'où 50 objets vides {} alors que count est correct.
  2. src/services/workflow-service.js (~lignes 123, 167, 187) : lit w.category || w.Category. Ces deux clés n'existent pas. Conséquence mesurée : list_workflow_categories renvoie 0 catégorie et getWorkflowStats() classe les 4012 workflows en Uncategorized.

C'est une application incomplète du piège D5 : le service gère bien la double casse pour name/id (d'où un filtrage correct), mais la projection du tool et la notion de catégorie ont été écrites sur des champs imaginaires.

À faire.

  • Aligner la projection de searchWorkflows sur les clés réelles, en conservant le repli w.id || w.Id de D5.
  • Code, Description, Created, Modified n'existent dans aucune casse : ne les projette pas plutôt que d'exposer des null permanents. Si tu juges qu'un équivalent existe dans commonInfo, inspecte-le d'abord et décide sur pièces.
  • Pour la catégorie : le seul champ approchant est applicationName, mais il vaut EasyWMS pour tous les workflows. La notion de catégorie n'a donc aucun support dans les données. Traite-la honnêtement — mappe sur applicationName en l'assumant, ou retire la notion — mais n'invente pas une taxonomie en dérivant des catégories d'un préfixe de nom. Le paramètre category de search_workflows doit rester cohérent avec ce que tu décides, et list_workflow_categories ne doit pas mentir sur ce qu'il renvoie.
  • Vérifie au passage get_workflow_details : il renvoie l'objet brut, il ne devrait pas être affecté — confirme-le plutôt que de le supposer.

Vérification attendue. search_workflows({query: "stacker"}) renvoie des objets peuplés avec au minimum id et name (attendu : des noms comme StackerCrane_SortPickingTasksBalancingOutputs_PR). list_workflow_categories renvoie un résultat cohérent avec la réalité des données, et non 0.


Méthode

Fais la phase 0 d'abord, sans toucher au code. Puis les trois correctifs dans l'ordre : L1.1 en premier, parce qu'il rend les deux autres plus faciles à diagnostiquer.

Vérifie contre le WMS réel, pas seulement par lecture de code. Chaque correctif a une vérification attendue décrite ci-dessus ; exécute-la. Les appels concernés sont en lecture seule, aucun risque d'écriture WMS. Si un point ne peut pas être vérifié, dis-le explicitement plutôt que de le déclarer fait.

Avant de conclure, relance la baseline complète :

  • npm test → 4/4,
  • handshake MCP → 23 outils, 6 resources,
  • les trois vérifications attendues ci-dessus.

Livraison

  • Un commit par correctif (L1.1, L1.2, L1.3), messages en français, pour que chacun soit revertable seul. Stage chemin par chemin, pas de git add -A.
  • Termine les messages de commit par Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>.
  • Ne pousse pas — la branche main a un remote, le push est une décision du propriétaire du dépôt.
  • Mets à jour ROADMAP.md : retire le lot 1 une fois livré.
  • Ajoute à DECISIONS.md l'entrée D22 (routage par table explicite : pourquoi le routage par préfixe a été abandonné, avec les deux collisions comme preuve). Suis le format des entrées existantes. D21 est réservée au lot 2, ne l'utilise pas.
  • Si tu découvres une anomalie hors périmètre, ne la corrige pas : ajoute-la à ROADMAP.md et signale-la dans ton compte-rendu.

Dans ton compte-rendu final, indique ce que tu as vérifié en exécution et ce que tu n'as pas pu vérifier.