Files
mcp-wms-api/CLAUDE.md
T
Arthur Ria d0a6cc1a0b L3.1b : plafonne la taille de réponse de search_logs (MAX_LOG_SEARCH_CHARS)
max_results (50) borne le nombre de résultats mais pas le volume : les
context_lines multiplient la taille, et la réponse aux seuls défauts
atteignait 52-56 000 caractères — rejetée par le client MCP. Le tool
écarte désormais des résultats ENTIERS (jamais coupés au milieu de leur
contexte) jusqu'à passer sous le plafond, et le signale : truncated,
returned/omitted, hint actionnable (affiner keyword, réduire
context_lines, baisser max_results).

Plafond : MAX_LOG_SEARCH_CHARS, variable d'environnement avec défaut
25 000 — même style de lecture que MAX_QUERY_ROWS/QUERY_TIMEOUT
(parseInt(process.env.X) || défaut), documentée dans CLAUDE.md
(réglages partagés). 25 000 correspond à l'ordre de grandeur cible du
lot (~20-25 000 chars) et se mesure sur content[0].text, la taille qui
fait foi côté protocole. Les défauts max_results=50 et context_lines=2
sont inchangés : le correctif est le bornage signalé, pas un changement
de comportement par défaut.

Mesures via le protocole (LIMAGRAIN, content[0].text) :
- {"keyword":"Error"} : avant 52 162 chars / 50 résultats ; après
  24 164 chars, returned 16, omitted 34, truncated true, hint présent.
- {"keyword":"Error","max_results":3} : 4 855 chars, 3/3, pas de
  troncature signalée.
- mot-clé sans occurrence : 112 chars, 0 résultat, pas de truncated.

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

291 lines
12 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
Deux caches, TTL commun `WORKFLOW_CACHE_TTL` (3 600 000 ms), chargement
paresseux, vidés à chaque bascule de profil (D10).
| Cache | Granularité | Pagination |
|---|---|---|
| `workflow-service` | global (~3 700 workflows) | `WORKFLOW_PAGE_SIZE`, 5000 |
| `ad-service` | **un par type** (20 types) | `AD_ELEMENT_TYPES` : `View` 200, `Workflow` 5000, `Resource` 15000, autres 100000 |
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.
---
## É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)**, jamais 1 : les statuts sont alors des chaînes
(D3).
- **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. `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).
**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.