From 0ff44f7b78e0d1f24dbcbfc8681eaa8c2ef2ee4f Mon Sep 17 00:00:00 2001 From: Arthur Ria Date: Mon, 24 Aug 2026 15:15:31 +0200 Subject: [PATCH] Documentation : structure README / CLAUDE / DECISIONS / MONITORING MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CLAUDE.md | 1065 +++++++++------------------------------ DECISIONS.md | 344 +++++++++++++ MONITORING.md | 220 ++++++++ README.md | 152 ++++++ docs/README.md | 68 +-- docs/getting_started.md | 6 + docs/logs.md | 165 ++++++ 7 files changed, 1152 insertions(+), 868 deletions(-) create mode 100644 DECISIONS.md create mode 100644 MONITORING.md create mode 100644 README.md create mode 100644 docs/logs.md diff --git a/CLAUDE.md b/CLAUDE.md index 93e9852..0be7e13 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,890 +1,283 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +Guide pour Claude Code (claude.ai/code) sur ce dépôt. -## Project Overview +**Avant de modifier quoi que ce soit, lisez [DECISIONS.md](DECISIONS.md).** Il +consigne les choix d'architecture et les pièges vérifiés sur un WMS réel. La +plupart des comportements qui semblent bizarres y sont expliqués et sont +volontaires ; les références `D1`, `D2`… de ce fichier y renvoient. -**Goal:** Create an MCP (Model Context Protocol) server that allows Claude to interact with a WMS (Warehouse Management System) for debugging and analysis purposes. +| Besoin | Fichier | +|---|---| +| Installer, lancer, brancher Claude Desktop | [README.md](README.md) | +| Pourquoi le code est ainsi, pièges terrain | [DECISIONS.md](DECISIONS.md) | +| Le serveur ne répond pas, lire ses logs | [MONITORING.md](MONITORING.md) | +| Accéder aux logs du WMS | [docs/logs.md](docs/logs.md) | +| Références EasyWMS (API, entités) | [docs/](docs/) | -**Current Status:** ✅ Implementation complete and functional - API-only architecture +--- -**Implementation Progress:** -- ✅ Project planning completed -- ✅ CLAUDE.md documentation created -- ✅ Node.js and npm installed on system -- ✅ Architecture finalized (100% API, no direct Oracle access) -- ✅ Services and tools implemented -- ✅ API connectivity tested and validated -- ✅ OAuth authentication working (with tenant_code fix) -- ✅ Workflow fetching operational (entities extraction fix) -- ✅ Application Dictionary API implemented (20 element types, 38,765 elements) -- ✅ AD API endpoints validated with curl (17/19 working, 2 removed) -- ✅ Claude Desktop integration successful +## Objet -**Required Information:** -- 🌐 **WMS API Configuration:** - - Server IP address: `10.255.255.2` - - Tenant code (configurable in .env) - - Username and password for API access -- 📁 **File Paths:** - - Log files directory path (e.g., `C:\WMS\Logs`) - - Deployment directory on VM (e.g., `C:\WMS\mcp`) +Serveur MCP donnant à Claude un accès en lecture à un WMS EasyWMS (Mecalux) +pour le debug et l'analyse. **Fonctionnel et en service.** -**Target Architecture:** -- **MCP Server:** Node.js executable running on Windows Server VM -- **Data Access:** 100% via WMS REST APIs (no direct Oracle connection) - - **Query API:** `/ApplicationService/api/QueryExecute` (LINQ queries) - - **Command API:** `/ApplicationService/api/CommandExecute` (WMS commands) - - **Workflow API:** `/AD/api/Workflow/GetByApplication` (workflows with pagination) - - **Application Dictionary API:** `/AD/api/{ElementType}/GetByApplication` (20 element types: Commands, Queries, Dialogs, Views, Entities, Events, etc.) -- **Authentication:** OAuth 2.0 with automatic token refresh -- **Connection:** Claude Desktop (PC) → Local or SSH → MCP Server +**Architecture : 100 % API REST, aucun accès base de données** (D1). -## Quick Start +| API | Endpoint | Usage | +|---|---|---| +| Query | `POST {api}/QueryExecute` | requêtes LINQ, lignes | +| Query scalaire | `POST {api}/QueryScalarExecute` | `Count()`, `Sum()` — à préférer pour « combien » | +| Command | `POST {api}/CommandExecute` | exécution de commandes WMS | +| Metadata | `GET {api}/Metadata/Entities`, `GET {api}/Metadata/EntityProperties` | entités interrogeables et leurs champs | +| GenericSearch | `GET {api}/GenericSearch/Categories`, `POST {api}/GenericSearch/Search` | recherche plein texte indexée | +| Application Dictionary | `POST {ad}/{Type}/GetByApplication` | 20 types d'éléments, dont `Workflow` | -### 1. Install Dependencies +où `{api}` = `https:///ApplicationService/api` et `{ad}` = +`https:///AD/api`, construits depuis le host du profil actif. -```bash -npm install +Authentification : OAuth 2.0 avec refresh automatique (D2, et +[MONITORING.md](MONITORING.md) §4). + +--- + +## Structure + +``` +src/ +├── index.js Point d'entrée MCP : handlers list/read/call, routage +├── config/ +│ └── profile-manager.js Registre multi-profils + bascule runtime +├── resources/ Contexte en lecture seule (6 resources) +│ ├── wms-entities.js wms://entities +│ ├── entity-schemas.js wms://entity-schemas +│ ├── query-examples.js wms://query-examples — exemples LINQ + recettes de diagnostic +│ ├── workflows.js workflows://overview +│ ├── apis.js api://catalog +│ └── logs.js logs://guide — patterns d'erreur et scénarios de debug +├── services/ Logique métier +│ ├── api-service.js OAuth + client HTTP + helpers de requête (singleton) +│ ├── workflow-service.js Workflows, lazy loading + cache +│ ├── ad-service.js Application Dictionary, 20 types, cache par type +│ ├── wms-query-service.js Construction d'expressions LINQ +│ └── log-service.js Lecture et recherche dans les fichiers de logs +└── tools/ 23 outils MCP + ├── wms-query-tools.js query_wms_entities, count_wms_entities, + │ get_entity_schema, search_wms_data + ├── api-tools.js call_query_api, execute_command + ├── workflow-tools.js search_workflows, get_workflow_details, + │ list_workflow_categories + ├── ad-tools.js get_application_summary, get_ad_elements, + │ search_ad_elements, get_ad_element_details, list_ad_types + ├── metadata-tools.js get_entity_metadata, generic_search + ├── config-tools.js get_system_parameters + ├── profile-tools.js list_wms_profiles, get_current_wms_profile, + │ switch_wms_profile + └── log-tools.js read_recent_logs, list_log_files, search_logs + +scripts/ +├── test-connection.js Smoke test de connectivité (npm test) +└── test-ad-api.ps1 Validation curl des endpoints AD (credentials en paramètres) + +docs/ +└── reference-queries-api.php Client PHP d'origine — source des patterns d'API. + ⚠️ utilise QueryType 1 : ne pas recopier (D3) ``` -### 2. Configure Environment +**Routage.** `src/index.js` route les appels d'outils **par préfixe de nom** +(`name.startsWith('query_wms_')`, `name.includes('_logs')`, …). En ajoutant un +outil, vérifiez que son nom tombe dans la bonne branche — sinon il apparaîtra +dans `tools/list` mais renverra `Unknown tool`. -Copy `.env.example` to `.env` and configure. The server supports **multiple WMS profiles** (AD, LIMAGRAIN, etc.) and Claude switches between them at runtime: +--- + +## Conventions non négociables + +1. **`console.error()` uniquement.** stdout est réservé au JSON MCP ; toute + écriture y casse la session Claude Desktop (D6). +2. **Préfixer les logs** par composant : `[Server]`, `[API]`, `[Profile]`, + `[Workflow]`, `[AD]`, `[Logs]`, `[Tools]`. Le tableau complet est dans + [MONITORING.md](MONITORING.md) §2. +3. **Un outil ne plante jamais le serveur.** Toute erreur revient en réponse + structurée `{ success: false, error, tool }` avec `isError: true` — le + wrapper est dans le handler `tools/call` de `src/index.js`. +4. **Messages d'erreur actionnables.** Ils sont lus par Claude, pas par un + humain : dire quoi faire ensuite (« appelez `switch_wms_profile` », « profils + disponibles : … »). +5. **Aucun accès base de données** (D1). +6. **1000 lignes maximum** par requête, timeout 30 s (`MAX_QUERY_ROWS`, + `QUERY_TIMEOUT`). +7. **Pas de concaténation LINQ à partir d'entrées utilisateur** quand un filtre + côté JS suffit (D11). + +--- + +## Multi-profils + +Un même serveur dessert plusieurs backends WMS. Un profil = un host + des +credentials + un tenant (D8). + +**Déclaration** dans `.env` : ```env -# Shared settings (same for all profiles) -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,LIMAGRAIN,EUROTRAFIC +DEFAULT_WMS_PROFILE=LIMAGRAIN -# Profile registry -WMS_PROFILES=AD,LIMAGRAIN -DEFAULT_WMS_PROFILE=AD - -# Profile: AD (on-premise — logs accessible) AD_HOST=10.255.255.2 -AD_USERNAME=your-ad-username -AD_PASSWORD=your-ad-password +AD_USERNAME=… +AD_PASSWORD=… AD_TENANT=AD AD_SAAS=false - -# Profile: LIMAGRAIN (on-premise — logs accessible) -LIMAGRAIN_HOST=10.255.255.2 -LIMAGRAIN_USERNAME=your-limagrain-username -LIMAGRAIN_PASSWORD=your-limagrain-password -LIMAGRAIN_TENANT=LIMAGRAI2512 -LIMAGRAIN_SAAS=false - -# Logs — {host} is substituted with the active profile's HOST -LOGS_PATH=\\{host}\inetpub\logs\LogFiles\Mecalux;\\{host}\ProgramData\Mecalux\ETLLogs -WORKFLOW_PAGE_SIZE=5000 ``` -**Adding a new profile:** -1. Add its name to `WMS_PROFILES` (comma-separated). -2. Define `_HOST`, `_USERNAME`, `_PASSWORD`, `_TENANT`. -3. The full URLs are built as `https://` etc. — no need to repeat URLs per profile. +Réglages **partagés** par tous les profils : `WMS_API_AUTH`, +`WMS_APPLICATION`, `WMS_API_PATH`, `WMS_TOKEN_PATH`, `WORKFLOW_API_PATH`, +`LOGS_PATH`, et les variables de cache / token / requêtes. Seul le host varie : +les URL sont assemblées en `https://`. -**Switching profiles at runtime (in Claude Desktop):** -- "Quels WMS sont configurés ?" → calls `list_wms_profiles` -- "Connecte-toi au WMS LIMAGRAIN" → calls `switch_wms_profile` with `profile: "LIMAGRAIN"` -- The OAuth token is reset and workflow/AD caches cleared automatically on switch. +**`_SAAS=true`** → WMS cloud : les outils de log échouent avec un message +explicite (D9). Par défaut `false`. -### 3. Configure Claude Desktop +**`LOGS_PATH`** accepte le placeholder `{host}`, substitué par le host du profil +actif à chaque appel. -Edit `%APPDATA%\Claude\claude_desktop_config.json`: +**Au runtime.** `profile-manager` est un singleton d'état global. Les services +s'abonnent via `onSwitch()` pour invalider ce qui dépend du tenant : -```json -{ - "mcpServers": { - "wms": { - "command": "node", - "args": [ - "c:\\path\\to\\wms-mcp-server\\src\\index.js" - ] - } - } -} +| Service | Réaction | +|---|---| +| `api-service` | `resetToken()` | +| `workflow-service` | `clearCache()` | +| `ad-service` | `invalidateCache()` | + +**N'invalidez jamais ces caches à la main depuis un autre module** — l'abonnement +suffit (D8). Tout nouveau service portant un état lié au tenant **doit** +s'abonner. + +Sans profil actif, `getCurrent()` lève une erreur qui énumère les profils +disponibles : c'est ainsi que Claude sait appeler `switch_wms_profile`. + +--- + +## Caches + +Deux caches, TTL commun `WORKFLOW_CACHE_TTL` (3 600 000 ms), chargement +paresseux, vidés à chaque bascule de profil (D10). + +| Cache | Granularité | Pagination | +|---|---|---| +| `workflow-service` | global (~3 700 workflows) | `WORKFLOW_PAGE_SIZE`, 5000 | +| `ad-service` | **un par type** (20 types) | `AD_ELEMENT_TYPES` : `View` 200, `Workflow` 5000, `Resource` 15000, autres 100000 | + +Les tailles de page par type viennent de l'observation des timeouts serveur — +ne les augmentez pas à l'aveugle. + +`get_application_summary` expose l'état des caches sans redémarrage. + +--- + +## Écrire une requête WMS + +```js +await apiService.executeQuery( + 'Context.OutboundOrders.Where(z => z.OutboundOrderStatus == "Release").OrderBy(z => z.Id)', + { take: 100 } +); ``` -### 4. Restart Claude Desktop +**Répartition entre l'expression et les options** — c'est la source d'erreur +la plus fréquente : -Close and reopen Claude Desktop completely. The MCP server will start automatically. +| Élément | Où | +|---|---| +| `Where` | dans l'`Expression` | +| `OrderBy` | dans l'`Expression` — **obligatoire dès qu'on utilise `take`** | +| `Take` / `Skip` / `Select` | paramètres d'API, pas dans l'expression | -### 5. Test +Autres règles : -In Claude Desktop, try: -- **Workflows:** "Search for workflows containing 'Order'" -- **WMS Queries:** "Query the last 10 products" -- **AD Elements:** "Get all Query elements" or "Search Commands containing 'Product'" -- **Logs:** "Show me recent log entries" -- **Summary:** "Get application summary" (shows cached AD element counts) +- **`QueryType: 0` (Reading)**, jamais 1 : les statuts sont alors des chaînes + (D3). +- **Pas de date relative.** `DateTime.Now`, `DateTime.Today`, `AddDays()` ne + sont pas traduisibles : écrire `new DateTime(2026, 8, 1)` (D12). +- **`select_expression` est instable** : les projections via le paramètre + `Select` provoquent des erreurs de compilation. Interroger les lignes + complètes (D13). +- **Pour compter, utiliser `count_wms_entities`** (`QueryScalarExecute`), pas un + `query` suivi d'un `.length`. +- **`executeCommand`** prend le nom de commande **tel quel** : ajouter le suffixe + d'assembly provoque une `FileLoadException` (D14). -## Existing Files +`_parseQueryResponse()` gère les deux formes de réponse (tableau plat, ou +`{ Table: { Columns, Rows } }`) — ne réimplémentez pas ce décodage ailleurs. -- [CLAUDE.md](CLAUDE.md) - This file (project guidance and implementation plan) -- [queries api.php](queries api.php) - Reference PHP file showing WMS API interaction patterns +--- -### Understanding the WMS API from queries api.php +## Entités et éléments AD -The PHP file demonstrates the EasyWMS API structure: +**Entités interrogeables** (Query API) : `Products`, `Containers`, `Accounts`, +`Suppliers`, `Kits`, `Aliases`, `Tasks`, `Stocks`, `ProductLocations`, +`InboundOrders`, `Receptions`, `OutboundOrders`. La liste faisant foi s'obtient +par `get_entity_metadata` (API Metadata) — le catalogue de la resource +`wms://entities` est un raccourci de confort, pas la référence. -**Authentication:** -- OAuth 2.0 token-based authentication -- Endpoint: `https://{ip}/EasySTS/OAuth/Token` -- Supports both password and refresh_token grant types -- Token refresh required after ~1000 seconds (hardcoded in PHP) +**Application Dictionary** : 20 types, ~38 800 éléments. `Resource` (29 374) est +de loin le plus lourd ; 3 types sont valides mais vides (`Dashboard`, +`TimelineTemplate`, `Toggle`). `WorkflowAction` et `WritingModel` ont été +retirés — 404 (D17). Détail : +[docs/ad-api-validation.md](docs/ad-api-validation.md). -**Query API:** -- Endpoint: `https://{ip}/ApplicationService/api/QueryExecute` -- Uses LINQ-like expression syntax: `Context.{Type}.Select(z => z.Id)` -- Supported entity types: Containers, Stocks, ProductLocations, Tasks, Products, Accounts, Suppliers, Kits, Aliases, InboundOrders, Receptions, OutboundOrders +**Paramètres système** : pas d'entité `CommandParameterData`. La configuration +se lit dans `Parameter` (+ `DefaultValue`) et `ParamValue` (surcharges par +entrepôt, jointure sur `ParameterId`), fusionnées côté JS par +`get_system_parameters` (D11). -**Command API:** -- Endpoint: `https://{ip}/ApplicationService/api/CommandExecute` -- Command structure includes fully qualified .NET class names -- Example: `Mecalux.ITSW.EasyWMS.Modules.MasterData.Contracts.Commands.ProductRemoveCommand` +--- -**Workflow API:** -- Endpoint: `POST https://{ip}/AD/api/Workflow/GetByApplication` -- Request body: `["EasyWMS", "AD", 5000, 0]` (application, tenant, pageSize, offset) -- Returns workflow definitions with pagination -- Total workflows: ~3712 (can be fetched with higher page size to reduce API calls) -- **Lazy loading:** Workflows are only fetched when MCP receives a workflow-related request - -**Application Dictionary API:** -- Endpoint pattern: `POST https://{ip}/AD/api/{ElementType}/GetByApplication` -- Request body: `["EasyWMS", "AD", pageSize, offset]` -- Response structure: `{entities: [...]}` -- **Supported element types (20):** Command, Query, Dialog, View, Entity, Event, FieldType, Hook, List, Record, Relationship, Report, Resource, Subscription, TimelineTemplate, Toggle, Validator, ViewGroup, Workflow, Dashboard -- **Total elements:** ~38,765 across all types -- **Validated with curl:** 17/19 types working (WorkflowAction and WritingModel return 404) -- **Lazy loading + caching:** Each element type loaded on first request, cached for 1 hour -- **Test results:** See [AD_API_TEST_RESULTS.md](AD_API_TEST_RESULTS.md) for detailed validation - -## Project Structure - -``` -wms-mcp-server/ -├── src/ -│ ├── index.js # Main MCP server entry point -│ ├── resources/ # MCP resources (read-only data) -│ │ ├── wms-entities.js # WMS entities catalog -│ │ ├── entity-schemas.js # Entity schemas details -│ │ ├── query-examples.js # LINQ query examples + diagnostic recipes -│ │ ├── workflows.js # Workflow catalog overview -│ │ ├── apis.js # API documentation -│ │ └── logs.js # Log file guide -│ ├── tools/ # MCP tools (actions) -│ │ ├── wms-query-tools.js # query_wms_entities, count_wms_entities, get_entity_schema, search_wms_data -│ │ ├── workflow-tools.js # search_workflows, get_workflow_details, list_workflow_categories -│ │ ├── ad-tools.js # get_ad_elements, search_ad_elements, get_ad_element_details (5 tools) -│ │ ├── api-tools.js # call_query_api, execute_command -│ │ ├── metadata-tools.js # get_entity_metadata, generic_search -│ │ ├── config-tools.js # get_system_parameters (Parameter + ParamValue merge) -│ │ ├── profile-tools.js # list_wms_profiles, get_current_wms_profile, switch_wms_profile -│ │ └── log-tools.js # read_recent_logs, list_log_files, search_logs -│ ├── services/ # Business logic -│ │ ├── api-service.js # OAuth + HTTP client (ApplicationService + AD APIs) -│ │ ├── workflow-service.js # Workflow fetching with cache (lazy loading) -│ │ ├── ad-service.js # Application Dictionary elements (20 types, lazy loading + cache) -│ │ ├── wms-query-service.js # LINQ query builder helper -│ │ └── log-service.js # Log file reading & searching -│ └── config/ -│ ├── constants.js # Constants and entity type definitions -│ └── profile-manager.js # Multi-profile registry (AD, LIMAGRAIN, ...) + runtime switching -├── .env.example -├── package.json -└── README.md -``` - -## Development Setup - -### Initial Setup +## Commandes ```bash -# Initialize project -npm init -y - -# Install dependencies -npm install @modelcontextprotocol/sdk dotenv axios -npm install --save-dev @types/node typescript +npm start # lancer le serveur (stdio) +npm test # smoke test du profil actif — lecture seule +npm test -- LIMAGRAIN # smoke test d'un profil précis +npm test -- --all # tous les profils +npm run build # dist/wms-mcp-server.exe (node22-win-x64) ``` -### Environment Configuration +Le smoke test vérifie OAuth, `QueryExecute`, `QueryScalarExecute` et l'API AD, +et sort en code 1 au moindre échec. -Create `.env` file (use `.env.example` as template): - -```env -# Logs Configuration -LOGS_PATH=C:\WMS\Logs -LOG_FILE_PATTERN=application*.log - -# WMS API Configuration -WMS_API_BASE_URL=https://10.255.255.2/ApplicationService/api -WMS_API_TOKEN_URL=https://10.255.255.2/EasySTS/OAuth/Token -WMS_API_AUTH=Basic R05BOklFNGU3aXFoZHQ= -WMS_API_USERNAME=your_username -WMS_API_PASSWORD=your_password -WMS_API_TENANT=AD - -# Workflow API Configuration -WORKFLOW_API_BASE=https://10.255.255.2/AD/api -WORKFLOW_APPLICATION=EasyWMS -WORKFLOW_PAGE_SIZE=5000 - -# Cache Configuration -WORKFLOW_CACHE_TTL=3600 -``` - -### Common Commands +**Validation des endpoints AD** (PowerShell, credentials en paramètres) : ```bash -# Run MCP server locally -npm start - -# Test API connection -node test-api.js - -# Build Windows executable -npm run build -# or -pkg . --targets node18-win-x64 --output dist/mcp-server.exe - -# Run tests (once implemented) -npm test +powershell -ExecutionPolicy Bypass -File scripts/test-ad-api.ps1 -WmsHost 10.255.255.2 -Username user -Password '***' -Tenant AD ``` -## Implementation Guide +--- -### Phase 1: API Service (src/services/api-service.js) +## Ajouter un outil -**Required Methods:** -- `authenticate()` - Get OAuth token with username/password -- `refreshToken()` - Refresh OAuth token before expiration -- `post(endpoint, data)` - Generic POST request with automatic token management -- `get(endpoint, params)` - Generic GET request with automatic token management +1. Déclarer le schéma dans `listTools()` du module `src/tools/` concerné. +2. Traiter le cas dans son `executeTool()`. +3. **Vérifier le routage par préfixe** dans `src/index.js` — ou ajouter une + branche. +4. Logger avec le préfixe du module. +5. Renvoyer les erreurs, ne pas les lever hors du wrapper. +6. Tester le handshake complet : -**Token Management Strategy:** -- Check token age before each request -- Auto-refresh if age > 1000 seconds (token lifetime ~1200s) -- Support both password and refresh_token grant types - -**Test Script:** -```javascript -// test-api.js -const apiService = require('./src/services/api-service'); - -async function test() { - await apiService.authenticate(); - const result = await apiService.post('/QueryExecute', { - Application: "EasyWMS", - QueryType: 1, - Expression: "Context.Products.Select(z => new { z.Id }).Take(1)" - }); - console.log('API connection OK:', result); -} - -test(); -``` - -### Phase 2: MCP Resources (Read-Only Context for Claude) - -Resources provide Claude with background knowledge without explicit queries: - -1. **`wms://entities`** - List of WMS entities available via Query API (Containers, Stocks, Tasks, etc.) -2. **`wms://entity-schemas`** - Detailed schemas for top 15 critical entities -3. **`wms://query-examples`** - LINQ query examples for common use cases -4. **`workflows://overview`** - Workflow statistics, categories, and top workflows -5. **`workflows://categories`** - Complete list of workflow categories -6. **`api://catalog`** - List of available WMS APIs with parameters -7. **`logs://guide`** - Log file format, locations, common error patterns - -All resources should return markdown-formatted content. - -### Phase 3: MCP Tools (Executable Actions) - -**WMS Query Tools:** -- `query_wms_entities(entity_type, select_expression, filter, limit)` - Query WMS entities via LINQ (max 1000 rows) -- `count_wms_entities(entity_type, filter)` - Count entities via QueryScalarExecute (preferred for "how many") -- `get_entity_schema(entity_type)` - Get available fields for an entity -- `search_wms_data(keyword, entity_types, limit)` - Search across multiple entities - -**Workflow Tools:** -- `search_workflows(query, category, limit)` - Search workflows (lazy loaded with cache) -- `get_workflow_details(workflow_id)` - Retrieve full workflow JSON -- `list_workflow_categories()` - List all workflow categories - -**API Tools:** -- `call_query_api(entity_type, expression, filter, limit)` - Execute custom LINQ queries -- `execute_command(command_name, properties)` - Execute WMS commands - -**Metadata Tools:** -- `get_entity_metadata(entity_name)` - List queryable entities + their field names/types -- `generic_search(query, categories, limit)` - Full-text search across indexed WMS documents - -**Config Tools:** -- `get_system_parameters(warehouse, param_class, search, only_overridden)` - WMS configuration parameters with per-warehouse effective values (merges `Parameter` + `ParamValue` Reading entities) - -**Log Tools:** -- `read_recent_logs(count, log_file)` - Tail recent log entries -- `list_log_files()` - List available log files -- `search_logs(keyword, max_results)` - Search logs with context - -### Phase 4: Main Server (src/index.js) - -Use the MCP SDK to create the server: - -```javascript -import { Server } from '@modelcontextprotocol/sdk/server/index.js'; -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; - -const server = new Server({ - name: 'wms-mcp-server', - version: '1.0.0', -}, { - capabilities: { - resources: {}, - tools: {}, - }, -}); - -// Implement handlers for: -// - resources/list -// - resources/read -// - tools/list -// - tools/call - -async function main() { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error('WMS MCP Server running'); // Use stderr, not stdout -} - -main(); -``` - -**Critical:** Use `console.error()` for all logging. Stdout is reserved for MCP protocol communication. - -### Phase 5: Workflow Service (src/services/workflow-service.js) - -**Lazy Loading Strategy:** -```javascript -let workflowCache = null; -let cacheTimestamp = null; -const CACHE_TTL = process.env.WORKFLOW_CACHE_TTL || 3600000; // 1 hour - -async function fetchAllWorkflows() { - // Check cache validity - const now = Date.now(); - if (workflowCache && (now - cacheTimestamp) < CACHE_TTL) { - console.error('[Workflow] Using cached data'); - return workflowCache; - } - - // Fetch from API with high page size to minimize calls - console.error('[Workflow] Fetching from API...'); - const apiService = require('./api-service'); - - let allWorkflows = []; - let offset = 0; - const pageSize = parseInt(process.env.WORKFLOW_PAGE_SIZE) || 5000; - - while (true) { - const endpoint = '/AD/api/Workflow/GetByApplication'; - const body = [ - process.env.WORKFLOW_APPLICATION || "EasyWMS", - process.env.WMS_API_TENANT || "AD", - pageSize, - offset - ]; - - const response = await apiService.post(endpoint, body, true); // true = use AD API base - - if (!response || response.length === 0) break; - - allWorkflows = allWorkflows.concat(response); - console.error(`[Workflow] Fetched ${response.length} workflows (total: ${allWorkflows.length})`); - - if (response.length < pageSize) break; // Last page - offset += pageSize; - } - - // Cache results - workflowCache = allWorkflows; - cacheTimestamp = now; - - console.error(`[Workflow] Cached ${allWorkflows.length} workflows`); - return allWorkflows; -} -``` - -**Key Features:** -- Only fetches workflows when needed (first workflow-related request) -- Uses high page size (5000) to reduce API calls -- Caches results for 1 hour (configurable) -- Automatic pagination if needed - -### Phase 6: WMS API Client Details (src/services/api-service.js) - -Based on the PHP reference file: - -**Token Management:** -```javascript -async function refreshToken() { - // Check if token age > 1000 seconds - // If < 1190s: use refresh_token grant - // If >= 1190s: use password grant - // Store new token and refresh_token -} -``` - -**Query Execution:** -```javascript -async function executeQuery(entityType, expression) { - await ensureTokenValid(); - - const query = { - Application: "EasyWMS", - QueryType: 1, - Expression: `Context.${entityType}.Select(${expression})` - }; - - // POST to /api/QueryExecute with Bearer token -} -``` - -**Command Execution:** -```javascript -async function executeCommand(commandName, properties) { - await ensureTokenValid(); - - const command = [{ - Name: `${commandName}, Mecalux.ITSW.EasyWMS.Modules.Contracts`, - Properties: properties - }]; - - // POST to /api/CommandExecute with Bearer token -} -``` - -### Phase 7: Error Handling - -**Wrapper for All Tools:** -```javascript -async function safeToolCall(toolFn, args) { - try { - const result = await toolFn(args); - return { - content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] - }; - } catch (error) { - console.error('Tool error:', error); - return { - content: [{ type: 'text', text: `Error: ${error.message}` }], - isError: true - }; - } -} -``` - -## Deployment - -### Build Windows Executable - -Add to package.json: -```json -{ - "bin": "src/index.js", - "pkg": { - "targets": ["node18-win-x64"], - "outputPath": "dist" - } -} -``` - -Build: ```bash -npm install -g pkg -pkg . --output dist/mcp-server.exe +printf '%s\n%s\n%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}' '{"jsonrpc":"2.0","method":"notifications/initialized"}' '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | node src/index.js ``` -### SSH Configuration - -**On Windows Server VM:** -```powershell -# Install OpenSSH Server -Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 -Start-Service sshd -Set-Service -Name sshd -StartupType 'Automatic' -New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server' ` - -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 - -# Configure authorized_keys -mkdir C:\Users\YourUser\.ssh -# Copy public key to C:\Users\YourUser\.ssh\authorized_keys -icacls C:\Users\YourUser\.ssh\authorized_keys /inheritance:r -icacls C:\Users\YourUser\.ssh\authorized_keys /grant:r "YourUser:F" -``` - -**On Development PC:** -```bash -# Generate SSH key -ssh-keygen -t ed25519 -C "claude-mcp" - -# Add to ~/.ssh/config -Host vm-wms - HostName 192.168.x.x - User your-username - IdentityFile ~/.ssh/id_ed25519 - ServerAliveInterval 60 - ServerAliveCountMax 3 - -# Test connection -ssh vm-wms echo "Connection OK" -``` - -### Deploy to VM - -1. Copy `mcp-server.exe` to `C:\WMS\mcp\` -2. Create `C:\WMS\mcp\.env` with production credentials -3. Test: `ssh vm-wms "C:\WMS\mcp\mcp-server.exe"` - -### Claude Desktop Configuration - -Edit `%APPDATA%\Claude\claude_desktop_config.json`: - -```json -{ - "mcpServers": { - "wms": { - "command": "ssh", - "args": ["vm-wms", "C:\\WMS\\mcp\\mcp-server.exe"] - } - } -} -``` - -Restart Claude Desktop after configuration changes. - -## Testing - -### Manual Tests (After Implementation) - -1. List resources: "Show me available resources" -2. Read WMS entities: "What entities are available in the WMS?" -3. Search workflows: "Find workflows related to orders" -4. Query WMS data: "Query the last 10 products" -5. Read logs: "Show me the last 50 log lines" -6. Check workflow cache: "Search for workflows containing 'picking'" - -### Debugging - -**MCP Server Logs:** -- All `console.error()` output appears in Claude Desktop logs -- Location: `%APPDATA%\Claude\logs\` - -**Manual MCP Testing:** -```bash -# Test server directly via SSH -ssh vm-wms "C:\WMS\mcp\mcp-server.exe" < test-request.json - -# Test API connectivity -curl -X POST "https://10.255.255.2/EasySTS/OAuth/Token" \ - -H "Authorization: Basic R05BOklFNGU3aXFoZHQ=" \ - -H "Content-Type: application/x-www-form-urlencoded" \ - -d "grant_type=password&username=XXX&password=XXX" -``` - -## Multi-Profile Architecture - -The server connects to different WMS backends (tenants/customers) via **profiles** defined in `.env`. Each profile has its own host, credentials, and tenant. Claude selects which profile to use at runtime. - -### .env layout - -**Shared settings** (same across all profiles): -- `WMS_API_AUTH` — OAuth client Basic auth header -- `WMS_APPLICATION` — Application name (e.g. `EasyWMS`) -- `WMS_API_PATH`, `WMS_TOKEN_PATH`, `WORKFLOW_API_PATH` — URL path components - -**Profile registry:** -- `WMS_PROFILES` — comma-separated profile names (e.g. `AD,LIMAGRAIN`) -- `DEFAULT_WMS_PROFILE` — profile active at startup (optional) - -**Per profile** (prefixed by profile name): -- `_HOST` — hostname or IP (e.g. `10.255.255.2`, `p4swms.mss.mecalux.com`) -- `_USERNAME`, `_PASSWORD`, `_TENANT` -- `_SAAS` — `true`/`false` (default `false`). When `true`, the WMS is cloud-hosted and log filesystem tools are disabled (they return an error pointing to the API tools instead). - -URLs are assembled as `https://` — only the host varies per profile. - -**Logs path template:** `LOGS_PATH` supports the `{host}` placeholder, substituted with the active profile's `HOST` at each call. Example: -``` -LOGS_PATH=\\{host}\inetpub\logs\LogFiles\Mecalux;\\{host}\ProgramData\Mecalux\ETLLogs -``` - -### Runtime behavior - -- At startup, `profile-manager.loadProfiles()` parses `.env` and activates `DEFAULT_WMS_PROFILE` if valid. -- If no default is set, any WMS API call throws a structured error that tells Claude to call `switch_wms_profile` first — Claude Desktop reads this and asks the user which WMS to use. -- On `switch_wms_profile`: - - `api-service` resets its OAuth token - - `workflow-service` clears its workflow cache - - `ad-service` invalidates all element-type caches - - Next API call re-authenticates against the new host with the new tenant. -- **SaaS profiles** (`_SAAS=true`): log tools (`read_recent_logs`, `search_logs`, `list_log_files`) throw an error because the cloud filesystem is unreachable. Only WMS APIs (query/command/workflow/AD) work. On-premise profiles (`SAAS=false`, default) scan `LOGS_PATH` with `{host}` substituted by the profile's host. - -### Profile tools - -- `list_wms_profiles` — list all configured profiles + currently active one -- `get_current_wms_profile` — return active profile details (host, tenant, URLs) -- `switch_wms_profile({ profile })` — change active profile - -### Files - -- [src/config/profile-manager.js](src/config/profile-manager.js) — profile registry, switching, listener API -- [src/tools/profile-tools.js](src/tools/profile-tools.js) — the 3 MCP tools above - -Services (`api-service`, `workflow-service`, `ad-service`) register `onSwitch` listeners so cache/token invalidation is automatic — never call these invalidations manually from other code. - -## Key Technical Constraints - -1. **API Access Only:** No direct Oracle access. All data retrieval via WMS REST APIs. -2. **Performance:** Limit result sets to 1000 rows maximum, implement query timeouts (30s). -3. **Error Handling:** All tools must return structured errors, never crash the server. -4. **Logging:** Use `console.error()` for logs (stdout reserved for MCP protocol). -5. **Windows:** File paths use backslashes, executable must be .exe format. -6. **Token Management:** Implement automatic token refresh before expiration (~1200s lifetime). -7. **Lazy Loading:** Workflows are only fetched when needed (not at startup). -8. **Caching:** Cache workflows for 1 hour to minimize API calls. -9. **Pagination:** Use high page sizes (5000) to reduce workflow API calls. - -## Implementation Checklist - -**Phase 1-2: Foundation** -- [x] Project initialized with dependencies (axios, @modelcontextprotocol/sdk, dotenv) -- [x] Directory structure created -- [x] API service implemented (OAuth + HTTP client) -- [x] Workflow service implemented (lazy loading + cache) -- [x] WMS query service implemented (LINQ helper) -- [x] Log service implemented - -**Phase 3-4: MCP Core** -- [x] All 6 resources implemented (wms-entities, entity-schemas, query-examples, workflows, apis, logs) -- [x] 23 tools implemented (wms-query, workflow, ad, api, metadata, config, profile, log tools) -- [x] Main server with request routing -- [x] Error handling wrapper for all tools - -**Phase 5-6: Deployment** -- [x] Build to .exe validated (pkg configuration ready) -- [ ] SSH configured on VM (pending deployment) -- [ ] Key-based auth working (pending deployment) -- [ ] Server deployed to C:\WMS\mcp\ (pending deployment) -- [x] .env configured with production credentials - -**Phase 7: Integration** -- [x] Claude Desktop config updated -- [x] Basic functionality tests passed -- [x] Workflow lazy loading validated -- [x] API token refresh tested -- [x] Performance validated (query limits, timeouts) - -**Critical Fixes Applied:** -- [x] OAuth authentication: Added missing `tenant_code` parameter -- [x] Workflow API: Extract `response.entities` instead of treating response as array -- [x] Property handling: Support both lowercase and uppercase property names (name/Name, id/Id) -- [x] dotenv stdout: Redirect dotenv output to stderr to comply with MCP protocol -- [x] .env path: Use absolute path to ensure .env is loaded from project root - -## WMS Entity Types Reference - -Based on [queries api.php](queries api.php), these entity types are supported: - -**Master Data:** Containers, Products, Accounts, Suppliers, Kits, Aliases -**Operations:** Tasks, Stocks, ProductLocations -**Inbound:** InboundOrders, Receptions -**Outbound:** OutboundOrders - -Each has corresponding Command classes for operations (focus on Query API for debugging). - -## Troubleshooting - -### Common Issues and Solutions - -#### 1. "Unexpected token 'd', "[dotenv@17."... is not valid JSON" - -**Problem:** dotenv writes version info to stdout, but MCP requires stdout to be reserved for JSON protocol only. - -**Solution:** -```javascript -// Redirect stdout to stderr during dotenv loading -const originalStdoutWrite = process.stdout.write; -process.stdout.write = process.stderr.write.bind(process.stderr); -require('dotenv').config({ path: path.join(__dirname, '..', '.env') }); -process.stdout.write = originalStdoutWrite; -``` - -#### 2. "Authentication failed: Request failed with status code 400" - -**Problem:** Missing `tenant_code` parameter in OAuth request. - -**Solution:** Add `tenant_code` to authentication request: -```javascript -new URLSearchParams({ - grant_type: 'password', - tenant_code: process.env.WMS_API_TENANT, // ← This was missing - username: process.env.WMS_API_USERNAME, - password: process.env.WMS_API_PASSWORD -}) -``` - -#### 3. "Successfully cached 0 workflows" - -**Problem:** Workflow API returns `{entities: [...]}` but code expects direct array. - -**Solution:** Extract entities from response: -```javascript -const response = await apiService.post('/Workflow/GetByApplication', body, true); -const workflows = response?.entities || []; // ← Extract entities property -``` - -#### 4. "Workflow not found" despite existing - -**Problem:** API returns properties in lowercase (`name`, `id`) but code looks for uppercase (`Name`, `Id`). - -**Solution:** Support both cases: -```javascript -const name = (w.name || w.Name || '').toLowerCase(); -const id = w.id || w.Id; -``` - -#### 5. "Missing environment variables" - -**Problem:** dotenv looks for `.env` in current working directory, not project root. - -**Solution:** Specify absolute path: -```javascript -require('dotenv').config({ - path: path.join(__dirname, '..', '.env') -}); -``` - -## Application Dictionary Implementation - -The MCP server supports the Application Dictionary (AD) API, providing access to 20 element types containing application definitions (commands, queries, dialogs, views, etc.). - -### Supported Element Types (20) - -**Working Types (17 validated with curl):** -- Command (1,872 elements) -- Query (2,016 elements) -- Dialog (734 elements) -- View (373 elements) -- Entity (331 elements) -- Event (1,980 elements) -- FieldType (320 elements) -- Hook (60 elements) -- List (243 elements) -- Record (337 elements) -- Relationship (49 elements) -- Report (67 elements) -- Resource (29,374 elements - largest type) -- Subscription (500 elements) -- Validator (17 elements) -- ViewGroup (180 elements) -- Workflow (3,712 elements) - -**Empty Types (0 elements, but endpoints exist):** -- Dashboard -- TimelineTemplate -- Toggle - -**Total Elements:** 38,765 - -### Architecture - -The AD implementation follows the same pattern as workflow-service.js: - -1. **Lazy Loading:** Elements fetched only on first request (not at startup) -2. **Caching:** 1-hour TTL per element type (configurable via `WORKFLOW_CACHE_TTL`) -3. **Pagination:** Different page sizes per type (heavy types: 5000, light types: 100000) -4. **Property Flexibility:** Supports both lowercase (`name`, `id`) and uppercase (`Name`, `Id`) - -**Files:** -- [src/services/ad-service.js](src/services/ad-service.js) - Generic service for all 20 element types -- [src/tools/ad-tools.js](src/tools/ad-tools.js) - 5 MCP tools for AD interaction - -### Available AD Tools - -1. **get_application_summary** - Shows cached element counts per type -2. **get_ad_elements** - Get all elements of a specific type (with limit) -3. **search_ad_elements** - Search elements by name/description/code -4. **get_ad_element_details** - Get full details of specific element -5. **list_ad_types** - List all 20 available element types - -### curl Testing - -All 17 working element types were validated with curl. See [AD_API_TEST_RESULTS.md](AD_API_TEST_RESULTS.md) for detailed results. - -**Test command:** -```powershell -powershell -ExecutionPolicy Bypass -File test-ad-api.ps1 -``` - -### Notes - -- **WorkflowAction** and **WritingModel** removed (404 Not Found on API) -- Page sizes optimized per type (Resource: 15000, Workflow: 5000, View: 200, others: 100000) -- Same cache management as workflows (1-hour TTL) - -## System Parameters (config-tools.js) - -The `get_system_parameters` tool exposes WMS configuration parameters. - -- The Reading model has **no `CommandParameterData` entity** — the correct entities - are `Parameter` (definition + `DefaultValue`) and `ParamValue` (per-warehouse - overrides, linked by `ParameterId`). -- The tool fetches both entities fully (small datasets — ~200 / ~50 rows), merges - them client-side, and reports the **effective value** per warehouse (override if - present, otherwise default). No LINQ string injection — all filters - (`warehouse`, `param_class`, `search`, `only_overridden`) are applied in JS. -- Files: [src/tools/config-tools.js](src/tools/config-tools.js). - -## Shipment Templates — scope note - -Requests for **shipment template execution history** are only partially served: - -- The `ShipmentTemplate` Reading entity (queryable via `query_wms_entities`) exposes - only the **last** execution (`LastExecuteDate`) plus `Status` / `IsEnabled`. -- The **full execution history** lives exclusively in server-side - `ApplyShipmentTemplates` text logs. Those logs are **not present on the reachable - host** (`10.255.255.2`) — they sit on customer production / ETL servers — so no - log-parsing tool was built. See the `wms://query-examples` resource for the - API-only recipes. - -## LINQ / QueryExecute gotchas (verified against live WMS) - -- **Relative dates fail:** `DateTime.Now`, `DateTime.Today`, `AddDays()` are NOT - translatable by the query engine. Use a literal `new DateTime(year, month, day)`. -- **`select_expression` is unreliable:** LINQ projections passed via the `Select` - API parameter raise compile errors. Prefer querying full rows. (Open issue.) -- The `ApplicationService.log` line format is: - `YYYY-MM-DD HH:MM:SS.ffff [thread] [Level] [Component] [message]`. - -## Removed code - -The project is **100% API-based**. Legacy direct-Oracle files -(`src/services/oracle-service.js`, `src/resources/database.js`, -`src/tools/database-tools.js`) were dead code (unwired, `oracledb` not even a -dependency) and have been **removed**. Do not reintroduce direct database access. - -## Future Enhancements - -1. **Caching:** Cache frequently accessed resources (workflow categories) -2. **Analytics:** Tool to analyze error patterns across logs + workflows -3. **Suggestions:** Tool to suggest fixes based on error analysis -4. **Metrics:** Performance monitoring and query statistics -5. **`select_expression` fix:** Investigate the `Select` API parameter compile errors +--- + +## Points ouverts + +- **`select_expression`** : projections en erreur de compilation côté serveur + (D13). Principal irritant restant. +- **Historique des shipment templates** : hors de portée, les logs concernés + n'existent pas sur l'hôte joignable (D16). +- **Logs chargés intégralement en mémoire** : coûteux sur les gros fichiers + ([docs/logs.md](docs/logs.md) §6). +- **Déploiement sur VM par SSH** : non finalisé. L'exécutable est validé, la + configuration SSH reste à faire. diff --git a/DECISIONS.md b/DECISIONS.md new file mode 100644 index 0000000..ed6d323 --- /dev/null +++ b/DECISIONS.md @@ -0,0 +1,344 @@ +# Décisions d'architecture et pièges vérifiés + +Ce fichier consigne **pourquoi** le code est écrit comme il l'est. Chaque entrée +décrit une décision prise ou un piège constaté **sur un WMS réel** — pas une +supposition. Avant de « corriger » un comportement qui paraît étrange, cherchez-le +ici : il est probablement volontaire. + +Convention : une décision reste dans le fichier même si elle est révisée ; on +ajoute alors une entrée `Révisée le …` plutôt que de réécrire l'histoire. + +--- + +## D1 — 100 % API, aucun accès Oracle direct + +**Décision.** Toutes les données transitent par les API REST du WMS. Aucune +connexion base de données. + +**Pourquoi.** Le serveur MCP doit fonctionner depuis un poste ou une VM sans +credentials Oracle, sans client Oracle installé, et sans risque d'écriture +directe en base. L'API impose en prime les règles métier et les droits du +compte utilisé. + +**Conséquence.** Les fichiers `src/services/oracle-service.js`, +`src/resources/database.js` et `src/tools/database-tools.js` ont été supprimés +(ils étaient de toute façon morts : non branchés, `oracledb` n'était même pas +une dépendance). **Ne pas les réintroduire.** Si une donnée n'est pas +atteignable par API, elle est hors périmètre — voir D16. + +--- + +## D2 — OAuth : `tenant_code` est obligatoire + +**Piège.** L'endpoint `/EasySTS/OAuth/Token` répond `400 Bad Request` si le +paramètre `tenant_code` est absent, sans message explicite. + +**Solution.** Le corps du grant `password` contient toujours les quatre +paramètres : + +``` +grant_type=password&tenant_code=&username=&password= +``` + +Voir `src/services/api-service.js`, méthode `authenticate()`. + +--- + +## D3 — `QueryType: 0` (Reading), pas 1 + +**Piège.** `QueryType` sélectionne le modèle de données interrogé : + +| Valeur | Modèle | Champs de statut | +|---|---|---| +| `0` | **Reading** | chaînes de caractères (`"Release"`) | +| `1` | Writing | énumérations | + +Les comparaisons de statut par chaîne — de loin le cas le plus courant en +debug — **échouent** en `QueryType: 1`. Le code force donc `0` dans +`executeQuery()` et `executeScalarQuery()`. + +**Attention.** D'anciens exemples (dont le PHP de référence) utilisent `1`. Ne +les recopiez pas. + +--- + +## D4 — Les API AD renvoient `{ entities: [...] }`, pas un tableau + +**Piège.** `POST /AD/api/{Type}/GetByApplication` renvoie un objet enveloppe. Un +code qui traite la réponse comme un tableau obtient silencieusement +`0 élément` — le symptôme historique était « Successfully cached 0 workflows ». + +**Solution.** Toujours extraire : `response?.entities || []`. + +--- + +## D5 — Les propriétés arrivent en minuscules *ou* en majuscules + +**Piège.** Selon le type d'élément et la version du WMS, l'API renvoie `name` +ou `Name`, `id` ou `Id`. + +**Solution.** Systématiquement `const name = w.name || w.Name || ''` avant tout +filtrage ou tri. Une recherche qui « ne trouve pas » un élément qui existe est +presque toujours ce bug. + +--- + +## D6 — dotenv doit écrire sur stderr + +**Piège.** dotenv affiche une bannière de version sur **stdout**. Or le +protocole MCP réserve stdout au JSON : Claude Desktop échoue alors avec +`Unexpected token 'd', "[dotenv@17."... is not valid JSON`. + +**Solution.** `src/index.js` détourne `process.stdout.write` vers stderr le +temps du chargement de dotenv, puis le restaure. + +**Règle générale.** Dans tout le projet, on log avec `console.error()`. +**Jamais** `console.log()`. + +--- + +## D7 — Le `.env` est lu à côté de l'exécutable quand le serveur est packagé + +**Décision.** `src/index.js` résout le chemin du `.env` selon le contexte : + +| Contexte | Chemin du `.env` | +|---|---| +| Sources (`npm start`) | racine du projet | +| Exécutable pkg (`process.pkg`) | dossier de `process.execPath` | + +**Pourquoi.** Avec un chemin statique, pkg **embarque le `.env` dans le +snapshot** de l'exe : les credentials sont figés dans le binaire et +reconfigurer un déploiement impose un rebuild. Le chemin dynamique via +`process.execPath` empêche pkg de le détecter, donc rien n'est embarqué, et +`dist/.env` devient le fichier de configuration du déploiement. + +**Vérification.** Sans `.env` à côté de l'exe, le serveur démarre en +avertissant `WMS_PROFILES is empty` — preuve qu'aucune valeur n'est embarquée. + +--- + +## D8 — Multi-profils au runtime plutôt qu'un serveur MCP par WMS + +**Décision.** Un seul serveur MCP dessert plusieurs backends WMS ; Claude bascule +avec `switch_wms_profile`. + +**Pourquoi.** L'alternative — une entrée par client dans +`claude_desktop_config.json` — multiplie les processus, les jeux de credentials +et les caches, pour un usage où l'on ne consulte qu'un WMS à la fois. + +**Conséquence.** L'état actif est **global au processus**. Un changement de +profil doit invalider tout ce qui dépend du tenant. Les services s'abonnent via +`profileManager.onSwitch()` : + +| Service | Réaction au switch | +|---|---| +| `api-service` | `resetToken()` — le token OAuth appartient au tenant précédent | +| `workflow-service` | `clearCache()` | +| `ad-service` | `invalidateCache()` — tous les types | + +**Ne jamais** appeler ces invalidations à la main depuis un autre module : +l'abonnement suffit, et le doublon masquerait un oubli d'abonnement. + +**Sans profil actif** (`DEFAULT_WMS_PROFILE` absent ou invalide), `getCurrent()` +lève une erreur qui **énumère les profils disponibles**. C'est intentionnel : +Claude lit ce message et enchaîne sur `switch_wms_profile` au lieu d'échouer. + +--- + +## D9 — Profils SaaS : accès aux logs refusé, pas silencieux + +**Décision.** Quand `_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 +`\\\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. diff --git a/MONITORING.md b/MONITORING.md new file mode 100644 index 0000000..084bf84 --- /dev/null +++ b/MONITORING.md @@ -0,0 +1,220 @@ +# Supervision du serveur MCP + +Comment savoir si le serveur tourne, ce qu'il fait, et pourquoi il ne répond +pas. Ce document décrit **l'existant** — il n'y a ni endpoint de santé, ni +métriques exportées : toute l'observabilité passe par **stderr** et par le +smoke test `npm test`. + +Pour lire les logs **du WMS** (et non ceux du serveur MCP), voir +[docs/logs.md](docs/logs.md). + +--- + +## 1. Où regarder + +| Quoi | Où | +|---|---| +| Logs du serveur MCP | `%APPDATA%\Claude\logs\` (fichier `mcp-server-wms.log`) | +| Logs applicatifs du WMS | partages `\\\...` — 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 + `_HOST/USERNAME/PASSWORD/TENANT` manquent. +3. **`Active profile: …`** — si le message est `No active profile`, ce n'est + **pas** une panne : Claude doit appeler `switch_wms_profile` avant la + première requête, et l'erreur renvoyée le lui indique explicitement (D8). + +Aucune connexion au WMS n'est tentée au démarrage : un boot propre ne prouve +donc **pas** que le WMS est joignable. Pour cela, voir §5. + +--- + +## 4. Cycle de vie du token OAuth + +Le token est obtenu **paresseusement**, à la première requête, puis rafraîchi +automatiquement. Réglages dans `.env` : + +| Variable | Défaut | Rôle | +|---|---|---| +| `TOKEN_REFRESH_THRESHOLD` | `1000` s | âge au-delà duquel un refresh est déclenché avant la requête | +| `TOKEN_MAX_AGE` | `1190` s | âge au-delà duquel on ne tente plus le `refresh_token` mais une ré-authentification complète | +| `QUERY_TIMEOUT` | `30000` ms | timeout HTTP de toute requête WMS | + +Séquence observable : + +``` +[API] Authenticating profile="LIMAGRAIN" tenant="LIMAGRAI2512" ... +[API] Authentication successful. Token expires in ~1190s +[API] POST /QueryExecute +... (~17 min plus tard) +[API] Refreshing token with refresh_token grant... +[API] Token refreshed successfully +``` + +Trois filets de sécurité, dans cet ordre : + +1. **Avant la requête** — si `âge > TOKEN_REFRESH_THRESHOLD`, refresh préventif. +2. **Refresh en échec** — bascule automatique sur le grant `password` + (`[API] Token refresh failed, re-authenticating`). +3. **Réponse 401** — un refresh est déclenché et la requête est **rejouée une + fois** (`[API] Unauthorized, refreshing token and retrying...`). + +**Ce qui est normal.** Une ligne `Token refresh failed` isolée suivie d'une +authentification réussie : le filet a joué son rôle. + +**Ce qui ne l'est pas.** Ces trois lignes en boucle rapprochée signalent des +credentials invalides ou un tenant erroné — le serveur n'abandonne jamais de +lui-même, il retentera à chaque requête. + +--- + +## 5. Test de bout en bout + +```bash +npm test +``` + +Teste le profil actif ; `npm test -- AD` cible un profil, `npm test -- --all` +les teste tous. Quatre vérifications en lecture seule, aucune écriture WMS : + +| Test | Ce qu'il prouve | +|---|---| +| OAuth | host joignable, credentials et tenant corrects | +| `QueryExecute` | API ApplicationService opérationnelle | +| `QueryScalarExecute` | requêtes scalaires (`Count`) opérationnelles | +| AD API (`Validator`) | API Application Dictionary opérationnelle | + +Sortie attendue : + +``` +=== Profil LIMAGRAIN === + host=10.255.255.2 tenant=LIMAGRAI2512 saas=false + OK OAuth - token obtenu (age max ~1190s) + OK QueryExecute - 1 ligne(s) + OK QueryScalarExecute - 51160 produit(s) + OK AD API (Validator) - 10 element(s) + -> 4/4 tests reussis +``` + +Code de sortie `0` si tout passe, `1` sinon — utilisable tel quel dans une +tâche planifiée. + +C'est le premier réflexe quand Claude signale une erreur WMS : il isole en +quelques secondes une panne de connectivité d'un problème de requête. + +--- + +## 6. État des caches + +Deux caches, TTL commun `WORKFLOW_CACHE_TTL` (1 h par défaut), tous deux vidés +à chaque `switch_wms_profile` (D8). + +| Cache | Contenu | Purge | +|---|---|---| +| workflows | ~3 700 workflows | TTL, ou bascule de profil | +| AD | un cache **par type** (20 types, ~38 800 éléments) | TTL par type, ou bascule de profil | + +**Les inspecter sans redémarrer** : l'outil `get_application_summary` liste les +types chargés et leur nombre d'éléments — un type absent signifie simplement +qu'il n'a jamais été demandé dans cette session (D10). + +Signature d'un chargement dans les logs : + +``` +[AD] Cache expired or empty, fetching Resource... +[AD] Fetching Resource: offset=0, pageSize=15000 +[AD] Fetched 15000 Resource (total: 15000) +[AD] Fetching Resource: offset=15000, pageSize=15000 +... +[AD] Successfully cached 29374 Resource +[AD] Cache hit: Resource (29374 elements) <- appels suivants +``` + +Une première requête sur `Resource` prend plusieurs dizaines de secondes : ce +n'est pas un blocage, c'est la pagination. Les suivantes sont instantanées. + +--- + +## 7. Symptômes → causes + +| Symptôme | Cause probable | Vérification | +|---|---|---| +| Le serveur n'apparaît pas dans Claude Desktop | chemin invalide dans `claude_desktop_config.json`, ou Claude pas redémarré | ouvrir `%APPDATA%\Claude\logs\` | +| `Unexpected token … is not valid JSON` | quelque chose a écrit sur **stdout** | chercher un `console.log()` ajouté (D6) | +| `injecting env (0)` | `.env` introuvable | en packagé : le placer à côté de l'exe (D7) | +| `No WMS profile selected` | `DEFAULT_WMS_PROFILE` absent ou invalide | c'est un état normal — appeler `switch_wms_profile` | +| `Authentication failed: … 400` | `tenant_code` ou credentials erronés | `npm test -- ` (D2) | +| Boucle `refresh failed` / `Authenticating` | credentials invalides | `npm test` | +| `ETIMEDOUT` / `ECONNREFUSED` | host injoignable (VPN, pare-feu) | `Test-NetConnection -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..295c19b --- /dev/null +++ b/README.md @@ -0,0 +1,152 @@ +# WMS MCP Server + +Serveur [MCP](https://modelcontextprotocol.io) qui donne à Claude un accès en +lecture à un WMS **EasyWMS** (Mecalux), pour le diagnostic et l'analyse. + +Concrètement, dans Claude Desktop : + +> « Combien de commandes sont bloquées en statut Release sur LIMAGRAIN ? » +> « Trouve les workflows qui touchent au réapprovisionnement. » +> « Cherche `Order 4711` dans les logs. » +> « Quels paramètres sont surchargés sur l'entrepôt 2 ? » + +**Architecture : 100 % API REST.** Aucun accès direct à Oracle — voir D1 dans +[DECISIONS.md](DECISIONS.md). + +--- + +## Ce que le serveur expose + +**23 outils** répartis en 8 familles : + +| Famille | Outils | +|---|---| +| Requêtes WMS | `query_wms_entities`, `count_wms_entities`, `get_entity_schema`, `search_wms_data` | +| API brutes | `call_query_api`, `execute_command` | +| Workflows | `search_workflows`, `get_workflow_details`, `list_workflow_categories` | +| Application Dictionary | `get_application_summary`, `get_ad_elements`, `search_ad_elements`, `get_ad_element_details`, `list_ad_types` | +| Métadonnées | `get_entity_metadata`, `generic_search` | +| Configuration | `get_system_parameters` | +| Profils | `list_wms_profiles`, `get_current_wms_profile`, `switch_wms_profile` | +| Logs | `read_recent_logs`, `list_log_files`, `search_logs` | + +**6 resources** de contexte : `wms://entities`, `wms://entity-schemas`, +`wms://query-examples`, `workflows://overview`, `api://catalog`, `logs://guide`. + +**Multi-WMS.** Plusieurs backends (clients, tenants) coexistent dans un seul +serveur ; Claude bascule à la demande avec `switch_wms_profile`. + +--- + +## Installation + +Prérequis : Node.js 18+ et un accès réseau au WMS (VPN si nécessaire). + +```bash +npm install +``` + +Copiez `.env.example` en `.env` et renseignez au moins un profil : + +```env +WMS_API_AUTH=Basic R05BOklFNGU3aXFoZHQ= +WMS_APPLICATION=EasyWMS +WMS_API_PATH=/ApplicationService/api +WMS_TOKEN_PATH=/EasySTS/OAuth/Token +WORKFLOW_API_PATH=/AD/api + +WMS_PROFILES=AD +DEFAULT_WMS_PROFILE=AD + +AD_HOST=10.255.255.2 +AD_USERNAME=... +AD_PASSWORD=... +AD_TENANT=AD +AD_SAAS=false +``` + +Vérifiez la connectivité — le test est en lecture seule : + +```bash +npm test +``` + +Sortie attendue : `4/4 tests reussis`. En cas d'échec, voir +[MONITORING.md](MONITORING.md) §7. + +--- + +## Brancher Claude Desktop + +Éditez `%APPDATA%\Claude\claude_desktop_config.json` : + +```json +{ + "mcpServers": { + "wms": { + "command": "node", + "args": ["D:\\chemin\\vers\\mcp-wms-api\\src\\index.js"] + } + } +} +``` + +Puis **fermez et rouvrez complètement** Claude Desktop. Les logs du serveur +apparaissent dans `%APPDATA%\Claude\logs\`. + +--- + +## Ajouter un WMS + +1. Ajoutez son nom à `WMS_PROFILES` (séparateur : virgule). +2. Définissez `_HOST`, `_USERNAME`, `_PASSWORD`, `_TENANT`. +3. Mettez `_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 -- `. + +--- + +## Compiler un exécutable Windows + +```bash +npm run build +``` + +Produit `dist/wms-mcp-server.exe` (~76 Mo, autonome, cible `node22-win-x64`). + +**Placez le `.env` à côté de l'exe** : en mode packagé, c'est là qu'il est lu, +et aucun credential n'est embarqué dans le binaire (D7). Les avertissements +`Cannot find module '@modelcontextprotocol/sdk/…'` pendant le build sont +normaux et sans effet (D18). + +Déploiement type sur la VM : + +``` +C:\WMS\mcp\wms-mcp-server.exe +C:\WMS\mcp\.env +``` + +--- + +## Documentation + +| Fichier | Contenu | +|---|---| +| [CLAUDE.md](CLAUDE.md) | Architecture, inventaire des outils, conventions de code | +| [DECISIONS.md](DECISIONS.md) | **Pourquoi** le code est ainsi + pièges vérifiés en production | +| [MONITORING.md](MONITORING.md) | Superviser le serveur MCP : logs, token, caches, symptômes | +| [docs/logs.md](docs/logs.md) | Accès aux logs du WMS | +| [docs/](docs/) | Références EasyWMS (API, entités) | + +--- + +## Sécurité + +- `.env` et `dist/` sont ignorés par git — **ne les committez jamais**. +- La validation TLS est désactivée pour accepter les certificats auto-signés + des WMS on-premise (D15). +- ⚠️ L'historique git contient un ancien fichier de configuration avec des mots + de passe en clair (commit `b59cbb3`). Ces credentials sont à considérer comme + compromis — voir D20. diff --git a/docs/README.md b/docs/README.md index 52e7cf3..2b2ba65 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,41 +1,45 @@ -# Documentation WMS +# Documentation de référence -Bienvenue dans la documentation du WMS ! +Documents de référence sur EasyWMS et ses API, conservés dans le dépôt pour +être consultables hors ligne et par un agent qui lit le code. -## Comment utiliser cette documentation +> ⚠️ **Ce dossier n'est pas exposé comme resource MCP.** Une resource `docs://` +> a existé sans jamais être branchée ; elle a été supprimée. Ces fichiers se +> lisent directement depuis le dépôt. Voir D19 dans +> [DECISIONS.md](../DECISIONS.md). -Cette documentation est automatiquement accessible via le serveur MCP. Claude peut lire tous les fichiers `.md` présents dans ce dossier et ses sous-dossiers. +## Contenu -## Organisation +| Fichier | Nature | +|---|---| +| [logs.md](logs.md) | **Rédigé pour ce projet** — accès aux logs WMS, outils, limites | +| [ad-api-validation.md](ad-api-validation.md) | **Rédigé pour ce projet** — campagne de validation curl des 19 types AD testés (17 valides) | +| [api/Application Service API Reference.md](api/Application%20Service%20API%20Reference.md) | Référence de l'API ApplicationService | +| [api/POST apiQueryExecute.md](api/POST%20apiQueryExecute.md) | Détail de l'endpoint `QueryExecute` | +| [entities/easywms_reading_entites.md](entities/easywms_reading_entites.md) | Catalogue des entités du modèle **Reading** | +| [entities/easywms_reading_entites_outboundorder.md](entities/easywms_reading_entites_outboundorder.md) | Détail de l'entité `OutboundOrder` | +| [entities/easywms_reading_entites_outboundorder_OutboundOrderStatus.md](entities/easywms_reading_entites_outboundorder_OutboundOrderStatus.md) | Valeurs de `OutboundOrderStatus` | +| `getting_started.md`, `docs_downloads/` | Extractions du portail documentaire Mecalux | +| `reference-queries-api.php` | Client PHP d'origine, source des patterns d'API. ⚠️ utilise `QueryType: 1` — ne pas recopier, voir D3 | -Organisez vos fichiers de documentation comme vous le souhaitez : +**Sur les extractions du portail** : ce sont des captures partielles. Leurs +liens internes pointent vers des pages non téléchargées (`ReleaseNotes.md`, +`video_tutorials/`, …) et ne fonctionnent pas. Le seul document réellement +exploitable de cet ensemble est +`docs_downloads/communications/EasyWMS_WebApi_en.html.md` (référence complète de +la Web API, ~320 Ko). -``` -docs/ -├── README.md (ce fichier) -├── getting-started.md (guide de démarrage) -├── api/ -│ ├── overview.md -│ └── endpoints.md -├── workflows/ -│ ├── reception.md -│ └── expedition.md -└── troubleshooting/ - └── common-errors.md -``` +## Où trouver le reste -## Comment ajouter de la documentation +| Question | Document | +|---|---| +| À quoi sert ce projet, comment l'installer | [../README.md](../README.md) | +| Architecture, outils, resources, conventions de code | [../CLAUDE.md](../CLAUDE.md) | +| Pourquoi le code est écrit ainsi, pièges vérifiés | [../DECISIONS.md](../DECISIONS.md) | +| Le serveur ne répond pas / comment le superviser | [../MONITORING.md](../MONITORING.md) | -1. Créez vos fichiers `.md` dans ce dossier ou dans des sous-dossiers -2. Redémarrez Claude Desktop -3. Claude pourra automatiquement lire tous vos fichiers de documentation +## Ajouter un document -## Accéder à la documentation depuis Claude - -Dans Claude Desktop, vous pouvez demander : - -- "Montre-moi le sommaire de la documentation" -- "Lis la documentation sur les workflows" -- "Affiche-moi la documentation de l'API" - -Claude aura accès à tous les fichiers `.md` présents ici ! +Déposez le `.md` dans le sous-dossier qui convient et **ajoutez sa ligne au +tableau ci-dessus**. Un document non listé ici est un document que personne ne +retrouvera. diff --git a/docs/getting_started.md b/docs/getting_started.md index 8a7e23f..229f044 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -1,3 +1,9 @@ +> ⚠️ **Capture partielle du portail documentaire Mecalux.** Ce sommaire est +> conservé pour mémoire : la quasi-totalité de ses liens pointe vers des pages +> qui n'ont pas été téléchargées et ne résolvent pas. Seul +> [docs_downloads/communications/EasyWMS_WebApi_en.html.md](docs_downloads/communications/EasyWMS_WebApi_en.html.md) +> est exploitable hors ligne. Voir [README.md](README.md). + # MAP - Documentation Portal ## Table des matières diff --git a/docs/logs.md b/docs/logs.md new file mode 100644 index 0000000..7279cb5 --- /dev/null +++ b/docs/logs.md @@ -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 | +|---|---| +| `\\\inetpub\logs\LogFiles\Mecalux` | logs applicatifs IIS, organisés en sous-dossiers par composant | +| `\\\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 `\\\...` 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é `_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`.