@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.

Files changed (45) hide show
  1. package/dist/mcp/handlers/action/index.js +17 -20
  2. package/dist/mcp/handlers/data/index.js +75 -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 +63 -18
  7. package/dist/mcp/handlers/persona/index.js +9 -10
  8. package/dist/mcp/handlers/persona/update.js +6 -3
  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 +160 -1
  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 +69 -26
  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 +81 -25
  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-generator.js +2 -1
  34. package/dist/sdk/workflow-intent.js +43 -3
  35. package/dist/sdk/workflow-transformer.js +0 -342
  36. package/docs/dashboard-operations.md +35 -0
  37. package/docs/ema-user-guide.md +66 -0
  38. package/docs/mcp-tools-guide.md +74 -45
  39. package/package.json +2 -2
  40. package/dist/mcp/handlers/persona/analyze.js +0 -275
  41. package/dist/mcp/handlers/persona/compare.js +0 -32
  42. package/dist/mcp/handlers/workflow/compile.js +0 -39
  43. package/docs/DEBUG-ANALYSIS-unused-category-type-mismatch.md +0 -481
  44. package/docs/TODO-fix-analyzer-and-modify.md +0 -182
  45. 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: "No operations provided. Returning workflow context for Agent to build operations.",
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
- // Current workflow state
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
- _tip: "Build ModificationOperation[] and pass as 'operations' parameter",
351
- _next_step: "Use the current_nodes and available_actions to build structured operations",
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
- // 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
414
+ // The modified workflow (LLM should analyze with ema://rules/*)
413
415
  modified_workflow: sanitized,
414
- _tip: errors.length > 0
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
- // Deploy if validation passes
420
- if (errors.length > 0) {
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: "Validation failed - cannot deploy",
423
- validation_errors: errors.map(e => e.reason || e.type),
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
- _tip: "Fix validation errors and try again",
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
- * 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
  }
@@ -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
- // Deprecation tracking - helps users migrate to new param names
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", "analyze", "sanitize", "snapshot", "history", "restore", "compare"],
233
- hint: "Specify method='get' to fetch, method='update' to modify",
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
- // Compare, clone, create, analyze - all handled by extracted handlers
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":