Files
mcp-wms-api/src/tools/api-tools.js
T
Arthur Ria 5386f54922 L2.2 : rejette les paramètres inconnus et les requis manquants (D23)
Mesure V1 (24/08/2026) : le SDK MCP ignore additionalProperties: false —
read_recent_logs({lines: 5}) avec la clause sur le schéma répondait
success: true, returnedLines: 100 (retombée silencieuse sur le défaut).
La validation vit donc dans le wrapper tools/call de src/index.js,
pilotée par les schémas de la table de routage (D22) : paramètre inconnu
ou requis manquant -> erreur structurée nommant le fautif et les
paramètres valides, avant tout dispatch.

Les 23 schémas portent additionalProperties: false — inerte côté SDK,
mais c'est le contrat que lisent les clients. Pas de renommage de
paramètres (écarté, cf. ROADMAP).

Mesures (via le protocole) :
- read_recent_logs({"lines": 5}) -> "Paramètre(s) inconnu(s) pour
  read_recent_logs : lines. Paramètres valides : count, log_file."
- read_recent_logs({"count": 5}) -> succès, returnedLines: 5
- boucle sur 22 outils avec {} (execute_command vérifié statiquement) :
  22/22 répondent, aucun Unknown tool, les 11 outils à paramètres requis
  échouent avec le message actionnable
- handshake : 23 outils, 6 resources

Docs : D23 dans DECISIONS.md, L2.2 retirée de la ROADMAP.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 17:32:15 +02:00

195 lines
5.3 KiB
JavaScript

const apiService = require('../services/api-service').getInstance();
const entityResolver = require('../services/entity-resolver');
/**
* 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,
},
},
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 } = args;
try {
// Résolution Name/TableName -> TableName (D21) — échec avant appel réseau
// sur nom inconnu.
const { tableName, warning } = await entityResolver.resolveEntityType(entity_type);
// 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,
});
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
success: true,
entityType: entity_type,
resolvedTableName: tableName,
...(warning ? { warning } : {}),
result,
},
null,
2
),
},
],
};
} catch (err) {
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
success: false,
error: err.message,
},
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,
};