@ema.co/mcp-toolkit 2026.1.26 → 2026.1.27-1

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.

Files changed (44) hide show
  1. package/dist/mcp/handlers/action/index.js +17 -20
  2. package/dist/mcp/handlers/data/index.js +72 -6
  3. package/dist/mcp/handlers/deprecation.js +50 -0
  4. package/dist/mcp/handlers/env/index.js +3 -3
  5. package/dist/mcp/handlers/knowledge/index.js +44 -237
  6. package/dist/mcp/handlers/persona/create.js +47 -18
  7. package/dist/mcp/handlers/persona/index.js +9 -10
  8. package/dist/mcp/handlers/persona/update.js +4 -2
  9. package/dist/mcp/handlers/reference/index.js +15 -2
  10. package/dist/mcp/handlers/sync/index.js +3 -18
  11. package/dist/mcp/handlers/workflow/analyze.js +53 -105
  12. package/dist/mcp/handlers/workflow/deploy.js +129 -0
  13. package/dist/mcp/handlers/workflow/generate.js +8 -28
  14. package/dist/mcp/handlers/workflow/index.js +258 -85
  15. package/dist/mcp/handlers/workflow/modify.js +9 -29
  16. package/dist/mcp/handlers/workflow/optimize.js +22 -108
  17. package/dist/mcp/handlers/workflow/utils.js +0 -102
  18. package/dist/mcp/handlers-consolidated.js +15 -38
  19. package/dist/mcp/prompts.js +82 -44
  20. package/dist/mcp/resources.js +335 -3
  21. package/dist/mcp/server.js +242 -457
  22. package/dist/mcp/tools.js +44 -61
  23. package/dist/sdk/action-schema-parser.js +11 -5
  24. package/dist/sdk/client.js +46 -17
  25. package/dist/sdk/ema-client.js +11 -0
  26. package/dist/sdk/generated/deprecated-actions.js +171 -0
  27. package/dist/sdk/guidance.js +58 -35
  28. package/dist/sdk/index.js +8 -7
  29. package/dist/sdk/knowledge.js +216 -1932
  30. package/dist/sdk/quality-gates.js +60 -336
  31. package/dist/sdk/validation-rules.js +33 -0
  32. package/dist/sdk/workflow-fixer.js +29 -360
  33. package/dist/sdk/workflow-intent.js +43 -3
  34. package/dist/sdk/workflow-transformer.js +0 -342
  35. package/docs/dashboard-operations.md +35 -0
  36. package/docs/ema-user-guide.md +66 -0
  37. package/docs/mcp-tools-guide.md +74 -45
  38. package/package.json +2 -2
  39. package/dist/mcp/handlers/persona/analyze.js +0 -275
  40. package/dist/mcp/handlers/persona/compare.js +0 -32
  41. package/dist/mcp/handlers/workflow/compile.js +0 -39
  42. package/docs/DEBUG-ANALYSIS-unused-category-type-mismatch.md +0 -481
  43. package/docs/TODO-fix-analyzer-and-modify.md +0 -182
  44. package/resources/action-schema.json +0 -5678
@@ -1,43 +1,228 @@
1
1
  /**
2
- * Workflow Handler - Unified greenfield/brownfield operations
2
+ * Workflow Handler
3
3
  *
4
- * Modes:
5
- * - generate: Create new workflow from input
6
- * - modify/extend: Transform existing workflow
7
- * - analyze: Return current state + issues
8
- * - deploy: Deploy workflow to persona
9
- * - compare: Compare two workflow versions
10
- * - compile: Compile WorkflowSpec to workflow_def
11
- * - optimize: Auto-fix issues
4
+ * PUBLIC TOOL MODES (workflow tool):
5
+ * - get: Return workflow data + schema for LLM to generate/modify
6
+ * - deploy: Deploy LLM-generated workflow_def
12
7
  *
13
- * Note: During extraction, individual mode handlers are being moved here.
14
- * Until complete, delegates to handlers-consolidated.ts.
8
+ * INTERNAL MODES (called from persona tool, not exposed):
9
+ * - modify: Used internally by persona(method="update", input="...")
10
+ * - generate: Used internally for workflow generation
11
+ * - analyze: Used internally for workflow analysis
12
+ * - optimize: Used internally for auto-fix
13
+ * - compare: Used internally for comparison
14
+ *
15
+ * THE LLM DOES ALL THE THINKING. MCP provides data and executes.
15
16
  */
17
+ import { generateSchema } from "../../../sdk/generation-schema.js";
16
18
  // Re-export types
17
19
  export * from "./types.js";
18
- // Re-export utilities
20
+ // Re-export utilities
19
21
  export * from "./utils.js";
20
- // Import mode handlers
22
+ // Import internal handlers (not exposed as tool modes, but used internally)
21
23
  import { handleWorkflowAnalyze } from "./analyze.js";
22
24
  import { handleWorkflowCompare } from "./compare.js";
23
- import { handleWorkflowCompile } from "./compile.js";
24
25
  import { handleWorkflowOptimize } from "./optimize.js";
25
26
  import { handleWorkflowDeploy } from "./deploy.js";
26
27
  import { handleWorkflowGenerate } from "./generate.js";
27
28
  import { handleWorkflowModify } from "./modify.js";
28
- // Re-export mode handlers
29
+ // Re-export for internal use (persona handler routes to these)
29
30
  export { handleWorkflowAnalyze } from "./analyze.js";
30
31
  export { handleWorkflowCompare } from "./compare.js";
31
- export { handleWorkflowCompile } from "./compile.js";
32
32
  export { handleWorkflowOptimize } from "./optimize.js";
33
33
  export { handleWorkflowDeploy } from "./deploy.js";
34
34
  export { handleWorkflowGenerate } from "./generate.js";
35
35
  export { handleWorkflowModify } from "./modify.js";
36
36
  export { applyWorkflowModifications, buildWorkflowAction, buildInputBinding, } from "./modify.js";
37
+ // Fallback deprecated actions - ONLY used when API unavailable
38
+ // Source: ema/ema_backend/grpc/workflow_actions/registered_actions.py (synced 2026-01-26)
39
+ // Exported for use by deploy handler
40
+ export const DEPRECATED_ACTIONS_FALLBACK = {
41
+ "search/v0": { replacement: "search/v2", notes: "v2 requires datastore_configs input" },
42
+ "web_search/v0": { replacement: "live_web_search or ai_web_search", notes: "Split into two specialized actions" },
43
+ "call_llm/v0": { replacement: "call_llm/v2" },
44
+ "human_collaboration/v0": { replacement: "general_hitl" },
45
+ "fixed_response/v0": { replacement: "fixed_response/v1" },
46
+ "custom_agent/v0": { replacement: "custom_agent/v1" },
47
+ "json_mapper/v0": { replacement: "json_mapper/v1" },
48
+ "text_categorizer/v0": { replacement: "text_categorizer/v1" },
49
+ "respond_with_sources/v0": { replacement: "respond_for_external_actions", notes: "Handles tool results better" },
50
+ };
51
+ /**
52
+ * Get deprecated actions from API (with fallback)
53
+ * Returns map of action/version -> replacement info
54
+ */
55
+ async function getDeprecatedActions(client) {
56
+ try {
57
+ const actions = await client.listActions();
58
+ const deprecated = {};
59
+ for (const action of actions) {
60
+ if (action.deprecated) {
61
+ // Use action name as key (API doesn't include version in deprecated flag)
62
+ deprecated[action.id] = {
63
+ notes: "Deprecated - check catalog for current version",
64
+ };
65
+ }
66
+ }
67
+ // Merge with fallback to get replacement info
68
+ for (const [key, info] of Object.entries(DEPRECATED_ACTIONS_FALLBACK)) {
69
+ const actionName = key.split("/")[0];
70
+ if (deprecated[actionName] || !deprecated[key]) {
71
+ deprecated[key] = { ...info, ...deprecated[key] };
72
+ }
73
+ }
74
+ return { deprecated, source: "api" };
75
+ }
76
+ catch {
77
+ // API unavailable, use fallback
78
+ return { deprecated: DEPRECATED_ACTIONS_FALLBACK, source: "fallback" };
79
+ }
80
+ }
81
+ /**
82
+ * Check if workflow uses deprecated actions
83
+ * Exported for use by deploy handler
84
+ */
85
+ export function checkWorkflowDeprecations(workflowDef, deprecatedActions) {
86
+ if (!workflowDef)
87
+ return [];
88
+ const warnings = [];
89
+ const actions = (workflowDef.actions ?? []);
90
+ for (const action of actions) {
91
+ const actionName = action.action?.name?.name;
92
+ const version = action.action?.name?.version || "v0";
93
+ if (!actionName)
94
+ continue;
95
+ const key = `${actionName}/${version}`;
96
+ const deprecation = deprecatedActions[key] || deprecatedActions[actionName];
97
+ if (deprecation) {
98
+ warnings.push({
99
+ action: action.name,
100
+ version,
101
+ replacement: deprecation.replacement,
102
+ notes: deprecation.notes,
103
+ severity: "warning",
104
+ });
105
+ }
106
+ }
107
+ return warnings;
108
+ }
109
+ /**
110
+ * Handle workflow(mode="get") - return data + schema + guidance for LLM
111
+ *
112
+ * Returns:
113
+ * - Current workflow_def (if exists)
114
+ * - Deprecation warnings for current workflow
115
+ * - Generation schema (agents, types, constraints)
116
+ * - Requirements and guidance (NOT hardcoded templates)
117
+ * - Available widgets
118
+ *
119
+ * LLM uses this to generate or modify workflow_def
120
+ */
121
+ async function handleWorkflowGet(args, client) {
122
+ const personaId = args.persona_id;
123
+ if (!personaId) {
124
+ return { error: "persona_id required" };
125
+ }
126
+ const persona = await client.getPersonaById(personaId);
127
+ if (!persona) {
128
+ return { error: `Persona not found: ${personaId}` };
129
+ }
130
+ const protoConfig = persona.proto_config;
131
+ const widgets = (protoConfig?.widgets ?? []);
132
+ const workflowDef = persona.workflow_def;
133
+ // Extract available widget names for LLM
134
+ const availableWidgets = widgets
135
+ .filter(w => typeof w.name === "string")
136
+ .map(w => ({
137
+ name: w.name,
138
+ type: w.type,
139
+ }));
140
+ // Get generation schema for LLM
141
+ const schema = generateSchema();
142
+ // Get deprecated actions (API-first, with fallback)
143
+ const { deprecated: deprecatedActions, source: deprecationSource } = await getDeprecatedActions(client);
144
+ // Check if current workflow uses deprecated actions
145
+ const deprecationWarnings = checkWorkflowDeprecations(workflowDef, deprecatedActions);
146
+ // Build response
147
+ const result = {
148
+ persona_id: persona.id,
149
+ persona_name: persona.name,
150
+ persona_type: persona.type,
151
+ workflow_def: workflowDef ?? null,
152
+ available_widgets: availableWidgets,
153
+ // Deprecation warnings (severity: warning)
154
+ deprecation_warnings: deprecationWarnings.length > 0 ? deprecationWarnings : undefined,
155
+ deprecated_actions_source: deprecationWarnings.length > 0 ? deprecationSource : undefined,
156
+ // Schema for LLM to generate valid workflow_def
157
+ generation_schema: {
158
+ agents: schema.agents,
159
+ constraints: schema.constraints,
160
+ input_rules: schema.inputRules,
161
+ },
162
+ // HARD REQUIREMENTS (must be satisfied)
163
+ hard_requirements: {
164
+ workflow_output: {
165
+ rule: "Every workflow MUST have results.WORKFLOW_OUTPUT mapped to a final action output",
166
+ format: '{ "results": { "WORKFLOW_OUTPUT": { "actionName": "...", "outputName": "..." } } }',
167
+ failure: "Persona cannot be activated without this",
168
+ },
169
+ workflow_name: {
170
+ rule: "workflowName must be ['ema', 'personas', '<persona_id>']",
171
+ format: '{ "workflowName": ["ema", "personas", "actual-persona-id"] }',
172
+ },
173
+ action_structure: {
174
+ rule: "Each action needs: name (unique ID), action.name (namespaces, name, version), action.inputs",
175
+ namespaces: "Most actions use ['actions', 'emainternal'], triggers use ['triggers', 'emainternal']",
176
+ },
177
+ no_deprecated_actions: deprecationWarnings.length > 0 ? {
178
+ rule: "Update deprecated actions before deploying",
179
+ warnings: deprecationWarnings,
180
+ } : undefined,
181
+ },
182
+ // GUIDANCE (best practices, not hard requirements)
183
+ guidance: {
184
+ flow_pattern: "trigger → categorizer (optional) → processing → response → WORKFLOW_OUTPUT",
185
+ categorizer: "For multi-intent workflows, use chat_categorizer early with a Fallback category",
186
+ fallback: "Every categorizer MUST have a Fallback category for unmatched intents",
187
+ hitl: "For actions with side effects (email, external calls), consider general_hitl for approval",
188
+ search: "Use search/v2 with datastore_configs. Wire query from trigger output.",
189
+ },
190
+ // LLM-driven analysis pattern: MCP provides data, LLM does the thinking
191
+ _next_steps: deprecationWarnings.length > 0
192
+ ? [
193
+ "WARNING: Workflow uses deprecated actions. Update them first:",
194
+ ...deprecationWarnings.map(w => ` - ${w.action}: ${w.replacement || "check catalog for replacement"}`),
195
+ "",
196
+ "To analyze workflow health:",
197
+ "1. Fetch ema://rules/anti-patterns",
198
+ "2. Fetch ema://rules/structural-invariants",
199
+ "3. Check workflow_def nodes against the rules",
200
+ "4. Report issues YOU find (MCP does not pre-compute)",
201
+ "",
202
+ "Then deploy with: workflow(mode='deploy', persona_id='...', workflow_def={...})",
203
+ ]
204
+ : [
205
+ "To analyze workflow health:",
206
+ "1. Fetch ema://rules/anti-patterns",
207
+ "2. Fetch ema://rules/structural-invariants",
208
+ "3. Check workflow_def nodes against the rules",
209
+ "4. Report issues YOU find (MCP does not pre-compute)",
210
+ "",
211
+ "Generate or modify workflow_def based on requirements above",
212
+ "Deploy with: workflow(mode='deploy', persona_id='...', workflow_def={...})",
213
+ ],
214
+ };
215
+ // Include deprecated actions reference if there are warnings or on request
216
+ if (deprecationWarnings.length > 0 || args.include_deprecated) {
217
+ result.deprecated_actions_reference = deprecatedActions;
218
+ }
219
+ return result;
220
+ }
37
221
  /**
38
222
  * Main workflow handler with mode-based dispatch
39
223
  *
40
- * Routes to the appropriate extracted handler based on effective mode.
224
+ * PUBLIC modes: get, deploy
225
+ * INTERNAL modes: modify, generate, analyze, optimize, compare (called from persona tool)
41
226
  */
42
227
  export async function handleWorkflow(args, client, getTemplateId) {
43
228
  const personaId = args.persona_id;
@@ -46,90 +231,78 @@ export async function handleWorkflow(args, client, getTemplateId) {
46
231
  const optimize = args.optimize;
47
232
  const compareTo = args.compare_to;
48
233
  const operations = args.operations;
49
- // Smart mode detection - user doesn't need to specify
50
- let effectiveMode;
51
- if (args.mode === "compile") {
52
- effectiveMode = "compile";
53
- }
54
- else if (compareTo) {
55
- effectiveMode = "compare";
56
- }
57
- else if (optimize && personaId) {
58
- effectiveMode = "optimize";
59
- }
60
- else if (personaId && workflowDef) {
61
- effectiveMode = "deploy";
62
- }
63
- else if (personaId && (input || operations)) {
64
- // Modify mode: either natural language input OR structured operations
65
- effectiveMode = "modify";
66
- }
67
- else if (input && !personaId) {
68
- effectiveMode = "generate";
69
- }
70
- else if (personaId || workflowDef || args.include) {
71
- effectiveMode = "analyze";
72
- }
73
- else {
74
- effectiveMode = "analyze";
75
- }
76
- // Explicit mode support
77
- const legacyMode = args.mode;
78
- if (legacyMode === "extend")
79
- effectiveMode = "modify";
80
- if (legacyMode === "modify")
81
- effectiveMode = "modify";
82
- if (legacyMode === "deploy")
83
- effectiveMode = "deploy";
84
- // Route to extracted handler
85
- switch (effectiveMode) {
86
- case "generate":
87
- return handleWorkflowGenerate(args, client, getTemplateId);
88
- case "modify":
89
- case "extend":
90
- return handleWorkflowModify(args, client);
91
- case "analyze":
92
- return handleWorkflowAnalyze(args, client);
93
- case "deploy":
94
- return handleWorkflowDeploy(args, client);
95
- case "compare":
96
- return handleWorkflowCompare(args, client);
97
- case "compile":
98
- return handleWorkflowCompile(args, client);
99
- case "optimize":
100
- return handleWorkflowOptimize(args, client);
101
- default:
102
- return { error: `Unknown mode: ${effectiveMode}` };
234
+ // Explicit mode takes priority
235
+ const mode = args.mode;
236
+ // PUBLIC MODES (from workflow tool)
237
+ if (mode === "get") {
238
+ return handleWorkflowGet(args, client);
239
+ }
240
+ if (mode === "deploy") {
241
+ return handleWorkflowDeploy(args, client);
242
+ }
243
+ // INTERNAL MODES (called from persona tool, not workflow tool)
244
+ if (mode === "modify" || mode === "extend") {
245
+ return handleWorkflowModify(args, client);
246
+ }
247
+ if (mode === "generate") {
248
+ return handleWorkflowGenerate(args, client, getTemplateId);
249
+ }
250
+ if (mode === "analyze") {
251
+ return handleWorkflowAnalyze(args, client);
252
+ }
253
+ if (mode === "optimize") {
254
+ return handleWorkflowOptimize(args, client);
255
+ }
256
+ if (mode === "compare") {
257
+ return handleWorkflowCompare(args, client);
258
+ }
259
+ // Auto-detect mode (for backwards compatibility with internal routing)
260
+ if (compareTo) {
261
+ return handleWorkflowCompare(args, client);
262
+ }
263
+ if (optimize && personaId) {
264
+ return handleWorkflowOptimize(args, client);
265
+ }
266
+ if (personaId && workflowDef) {
267
+ return handleWorkflowDeploy(args, client);
103
268
  }
269
+ if (personaId && (input || operations)) {
270
+ return handleWorkflowModify(args, client);
271
+ }
272
+ if (input && !personaId) {
273
+ return handleWorkflowGenerate(args, client, getTemplateId);
274
+ }
275
+ if (personaId) {
276
+ return handleWorkflowAnalyze(args, client);
277
+ }
278
+ // Invalid mode
279
+ return {
280
+ error: `Invalid or missing mode: ${mode}`,
281
+ public_modes: ["get", "deploy"],
282
+ hint: "MCP provides data (get) and executes (deploy). LLM does all thinking.",
283
+ };
104
284
  }
105
285
  /**
106
286
  * Check if a workflow mode has been extracted
107
287
  */
108
288
  export function hasExtractedWorkflowHandler(mode) {
109
- const extractedModes = ["analyze", "compare", "compile", "optimize", "deploy", "generate", "modify", "extend"];
289
+ const extractedModes = ["get", "deploy", "modify", "extend", "generate", "analyze", "optimize", "compare"];
110
290
  return extractedModes.includes(mode);
111
291
  }
112
292
  /**
113
293
  * Get handler for a specific workflow mode
114
- * Note: generate handler has a different signature (requires getTemplateId)
115
294
  */
116
295
  export function getWorkflowModeHandler(mode) {
117
296
  switch (mode) {
118
- case "analyze":
119
- return handleWorkflowAnalyze;
120
- case "compare":
121
- return handleWorkflowCompare;
122
- case "compile":
123
- return handleWorkflowCompile;
124
- case "optimize":
125
- return handleWorkflowOptimize;
297
+ case "get":
126
298
  case "deploy":
127
- return handleWorkflowDeploy;
128
- case "generate":
129
- return handleWorkflowGenerate;
130
299
  case "modify":
131
300
  case "extend":
132
- return handleWorkflowModify;
301
+ case "generate":
302
+ case "analyze":
303
+ case "optimize":
304
+ case "compare":
305
+ return handleWorkflow;
133
306
  default:
134
307
  return undefined;
135
308
  }
@@ -8,7 +8,6 @@
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";
14
13
  // ─────────────────────────────────────────────────────────────────────────
@@ -323,9 +322,6 @@ export async function handleWorkflowModify(args, client) {
323
322
  return { error: "Persona has no workflow_def" };
324
323
  }
325
324
  const actions = (workflow.actions || []);
326
- // Run analysis to give Agent context
327
- const issues = detectWorkflowIssues(workflow);
328
- const suggestions = suggestWorkflowFixes(issues);
329
325
  // Load schema registry for action catalog
330
326
  const schemaRegistry = await ensureSchemaRegistry(client);
331
327
  const availableActions = schemaRegistry.isLoaded()
@@ -335,20 +331,21 @@ export async function handleWorkflowModify(args, client) {
335
331
  message: "No operations provided. Returning workflow context for Agent to build operations.",
336
332
  persona_id: personaId,
337
333
  persona_name: persona.name,
338
- // Current workflow state
334
+ // Current workflow state (DATA for Agent)
339
335
  current_nodes: actions.map(a => ({
340
336
  name: a.name,
341
337
  display_name: a.displaySettings?.displayName,
342
338
  action_type: a.action?.name?.name,
343
339
  })),
344
- // Analysis
345
- issues: issues.map(i => ({ severity: i.severity, type: i.type, node: i.node, reason: i.reason })),
346
- suggestions,
347
340
  // Available actions from registry
348
341
  available_actions: availableActions.slice(0, 20), // Top 20 for context
349
342
  // Guidance for Agent
350
- _tip: "Build ModificationOperation[] and pass as 'operations' parameter",
351
- _next_step: "Use the current_nodes and available_actions to build structured operations",
343
+ _next_steps: [
344
+ "1. Review current_nodes and available_actions above",
345
+ "2. Fetch ema://rules/anti-patterns to check for issues",
346
+ "3. Build ModificationOperation[] based on your analysis",
347
+ "4. Pass operations as 'operations' parameter",
348
+ ],
352
349
  // Example operation structures
353
350
  example_operations: {
354
351
  insert_hitl: {
@@ -392,9 +389,6 @@ export async function handleWorkflowModify(args, client) {
392
389
  const result = applyWorkflowModifications(workflow, operations, schemaRegistry);
393
390
  // Sanitize before deploy
394
391
  const sanitized = sanitizeWorkflowForDeploy(result.workflow);
395
- // Run validation
396
- const postIssues = detectWorkflowIssues(sanitized);
397
- const errors = postIssues.filter(i => i.severity === "critical");
398
392
  if (preview) {
399
393
  return {
400
394
  preview: true,
@@ -406,23 +400,9 @@ export async function handleWorkflowModify(args, client) {
406
400
  nodes_removed: result.nodesRemoved,
407
401
  nodes_modified: result.nodesModified,
408
402
  connections_changed: result.connectionsChanged,
409
- // Validation
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
403
+ // The modified workflow (LLM should analyze with ema://rules/*)
413
404
  modified_workflow: sanitized,
414
- _tip: errors.length > 0
415
- ? "Fix validation errors before deploying"
416
- : "Preview looks good. Call without preview=true to deploy",
417
- };
418
- }
419
- // Deploy if validation passes
420
- if (errors.length > 0) {
421
- return {
422
- error: "Validation failed - cannot deploy",
423
- validation_errors: errors.map(e => e.reason || e.type),
424
- changes_attempted: result.changesApplied,
425
- _tip: "Fix validation errors and try again",
405
+ _tip: "Preview complete. Use ema://rules/anti-patterns to check for issues. Call without preview=true to deploy.",
426
406
  };
427
407
  }
428
408
  // Deploy the workflow
@@ -1,80 +1,22 @@
1
1
  /**
2
2
  * Workflow Optimize Handler
3
3
  *
4
- * Detects and fixes workflow issues.
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
- // Analyze and detect issues
93
- const issues = detectWorkflowIssues(existingWorkflow);
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: preview ? "preview" : "deployed",
37
+ status: "DEPRECATED - Use LLM analysis instead",
109
38
  persona_id: personaId,
110
39
  persona_name: persona.name,
111
- issues_found: issues.length,
112
- issues: issues.map(i => ({
113
- type: i.type,
114
- severity: i.severity,
115
- reason: i.reason,
116
- })),
117
- suggested_fixes: fixes,
118
- fixed_workflow: fixedWorkflow,
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
  }