# 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](../CLAUDE.md) et [../DECISIONS.md](../DECISIONS.md) avant de toucher au code. **Mission** : trois blocs de [../ROADMAP.md](../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 : ```bash 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) : ```bash 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) : ```bash 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`, `Aliases` → `Alias`, 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.