/** * WMS Query Tools * MCP tools for querying WMS entities via LINQ expressions */ const wmsQueryService = require('../services/wms-query-service'); /** * List available WMS query tools */ function listTools() { return [ { name: 'query_wms_entities', description: `Query WMS entities using LINQ expressions. Returns rows (up to 1000). Uses QueryExecute with QueryType=Reading by default — status fields are STRINGS (enum names, not integers). Other contexts via query_type (opt-in, see the parameter warning). Common entities: Products, Containers, Tasks, Stocks, ProductLocations, Locations, InboundOrders, OutboundOrders, Receptions, Accounts, Suppliers, Kits, Alias. Full list via get_entity_metadata. entity_type accepts the AD entity name (Container) or the TableName (Containers), case-insensitive — resolved via the Metadata API. IMPORTANT — before building a filter with a status/enum field: 1. Check docs first: read resource docs://entities/ (e.g. easywms_reading_entites_outboundorder_OutboundOrderStatus for OutboundOrders) 2. If no doc found: call get_ad_elements(element_type="Entity", search=entity_name) 3. Last resort: call get_entity_metadata(entity_name=...) to get field names Never guess enum string values — they differ between Reading and Writing models.`, inputSchema: { type: 'object', additionalProperties: false, properties: { entity_type: { type: 'string', description: 'Entity type — AD name (Container) or TableName (Containers), case-insensitive, resolved via the Metadata API. E.g. Products, Containers, Tasks, Stocks, ProductLocations, InboundOrders, OutboundOrders, Accounts, Suppliers, Kits, Alias, Receptions.', }, select_expression: { type: 'string', description: 'LINQ select expression (default: "z => z" for all fields, or "z => new { z.Id, z.Name }" for specific fields)', default: 'z => z', }, filter: { type: 'string', description: 'Optional LINQ filter (e.g., "z => z.Id > 100" or "z => z.Status == \'Active\'")', }, limit: { type: 'number', description: 'Maximum results to return (default: 100, max: 1000)', default: 100, }, query_type: { type: 'number', description: 'QueryContextType (default: 0 = Reading — status fields are strings, keep it unless you know why). Opt-in: 1 = Writing (status fields become ENUMS — string comparisons like == "Release" FAIL), 2 = DataWarehouse (often not configured), 3 = Metrics (different data model). Entity names unknown to the Reading metadata are passed through as-is with a warning when query_type != 0.', default: 0, }, }, required: ['entity_type'], }, }, { name: 'get_entity_schema', description: 'Get the schema/structure of a WMS entity by querying one sample record', inputSchema: { type: 'object', additionalProperties: false, properties: { entity_type: { type: 'string', description: 'Entity type to inspect', }, }, required: ['entity_type'], }, }, { name: 'count_wms_entities', description: `PREFERRED TOOL for counting ("combien", "nombre de", "how many"). Uses QueryScalarExecute → Reading model. Never adds Take(), safe on all entity types. IMPORTANT: Always use this tool first for any counting question — do not use query_wms_entities for counts. IMPORTANT — enum/status values MUST be verified before use. Reading model uses string enum names, NOT integers. Lookup order for enum values: 1. Read the relevant doc: docs://entities/ (e.g. easywms_reading_entites_outboundorder_OutboundOrderStatus for OutboundOrders) 2. If no doc: call get_ad_elements(element_type="Entity", search=entity_name) 3. Last resort: call get_entity_metadata(entity_name=...) — gives field names but not all enum values Never hardcode enum values without checking — Reading vs Writing model distinction has caused bugs before. Verified values (curl-tested): - Tasks actives: filter='z.TaskStatus == "InProcess" || z.TaskStatus == "Pending"' - Tasks en cours: filter='z.TaskStatus == "InProcess"' - Tasks en attente: filter='z.TaskStatus == "Pending"' - ODS lancés: filter='z.OutboundOrderStatus == "Release"' - OS incomplètes actives: filter='z.IncompleteOrder == true && z.IsActive == true' (par classe: ajouter && z.OutboundClassCode == "X") - Containers bloquants: filter='z.NumContainerPendingTasks > 0' (sur un emplacement: ajouter && z.LocationCode == "X")`, inputSchema: { type: 'object', additionalProperties: false, properties: { entity_type: { type: 'string', description: 'Entity type (Products, Containers, Tasks, Stocks, OutboundOrders, InboundOrders, etc.)', }, filter: { type: 'string', description: 'Optional LINQ filter condition. Status fields are strings (enum names from Reading model). Always verify enum values via docs://entities/ before use.', }, query_type: { type: 'number', description: 'QueryContextType (default: 0 = Reading — status fields are strings, keep it unless you know why). Opt-in: 1 = Writing (status fields become ENUMS — string comparisons like == "Release" FAIL), 2 = DataWarehouse (often not configured), 3 = Metrics (different data model). Entity names unknown to the Reading metadata are passed through as-is with a warning when query_type != 0.', default: 0, }, }, required: ['entity_type'], }, }, { name: 'search_wms_data', description: 'Search for a keyword across multiple WMS entities', inputSchema: { type: 'object', additionalProperties: false, properties: { keyword: { type: 'string', description: 'Keyword to search for', }, entity_types: { type: 'array', items: { type: 'string' }, description: 'List of entity types to search in (default: Products, Containers, Tasks)', }, limit: { type: 'number', description: 'Maximum results per entity (default: 50)', default: 50, }, }, required: ['keyword'], }, }, ]; } /** * Execute WMS query tool */ async function executeTool(name, args) { try { switch (name) { case 'query_wms_entities': return await queryWmsEntities(args); case 'count_wms_entities': return await countWmsEntities(args); case 'get_entity_schema': return await getEntitySchema(args); case 'search_wms_data': return await searchWmsData(args); default: throw new Error(`Unknown WMS query tool: ${name}`); } } catch (error) { console.error(`[WMSQueryTools] Error executing ${name}:`, error.message); return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message, tool: name }, null, 2) }], isError: true }; } } /** * Tool: query_wms_entities */ async function queryWmsEntities(args) { const { entity_type, select_expression = 'z => z', filter, limit = 100, query_type = 0 } = args; console.error(`[WMSQueryTools] Querying ${entity_type}: limit=${limit} query_type=${query_type}`); const result = await wmsQueryService.queryEntities( entity_type, select_expression, filter, limit, query_type ); return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...result }, null, 2) }] }; } /** * Tool: count_wms_entities */ async function countWmsEntities(args) { const { entity_type, filter, query_type = 0 } = args; console.error(`[WMSQueryTools] Counting ${entity_type}${filter ? ` where ${filter}` : ''} query_type=${query_type}`); const result = await wmsQueryService.countEntities(entity_type, filter || null, query_type); return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...result }, null, 2) }] }; } /** * Tool: get_entity_schema */ async function getEntitySchema(args) { const { entity_type } = args; console.error(`[WMSQueryTools] Getting schema for ${entity_type}`); const result = await wmsQueryService.getEntitySchema(entity_type); return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...result }, null, 2) }] }; } /** * Tool: search_wms_data */ async function searchWmsData(args) { const { keyword, entity_types = ['Products', 'Containers', 'Tasks'], limit = 50 } = args; console.error(`[WMSQueryTools] Searching for "${keyword}" in ${entity_types.join(', ')}`); const results = {}; let totalFound = 0; for (const entityType of entity_types) { try { const result = await wmsQueryService.searchEntities(entityType, keyword, limit); results[entityType] = { count: result.count, data: result.data }; totalFound += result.count; } catch (error) { console.error(`[WMSQueryTools] Search failed for ${entityType}:`, error.message); results[entityType] = { error: error.message, count: 0 }; } } return { content: [{ type: 'text', text: JSON.stringify({ success: true, keyword, totalFound, results }, null, 2) }] }; } module.exports = { listTools, executeTool, };