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>
16 KiB
Passation — lot 1
Comment s'en servir. Ouvrir une nouvelle session Claude Code dans
D:\GIT\_PERSO\mcp-wms-apiet lui donner tout ce qui suit, ou simplement : « Lisdocs/handoff-lot1.mdet exécute-le. »Rédigé le 24/08/2026. Toutes les mesures citées ont été prises contre le WMS réel (profil
LIMAGRAIN, tenantLIMAGRAI2512).
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
console.error()uniquement, jamaisconsole.log(). stdout est réservé au JSON du protocole MCP ; une seule écriture parasite casse la session Claude Desktop. Voir D6.- Un outil ne lève jamais d'exception hors du wrapper. Toute erreur revient en
{ success: false, error, tool }avecisError: true. - Les messages d'erreur sont lus par un agent, pas par un humain : ils doivent dire quoi faire ensuite.
- Aucun accès base de données (D1). Tout passe par les API REST.
- Préfixer les logs par composant :
[Server],[API],[Workflow],[WorkflowTools]… - 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érationnel — Context.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 seulEasyWMS; - 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.mdsi 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
ApplicationetQueryTypedans 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
applicationouquery_typene doit pas la toucher.
- L1.1 : inclure
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(etInnerException.Message) portent l'essentiel — privilégie-les auJSON.stringifyintégral, qui noie l'information dans du bruitWatsonBuckets/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/calldoit ê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 :
src/tools/workflow-tools.js, fonctionsearchWorkflows(~ligne 110) : projettew.Id,w.Code,w.Name,w.Category,w.Description,w.Version,w.Created,w.Modified. Tous valentundefined,JSON.stringifyles supprime — d'où 50 objets vides{}alors quecountest correct.src/services/workflow-service.js(~lignes 123, 167, 187) : litw.category || w.Category. Ces deux clés n'existent pas. Conséquence mesurée :list_workflow_categoriesrenvoie 0 catégorie etgetWorkflowStats()classe les 4012 workflows enUncategorized.
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
searchWorkflowssur les clés réelles, en conservant le repliw.id || w.Idde D5. Code,Description,Created,Modifiedn'existent dans aucune casse : ne les projette pas plutôt que d'exposer desnullpermanents. Si tu juges qu'un équivalent existe danscommonInfo, inspecte-le d'abord et décide sur pièces.- Pour la catégorie : le seul champ approchant est
applicationName, mais il vautEasyWMSpour tous les workflows. La notion de catégorie n'a donc aucun support dans les données. Traite-la honnêtement — mappe surapplicationNameen 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ètrecategorydesearch_workflowsdoit rester cohérent avec ce que tu décides, etlist_workflow_categoriesne 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
maina 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.mdl'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.mdet 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.