Files
mcp-wms-api/src/tools/workflow-tools.js
T
Arthur Ria 92de85cf53 L4.2 : paramètre application sur les outils AD et workflow (D26)
L'application venait de WMS_APPLICATION (partagée par tous les profils) : le
MCP n'interrogeait que EasyWMS, alors que CustomApp porte le spécifique
client (153 workflows CST_ sur ce tenant) et que 9 applications sont
déclarées par Application/GetAll.

Paramètre application (défaut : l'application du profil, comportement
inchangé sans lui) sur get_ad_elements, search_ad_elements,
get_ad_element_details, search_workflows, get_workflow_details,
list_workflow_categories. Clés de cache : ad-service passe par
(application, type), workflow-service par application — sans quoi un appel
CustomApp polluerait le cache EasyWMS. L'invalidation reste l'abonnement
onSwitch (D8), le chargement reste paresseux (aucun préchargement des 9
applications, D10). list_workflow_categories s'adosse à Application/GetAll
(liste allégée en cache : le blob data de chaque application pèse ~100 Ko) ;
get_application_summary regroupe par application et ne détaille que les
entrées en cache (D24), workflows compris. Acté en D26 ; CLAUDE.md mis à
jour (Caches, AD), L4.2 retiré de la ROADMAP.

Vérifications rejouées via le protocole (LIMAGRAIN / LIMAGRAI2512),
requêtes séquentielles :
- search_workflows(CST_, application:CustomApp) -> 5 objets peuplés dont
  CST_SendRejectContainersToPK ([Workflow] Successfully cached 153 workflows
  for "CustomApp").
- get_ad_elements(Workflow, application:CustomApp) -> count 153, éléments
  CST_* ([AD] Successfully cached 153 CustomApp::Workflow).
- Séquence EasyWMS -> CustomApp -> EasyWMS sur search_workflows : 4012 vs
  153, retour en cache hit ([Workflow] Using cached data for "EasyWMS"),
  aucune pollution ; get_application_summary montre les deux caches
  (workflowCachesByApplication EasyWMS 4012 / CustomApp 153).
- Sans paramètre application -> comportement inchangé (W1 = W3).
- switch_wms_profile EUROTRAFIC puis retour -> [Workflow] Cache cleared,
  [AD] All caches invalidated, [EntityResolver] Cache cleared ; summary vide.
- list_workflow_categories -> 9 applications, comptes réels des applications
  chargées.
Baseline : tools/list 23 (les 23 noms répondent), resources 6, rejet D23
d'un paramètre inconnu OK (search_workflows/applikation), npm test 4/4
exit 0.

Anomalie hors périmètre consignée dans ROADMAP.md : get_workflow_details
peut dépasser le seuil de rejet client (~101 800 caractères mesurés sur
CST_SendRejectContainersToPK), comportement antérieur au lot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 11:24:12 +02:00

213 lines
7.0 KiB
JavaScript

/**
* Workflow Tools
* MCP tools for searching and retrieving workflow information via API
*/
const workflowService = require('../services/workflow-service');
/**
* List available workflow tools
*/
function listTools() {
return [
{
name: 'search_workflows',
description: 'Search workflows by name. Returns matching workflows with metadata. Workflows are lazy-loaded from API on first request and cached for 1 hour, per application. Client-specific workflows (CST_* prefix) live in the "CustomApp" application — pass application: "CustomApp" to search them.',
inputSchema: {
type: 'object',
additionalProperties: false,
properties: {
query: {
type: 'string',
description: 'Search query (searches in workflow name)',
},
category: {
type: 'string',
description: 'Filter by the applicationName field of the returned workflows (workflows have no category field). Since `application` selects which application is fetched, all its workflows share the same applicationName — prefer `application` to change scope; `category` only narrows within the fetched set.',
},
limit: {
type: 'number',
description: 'Maximum results to return (default: 50)',
default: 50,
},
application: {
type: 'string',
description: 'AD application whose workflows are searched (default: the active profile\'s application, usually EasyWMS). Client-specific workflows live in "CustomApp". Full list via list_workflow_categories.',
},
},
},
},
{
name: 'get_workflow_details',
description: 'Get full details of a specific workflow by ID or name',
inputSchema: {
type: 'object',
additionalProperties: false,
properties: {
workflow_id: {
type: 'string',
description: 'Workflow ID or exact name',
},
application: {
type: 'string',
description: 'AD application the workflow belongs to (default: the active profile\'s application, usually EasyWMS). Client-specific workflows (CST_*) live in "CustomApp".',
},
},
required: ['workflow_id'],
},
},
{
name: 'list_workflow_categories',
description: 'List the AD applications declared on the tenant (Application/GetAll) with their workflow counts where already loaded. Workflows have no category field — the application is the only grouping. Use the `application` parameter of the workflow/AD tools to query a specific one (e.g. "CustomApp" for client-specific CST_* workflows).',
inputSchema: {
type: 'object',
additionalProperties: false,
properties: {
application: {
type: 'string',
description: 'Load and count the workflows of this application (default: the active profile\'s application). Other applications are listed without loading them.',
},
},
},
},
];
}
/**
* Execute workflow tool
*/
async function executeTool(name, args) {
try {
switch (name) {
case 'search_workflows':
return await searchWorkflows(args);
case 'get_workflow_details':
return await getWorkflowDetails(args);
case 'list_workflow_categories':
return await listWorkflowCategories(args);
default:
throw new Error(`Unknown workflow tool: ${name}`);
}
} catch (error) {
console.error(`[WorkflowTools] Error executing ${name}:`, error.message);
return {
content: [{
type: 'text',
text: JSON.stringify({
success: false,
error: error.message,
tool: name
}, null, 2)
}],
isError: true
};
}
}
/**
* Tool: search_workflows
*/
async function searchWorkflows(args) {
const { query, category, limit = 50, application } = args;
console.error(`[WorkflowTools] Searching workflows: query="${query}", category="${category}", limit=${limit}, application=${application || '(profil)'}`);
const results = await workflowService.searchWorkflows(query, category, limit, application);
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
...(application ? { application } : {}),
count: results.length,
// Clés réelles de l'API AD (minuscules, cf. D5) : id, name, version,
// applicationName, commonInfo. Pas de code/category/description.
workflows: results.map(w => {
const commonInfo = w.commonInfo || w.CommonInfo || {};
return {
id: w.id || w.Id,
name: w.name || w.Name,
applicationName: w.applicationName || w.ApplicationName,
version: w.version || w.Version,
createdBy: commonInfo.createdBy,
createDate: commonInfo.createDate,
updateDate: commonInfo.updateDate
};
})
}, null, 2)
}]
};
}
/**
* Tool: get_workflow_details
*/
async function getWorkflowDetails(args) {
const { workflow_id, application } = args;
console.error(`[WorkflowTools] Getting workflow details: ${workflow_id} (application: ${application || '(profil)'})`);
const workflow = await workflowService.getWorkflowDetails(workflow_id, application);
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
workflow
}, null, 2)
}]
};
}
/**
* Tool: list_workflow_categories
*/
async function listWorkflowCategories(args) {
const { application } = args || {};
console.error(`[WorkflowTools] Listing applications (workflow groupings), application=${application || '(profil)'}`);
// La liste vient d'Application/GetAll (9 applications sur le tenant mesuré),
// pas des applicationName du seul cache actif (D26). Seule l'application
// demandée (ou celle du profil) est chargée — pas de préchargement des
// autres (D10) : leurs comptes n'apparaissent que si déjà en cache.
const applications = await workflowService.fetchApplications();
const stats = await workflowService.getWorkflowStats(application);
const cacheStatus = workflowService.getCacheStatus();
const enriched = applications.map(a => ({
name: a.name,
version: a.version,
...(cacheStatus[a.name]
? { workflowCount: cacheStatus[a.name].count, cacheAge: cacheStatus[a.name].age }
: { workflowCount: null }),
}));
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
note: 'Workflows have no category field in the AD API — the application is the only grouping. workflowCount is only known for applications already loaded (lazy loading); pass application to search_workflows/get_ad_elements to load one.',
totalApplications: applications.length,
applications: enriched,
loaded: {
application: stats.application,
totalWorkflows: stats.total,
cacheAge: stats.cacheAge
}
}, null, 2)
}]
};
}
module.exports = {
listTools,
executeTool,
};