d0a6cc1a0b
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>
291 lines
12 KiB
Markdown
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` |
|
|
|
|
où `{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.
|