Files
mcp-wms-api/docs/handoff-lot4.md
T
Arthur Ria 35b53dbef5 Passation lot 4 : query_type, application AD/workflow, catalog D3 (L4.0-L4.2)
Mesures rafraîchies du 25/08/2026 : Writing (QueryType 1) répond 1 ligne
sur Context.Products, Metrics (3) existe sans Products, CustomApp
renvoie ses workflows CST_ sous la clé entities de GetByApplication.
Découverte consignée en L4.0 : la resource api://catalog documente un
exemple QueryType: 1 (le piège D3), un Select dans l'expression et
l'entité fantôme Aliases.

D25 (rapport D3/query_type) et D26 (clés de cache par application)
réservées. L4.3-L4.5 et l'exploration Metrics explicitement hors
périmètre. Le point délicat resolver/Writing est cadré : nom inconnu du
Reading transmis tel quel avec warning quand query_type != 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 10:58:50 +02:00

13 KiB

Passation — lot 4 (L4.0, L4.1, L4.2)

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 : trois blocs de ../ROADMAP.md — corriger la resource api://catalog qui enseigne le piège D3 (L4.0), exposer query_type sur les outils de requête (L4.1), et rendre les applications autres qu'EasyWMS accessibles via un paramètre application sur les outils AD et workflow (L4.2).

Hors périmètre :

  • L4.3 (identifier le MCP dans les logs) : dépend d'une configuration côté WMS, hors de portée d'une session de codage.
  • L4.4 (API WorkflowLog) et l'exploration du contexte Metrics : ce sont des investigations, elles feront l'objet d'une phase séparée dont le livrable sera un rapport, pas du code. N'y touche pas.
  • L4.5 (Parameters, QueryExecuteStream…) : consigné, pas ce lot.
  • Aucun nouvel outil : le compte reste à 23. Les deux correctifs sont des paramètres sur des outils existants.
  • 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). 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);}});"

Appeler un outil via le protocole (la seule preuve qu'un outil marche) :

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

Sonder le WMS directement (court-circuite les outils) :

node -e "
require('dotenv').config();
const pm=require('./src/config/profile-manager'); pm.loadProfiles();
const api=require('./src/services/api-service').getInstance();
(async()=>{ /* … */ })();"

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 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 — déclare query_type et application dans les inputSchema, sinon le wrapper les rejettera. Le wrapper ne valide pas les valeurs : les gardes de valeur (query_type hors 0-3, etc.) vivent dans le code de l'outil.
  6. D8 : tout état lié au tenant s'invalide par profileManager.onSwitch(), jamais à la main depuis un autre module. Les caches modifiés en L4.2 restent abonnés.
  7. D24 : toute sortie potentiellement volumineuse est bornée et signalée (truncated/hint). Les pages de workflows CustomApp (153) tiennent largement ; ne l'oublie pas si tu exposes des listes plus larges.

Numéros de décision réservés : D25 = exposition de query_type (son rapport à D3), D26 = paramètre application et clés de cache. Vérifie que D24 est bien la dernière décision avant d'écrire.


Phase 0 — Confirmer les mesures

Toutes rejouées le 25/08/2026 (LIMAGRAI2512) par la sonde directe. À confirmer, pas à réinvestiguer :

Sonde Constaté le 25/08
POST /QueryExecute {Application:'EasyWMS', QueryType:1, Expression:'Context.Products.OrderBy(z => z.Id)', Take:1} OK, 1 ligne — le modèle Writing répond
Même appel avec QueryType:3 HTTP 500 : 'ApplicationMetricDataContext' ne contient pas de définition pour 'Products' — le contexte Metrics existe, son modèle est autre (ne l'explore pas, hors périmètre)
QueryType:2 non configuré sur ce tenant (Could not resolve serviceType 'IDataWarehouse…', mesure du 24/08)
POST /Workflow/GetByApplication payload ['CustomApp', tenant, 5, 0] (API AD) 5 workflows sous la clé entities de la réponse : CST_SendRejectContainersToPK, Helper_ContainerByCode, …

Points déjà tranchés, ne les réinvestigue pas (mesures des 24-25/08 dans la ROADMAP) :

  • Le champ Application de QueryExecute ne partitionne rien (contexte commun au tenant) : inutile de le paramétrer côté requêtes.
  • Les 11 entités CustomApp ne sont requêtables dans aucun contexte — l'API AD est le seul accès au spécifique client.
  • ClientModule est sans effet (L4.3).

L4.0 — Corriger la resource api://catalog

Problème. src/resources/apis.js (resource lue par les sessions Claude) documente un exemple QueryExecute avec "QueryType": 1 — exactement ce que D3 interdit de recopier —, un .Select(z => z) dans l'expression (contraire à la répartition expression/options), et l'entité fantôme Aliases (corrigée partout ailleurs au lot 2).

À faire. Exemple avec QueryType: 0 et expression sans Select, AliasesAlias, renvoi vers get_entity_metadata comme source de vérité sur les entités. Relis toute la resource pendant que tu y es — signale (sans forcément corriger) toute autre affirmation contredite par DECISIONS.md.

Vérification attendue : lire api://catalog via resources/read en protocole ; la sortie ne contient plus ni "QueryType": 1 ni Aliases.

L4.1 — Exposer query_type sur les outils de requête

Problème. QueryType est figé à 0 en dur à deux endroits : src/services/api-service.js:333 (executeQuery) et :370 (executeScalarQuery). Le modèle Writing — opérationnel, mesuré — est inatteignable.

À faire.

  • Paramètre query_type (entier, défaut 0) sur trois outils : call_query_api, query_wms_entities, count_wms_entities. Pas sur get_entity_schema ni search_wms_data — ils restent des raccourcis Reading.
  • Garde de valeur dans le code (le wrapper D23 ne valide pas les valeurs) : hors 0..3 → erreur locale actionnable avant tout réseau, nommant les quatre contextes. 2 et 3 sont transmis tels quels : le WMS répond, et depuis L1.1 son diagnostic remonte entier.
  • D3 reste la règle par défaut : en Writing les statuts sont des énumérations, == "Release" échoue. La bascule est un opt-in explicite — les descriptions d'outils doivent porter l'avertissement. Acte le rapport D3/query_type en D25.
  • Interaction avec le resolver (attention, c'est le point délicat) : la table de résolution est construite sur le Metadata Reading. Quand query_type != 0 : un nom qui se résout se résout normalement (Products marche en Writing, mesuré) ; un nom inconnu du Reading ne doit pas être bloqué en dur — passe-le tel quel avec un warning dans la réponse (même mécanique que le repli « Metadata injoignable » existant), car le modèle Writing/Metrics peut contenir des entités hors Reading.

Vérification attendue (protocole, LIMAGRAIN) :

  • call_query_api("Products", query_type: 1, limit: 1) → succès, 1 ligne.
  • call_query_api("Products", query_type: 3) → erreur structurée contenant ApplicationMetricDataContext.
  • call_query_api("Products", query_type: 7) → erreur locale avant réseau (aucun [API] POST dans stderr), nommant les valeurs valides.
  • query_wms_entities("Container", limit: 1) sans query_type → strictement le comportement d'aujourd'hui (résolu Containers, Reading).
  • Un nom inconnu avec query_type: 1 → transmis tel quel avec warning, pas d'échec local.
  • count_wms_entities("Product", query_type: 1) → un nombre (~51 160).

L4.2 — Paramètre application sur les outils AD et workflow

Problème. application vient du profil (WMS_APPLICATION, partagé) : src/services/ad-service.js:85 et src/services/workflow-service.js:50. Le MCP n'interroge donc jamais que EasyWMS. Or CustomApp porte le spécifique client (153 workflows CST_* sur ce tenant) — précisément ce qu'on cherche en debug — et 9 applications sont déclarées (POST /AD/api/Application/GetAll, payload null).

À faire.

  • Paramètre application (défaut : l'application du profil, donc comportement inchangé sans lui) sur : get_ad_elements, search_ad_elements, get_ad_element_details, search_workflows, get_workflow_details, list_workflow_categories.
  • Clés de cache : ad-service passe de « un cache par type » à « un cache par (application, type) » ; workflow-service de « un cache global » à « un cache par application ». Sans ça, un appel CustomApp pollue le cache EasyWMS. L'abonnement onSwitch() continue d'invalider tout (D8). Acte le contrat en D26.
  • get_application_summary doit refléter les nouvelles clés (état par application et par type) sans exploser en volume (D24).
  • list_workflow_categories : adosse la liste à Application/GetAll (9 applications, comptes réels par application) plutôt qu'aux applicationName du seul cache EasyWMS. La note de L1.3 reste vraie — il n'existe pas de champ catégorie ; la réponse liste des applications et le dit. Le paramètre category de search_workflows (filtre sur applicationName) doit rester cohérent avec le nouveau paramètre application — documente leur articulation dans les descriptions, ne casse ni l'un ni l'autre.
  • Pente naturelle interdite : ne précharge pas les 9 applications (le type Resource pèse 29 374 éléments sur la seule EasyWMS). Le chargement reste paresseux, par application effectivement demandée.

Vérification attendue (protocole, LIMAGRAIN) :

  • search_workflows("CST_", application: "CustomApp") → objets peuplés (CST_SendRejectContainersToPK…).
  • get_ad_elements("Workflow", application: "CustomApp") → éléments CST_*.
  • Séquence séquentielle EasyWMS → CustomApp → EasyWMS sur search_workflows : les comptes restent distincts (~4 012 vs 153), aucune pollution croisée ; get_application_summary montre les deux caches.
  • Sans paramètre application → comportement strictement inchangé.
  • switch_wms_profile puis retour → caches invalidés (log Cache cleared).

Méthode

  1. Phase 0 d'abord (sondes rejouées telles quelles — la forme entities de la réponse AD est déjà établie, ne la redécouvre pas).
  2. L4.0, puis L4.1, puis L4.2 — chaque bloc vérifié en exécution via le protocole avant de passer au suivant.
  3. Attention à la concurrence : le serveur traite les tools/call en concurrence (point ouvert de la ROADMAP). Pour les vérifications qui enchaînent bascules ou séquences de cache, envoie les requêtes séquentiellement (attendre chaque réponse), pas en rafale.
  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 (L4.0, L4.1, L4.2), messages expliquant le pourquoi, mesures rejouées dans le corps du message.
  • Documentation dans les mêmes commits : D25 et D26 dans DECISIONS.md ; CLAUDE.md — nuancer « QueryType: 0, jamais 1 » en « défaut 0, query_type est un opt-in documenté (D25) », documenter le paramètre application et les nouvelles clés de cache dans la section Caches ; ROADMAP.md — retirer L4.0, L4.1, L4.2 (L4.3, L4.4, L4.5 restent).
  • 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 sorties), plus la baseline finale. Laisse handoff-lot4.md en place pour la révision.