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

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.

@@ -14,10 +14,20 @@ export async function handleAction(args, client) {
14
14
  const id = args.id;
15
15
  const identifier = args.identifier; // deprecated alias for 'id'
16
16
  const idOrName = id ?? identifier;
17
- // Categories list
17
+ // Categories list - API-first with catalog fallback
18
18
  if (args.categories) {
19
+ try {
20
+ const actions = await client.listActions();
21
+ const apiCategories = [...new Set(actions.map(a => a.category).filter(Boolean))];
22
+ if (apiCategories.length > 0) {
23
+ return { categories: apiCategories, count: apiCategories.length, source: "api" };
24
+ }
25
+ }
26
+ catch {
27
+ // Fallback to catalog
28
+ }
19
29
  const categories = [...new Set(Object.values(AGENT_CATALOG).map(a => a.category))];
20
- return { categories, count: categories.length };
30
+ return { categories, count: categories.length, source: "catalog" };
21
31
  }
22
32
  // Suggest for use case
23
33
  if (args.suggest) {
@@ -97,6 +107,7 @@ export async function handleAction(args, client) {
97
107
  ...a,
98
108
  documentation: a.name ? getAgentByName(a.name) : undefined,
99
109
  })),
110
+ source: "api",
100
111
  };
101
112
  }
102
113
  return {
@@ -107,6 +118,7 @@ export async function handleAction(args, client) {
107
118
  category: a.category,
108
119
  enabled: a.enabled,
109
120
  })),
121
+ source: "api",
110
122
  };
111
123
  }
112
124
  catch (e) {
@@ -138,6 +138,8 @@ export async function handleData(args, client, readFile) {
138
138
  // Upload file or LLM-generated content (dashboard rows)
139
139
  const filePath = (dataArgs?.path ?? dataArgs?.file ?? args.file);
140
140
  const items = dataArgs?.items;
141
+ // Widget name for targeting specific upload widgets (especially for Document Generation personas)
142
+ const widgetName = (dataArgs?.widget_name ?? args.widget_name);
141
143
  if (filePath) {
142
144
  // File upload - use provided readFile or fall back to fs
143
145
  try {
@@ -151,11 +153,14 @@ export async function handleData(args, client, readFile) {
151
153
  }
152
154
  const path = await import("path");
153
155
  const filename = path.basename(filePath);
154
- const result = await client.uploadDataSource(personaId, fileContent, filename);
156
+ const result = await client.uploadDataSource(personaId, fileContent, filename, {
157
+ widgetName: widgetName, // Pass through widget_name for doc gen personas
158
+ });
155
159
  return {
156
160
  method: "upload",
157
161
  persona_id: personaId,
158
162
  path: filePath,
163
+ widget_name: widgetName ?? "fileUpload",
159
164
  ...result,
160
165
  };
161
166
  }
@@ -164,14 +169,74 @@ export async function handleData(args, client, readFile) {
164
169
  }
165
170
  }
166
171
  else if (items && items.length > 0) {
167
- // Dashboard row upload (LLM-generated content)
172
+ // Dashboard row upload (LLM-generated content or file attachments)
168
173
  try {
169
174
  const results = [];
175
+ const path = await import("path");
176
+ const fs = await import("fs/promises");
170
177
  for (const item of items) {
171
- // Convert item to DashboardInput format (name + string_value)
172
- const inputs = Object.entries(item).map(([key, value]) => ({
173
- name: key,
174
- string_value: typeof value === "string" ? value : JSON.stringify(value),
178
+ // Convert item to DashboardInput format
179
+ // Supports: string_value, number_value, boolean_value, document_value
180
+ const inputs = await Promise.all(Object.entries(item).map(async ([key, value]) => {
181
+ // File attachment: { file: "/path/to/file.pdf" } or { file_path: "..." }
182
+ if (typeof value === "object" && value !== null && ("file" in value || "file_path" in value)) {
183
+ const filePath = value.file ?? value.file_path;
184
+ if (typeof filePath === "string") {
185
+ let fileContent;
186
+ if (readFile) {
187
+ fileContent = await readFile(filePath);
188
+ }
189
+ else {
190
+ fileContent = await fs.readFile(filePath);
191
+ }
192
+ const filename = path.basename(filePath);
193
+ const ext = path.extname(filePath).toLowerCase();
194
+ const mimeTypes = {
195
+ ".pdf": "application/pdf",
196
+ ".json": "application/json",
197
+ ".txt": "text/plain",
198
+ ".csv": "text/csv",
199
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
200
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
201
+ };
202
+ return {
203
+ name: key,
204
+ document_value: [{
205
+ name: filename,
206
+ contents: fileContent.toString("base64"),
207
+ is_base64_encoded: true,
208
+ mime_type: mimeTypes[ext] ?? "application/octet-stream",
209
+ }],
210
+ };
211
+ }
212
+ }
213
+ // Inline document: { contents: "...", mime_type: "..." }
214
+ if (typeof value === "object" && value !== null && "contents" in value) {
215
+ const doc = value;
216
+ return {
217
+ name: key,
218
+ document_value: [{
219
+ name: doc.name ?? `${key}.txt`,
220
+ contents: typeof doc.contents === "string" && doc.is_base64_encoded
221
+ ? doc.contents
222
+ : Buffer.from(String(doc.contents)).toString("base64"),
223
+ is_base64_encoded: true,
224
+ mime_type: doc.mime_type ?? "text/plain",
225
+ }],
226
+ };
227
+ }
228
+ // Primitive values
229
+ if (typeof value === "number") {
230
+ return { name: key, number_value: value };
231
+ }
232
+ if (typeof value === "boolean") {
233
+ return { name: key, boolean_value: value };
234
+ }
235
+ // Default: string value
236
+ return {
237
+ name: key,
238
+ string_value: typeof value === "string" ? value : JSON.stringify(value),
239
+ };
175
240
  }));
176
241
  const result = await client.uploadAndRunDashboardRow(personaId, inputs);
177
242
  results.push(result);
@@ -181,6 +246,7 @@ export async function handleData(args, client, readFile) {
181
246
  persona_id: personaId,
182
247
  uploaded_rows: results.length,
183
248
  row_ids: results.map(r => r.row_id),
249
+ _tip: "Dashboard rows created with file attachments trigger workflow execution automatically",
184
250
  };
185
251
  }
186
252
  catch (error) {
@@ -25,9 +25,9 @@ export async function handleEnv(_args, getEnvironments, toolkit) {
25
25
  getting_started: {
26
26
  workflow: [
27
27
  "1. List personas: persona(method=\"list\")",
28
- "2. Get details: persona(method=\"get\", id=\"...\")",
29
- "3. Analyze before modifying: persona(method=\"get\", id=\"...\", analyze=true)",
30
- "4. Preview before deploying: persona(method=\"update\", id=\"...\", preview=true, ...)",
28
+ "2. Get workflow data: workflow(mode=\"get\", persona_id=\"...\")",
29
+ "3. Analyze workflow: Fetch ema://rules/anti-patterns and check workflow_def against rules",
30
+ "4. Preview before deploying: workflow(mode=\"deploy\", persona_id=\"...\", workflow_def={...}, preview=true)",
31
31
  ],
32
32
  critical_rules: criticalRules,
33
33
  resources: [
@@ -34,10 +34,22 @@ export async function handleReference(args, context) {
34
34
  if (type === "actions") {
35
35
  const client = context?.client;
36
36
  const id = args.id;
37
- // Categories list
37
+ // Categories list - API-first with catalog fallback
38
38
  if (args.categories) {
39
+ if (client) {
40
+ try {
41
+ const actions = await client.listActions();
42
+ const apiCategories = [...new Set(actions.map(a => a.category).filter(Boolean))];
43
+ if (apiCategories.length > 0) {
44
+ return { categories: apiCategories, count: apiCategories.length, source: "api" };
45
+ }
46
+ }
47
+ catch {
48
+ // Fallback to catalog
49
+ }
50
+ }
39
51
  const categories = [...new Set(Object.values(AGENT_CATALOG).map(a => a.category))];
40
- return { categories, count: categories.length };
52
+ return { categories, count: categories.length, source: "catalog" };
41
53
  }
42
54
  // Suggest for use case
43
55
  if (args.suggest) {
@@ -114,6 +126,7 @@ export async function handleReference(args, context) {
114
126
  category: a.category,
115
127
  enabled: a.enabled,
116
128
  })),
129
+ source: "api",
117
130
  };
118
131
  }
119
132
  catch {
@@ -1,20 +1,21 @@
1
1
  /**
2
2
  * Workflow Analyze Handler
3
3
  *
4
- * Analyzes workflow structure, detects issues, and provides metrics.
4
+ * Returns workflow DATA for LLM to analyze using rules from ema://rules/*.
5
5
  *
6
- * Features:
7
- * - Issue detection (critical, warning, info)
8
- * - Connection validation
9
- * - Metrics calculation
10
- * - Execution flow analysis (loops, dead code, multiple responders)
11
- * - Auto-fix support
6
+ * IMPORTANT: This handler does NOT pre-compute issues or fixes.
7
+ * The LLM applies ANTI_PATTERNS, INPUT_SOURCE_RULES, and OPTIMIZATION_RULES
8
+ * to reason about the workflow.
9
+ *
10
+ * Returns:
11
+ * - workflow_def: The raw workflow structure
12
+ * - connections: Type information for each edge
13
+ * - metrics: Basic counts (nodes, edges, etc.)
14
+ * - rules_hint: Pointer to ema://rules/* for analysis guidance
12
15
  */
13
- import { detectWorkflowIssues, suggestWorkflowFixes, validateWorkflowConnections } from "../../../sdk/knowledge.js";
14
- import { autoFixWorkflow } from "../../../sdk/workflow-fixer.js";
15
- import { analyzeExecutionFlow, generateASCIIFlow } from "../../../sdk/workflow-execution-analyzer.js";
16
+ import { validateWorkflowConnections, parseWorkflowDef } from "../../../sdk/knowledge.js";
16
17
  /**
17
- * Calculate workflow metrics
18
+ * Calculate workflow metrics (pure data, no analysis)
18
19
  */
19
20
  function calculateMetrics(workflow) {
20
21
  const actions = workflow.actions;
@@ -36,15 +37,24 @@ function calculateMetrics(workflow) {
36
37
  // Get trigger type
37
38
  const trigger = workflow.trigger;
38
39
  const triggerType = trigger?.trigger_type;
40
+ // Check for categorizer
41
+ const hasCategorizer = actions.some(a => {
42
+ const name = a.name?.toLowerCase() ?? "";
43
+ const action = a.action?.name?.name;
44
+ return name.includes("categorizer") || action?.includes("categorizer");
45
+ });
39
46
  return {
40
47
  node_count: actions.length,
41
48
  connection_count: connectionCount,
42
49
  has_hitl: hasHitl,
50
+ has_categorizer: hasCategorizer,
43
51
  trigger_type: triggerType,
44
52
  };
45
53
  }
46
54
  /**
47
55
  * Handle workflow analyze mode
56
+ *
57
+ * Returns DATA for LLM to analyze - does NOT pre-compute issues.
48
58
  */
49
59
  export async function handleWorkflowAnalyze(args, client) {
50
60
  const personaId = args.persona_id;
@@ -62,110 +72,48 @@ export async function handleWorkflowAnalyze(args, client) {
62
72
  if (!workflow) {
63
73
  return { error: "No workflow to analyze. Provide workflow_def or persona_id." };
64
74
  }
65
- // Determine what to include
66
- const include = args.include || ["issues", "connections", "fixes", "metrics"];
75
+ // Parse workflow to simpler structure
76
+ const nodes = parseWorkflowDef(workflow);
77
+ // Get connection type information
78
+ const connections = validateWorkflowConnections(workflow);
79
+ // Calculate metrics
80
+ const metrics = calculateMetrics(workflow);
67
81
  const result = {
68
82
  mode: "analyze",
69
83
  persona_id: personaId,
70
84
  persona_name: persona?.name,
71
- environment: "demo",
85
+ // RAW DATA for LLM to analyze
86
+ workflow_def: workflow,
87
+ node_count: nodes.length,
88
+ nodes: nodes.map(n => ({
89
+ id: n.id,
90
+ action: n.action_name,
91
+ display_name: n.display_name,
92
+ inputs: n.incoming_edges?.map(e => `${e.source_node_id}.${e.source_output}`),
93
+ })),
94
+ // Type information for connections
95
+ connections: connections.map(c => ({
96
+ edge: c.edge_id,
97
+ source_type: c.source_type,
98
+ target_type: c.target_type,
99
+ compatible: c.compatible,
100
+ note: c.note,
101
+ })),
102
+ // Basic metrics
103
+ metrics,
104
+ // Tell LLM where to find analysis rules
105
+ _next_steps: [
106
+ "1. Fetch ema://rules/anti-patterns",
107
+ "2. Fetch ema://rules/structural-invariants",
108
+ "3. Check each node against the rules",
109
+ "4. Report issues YOU find (MCP does not pre-compute)",
110
+ ],
72
111
  };
73
- // Issues and fixes
74
- if (include.includes("issues") || include.includes("fixes")) {
75
- const issues = detectWorkflowIssues(workflow);
76
- if (include.includes("issues")) {
77
- result.issues = issues;
78
- }
79
- if (include.includes("fixes")) {
80
- result.fixes = suggestWorkflowFixes(issues);
81
- }
82
- result.issue_summary = {
83
- total: issues.length,
84
- critical: issues.filter((i) => i.severity === "critical").length,
85
- warning: issues.filter((i) => i.severity === "warning").length,
86
- info: issues.filter((i) => i.severity === "info").length,
87
- };
88
- result.validation_passed = issues.filter((i) => i.severity === "critical").length === 0;
89
- }
90
- // Connection validation
91
- if (include.includes("connections")) {
92
- result.connections = validateWorkflowConnections(workflow);
93
- }
94
- // Metrics
95
- if (include.includes("metrics")) {
96
- result.metrics = calculateMetrics(workflow);
97
- }
98
- // Execution flow analysis
99
- if (include.includes("execution_flow")) {
100
- const execAnalysis = analyzeExecutionFlow(workflow);
101
- result.execution_flow = {
102
- summary: execAnalysis.summary,
103
- loops: execAnalysis.loops,
104
- multiple_responder_issues: execAnalysis.multipleResponderIssues,
105
- redundant_classifiers: execAnalysis.redundantClassifiers,
106
- data_flow_issues: execAnalysis.dataFlowIssues,
107
- dead_code_paths: execAnalysis.deadCodePaths,
108
- };
109
- // Include ASCII visualization if requested
110
- if (args.visualize) {
111
- result.execution_flow_ascii = generateASCIIFlow(execAnalysis);
112
- }
113
- // Add specific warnings for triple response risk
114
- if (execAnalysis.summary.mayRepeatResponses) {
115
- result.triple_response_warning = "⚠️ This workflow may cause duplicate/triple responses due to ungated parallel responders";
116
- }
117
- }
118
112
  if (persona) {
119
113
  result.persona = {
120
114
  id: persona.id,
121
115
  name: persona.name
122
116
  };
123
117
  }
124
- // Calculate optimization guidance
125
- const issues = result.issues;
126
- const criticalCount = issues?.filter(i => i.severity === "critical").length ?? 0;
127
- const warningCount = issues?.filter(i => i.severity === "warning").length ?? 0;
128
- // AUTO-FIX: If fix=true, apply fixes and deploy
129
- if (args.fix && personaId && persona && workflow) {
130
- const fixResult = autoFixWorkflow(workflow);
131
- result.fixes_applied = fixResult.fixesApplied;
132
- result.fixes_warnings = fixResult.warnings;
133
- if (fixResult.fixesApplied.length > 0 && fixResult.success) {
134
- // Deploy the fixed workflow
135
- try {
136
- const fixedWorkflow = fixResult.workflowDef;
137
- await client.updateAiEmployee({
138
- persona_id: personaId,
139
- workflow: fixedWorkflow,
140
- proto_config: persona.proto_config,
141
- });
142
- result.fix_status = "deployed";
143
- result.fixes_deployed = fixResult.fixesApplied.length;
144
- result.status = `✅ Applied ${fixResult.fixesApplied.length} fixes and deployed`;
145
- }
146
- catch (deployErr) {
147
- result.fix_status = "failed";
148
- result.fix_error = deployErr instanceof Error ? deployErr.message : String(deployErr);
149
- result.status = `⚠️ Fixes applied but deploy failed: ${result.fix_error}`;
150
- }
151
- }
152
- else if (fixResult.fixesApplied.length === 0) {
153
- result.fix_status = "no_fixes_needed";
154
- result.status = "✅ No auto-fixable issues found";
155
- }
156
- else {
157
- result.fix_status = "failed";
158
- result.status = "⚠️ Fix generation failed";
159
- }
160
- return result;
161
- }
162
- // Provide optimization suggestion
163
- if (criticalCount > 0 || warningCount > 0) {
164
- result.optimization_suggestion = `This workflow has ${criticalCount} critical and ${warningCount} warning issues. ` +
165
- `Use workflow(persona_id="${personaId ?? 'ID'}", optimize=true) to auto-fix.`;
166
- }
167
- else {
168
- result.status = "✅ Workflow is healthy - no issues detected";
169
- }
170
118
  return result;
171
119
  }
@@ -2,8 +2,10 @@
2
2
  * Workflow Deploy Handler
3
3
  *
4
4
  * Deploys a workflow_def to an existing persona.
5
+ * Includes deprecation warnings (does not block, just warns).
5
6
  */
6
7
  import { sanitizeWorkflowForDeploy } from "./utils.js";
8
+ import { DEPRECATED_ACTIONS_FALLBACK, checkWorkflowDeprecations } from "./index.js";
7
9
  /**
8
10
  * Handle workflow deploy mode
9
11
  */
@@ -23,6 +25,8 @@ export async function handleWorkflowDeploy(args, client) {
23
25
  }
24
26
  // Sanitize workflow before deployment
25
27
  const sanitizedWorkflow = sanitizeWorkflowForDeploy(workflowDef);
28
+ // Check for deprecated actions (warn, don't block)
29
+ const deprecationWarnings = checkWorkflowDeprecations(workflowDef, DEPRECATED_ACTIONS_FALLBACK);
26
30
  // Determine proto_config to use: provided > existing
27
31
  const existingProtoConfig = persona.proto_config;
28
32
  const providedProtoConfig = args.proto_config;
@@ -31,6 +35,11 @@ export async function handleWorkflowDeploy(args, client) {
31
35
  const projectSettings = protoConfigToUse.projectSettings;
32
36
  const isVoice = projectSettings?.projectType === 5;
33
37
  const warnings = [];
38
+ // Add deprecation warning message if deprecated actions found
39
+ if (deprecationWarnings.length > 0) {
40
+ warnings.push(`Workflow uses ${deprecationWarnings.length} deprecated action(s). ` +
41
+ "Consider updating to prevent future breakage when deprecated actions are removed.");
42
+ }
34
43
  if (isVoice && !providedProtoConfig) {
35
44
  // Check if existing proto_config has conversationSettings populated
36
45
  const widgets = (protoConfigToUse.widgets ?? []);
@@ -58,6 +67,12 @@ export async function handleWorkflowDeploy(args, client) {
58
67
  if (warnings.length > 0) {
59
68
  result.warnings = warnings;
60
69
  }
70
+ // Include detailed deprecation info if any deprecated actions found
71
+ if (deprecationWarnings.length > 0) {
72
+ result.deprecation_warnings = deprecationWarnings;
73
+ result._warning = "Workflow deployed but contains deprecated actions. Update before platform removes them.";
74
+ result._next_step = "Use workflow(mode='get') to see current schema, then deploy updated workflow_def with non-deprecated actions.";
75
+ }
61
76
  return result;
62
77
  }
63
78
  catch (err) {
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { parseInput, intentToSpec, generateWorkflow } from "../../../sdk/workflow-intent.js";
12
12
  import { compileWorkflow } from "../../../sdk/workflow-generator.js";
13
- import { detectWorkflowIssues } from "../../../sdk/knowledge.js";
13
+ // Detection removed - LLM analyzes with rules from ema://rules/*
14
14
  import { runIntentArchitect } from "../../../sdk/intent-architect.js";
15
15
  import { ensureActionRegistry } from "../../../sdk/action-registry.js";
16
16
  import { ensureSchemaRegistry, validateWorkflowSpec, generateActionCatalogForLLM } from "../../../sdk/workflow-validator.js";
@@ -271,14 +271,14 @@ export async function handleWorkflowGenerate(args, client, getTemplateId) {
271
271
  }
272
272
  }
273
273
  const compiled = compileWorkflow(spec, { registry: actionRegistry });
274
- // Validate generated workflow
275
- const issues = detectWorkflowIssues(compiled.workflow_def);
276
274
  const result = {
277
275
  mode: "generate",
278
276
  status: preview ? "preview" : "deployed",
279
277
  workflow_def: compiled.workflow_def,
280
278
  proto_config: compiled.proto_config,
281
279
  validation: parseResult.validation,
280
+ // LLM should analyze with ema://rules/anti-patterns
281
+ _analysis_hint: "Use ema://rules/anti-patterns to check for issues",
282
282
  };
283
283
  if (specValidation) {
284
284
  result.api_validation = {
@@ -287,9 +287,6 @@ export async function handleWorkflowGenerate(args, client, getTemplateId) {
287
287
  action_coverage: specValidation.action_coverage,
288
288
  };
289
289
  }
290
- if (issues.length > 0) {
291
- result.issues = issues;
292
- }
293
290
  // Deploy if not preview
294
291
  if (!preview && personaId) {
295
292
  const persona = await client.getPersonaById(personaId);
@@ -36,7 +36,8 @@ export { handleWorkflowModify } from "./modify.js";
36
36
  export { applyWorkflowModifications, buildWorkflowAction, buildInputBinding, } from "./modify.js";
37
37
  // Fallback deprecated actions - ONLY used when API unavailable
38
38
  // Source: ema/ema_backend/grpc/workflow_actions/registered_actions.py (synced 2026-01-26)
39
- const DEPRECATED_ACTIONS_FALLBACK = {
39
+ // Exported for use by deploy handler
40
+ export const DEPRECATED_ACTIONS_FALLBACK = {
40
41
  "search/v0": { replacement: "search/v2", notes: "v2 requires datastore_configs input" },
41
42
  "web_search/v0": { replacement: "live_web_search or ai_web_search", notes: "Split into two specialized actions" },
42
43
  "call_llm/v0": { replacement: "call_llm/v2" },
@@ -79,8 +80,9 @@ async function getDeprecatedActions(client) {
79
80
  }
80
81
  /**
81
82
  * Check if workflow uses deprecated actions
83
+ * Exported for use by deploy handler
82
84
  */
83
- function checkWorkflowDeprecations(workflowDef, deprecatedActions) {
85
+ export function checkWorkflowDeprecations(workflowDef, deprecatedActions) {
84
86
  if (!workflowDef)
85
87
  return [];
86
88
  const warnings = [];
@@ -185,14 +187,27 @@ async function handleWorkflowGet(args, client) {
185
187
  hitl: "For actions with side effects (email, external calls), consider general_hitl for approval",
186
188
  search: "Use search/v2 with datastore_configs. Wire query from trigger output.",
187
189
  },
188
- // Tell LLM to check for deprecated actions if any exist
190
+ // LLM-driven analysis pattern: MCP provides data, LLM does the thinking
189
191
  _next_steps: deprecationWarnings.length > 0
190
192
  ? [
191
193
  "WARNING: Workflow uses deprecated actions. Update them first:",
192
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
+ "",
193
202
  "Then deploy with: workflow(mode='deploy', persona_id='...', workflow_def={...})",
194
203
  ]
195
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
+ "",
196
211
  "Generate or modify workflow_def based on requirements above",
197
212
  "Deploy with: workflow(mode='deploy', persona_id='...', workflow_def={...})",
198
213
  ],
@@ -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