@ema.co/mcp-toolkit 2026.1.26 → 2026.1.27-10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @ema.co/mcp-toolkit might be problematic. Click here for more details.
- package/dist/mcp/handlers/action/index.js +17 -20
- package/dist/mcp/handlers/data/index.js +75 -6
- package/dist/mcp/handlers/deprecation.js +50 -0
- package/dist/mcp/handlers/env/index.js +3 -3
- package/dist/mcp/handlers/knowledge/index.js +44 -237
- package/dist/mcp/handlers/persona/create.js +63 -18
- package/dist/mcp/handlers/persona/index.js +9 -10
- package/dist/mcp/handlers/persona/update.js +6 -3
- package/dist/mcp/handlers/reference/index.js +15 -2
- package/dist/mcp/handlers/sync/index.js +3 -18
- package/dist/mcp/handlers/workflow/analyze.js +53 -105
- package/dist/mcp/handlers/workflow/deploy.js +160 -1
- package/dist/mcp/handlers/workflow/generate.js +8 -28
- package/dist/mcp/handlers/workflow/index.js +258 -85
- package/dist/mcp/handlers/workflow/modify.js +69 -26
- package/dist/mcp/handlers/workflow/optimize.js +22 -108
- package/dist/mcp/handlers/workflow/utils.js +0 -102
- package/dist/mcp/handlers-consolidated.js +15 -38
- package/dist/mcp/prompts.js +82 -44
- package/dist/mcp/resources.js +335 -3
- package/dist/mcp/server.js +242 -457
- package/dist/mcp/tools.js +44 -61
- package/dist/sdk/action-schema-parser.js +11 -5
- package/dist/sdk/client.js +81 -25
- package/dist/sdk/ema-client.js +11 -0
- package/dist/sdk/generated/deprecated-actions.js +171 -0
- package/dist/sdk/guidance.js +58 -35
- package/dist/sdk/index.js +8 -7
- package/dist/sdk/knowledge.js +216 -1932
- package/dist/sdk/quality-gates.js +60 -336
- package/dist/sdk/validation-rules.js +33 -0
- package/dist/sdk/workflow-fixer.js +29 -360
- package/dist/sdk/workflow-generator.js +2 -1
- package/dist/sdk/workflow-intent.js +43 -3
- package/dist/sdk/workflow-transformer.js +0 -342
- package/docs/dashboard-operations.md +35 -0
- package/docs/ema-user-guide.md +66 -0
- package/docs/mcp-tools-guide.md +74 -45
- package/package.json +2 -2
- package/dist/mcp/handlers/persona/analyze.js +0 -275
- package/dist/mcp/handlers/persona/compare.js +0 -32
- package/dist/mcp/handlers/workflow/compile.js +0 -39
- package/docs/DEBUG-ANALYSIS-unused-category-type-mismatch.md +0 -481
- package/docs/TODO-fix-analyzer-and-modify.md +0 -182
- package/resources/action-schema.json +0 -5678
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
*
|
|
9
9
|
* See: src/mcp/AGENTS.md for anti-patterns to avoid.
|
|
10
10
|
*/
|
|
11
|
-
import { detectWorkflowIssues, suggestWorkflowFixes } from "../../../sdk/knowledge.js";
|
|
12
11
|
import { sanitizeWorkflowForDeploy } from "./utils.js";
|
|
13
12
|
import { ensureSchemaRegistry } from "../../../sdk/workflow-validator.js";
|
|
13
|
+
import { hasKnowledgeSearchNodes } from "./deploy.js";
|
|
14
14
|
// ─────────────────────────────────────────────────────────────────────────
|
|
15
15
|
// Action Building Helpers (use ActionRegistry for metadata)
|
|
16
16
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -312,6 +312,8 @@ export async function handleWorkflowModify(args, client) {
|
|
|
312
312
|
const operations = args.operations;
|
|
313
313
|
const preview = Boolean(args.preview);
|
|
314
314
|
// If no structured operations provided, return guidance
|
|
315
|
+
// NOTE: This handler does NOT parse natural language - Agent must build operations
|
|
316
|
+
const userInput = args.input;
|
|
315
317
|
if (!operations || operations.length === 0) {
|
|
316
318
|
// Fetch persona for context
|
|
317
319
|
const persona = await client.getPersonaById(personaId);
|
|
@@ -323,32 +325,38 @@ export async function handleWorkflowModify(args, client) {
|
|
|
323
325
|
return { error: "Persona has no workflow_def" };
|
|
324
326
|
}
|
|
325
327
|
const actions = (workflow.actions || []);
|
|
326
|
-
// Run analysis to give Agent context
|
|
327
|
-
const issues = detectWorkflowIssues(workflow);
|
|
328
|
-
const suggestions = suggestWorkflowFixes(issues);
|
|
329
328
|
// Load schema registry for action catalog
|
|
330
329
|
const schemaRegistry = await ensureSchemaRegistry(client);
|
|
331
330
|
const availableActions = schemaRegistry.isLoaded()
|
|
332
331
|
? schemaRegistry.getAllActions().map(a => ({ name: a.name, category: a.category, description: a.description }))
|
|
333
332
|
: [];
|
|
334
333
|
return {
|
|
335
|
-
message:
|
|
334
|
+
message: userInput
|
|
335
|
+
? `You provided input="${userInput}". This handler requires STRUCTURED operations, not natural language. Build operations from the context below.`
|
|
336
|
+
: "No operations provided. Returning workflow context for Agent to build operations.",
|
|
336
337
|
persona_id: personaId,
|
|
337
338
|
persona_name: persona.name,
|
|
338
|
-
//
|
|
339
|
+
// If user provided input, remind them to convert to operations
|
|
340
|
+
...(userInput && {
|
|
341
|
+
_warning: "IMPORTANT: The 'input' parameter is for providing context to the Agent, not for direct execution. The Agent must convert this into structured 'operations'.",
|
|
342
|
+
user_intent: userInput,
|
|
343
|
+
}),
|
|
344
|
+
// Current workflow state (DATA for Agent)
|
|
339
345
|
current_nodes: actions.map(a => ({
|
|
340
346
|
name: a.name,
|
|
341
347
|
display_name: a.displaySettings?.displayName,
|
|
342
348
|
action_type: a.action?.name?.name,
|
|
343
349
|
})),
|
|
344
|
-
// Analysis
|
|
345
|
-
issues: issues.map(i => ({ severity: i.severity, type: i.type, node: i.node, reason: i.reason })),
|
|
346
|
-
suggestions,
|
|
347
350
|
// Available actions from registry
|
|
348
351
|
available_actions: availableActions.slice(0, 20), // Top 20 for context
|
|
349
352
|
// Guidance for Agent
|
|
350
|
-
|
|
351
|
-
|
|
353
|
+
_next_steps: [
|
|
354
|
+
userInput
|
|
355
|
+
? `1. Interpret the user intent: "${userInput}"`
|
|
356
|
+
: "1. Review current_nodes and available_actions above",
|
|
357
|
+
"2. Build ModificationOperation[] based on your analysis",
|
|
358
|
+
"3. Call again with operations=[{type:'insert'|'remove'|'rewire', ...}]",
|
|
359
|
+
],
|
|
352
360
|
// Example operation structures
|
|
353
361
|
example_operations: {
|
|
354
362
|
insert_hitl: {
|
|
@@ -392,9 +400,6 @@ export async function handleWorkflowModify(args, client) {
|
|
|
392
400
|
const result = applyWorkflowModifications(workflow, operations, schemaRegistry);
|
|
393
401
|
// Sanitize before deploy
|
|
394
402
|
const sanitized = sanitizeWorkflowForDeploy(result.workflow);
|
|
395
|
-
// Run validation
|
|
396
|
-
const postIssues = detectWorkflowIssues(sanitized);
|
|
397
|
-
const errors = postIssues.filter(i => i.severity === "critical");
|
|
398
403
|
if (preview) {
|
|
399
404
|
return {
|
|
400
405
|
preview: true,
|
|
@@ -406,30 +411,68 @@ export async function handleWorkflowModify(args, client) {
|
|
|
406
411
|
nodes_removed: result.nodesRemoved,
|
|
407
412
|
nodes_modified: result.nodesModified,
|
|
408
413
|
connections_changed: result.connectionsChanged,
|
|
409
|
-
//
|
|
410
|
-
validation_errors: errors.map(e => e.reason || e.type),
|
|
411
|
-
validation_warnings: postIssues.filter(i => i.severity === "warning").map(w => w.reason || w.type),
|
|
412
|
-
// The modified workflow
|
|
414
|
+
// The modified workflow (LLM should analyze with ema://rules/*)
|
|
413
415
|
modified_workflow: sanitized,
|
|
414
|
-
_tip:
|
|
415
|
-
? "Fix validation errors before deploying"
|
|
416
|
-
: "Preview looks good. Call without preview=true to deploy",
|
|
416
|
+
_tip: "Preview complete. Use ema://rules/anti-patterns to check for issues. Call without preview=true to deploy.",
|
|
417
417
|
};
|
|
418
418
|
}
|
|
419
|
-
//
|
|
420
|
-
|
|
419
|
+
// VALIDATION: Check search nodes vs data sources consistency
|
|
420
|
+
// Same validation as deploy.ts to prevent deploying workflows that won't use uploaded documents
|
|
421
|
+
const hasSearchNodes = hasKnowledgeSearchNodes(sanitized);
|
|
422
|
+
let dataSourceStats;
|
|
423
|
+
try {
|
|
424
|
+
dataSourceStats = await client.getDataSourceAggregates(personaId);
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
// If we can't check stats, continue but log
|
|
428
|
+
console.error(`[modify] Could not verify data sources for ${personaId}`);
|
|
429
|
+
}
|
|
430
|
+
// VALIDATION: If persona has indexed data sources but workflow has NO search nodes → BLOCK
|
|
431
|
+
if (!hasSearchNodes && dataSourceStats && dataSourceStats.success > 0) {
|
|
421
432
|
return {
|
|
422
|
-
error: "
|
|
423
|
-
|
|
433
|
+
error: "DEPLOYMENT BLOCKED: Persona has indexed knowledge files but workflow has no search nodes. The uploaded documents will NEVER be used.",
|
|
434
|
+
validation_failed: "data_sources_require_search_nodes",
|
|
435
|
+
current_state: {
|
|
436
|
+
search_nodes_detected: false,
|
|
437
|
+
data_sources_total: dataSourceStats.total,
|
|
438
|
+
data_sources_indexed: dataSourceStats.success,
|
|
439
|
+
},
|
|
440
|
+
changes_attempted: result.changesApplied,
|
|
441
|
+
_fix: [
|
|
442
|
+
"Your workflow needs a search node to query the uploaded documents.",
|
|
443
|
+
"Add a search/v2 node to your operations:",
|
|
444
|
+
"operations=[{type:'insert', insert:{action_type:'search/v2', display_name:'Knowledge Search'}}]",
|
|
445
|
+
],
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
// VALIDATION: If workflow has search nodes but no indexed data sources → BLOCK
|
|
449
|
+
if (hasSearchNodes && dataSourceStats && dataSourceStats.success === 0) {
|
|
450
|
+
return {
|
|
451
|
+
error: "DEPLOYMENT BLOCKED: Workflow contains search nodes but no data sources have been indexed.",
|
|
452
|
+
validation_failed: "search_nodes_require_indexed_data",
|
|
453
|
+
current_state: {
|
|
454
|
+
search_nodes_detected: true,
|
|
455
|
+
data_sources_total: dataSourceStats.total,
|
|
456
|
+
data_sources_indexed: 0,
|
|
457
|
+
},
|
|
424
458
|
changes_attempted: result.changesApplied,
|
|
425
|
-
|
|
459
|
+
_fix: [
|
|
460
|
+
"Upload and index documents first:",
|
|
461
|
+
"1. persona(id='...', data={method:'upload', path:'/path/to/doc'})",
|
|
462
|
+
"2. persona(id='...', data={method:'stats'}) → wait for success > 0",
|
|
463
|
+
"3. Then retry the workflow modification",
|
|
464
|
+
],
|
|
426
465
|
};
|
|
427
466
|
}
|
|
428
467
|
// Deploy the workflow
|
|
468
|
+
// CRITICAL: Must include proto_config alongside workflow for changes to persist
|
|
469
|
+
// Always use the FULL existing proto_config from the persona
|
|
470
|
+
const existingProtoConfig = (persona.proto_config ?? {});
|
|
429
471
|
try {
|
|
430
472
|
await client.updateAiEmployee({
|
|
431
473
|
persona_id: personaId,
|
|
432
474
|
workflow: sanitized,
|
|
475
|
+
proto_config: existingProtoConfig,
|
|
433
476
|
});
|
|
434
477
|
return {
|
|
435
478
|
success: true,
|
|
@@ -1,80 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Workflow Optimize Handler
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* DEPRECATED: This handler returned pre-computed fixes which violates
|
|
5
|
+
* the "MCP = data, LLM = logic" principle.
|
|
6
|
+
*
|
|
7
|
+
* The LLM should:
|
|
8
|
+
* 1. Get workflow with workflow(mode="get")
|
|
9
|
+
* 2. Fetch rules from ema://rules/anti-patterns
|
|
10
|
+
* 3. Apply rules and propose fixes
|
|
11
|
+
* 4. Deploy via workflow(mode="deploy")
|
|
5
12
|
*/
|
|
6
|
-
import { detectWorkflowIssues, suggestWorkflowFixes } from "../../../sdk/knowledge.js";
|
|
7
|
-
/**
|
|
8
|
-
* Apply simple workflow fixes for auto_fixable issues
|
|
9
|
-
* Handles orphan removal with cascading dependency cleanup
|
|
10
|
-
*/
|
|
11
|
-
function applySimpleFixes(workflow, issues) {
|
|
12
|
-
// Deep clone to avoid mutating original
|
|
13
|
-
const fixed = JSON.parse(JSON.stringify(workflow));
|
|
14
|
-
// Get nodes array from workflow (handle different structures)
|
|
15
|
-
const getNodes = (w) => {
|
|
16
|
-
if (Array.isArray(w.nodes))
|
|
17
|
-
return w.nodes;
|
|
18
|
-
const wd = w.workflow_def;
|
|
19
|
-
if (wd && Array.isArray(wd.nodes))
|
|
20
|
-
return wd.nodes;
|
|
21
|
-
return undefined;
|
|
22
|
-
};
|
|
23
|
-
const nodes = getNodes(fixed);
|
|
24
|
-
if (!nodes)
|
|
25
|
-
return fixed;
|
|
26
|
-
// Collect all orphan node IDs to remove
|
|
27
|
-
const nodesToRemove = new Set();
|
|
28
|
-
for (const issue of issues) {
|
|
29
|
-
if (issue.type === "orphan" && issue.auto_fixable && issue.node) {
|
|
30
|
-
nodesToRemove.add(issue.node);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
if (nodesToRemove.size === 0)
|
|
34
|
-
return fixed;
|
|
35
|
-
// Step 1: Remove orphan nodes
|
|
36
|
-
const filteredNodes = nodes.filter(n => {
|
|
37
|
-
const nodeId = n.id;
|
|
38
|
-
return nodeId && !nodesToRemove.has(nodeId);
|
|
39
|
-
});
|
|
40
|
-
// Step 2: Clean up dangling references in remaining nodes
|
|
41
|
-
for (const node of filteredNodes) {
|
|
42
|
-
const incomingEdges = node.incoming_edges;
|
|
43
|
-
if (incomingEdges && Array.isArray(incomingEdges)) {
|
|
44
|
-
// Filter out edges that reference removed nodes
|
|
45
|
-
node.incoming_edges = incomingEdges.filter(edge => {
|
|
46
|
-
const sourceNodeId = edge.source_node_id;
|
|
47
|
-
return sourceNodeId && !nodesToRemove.has(sourceNodeId);
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
// Also clean up inputBindings if they exist (different workflow format)
|
|
51
|
-
const inputBindings = node.inputBindings;
|
|
52
|
-
if (inputBindings && Array.isArray(inputBindings)) {
|
|
53
|
-
node.inputBindings = inputBindings.filter(binding => {
|
|
54
|
-
const actionOutput = binding.actionOutput;
|
|
55
|
-
const actionName = actionOutput?.actionName;
|
|
56
|
-
return !actionName || !nodesToRemove.has(actionName);
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
// Update nodes in the workflow
|
|
61
|
-
if (Array.isArray(fixed.nodes)) {
|
|
62
|
-
fixed.nodes = filteredNodes;
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
const wd = fixed.workflow_def;
|
|
66
|
-
if (wd) {
|
|
67
|
-
wd.nodes = filteredNodes;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return fixed;
|
|
71
|
-
}
|
|
72
13
|
/**
|
|
73
14
|
* Handle workflow optimize mode
|
|
15
|
+
*
|
|
16
|
+
* NOW DEPRECATED - Returns guidance for LLM to do the analysis.
|
|
74
17
|
*/
|
|
75
18
|
export async function handleWorkflowOptimize(args, client) {
|
|
76
19
|
const personaId = args.persona_id;
|
|
77
|
-
const preview = args.preview !== false;
|
|
78
20
|
if (!personaId) {
|
|
79
21
|
return { error: "persona_id required for optimize mode" };
|
|
80
22
|
}
|
|
@@ -89,48 +31,20 @@ export async function handleWorkflowOptimize(args, client) {
|
|
|
89
31
|
hint: "Use mode='generate' to create a workflow first",
|
|
90
32
|
};
|
|
91
33
|
}
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
const fixes = suggestWorkflowFixes(issues);
|
|
95
|
-
if (issues.length === 0) {
|
|
96
|
-
return {
|
|
97
|
-
mode: "optimize",
|
|
98
|
-
status: "✅ No issues found",
|
|
99
|
-
persona_id: personaId,
|
|
100
|
-
persona_name: persona.name,
|
|
101
|
-
workflow_healthy: true,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
// Apply fixes
|
|
105
|
-
const fixedWorkflow = applySimpleFixes(existingWorkflow, issues);
|
|
106
|
-
const result = {
|
|
34
|
+
// Return the workflow and guidance for LLM to do the analysis
|
|
35
|
+
return {
|
|
107
36
|
mode: "optimize",
|
|
108
|
-
status:
|
|
37
|
+
status: "DEPRECATED - Use LLM analysis instead",
|
|
109
38
|
persona_id: personaId,
|
|
110
39
|
persona_name: persona.name,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
40
|
+
// Return the workflow for LLM to analyze
|
|
41
|
+
workflow_def: existingWorkflow,
|
|
42
|
+
_next_steps: [
|
|
43
|
+
"1. Use workflow(mode='get') to get workflow data",
|
|
44
|
+
"2. Fetch ema://rules/anti-patterns",
|
|
45
|
+
"3. Apply rules to find issues (LLM does this, not MCP)",
|
|
46
|
+
"4. Modify workflow based on analysis",
|
|
47
|
+
"5. Deploy via workflow(mode='deploy', workflow_def={...})",
|
|
48
|
+
],
|
|
119
49
|
};
|
|
120
|
-
// If preview=false, deploy the fixed workflow
|
|
121
|
-
if (!preview) {
|
|
122
|
-
await client.updateAiEmployee({
|
|
123
|
-
persona_id: personaId,
|
|
124
|
-
workflow: fixedWorkflow,
|
|
125
|
-
proto_config: args.proto_config || persona.proto_config,
|
|
126
|
-
});
|
|
127
|
-
result.deployed = true;
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
result.next_steps = [
|
|
131
|
-
"Review the suggested fixes and fixed_workflow",
|
|
132
|
-
`Deploy with: workflow(mode="optimize", persona_id="${personaId}", preview=false)`,
|
|
133
|
-
];
|
|
134
|
-
}
|
|
135
|
-
return result;
|
|
136
50
|
}
|
|
@@ -1,30 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared utilities for workflow handlers
|
|
3
3
|
*/
|
|
4
|
-
/**
|
|
5
|
-
* Get the type/category of an action node
|
|
6
|
-
*/
|
|
7
|
-
export function getActionType(action) {
|
|
8
|
-
const name = action.name;
|
|
9
|
-
if (!name)
|
|
10
|
-
return "unknown";
|
|
11
|
-
// Categorize by action name patterns
|
|
12
|
-
if (name.includes("hitl") || name.includes("human"))
|
|
13
|
-
return "hitl";
|
|
14
|
-
if (name.includes("email") || name.includes("send"))
|
|
15
|
-
return "communication";
|
|
16
|
-
if (name.includes("search") || name.includes("retrieve"))
|
|
17
|
-
return "retrieval";
|
|
18
|
-
if (name.includes("classify") || name.includes("categorize"))
|
|
19
|
-
return "classification";
|
|
20
|
-
if (name.includes("generate") || name.includes("compose"))
|
|
21
|
-
return "generation";
|
|
22
|
-
if (name.includes("output") || name === "WORKFLOW_OUTPUT")
|
|
23
|
-
return "output";
|
|
24
|
-
if (name.includes("input") || name === "WORKFLOW_INPUT")
|
|
25
|
-
return "input";
|
|
26
|
-
return "processing";
|
|
27
|
-
}
|
|
28
4
|
/**
|
|
29
5
|
* Sanitize workflow for deployment
|
|
30
6
|
* Removes internal-only fields, validates structure
|
|
@@ -52,81 +28,3 @@ export function sanitizeWorkflowForDeploy(workflow) {
|
|
|
52
28
|
});
|
|
53
29
|
return sanitized;
|
|
54
30
|
}
|
|
55
|
-
/**
|
|
56
|
-
* Check if a node has a path to the output
|
|
57
|
-
*/
|
|
58
|
-
export function checkNodeHasPath(nodeId, connections, outputNodeId, visited = new Set()) {
|
|
59
|
-
if (nodeId === outputNodeId)
|
|
60
|
-
return true;
|
|
61
|
-
if (visited.has(nodeId))
|
|
62
|
-
return false;
|
|
63
|
-
visited.add(nodeId);
|
|
64
|
-
const outgoing = connections.filter(c => c.from === nodeId);
|
|
65
|
-
for (const conn of outgoing) {
|
|
66
|
-
if (checkNodeHasPath(conn.to, connections, outputNodeId, visited)) {
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Find orphan nodes (nodes with no path to output)
|
|
74
|
-
*/
|
|
75
|
-
export function findOrphanNodes(workflow) {
|
|
76
|
-
const actions = workflow.actions;
|
|
77
|
-
if (!actions || actions.length === 0)
|
|
78
|
-
return [];
|
|
79
|
-
// Find output node
|
|
80
|
-
const outputNode = actions.find(a => a.name?.includes("OUTPUT") ||
|
|
81
|
-
a.id?.includes("OUTPUT"));
|
|
82
|
-
if (!outputNode) {
|
|
83
|
-
// No output node - can't determine orphans
|
|
84
|
-
return [];
|
|
85
|
-
}
|
|
86
|
-
const outputId = (outputNode.id ?? outputNode.name);
|
|
87
|
-
// Build connection list
|
|
88
|
-
const connections = [];
|
|
89
|
-
for (const action of actions) {
|
|
90
|
-
const inputs = action.inputs;
|
|
91
|
-
if (inputs) {
|
|
92
|
-
const actionId = (action.id ?? action.name);
|
|
93
|
-
for (const [_key, value] of Object.entries(inputs)) {
|
|
94
|
-
if (typeof value === "string" && value.includes(".")) {
|
|
95
|
-
const sourceAction = value.split(".")[0];
|
|
96
|
-
connections.push({ from: sourceAction, to: actionId });
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
// Find orphans
|
|
102
|
-
const orphans = [];
|
|
103
|
-
for (const action of actions) {
|
|
104
|
-
const actionId = (action.id ?? action.name);
|
|
105
|
-
const actionName = action.name;
|
|
106
|
-
if (actionId === outputId)
|
|
107
|
-
continue; // Skip output node
|
|
108
|
-
if (!checkNodeHasPath(actionId, connections, outputId)) {
|
|
109
|
-
orphans.push({
|
|
110
|
-
id: actionId,
|
|
111
|
-
name: actionName,
|
|
112
|
-
reason: "No path to output node",
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return orphans;
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Extract enum type name from a value
|
|
120
|
-
*/
|
|
121
|
-
export function extractEnumTypeName(value) {
|
|
122
|
-
if (typeof value === "object" && value !== null) {
|
|
123
|
-
const obj = value;
|
|
124
|
-
if (typeof obj.name === "string")
|
|
125
|
-
return obj.name;
|
|
126
|
-
if (typeof obj.type === "string")
|
|
127
|
-
return obj.type;
|
|
128
|
-
}
|
|
129
|
-
if (typeof value === "string")
|
|
130
|
-
return value;
|
|
131
|
-
return undefined;
|
|
132
|
-
}
|
|
@@ -28,37 +28,8 @@ import { handleWorkflow as handleWorkflowExtracted } from "./handlers/workflow/i
|
|
|
28
28
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
29
29
|
// isValidWidget and sanitizeWidgets imported from ../sdk/proto-config.js
|
|
30
30
|
// validateWidgetsForApi imported from handlers/utils.ts
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
34
|
-
const DEPRECATED_PARAMS = {
|
|
35
|
-
identifier: { newName: "id", removeVersion: "2.0.0" },
|
|
36
|
-
clone_from: { newName: "from", removeVersion: "2.0.0" },
|
|
37
|
-
template_id: { newName: "from", removeVersion: "2.0.0" },
|
|
38
|
-
clone_data: { newName: "include_data", removeVersion: "2.0.0" },
|
|
39
|
-
};
|
|
40
|
-
/**
|
|
41
|
-
* Check for deprecated params and collect warnings.
|
|
42
|
-
* Returns array of warning messages for deprecated params that were used.
|
|
43
|
-
*/
|
|
44
|
-
function checkDeprecatedParams(args) {
|
|
45
|
-
const warnings = [];
|
|
46
|
-
for (const [oldName, { newName, removeVersion }] of Object.entries(DEPRECATED_PARAMS)) {
|
|
47
|
-
if (args[oldName] !== undefined) {
|
|
48
|
-
warnings.push(`'${oldName}' is deprecated, use '${newName}' instead (will be removed in v${removeVersion})`);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return warnings;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Add deprecation warnings to response if any deprecated params were used.
|
|
55
|
-
*/
|
|
56
|
-
function addDeprecationWarnings(result, warnings) {
|
|
57
|
-
if (warnings.length > 0) {
|
|
58
|
-
return { ...result, _deprecation_warnings: warnings };
|
|
59
|
-
}
|
|
60
|
-
return result;
|
|
61
|
-
}
|
|
31
|
+
// Deprecation tracking - single source of truth
|
|
32
|
+
import { checkDeprecatedParams, addDeprecationWarnings } from "./handlers/deprecation.js";
|
|
62
33
|
// Template utilities imported from handlers/utils.ts:
|
|
63
34
|
// - normalizeTriggerType
|
|
64
35
|
// - getTemplates
|
|
@@ -229,8 +200,8 @@ export async function handlePersona(args, client, getTemplateId, createClientFor
|
|
|
229
200
|
return {
|
|
230
201
|
error: "Explicit method required",
|
|
231
202
|
message: "You provided a persona id/name. What operation would you like to perform?",
|
|
232
|
-
valid_methods: ["get", "update", "delete", "
|
|
233
|
-
hint: "
|
|
203
|
+
valid_methods: ["get", "update", "delete", "sanitize", "snapshot", "history", "restore"],
|
|
204
|
+
hint: "LLM does analysis/comparison. Use method='get' to fetch data, then reason about it.",
|
|
234
205
|
example: `persona(method="get", id="${idOrName}")`,
|
|
235
206
|
};
|
|
236
207
|
}
|
|
@@ -316,16 +287,22 @@ export async function handlePersona(args, client, getTemplateId, createClientFor
|
|
|
316
287
|
return { error: `Failed to get schema: ${error instanceof Error ? error.message : String(error)}` };
|
|
317
288
|
}
|
|
318
289
|
}
|
|
319
|
-
//
|
|
320
|
-
case "compare":
|
|
290
|
+
// Clone and create - handled by extracted handlers
|
|
321
291
|
case "clone":
|
|
322
|
-
case "create":
|
|
323
|
-
case "analyze": {
|
|
292
|
+
case "create": {
|
|
324
293
|
// These modes have been extracted to handlers/persona/*.ts
|
|
325
294
|
// - create.ts: handleCreate (~410 lines)
|
|
326
|
-
// - analyze.ts: handleAnalyze (~195 lines)
|
|
327
295
|
return { error: `Mode "${effectiveMode}" should be handled by extracted handler` };
|
|
328
296
|
}
|
|
297
|
+
// Analyze and compare - LLM does this, not MCP
|
|
298
|
+
case "analyze":
|
|
299
|
+
case "compare": {
|
|
300
|
+
return {
|
|
301
|
+
error: `Method "${effectiveMode}" removed - LLM does analysis/comparison`,
|
|
302
|
+
hint: "Use method='get' to fetch persona data, then do your own analysis/comparison.",
|
|
303
|
+
example: `persona(method="get", id="...", include_workflow=true)`,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
329
306
|
// ─────────────── Version Management Modes ───────────────
|
|
330
307
|
// Extracted to handlers/persona/version.ts
|
|
331
308
|
case "snapshot":
|