1a1b9ebe03
Ajoute ROADMAP.md et le relie depuis README.md et CLAUDE.md.
Origine : un rapport d'usage d'une session Cowork sur le profil LIMAGRAIN a
signalé 8 anomalies. Vérification faite contre le WMS réel, 4 bugs sont
confirmés et reproduits, dont un non signalé par le rapport.
Cause racine commune : entity_type est interpolé dans Context.{entity_type}
sans aucune validation, alors que le nom attendu est le TableName de l'API
Metadata et non le nom d'entité de l'AD. Container -> Containers, mais
Alias -> Alias : c'est un mapping, pas une règle de pluralisation. L'API
Metadata connaît 232 entités là où le MCP en expose 12 en dur, et la liste
documentée était fausse (Aliases n'existe pas).
Lot 1 (déblocage) : corps des erreurs HTTP remonté, routage des outils par
table explicite, projections de champs des workflows.
Lot 2 (fond) : résolution des entités via l'API Metadata, rejet des
paramètres inconnus.
Lot 3 : bornage des sorties volumineuses, documentation.
Trois propositions du rapport sont explicitement écartées, avec leur raison :
uniformisation des noms de paramètres, indexStatus sur generic_search, outil
dédié d'aide à la syntaxe.
CLAUDE.md : la section « Points ouverts » renvoie désormais vers la roadmap et
avertit des deux pièges non encore corrigés.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
290 lines
12 KiB
Markdown
290 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) |
|
|
| 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 |
|
|
| 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)
|
|
│ ├── 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` route les appels d'outils **par préfixe de nom**
|
|
(`name.startsWith('query_wms_')`, `name.includes('_logs')`, …). En ajoutant un
|
|
outil, vérifiez que son nom tombe dans la bonne branche — sinon il apparaîtra
|
|
dans `tools/list` mais renverra `Unknown tool`.
|
|
|
|
---
|
|
|
|
## 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.
|
|
|
|
**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) : `Products`, `Containers`, `Accounts`,
|
|
`Suppliers`, `Kits`, `Aliases`, `Tasks`, `Stocks`, `ProductLocations`,
|
|
`InboundOrders`, `Receptions`, `OutboundOrders`. La liste faisant foi 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. **Vérifier le routage par préfixe** dans `src/index.js` — ou ajouter une
|
|
branche.
|
|
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, cause racine
|
|
commune (résolution `Name` -> `TableName` des entités), et propositions
|
|
explicitement écartées.
|
|
|
|
⚠️ Deux pièges connus et non encore corrigés, à garder en tête en attendant le
|
|
lot 1 :
|
|
|
|
- `entity_type` est interpolé sans validation dans `Context.{entity_type}`. Le
|
|
nom attendu est le `TableName` de l'API Metadata, pas le nom d'entité de l'AD
|
|
(`Container` -> `Containers`, mais `Alias` -> `Alias`). Un mauvais nom donne un
|
|
HTTP 500 dont le détail est aujourd'hui perdu.
|
|
- `get_entity_metadata` et `list_log_files` sont listés dans `tools/list` mais
|
|
non routés dans `src/index.js` : ils renvoient `Unknown tool`.
|