Files
mcp-wms-api/CLAUDE.md
T
Arthur Ria 6f54d765c4 L5.1 : fenetre verbatim sur le blob data de get_workflow_details
La reponse embarquait la definition EasyBuilder complete, au-dela du seuil de
rejet du client MCP (~70 000 caracteres, D24). Deux parametres de fenetre au
schema (D23) : max_data_chars (defaut 20 000) et data_offset (defaut 0). Les
metadonnees du workflow restent completes dans chaque tranche ; seul `data`
est fenetre, et dataTotalChars est porte par toute reponse.

La tranche est verbatim -- decoupe de chaine, rien d'autre. Ne jamais resumer
ni parser ce blob : la concatenation des tranches doit reconstituer la
definition a l'octet pres. Verifie : 20 000 + 20 000 + 20 000 + 11 512 =
71 512, concatenation identique au blob d'origine (premiers et derniers
caracteres compris).

Mesures avant/apres (protocole, LIMAGRAIN, longueur de content[0].text) :

  StackerCrane_LocationIsAccessibleByExtractor_PR   79 092 -> 23 117
    (blob data : 71 512, desormais annonce par dataTotalChars)
  CST_SendRejectContainersToPK (CustomApp)         101 816 -> 23 023
    (blob data : 92 362)
  StackerCrane_LoadMovementForOutboundTask_PR       10 587 -> 10 652
    (blob de 9 013 : sous le defaut, objet workflow identique a l'octet pres,
     ni truncated ni hint -- les +65 caracteres sont les trois champs de
     fenetre, contrat "total toujours porte" de D24)

Gardes de valeur dans le code de l'outil, pas dans le wrapper (D23 ne valide
que les noms) : data_offset: -5 et max_data_chars: 1.5 echouent avant tout
appel reseau avec un message nommant l'attendu.

Baseline preservee : 23 outils, 6 resources.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 15:08:13 +02:00

316 lines
14 KiB
Markdown

# CLAUDE.md
Guide pour Claude Code (claude.ai/code) sur ce dépôt.
**Avant de modifier quoi que ce soit, lisez [DECISIONS.md](DECISIONS.md).** Il
consigne les choix d'architecture et les pièges vérifiés sur un WMS réel. La
plupart des comportements qui semblent bizarres y sont expliqués et sont
volontaires ; les références `D1`, `D2`… de ce fichier y renvoient.
| Besoin | Fichier |
|---|---|
| Installer, lancer, brancher Claude Desktop | [README.md](README.md) |
| Pourquoi le code est ainsi, pièges terrain | [DECISIONS.md](DECISIONS.md) |
| Le serveur ne répond pas, lire ses logs | [MONITORING.md](MONITORING.md) |
| Ce qui reste à faire | [ROADMAP.md](ROADMAP.md) |
| Superviser le projet, réviser une livraison | [docs/supervision.md](docs/supervision.md) |
| Accéder aux logs du WMS | [docs/logs.md](docs/logs.md) |
| Références EasyWMS (API, entités) | [docs/](docs/) |
---
## Objet
Serveur MCP donnant à Claude un accès en lecture à un WMS EasyWMS (Mecalux)
pour le debug et l'analyse. **Fonctionnel et en service.**
**Architecture : 100 % API REST, aucun accès base de données** (D1).
| API | Endpoint | Usage |
|---|---|---|
| Query | `POST {api}/QueryExecute` | requêtes LINQ, lignes |
| Référence complète | `https://<host>/ApplicationService/Help` | page d'aide générée du service — **la source de vérité** sur les champs et les endpoints |
| Query scalaire | `POST {api}/QueryScalarExecute` | `Count()`, `Sum()` — à préférer pour « combien » |
| Command | `POST {api}/CommandExecute` | exécution de commandes WMS |
| Metadata | `GET {api}/Metadata/Entities`, `GET {api}/Metadata/EntityProperties` | entités interrogeables et leurs champs |
| GenericSearch | `GET {api}/GenericSearch/Categories`, `POST {api}/GenericSearch/Search` | recherche plein texte indexée |
| Application Dictionary | `POST {ad}/{Type}/GetByApplication` | 20 types d'éléments, dont `Workflow` |
`{api}` = `https://<host>/ApplicationService/api` et `{ad}` =
`https://<host>/AD/api`, construits depuis le host du profil actif.
Authentification : OAuth 2.0 avec refresh automatique (D2, et
[MONITORING.md](MONITORING.md) §4).
---
## Structure
```
src/
├── index.js Point d'entrée MCP : handlers list/read/call, routage
├── config/
│ └── profile-manager.js Registre multi-profils + bascule runtime
├── resources/ Contexte en lecture seule (6 resources)
│ ├── wms-entities.js wms://entities
│ ├── entity-schemas.js wms://entity-schemas
│ ├── query-examples.js wms://query-examples — exemples LINQ + recettes de diagnostic
│ ├── workflows.js workflows://overview
│ ├── apis.js api://catalog
│ └── logs.js logs://guide — patterns d'erreur et scénarios de debug
├── services/ Logique métier
│ ├── api-service.js OAuth + client HTTP + helpers de requête (singleton)
│ ├── entity-resolver.js Résolution Name|TableName -> TableName (D21)
│ ├── workflow-service.js Workflows, lazy loading + cache
│ ├── ad-service.js Application Dictionary, 20 types, cache par type
│ ├── wms-query-service.js Construction d'expressions LINQ
│ └── log-service.js Lecture et recherche dans les fichiers de logs
└── tools/ 23 outils MCP
├── wms-query-tools.js query_wms_entities, count_wms_entities,
│ get_entity_schema, search_wms_data
├── api-tools.js call_query_api, execute_command
├── workflow-tools.js search_workflows, get_workflow_details,
│ list_workflow_categories
├── ad-tools.js get_application_summary, get_ad_elements,
│ search_ad_elements, get_ad_element_details, list_ad_types
├── metadata-tools.js get_entity_metadata, generic_search
├── config-tools.js get_system_parameters
├── profile-tools.js list_wms_profiles, get_current_wms_profile,
│ switch_wms_profile
└── log-tools.js read_recent_logs, list_log_files, search_logs
scripts/
├── test-connection.js Smoke test de connectivité (npm test)
└── test-ad-api.ps1 Validation curl des endpoints AD (credentials en paramètres)
docs/
└── reference-queries-api.php Client PHP d'origine — source des patterns d'API.
⚠️ utilise QueryType 1 : ne pas recopier (D3)
```
**Routage.** `src/index.js` construit au démarrage une **table nom d'outil →
module** depuis les `listTools()` des 8 modules de `src/tools/` ; `tools/list`
et le dispatch sont servis par cette même table, donc un outil listé est routé
par construction (D22). Deux modules déclarant le même nom font échouer le
serveur au démarrage.
---
## Conventions non négociables
1. **`console.error()` uniquement.** stdout est réservé au JSON MCP ; toute
écriture y casse la session Claude Desktop (D6).
2. **Préfixer les logs** par composant : `[Server]`, `[API]`, `[Profile]`,
`[Workflow]`, `[AD]`, `[Logs]`, `[<X>Tools]`. Le tableau complet est dans
[MONITORING.md](MONITORING.md) §2.
3. **Un outil ne plante jamais le serveur.** Toute erreur revient en réponse
structurée `{ success: false, error, tool }` avec `isError: true` — le
wrapper est dans le handler `tools/call` de `src/index.js`.
4. **Messages d'erreur actionnables.** Ils sont lus par Claude, pas par un
humain : dire quoi faire ensuite (« appelez `switch_wms_profile` », « profils
disponibles : … »).
5. **Aucun accès base de données** (D1).
6. **1000 lignes maximum** par requête, timeout 30 s (`MAX_QUERY_ROWS`,
`QUERY_TIMEOUT`).
7. **Pas de concaténation LINQ à partir d'entrées utilisateur** quand un filtre
côté JS suffit (D11).
---
## Multi-profils
Un même serveur dessert plusieurs backends WMS. Un profil = un host + des
credentials + un tenant (D8).
**Déclaration** dans `.env` :
```env
WMS_PROFILES=AD,LIMAGRAIN,EUROTRAFIC
DEFAULT_WMS_PROFILE=LIMAGRAIN
AD_HOST=10.255.255.2
AD_USERNAME=
AD_PASSWORD=
AD_TENANT=AD
AD_SAAS=false
```
Réglages **partagés** par tous les profils : `WMS_API_AUTH`,
`WMS_APPLICATION`, `WMS_API_PATH`, `WMS_TOKEN_PATH`, `WORKFLOW_API_PATH`,
`LOGS_PATH`, et les variables de cache / token / requêtes. Seul le host varie :
les URL sont assemblées en `https://<HOST><PATH>`.
**`<NOM>_SAAS=true`** → WMS cloud : les outils de log échouent avec un message
explicite (D9). Par défaut `false`.
**`LOGS_PATH`** accepte le placeholder `{host}`, substitué par le host du profil
actif à chaque appel.
**`MAX_LOG_SEARCH_CHARS`** (défaut 25 000) : plafond en caractères de la réponse
de `search_logs` — au-delà, des résultats entiers sont écartés et signalés
(`truncated`, D24).
**Au runtime.** `profile-manager` est un singleton d'état global. Les services
s'abonnent via `onSwitch()` pour invalider ce qui dépend du tenant :
| Service | Réaction |
|---|---|
| `api-service` | `resetToken()` |
| `workflow-service` | `clearCache()` |
| `ad-service` | `invalidateCache()` |
**N'invalidez jamais ces caches à la main depuis un autre module** — l'abonnement
suffit (D8). Tout nouveau service portant un état lié au tenant **doit**
s'abonner.
Sans profil actif, `getCurrent()` lève une erreur qui énumère les profils
disponibles : c'est ainsi que Claude sait appeler `switch_wms_profile`.
---
## Caches
TTL commun `WORKFLOW_CACHE_TTL` (3 600 000 ms), chargement paresseux, vidés à
chaque bascule de profil (D10). Les outils AD et workflow acceptent un
paramètre **`application`** (défaut : l'application du profil) — les clés de
cache incluent l'application pour éviter toute pollution croisée (D26).
| Cache | Granularité | Pagination |
|---|---|---|
| `workflow-service` | **un par application** (~4 000 EasyWMS, 153 CustomApp) + liste allégée d'`Application/GetAll` | `WORKFLOW_PAGE_SIZE`, 5000 |
| `ad-service` | **un par (application, type)** (20 types) | `AD_ELEMENT_TYPES` : `View` 200, `Workflow` 5000, `Resource` 15000, autres 100000 |
**Ne préchargez jamais les 9 applications** : seule l'application demandée est
chargée (D26).
Les tailles de page par type viennent de l'observation des timeouts serveur —
ne les augmentez pas à l'aveugle.
`get_application_summary` expose l'état des caches sans redémarrage.
---
## Sorties bornées (D24)
Une réponse d'outil de plus de ~70 000 caractères est **rejetée par le client
MCP**. Les outils qui peuvent dépasser ce seuil bornent et **signalent** :
`truncated: true` (jamais `false`), `hint` actionnable, `returned`, et le total
avant la coupe. Réutilisez ce vocabulaire, n'en inventez pas un second.
**`get_workflow_details` fenêtre le blob `data`** (la définition EasyBuilder :
71 512 caractères sur un StackerCrane, 92 362 sur un gros `CST_*`) :
`max_data_chars` (défaut 20 000) et `data_offset` (défaut 0). Les métadonnées
restent complètes, `dataTotalChars` est porté par toute réponse, et la tranche
est **verbatim** — concaténer les tranches dans l'ordre des offsets reconstitue
la définition à l'octet près. Ne la résumez pas, ne la « parsez » pas.
---
## Écrire une requête WMS
```js
await apiService.executeQuery(
'Context.OutboundOrders.Where(z => z.OutboundOrderStatus == "Release").OrderBy(z => z.Id)',
{ take: 100 }
);
```
**Répartition entre l'expression et les options** — c'est la source d'erreur
la plus fréquente :
| Élément | Où |
|---|---|
| `Where` | dans l'`Expression` |
| `OrderBy` | dans l'`Expression`**obligatoire dès qu'on utilise `take`** |
| `Take` / `Skip` / `Select` | paramètres d'API, pas dans l'expression |
Autres règles :
- **`QueryType: 0` (Reading) par défaut** : les statuts sont alors des chaînes
(D3). La bascule vers Writing/Metrics passe par le paramètre `query_type`
des outils de requête — un opt-in documenté (D25), jamais un défaut : ne
recopiez aucun exemple en `QueryType: 1`.
- **Pas de date relative.** `DateTime.Now`, `DateTime.Today`, `AddDays()` ne
sont pas traduisibles : écrire `new DateTime(2026, 8, 1)` (D12).
- **`select_expression` est instable** : les projections via le paramètre
`Select` provoquent des erreurs de compilation. Interroger les lignes
complètes (D13).
- **Pour compter, utiliser `count_wms_entities`** (`QueryScalarExecute`), pas un
`query` suivi d'un `.length`.
- **`executeCommand`** prend le nom de commande **tel quel** : ajouter le suffixe
d'assembly provoque une `FileLoadException` (D14).
`_parseQueryResponse()` gère les deux formes de réponse (tableau plat, ou
`{ Table: { Columns, Rows } }`) — ne réimplémentez pas ce décodage ailleurs.
---
## Entités et éléments AD
**Entités interrogeables** (Query API) : `entity_type` accepte le nom d'entité
AD (`Container`) ou le `TableName` (`Containers`), insensible à la casse — la
résolution passe par `entity-resolver.js` (D21). Courantes : `Products`,
`Containers`, `Accounts`, `Suppliers`, `Kits`, `Alias` (invariant, pas de
pluriel), `Tasks`, `Stocks`, `ProductLocations`, `InboundOrders`, `Receptions`,
`OutboundOrders`. La liste faisant foi (288 entités, toutes applications
confondues) s'obtient par `get_entity_metadata` (API Metadata) — le catalogue
de la resource `wms://entities` est un raccourci de confort, pas la référence.
**Application Dictionary** : 20 types, ~38 800 éléments (sur `EasyWMS`).
`Resource` (29 374) est de loin le plus lourd ; 3 types sont valides mais vides
(`Dashboard`, `TimelineTemplate`, `Toggle`). `WorkflowAction` et `WritingModel`
ont été retirés — 404 (D17). Détail :
[docs/ad-api-validation.md](docs/ad-api-validation.md). 9 applications AD sont
déclarées ; **`CustomApp` porte le spécifique client** (workflows `CST_*`) et
s'interroge via le paramètre `application` des outils AD et workflow (D26).
**Paramètres système** : pas d'entité `CommandParameterData`. La configuration
se lit dans `Parameter` (+ `DefaultValue`) et `ParamValue` (surcharges par
entrepôt, jointure sur `ParameterId`), fusionnées côté JS par
`get_system_parameters` (D11).
---
## Commandes
```bash
npm start # lancer le serveur (stdio)
npm test # smoke test du profil actif — lecture seule
npm test -- LIMAGRAIN # smoke test d'un profil précis
npm test -- --all # tous les profils
npm run build # dist/wms-mcp-server.exe (node22-win-x64)
```
Le smoke test vérifie OAuth, `QueryExecute`, `QueryScalarExecute` et l'API AD,
et sort en code 1 au moindre échec.
**Validation des endpoints AD** (PowerShell, credentials en paramètres) :
```bash
powershell -ExecutionPolicy Bypass -File scripts/test-ad-api.ps1 -WmsHost 10.255.255.2 -Username user -Password '***' -Tenant AD
```
---
## Ajouter un outil
1. Déclarer le schéma dans `listTools()` du module `src/tools/` concerné.
2. Traiter le cas dans son `executeTool()`.
3. Rien à faire dans `src/index.js` pour un module existant : la table de
routage est construite depuis `listTools()` (D22). Un **nouveau module**
doit être ajouté à `TOOL_MODULES`.
4. Logger avec le préfixe du module.
5. Renvoyer les erreurs, ne pas les lever hors du wrapper.
6. Tester le handshake complet :
```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/list"}' | node src/index.js
```
---
## Points ouverts
Voir [ROADMAP.md](ROADMAP.md) : lots de correction planifiés et propositions
explicitement écartées.