@ema.co/mcp-toolkit 2026.2.13 → 2026.2.23

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 (60) hide show
  1. package/.context/public/guides/ema-user-guide.md +12 -16
  2. package/.context/public/guides/mcp-tools-guide.md +203 -334
  3. package/dist/cli/index.js +2 -2
  4. package/dist/mcp/domain/loop-detection.js +89 -0
  5. package/dist/mcp/domain/sanitizer.js +1 -1
  6. package/dist/mcp/domain/structural-rules.js +4 -5
  7. package/dist/mcp/domain/validation-rules.js +5 -5
  8. package/dist/mcp/domain/workflow-graph.js +3 -5
  9. package/dist/mcp/domain/workflow-path-enumerator.js +7 -4
  10. package/dist/mcp/guidance.js +62 -29
  11. package/dist/mcp/handlers/debug/adapter.js +15 -0
  12. package/dist/mcp/handlers/debug/formatters.js +282 -0
  13. package/dist/mcp/handlers/debug/index.js +133 -0
  14. package/dist/mcp/handlers/demo/adapter.js +180 -0
  15. package/dist/mcp/handlers/env/config.js +2 -2
  16. package/dist/mcp/handlers/feedback/index.js +1 -1
  17. package/dist/mcp/handlers/index.js +0 -1
  18. package/dist/mcp/handlers/persona/adapter.js +135 -0
  19. package/dist/mcp/handlers/persona/index.js +237 -8
  20. package/dist/mcp/handlers/persona/schema.js +27 -0
  21. package/dist/mcp/handlers/reference/index.js +6 -4
  22. package/dist/mcp/handlers/sync/adapter.js +200 -0
  23. package/dist/mcp/handlers/workflow/adapter.js +174 -0
  24. package/dist/mcp/handlers/workflow/fix.js +11 -12
  25. package/dist/mcp/handlers/workflow/index.js +12 -40
  26. package/dist/mcp/handlers/workflow/validation.js +1 -1
  27. package/dist/mcp/knowledge-guidance-topics.js +615 -0
  28. package/dist/mcp/knowledge-types.js +7 -0
  29. package/dist/mcp/knowledge.js +75 -1403
  30. package/dist/mcp/resources-dynamic.js +2395 -0
  31. package/dist/mcp/resources-validation.js +408 -0
  32. package/dist/mcp/resources.js +72 -2508
  33. package/dist/mcp/server.js +69 -2825
  34. package/dist/mcp/tools.js +106 -5
  35. package/dist/sdk/client-adapter.js +265 -24
  36. package/dist/sdk/ema-client.js +100 -9
  37. package/dist/sdk/generated/agent-catalog.js +615 -0
  38. package/dist/sdk/generated/well-known-types.js +99 -0
  39. package/dist/sdk/generated/widget-catalog.js +60 -0
  40. package/dist/sdk/grpc-client.js +115 -1
  41. package/dist/sync/sdk.js +2 -2
  42. package/dist/sync.js +4 -3
  43. package/docs/README.md +17 -9
  44. package/package.json +3 -2
  45. package/.context/public/guides/dashboard-operations.md +0 -349
  46. package/.context/public/guides/email-patterns.md +0 -125
  47. package/.context/public/guides/workflow-builder-patterns.md +0 -708
  48. package/dist/mcp/domain/intent-architect.js +0 -914
  49. package/dist/mcp/domain/quality-gates.js +0 -110
  50. package/dist/mcp/domain/workflow-execution-analyzer.js +0 -412
  51. package/dist/mcp/domain/workflow-intent.js +0 -1806
  52. package/dist/mcp/domain/workflow-merge.js +0 -449
  53. package/dist/mcp/domain/workflow-tracer.js +0 -648
  54. package/dist/mcp/domain/workflow-transformer.js +0 -742
  55. package/dist/mcp/handlers/knowledge/index.js +0 -54
  56. package/dist/mcp/handlers/persona/intent.js +0 -141
  57. package/dist/mcp/handlers/workflow/analyze.js +0 -119
  58. package/dist/mcp/handlers/workflow/compare.js +0 -70
  59. package/dist/mcp/handlers/workflow/generate.js +0 -384
  60. package/dist/mcp/handlers-consolidated.js +0 -333
package/dist/cli/index.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * config validate - Validate config file
12
12
  */
13
13
  import { loadConfig } from "../sdk/config.js";
14
- import { EmaClient } from "../sdk/client.js";
14
+ import { EmaClientAdapter } from "../sdk/client-adapter.js";
15
15
  import { SyncSDK } from "../sync/sdk.js";
16
16
  function printUsage() {
17
17
  console.log(`
@@ -220,7 +220,7 @@ async function runAgentsCommand(subcommand, args, options) {
220
220
  baseUrl: master.baseUrl,
221
221
  bearerToken: getEnvOrThrow(master.bearerTokenEnv),
222
222
  };
223
- const client = new EmaClient(env);
223
+ const client = new EmaClientAdapter(env);
224
224
  switch (subcommand) {
225
225
  case "list": {
226
226
  const actions = await client.listAgents();
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Loop Detection for Workflow Graphs
3
+ *
4
+ * Uses the shared WorkflowGraph representation from workflow-graph.ts
5
+ * for cycle and re-entry detection in workflow validation.
6
+ */
7
+ import { buildWorkflowGraph } from "./workflow-graph.js";
8
+ export function detectLoops(workflowDef) {
9
+ if (!workflowDef || typeof workflowDef !== "object")
10
+ return [];
11
+ const graph = buildWorkflowGraph(workflowDef);
12
+ if (graph.nodes.size === 0)
13
+ return [];
14
+ const loops = [];
15
+ const { forward } = graph;
16
+ const visited = new Set();
17
+ const recursionStack = new Set();
18
+ const path = [];
19
+ function dfs(nodeId) {
20
+ visited.add(nodeId);
21
+ recursionStack.add(nodeId);
22
+ path.push(nodeId);
23
+ for (const neighbor of (forward.get(nodeId) ?? [])) {
24
+ if (!visited.has(neighbor)) {
25
+ if (dfs(neighbor))
26
+ return true;
27
+ }
28
+ else if (recursionStack.has(neighbor)) {
29
+ const cycleStart = path.indexOf(neighbor);
30
+ const cyclePath = path.slice(cycleStart);
31
+ cyclePath.push(neighbor);
32
+ loops.push({
33
+ type: 'circular_dependency',
34
+ nodes: cyclePath,
35
+ description: `Circular dependency: ${cyclePath.join(' → ')}`,
36
+ severity: 'critical',
37
+ fixSuggestion: `Break the cycle by removing one edge.`,
38
+ });
39
+ return true;
40
+ }
41
+ }
42
+ path.pop();
43
+ recursionStack.delete(nodeId);
44
+ return false;
45
+ }
46
+ if (graph.trigger)
47
+ dfs(graph.trigger);
48
+ for (const nodeId of graph.nodes.keys()) {
49
+ if (!visited.has(nodeId))
50
+ dfs(nodeId);
51
+ }
52
+ // Detect categorizer routing back to upstream (re-entry)
53
+ detectReentryLoops(graph, loops);
54
+ return loops;
55
+ }
56
+ /**
57
+ * Detect categorizer nodes routing back to upstream nodes (re-entry risk).
58
+ * Uses the graph's reverse adjacency map instead of manual edge traversal.
59
+ */
60
+ function detectReentryLoops(graph, loops) {
61
+ for (const [nodeId, graphNode] of graph.nodes) {
62
+ if (graphNode.actionType !== 'chat_categorizer')
63
+ continue;
64
+ const downstream = graph.forward.get(nodeId) ?? new Set();
65
+ const upstream = new Set();
66
+ function collectUpstream(nId, depth = 0) {
67
+ if (depth > 20)
68
+ return;
69
+ for (const parent of (graph.reverse.get(nId) ?? [])) {
70
+ if (!upstream.has(parent)) {
71
+ upstream.add(parent);
72
+ collectUpstream(parent, depth + 1);
73
+ }
74
+ }
75
+ }
76
+ collectUpstream(nodeId);
77
+ for (const downNode of downstream) {
78
+ if (upstream.has(downNode)) {
79
+ loops.push({
80
+ type: 'reentry_loop',
81
+ nodes: [nodeId, downNode],
82
+ description: `Categorizer "${graphNode.displayName || nodeId}" routes to "${downNode}" which is upstream - can cause re-processing`,
83
+ severity: 'warning',
84
+ fixSuggestion: `Add state tracking or one-time-execution gate to prevent re-processing.`,
85
+ });
86
+ }
87
+ }
88
+ }
89
+ }
@@ -896,7 +896,7 @@ export function applySessionMappings(text, session) {
896
896
  * Content fields that SHOULD be sanitized (allowlist approach).
897
897
  * Only these fields contain user-facing content that may have PII.
898
898
  *
899
- * TODO: Generate this from OpenAPI spec or gRPC definitions
899
+ * Manually maintained candidate for auto-generation from OpenAPI/gRPC definitions.
900
900
  */
901
901
  const CONTENT_FIELDS = new Set([
902
902
  // Persona-level
@@ -2,9 +2,8 @@
2
2
  * Structural Rules for LLM Context
3
3
  *
4
4
  * These rules encode the validation logic from:
5
- * - workflow-fixer.ts
6
- * - workflow-execution-analyzer.ts
7
5
  * - knowledge.ts (detectWorkflowIssues)
6
+ * - loop-detection.ts (cycle detection, extracted from workflow-execution-analyzer.ts)
8
7
  *
9
8
  * PURPOSE: Feed these to the LLM so it can self-validate during generation/transformation.
10
9
  * This is the "teach the LLM the rules" approach vs "fix after the fact".
@@ -105,9 +104,9 @@ export const STRUCTURAL_INVARIANTS = [
105
104
  {
106
105
  id: "hitl_has_both_paths",
107
106
  name: "HITL Must Have Success AND Failure Paths",
108
- rule: "A general_hitl node has two outcomes: approval and rejection. Both MUST have downstream handlers.",
107
+ rule: "Legacy general_hitl nodes (if present) have two outcomes: approval and rejection. Both MUST have downstream handlers. Note: general_hitl is NOT deployable for new workflows — HITL is a flag on entity_extraction_with_documents and send_email_agent only.",
109
108
  violation: "HITL 'approval' only has success path - rejections hang",
110
- fix: "Add handler for hitl.approval_decision = 'reject' (typically fixed_response with apology)",
109
+ fix: "For legacy workflows: add handler for hitl.approval_decision = 'reject'. For new workflows: use HITL flag on send_email_agent or entity_extraction_with_documents instead.",
111
110
  severity: "critical",
112
111
  },
113
112
  // ═══════════════════════════════════════════════════════════════════════════
@@ -338,7 +337,7 @@ BEFORE finalizing any workflow modification, verify these rules:
338
337
 
339
338
  ### HITL Rules
340
339
 
341
- 10. **Both Paths Required**: general_hitl needs handlers for both approval AND rejection.
340
+ 10. **Both Paths Required**: Legacy general_hitl nodes need handlers for both approval AND rejection. Note: general_hitl is NOT deployable — HITL is a flag on entity_extraction_with_documents and send_email_agent only.
342
341
 
343
342
  ### Raw workflow_def Format Rules (CRITICAL)
344
343
 
@@ -135,7 +135,7 @@ export const ANTI_PATTERNS = [
135
135
  pattern: "send_email_agent without entity_extraction to validate recipient data",
136
136
  problem: "Sending emails without extracting and validating recipient data risks sending to wrong people or with wrong content. Emails are high-impact actions with external side effects.",
137
137
  solution: "Always: 1) Extract required fields (email_address, subject) via entity_extraction, 2) Validate completeness via categorizer, 3) Ask user if missing. " +
138
- "For approval: enable the HITL flag on send_email_agent (do NOT add a standalone general_hitl node).",
138
+ "For approval: enable the HITL flag on send_email_agent (disable_human_interaction: false). general_hitl is NOT deployable.",
139
139
  detection: {
140
140
  issueType: "incomplete_email_validation",
141
141
  condition: "send_email_agent without preceding entity_extraction node to extract/validate recipient data",
@@ -195,15 +195,15 @@ export const ANTI_PATTERNS = [
195
195
  name: "Incomplete HITL Paths",
196
196
  pattern: "HITL with only success path",
197
197
  problem: "Rejected requests have no handling, leaving users without response.",
198
- solution: "If workflow already has general_hitl node: ALWAYS implement both success AND failure paths. For NEW workflows: use HITL flag on the agent instead of standalone general_hitl nodes (see hitl-patterns guidance).",
198
+ solution: "If workflow already has general_hitl node: ALWAYS implement both success AND failure paths. For NEW workflows: use HITL flag on the agent (only entity_extraction_with_documents and send_email_agent support HITL). general_hitl is NOT deployable.",
199
199
  detection: {
200
200
  issueType: "incomplete_hitl",
201
201
  condition: "HITL node missing 'hitl_status_HITL Success' or 'hitl_status_HITL Failure' edge (note: space, not underscore)",
202
202
  },
203
203
  severity: "critical",
204
- // TODO: Revisit if general_hitl is re-enabled as a standalone node.
205
- // Currently HITL is a flag on agents (send_email_agent, external_action_caller).
206
- // This anti-pattern still fires for existing workflows that use general_hitl.
204
+ // general_hitl is NOT deployable it appears in catalogs but cannot be deployed.
205
+ // HITL is a flag on entity_extraction_with_documents and send_email_agent only.
206
+ // external_action_caller does NOT support HITL. This rule still fires for legacy workflows.
207
207
  },
208
208
  {
209
209
  id: "orphan-nodes",
@@ -2,12 +2,10 @@
2
2
  * Shared Workflow Graph Representation
3
3
  *
4
4
  * Single source of truth for parsing workflow_def JSON into a typed graph.
5
- * Replaces duplicated graph-building across workflow-execution-analyzer,
6
- * workflow-optimizer (deprecated), and workflow-tracer.
5
+ * Used by the workflow optimizer for static analysis.
7
6
  *
8
- * NOTE: `workflow-execution-analyzer.ts` still has its own `buildGraphMaps()`
9
- * which serves validation (runtime checks). This graph serves optimization
10
- * (static analysis). Future work: consolidate both into this module.
7
+ * `loop-detection.ts` uses `buildWorkflowGraph()` from this module for
8
+ * cycle and re-entry detection (consolidated from a prior local `buildGraphMaps`).
11
9
  */
12
10
  // ─────────────────────────────────────────────────────────────────────────────
13
11
  // Constants
@@ -50,7 +50,7 @@ export function enumeratePaths(workflow, options) {
50
50
  throw new Error("Branch outcome without branch_point");
51
51
  }
52
52
  for (const value of result.branch_point.possible_values) {
53
- const forkedSession = forkSessionForBranching(session, result.branch_point, value);
53
+ const forkedSession = forkSessionForBranching(session, result.branch_point, value, workflow);
54
54
  sessionsToProcess.push(forkedSession);
55
55
  }
56
56
  }
@@ -80,7 +80,7 @@ function createInitialSession(workflow) {
80
80
  action_states: new Map(),
81
81
  };
82
82
  }
83
- function forkSessionForBranching(session, branchInfo, chosenValue) {
83
+ function forkSessionForBranching(session, branchInfo, chosenValue, workflow) {
84
84
  // Deep copy session
85
85
  const forked = {
86
86
  completed_actions: [...session.completed_actions],
@@ -102,8 +102,11 @@ function forkSessionForBranching(session, branchInfo, chosenValue) {
102
102
  node_id: branchInfo.action_that_branched,
103
103
  output_name: branchInfo.branching_output_name,
104
104
  });
105
- // TODO: Generate dummy outputs for other outputs of branching action
106
- // TODO: Generate dummy outputs for non-branching actions
105
+ // Add remaining outputs from the branching action (e.g. categorizer also produces confidence)
106
+ const branchingNode = workflow.nodes?.find(n => n.id === branchInfo.action_that_branched);
107
+ if (branchingNode) {
108
+ addActionOutputs(forked, branchingNode);
109
+ }
107
110
  return forked;
108
111
  }
109
112
  // ─────────────────────────────────────────────────────────────────────────────
@@ -231,7 +231,7 @@ persona({ id: "abc", env: "prod" }) // production`,
231
231
  export const TOOL_GUIDANCE = {
232
232
  persona: {
233
233
  toolName: "persona",
234
- quickTip: "Use analyze=true before modifying. Use preview=true before deploying. Build structured operations for modify.",
234
+ quickTip: "Use workflow(mode='get') to understand the workflow before modifying. Use preview=true before deploying.",
235
235
  operations: [
236
236
  {
237
237
  name: "List all",
@@ -243,11 +243,6 @@ export const TOOL_GUIDANCE = {
243
243
  description: "Get details for a specific persona",
244
244
  example: 'persona(id="abc")',
245
245
  },
246
- {
247
- name: "Analyze",
248
- description: "Get workflow_spec, issues, and fix suggestions",
249
- example: 'persona(id="abc", analyze=true)',
250
- },
251
246
  {
252
247
  name: "Update config",
253
248
  description: "Update persona config (name, description, widgets)",
@@ -371,6 +366,51 @@ export const TOOL_GUIDANCE = {
371
366
  ],
372
367
  applicableRules: [],
373
368
  },
369
+ debug: {
370
+ toolName: "debug",
371
+ quickTip: "Inspect workflow executions and audit conversations. Follow the drill-down: conversations → detail → show_work → action_detail.",
372
+ operations: [
373
+ {
374
+ name: "List conversations",
375
+ description: "List audit conversations for a persona",
376
+ example: 'debug(method="conversations", persona_id="abc")',
377
+ },
378
+ {
379
+ name: "Conversation detail",
380
+ description: "Get messages with workflow_run_ids",
381
+ example: 'debug(method="conversation_detail", conversation_id="...")',
382
+ },
383
+ {
384
+ name: "Show work",
385
+ description: "See all actions' execution traces for a workflow run",
386
+ example: 'debug(method="show_work", persona_id="abc", workflow_run_id="...")',
387
+ },
388
+ {
389
+ name: "Action detail",
390
+ description: "Deep trace: inputs, outputs, LLM calls, steps",
391
+ example: 'debug(method="action_detail", persona_id="abc", workflow_run_id="...", action_name="...")',
392
+ },
393
+ {
394
+ name: "Search messages",
395
+ description: "Full-text search across conversation messages",
396
+ example: 'debug(method="search", persona_id="abc", query="pricing")',
397
+ },
398
+ ],
399
+ nextSteps: {
400
+ conversations: "Pick a conversation_id → debug(method='conversation_detail')",
401
+ conversation_detail: "Pick a workflow_run_id → debug(method='show_work', persona_id='...', workflow_run_id='...')",
402
+ show_work: "Pick an action_name (especially ERRORED ones) → debug(method='action_detail', persona_id='...', ...)",
403
+ action_detail: "Examine inputs/outputs/llm_calls to find the root cause",
404
+ search: "Pick a conversation_id from results → debug(method='conversation_detail')",
405
+ },
406
+ commonMistakes: [
407
+ "Jumping to action_detail without running show_work first (you need the action_name)",
408
+ "Forgetting persona_id — required for conversations, show_work, action_detail, and search",
409
+ "Not following _next_step hints in responses",
410
+ "The search method depends on DebuggerService which may not be deployed in all environments",
411
+ ],
412
+ applicableRules: [],
413
+ },
374
414
  };
375
415
  // ─────────────────────────────────────────────────────────────────────────────
376
416
  // Contextual Tips (for tool responses)
@@ -383,19 +423,13 @@ export const CONTEXTUAL_TIPS = [
383
423
  suggestedAction: "persona(id='<id>')",
384
424
  level: "info",
385
425
  },
386
- // After getting persona without analysis
426
+ // After getting persona suggest workflow get for understanding
387
427
  {
388
- condition: "operation === 'get' && !args.analyze",
389
- message: "Run analyze to understand the workflow before making changes",
390
- suggestedAction: "persona(id='...', analyze=true)",
428
+ condition: "operation === 'get'",
429
+ message: "Use workflow(mode='get') to understand the workflow before making changes",
430
+ suggestedAction: "workflow(mode='get', persona_id='...')",
391
431
  level: "info",
392
432
  },
393
- // After analysis shows issues
394
- {
395
- condition: "operation === 'analyze' && result.issues?.length > 0",
396
- message: `Found ${"{result.issues.length}"} issues. Review and build workflow_spec to fix.`,
397
- level: "warning",
398
- },
399
433
  // Update without preview
400
434
  {
401
435
  condition: "operation === 'update' && !args.preview && result.success",
@@ -426,7 +460,7 @@ export function generateServerInstructions(buildInfo) {
426
460
 
427
461
  ## Workflow Pattern
428
462
  1. **Discover**: persona() to list AI Employees
429
- 2. **Understand**: persona(id="...", analyze=true) before any changes
463
+ 2. **Understand**: workflow(mode="get", persona_id="...") before any changes
430
464
  3. **Preview**: Always use preview=true before deploying
431
465
  4. **Deploy**: Only after reviewing preview
432
466
 
@@ -445,8 +479,16 @@ All workflow modifications follow a 3-step flow:
445
479
 
446
480
  The LLM generates the full workflow_def. MCP provides data and executes.
447
481
 
482
+ ## Debugging Workflow Executions
483
+ Use the \`debug\` tool to inspect workflow runs and troubleshoot issues:
484
+ 1. \`debug(method="conversations", persona_id="...")\` → list audit conversations
485
+ 2. \`debug(method="show_work", workflow_run_id="...")\` → see action execution traces
486
+ 3. \`debug(method="action_detail", ...)\` → deep trace with LLM calls, inputs/outputs
487
+ Also available as persona sub-resource: \`persona(id="abc", debug={method:"conversations"})\`
488
+
448
489
  ## Resources
449
490
  - \`ema://docs/usage-guide\` - Complete guide
491
+ - \`ema://docs/debugging-guide\` - Debugging workflow executions
450
492
  - \`ema://catalog/agents-summary\` - Action catalog
451
493
  - \`ema://rules/anti-patterns\` - Common mistakes
452
494
 
@@ -599,22 +641,13 @@ export function getContextualTip(context) {
599
641
  level: "info",
600
642
  };
601
643
  }
602
- if (operation === "get" && !args.analyze) {
644
+ if (operation === "get") {
603
645
  return {
604
- message: "Run analyze to understand the workflow before making changes",
605
- suggestedAction: "persona(id='...', analyze=true)",
646
+ message: "Use workflow(mode='get') to understand the workflow before making changes",
647
+ suggestedAction: "workflow(mode='get', persona_id='...')",
606
648
  level: "info",
607
649
  };
608
650
  }
609
- if (operation === "analyze" && Array.isArray(result.issues)) {
610
- const issues = result.issues;
611
- if (issues.length > 0) {
612
- return {
613
- message: `Found ${issues.length} issues. Review and build workflow_spec to fix.`,
614
- level: "warning",
615
- };
616
- }
617
- }
618
651
  if (operation === "update" && !args.preview) {
619
652
  return {
620
653
  message: "Changes deployed. Consider using preview=true next time.",
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Debug Adapter
3
+ *
4
+ * Thin adapter that resolves client from env and delegates to handleDebug.
5
+ * Same pattern as workflow/adapter.ts.
6
+ *
7
+ * Extracted from server.ts to keep the dispatch table thin.
8
+ */
9
+ import { handleDebug } from "./index.js";
10
+ export async function handleDebugAdapter(args, createClient, getDefaultEnvName) {
11
+ const targetEnv = args.env ?? getDefaultEnvName();
12
+ // createClient() returns EmaClientAdapter at runtime (see env/config.ts)
13
+ const client = createClient(targetEnv);
14
+ return handleDebug(args, client);
15
+ }