92de85cf53
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>
213 lines
7.0 KiB
JavaScript
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,
|
|
};
|