/** * Metadata & GenericSearch Tools * - get_entity_metadata : list queryable entities and their properties (Metadata API) * - generic_search : full-text search across indexed WMS documents (GenericSearch API) */ const apiService = require('../services/api-service').getInstance(); function listTools() { return [ { name: 'get_entity_metadata', description: `Get metadata for WMS entities — field names, types, and assembly info — without querying actual data. Use this to discover the exact field names and types for any entity before building a filter. - Without entity_name: lists all queryable entities - With entity_name (partial match ok, case-insensitive): returns field names + types for that entity`, inputSchema: { type: 'object', additionalProperties: false, properties: { entity_name: { type: 'string', description: 'Partial entity name to look up (e.g. "OutboundOrder", "Task", "Stock"). Omit to list all entities.', }, }, }, }, { name: 'generic_search', description: `Full-text search across indexed WMS documents (faster than LINQ for keyword searches). First call without arguments to get available categories, then search within them. Examples: - generic_search(query="ORD-2024-001") — find an order by code - generic_search(query="picking error", categories=["Tasks"]) — search tasks - generic_search() — list available search categories`, inputSchema: { type: 'object', additionalProperties: false, properties: { query: { type: 'string', description: 'Search keyword or phrase', }, categories: { type: 'array', items: { type: 'string' }, description: 'Document categories to search (omit to search all). Get available categories by calling without query.', }, limit: { type: 'number', description: 'Max results (default: 20)', default: 20, }, skip: { type: 'number', description: 'Offset for pagination (default: 0)', default: 0, }, }, }, }, ]; } async function executeTool(name, args) { switch (name) { case 'get_entity_metadata': return await getEntityMetadata(args); case 'generic_search': return await genericSearch(args); default: throw new Error(`Unknown metadata tool: ${name}`); } } async function getEntityMetadata(args) { const { entity_name } = args || {}; try { const entities = await apiService.getMetadataEntities(); if (!entity_name) { // Return full entity list return { content: [{ type: 'text', text: JSON.stringify({ success: true, totalEntities: Array.isArray(entities) ? entities.length : '?', entities, }, null, 2), }], }; } // Find matching entities (case-insensitive partial match) const search = entity_name.toLowerCase(); const matches = Array.isArray(entities) ? entities.filter(e => { const name = (e.FullName || e.Name || e.fullName || e.name || JSON.stringify(e)).toLowerCase(); return name.includes(search); }) : []; if (matches.length === 0) { return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: `No entity matching "${entity_name}" found`, tool: 'get_entity_metadata', availableCount: Array.isArray(entities) ? entities.length : '?', hint: 'Call get_entity_metadata without entity_name to see all entities', }, null, 2), }], isError: true, }; } // Fetch properties for each match (up to 3) const results = []; for (const entity of matches.slice(0, 3)) { const assemblyFullName = entity.AssemblyFullName || entity.assemblyFullName; const fullName = entity.FullName || entity.fullName; let properties = null; if (assemblyFullName && fullName) { try { properties = await apiService.getEntityProperties(assemblyFullName, fullName); } catch (err) { console.error(`[Metadata] Could not fetch properties for ${fullName}: ${err.message}`); } } results.push({ entity, properties }); } return { content: [{ type: 'text', text: JSON.stringify({ success: true, query: entity_name, matchCount: matches.length, results, }, null, 2), }], }; } catch (err) { return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: err.message, tool: 'get_entity_metadata' }, null, 2), }], isError: true, }; } } async function genericSearch(args) { const { query, categories = [], limit = 20, skip = 0 } = args || {}; try { // No query: return available categories if (!query) { const cats = await apiService.getSearchCategories(); return { content: [{ type: 'text', text: JSON.stringify({ success: true, availableCategories: cats, hint: 'Pass query + optional categories[] to search', }, null, 2), }], }; } const result = await apiService.genericSearch(query, categories, limit, skip); return { content: [{ type: 'text', text: JSON.stringify({ success: true, query, categories: categories.length > 0 ? categories : 'all', result, }, null, 2), }], }; } catch (err) { return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: err.message, tool: 'generic_search' }, null, 2), }], isError: true, }; } } module.exports = { listTools, executeTool };