const apiService = require('../services/api-service').getInstance(); const entityResolver = require('../services/entity-resolver'); const { assertValidQueryType } = require('../services/wms-query-service'); /** * Tools MCP pour interagir avec les APIs WMS */ /** * Liste des tools disponibles * @returns {Array} - Liste des tools */ function listTools() { return [ { name: 'call_query_api', description: 'Appelle l\'API Query du WMS pour interroger des entités (Containers, Stocks, Tasks, Products, etc.)', inputSchema: { type: 'object', additionalProperties: false, properties: { entity_type: { type: 'string', description: 'Type d\'entité — nom AD (Container) ou TableName (Containers), insensible à la casse, résolu via l\'API Metadata. Ex: Containers, Stocks, ProductLocations, Tasks, Products, Accounts, Suppliers, Kits, Alias, InboundOrders, Receptions, OutboundOrders. Liste complète via get_entity_metadata.', }, expression: { type: 'string', description: 'Expression LINQ (ex: "z => z" pour tout, "z => z.Id" pour les IDs uniquement)', default: 'z => z', }, filter: { type: 'string', description: 'Filtre LINQ optionnel (ex: "z.Id > 100")', }, limit: { type: 'number', description: 'Limite de résultats (défaut: 100)', default: 100, }, query_type: { type: 'number', description: 'QueryContextType (défaut: 0 = Reading — statuts en chaînes, à garder sauf raison explicite). Opt-in : 1 = Writing (statuts en ÉNUMÉRATIONS — les comparaisons de chaînes comme == "Release" ÉCHOUENT), 2 = DataWarehouse (souvent non configuré), 3 = Metrics (modèle de données distinct). En query_type != 0, un nom d\'entité inconnu du Metadata Reading est transmis tel quel avec un warning.', default: 0, }, }, required: ['entity_type'], }, }, { name: 'execute_command', description: 'Exécute une commande WMS (ATTENTION: peut modifier des données). Toujours récupérer la commande via get_ad_elements/get_ad_element_details avant d\'exécuter.', inputSchema: { type: 'object', additionalProperties: false, properties: { command_name: { type: 'string', description: 'InternalCommandName exact tel que retourné par get_ad_element_details (ex: "Mecalux.ITSW.EasyWMS.Modules.MasterData.Contracts.Commands.SupplierCreateCommand"). Ne pas ajouter de suffix assembly — cela cause une FileLoadException.', }, properties: { type: 'object', description: 'Propriétés de la commande. Pour toute commande de création (Create), un champ Id de type GUID est requis et doit être généré à la volée (ex: crypto.randomUUID() ou uuidv4()). Ne jamais laisser Id vide ou null.', }, }, required: ['command_name', 'properties'], }, }, ]; } /** * Exécute un tool API * @param {string} name - Nom du tool * @param {Object} args - Arguments du tool * @returns {Promise} - Résultat du tool */ async function executeTool(name, args) { switch (name) { case 'call_query_api': return await callQueryAPI(args); case 'execute_command': return await executeCommand(args); default: throw new Error(`Unknown API tool: ${name}`); } } /** * Tool: call_query_api */ async function callQueryAPI(args) { const { entity_type, expression = 'z => z', filter, limit = 100, query_type = 0 } = args; // Conservé hors du try : si la requête échoue ensuite côté WMS, le warning // de résolution (nom hors Reading) reste dans la réponse d'erreur. let resolution = null; try { // Garde de valeur avant tout réseau (D25) — le wrapper D23 ne valide pas // les valeurs. const queryType = assertValidQueryType(query_type); // Résolution Name/TableName -> TableName (D21) — échec avant appel réseau // sur nom inconnu, sauf en query_type != 0 (passage tel quel + warning, D25). resolution = await entityResolver.resolveEntityType(entity_type, { allowUnknown: queryType !== 0, }); const { tableName, warning } = resolution; // Expression = Context.Entity + optional Where + OrderBy (required by EF when Take is used) let linqExpression = `Context.${tableName}`; if (filter) { const whereExpr = /^\s*\w+\s*=>/.test(filter) ? filter : `z => ${filter}`; linqExpression += `.Where(${whereExpr})`; } linqExpression += `.OrderBy(z => z.Id)`; const result = await apiService.executeQuery(linqExpression, { take: limit || undefined, select: expression !== 'z => z' ? expression : undefined, queryType, }); return { content: [ { type: 'text', text: JSON.stringify( { success: true, entityType: entity_type, resolvedTableName: tableName, ...(warning ? { warning } : {}), ...(queryType !== 0 ? { queryType } : {}), result, }, null, 2 ), }, ], }; } catch (err) { return { content: [ { type: 'text', text: JSON.stringify( { success: false, error: err.message, ...(resolution?.warning ? { warning: resolution.warning } : {}), }, null, 2 ), }, ], isError: true, }; } } /** * Tool: execute_command */ async function executeCommand(args) { const { command_name, properties } = args; try { const result = await apiService.executeCommand(command_name, properties); return { content: [ { type: 'text', text: JSON.stringify( { success: true, command: command_name, result, }, null, 2 ), }, ], }; } catch (err) { return { content: [ { type: 'text', text: JSON.stringify( { success: false, error: err.message, }, null, 2 ), }, ], isError: true, }; } } module.exports = { listTools, executeTool, };