Compare commits

...

5 Commits

Author SHA1 Message Date
Arthur Ria 86923542fa Roadmap et décisions : exploitation de la référence API du service
Source : la page d'aide générée https://<host>/ApplicationService/Help, qui
documente des champs et des endpoints que le MCP n'utilise pas. Toutes les
affirmations ci-dessous ont été testées contre LIMAGRAI2512.

D3 corrigé — QueryContextType a quatre valeurs, pas deux :
Reading 0 (ApplicationReadingContext), Writing 1 (ApplicationWritingRepository),
DataWarehouse 2 (non configuré sur ce tenant : IDataWarehouse non résolu),
Metrics 3 (ApplicationMetricDataContext, présent, modèle non exploré). Le
message d'erreur nomme le contexte, ce qui donne un moyen rapide de savoir quel
QueryType a servi.

L4.1 étendu aux quatre contextes.

L4.2 tranché sur son point dur : le champ Application ne partitionne pas le
contexte de lecture. Context.AgvTasks répond aussi bien avec Application AGV
qu'avec EasyWMS — le contexte est commun au tenant. La table de résolution du
lot 2 devra donc agréger le Metadata de toutes les applications, mais un
paramètre application sur QueryExecute serait inutile. Les entités CustomApp
restent inatteignables sous les quatre QueryType, au singulier comme au
pluriel, et Metadata renvoie 0 entité pour cette application : ce sont des
définitions EasyBuilder sans projection requêtable. L'API AD est le seul accès
au spécifique client.

Trois chantiers ajoutés :
- L4.3 ClientModule, non renseigné, d'où des requêtes du MCP journalisées sous
  « Client: GNA » et indistinguables du vrai client GNA.
- L4.4 API WorkflowLog (GetInstances, GetLogs, Validate), joignable et
  fonctionnelle, susceptible de remettre en cause D16.
- L4.5 champs inexploités de QueryExecute : Parameters (requêtes paramétrées,
  piste pour D13), CommandTimeout, QueryId + QueryCancel, QueryExecuteStream
  (piste pour L3.1), plus les endpoints event sourcing et les sondes
  healthcheck/ready.

CLAUDE.md pointe désormais vers la page d'aide comme source de vérité.
MONITORING.md documente healthcheck/ready comme sonde légère.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 15:58:28 +02:00
Arthur Ria 7621b87c49 Roadmap : ajoute le lot 4 (modèle de données et applications)
Deux angles morts mesurés sur le tenant LIMAGRAI2512.

L4.1 — QueryType est figé à 0 (Reading) en dur dans api-service.js : le modèle
Writing est inatteignable. Ce n'est pas une limite de l'API, QueryType 1
répond correctement sur Context.Products — il manque le paramètre. D3 reste
vrai en revanche : en Writing les statuts sont des énumérations, donc le
défaut doit rester 0.

L4.2 — Application vient de WMS_APPLICATION, partagé par tous les profils,
sans surcharge possible. Le MCP n'interroge que EasyWMS alors que
/AD/api/Application/GetAll en déclare 9. CustomApp porte le spécifique client
(153 workflows, 54 queries, 11 entités préfixés CST_) et est entièrement
invisible ; avec AGV, Notifications, GalileoFaults et Common, ce sont 260
workflows hors périmètre.

Côté API AD le correctif est simple, l'application n'étant qu'un champ du
payload — vérifié, ["CustomApp", tenant, 5, 0] renvoie bien les workflows CST_.
Il faudra en revanche indexer les caches par application.

Côté QueryExecute c'est non résolu : passer Application "CustomApp" ne change
pas le contexte de lecture, les entités CST_ ne répondent ni au singulier ni au
pluriel et aucune n'apparaît dans les 232 entités du Metadata EasyWMS. Elles
sont définies dans EasyBuilder (FromMetadata: false). Consigné comme question
ouverte, sans solution promise.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 15:45:52 +02:00
Arthur Ria 1a1b9ebe03 Roadmap : lots de correction issus du diagnostic du 24/08
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>
2026-08-24 15:39:08 +02:00
Arthur Ria 0ff44f7b78 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>
2026-08-24 15:15:31 +02:00
Arthur Ria 9b95e15cbc Nettoyage du dépôt : doublons, code mort, secrets, build
Fichiers hors périmètre ou dupliqués :
- suppression des 5 .md dupliqués à la racine (copies md5-identiques de
  docs/api/ et docs/entities/)
- suppression de JANITOR_main.js / JANITOR_entities.json (application
  Electron sans lien avec le serveur MCP)
- suppression de temp/*.json (dumps de workflows versionnés par accident)
  et ajout de temp/ au .gitignore
- suppression de claude_desktop_config_ssh.json : mots de passe en clair et
  variables ORACLE_* d'une architecture abandonnée
- AD_API_TEST_RESULTS.md -> docs/ad-api-validation.md (credentials du
  snippet remplacés par des variables d'environnement)
- suppression d'IMPLEMENTATION_SUMMARY.md, doublon du précédent
- queries api.php -> docs/reference-queries-api.php (renommage seul)

Code mort :
- suppression de src/resources/documentation.js : la resource docs:// n'a
  jamais été branchée dans src/index.js
- suppression de src/config/constants.js : module entièrement inutilisé,
  requis par wms-query-service.js mais dont aucune constante n'était lue.
  Emporte RESOURCE_URIS.WORKFLOWS_CATEGORIES, URI déclarée jamais servie.
- log-service.js : suppression de findRecentErrors, readFullLog et
  getLogStats, exportées mais exposées par aucun outil MCP
- suppression de LOG_FILE_PATTERN (lue depuis .env, jamais appliquée : le
  scan filtre sur .log en dur), y compris dans .env.example
- log-service.js : préfixe [Logs] sur les messages, comme les autres modules

Secrets :
- test-ad-api.ps1 -> scripts/test-ad-api.ps1, credentials passés en
  paramètres ou par WMS_USERNAME / WMS_PASSWORD au lieu d'être en dur

Build et test :
- @yao-pkg/pkg en devDependency, cible node22-win-x64 : npm run build
  échouait faute de pkg, et node20 n'a pas de binaire prébuilt (bascule sur
  une compilation de Node qui échoue sans toolchain MSVC)
- index.js : le .env est lu à côté de l'exécutable quand le serveur est
  packagé. Avec un chemin statique, pkg embarquait le .env dans le snapshot,
  figeant les credentials dans le binaire.
- scripts/test-connection.js : npm test pointait sur un fichier absent.
  Smoke test en lecture seule (OAuth, QueryExecute, QueryScalarExecute,
  API AD), par profil ou sur tous.

Vérifié après nettoyage : 23 outils et 6 resources répondent au handshake
MCP, npm test passe 4/4 contre le WMS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 15:15:08 +02:00
36 changed files with 3642 additions and 12361 deletions
-1
View File
@@ -14,7 +14,6 @@
# reference the active profile's HOST — the server substitutes it at runtime. # reference the active profile's HOST — the server substitutes it at runtime.
# Log access is automatically disabled when the active profile has SAAS=true. # Log access is automatically disabled when the active profile has SAAS=true.
LOGS_PATH=\\{host}\inetpub\logs\LogFiles\Mecalux;\\{host}\ProgramData\Mecalux\ETLLogs LOGS_PATH=\\{host}\inetpub\logs\LogFiles\Mecalux;\\{host}\ProgramData\Mecalux\ETLLogs
LOG_FILE_PATTERN=*.log
# ---------------------------------------- # ----------------------------------------
# Shared WMS settings (same across all profiles) # Shared WMS settings (same across all profiles)
+3
View File
@@ -20,3 +20,6 @@ Thumbs.db
.idea/ .idea/
*.swp *.swp
*.swo *.swo
# Dumps temporaires (workflows exportes, etc.)
temp/
-669
View File
@@ -1,669 +0,0 @@
## Application
| API | Description |
| --- | --- |
| [GET api/application/apiversion](https://10.255.255.2/ApplicationService/Help/Api/GET-api-application-apiversion) |
No documentation available.
|
| [GET api/application/apichangelog](https://10.255.255.2/ApplicationService/Help/Api/GET-api-application-apichangelog) |
No documentation available.
|
| [POST api/application/GetResources](https://10.255.255.2/ApplicationService/Help/Api/POST-api-application-GetResources) |
No documentation available.
|
## CommandExecute
| API | Description |
| --- | --- |
| [POST api/CommandExecute](https://10.255.255.2/ApplicationService/Help/Api/POST-api-CommandExecute) |
No documentation available.
|
## CommandFiles
| API | Description |
| --- | --- |
| [POST api/CommandUploadFile](https://10.255.255.2/ApplicationService/Help/Api/POST-api-CommandUploadFile) |
No documentation available.
|
| [GET api/CommandDownloadFile?fileCode={fileCode}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-CommandDownloadFile_fileCode) |
No documentation available.
|
## Configuration
| API | Description |
| --- | --- |
| [GET api/configuration/apiversion](https://10.255.255.2/ApplicationService/Help/Api/GET-api-configuration-apiversion) |
No documentation available.
|
| [GET api/configuration/apichangelog](https://10.255.255.2/ApplicationService/Help/Api/GET-api-configuration-apichangelog) |
No documentation available.
|
| [POST api/configuration/updateorganization](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-updateorganization) |
No documentation available.
|
| [POST api/configuration/updateusergroups](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-updateusergroups) |
No documentation available.
|
| [POST api/configuration/updaterfmenuitems](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-updaterfmenuitems) |
No documentation available.
|
| [POST api/configuration/updatejobs](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-updatejobs) |
No documentation available.
|
| [POST api/configuration/stopjobs](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-stopjobs) |
No documentation available.
|
| [POST api/configuration/startjobs](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-startjobs) |
No documentation available.
|
| [POST api/configuration/executejob](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-executejob) |
No documentation available.
|
| [POST api/configuration/updatesubscriptions](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-updatesubscriptions) |
No documentation available.
|
| [POST api/configuration/updatetimelinetemplates](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-updatetimelinetemplates) |
No documentation available.
|
| [POST api/configuration/updatetypes](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-updatetypes) |
No documentation available.
|
| [POST api/configuration/updateprocesses](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-updateprocesses) |
No documentation available.
|
| [POST api/configuration/updatequeries](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-updatequeries) |
No documentation available.
|
| [POST api/configuration/updateDataWarehouseContext](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-updateDataWarehouseContext) |
No documentation available.
|
| [POST api/configuration/updateresources](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-updateresources) |
No documentation available.
|
| [POST api/configuration/updateapplication](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-updateapplication) |
No documentation available.
|
| [POST api/configuration/updateapplications](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-updateapplications) |
No documentation available.
|
| [POST api/configuration/updatelicense](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-updatelicense) |
No documentation available.
|
| [POST api/configuration/updatehooks](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-updatehooks) |
No documentation available.
|
| [POST api/configuration/updategenericsearch](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-updategenericsearch) |
No documentation available.
|
| [POST api/configuration/undoLatestMigration?applicationName={applicationName}](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-undoLatestMigration_applicationName) |
No documentation available.
|
| [POST api/configuration/enableapplication?applicationName={applicationName}](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-enableapplication_applicationName) |
No documentation available.
|
| [POST api/configuration/disableapplication?applicationName={applicationName}](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-disableapplication_applicationName) |
No documentation available.
|
| [POST api/configuration/enabletelemetry](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-enabletelemetry) |
No documentation available.
|
| [POST api/configuration/disabletelemetry](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-disabletelemetry) |
No documentation available.
|
| [GET api/configuration/tenant](https://10.255.255.2/ApplicationService/Help/Api/GET-api-configuration-tenant) |
No documentation available.
|
| [POST api/configuration/enablefeature](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-enablefeature) |
No documentation available.
|
| [POST api/configuration/disablefeature](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-disablefeature) |
No documentation available.
|
| [POST api/configuration/getfeature](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-getfeature) |
No documentation available.
|
| [GET api/configuration/applications](https://10.255.255.2/ApplicationService/Help/Api/GET-api-configuration-applications) |
No documentation available.
|
| [GET api/configuration/applicationFeatures?applicationName={applicationName}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-configuration-applicationFeatures_applicationName) |
No documentation available.
|
| [GET api/configuration/tenants](https://10.255.255.2/ApplicationService/Help/Api/GET-api-configuration-tenants) |
No documentation available.
|
| [GET api/configuration/tenantinfo?tenantCode={tenantCode}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-configuration-tenantinfo_tenantCode) |
No documentation available.
|
| [GET api/configuration/serviceinfo](https://10.255.255.2/ApplicationService/Help/Api/GET-api-configuration-serviceinfo) |
No documentation available.
|
| [POST api/configuration/updateservicemode](https://10.255.255.2/ApplicationService/Help/Api/POST-api-configuration-updateservicemode) |
No documentation available.
|
## Events
| API | Description |
| --- | --- |
| [POST api/PublishEvents](https://10.255.255.2/ApplicationService/Help/Api/POST-api-PublishEvents) |
No documentation available.
|
## GatewayCommit
| API | Description |
| --- | --- |
| [POST api/GatewayCommit](https://10.255.255.2/ApplicationService/Help/Api/POST-api-GatewayCommit) |
No documentation available.
|
## GatewayExecuteCommand
| API | Description |
| --- | --- |
| [POST api/GatewayExecuteCommand](https://10.255.255.2/ApplicationService/Help/Api/POST-api-GatewayExecuteCommand) |
No documentation available.
|
## GatewayExecuteCustomCommand
| API | Description |
| --- | --- |
| [POST api/GatewayExecuteCustomCommand](https://10.255.255.2/ApplicationService/Help/Api/POST-api-GatewayExecuteCustomCommand) |
No documentation available.
|
## GatewayReject
| API | Description |
| --- | --- |
| [POST api/GatewayReject](https://10.255.255.2/ApplicationService/Help/Api/POST-api-GatewayReject) |
No documentation available.
|
## GatewayRollback
| API | Description |
| --- | --- |
| [POST api/GatewayRollback](https://10.255.255.2/ApplicationService/Help/Api/POST-api-GatewayRollback) |
No documentation available.
|
## GatewayUpdateRoutes
| API | Description |
| --- | --- |
| [POST api/GatewayUpdateRoutes](https://10.255.255.2/ApplicationService/Help/Api/POST-api-GatewayUpdateRoutes) |
No documentation available.
|
## GatewayUpdateStations
| API | Description |
| --- | --- |
| [POST api/GatewayUpdateStations](https://10.255.255.2/ApplicationService/Help/Api/POST-api-GatewayUpdateStations) |
No documentation available.
|
## GenericSearch
Generic Search Controller.
| API | Description |
| --- | --- |
| [POST api/GenericSearch/Search](https://10.255.255.2/ApplicationService/Help/Api/POST-api-GenericSearch-Search) |
Searches the specified documents.
|
| [POST api/GenericSearch/Document](https://10.255.255.2/ApplicationService/Help/Api/POST-api-GenericSearch-Document) |
Gets the specified category document.
|
| [GET api/GenericSearch/Categories?applicationName={applicationName}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-GenericSearch-Categories_applicationName) |
Gets the categories.
|
| [POST api/GenericSearch/Categories?applicationName={applicationName}](https://10.255.255.2/ApplicationService/Help/Api/POST-api-GenericSearch-Categories_applicationName) |
Gets the categories.
|
## Health
| API | Description |
| --- | --- |
| [GET api/healthcheck?tenantCode={tenantCode}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-healthcheck_tenantCode) |
No documentation available.
|
| [POST api/healthcheck?tenantCode={tenantCode}](https://10.255.255.2/ApplicationService/Help/Api/POST-api-healthcheck_tenantCode) |
No documentation available.
|
| [GET api/ready?tenantCode={tenantCode}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-ready_tenantCode) |
No documentation available.
|
## Metadata
| API | Description |
| --- | --- |
| [GET api/Metadata/Commands](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-Commands) |
No documentation available.
|
| [GET api/Metadata/Commands?applicationName={applicationName}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-Commands_applicationName) |
No documentation available.
|
| [GET api/Metadata/CommandProperties?assemblyFullName={assemblyFullName}&fullName={fullName}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-CommandProperties_assemblyFullName_fullName) |
No documentation available.
|
| [GET api/Metadata/CommandsAll](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-CommandsAll) |
No documentation available.
|
| [GET api/Metadata/CommandsAll?applicationName={applicationName}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-CommandsAll_applicationName) |
No documentation available.
|
| [GET api/Metadata/Entities](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-Entities) |
No documentation available.
|
| [GET api/Metadata/Entities?applicationName={applicationName}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-Entities_applicationName) |
No documentation available.
|
| [GET api/Metadata/EntityProperties?assemblyFullName={assemblyFullName}&fullName={fullName}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-EntityProperties_assemblyFullName_fullName) |
No documentation available.
|
| [GET api/Metadata/EntitiesAll](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-EntitiesAll) |
No documentation available.
|
| [GET api/Metadata/EntitiesAll?applicationName={applicationName}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-EntitiesAll_applicationName) |
No documentation available.
|
| [GET api/Metadata/Events](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-Events) |
No documentation available.
|
| [GET api/Metadata/Events?applicationName={applicationName}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-Events_applicationName) |
No documentation available.
|
| [GET api/Metadata/EventProperties?assemblyFullName={assemblyFullName}&fullName={fullName}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-EventProperties_assemblyFullName_fullName) |
No documentation available.
|
| [GET api/Metadata/EventsAll](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-EventsAll) |
No documentation available.
|
| [GET api/Metadata/EventsAll?applicationName={applicationName}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-EventsAll_applicationName) |
No documentation available.
|
| [GET api/Metadata/Aggregates](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-Aggregates) |
No documentation available.
|
| [GET api/Metadata/Aggregates?applicationName={applicationName}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-Aggregates_applicationName) |
No documentation available.
|
| [GET api/Metadata/AggregateProperties?assemblyFullName={assemblyFullName}&fullName={fullName}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-AggregateProperties_assemblyFullName_fullName) |
No documentation available.
|
| [GET api/Metadata/AggregatesAll](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-AggregatesAll) |
No documentation available.
|
| [GET api/Metadata/AggregatesAll?applicationName={applicationName}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-Metadata-AggregatesAll_applicationName) |
No documentation available.
|
## QueryCommands
| API | Description |
| --- | --- |
| [POST api/QueryCommands](https://10.255.255.2/ApplicationService/Help/Api/POST-api-QueryCommands) |
No documentation available.
|
## QueryEvents
| API | Description |
| --- | --- |
| [POST api/QueryEvents](https://10.255.255.2/ApplicationService/Help/Api/POST-api-QueryEvents) |
No documentation available.
|
| [POST api/QueryCommandEvents](https://10.255.255.2/ApplicationService/Help/Api/POST-api-QueryCommandEvents) |
No documentation available.
|
| [POST api/QueryCorrelationEvents](https://10.255.255.2/ApplicationService/Help/Api/POST-api-QueryCorrelationEvents) |
No documentation available.
|
## QueryExecute
| API | Description |
| --- | --- |
| [GET api/QueryExecuteStream](https://10.255.255.2/ApplicationService/Help/Api/GET-api-QueryExecuteStream) |
No documentation available.
|
| [POST api/QueryExecuteStream](https://10.255.255.2/ApplicationService/Help/Api/POST-api-QueryExecuteStream) |
No documentation available.
|
| [POST api/QueryCancel](https://10.255.255.2/ApplicationService/Help/Api/POST-api-QueryCancel) |
No documentation available.
|
| [POST api/QueriesCancel](https://10.255.255.2/ApplicationService/Help/Api/POST-api-QueriesCancel) |
No documentation available.
|
| [POST api/QueryExecute](https://10.255.255.2/ApplicationService/Help/Api/POST-api-QueryExecute) |
No documentation available.
|
## QueryScalarExecute
| API | Description |
| --- | --- |
| [POST api/QueryScalarExecuteAsync](https://10.255.255.2/ApplicationService/Help/Api/POST-api-QueryScalarExecuteAsync) |
No documentation available.
|
| [POST api/QueryScalarExecute](https://10.255.255.2/ApplicationService/Help/Api/POST-api-QueryScalarExecute) |
No documentation available.
|
## QuerySnapshots
| API | Description |
| --- | --- |
| [POST api/QuerySnapshots](https://10.255.255.2/ApplicationService/Help/Api/POST-api-QuerySnapshots) |
No documentation available.
|
## Workflow
| API | Description |
| --- | --- |
| [POST api/Workflow/Logon](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-Logon) |
No documentation available.
|
| [POST api/Workflow/GetUserScreen](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-GetUserScreen) |
No documentation available.
|
| [POST api/Workflow/GetUserScreenInfo](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-GetUserScreenInfo) |
No documentation available.
|
| [POST api/Workflow/GetUserScreenParameters](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-GetUserScreenParameters) |
No documentation available.
|
| [POST api/Workflow/GetUserProcessAttributes](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-GetUserProcessAttributes) |
No documentation available.
|
| [POST api/Workflow/ExecuteUserAction](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-ExecuteUserAction) |
No documentation available.
|
| [POST api/Workflow/ExecuteUserActionGetScreen](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-ExecuteUserActionGetScreen) |
No documentation available.
|
| [POST api/Workflow/TerminateUserSession](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-TerminateUserSession) |
No documentation available.
|
| [POST api/Workflow/Start](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-Start) |
No documentation available.
|
| [POST api/Workflow/GetScreen](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-GetScreen) |
No documentation available.
|
| [POST api/Workflow/GetScreenParameters](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-GetScreenParameters) |
No documentation available.
|
| [POST api/Workflow/GetProcessAttributes](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-GetProcessAttributes) |
No documentation available.
|
| [POST api/Workflow/ExecuteAction](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-ExecuteAction) |
No documentation available.
|
| [POST api/Workflow/ExecuteActionGetScreen](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-ExecuteActionGetScreen) |
No documentation available.
|
| [POST api/Workflow/Terminate](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-Terminate) |
No documentation available.
|
| [POST api/Workflow/GetSessions](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-GetSessions) |
No documentation available.
|
| [POST api/Workflow/ExecuteProcessAction](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-ExecuteProcessAction) |
No documentation available.
|
| [POST api/Workflow/Cancel](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-Cancel) |
No documentation available.
|
| [POST api/Workflow/ExecuteValidateDialogFormat](https://10.255.255.2/ApplicationService/Help/Api/POST-api-Workflow-ExecuteValidateDialogFormat) |
No documentation available.
|
## WorkflowLog
| API | Description |
| --- | --- |
| [GET api/WorkflowLog/GetInstances?processDefinitionId={processDefinitionId}&skip={skip}&take={take}&startDateFrom={startDateFrom}&startDateTo={startDateTo}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-WorkflowLog-GetInstances_processDefinitionId_skip_take_startDateFrom_startDateTo) |
No documentation available.
|
| [GET api/WorkflowLog/GetInstances?processDefinitionId={processDefinitionId}&skip={skip}&take={take}&startDateFrom={startDateFrom}&startDateTo={startDateTo}&filterAttributeName={filterAttributeName}&filterAttributeValue={filterAttributeValue}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-WorkflowLog-GetInstances_processDefinitionId_skip_take_startDateFrom_startDateTo_filterAttributeName_filterAttributeValue) |
No documentation available.
|
| [GET api/WorkflowLog/GetInstance?processId={processId}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-WorkflowLog-GetInstance_processId) |
No documentation available.
|
| [GET api/WorkflowLog/GetLogs?processId={processId}&skip={skip}&take={take}&logDateFrom={logDateFrom}&logDateTo={logDateTo}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-WorkflowLog-GetLogs_processId_skip_take_logDateFrom_logDateTo) |
No documentation available.
|
| [GET api/WorkflowLog/ValidateInstance?applicationName={applicationName}&processDefinitionId={processDefinitionId}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-WorkflowLog-ValidateInstance_applicationName_processDefinitionId) |
No documentation available.
|
| [GET api/WorkflowLog/Validate?applicationName={applicationName}&processDefinitionId={processDefinitionId}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-WorkflowLog-Validate_applicationName_processDefinitionId) |
No documentation available.
|
| [GET api/WorkflowLog/ValidateInstanceByVersionId?applicationName={applicationName}&processVersionId={processVersionId}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-WorkflowLog-ValidateInstanceByVersionId_applicationName_processVersionId) |
No documentation available.
|
| [GET api/WorkflowLog/ValidateByVersionId?applicationName={applicationName}&processVersionId={processVersionId}](https://10.255.255.2/ApplicationService/Help/Api/GET-api-WorkflowLog-ValidateByVersionId_applicationName_processVersionId) |
No documentation available.
|
+232 -832
View File
File diff suppressed because it is too large Load Diff
+352
View File
@@ -0,0 +1,352 @@
# 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` (type `QueryContextType`) sélectionne le contexte de
données interrogé. Il a **quatre** valeurs, pas deux — vérifiées une à une sur
le tenant `LIMAGRAI2512` :
| Valeur | Contexte | Statut sur ce tenant |
|---|---|---|
| `0` | **Reading**`ApplicationReadingContext` | opérationnel, champs de statut en **chaînes** (`"Release"`) |
| `1` | Writing — `ApplicationWritingRepository` | opérationnel, champs de statut en **énumérations** |
| `2` | DataWarehouse | **non configuré** : `Could not resolve serviceType 'IDataWarehouse…'` |
| `3` | Metrics — `ApplicationMetricDataContext` | contexte présent, modèle de données non exploré |
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()`.
Le message d'erreur nomme le contexte (`ApplicationReadingContext`,
`ApplicationWritingRepository`, …) : c'est le moyen le plus rapide de savoir
quel `QueryType` a réellement été utilisé.
**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.
-215
View File
@@ -1,215 +0,0 @@
# WMS MCP Server - Implementation Summary
**Date:** 2026-04-02
**Status:** ✅ **COMPLETE AND VALIDATED**
## Overview
Successfully implemented a comprehensive MCP (Model Context Protocol) server for the WMS (Warehouse Management System) with 100% API-based architecture. The server provides Claude Desktop with full access to WMS data, workflows, and Application Dictionary elements.
## Implementation Highlights
### ✅ Phase 1: Core Infrastructure
- **API Service:** OAuth 2.0 authentication with automatic token refresh
- **MCP Server:** Full MCP SDK implementation with resources and tools
- **Configuration:** Environment-based configuration with .env support
- **Error Handling:** Comprehensive error handling and logging
### ✅ Phase 2: WMS APIs
- **Query API:** LINQ-based entity querying (12 entity types)
- **Command API:** WMS command execution
- **Workflow API:** Workflow fetching with lazy loading and caching
- Successfully validated: 3,712 workflows retrieved
### ✅ Phase 3: Application Dictionary (NEW)
- **20 Element Types:** Commands, Queries, Dialogs, Views, Entities, Events, etc.
- **38,765 Total Elements:** Comprehensive coverage of application definitions
- **17/19 Types Validated:** curl testing confirmed API endpoints work
- **Lazy Loading + Cache:** Same pattern as workflows (1-hour TTL)
## Test Results
### curl Validation (Application Dictionary)
| Status | Count | Percentage |
|--------|-------|------------|
| ✅ Working | 17 | 89.5% |
| ❌ Failed (404) | 2 | 10.5% |
**Working Types:**
- Command (1,872)
- Query (2,016)
- Dialog (734)
- View (373)
- Entity (331)
- Event (1,980)
- FieldType (320)
- Hook (60)
- List (243)
- Record (337)
- Relationship (49)
- Report (67)
- Resource (29,374) ← Largest type
- Subscription (500)
- Validator (17)
- ViewGroup (180)
- Workflow (3,712)
**Failed Types (Removed):**
- WorkflowAction (404 Not Found)
- WritingModel (404 Not Found)
**Empty Types (Available):**
- Dashboard (0)
- TimelineTemplate (0)
- Toggle (0)
## Files Created/Modified
### Services
- ✅ `src/services/api-service.js` - OAuth + HTTP client
- ✅ `src/services/workflow-service.js` - Workflow fetching
- ✅ `src/services/ad-service.js` - **NEW: Application Dictionary service**
- ✅ `src/services/wms-query-service.js` - LINQ query builder
- ✅ `src/services/log-service.js` - Log file operations
### Tools
- ✅ `src/tools/workflow-tools.js` - Workflow tools (3 tools)
- ✅ `src/tools/ad-tools.js` - **NEW: AD tools (5 tools)**
- ✅ `src/tools/wms-query-tools.js` - WMS query tools (3 tools)
- ✅ `src/tools/api-tools.js` - API tools (2 tools)
- ✅ `src/tools/log-tools.js` - Log tools (2 tools)
### Resources
- ✅ `src/resources/wms-entities.js` - Entity catalog
- ✅ `src/resources/entity-schemas.js` - Schema documentation
- ✅ `src/resources/query-examples.js` - LINQ examples
- ✅ `src/resources/workflows.js` - Workflow overview
- ✅ `src/resources/apis.js` - API documentation
- ✅ `src/resources/logs.js` - Log guide
### Core
- ✅ `src/index.js` - Main MCP server with AD integration
- ✅ `.env` - Environment configuration
- ✅ `package.json` - Dependencies
### Documentation
- ✅ `CLAUDE.md` - Updated with AD implementation details
- ✅ `AD_API_TEST_RESULTS.md` - **NEW: Detailed curl test results**
- ✅ `test-ad-api.ps1` - **NEW: PowerShell test script**
- ✅ `IMPLEMENTATION_SUMMARY.md` - This file
## Total MCP Tools Implemented
| Category | Tool Count | Tool Names |
|----------|------------|------------|
| **Workflow** | 3 | search_workflows, get_workflow_details, list_workflow_categories |
| **AD Elements** | 5 | get_application_summary, get_ad_elements, search_ad_elements, get_ad_element_details, list_ad_types |
| **WMS Queries** | 3 | query_wms_entities, get_entity_schema, search_wms_data |
| **APIs** | 2 | call_query_api, execute_command |
| **Logs** | 2 | read_recent_logs, search_logs |
| **TOTAL** | **15** | |
## Total MCP Resources Implemented
1. `wms://entities` - WMS entity catalog
2. `wms://entity-schemas` - Entity schema details
3. `wms://query-examples` - LINQ query examples
4. `workflows://overview` - Workflow overview
5. `api://catalog` - API catalog
6. `logs://guide` - Log file guide
**TOTAL: 6 resources**
## Critical Fixes Applied
### 1. dotenv stdout pollution
**Problem:** dotenv writes to stdout, breaking MCP protocol
**Solution:** Redirect stdout to stderr during dotenv loading
### 2. Missing tenant_code
**Problem:** OAuth returns 400 Bad Request
**Solution:** Added `tenant_code: process.env.WMS_API_TENANT` to auth request
### 3. API response structure
**Problem:** Code expected direct array, API returns `{entities: [...]}`
**Solution:** Extract entities: `response?.entities || []`
### 4. Property name inconsistency
**Problem:** API returns both lowercase and uppercase properties
**Solution:** Support both: `e.name || e.Name`
### 5. WorkflowAction & WritingModel 404
**Problem:** Two AD element types return 404 Not Found
**Solution:** Removed from AD_ELEMENT_TYPES configuration
## Performance Optimizations
1. **Lazy Loading:** Data fetched only when needed (not at startup)
2. **Caching:** 1-hour TTL for all cached data
3. **Pagination:** High page sizes to minimize API calls
- Workflows: 5,000 per page
- Resource: 15,000 per page
- Others: 100,000 per page (usually single fetch)
4. **Property Access:** Flexible property name handling (lowercase/uppercase)
## Architecture Achievements
**100% API-based:** No Oracle database dependency
**OAuth 2.0:** Secure token-based authentication
**Lazy Loading:** Minimal startup time
**Caching:** Reduced API load
**Error Resilient:** Comprehensive error handling
**MCP Compliant:** Full protocol compliance
**Validated:** curl testing confirmed functionality
## Usage Examples
### Query Workflows
```
"Search for workflows containing 'Order'"
"Get details of workflow 'Helper_MasterOutboundOrderById'"
```
### Query AD Elements
```
"Get all Command elements"
"Search Queries containing 'Load'"
"Get details of Dialog 'WorkStation_SelectDivision_V1'"
"Show me application summary"
```
### Query WMS Data
```
"Query the last 10 products"
"Get schema for Products entity"
```
### Read Logs
```
"Show me recent log entries"
"Search logs for 'error' keyword"
```
## Next Steps (Optional)
1. **Deploy to Production VM:** Copy to `C:\WMS\mcp\` on server
2. **SSH Configuration:** Set up SSH for remote access from dev PC
3. **Build Executable:** Create standalone .exe with `pkg`
4. **Monitor Performance:** Track cache hit rates and API call frequency
5. **Expand Coverage:** Add more AD element type tools as needed
## Conclusion
The WMS MCP Server implementation is **complete, validated, and ready for use**. All 17 working Application Dictionary element types have been tested with curl and confirmed functional. The server successfully integrates with Claude Desktop, providing comprehensive access to WMS workflows, application definitions, runtime data, and logs through a clean MCP interface.
**Total Coverage:**
- ✅ 15 MCP Tools
- ✅ 6 MCP Resources
- ✅ 5 API endpoints (Query, Command, Workflow, AD x20, OAuth)
- ✅ 20 AD element types (17 working, 3 empty)
- ✅ 38,765 AD elements accessible
- ✅ 3,712 workflows accessible
- ✅ 12 WMS entity types queryable
The implementation exceeds the original requirements by adding comprehensive Application Dictionary support beyond the initial workflow-only scope.
-129
View File
@@ -1,129 +0,0 @@
{
"entities": [
{
"id": "Stock",
"name": "Stocks",
"displayName": "Stocks",
"component": "StockId",
"remove_command": "Mecalux.ITSW.EasyWMS.Modules.Contracts.Commands.StockRemoveCommand"
},
{
"id": "Containers",
"name": "Containers",
"displayName": "Supports",
"component": "ComponentId",
"remove_command": "Mecalux.ITSW.EasyWMS.Modules.MasterData.Contracts.Commands.ContainerRemoveCommand"
},
{
"id": "ProductLocations",
"name": "ProductLocations",
"displayName": "Emplacements picking dédiés",
"component": "ProductLocationId",
"remove_command": "Mecalux.ITSW.EasyWMS.Modules.Expeditions.Contracts.Commands.ProductLocationRemoveCommand"
},
{
"id": "Tasks",
"name": "Tasks",
"displayName": "Tâches",
"component": "TaskId",
"remove_command": "Mecalux.ITSW.EasyWMS.Modules.Contracts.Commands.TaskRemoveCommand"
},
{
"id": "Products",
"name": "Products",
"displayName": "Articles",
"component": "ProductId",
"remove_command": "Mecalux.ITSW.EasyWMS.Modules.MasterData.Contracts.Commands.ProductRemoveCommand"
},
{
"id": "Accounts",
"name": "Accounts",
"displayName": "Clients",
"component": "AccountId",
"remove_command": "Mecalux.ITSW.EasyWMS.Modules.MasterData.Contracts.Commands.AccountRemoveCommand"
},
{
"id": "Suppliers",
"name": "Suppliers",
"displayName": "Fournisseurs",
"component": "SupplierId",
"remove_command": "Mecalux.ITSW.EasyWMS.Modules.MasterData.Contracts.Commands.SupplierRemoveCommand"
},
{
"id": "Kits",
"name": "Kits",
"displayName": "Kits",
"where1": "(z => z.IsEnable == true)",
"where1_name": "Kits actifs",
"btn1" : "Désactiver",
"component": "KitId",
"remove_command": "Mecalux.ITSW.EasyWMS.Modules.MasterData.Contracts.Commands.KitDeleteCommand",
"btn1_command": "Mecalux.ITSW.EasyWMS.Modules.MasterData.Contracts.Commands.KitDisableCommand"
},
{
"id": "Aliases",
"name": "Aliases",
"displayName": "Alias",
"component": "AliasId",
"remove_command": "Mecalux.ITSW.EasyWMS.Modules.MasterData.Contracts.Commands.AliasRemoveCommand"
},
{
"id": "InboundOrders",
"name": "InboundOrders",
"displayName": "Ordres d'entrée",
"component": "OutboundOrderId",
"remove_command": "Mecalux.ITSW.EasyWMS.Modules.Receptions.Contracts.Commands.InboundOrderRemoveCommand",
"where1": "(z => z.InboundStatus == Mecalux.ITSW.EasyWMS.Modules.Common.Domain.InboundStatus.ReceptionPending)",
"where1_name" : "Réception en attente",
"btn1" : "Annuler",
"btn1_command" :"Mecalux.ITSW.EasyWMS.Modules.Receptions.Contracts.Commands.InboundOrderCancelCommandV2",
"where2": "(z => z.InboundStatus == Mecalux.ITSW.EasyWMS.Modules.Common.Domain.InboundStatus.Receiving)",
"where2_name" : "Réception en cours",
"where3": "(z => z.InboundStatus == Mecalux.ITSW.EasyWMS.Modules.Common.Domain.InboundStatus.Completed)",
"where3_name" : "Complété",
"btn3": "Fermer",
"btn3_command" : "Mecalux.ITSW.EasyWMS.Modules.Receptions.Contracts.Commands.InboundOrderClosingCommandV2",
"where4": "(z => z.InboundStatus == Mecalux.ITSW.EasyWMS.Modules.Common.Domain.InboundStatus.PartiallyReceived)",
"where4_name" : "Reçu partiellement",
"where5": "(z => z.InboundStatus == Mecalux.ITSW.EasyWMS.Modules.Common.Domain.InboundStatus.Cancelled)",
"where5_name" : "Annulé",
"where6": "(z => z.InboundStatus == Mecalux.ITSW.EasyWMS.Modules.Common.Domain.InboundStatus.Closing)",
"where6_name" : "Fermeture"
},
{
"id": "Receptions",
"name": "Receptions",
"displayName": "Réceptions",
"component": "ReceptionId",
"remove_command": "Mecalux.ITSW.EasyWMS.Modules.Receptions.Contracts.Commands.ReceptionRemoveCommand"
},
{
"id": "OutboundOrders",
"name": "OutboundOrders",
"displayName": "Ordres de sortie",
"component": "OutboundOrderId",
"remove_command": "Mecalux.ITSW.EasyWMS.Modules.Expeditions.Contracts.Commands.OutboundOrderCancelCommand",
"where1": "(z => z.OutboundOrderStatus == Mecalux.ITSW.EasyWMS.Modules.Common.Domain.OutboundOrderStatus.Creating)",
"where1_name" : "Création",
"btn1" : "Annuler",
"btn1_command" :"Mecalux.ITSW.EasyWMS.Modules.Expeditions.Contracts.Commands.OutboundOrderCancelCommand",
"where2": "(z => z.OutboundOrderStatus == Mecalux.ITSW.EasyWMS.Modules.Common.Domain.OutboundOrderStatus.Waiting)",
"where2_name" : "En attente",
"btn2" : "Annuler",
"btn2_command" :"Mecalux.ITSW.EasyWMS.Modules.Expeditions.Contracts.Commands.OutboundOrderCancelCommand",
"where3": "(z => z.OutboundOrderStatus == Mecalux.ITSW.EasyWMS.Modules.Common.Domain.OutboundOrderStatus.Release)",
"where3_name" : "Lancé"
}
]
}
-1246
View File
File diff suppressed because it is too large Load Diff
+225
View File
@@ -0,0 +1,225 @@
# 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.
Sonde plus légère, si l'on veut seulement savoir si le WMS répond (sans
authentification applicative) : `GET https://<host>/ApplicationService/api/healthcheck?tenantCode=<TENANT>`
et `.../api/ready?tenantCode=<TENANT>` renvoient 200. Elles ne disent rien de la
validité des credentials — pour ça, `npm test`.
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.
-227
View File
@@ -1,227 +0,0 @@
## Request Information
### URI Parameters
None.
### Body Parameters
[QueryExecute](https://10.255.255.2/ApplicationService/Help/ResourceModel?modelName=QueryExecute)
| Name | Description | Type | Additional information |
| --- | --- | --- | --- |
| Application |
Application name.
| String |
None.
|
| ClientModule |
Client module that executes the query.
| String |
None.
|
| QueryType |
Type of the query.
| [QueryContextType](https://10.255.255.2/ApplicationService/Help/ResourceModel?modelName=QueryContextType) |
Default: Reading
|
| Expression |
Query expression.
| String |
None.
|
| OrderBy |
Order by to be applied to the expression.
| String |
None.
|
| Select |
Select to be applied to the expression.
| String |
None.
|
| Skip |
Skip to be applied to the expression.
| Int32 |
None.
|
| Take |
Take to be applied to the expression.
| Int32 |
None.
|
| Parameters |
Dictionary with the parameters and their values for the query.
| Dictionary of String \[key\] and [QueryParameterValue](https://10.255.255.2/ApplicationService/Help/ResourceModel?modelName=QueryParameterValue) \[value\] |
None.
|
| InlineCount |
Indicate if an inline count must be done to the query.
| Boolean |
None.
|
| CommandTimeout |
Gets or sets the command/query timeout.
| Int32? |
None.
|
| QueryId |
Gets or sets the query execution identifier.
| Guid? |
None.
|
### Request Formats
**Sample:**
```
{
"Application": "sample string 1",
"ClientModule": "sample string 2",
"QueryType": 0,
"Expression": "sample string 3",
"OrderBy": "sample string 4",
"Select": "sample string 5",
"Skip": 6,
"Take": 7,
"Parameters": {
"sample string 1": {
"TypeName": "sample string 1",
"Value": {}
},
"sample string 2": {
"TypeName": "sample string 1",
"Value": {}
}
},
"InlineCount": true,
"CommandTimeout": 1,
"QueryId": "e57b9e2d-1ba9-42c7-a035-fe964ce53fd6"
}
```
**Sample:**
Sample not available.
**Sample:**
```
Binary JSON content. See http://bsonspec.org for details.
```
## Response Information
### Resource Description
[QueryResultData](https://10.255.255.2/ApplicationService/Help/ResourceModel?modelName=QueryResultData)
| Name | Description | Type | Additional information |
| --- | --- | --- | --- |
| InLineCount |
Gets or sets the in line count.
| Int32 |
None.
|
| Table |
Gets or sets the table.
| [QueryTable](https://10.255.255.2/ApplicationService/Help/ResourceModel?modelName=QueryTable) |
None.
|
### Response Formats
**Sample:**
```
{
"InLineCount": 1,
"Table": {
"TableName": "sample string 1",
"Columns": [
{
"ColumnName": "sample string 1",
"TypeName": "sample string 2"
},
{
"ColumnName": "sample string 1",
"TypeName": "sample string 2"
}
],
"Rows": [
null,
{
"Values": {
"sample string 1": {},
"sample string 3": {}
}
}
]
}
}
```
**Sample:**
```
Binary JSON content. See http://bsonspec.org for details.
```
+153
View File
@@ -0,0 +1,153 @@
# 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 |
| [ROADMAP.md](ROADMAP.md) | Travaux planifiés par lot, et ce qui a été écarté |
| [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.
+301
View File
@@ -0,0 +1,301 @@
# Roadmap
Travaux planifiés, par lot. Chaque lot est livrable indépendamment.
Constats issus de la session de diagnostic du **24/08/2026** (profil `LIMAGRAIN`,
host `10.255.255.2`, tenant `LIMAGRAI2512`), déclenchée par un rapport d'usage
d'une session Cowork. Toutes les anomalies ci-dessous ont été **reproduites**
contre le WMS réel — ce ne sont pas des hypothèses.
Les décisions actées vivent dans [DECISIONS.md](DECISIONS.md) ; ce fichier ne
contient que ce qui reste à faire.
---
## Cause racine commune
Le MCP interpole `entity_type` dans `Context.{entity_type}` **sans aucune
validation** (vérifié : aucune liste blanche dans le code). Or le nom attendu
par le contexte de lecture n'est pas le nom d'entité de l'Application
Dictionary.
L'API Metadata (`GET /Metadata/Entities`, **232 entités**) donne la
correspondance exacte :
| `Name` (renvoyé par `search_ad_elements`) | `TableName` (attendu par `Context.`) |
|---|---|
| `Container` | `Containers` |
| `Product` | `Products` |
| `ContainerType` | `ContainerTypes` |
| `Alias` | `Alias`**invariant, pas de pluriel** |
| `Item` | *n'existe pas dans le modèle Reading* |
Ce n'est donc pas une règle de pluralisation : c'est un mapping, et seul
`TableName` fait foi. `TableName` est unique sur les 232 entités.
Conséquences déjà constatées :
- une session utilisant les noms de l'AD (singuliers) déclenche un **HTTP 500**
sur chaque requête ;
- la liste d'entités documentée était fausse (`Aliases` n'existe pas, c'est
`Alias`) ;
- le MCP n'expose que 12 entités figées là où l'API en connaît 232.
---
## Lot 1 — Déblocage
Objectif : rendre le MCP auto-diagnosticable et réparer ce qui est cassé. Ce lot
seul aurait suffi à ce qu'une session se débrouille sans intervention.
### L1.1 — Remonter le détail des erreurs HTTP
Aujourd'hui toute erreur d'API se résume à `Request failed with status code 500`.
Or le WMS renvoie déjà le diagnostic complet dans le corps de la réponse :
```json
{"ClassName":"System.AggregateException","Message":"Compile Error: ...
'ApplicationReadingContext' ne contient pas de définition pour 'Container' ..."}
```
Enrichir l'erreur au point de passage unique (`api-service.post` / `.get`) avec :
statut, URL, verbe, payload envoyé, corps de réponse tronqué à ~2000 caractères.
**Fichier :** `src/services/api-service.js` (catch de `post` et `get`).
### L1.2 — Fiabiliser le routage des outils
Deux outils sont listés dans `tools/list` mais ne sont routés vers aucun module,
à cause du routage par préfixe :
| Outil | Cause | Erreur observée |
|---|---|---|
| `get_entity_metadata` | capté par `startsWith('get_entity_')` avant sa propre branche | `Unknown WMS query tool` |
| `list_log_files` | ne contient pas `_logs` mais `_log_files` | `Unknown tool` |
Remplacer le routage par préfixe par une **table explicite nom → module**,
construite depuis les `listTools()` de chaque module. Un outil listé mais non
routé devient alors impossible par construction, au lieu d'être rattrapé au cas
par cas.
**Fichier :** `src/index.js` (handler `tools/call`).
### L1.3 — Corriger les projections de champs des workflows
L'API AD renvoie les champs en minuscules (`id`, `name`, `version`,
`applicationName`). Deux endroits supposent une autre forme :
- `search_workflows` projette `w.Id`, `w.Code`, `w.Name`, `w.Category` → tous
`undefined`, supprimés par `JSON.stringify`**50 objets vides** pour un
`count` pourtant correct ;
- `workflow-service` lit `w.category || w.Category`, deux clés inexistantes →
`list_workflow_categories` renvoie **0 catégorie** et classe les 4012
workflows en `Uncategorized`.
Le champ le plus proche d'une catégorie est `applicationName`, mais il vaut
`EasyWMS` pour tous les workflows : la notion de catégorie n'a **aucun support**
dans les données. Décider en connaissance de cause plutôt que d'inventer une
taxonomie.
**Fichiers :** `src/tools/workflow-tools.js`, `src/services/workflow-service.js`.
---
## Lot 2 — Correctif de fond
### L2.1 — Résolution des entités via l'API Metadata
Accepter `entity_type` au nom d'entité (`Container`) ou au nom de jeu
(`Containers`), insensible à la casse, et émettre `Context.{TableName}`. Cache
identique aux autres (TTL partagé, invalidation au changement de profil).
Sur nom inconnu, échouer **avant tout appel réseau**, avec un message
actionnable :
> « Item » n'existe pas dans le modèle Reading. Proches : ItemGroup, StockItem.
> 232 entités disponibles — utilisez `get_entity_metadata` pour la liste.
Supprime la cause des 500 et débloque 232 entités au lieu de 12.
### L2.2 — Rejeter les paramètres inconnus
Le SDK MCP ignore silencieusement les paramètres non déclarés : un appel
`read_recent_logs(lines: 60)` retombe sur le défaut `count = 100` sans le
moindre signal, et l'appelant conclut à un paramètre ignoré.
Ajouter `additionalProperties: false` aux 23 schémas d'outils.
C'est le correctif retenu **à la place** d'une uniformisation des noms de
paramètres : renommer casse les usages existants pour un gain cosmétique, alors
que la cause réelle est l'absence de signal.
---
## Lot 3 — Ergonomie et documentation
### L3.1 — Bornage des sorties volumineuses
- `get_system_parameters` : ajouter `limit` / `offset`, aujourd'hui absents
(sortie constatée : 70 000 caractères, rejetée par le client).
- `search_logs` : garde-fou de taille. `max_results` existe déjà, mais les
`context_lines` multiplient le volume (88 000 caractères pour 50 résultats).
- Renvoyer `truncated: true` explicitement plutôt que de laisser le client se
faire rejeter.
### L3.2 — Documentation
- DECISIONS.md : **D21** la règle `TableName`, **D22** le routage par table
explicite.
- CLAUDE.md : corriger la liste d'entités (`Aliases``Alias`) et renvoyer vers
`get_entity_metadata` comme source de vérité.
- `wms://query-examples` : un exemple singulier/pluriel commenté.
---
## Lot 4 — Modèle de données et applications
Deux angles morts constatés le 24/08/2026, plus larges que les lots 1 à 3. Les
chiffres ci-dessous sont mesurés sur le tenant `LIMAGRAI2512`.
### L4.1 — Le modèle Writing est inatteignable
`QueryType` est figé à `0` (Reading) en dur dans `api-service.js`
(`executeQuery` et `executeScalarQuery`). Or `QueryContextType` a **quatre**
valeurs. Testées une à une :
| Valeur | Contexte | Résultat sur `LIMAGRAI2512` |
|---|---|---|
| `0` | Reading | opérationnel (seul utilisé aujourd'hui) |
| `1` | Writing | **opérationnel**`Context.Products` répond |
| `2` | DataWarehouse | **non configuré** : `Could not resolve serviceType 'IDataWarehouse…'` |
| `3` | Metrics | contexte présent (`ApplicationMetricDataContext`), modèle non exploré |
Exposer `query_type` sur les outils de requête, défaut `0`. Attention : D3 reste
vrai — en Writing les champs de statut sont des **énumérations**, donc
`== "Release"` échoue. La bascule doit être un choix explicite et documenté.
Le contexte `Metrics` mérite une exploration à part : c'est probablement là que
vivent les données agrégées produites par les jobs `MetricGatherer`.
### L4.2 — Une seule application sur neuf est visible
`Application` vient de `WMS_APPLICATION` dans `.env`, **partagé par tous les
profils**, sans surcharge par appel ni paramètre d'outil. Le MCP n'interroge donc
jamais que `EasyWMS`.
`POST /AD/api/Application/GetAll` en déclare **9** :
| Application | Workflows | Queries | Entities |
|---|---:|---:|---:|
| EasyWMS | 4012 | 2239 | 338 |
| **CustomApp** | **153** | **54** | **11** |
| AGV | 71 | 14 | 5 |
| Notifications | 26 | 35 | 24 |
| GalileoFaults | 9 | 20 | 24 |
| Common | 1 | 7 | 25 |
| SmartUI, User, WarehouseWebDesigner | 0 | 08 | 0 |
**CustomApp porte le spécifique client** — ses workflows sont préfixés `CST_`
(`CST_SendRejectContainersToPK`, `CST_Task`, `CST_Container`…). C'est
précisément ce qu'on cherche en debug, et c'est aujourd'hui invisible. Au total
**260 workflows et ~130 queries** hors périmètre.
Deux chantiers de difficulté très différentes :
**API AD — simple.** L'application est un champ du payload
(`[application, tenant, pageSize, offset]`). Vérifié : `["CustomApp", tenant,
5, 0]` sur `/Workflow/GetByApplication` renvoie bien les workflows `CST_`. Il
suffit d'un paramètre `application` sur les outils AD et workflow, avec une clé
de cache incluant l'application (sinon un cache pollué mélange les
applications).
**QueryExecute — tranché : le champ `Application` ne partitionne rien.**
`Context.AgvTasks` (entité de l'application AGV) répond aussi bien avec
`Application: "AGV"` qu'avec `Application: "EasyWMS"`. Le contexte de lecture est
**commun au tenant** : toutes les applications y déversent leurs entités.
Conséquence pour L2.1 : la table de résolution doit **agréger le Metadata de
toutes les applications** (`GET /Metadata/Entities?applicationName=…` par
application, 232 + 20 + 6 + …), et non se limiter à `EasyWMS`. Inutile en
revanche d'ajouter un paramètre `application` à `QueryExecute` : il ne changerait
rien.
**Les entités `CustomApp` ne sont interrogeables dans aucun contexte.** Les 11
entités `CST_` ont été testées sous les quatre `QueryType`, au singulier et au
pluriel : échec partout, et `Metadata/Entities` comme `Metadata/EntitiesAll`
renvoient **0 entité** pour `CustomApp`. Aucune n'est marquée
`isDataWarehouse`. Ce sont des définitions EasyBuilder (`FromMetadata: false`)
sans projection dans un contexte requêtable.
**L'API AD reste donc le seul accès au spécifique client** — ce qui rend le
paramètre `application` sur les outils AD et workflow d'autant plus utile.
### L4.3 — Identifier le MCP dans les logs du WMS
`QueryExecute` accepte un champ **`ClientModule`** que le MCP n'envoie pas.
Résultat : ses requêtes apparaissent dans les logs du WMS sous
`Execute error. Client: GNA` — le client OAuth partagé — donc indistinguables de
celles du vrai client GNA.
Renseigner `ClientModule` (`"MCP-WMS"` ou le nom du profil actif) rend chaque
requête du MCP traçable côté serveur. Vérifié : le champ est accepté.
### L4.4 — Historique d'exécution des workflows par API
`ApplicationService` expose une API `WorkflowLog` que le MCP n'utilise pas :
| Endpoint | Usage |
|---|---|
| `GET /WorkflowLog/GetInstances?processDefinitionId=&skip=&take=&startDateFrom=&startDateTo=` | instances d'un workflow sur une plage de dates, filtrables par attribut |
| `GET /WorkflowLog/GetInstance?processId=` | une instance |
| `GET /WorkflowLog/GetLogs?processId=&skip=&take=&logDateFrom=&logDateTo=` | journal d'exécution d'une instance |
| `GET /WorkflowLog/Validate?applicationName=&processDefinitionId=` | validation d'une définition |
Endpoints joignables et fonctionnels — `Validate` renvoie
`{"Success":true,"ErrorMessage":null,"Warnings":[]}`. `GetInstances` répond `[]`
sur le workflow testé : à confirmer sur un workflow ayant réellement tourné, la
journalisation n'étant pas forcément active partout.
**Peut remettre en cause D16** (historique des shipment templates jugé hors de
portée faute de logs fichier) : si l'historique d'exécution est disponible par
API, la conclusion change. À vérifier avant d'écrire quoi que ce soit.
### L4.5 — Champs de `QueryExecute` inexploités
La référence de l'API documente des champs que le MCP n'envoie jamais :
| Champ | Intérêt |
|---|---|
| `Parameters` | requêtes **paramétrées** (dictionnaire `nom -> {TypeName, Value}`) — supprimerait toute concaténation de chaîne dans les filtres, et pourrait débloquer D13 (`Select`) |
| `CommandTimeout` | timeout par requête, au lieu du timeout HTTP global de 30 s |
| `QueryId` + `POST /QueryCancel` | annulation d'une requête longue |
| `POST /QueryExecuteStream` | résultats en flux — piste sérieuse pour L3.1 (sorties volumineuses) |
Autres endpoints jamais utilisés, à évaluer : `QueryEvents`, `QueryCommands`,
`QueryCorrelationEvents`, `QuerySnapshots` (event sourcing — utile en debug),
`GET /Metadata/Commands|Events|Aggregates` et leurs variantes `…All`,
`GET /configuration/applications` (liste les applications **avec leur version**,
plus riche que `/AD/api/Application/GetAll`), `GET /healthcheck?tenantCode=` et
`GET /ready?tenantCode=` (sondes de disponibilité, répondent 200).
---
---
## Écarté
| Proposition | Raison |
|---|---|
| Uniformiser les noms de paramètres (`entity_type` / `query` partout) | Casse les usages existants ; les alias de transition doublent la surface à maintenir. La cause réelle est traitée par L2.2. |
| Exposer un `indexStatus` sur `generic_search` | `TotalDocuments: 0` est déjà le signal. Le MCP n'a aucun moyen d'interroger l'état de l'index de recherche. |
| Outil dédié `get_query_syntax_help` | L'information doit se trouver dans le message d'erreur, là où elle est lue (L2.1), pas dans un outil qu'il faut penser à appeler. |
---
## Points ouverts (hors lots)
- **`select_expression`** : les projections via le paramètre `Select` provoquent
des erreurs de compilation côté serveur (D13). Irritant principal restant.
- **Déploiement SSH sur la VM** : l'exécutable est validé, la configuration SSH
reste à faire.
- **Historique des shipment templates** : hors de portée, les logs concernés
n'existent pas sur l'hôte joignable (D16).
-39
View File
@@ -1,39 +0,0 @@
{
"mcpServers": {
"wms": {
"command": "ssh",
"args": [
"wms-vm",
"C:\\Users\\mecalux\\Desktop\\wms-mcp-server\\dist\\wms-mcp-server.exe"
],
"env": {
"ORACLE_USER": "db_read",
"ORACLE_PASSWORD": "PF9.43uAq17$U",
"ORACLE_CONNECTION_STRING": "localhost:1521/orcl",
"ORACLE_USER_WORKFLOWS": "db_ad",
"ORACLE_PASSWORD_WORKFLOWS": "PF9.43uAq17$U",
"ORACLE_CONNECTION_STRING_WORKFLOWS": "localhost:1521/orcl",
"WORKFLOWS_TABLE": "AD_WFPROCESSES",
"DOCS_PATH": "C:\\Users\\mecalux\\Desktop\\wms-mcp-server\\docs",
"LOGS_PATH": "C:\\inetpub\\logs\\LogFiles\\Mecalux;C:\\ProgramData\\Mecalux\\ETLLogs",
"LOG_FILE_PATTERN": "*.log",
"API_DEFINITIONS_TABLE": "API_DEFINITIONS",
"WMS_API_BASE_URL": "https://localhost/ApplicationService/api",
"WMS_API_TOKEN_URL": "https://localhost/EasySTS/OAuth/Token",
"WMS_API_AUTH": "Basic R05BOklFNGU3aXFoZHQ=",
"WMS_API_TENANT": "AD",
"WMS_API_USERNAME": "mecalux",
"WMS_API_PASSWORD": "ANDREZmlx6",
"TOKEN_REFRESH_THRESHOLD": "1000",
"TOKEN_MAX_AGE": "1190",
"MAX_QUERY_ROWS": "1000",
"QUERY_TIMEOUT": "30000",
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
}
},
"preferences": {
"coworkScheduledTasksEnabled": false,
"sidebarMode": "chat"
}
}
+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).
``` ## Où trouver le reste
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
```
## 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 ## Ajouter un document
2. Redémarrez Claude Desktop
3. Claude pourra automatiquement lire tous vos fichiers de documentation
## Accéder à la documentation depuis Claude 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
Dans Claude Desktop, vous pouvez demander : retrouvera.
- "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 !
@@ -119,8 +119,8 @@ $tokenResponse = Invoke-RestMethod -Uri "https://localhost/EasySTS/OAuth/Token"
-Body @{ -Body @{
grant_type = "password" grant_type = "password"
tenant_code = "AD" tenant_code = "AD"
username = "mecalux" username = $env:WMS_USERNAME
password = "ANDREZmlx6" password = $env:WMS_PASSWORD
} }
$token = $tokenResponse.access_token $token = $tokenResponse.access_token
@@ -137,13 +137,21 @@ $response = Invoke-RestMethod -Uri "https://localhost/AD/api/Command/GetByApplic
$response.entities | Select-Object -First 5 $response.entities | Select-Object -First 5
``` ```
## Next Steps ## Suivi
1. ✅ Remove WorkflowAction and WritingModel from AD_ELEMENT_TYPES Toutes les actions issues de cette campagne sont closes :
2. ✅ Update tool descriptions to reflect 20 types (not 22)
3. ⏭️ Test MCP server with Claude Desktop - `WorkflowAction` et `WritingModel` retirés de `AD_ELEMENT_TYPES` (D17).
4. ⏭️ Validate all 5 AD tools work correctly - Descriptions des outils alignées sur **20** types.
5. ⏭️ Update CLAUDE.md with final implementation details - Les 5 outils AD sont opérationnels et validés avec Claude Desktop.
- Architecture et pièges consignés dans [../CLAUDE.md](../CLAUDE.md) et
[../DECISIONS.md](../DECISIONS.md).
Pour rejouer la campagne complète :
```bash
powershell -ExecutionPolicy Bypass -File ../scripts/test-ad-api.ps1 -WmsHost <host> -Username <user> -Password '***' -Tenant AD
```
## Conclusion ## Conclusion
+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 # MAP - Documentation Portal
## Table des matières ## 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`.
File diff suppressed because one or more lines are too long
-137
View File
@@ -1,137 +0,0 @@
## OutboundOrder
Full name: **Mecalux.ITSW.EasyWMS.Modules.Expeditions.Reading.Domain.OutboundOrder**
## Summary
Represents an outbound order - Is used for managing and tracking outbound orders. It includes key details.
## Properties
| Name | Type | Description |
| --- | --- | --- |
| AccountCode | string | Gets or sets the \[Account\] code. The account code for the outbound order (An account is each of a company´s delivery points). Example: "ACC\_NIKE\_01025" |
| AccountId | Guid? | Gets or sets the \[Account\] identifier. The account identifier for the outbound order (An account is each of a company´s delivery points). Example: 85f988c3-t2f4-44fc-bd2d-ca3db667812t. |
| Address | [Address](https://msscc.mecalux.com/documentation/Development/master/ES/apis/easywms/Domain/Address.md) | Gets or sets the \[Address\]. The delivery address for the outbound order - Represents a physical address with properties (Street, number, door, floor, letter, city, neighborhood, country, zipcode and comments about the address). |
| AgencyCode | string | Gets or sets the \[Agency\] code. The carrier code for the outbound order (Transport agency code. There must be the transport agency code). Example: "SEUR" |
| AgencyId | Guid? | Gets or sets the \[Agency\] identifier. The carrier identifier for the outbound order (Transport agency code. There must be the transport agency code). Example: 15f9e8c3-g2f4-44fc-bd2d-ca3db667617y. |
| AllowDynamicReplenishment | bool | Indicates whether the outbound order allows dynamic replenishment. If allows dynamic replenishment (true). Otherwise (false). Example: false |
| AnyOutboundLineInWorkWave | bool | Flag that indicates if any of it's line is associated to a wave. Example: false |
| AnyStockShipped | bool | Indicates if the order has any stock shipped. Rule to get: Count all of the outbound order lines with the identifier of the outbound order, if the order has lines with quantity shipped or quantity shipped substitute greater than zero (true); otherwise (false) Example: true |
| AssignedUser | string | The user for whom the outbound order is assigned. Example: "mecalux001" |
| AutoCreated | bool | \[CANNOT BE USED\] Indicates if the outbound order has been auto created. Currently it is always `false`. |
| AutoRelease | bool | Indicates whether the outbound order should be auto released. If the outbound order should be auto released (true). Otherwise (false). Example: true |
| AutoReleaseDate | DateTime? | Indicates when the outbound order will be automatically released if the auto release flag is set to true. Example: 03/25/2025 0:00:00. |
| AutoReserve | bool | \[NOT IMPLEMENTED\] Indicates if the outbound order should reserve stock automatically. |
| AutoReserveDate | DateTime? | \[NOT IMPLEMENTED\] Indicates when the outbound order will be automatically reserved if the auto reserve flag is set to `true` |
| CalculatedNumAdjustStockIssues | long | Gets or sets the total number of adjust stock issues. |
| CalculatedNumCommunicationErrorIssues | long | Gets or sets the total number of communication error issues. |
| CalculatedNumExpiredDatesIssues | long | Gets or sets the total number of expired dates issues. |
| CalculatedNumNeedReplenishmentIssues | long | Gets or sets the total number of need replenishment issues. |
| ClientContainerTypeCode | string | The favourite container type code for picking. Example: "American". |
| ClientContainerTypeId | Guid? | The favourite container type identifier for picking. Example: 75f828c3-f2f4-44fc-bd2d-ca3db667896r. |
| Code | string | **Required**
It is a unique code that identifies the outbound order. Example: "ORDER\_SU1". |
| CreationDateOnERP | DateTime? | The ERP creation date for the outbound order. Example: 01/25/2024 0:00:00. |
| CurrentOperation | string | The operation for the outbound order. The possible values are the ones in [OutboundOrderOperation](https://msscc.mecalux.com/documentation/Development/master/ES/apis/easywms/Domain/OutboundOrderOperation.md). Example: "Releasing" |
| CustomAttribute | CustomAttribute | Gets or sets the \[Common.Domain.CustomAttribute\]. The outbound order's custom attributes. |
| DeliveryInstruction | string | \[USED IN MULTI-CARRIER SHIPPING APP\] The delivery instructions Example: "Ensure the package is not exposed to direct sunlight or rain". |
| Description | string | It is a description of the outbound order. Example: "Includes special packaging". |
| Document | string | The outbound order document Example: "DOC01". |
| ERPCode | string | The ERP code for the outbound order. Example: "ERP\_US\_001" |
| ExpectedDockStationCode | string | The expected dock station code for the outbound order. (A dock is an area in the warehouse where trucks (or any other vehicle used to transport goods). Example: "DOCK\_01". |
| ExpectedDockStationId | Guid? | The expected dock station identifier for the outbound order. (A dock is an area in the warehouse where trucks (or any other vehicle used transport goods). Example: 88f828c3-f334-44fc-bd2d-fa3db667896t. |
| FirstTroubleDate | DateTime? | The first trouble date. It is null if no trouble has occurred. Example: 02/25/2025 0:00:00. |
| FollowSequence | bool | Indicates whether the line number should be used as a sequencing method for the preparation. If the line number should be used as a sequencing method for the preparation (true). Otherwise (false). Example: false |
| GroupType | string | The group type of the outbound order. The possible values are the ones in [GroupType](https://msscc.mecalux.com/documentation/Development/master/ES/apis/easywms/Domain/GroupType.md) + an empty string when it is not a group. Example: Merge |
| HasTroubles | bool | Indicates whether the outbound order has troubles. Example: true. |
| IncompleteOrder | bool | Indicates whether the outbound order is incomplete (An incomplete order is when a order is not fully fulfilled, missing items, incorrect items, or partial delivery). Example: false. |
| InternalInfo | InternalInfo | Gets or sets the \[Application.Common.Domain.InternalInfo\]. The internal system information of the outbound order. |
| IsActive | bool | Indicates if the outbound order is active in the system Example: true. |
| IsSingleUnit | bool | Indicates if the outbound order is single unit. Example: true. |
| IsTenseFlow | bool | Indicates if the order is tense flow (Tense Flow is strategy that synchronizes the arrival of goods at the warehouse with their departure for distribution, reducing storage needs and costs while improving operational efficiency). Example: true |
| LoadDockStageLocationCode | string | The actual dock stage location code for the outbound order. Example: "DOCK\_03". |
| LoadDockStageLocationId | Guid? | The actual dock stage location identifier for the outbound order. Example: 09f828c3-g334-44fc-bd2d-fa3db667885g. |
| LoadDockStationCode | string | The actual dock station code for the outbound order. (A dock is an area in the warehouse where truck (or any other vehicle user to transport goods). Example: "DOCK\_02". |
| LoadDockStationId | Guid? | The actual dock station identifier for the outbound order. (A dock is an area in the warehouse where truck (or any other vehicle user to transport goods). Example: 19f828c3-g334-44fc-bd2d-fa3db667896f. |
| ManualEquipmentCode | string | The code of the manual equipment assigned to the outbound order. (It is managed by an operator using a radio frequency terminal. Manual equipment includes pallet, trucks, forklifts etc.). Example: "EQST\_PTL\_ONLYSTOCK\_01" |
| ManualEquipmentId | Guid? | The identifier of the manual equipment assigned to the outbound order. (It is managed by an operator using a radio frequency terminal. Manual equipment includes pallet, trucks, forklifts etc.). Example: 41f838c3-h354-442c-bd2d-fa32b667913y. |
| MinSupplyPercent | long | \[NOT IMPLEMENTED\] The minimum percentage of storaged stock necessary to supply stock to the order. |
| NumCancelledLines | long | The number of outbound lines from the order that are in cancelled status. |
| NumClientContainers | long | The number of client containers for the outbound order. |
| NumClosedLines | long | The number of outbound lines from the order that are in closed status. |
| NumCutInProcessTasks | long | The number of cut in process task for the outbound order. |
| NumCutPendingTasks | long | The number of cut pending task for the outbound order. |
| NumLoads | long | The number of loads for the outbound order. Example: 1 |
| NumOutboundOrderChildren | long | The number of outbound order children for the group or merge. |
| NumOutboundOrderLineDetails | long | The number of outbound order line details for the outbound order. Example: 2 |
| NumOutboundOrderLines | long | **Obsolete("Use NumTotalOutboundOrderLines")**
The number of outbound lines for the outbound order. Example: 2 |
| NumPendingTasks | long | The number of pending tasks for the outbound order. |
| NumPreparedTasks | long | The number of prepared tasks for the outbound order. |
| NumReleasedLines | long | The number of outbound lines from the order that are in released status. |
| NumStockLines | long | The number of stock lines for the outbound order. |
| NumStoppedLines | long | The number of outbound lines from the order that are stopped when the whole order is being stopped. |
| NumTotalOutboundOrderLines | long | The total number of outbound order lines. Rule to calculate: Count of all outbound order lines with the identifier of the outbound order and outbound order status different of the ´Cancelled´. Example: 5 |
| NumWavedLines | long | **Obsolete("Waves are no longer supported. Use WorkWaves")**
\[OBSOLETE\] The number of outbound lines from the outbound order included in a wave. |
| NumWaves | long | **Obsolete("Waves are no longer supported. Use WorkWaves")**
\[OBSOLETE\] The number of waves where the outbound order is included. |
| OrderWeight | decimal | **Obsolete("This field is no longer maintained. Please perform the necessary query to obtain the value")**
\[OBSOLETE\] The outbound order weight. |
| OutboundClassCode | string | Gets or sets the \[OutboundClass\] code. The outbound class code for the outbound order (The outbound class is a classification of the routes and shipping orders). Example: "SHIPPING\_GROUP\_01" |
| OutboundClassId | Guid? | Gets or sets the \[OutboundClass\] identifier. The outbound class identifier for the outbound order (The outbound class is a classification of the routes and shipping orders). Example: 08d877b3-t2e4-44fc-cd2d-ca3db667515t. |
| OutboundIssueCodes | string | Gets or sets the issues. |
| OutboundOrderGroupMasterCode | string | Gets or sets the \[OutboundOrderGroup\] code. The code of the master outbound order for the group the outbound order is part of. It is part of none, it is an empty string. Example: "GROUPING\_01". |
| OutboundOrderGroupMasterId | Guid? | Gets or sets the \[OutboundOrderGroup\] identifier. The identifier of the master outbound order for the group the outbound order is part of. It is part of none, it is null. Example: 41f838c3-h354-442c-bd2d-fa32b667913y. |
| OutboundOrderSituation | string | \[CANNOT BE USED\] The outbound order situation. |
| OutboundOrderStatus | string | **Required**
The outbound order's status. The possible values are the ones in [OutboundOrderStatus](https://msscc.mecalux.com/documentation/Development/master/ES/apis/easywms/Domain/OutboundOrderStatus.md). Example: "Creating" |
| OutboundType | string | The outbound order's outbound type. The possible values are the ones in [OutboundType](https://msscc.mecalux.com/documentation/Development/master/ES/apis/easywms/Domain/OutboundType.md). Example: "Customer" |
| OwnerCode | string | Gets or sets the \[Owner\] code. The owner code for the outbound order. Example: "VW\_02". |
| OwnerId | Guid? | Gets or sets the \[Owner\] identifier. The owner identifier for the outbound order. Example: 65f828c3-t2f4-44fc-bd2d-ca3db667896g. |
| PackagingLocationCode | string | The packaging location code for the outbound order. Example: "UDISTRIBUTION\_UAP\_02". |
| PackagingLocationId | Guid? | The packaging location identifier for the outbound order. Example: 39f838c3-g354-442c-bd2d-fa32b667882g. |
| PaperPick | bool | Indicates whether the outbound order will be prepared using paper pick. (Paper picking is a process by which the operator performs the picking manually, without using the RF terminal or the work station.) Example: true. |
| PartialClosedSequence | long? | The last partial closed sequence. Example: 3 |
| PendingReceipt | bool | Indicates whether the outbound order is waiting for receiving an ASN container. True if it is waiting for an ASN to be received or otherwise is false. Example: false. |
| Planning | [Planning](https://msscc.mecalux.com/documentation/Development/master/ES/apis/easywms/Domain/Planning.md) | Gets or sets the \[Planning\]. The outbound order planning information. |
| PrepackagingLogic | string | The pre-packaging logic for the outbound order. The possible values are the ones in [PrepackagingLogic](https://msscc.mecalux.com/documentation/Development/master/ES/apis/easywms/Domain/PrepackagingLogic.md). Example: MinimumParcels |
| PrepackagingProcess | string | The pre-packaging type for the outbound order. The possible values are the ones in [PrepackagingProcess](https://msscc.mecalux.com/documentation/Development/master/ES/apis/easywms/Domain/PrepackagingProcess.md). Example: Preparation |
| PrepackagingType | string | The pre-packaging type for the outbound order. The possible values are the ones in [PrepackagingType](https://msscc.mecalux.com/documentation/Development/master/ES/apis/easywms/Domain/PrepackagingType.md). Example: ERPInformed |
| PrepackagingWorkingMode | string | The pre-packaging working mode for the outbound order. The possible values are the ones in [PrepackagingWorkingMode](https://msscc.mecalux.com/documentation/Development/master/ES/apis/easywms/Domain/PrepackagingWorkingMode.md). Example: Strict |
| Priority | string | The outbound order priority. The possible values are the ones in [Priority](https://msscc.mecalux.com/documentation/Development/master/ES/apis/easywms/Domain/Priority.md). Example: "Urgent". |
| RealReleaseDate | DateTime? | Indicates the first release date for the outbound order. Example: 03/25/2025 0:00:00. |
| ReceivedFromERP | bool | Indicates whether the outbound order was received from the ERP. If received from the ERP (true). Otherwise (false). Example: true |
| ReleasedBy | string | The first user who released the outbound order. Example: "mecalux567" |
| RequiredClientContainerTypeCode | string | Gets or sets the \[ContainerType\] code. The required client container type code for picking. Example: "EuroPallet". |
| RequiredClientContainerTypeId | Guid? | Gets or sets the \[ContainerType\] identifier. The required client container type identifier for picking. Example: 85f828c3-t2f4-44fc-bd2d-ca3db667896t. |
| RouteCode | string | Gets or sets the \[Route\] code. The route code for the outbound order. Example: "NORTHERN\_ROUTE". |
| RouteId | Guid? | Gets or sets the \[Route\] identifier. The route identifier for the outbound order. Example: 48d877b3-c2e4-44fc-bd2d-ca3db667515c. |
| ShipExpiredStock | bool | Indicates if the outbound order shipped products with expired dates. |
| ShipmentTemplateCode | string | Gets or sets the \[ShipmentTemplate\] code. The code of the shipment template used in the outbound order. Example: "ST\_SINGLE\_UNIT" |
| ShipmentTemplateId | Guid? | Gets or sets the \[ShipmentTemplate\] identifier. The identifier of the shipment template used in the outbound order. Example: 21f838c3-e354-442c-td2d-ha32b667915u. |
| ShippingDeadline | DateTime? | Gets the shipping deadline. |
| ShippingDeadlineManuallySet | bool | Indicates whether the shipping deadline was received from the assistant. If received from the assistant (true). Otherwise (false). |
| ShipReq | [ShippingRequirements](https://msscc.mecalux.com/documentation/Development/master/ES/apis/easywms/Domain/ShippingRequirements.md) | Gets or sets the \[ShippingRequirements\]. The outbound order shipping requirements. |
| SkipTroubledTasks | bool | The flag to skip troubled tasks. When `true`, troubled tasks will be skipped. When `false`, they will block the execution of the following tasks. Example: true |
| Source | string | The outbound order source. Example: "L10". |
| SubwarehouseCode | string | Gets or sets the \[Subwarehouse\] code. The subwarehouse code for the outbound order (Subwarehouses is grouping locations to build rules that apply over a specific warehouse area or locations subset). Example: "ST\_SINGLE\_UNIT" |
| SubwarehouseId | Guid? | Gets or sets the \[Subwarehouse\] identifier. The subwarehouse identifier for the outbound order (Subwarehouses is grouping locations to build rules that apply over a specific warehouse area or locations subset). Example: 32f838c3-ef54-442c-td2d-ha32b6679r5g. |
| SupplierCode | string | Gets or sets the \[Supplier\] code. The supplier code for the outbound order (A supplier is a company or entity that supplies stock to the node). Example: "NIKE" |
| SupplierId | Guid? | Gets or sets the \[Supplier\] identifier. The supplier identifier for the outbound order (A supplier is a company or entity that supplies stock to the node). Example: 25f9e8c3-g2f4-45fc-bd2d-ca3db667718u. |
| TenseFlowStageCode | string | The tense flow stage code (Tense Flow is strategy that synchronizes the arrival of goods at the warehouse with their departure for distribution, reducing storage needs and costs while improving operational efficiency). Example: "STG\_01" |
| TenseFlowStageId | Guid? | The tense flow stage identifier (Tense Flow is strategy that synchronizes the arrival of goods at the warehouse with their departure for distribution, reducing storage needs and costs while improving operational efficiency). Example: 35f838c3-fg54-542c-td2d-ha32b667190t. |
| Transport | [OutboundOrderTransport](https://msscc.mecalux.com/documentation/Development/master/ES/apis/easywms/Domain/OutboundOrderTransport.md) | Gets or sets the \[OutboundOrderTransport\]. The outbound order transport information. |
| ValidDate | DateTime? | \[NOT IMPLEMENTED\] The outbound order valid date. |
| WarehouseCode | string | **Required**
Gets or sets the \[Warehouse\] code. The warehouse code for the outbound order. Example: "KITDEMO\_4". |
| WarehouseId | Guid | Gets or sets the \[Warehouse\] identifier. The warehouse identifier for the outbound order. Example: 07d877b3-c2e4-44fc-bd2d-ca3db667514a. |
| WarehouseToCode | string | Gets or sets the \[Warehouse\] code. The destination warehouse code for the outbound order. Example: "WH\_STORE\_01". |
| WarehouseToId | Guid? | Gets or sets the \[Warehouse\] identifier. The destination warehouse identifier for the outbound order. Example: 18d877b3-c2e4-44fc-bd2d-ca3db667515b. |
| WorkOrderCode | string | Gets or sets the \[WorkOrder\] code. The work order code for the outbound order (assembly or disassembly of kit). Example: "WORK\_ORDER\_ASSEMBLY\_01" |
| WorkOrderId | Guid? | Gets or sets the \[WorkOrder\] identifier. The work order identifier for the outbound order (assembly or disassembly of kit). Example: 78d877b3-f2e4-44fc-bd2d-ca3db667515g. |
| WorkWaveCode | string | Gets or sets the \[WorkWave\] code. The code of the wave to which the outbound order is associated. Example: "Wave\_A" |
| WorkWaveId | Guid? | Gets or sets the \[WorkWave\] identifier. Identifier used to track and manage wave. Example: 12f838c3-ff54-542c-td2d-ha32b6679r5t. |
If you have any suggestion or comment on this documentation, please submit it to [documentation@mecalux.com](mailto:documentation@mecalux.com)
@@ -1,32 +0,0 @@
## OutboundOrderStatus
Full name: **Mecalux.ITSW.EasyWMS.Modules.Common.Domain.OutboundOrderStatus**
## Summary
Represents the status of outbound orders.
## Values
| Name | Value | Description |
| --- | --- | --- |
| Creating | 0 | The outbound order is being created. this will occur when they are created by the ERP or manually through the user interface. |
| Waiting | 1 | The outbound order is waiting to be released. this will occur when the order is correctly received from the ERP or when it's creation is finished manually through the user interface. |
| Release | 2 | The outbound order is release and it's tasks may be generated. An order is released when it is released through the user interface, when it's auto release date is reached, when it is associated with a route with an auto release date which has been reached or when all the lines from the outbound order has been manually released. |
| PartiallyRelease | 3 | The outbound order is partially release and it's tasks may be generated. An order is partially released when one or more lines from the order, always less than it's total lines, has been released. |
| Working | 4 | The outbound order is working. The order will be in this status from the beggining to the end of it's preparation process. To be in this status, one of it's generated tasks must be "In Process" status. |
| PartiallyWorking | 5 | The outbound order is partially working. The order will be in this status from the beggining to the end of it's preparation process. Only the orders with the status "PartiallyRelease" can change to this status. To be in this status, one of it's generated tasks must be "In Process" status. |
| Pausing | 6 | The outbound order is being paused. Not used. |
| Paused | 7 | The outbound order was paused. All it's work has been halted and it's tasks will be cancelled. This can occur when the order has been paused from the user interface. |
| Cancelling | 8 | The outbound order is being cancelled. Not used. |
| Cancelled | 9 | The outbound order was cancelled. All it's work will be halted, it's tasks will be cancelled and the assigned stock to the order will be unassigned |
| Closing | 10 | The outbound order is being closed. Not used. |
| Closed | 11 | The outbound order was closed. The preparation process of it's lines has been finished and the order is ready to ship or already shipped. This will occur when a user close the order through the user interface or when the system is configured to auto close al prepared orders. Also, the order will be archived if the system is configured as well to auto archive closed orders. |
| Expired | 12 | The outbound order is outdated. The outbound order was not released before the valid date. Not used. |
| StockFailure | 13 | The outbound order has all theirs lines on stock failure. An order will be in this status when it has been released and all of it's lines couldn't be assigned |
| Merged | 14 | The outbound order was merged. The orders in this status has been fused through the user interface. |
| Grouped | 15 | The outbound order was grouped. The orders in this status has been grouped through the user interface or automatically from a shipment template configured by the user. |
| Secured | 16 | The outbound order has its stock secured. The orders in this status has been secured through the user interface and has a secured quantity. |
| Assigned | 17 | The outbound order has its stock assigned. The orders in this status has been assigned through the user interface and has a assigned quantity. |
If you have any suggestion or comment on this documentation, please submit it to [documentation@mecalux.com](mailto:documentation@mecalux.com)
+1998 -6
View File
File diff suppressed because it is too large Load Diff
+14 -5
View File
@@ -1,17 +1,21 @@
{ {
"name": "wms-mcp-server", "name": "wms-mcp-server",
"version": "1.0.0", "version": "1.0.0",
"description": "MCP Server for WMS debugging and analysis", "description": "Serveur MCP pour le debug et l analyse d un WMS EasyWMS (100% API)",
"main": "src/index.js", "main": "src/index.js",
"bin": { "bin": {
"wms-mcp-server": "./src/index.js" "wms-mcp-server": "./src/index.js"
}, },
"scripts": { "scripts": {
"start": "node src/index.js", "start": "node src/index.js",
"test": "node test-connection.js", "test": "node scripts/test-connection.js",
"build": "pkg . --targets node18-win-x64 --output dist/wms-mcp-server.exe" "build": "pkg . --targets node22-win-x64 --output dist/wms-mcp-server.exe"
}, },
"keywords": ["mcp", "wms", "debugging"], "keywords": [
"mcp",
"wms",
"debugging"
],
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"type": "commonjs", "type": "commonjs",
@@ -24,7 +28,12 @@
"assets": [ "assets": [
"node_modules/@modelcontextprotocol/**/*" "node_modules/@modelcontextprotocol/**/*"
], ],
"targets": ["node18-win-x64"], "targets": [
"node22-win-x64"
],
"outputPath": "dist" "outputPath": "dist"
},
"devDependencies": {
"@yao-pkg/pkg": "^6.22.0"
} }
} }
+22 -7
View File
@@ -1,15 +1,30 @@
# AD API Testing Script # AD API Testing Script
# Tests each Application Dictionary element type endpoint # Tests each Application Dictionary element type endpoint
# Configuration # Configuration - aucun credential en dur : passer par les parametres ou l'environnement.
$baseUrl = "https://localhost" # Exemple : . est-ad-api.ps1 -Host 10.255.255.2 -Username mecalux -Password '***' -Tenant AD
param(
[string]$WmsHost = $(if ($env:WMS_HOST) { $env:WMS_HOST } else { "localhost" }),
[string]$Username = $env:WMS_USERNAME,
[string]$Password = $env:WMS_PASSWORD,
[string]$Tenant = $(if ($env:WMS_TENANT) { $env:WMS_TENANT } else { "AD" }),
[string]$Auth = $(if ($env:WMS_API_AUTH) { $env:WMS_API_AUTH } else { "Basic R05BOklFNGU3aXFoZHQ=" }),
[string]$Application = "EasyWMS"
)
if (-not $Username -or -not $Password) {
Write-Host "Username / Password manquants. Passez -Username / -Password ou definissez WMS_USERNAME / WMS_PASSWORD." -ForegroundColor Red
exit 1
}
$baseUrl = "https://$WmsHost"
$tokenUrl = "$baseUrl/EasySTS/OAuth/Token" $tokenUrl = "$baseUrl/EasySTS/OAuth/Token"
$adApiBase = "$baseUrl/AD/api" $adApiBase = "$baseUrl/AD/api"
$auth = "Basic R05BOklFNGU3aXFoZHQ=" $auth = $Auth
$username = "mecalux" $username = $Username
$password = "ANDREZmlx6" $password = $Password
$tenant = "AD" $tenant = $Tenant
$application = "EasyWMS" $application = $Application
# Skip SSL certificate validation (self-signed cert) # Skip SSL certificate validation (self-signed cert)
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env node
/**
* Smoke test de connectivite WMS.
*
* npm test -> teste le profil DEFAULT_WMS_PROFILE
* npm test -- LIMAGRAIN -> teste le profil nomme
* npm test -- --all -> teste tous les profils declares dans WMS_PROFILES
*
* Verifie, pour chaque profil : chargement du profil, OAuth, QueryExecute,
* QueryScalarExecute et l'API AD. N'ecrit rien dans le WMS.
*/
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
const profileManager = require('../src/config/profile-manager');
const apiService = require('../src/services/api-service').getInstance();
function ok(label, detail) {
console.log(` OK ${label}${detail ? ` - ${detail}` : ''}`);
}
function ko(label, error) {
console.log(` FAIL ${label} - ${error.message}`);
}
async function step(label, fn) {
try {
ok(label, await fn());
return true;
} catch (error) {
ko(label, error);
return false;
}
}
async function testProfile(name) {
console.log(`\n=== Profil ${name} ===`);
profileManager.switchTo(name);
const profile = profileManager.getCurrent();
console.log(` host=${profile.host} tenant=${profile.tenant} saas=${profile.saas}`);
let passed = 0;
const total = 4;
if (await step('OAuth', async () => {
await apiService.authenticate();
return `token obtenu (age max ~${process.env.TOKEN_MAX_AGE || 1190}s)`;
})) passed++;
if (await step('QueryExecute', async () => {
const rows = await apiService.executeQuery('Context.Products.OrderBy(z => z.Id)', { take: 1 });
return `${Array.isArray(rows) ? rows.length : 0} ligne(s)`;
})) passed++;
if (await step('QueryScalarExecute', async () => {
const count = await apiService.executeScalarQuery('Context.Products.Count()');
return `${count} produit(s)`;
})) passed++;
if (await step('AD API (Validator)', async () => {
const res = await apiService.post('/Validator/GetByApplication',
[profile.application, profile.tenant, 10, 0], true);
return `${res?.entities?.length ?? 0} element(s)`;
})) passed++;
console.log(` -> ${passed}/${total} tests reussis`);
return passed === total;
}
async function main() {
profileManager.loadProfiles();
const available = profileManager.listProfiles();
if (available.length === 0) {
console.error('Aucun profil charge. Verifiez WMS_PROFILES et <NAME>_HOST/USERNAME/PASSWORD/TENANT dans .env');
process.exit(1);
}
const arg = process.argv[2];
let targets;
if (arg === '--all') {
targets = available;
} else if (arg) {
targets = [arg];
} else {
targets = [profileManager.getCurrentName() || available[0]];
}
let allOk = true;
for (const name of targets) {
try {
allOk = (await testProfile(name)) && allOk;
} catch (error) {
console.log(`\n=== Profil ${name} ===\n FAIL ${error.message}`);
allOk = false;
}
}
console.log(`\n${allOk ? 'Tous les profils testes sont operationnels.' : 'Au moins un test a echoue (voir ci-dessus).'}`);
process.exit(allOk ? 0 : 1);
}
main();
-83
View File
@@ -1,83 +0,0 @@
/**
* Constants for WMS MCP Server
*/
// WMS Entity Types available via Query API
// Based on queries api.php reference
const WMS_ENTITY_TYPES = [
// Master Data
'Products',
'Containers',
'Accounts',
'Suppliers',
'Kits',
'Aliases',
// Operations
'Tasks',
'Stocks',
'ProductLocations',
// Inbound
'InboundOrders',
'Receptions',
// Outbound
'OutboundOrders'
];
// Entity Categories for documentation
const ENTITY_CATEGORIES = {
'Master Data': ['Products', 'Containers', 'Accounts', 'Suppliers', 'Kits', 'Aliases'],
'Operations': ['Tasks', 'Stocks', 'ProductLocations'],
'Inbound': ['InboundOrders', 'Receptions'],
'Outbound': ['OutboundOrders']
};
// Common WMS Commands
// These can be used with call_command_api tool
const WMS_COMMANDS = {
'ProductRemove': 'Mecalux.ITSW.EasyWMS.Modules.MasterData.Contracts.Commands.ProductRemoveCommand',
'ProductUpdate': 'Mecalux.ITSW.EasyWMS.Modules.MasterData.Contracts.Commands.ProductUpdateCommand',
'ContainerCreate': 'Mecalux.ITSW.EasyWMS.Modules.MasterData.Contracts.Commands.ContainerCreateCommand',
'TaskCancel': 'Mecalux.ITSW.EasyWMS.Modules.Tasks.Contracts.Commands.TaskCancelCommand'
// Add more as needed
};
// MCP Resource URIs
const RESOURCE_URIS = {
WMS_ENTITIES: 'wms://entities',
ENTITY_SCHEMAS: 'wms://entity-schemas',
QUERY_EXAMPLES: 'wms://query-examples',
WORKFLOWS_OVERVIEW: 'workflows://overview',
WORKFLOWS_CATEGORIES: 'workflows://categories',
API_CATALOG: 'api://catalog',
LOGS_GUIDE: 'logs://guide'
};
// Log patterns for error detection
const LOG_ERROR_PATTERNS = [
'ERROR',
'EXCEPTION',
'FATAL',
'CRITICAL',
'FAILED',
'FAILURE',
'WARNING'
];
// Query limits
const QUERY_LIMITS = {
MAX_ROWS: parseInt(process.env.MAX_QUERY_ROWS) || 1000,
DEFAULT_LIMIT: 100,
TIMEOUT_MS: parseInt(process.env.QUERY_TIMEOUT) || 30000
};
module.exports = {
WMS_ENTITY_TYPES,
ENTITY_CATEGORIES,
WMS_COMMANDS,
RESOURCE_URIS,
LOG_ERROR_PATTERNS,
QUERY_LIMITS
};
+10 -3
View File
@@ -14,9 +14,16 @@ const path = require('path');
const originalStdoutWrite = process.stdout.write; const originalStdoutWrite = process.stdout.write;
process.stdout.write = process.stderr.write.bind(process.stderr); process.stdout.write = process.stderr.write.bind(process.stderr);
require('dotenv').config({ // Resolve .env:
path: path.join(__dirname, '..', '.env') // - packaged (.exe built with pkg): next to the executable, so the deployed
}); // server can be reconfigured without a rebuild and no credential is ever
// baked into the binary snapshot.
// - from sources: project root.
const ENV_PATH = process.pkg
? path.join(path.dirname(process.execPath), '.env')
: path.join(__dirname, '..', '.env');
require('dotenv').config({ path: ENV_PATH });
// Restore stdout // Restore stdout
process.stdout.write = originalStdoutWrite; process.stdout.write = originalStdoutWrite;
-193
View File
@@ -1,193 +0,0 @@
const fs = require('fs').promises;
const path = require('path');
/**
* Resources MCP pour la documentation
* Permet à Claude d'accéder à la documentation structurée en fichiers Markdown
*/
// Chemin vers le dossier de documentation
const DOCS_PATH = process.env.DOCS_PATH || path.join(__dirname, '..', '..', 'docs');
/**
* Scanne récursivement un dossier pour trouver tous les fichiers .md
* @param {string} dir - Dossier à scanner
* @param {string} baseDir - Dossier de base pour les chemins relatifs
* @returns {Promise<Array>} - Liste des fichiers .md
*/
async function scanMarkdownFiles(dir, baseDir = dir) {
let files = [];
try {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
// Récursion dans les sous-dossiers
const subFiles = await scanMarkdownFiles(fullPath, baseDir);
files = files.concat(subFiles);
} else if (entry.isFile() && entry.name.endsWith('.md')) {
// Fichier Markdown trouvé
const relativePath = path.relative(baseDir, fullPath);
files.push({
name: entry.name,
path: fullPath,
relativePath: relativePath.replace(/\\/g, '/'), // Normaliser les slashes
uri: `docs://${relativePath.replace(/\\/g, '/')}`,
});
}
}
} catch (err) {
// Dossier n'existe pas ou erreur de lecture
console.error(`Error scanning directory ${dir}:`, err.message);
}
return files;
}
/**
* Liste les resources disponibles pour la documentation
* @returns {Promise<Array>} - Liste des resources
*/
async function listResources() {
try {
// Scanner les fichiers Markdown
const mdFiles = await scanMarkdownFiles(DOCS_PATH);
const resources = [
{
uri: 'docs://index',
name: 'Documentation Index',
description: 'Sommaire de toute la documentation disponible',
mimeType: 'text/markdown',
},
];
// Ajouter chaque fichier .md comme resource
mdFiles.forEach((file) => {
resources.push({
uri: file.uri,
name: file.name.replace('.md', ''),
description: `Documentation: ${file.relativePath}`,
mimeType: 'text/markdown',
});
});
return resources;
} catch (err) {
console.error('Error listing documentation resources:', err);
return [
{
uri: 'docs://index',
name: 'Documentation Index',
description: 'Sommaire de toute la documentation disponible',
mimeType: 'text/markdown',
},
];
}
}
/**
* Génère un index/sommaire de la documentation
* @returns {Promise<string>} - Markdown avec le sommaire
*/
async function generateIndex() {
try {
const mdFiles = await scanMarkdownFiles(DOCS_PATH);
if (mdFiles.length === 0) {
return `# Documentation\n\n*Aucun fichier de documentation trouvé dans \`${DOCS_PATH}\`*\n\n` +
`Pour ajouter de la documentation :\n` +
`1. Créez un dossier \`docs\` à la racine du projet\n` +
`2. Ajoutez vos fichiers .md (organisation libre avec sous-dossiers)\n` +
`3. Redémarrez le serveur MCP\n`;
}
let markdown = '# Documentation WMS\n\n';
markdown += `**${mdFiles.length} fichiers de documentation disponibles**\n\n`;
markdown += `📁 Emplacement : \`${DOCS_PATH}\`\n\n`;
// Grouper par dossier
const grouped = {};
mdFiles.forEach((file) => {
const dir = path.dirname(file.relativePath);
const folder = dir === '.' ? '📄 Racine' : `📁 ${dir}`;
if (!grouped[folder]) {
grouped[folder] = [];
}
grouped[folder].push(file);
});
// Générer le sommaire
markdown += '## Sommaire\n\n';
Object.keys(grouped).sort().forEach((folder) => {
markdown += `### ${folder}\n\n`;
grouped[folder].forEach((file) => {
markdown += `- **${file.name.replace('.md', '')}** - \`${file.uri}\`\n`;
});
markdown += '\n';
});
markdown += '---\n\n';
markdown += '*Pour lire un fichier, demandez à Claude de lire la resource correspondante (par exemple: "Lis la documentation X")*\n';
return markdown;
} catch (err) {
return `# Erreur\n\nImpossible de générer l'index de documentation: ${err.message}`;
}
}
/**
* Lit un fichier de documentation
* @param {string} relativePath - Chemin relatif du fichier
* @returns {Promise<string>} - Contenu Markdown du fichier
*/
async function readDocFile(relativePath) {
try {
const filePath = path.join(DOCS_PATH, relativePath);
const content = await fs.readFile(filePath, 'utf8');
return content;
} catch (err) {
return `# Erreur\n\nImpossible de lire le fichier \`${relativePath}\`: ${err.message}`;
}
}
/**
* Lit une resource documentation selon son URI
* @param {string} uri - URI de la resource (format: docs://path/to/file.md)
* @returns {Promise<Object>} - Contenu de la resource
*/
async function readResource(uri) {
let content;
if (uri === 'docs://index') {
content = await generateIndex();
} else if (uri.startsWith('docs://')) {
// Extraire le chemin relatif de l'URI
const relativePath = uri.replace('docs://', '');
content = await readDocFile(relativePath);
} else {
throw new Error(`Unknown documentation resource: ${uri}`);
}
return {
contents: [
{
uri,
mimeType: 'text/markdown',
text: content,
},
],
};
}
module.exports = {
listResources,
readResource,
};
+4 -109
View File
@@ -20,8 +20,6 @@ const DEFAULT_LOG_PATHS = [
'\\\\{host}\\ProgramData\\Mecalux\\ETLLogs', '\\\\{host}\\ProgramData\\Mecalux\\ETLLogs',
]; ];
const LOG_FILE_PATTERN = process.env.LOG_FILE_PATTERN || '*.log';
/** /**
* Retourne les chemins de logs à scanner pour le profil actif. * Retourne les chemins de logs à scanner pour le profil actif.
* Substitue {host} par l'hostname du profil. * Substitue {host} par l'hostname du profil.
@@ -69,13 +67,13 @@ async function scanLogsRecursively(dir, fileList = []) {
}); });
} catch (statErr) { } catch (statErr) {
// Ignorer les fichiers inaccessibles // Ignorer les fichiers inaccessibles
console.error(`Cannot access file ${fullPath}: ${statErr.message}`); console.error(`[Logs] Cannot access file ${fullPath}: ${statErr.message}`);
} }
} }
} }
} catch (err) { } catch (err) {
// Ne pas planter si un dossier n'existe pas ou n'est pas accessible // Ne pas planter si un dossier n'existe pas ou n'est pas accessible
console.error(`Cannot scan directory ${dir}: ${err.message}`); console.error(`[Logs] Cannot scan directory ${dir}: ${err.message}`);
} }
return fileList; return fileList;
@@ -163,7 +161,7 @@ async function resolveLogFilePath(logFile) {
try { try {
subEntries = await fs.readdir(basePath, { withFileTypes: true }); subEntries = await fs.readdir(basePath, { withFileTypes: true });
} catch (err) { } catch (err) {
console.error(`Cannot read base log path ${basePath}: ${err.message}`); console.error(`[Logs] Cannot read base log path ${basePath}: ${err.message}`);
} }
const subDirs = subEntries.filter(e => e.isDirectory()).map(e => e.name); const subDirs = subEntries.filter(e => e.isDirectory()).map(e => e.name);
@@ -286,7 +284,7 @@ async function searchLogs(keyword, maxResults = 50, contextLines = 2) {
} }
} catch (readErr) { } catch (readErr) {
// Ignorer les fichiers illisibles // Ignorer les fichiers illisibles
console.error(`Cannot read file ${file.path}: ${readErr.message}`); console.error(`[Logs] Cannot read file ${file.path}: ${readErr.message}`);
} }
} }
@@ -301,112 +299,9 @@ async function searchLogs(keyword, maxResults = 50, contextLines = 2) {
} }
} }
/**
* Recherche des erreurs dans les logs récents
* @param {number} maxResults - Nombre maximum de résultats
* @returns {Promise<Object>} - Erreurs trouvées
*/
async function findRecentErrors(maxResults = 20) {
const errorPatterns = ['error', 'exception', 'failed', 'fatal', 'critical'];
const allErrors = [];
try {
for (const pattern of errorPatterns) {
if (allErrors.length >= maxResults) break;
const results = await searchLogs(pattern, maxResults - allErrors.length, 1);
allErrors.push(...results.results);
}
// Dédupliquer par numéro de ligne et fichier
const unique = allErrors.filter(
(error, index, self) =>
index ===
self.findIndex(
(e) => e.fullPath === error.fullPath && e.lineNumber === error.lineNumber
)
);
return {
totalErrors: unique.length,
errors: unique.slice(0, maxResults),
};
} catch (err) {
throw new Error(`Failed to find errors: ${err.message}`);
}
}
/**
* Lit tout le contenu d'un fichier de log spécifique
* @param {string} logFilePath - Chemin complet du fichier
* @returns {Promise<Object>} - Contenu du fichier
*/
async function readFullLog(logFilePath) {
try {
const content = await fs.readFile(logFilePath, 'utf8');
const lines = content.split('\n').filter((line) => line.trim() !== '');
return {
file: path.basename(logFilePath),
fullPath: logFilePath,
totalLines: lines.length,
content: lines,
};
} catch (err) {
throw new Error(`Failed to read log file: ${err.message}`);
}
}
/**
* Obtient des statistiques sur les logs
* @returns {Promise<Object>} - Statistiques
*/
async function getLogStats() {
try {
const files = await listLogFiles();
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
// Grouper par dossier
const byDirectory = {};
files.forEach(file => {
const dir = file.directory;
if (!byDirectory[dir]) {
byDirectory[dir] = {
directory: dir,
count: 0,
totalSize: 0,
files: [],
};
}
byDirectory[dir].count++;
byDirectory[dir].totalSize += file.size;
byDirectory[dir].files.push({
name: file.name,
sizeMB: (file.size / (1024 * 1024)).toFixed(2),
modified: file.modified.toISOString(),
});
});
return {
configuredPaths: getLogPaths(),
totalFiles: files.length,
totalSize,
totalSizeMB: (totalSize / (1024 * 1024)).toFixed(2),
oldestFile: files[files.length - 1]?.name,
newestFile: files[0]?.name,
byDirectory: Object.values(byDirectory).sort((a, b) => b.count - a.count),
};
} catch (err) {
throw new Error(`Failed to get log stats: ${err.message}`);
}
}
module.exports = { module.exports = {
listLogFiles, listLogFiles,
findLatestLogFile, findLatestLogFile,
readRecentLogs, readRecentLogs,
searchLogs, searchLogs,
findRecentErrors,
readFullLog,
getLogStats,
}; };
-1
View File
@@ -4,7 +4,6 @@
*/ */
const apiService = require('./api-service').getInstance(); const apiService = require('./api-service').getInstance();
const constants = require('../config/constants');
/** /**
* Build a LINQ select expression * Build a LINQ select expression
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff