a1acb781b0
Convention 3 impose { success: false, error, tool }, mais le champ tool n'etait
ajoute que par le wrapper de src/index.js. Les enveloppes construites
localement dans src/tools/ ne le portaient pas : call_query_api avec
query_type: 7 repondait { success: false, error: "query_type invalide : ..." },
sans tool. Le contrat etait donc respecte ou non selon le chemin d'erreur --
alors qu'il est lu par Claude, pas par un humain.
Balayage des 8 modules : 15 enveloppes success:false au total, 11 y ont gagne
le champ tool (ad-tools, config-tools, wms-query-tools et workflow-tools
l'avaient deja via le catch de leur executeTool). Les champs additionnels sont
conserves et passent apres tool : warning de resolution d'api-tools,
availableCount + hint de get_entity_metadata, listes de profils de
profile-tools.
Grep de controle -- 15 enveloppes, 0 sans tool :
src/tools/ad-tools.js:140 tool: name
src/tools/api-tools.js:170 tool: 'call_query_api'
src/tools/api-tools.js:217 tool: 'execute_command'
src/tools/config-tools.js:179 tool: 'get_system_parameters'
src/tools/log-tools.js:116 tool: 'list_log_files'
src/tools/log-tools.js:157 tool: 'read_recent_logs'
src/tools/log-tools.js:227 tool: 'search_logs'
src/tools/metadata-tools.js:110 tool: 'get_entity_metadata'
src/tools/metadata-tools.js:154 tool: 'get_entity_metadata'
src/tools/metadata-tools.js:197 tool: 'generic_search'
src/tools/profile-tools.js:111 tool: 'get_current_wms_profile'
src/tools/profile-tools.js:129 tool: 'switch_wms_profile'
src/tools/profile-tools.js:159 tool: 'switch_wms_profile'
src/tools/wms-query-tools.js:169 tool: name
src/tools/workflow-tools.js:117 tool: name
Verifie en execution (protocole, LIMAGRAIN sauf mention) -- 11 enveloppes
declenchees, toutes avec tool :
call_query_api query_type: 7 -> tool: call_query_api
get_entity_metadata entite inconnue -> tool + availableCount + hint
query_wms_entities entite inconnue -> tool: query_wms_entities
get_workflow_details id inconnu -> tool: get_workflow_details
get_ad_elements type inconnu -> tool: get_ad_elements
switch_wms_profile profil inconnu -> tool + profiles
read_recent_logs fichier inexistant -> tool: read_recent_logs
list_log_files / search_logs /
read_recent_logs sur EUROTRAFIC -> tool, garde SaaS D9 (sans reseau)
Trois catch restent couverts statiquement, faute de declencheur : celui
d'execute_command (interdit d'appel, il ecrit dans le WMS), celui de
generic_search (l'API tolere categorie inexistante comme limit negative :
elle repond success), et celui de get_current_wms_profile (inatteignable tant
qu'un profil par defaut se charge au demarrage).
Baseline preservee : 23 outils, 6 resources.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
205 lines
5.9 KiB
JavaScript
205 lines
5.9 KiB
JavaScript
/**
|
|
* 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 };
|