Documentation : structure README / CLAUDE / DECISIONS / MONITORING

Nouveaux documents :
- README.md : porte d'entrée humaine, absente jusqu'ici. Objet du projet,
  installation, npm test, branchement Claude Desktop, ajout d'un profil WMS,
  compilation de l'exécutable.
- DECISIONS.md : 20 décisions et pièges vérifiés sur un WMS réel (D1..D20),
  chacun avec son pourquoi. Extrait ce qui était noyé dans CLAUDE.md :
  100 % API, tenant_code OAuth, réponses {entities}, casse des propriétés,
  dotenv sur stderr, dates relatives LINQ non traduisibles, absence de
  CommandParameterData, etc.
- MONITORING.md : supervision du serveur MCP. Préfixes de logs, séquence
  d'un démarrage sain, cycle de vie du token OAuth et ses trois filets,
  état des caches, table symptôme -> cause. Une section dit explicitement
  ce qui n'est pas instrumenté (ni healthcheck, ni métriques, ni alerte).
- docs/logs.md : accès aux logs du WMS. Chemins, placeholder {host},
  blocage volontaire sur les profils SaaS, les trois outils, format des
  lignes, limites connues.

Mises à jour :
- CLAUDE.md réécrit et aligné sur le code. Correction de l'écart le plus
  gênant : le code utilise QueryType 0 (Reading), la doc annonçait 1, soit
  l'inverse de ce qui fonctionne pour les comparaisons de statut par
  chaîne. Corrigés également : 6 resources et non 7 (workflows://categories
  n'existe pas), section .env mono-profil obsolète, références à des
  fichiers de test absents, README annoncé mais inexistant. Le suivi de
  projet et les checklists de phases sont retirés.
- docs/README.md : index réel du dossier. L'ancien promettait une resource
  docs:// qui n'a jamais existé.
- docs/getting_started.md : avertissement en tête, c'est une capture
  partielle du portail Mecalux dont les liens internes ne résolvent pas.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Arthur Ria
2026-08-24 15:15:31 +02:00
parent 9b95e15cbc
commit 0ff44f7b78
7 changed files with 1152 additions and 868 deletions
+229 -836
View File
File diff suppressed because it is too large Load Diff
+344
View File
@@ -0,0 +1,344 @@
# Décisions d'architecture et pièges vérifiés
Ce fichier consigne **pourquoi** le code est écrit comme il l'est. Chaque entrée
décrit une décision prise ou un piège constaté **sur un WMS réel** — pas une
supposition. Avant de « corriger » un comportement qui paraît étrange, cherchez-le
ici : il est probablement volontaire.
Convention : une décision reste dans le fichier même si elle est révisée ; on
ajoute alors une entrée `Révisée le …` plutôt que de réécrire l'histoire.
---
## D1 — 100 % API, aucun accès Oracle direct
**Décision.** Toutes les données transitent par les API REST du WMS. Aucune
connexion base de données.
**Pourquoi.** Le serveur MCP doit fonctionner depuis un poste ou une VM sans
credentials Oracle, sans client Oracle installé, et sans risque d'écriture
directe en base. L'API impose en prime les règles métier et les droits du
compte utilisé.
**Conséquence.** Les fichiers `src/services/oracle-service.js`,
`src/resources/database.js` et `src/tools/database-tools.js` ont été supprimés
(ils étaient de toute façon morts : non branchés, `oracledb` n'était même pas
une dépendance). **Ne pas les réintroduire.** Si une donnée n'est pas
atteignable par API, elle est hors périmètre — voir D16.
---
## D2 — OAuth : `tenant_code` est obligatoire
**Piège.** L'endpoint `/EasySTS/OAuth/Token` répond `400 Bad Request` si le
paramètre `tenant_code` est absent, sans message explicite.
**Solution.** Le corps du grant `password` contient toujours les quatre
paramètres :
```
grant_type=password&tenant_code=<TENANT>&username=<USER>&password=<PASS>
```
Voir `src/services/api-service.js`, méthode `authenticate()`.
---
## D3 — `QueryType: 0` (Reading), pas 1
**Piège.** `QueryType` sélectionne le modèle de données interrogé :
| Valeur | Modèle | Champs de statut |
|---|---|---|
| `0` | **Reading** | chaînes de caractères (`"Release"`) |
| `1` | Writing | énumérations |
Les comparaisons de statut par chaîne — de loin le cas le plus courant en
debug — **échouent** en `QueryType: 1`. Le code force donc `0` dans
`executeQuery()` et `executeScalarQuery()`.
**Attention.** D'anciens exemples (dont le PHP de référence) utilisent `1`. Ne
les recopiez pas.
---
## D4 — Les API AD renvoient `{ entities: [...] }`, pas un tableau
**Piège.** `POST /AD/api/{Type}/GetByApplication` renvoie un objet enveloppe. Un
code qui traite la réponse comme un tableau obtient silencieusement
`0 élément` — le symptôme historique était « Successfully cached 0 workflows ».
**Solution.** Toujours extraire : `response?.entities || []`.
---
## D5 — Les propriétés arrivent en minuscules *ou* en majuscules
**Piège.** Selon le type d'élément et la version du WMS, l'API renvoie `name`
ou `Name`, `id` ou `Id`.
**Solution.** Systématiquement `const name = w.name || w.Name || ''` avant tout
filtrage ou tri. Une recherche qui « ne trouve pas » un élément qui existe est
presque toujours ce bug.
---
## D6 — dotenv doit écrire sur stderr
**Piège.** dotenv affiche une bannière de version sur **stdout**. Or le
protocole MCP réserve stdout au JSON : Claude Desktop échoue alors avec
`Unexpected token 'd', "[dotenv@17."... is not valid JSON`.
**Solution.** `src/index.js` détourne `process.stdout.write` vers stderr le
temps du chargement de dotenv, puis le restaure.
**Règle générale.** Dans tout le projet, on log avec `console.error()`.
**Jamais** `console.log()`.
---
## D7 — Le `.env` est lu à côté de l'exécutable quand le serveur est packagé
**Décision.** `src/index.js` résout le chemin du `.env` selon le contexte :
| Contexte | Chemin du `.env` |
|---|---|
| Sources (`npm start`) | racine du projet |
| Exécutable pkg (`process.pkg`) | dossier de `process.execPath` |
**Pourquoi.** Avec un chemin statique, pkg **embarque le `.env` dans le
snapshot** de l'exe : les credentials sont figés dans le binaire et
reconfigurer un déploiement impose un rebuild. Le chemin dynamique via
`process.execPath` empêche pkg de le détecter, donc rien n'est embarqué, et
`dist/.env` devient le fichier de configuration du déploiement.
**Vérification.** Sans `.env` à côté de l'exe, le serveur démarre en
avertissant `WMS_PROFILES is empty` — preuve qu'aucune valeur n'est embarquée.
---
## D8 — Multi-profils au runtime plutôt qu'un serveur MCP par WMS
**Décision.** Un seul serveur MCP dessert plusieurs backends WMS ; Claude bascule
avec `switch_wms_profile`.
**Pourquoi.** L'alternative — une entrée par client dans
`claude_desktop_config.json` — multiplie les processus, les jeux de credentials
et les caches, pour un usage où l'on ne consulte qu'un WMS à la fois.
**Conséquence.** L'état actif est **global au processus**. Un changement de
profil doit invalider tout ce qui dépend du tenant. Les services s'abonnent via
`profileManager.onSwitch()` :
| Service | Réaction au switch |
|---|---|
| `api-service` | `resetToken()` — le token OAuth appartient au tenant précédent |
| `workflow-service` | `clearCache()` |
| `ad-service` | `invalidateCache()` — tous les types |
**Ne jamais** appeler ces invalidations à la main depuis un autre module :
l'abonnement suffit, et le doublon masquerait un oubli d'abonnement.
**Sans profil actif** (`DEFAULT_WMS_PROFILE` absent ou invalide), `getCurrent()`
lève une erreur qui **énumère les profils disponibles**. C'est intentionnel :
Claude lit ce message et enchaîne sur `switch_wms_profile` au lieu d'échouer.
---
## D9 — Profils SaaS : accès aux logs refusé, pas silencieux
**Décision.** Quand `<PROFIL>_SAAS=true`, `read_recent_logs`, `search_logs` et
`list_log_files` **lèvent une erreur explicite** renvoyant vers les outils API.
**Pourquoi.** Le WMS est hébergé dans le cloud Mecalux : le partage
`\\<host>\inetpub\logs\...` n'est pas joignable. Retourner « 0 fichier » ferait
croire à une absence d'erreurs dans les logs, ce qui est un faux négatif
dangereux en diagnostic. Voir [docs/logs.md](docs/logs.md).
---
## D10 — Chargement paresseux + cache 1 h
**Décision.** Workflows et éléments AD ne sont **pas** chargés au démarrage,
mais à la première requête qui les concerne, puis mis en cache
(`WORKFLOW_CACHE_TTL`, 3 600 000 ms par défaut).
**Pourquoi.** L'ensemble représente ~38 800 éléments dont 29 374 `Resource` :
tout charger au boot ferait échouer le handshake MCP par timeout, pour des
données souvent inutiles à la session.
**Pagination.** La taille de page est réglée **par type** dans
`AD_ELEMENT_TYPES` (`src/services/ad-service.js`) : `View: 200`,
`Workflow: 5000`, `Resource: 15000`, tout le reste `100000` (soit une seule
page). Ces valeurs viennent de l'observation des timeouts serveur — les
augmenter à l'aveugle fait échouer les types lourds.
---
## D11 — Les paramètres système : `Parameter` + `ParamValue`, fusionnés en JS
**Piège.** Le modèle Reading ne contient **pas** d'entité
`CommandParameterData`. La configuration se lit dans deux entités :
| Entité | Contenu |
|---|---|
| `Parameter` | définition + `DefaultValue` |
| `ParamValue` | surcharges par entrepôt, liées par `ParameterId` |
**Décision.** `get_system_parameters` charge les deux intégralement (~200 et
~50 lignes) et fait la fusion **côté JavaScript**, en exposant la *valeur
effective* par entrepôt (surcharge si présente, défaut sinon).
**Pourquoi côté JS.** Les filtres (`warehouse`, `param_class`, `search`,
`only_overridden`) sont appliqués en JS pour éviter toute concaténation de
chaîne LINQ à partir d'entrées utilisateur — pas d'injection possible, et pas
de dépendance aux limites du traducteur LINQ (D12).
Fichier : `src/tools/config-tools.js`.
---
## D12 — Les dates relatives ne sont pas traduisibles en LINQ
**Piège vérifié en production.** `DateTime.Now`, `DateTime.Today` et
`AddDays()` ne sont **pas** traduits par le moteur de requêtes : la requête
échoue à la compilation.
**Solution.** Toujours une date littérale :
```csharp
Context.OutboundOrders.Where(z => z.CreationDate > new DateTime(2026, 8, 1))
```
C'est à l'appelant (donc à Claude) de calculer la date avant d'écrire la
requête.
---
## D13 — `select_expression` reste instable
**État.** Les projections passées via le paramètre API `Select` déclenchent des
erreurs de compilation côté serveur.
**Contournement actuel.** Interroger les lignes complètes et filtrer les
colonnes côté client.
**Non résolu.** C'est le principal point ouvert du projet. Toute tentative de
correction doit être validée sur un vrai WMS avant d'être documentée ici.
---
## D14 — `executeCommand` : pas de suffixe d'assembly
**Piège.** Ajouter `, Mecalux.ITSW.EasyWMS.Modules.Contracts` au nom de commande
provoque une `FileLoadException`.
**Solution.** Utiliser le `command_name` **tel quel** :
l'`InternalCommandName` fourni par l'AD contient déjà le nom pleinement
qualifié correct.
---
## D15 — Validation TLS désactivée
**Décision.** `httpsAgent: new https.Agent({ rejectUnauthorized: false })`.
**Pourquoi.** Les WMS on-premise sont exposés en HTTPS avec un certificat
auto-signé sur une IP privée.
**Limite assumée.** Acceptable sur réseau interne ou via VPN. Sur un profil
SaaS joint par Internet, cela supprime la protection contre l'interception —
à revoir si l'outil sort du cadre du diagnostic interne.
---
## D16 — Historique des shipment templates : hors périmètre
**Constat.** L'entité Reading `ShipmentTemplate` n'expose que la **dernière**
exécution (`LastExecuteDate`, `Status`, `IsEnabled`).
L'historique complet n'existe que dans les logs texte
`ApplyShipmentTemplates`, **absents de l'hôte joignable** (`10.255.255.2`) :
ils résident sur les serveurs de production / ETL des clients.
**Décision.** Aucun outil d'analyse de ces logs n'a été construit — il n'aurait
rien à lire. Les recettes purement API sont dans la resource
`wms://query-examples`.
---
## D17 — `WorkflowAction` et `WritingModel` retirés de la liste AD
**Constat.** Les endpoints `/AD/api/WorkflowAction/GetByApplication` et
`/AD/api/WritingModel/GetByApplication` répondent `404 Not Found`.
**Décision.** Ces deux types sont sortis de `AD_ELEMENT_TYPES` : il en reste
**20**, dont 3 valides mais vides (`Dashboard`, `TimelineTemplate`, `Toggle`).
Détail de la campagne de validation :
[docs/ad-api-validation.md](docs/ad-api-validation.md).
---
## D18 — Build : `@yao-pkg/pkg` ciblant node22
**Décision.** Le build utilise `@yao-pkg/pkg` (fork maintenu de `pkg`, archivé
depuis) avec la cible **`node22-win-x64`**.
**Pourquoi cette cible.** `node20-win-x64` n'a pas de binaire prébuilt
disponible : pkg bascule alors sur une compilation de Node depuis les sources,
qui échoue faute de `vcbuild.bat` (toolchain MSVC absente).
**Avertissements normaux au build.** `Cannot find module
'@modelcontextprotocol/sdk/server/index.js'` et `Entry 'main' not found` :
pkg ne sait pas résoudre statiquement la table `exports` du SDK. L'exécutable
produit **fonctionne** — vérifié en démarrant l'exe. Ne pas chercher à
« corriger » ces avertissements.
---
## D19 — La resource `docs://` a été supprimée
**Constat.** `src/resources/documentation.js` (193 lignes) exposait un index des
`.md` de `docs/`, mais n'a **jamais été branché** dans `src/index.js` : le
handler `resources/list` ne l'incluait pas et `resources/read` ne routait aucune
URI `docs://`. `docs/README.md` promettait pourtant la fonctionnalité aux
utilisateurs.
**Décision.** Fichier supprimé, `docs/README.md` corrigé. `docs/` reste un
dossier de référence pour les humains et pour un agent qui lit le dépôt — pas
une resource MCP.
**Si on veut la fonctionnalité un jour**, il faut la brancher réellement (2
lignes dans `src/index.js`) *et* décider de son sort dans l'exécutable pkg, qui
n'embarque pas `docs/`.
---
## D20 — Purge du dépôt (2026-08-24)
Supprimés lors du nettoyage :
| Élément | Raison |
|---|---|
| 5 `.md` dupliqués à la racine | copies md5-identiques de `docs/api/` et `docs/entities/` |
| `JANITOR_main.js`, `JANITOR_entities.json` | application Electron sans lien avec le MCP |
| `temp/*.json` | dumps de workflows versionnés par accident (`temp/` désormais ignoré) |
| `claude_desktop_config_ssh.json` | **mots de passe en clair** + variables `ORACLE_*` de l'architecture supprimée (D1) |
| `IMPLEMENTATION_SUMMARY.md` | doublon d'`AD_API_TEST_RESULTS.md`, déplacé en `docs/ad-api-validation.md` |
| `src/resources/documentation.js` | code mort (D19) |
| `src/config/constants.js` (83 l.) | module entier inutilisé : `require` présent dans `wms-query-service.js`, mais **aucune** de ses constantes n'était lue |
| `log-service.js` : `findRecentErrors`, `readFullLog`, `getLogStats` (~100 l.) | exportées, jamais appelées — aucun outil MCP ne les exposait |
| `RESOURCE_URIS.WORKFLOWS_CATEGORIES` | URI déclarée, jamais servie |
| `LOG_FILE_PATTERN` | lue depuis `.env`, jamais utilisée (le scan filtre sur `.log` en dur) |
Les trois fonctions de `log-service.js` étaient fonctionnelles ; si l'une d'elles
redevient utile (`findRecentErrors` en particulier), la reprendre depuis le
commit `b59cbb3` et **l'exposer réellement** comme outil MCP plutôt que de la
laisser inatteignable.
⚠️ **Credentials à faire tourner.** `claude_desktop_config_ssh.json` et
l'ancienne version de `test-ad-api.ps1` contenaient des mots de passe en clair.
Le fichier est retiré du répertoire de travail, **mais il reste dans
l'historique git** (commit `b59cbb3`). Considérez ces mots de passe comme
compromis et changez-les ; à défaut, réécrivez l'historique avant toute
publication du dépôt.
+220
View File
@@ -0,0 +1,220 @@
# Supervision du serveur MCP
Comment savoir si le serveur tourne, ce qu'il fait, et pourquoi il ne répond
pas. Ce document décrit **l'existant** — il n'y a ni endpoint de santé, ni
métriques exportées : toute l'observabilité passe par **stderr** et par le
smoke test `npm test`.
Pour lire les logs **du WMS** (et non ceux du serveur MCP), voir
[docs/logs.md](docs/logs.md).
---
## 1. Où regarder
| Quoi | Où |
|---|---|
| Logs du serveur MCP | `%APPDATA%\Claude\logs\` (fichier `mcp-server-wms.log`) |
| Logs applicatifs du WMS | partages `\\<host>\...` — voir [docs/logs.md](docs/logs.md) |
| État de la connexion WMS | `npm test` (voir §5) |
| Profil actif / caches | outils `get_current_wms_profile`, `get_application_summary` |
**Tout passe par stderr.** stdout est réservé au JSON du protocole MCP : la
moindre écriture sur stdout casse la session Claude Desktop (voir D6 dans
[DECISIONS.md](DECISIONS.md)). En pratique, cela veut dire que **les logs sont
la seule sortie observable**, et qu'ils sont complets.
Suivre les logs en direct :
```bash
Get-Content -Wait -Tail 50 "$env:APPDATA\Claude\logs\mcp-server-wms.log"
```
---
## 2. Lire les préfixes
Chaque ligne est préfixée par son composant. Le préfixe suffit à localiser le
problème.
| Préfixe | Composant | Ce qu'il signale |
|---|---|---|
| `[Server]` | `src/index.js` | démarrage, routage des outils, erreurs non rattrapées |
| `[Profile]` | `config/profile-manager.js` | chargement des profils, bascule de profil |
| `[API]` | `services/api-service.js` | OAuth, chaque requête HTTP, retries 401 |
| `[Workflow]` | `services/workflow-service.js` | cache workflows, pagination |
| `[AD]` | `services/ad-service.js` | cache par type d'élément, pagination |
| `[Logs]` | `services/log-service.js` | chemins de logs illisibles ou absents |
| `[WMSQuery]`, `[Metadata]` | services | construction des requêtes |
| `[*Tools]` | `src/tools/` | exécution d'un outil précis |
---
## 3. Démarrage : à quoi ressemble un boot sain
```
[dotenv@17.2.4] injecting env (30) from .env
[Profile] Loaded 3 profile(s). Active: LIMAGRAIN
[Server] Starting WMS MCP Server...
[Server] Architecture: 100% API-based (no direct database access)
[Server] Profiles available: AD, EUROTRAFIC, LIMAGRAIN
[Server] Active profile: LIMAGRAIN
[Server] WMS MCP Server running on stdio
[Server] Ready to accept requests from Claude Desktop
```
Trois points à contrôler dans cet ordre :
1. **`injecting env (N)`** — si `N` vaut 0, le `.env` n'a pas été trouvé. En
mode packagé il est attendu **à côté de l'exe** (D7).
2. **`Loaded N profile(s)`** — si 0, `WMS_PROFILES` est vide ou les variables
`<NOM>_HOST/USERNAME/PASSWORD/TENANT` manquent.
3. **`Active profile: …`** — si le message est `No active profile`, ce n'est
**pas** une panne : Claude doit appeler `switch_wms_profile` avant la
première requête, et l'erreur renvoyée le lui indique explicitement (D8).
Aucune connexion au WMS n'est tentée au démarrage : un boot propre ne prouve
donc **pas** que le WMS est joignable. Pour cela, voir §5.
---
## 4. Cycle de vie du token OAuth
Le token est obtenu **paresseusement**, à la première requête, puis rafraîchi
automatiquement. Réglages dans `.env` :
| Variable | Défaut | Rôle |
|---|---|---|
| `TOKEN_REFRESH_THRESHOLD` | `1000` s | âge au-delà duquel un refresh est déclenché avant la requête |
| `TOKEN_MAX_AGE` | `1190` s | âge au-delà duquel on ne tente plus le `refresh_token` mais une ré-authentification complète |
| `QUERY_TIMEOUT` | `30000` ms | timeout HTTP de toute requête WMS |
Séquence observable :
```
[API] Authenticating profile="LIMAGRAIN" tenant="LIMAGRAI2512" ...
[API] Authentication successful. Token expires in ~1190s
[API] POST /QueryExecute
... (~17 min plus tard)
[API] Refreshing token with refresh_token grant...
[API] Token refreshed successfully
```
Trois filets de sécurité, dans cet ordre :
1. **Avant la requête** — si `âge > TOKEN_REFRESH_THRESHOLD`, refresh préventif.
2. **Refresh en échec** — bascule automatique sur le grant `password`
(`[API] Token refresh failed, re-authenticating`).
3. **Réponse 401** — un refresh est déclenché et la requête est **rejouée une
fois** (`[API] Unauthorized, refreshing token and retrying...`).
**Ce qui est normal.** Une ligne `Token refresh failed` isolée suivie d'une
authentification réussie : le filet a joué son rôle.
**Ce qui ne l'est pas.** Ces trois lignes en boucle rapprochée signalent des
credentials invalides ou un tenant erroné — le serveur n'abandonne jamais de
lui-même, il retentera à chaque requête.
---
## 5. Test de bout en bout
```bash
npm test
```
Teste le profil actif ; `npm test -- AD` cible un profil, `npm test -- --all`
les teste tous. Quatre vérifications en lecture seule, aucune écriture WMS :
| Test | Ce qu'il prouve |
|---|---|
| OAuth | host joignable, credentials et tenant corrects |
| `QueryExecute` | API ApplicationService opérationnelle |
| `QueryScalarExecute` | requêtes scalaires (`Count`) opérationnelles |
| AD API (`Validator`) | API Application Dictionary opérationnelle |
Sortie attendue :
```
=== Profil LIMAGRAIN ===
host=10.255.255.2 tenant=LIMAGRAI2512 saas=false
OK OAuth - token obtenu (age max ~1190s)
OK QueryExecute - 1 ligne(s)
OK QueryScalarExecute - 51160 produit(s)
OK AD API (Validator) - 10 element(s)
-> 4/4 tests reussis
```
Code de sortie `0` si tout passe, `1` sinon — utilisable tel quel dans une
tâche planifiée.
C'est le premier réflexe quand Claude signale une erreur WMS : il isole en
quelques secondes une panne de connectivité d'un problème de requête.
---
## 6. État des caches
Deux caches, TTL commun `WORKFLOW_CACHE_TTL` (1 h par défaut), tous deux vidés
à chaque `switch_wms_profile` (D8).
| Cache | Contenu | Purge |
|---|---|---|
| workflows | ~3 700 workflows | TTL, ou bascule de profil |
| AD | un cache **par type** (20 types, ~38 800 éléments) | TTL par type, ou bascule de profil |
**Les inspecter sans redémarrer** : l'outil `get_application_summary` liste les
types chargés et leur nombre d'éléments — un type absent signifie simplement
qu'il n'a jamais été demandé dans cette session (D10).
Signature d'un chargement dans les logs :
```
[AD] Cache expired or empty, fetching Resource...
[AD] Fetching Resource: offset=0, pageSize=15000
[AD] Fetched 15000 Resource (total: 15000)
[AD] Fetching Resource: offset=15000, pageSize=15000
...
[AD] Successfully cached 29374 Resource
[AD] Cache hit: Resource (29374 elements) <- appels suivants
```
Une première requête sur `Resource` prend plusieurs dizaines de secondes : ce
n'est pas un blocage, c'est la pagination. Les suivantes sont instantanées.
---
## 7. Symptômes → causes
| Symptôme | Cause probable | Vérification |
|---|---|---|
| Le serveur n'apparaît pas dans Claude Desktop | chemin invalide dans `claude_desktop_config.json`, ou Claude pas redémarré | ouvrir `%APPDATA%\Claude\logs\` |
| `Unexpected token … is not valid JSON` | quelque chose a écrit sur **stdout** | chercher un `console.log()` ajouté (D6) |
| `injecting env (0)` | `.env` introuvable | en packagé : le placer à côté de l'exe (D7) |
| `No WMS profile selected` | `DEFAULT_WMS_PROFILE` absent ou invalide | c'est un état normal — appeler `switch_wms_profile` |
| `Authentication failed: … 400` | `tenant_code` ou credentials erronés | `npm test -- <PROFIL>` (D2) |
| Boucle `refresh failed` / `Authenticating` | credentials invalides | `npm test` |
| `ETIMEDOUT` / `ECONNREFUSED` | host injoignable (VPN, pare-feu) | `Test-NetConnection <host> -Port 443` |
| `timeout of 30000ms exceeded` | requête trop lourde | ajouter un `Where`, réduire `take`, ou augmenter `QUERY_TIMEOUT` |
| `Successfully cached 0 workflows` | réponse non enveloppée par `entities` | D4 |
| Recherche vide sur un élément existant | casse des propriétés (`name` vs `Name`) | D5 |
| `Log access is disabled for SaaS profile` | profil `SAAS=true` | comportement voulu (D9), utiliser les outils API |
| Erreur de compilation LINQ sur une date | `DateTime.Now` employé | date littérale (D12) |
---
## 8. Ce qui n'est pas instrumenté
À connaître avant de promettre une supervision qui n'existe pas :
- **Pas de healthcheck** exposé, ni HTTP ni MCP. `npm test` est le seul contrôle
automatisable, et il faut le lancer soi-même.
- **Pas de métriques** : ni compteur d'appels, ni latence, ni taux d'erreur.
- **Pas de fichier de log propre au serveur** : tout est capté par Claude
Desktop, avec sa rotation à lui.
- **Pas d'alerte** : une panne d'authentification n'est visible qu'au prochain
appel d'un outil.
- **`uncaughtException` et `unhandledRejection` sont journalisés mais
n'arrêtent pas le processus** (`src/index.js`). Le serveur peut donc survivre
dans un état dégradé — d'où l'intérêt de relire les logs jusqu'au début en cas
de comportement erratique, et non seulement la dernière erreur.
+152
View File
@@ -0,0 +1,152 @@
# WMS MCP Server
Serveur [MCP](https://modelcontextprotocol.io) qui donne à Claude un accès en
lecture à un WMS **EasyWMS** (Mecalux), pour le diagnostic et l'analyse.
Concrètement, dans Claude Desktop :
> « Combien de commandes sont bloquées en statut Release sur LIMAGRAIN ? »
> « Trouve les workflows qui touchent au réapprovisionnement. »
> « Cherche `Order 4711` dans les logs. »
> « Quels paramètres sont surchargés sur l'entrepôt 2 ? »
**Architecture : 100 % API REST.** Aucun accès direct à Oracle — voir D1 dans
[DECISIONS.md](DECISIONS.md).
---
## Ce que le serveur expose
**23 outils** répartis en 8 familles :
| Famille | Outils |
|---|---|
| Requêtes WMS | `query_wms_entities`, `count_wms_entities`, `get_entity_schema`, `search_wms_data` |
| API brutes | `call_query_api`, `execute_command` |
| Workflows | `search_workflows`, `get_workflow_details`, `list_workflow_categories` |
| Application Dictionary | `get_application_summary`, `get_ad_elements`, `search_ad_elements`, `get_ad_element_details`, `list_ad_types` |
| Métadonnées | `get_entity_metadata`, `generic_search` |
| Configuration | `get_system_parameters` |
| Profils | `list_wms_profiles`, `get_current_wms_profile`, `switch_wms_profile` |
| Logs | `read_recent_logs`, `list_log_files`, `search_logs` |
**6 resources** de contexte : `wms://entities`, `wms://entity-schemas`,
`wms://query-examples`, `workflows://overview`, `api://catalog`, `logs://guide`.
**Multi-WMS.** Plusieurs backends (clients, tenants) coexistent dans un seul
serveur ; Claude bascule à la demande avec `switch_wms_profile`.
---
## Installation
Prérequis : Node.js 18+ et un accès réseau au WMS (VPN si nécessaire).
```bash
npm install
```
Copiez `.env.example` en `.env` et renseignez au moins un profil :
```env
WMS_API_AUTH=Basic R05BOklFNGU3aXFoZHQ=
WMS_APPLICATION=EasyWMS
WMS_API_PATH=/ApplicationService/api
WMS_TOKEN_PATH=/EasySTS/OAuth/Token
WORKFLOW_API_PATH=/AD/api
WMS_PROFILES=AD
DEFAULT_WMS_PROFILE=AD
AD_HOST=10.255.255.2
AD_USERNAME=...
AD_PASSWORD=...
AD_TENANT=AD
AD_SAAS=false
```
Vérifiez la connectivité — le test est en lecture seule :
```bash
npm test
```
Sortie attendue : `4/4 tests reussis`. En cas d'échec, voir
[MONITORING.md](MONITORING.md) §7.
---
## Brancher Claude Desktop
Éditez `%APPDATA%\Claude\claude_desktop_config.json` :
```json
{
"mcpServers": {
"wms": {
"command": "node",
"args": ["D:\\chemin\\vers\\mcp-wms-api\\src\\index.js"]
}
}
}
```
Puis **fermez et rouvrez complètement** Claude Desktop. Les logs du serveur
apparaissent dans `%APPDATA%\Claude\logs\`.
---
## Ajouter un WMS
1. Ajoutez son nom à `WMS_PROFILES` (séparateur : virgule).
2. Définissez `<NOM>_HOST`, `<NOM>_USERNAME`, `<NOM>_PASSWORD`, `<NOM>_TENANT`.
3. Mettez `<NOM>_SAAS=true` si le WMS est hébergé dans le cloud Mecalux — les
outils de log seront alors désactivés pour ce profil, à dessein.
Les URL se construisent à partir du host : rien d'autre à dupliquer. Testez
avec `npm test -- <NOM>`.
---
## Compiler un exécutable Windows
```bash
npm run build
```
Produit `dist/wms-mcp-server.exe` (~76 Mo, autonome, cible `node22-win-x64`).
**Placez le `.env` à côté de l'exe** : en mode packagé, c'est là qu'il est lu,
et aucun credential n'est embarqué dans le binaire (D7). Les avertissements
`Cannot find module '@modelcontextprotocol/sdk/…'` pendant le build sont
normaux et sans effet (D18).
Déploiement type sur la VM :
```
C:\WMS\mcp\wms-mcp-server.exe
C:\WMS\mcp\.env
```
---
## Documentation
| Fichier | Contenu |
|---|---|
| [CLAUDE.md](CLAUDE.md) | Architecture, inventaire des outils, conventions de code |
| [DECISIONS.md](DECISIONS.md) | **Pourquoi** le code est ainsi + pièges vérifiés en production |
| [MONITORING.md](MONITORING.md) | Superviser le serveur MCP : logs, token, caches, symptômes |
| [docs/logs.md](docs/logs.md) | Accès aux logs du WMS |
| [docs/](docs/) | Références EasyWMS (API, entités) |
---
## Sécurité
- `.env` et `dist/` sont ignorés par git — **ne les committez jamais**.
- La validation TLS est désactivée pour accepter les certificats auto-signés
des WMS on-premise (D15).
- ⚠️ L'historique git contient un ancien fichier de configuration avec des mots
de passe en clair (commit `b59cbb3`). Ces credentials sont à considérer comme
compromis — voir D20.
+36 -32
View File
@@ -1,41 +1,45 @@
# Documentation WMS
# Documentation de référence
Bienvenue dans la documentation du WMS !
Documents de référence sur EasyWMS et ses API, conservés dans le dépôt pour
être consultables hors ligne et par un agent qui lit le code.
## Comment utiliser cette documentation
> ⚠️ **Ce dossier n'est pas exposé comme resource MCP.** Une resource `docs://`
> a existé sans jamais être branchée ; elle a été supprimée. Ces fichiers se
> lisent directement depuis le dépôt. Voir D19 dans
> [DECISIONS.md](../DECISIONS.md).
Cette documentation est automatiquement accessible via le serveur MCP. Claude peut lire tous les fichiers `.md` présents dans ce dossier et ses sous-dossiers.
## Contenu
## Organisation
| Fichier | Nature |
|---|---|
| [logs.md](logs.md) | **Rédigé pour ce projet** — accès aux logs WMS, outils, limites |
| [ad-api-validation.md](ad-api-validation.md) | **Rédigé pour ce projet** — campagne de validation curl des 19 types AD testés (17 valides) |
| [api/Application Service API Reference.md](api/Application%20Service%20API%20Reference.md) | Référence de l'API ApplicationService |
| [api/POST apiQueryExecute.md](api/POST%20apiQueryExecute.md) | Détail de l'endpoint `QueryExecute` |
| [entities/easywms_reading_entites.md](entities/easywms_reading_entites.md) | Catalogue des entités du modèle **Reading** |
| [entities/easywms_reading_entites_outboundorder.md](entities/easywms_reading_entites_outboundorder.md) | Détail de l'entité `OutboundOrder` |
| [entities/easywms_reading_entites_outboundorder_OutboundOrderStatus.md](entities/easywms_reading_entites_outboundorder_OutboundOrderStatus.md) | Valeurs de `OutboundOrderStatus` |
| `getting_started.md`, `docs_downloads/` | Extractions du portail documentaire Mecalux |
| `reference-queries-api.php` | Client PHP d'origine, source des patterns d'API. ⚠️ utilise `QueryType: 1` — ne pas recopier, voir D3 |
Organisez vos fichiers de documentation comme vous le souhaitez :
**Sur les extractions du portail** : ce sont des captures partielles. Leurs
liens internes pointent vers des pages non téléchargées (`ReleaseNotes.md`,
`video_tutorials/`, …) et ne fonctionnent pas. Le seul document réellement
exploitable de cet ensemble est
`docs_downloads/communications/EasyWMS_WebApi_en.html.md` (référence complète de
la Web API, ~320 Ko).
```
docs/
├── README.md (ce fichier)
├── getting-started.md (guide de démarrage)
├── api/
│ ├── overview.md
│ └── endpoints.md
├── workflows/
│ ├── reception.md
│ └── expedition.md
└── troubleshooting/
└── common-errors.md
```
## Où trouver le reste
## Comment ajouter de la documentation
| Question | Document |
|---|---|
| À quoi sert ce projet, comment l'installer | [../README.md](../README.md) |
| Architecture, outils, resources, conventions de code | [../CLAUDE.md](../CLAUDE.md) |
| Pourquoi le code est écrit ainsi, pièges vérifiés | [../DECISIONS.md](../DECISIONS.md) |
| Le serveur ne répond pas / comment le superviser | [../MONITORING.md](../MONITORING.md) |
1. Créez vos fichiers `.md` dans ce dossier ou dans des sous-dossiers
2. Redémarrez Claude Desktop
3. Claude pourra automatiquement lire tous vos fichiers de documentation
## Ajouter un document
## Accéder à la documentation depuis Claude
Dans Claude Desktop, vous pouvez demander :
- "Montre-moi le sommaire de la documentation"
- "Lis la documentation sur les workflows"
- "Affiche-moi la documentation de l'API"
Claude aura accès à tous les fichiers `.md` présents ici !
Déposez le `.md` dans le sous-dossier qui convient et **ajoutez sa ligne au
tableau ci-dessus**. Un document non listé ici est un document que personne ne
retrouvera.
+6
View File
@@ -1,3 +1,9 @@
> ⚠️ **Capture partielle du portail documentaire Mecalux.** Ce sommaire est
> conservé pour mémoire : la quasi-totalité de ses liens pointe vers des pages
> qui n'ont pas été téléchargées et ne résolvent pas. Seul
> [docs_downloads/communications/EasyWMS_WebApi_en.html.md](docs_downloads/communications/EasyWMS_WebApi_en.html.md)
> est exploitable hors ligne. Voir [README.md](README.md).
# MAP - Documentation Portal
## Table des matières
+165
View File
@@ -0,0 +1,165 @@
# Logs du WMS
Comment le serveur MCP accède aux fichiers de logs du WMS, ce qu'il sait faire
et ce qu'il ne peut pas faire.
Pour superviser **le serveur MCP lui-même**, voir
[MONITORING.md](../MONITORING.md).
---
## 1. Où sont les logs
Les logs sont lus **directement sur le système de fichiers** du serveur WMS,
via des partages réseau Windows — jamais par API.
| Emplacement | Contenu |
|---|---|
| `\\<host>\inetpub\logs\LogFiles\Mecalux` | logs applicatifs IIS, organisés en sous-dossiers par composant |
| `\\<host>\ProgramData\Mecalux\ETLLogs` | logs du middleware ETL |
Configuration dans `.env`, plusieurs chemins séparés par des `;` :
```env
LOGS_PATH=\\{host}\inetpub\logs\LogFiles\Mecalux;\\{host}\ProgramData\Mecalux\ETLLogs
```
**Le placeholder `{host}` est remplacé à chaque appel** par le host du profil
actif. Il n'y a donc pas de chemin de logs à redéfinir par profil : un seul
gabarit suffit, et il suit automatiquement `switch_wms_profile`.
Le scan est **récursif** et ne retient que les fichiers dont le nom se termine
par `.log`. Un dossier absent ou inaccessible n'interrompt pas le scan : il est
journalisé (`[Logs] Cannot scan directory …`) et ignoré.
---
## 2. Prérequis d'accès
Les partages `\\<host>\...` doivent être joignables **depuis la machine qui
exécute le serveur MCP**, avec les droits de lecture du compte Windows courant.
Le serveur ne présente aucun credential propre pour SMB — il hérite de la
session Windows.
Vérification rapide, hors serveur MCP :
```bash
Test-Path "\\10.255.255.2\inetpub\logs\LogFiles\Mecalux"
```
Si cette commande renvoie `False`, aucun outil de log ne fonctionnera : le
problème est réseau ou droits, pas applicatif.
---
## 3. Profils SaaS : les logs ne sont pas accessibles
Quand le profil actif est déclaré `<PROFIL>_SAAS=true`, les trois outils de log
**échouent volontairement** avec :
> Log access is disabled for SaaS profile "X". The WMS is cloud-hosted — local
> log files are not reachable. Use the WMS API (query/command/workflow tools)
> instead.
C'est un choix explicite, pas une régression : le WMS est hébergé dans le cloud
Mecalux, son système de fichiers n'est pas exposé. Retourner « 0 fichier »
laisserait croire à une absence d'erreurs — un faux négatif dangereux en
diagnostic. Voir D9 dans [DECISIONS.md](../DECISIONS.md).
**Sur un profil SaaS, le diagnostic passe donc uniquement par les API** :
`query_wms_entities`, `count_wms_entities`, `search_wms_data`, les outils AD et
`get_system_parameters`.
---
## 4. Les trois outils
### `list_log_files`
Liste tous les `.log` trouvés sous `LOGS_PATH`, **triés du plus récent au plus
ancien**, avec taille, date de modification et dossier d'origine. Point de
départ naturel : il montre quels composants ont écrit récemment.
Si aucun fichier n'est trouvé, l'erreur **énumère les chemins scannés** — c'est
généralement suffisant pour diagnostiquer un `{host}` mal résolu.
### `read_recent_logs(count, log_file)`
Renvoie les `count` dernières lignes (100 par défaut). Sans `log_file`, prend
**le fichier le plus récemment modifié, tous dossiers confondus** — ce qui n'est
pas forcément celui que l'on croit sur un serveur multi-composants ; préférez
nommer le fichier.
La résolution de `log_file` se fait en trois passes, de la plus stricte à la
plus permissive :
1. chemin direct sous le premier `LOGS_PATH`
(ex. `ApplicationDictionary\ApplicationDictionary.log`) ;
2. nom exact recherché dans chaque sous-dossier immédiat ;
3. correspondance partielle, insensible à la casse.
En cas d'échec, l'erreur liste les fichiers disponibles.
### `search_logs(keyword, max_results, context_lines)`
Recherche insensible à la casse dans **tous** les fichiers, du plus récent au
plus ancien, en s'arrêtant à `max_results` (50 par défaut). Chaque résultat
porte son fichier, son numéro de ligne et `context_lines` lignes avant/après (2
par défaut), la ligne trouvée étant marquée `isMatch`.
C'est l'outil à privilégier pour tracer un identifiant métier (numéro de
commande, code produit, id de tâche) à travers les composants.
---
## 5. Format des lignes
`ApplicationService.log` suit ce format :
```
YYYY-MM-DD HH:MM:SS.ffff [thread] [Level] [Component] [message]
```
Exemple :
```
2026-08-24 09:14:22.1873 [42] [ERROR] [OutboundOrderService] [Order 4711 not found]
```
Conséquence pratique : une recherche par **date préfixe** (`2026-08-24 09:`)
fonctionne bien, et le niveau se filtre par `[ERROR]` — crochets inclus, pour
éviter les faux positifs sur le mot « error » dans un message.
Les logs ETL n'ont pas le même format ; ne présumez pas d'une structure commune
entre les deux emplacements.
---
## 6. Limites à connaître
- **Lecture intégrale en mémoire.** `read_recent_logs` et `search_logs`
chargent chaque fichier entier avant de le découper. Sur un log de plusieurs
centaines de Mo, c'est lent et coûteux en RAM. Vérifiez les tailles avec
`list_log_files` avant de lancer une recherche large.
- **Recherche par sous-chaîne uniquement**, pas d'expression régulière.
- **Pas de filtre temporel** : `search_logs` ne sait pas restreindre à une
plage horaire. Le contournement est d'inclure le préfixe de date dans le
mot-clé.
- **Aucun filtre de nom de fichier configurable.** Le scan retient tous les
`.log`. (Une variable `LOG_FILE_PATTERN` a existé dans `.env` sans jamais
être appliquée ; elle a été supprimée — voir D20.)
- **Pas de rotation ni de purge** : le serveur MCP lit, il n'écrit ni ne
supprime rien.
---
## 7. Hors périmètre : l'historique des shipment templates
Les logs `ApplyShipmentTemplates` **ne sont pas présents** sur l'hôte joignable
(`10.255.255.2`) : ils résident sur les serveurs de production / ETL des
clients. Aucun outil ne les analyse, car il n'y aurait rien à lire.
Ce que l'on peut obtenir par API se limite à la **dernière** exécution, via
l'entité Reading `ShipmentTemplate` (`LastExecuteDate`, `Status`,
`IsEnabled`). Voir D16 dans [DECISIONS.md](../DECISIONS.md) et les recettes de
la resource `wms://query-examples`.