706e628715
QueryType était figé à 0 en dur dans executeQuery/executeScalarQuery : le modèle Writing, opérationnel et mesuré, était inatteignable. Paramètre query_type (défaut 0) sur call_query_api, query_wms_entities, count_wms_entities — get_entity_schema et search_wms_data restent des raccourcis Reading. D3 reste la règle par défaut : la bascule est un opt-in, avertie dans les descriptions d'outils (statuts en énumérations en Writing). Garde de valeur assertValidQueryType dans le code (le wrapper D23 ne valide pas les valeurs), avant tout réseau. En query_type != 0, un nom inconnu du Metadata Reading passe tel quel avec warning (allowUnknown du resolver) — conservé aussi dans la réponse d'erreur si le WMS échoue ensuite. Acté en D25 ; CLAUDE.md nuancé, L4.1 retiré de la ROADMAP (reliquat : exploration Metrics, rapport à part). Vérifications rejouées via le protocole (LIMAGRAIN / LIMAGRAI2512) : - call_query_api(Products, query_type:1, limit:1) -> success, 1 ligne, queryType:1 dans la réponse. - call_query_api(Products, query_type:3) -> erreur structurée contenant 'ApplicationMetricDataContext' ne contient pas de définition pour 'Products'. - call_query_api(Products, query_type:7) -> erreur locale nommant les 4 contextes, aucun [API] POST/GET dans stderr. - query_wms_entities(Container, limit:1) sans query_type -> comportement inchangé (resolvedTableName Containers, Reading, pas de champ queryType). - call_query_api(FooBar123, query_type:1) -> transmis tel quel (payload QueryType:1 Expression Context.FooBar123...), erreur WMS ApplicationWritingRepository + warning de résolution dans la réponse. - count_wms_entities(Product, query_type:1) -> count 51145 (~51160). Baseline : tools/list 23, resources 6, rejet D23 d'un paramètre inconnu OK. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
215 lines
6.5 KiB
JavaScript
215 lines
6.5 KiB
JavaScript
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<Object>} - 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,
|
|
};
|