@ema.co/mcp-toolkit 2026.2.13 → 2026.2.19

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 (37) 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/mcp/domain/loop-detection.js +97 -0
  4. package/dist/mcp/domain/structural-rules.js +4 -5
  5. package/dist/mcp/domain/validation-rules.js +5 -5
  6. package/dist/mcp/domain/workflow-graph.js +3 -5
  7. package/dist/mcp/guidance.js +9 -29
  8. package/dist/mcp/handlers/feedback/index.js +1 -1
  9. package/dist/mcp/handlers/persona/index.js +237 -8
  10. package/dist/mcp/handlers/persona/schema.js +27 -0
  11. package/dist/mcp/handlers/reference/index.js +6 -4
  12. package/dist/mcp/handlers/workflow/index.js +15 -19
  13. package/dist/mcp/handlers/workflow/validation.js +1 -1
  14. package/dist/mcp/knowledge-types.js +7 -0
  15. package/dist/mcp/knowledge.js +61 -800
  16. package/dist/mcp/resources.js +217 -1
  17. package/dist/mcp/server.js +195 -2152
  18. package/dist/mcp/tools.js +2 -3
  19. package/dist/sdk/generated/agent-catalog.js +615 -0
  20. package/dist/sdk/generated/widget-catalog.js +60 -0
  21. package/docs/README.md +17 -9
  22. package/package.json +1 -1
  23. package/.context/public/guides/dashboard-operations.md +0 -349
  24. package/.context/public/guides/email-patterns.md +0 -125
  25. package/.context/public/guides/workflow-builder-patterns.md +0 -708
  26. package/dist/mcp/domain/intent-architect.js +0 -914
  27. package/dist/mcp/domain/quality-gates.js +0 -110
  28. package/dist/mcp/domain/workflow-execution-analyzer.js +0 -412
  29. package/dist/mcp/domain/workflow-intent.js +0 -1806
  30. package/dist/mcp/domain/workflow-merge.js +0 -449
  31. package/dist/mcp/domain/workflow-tracer.js +0 -648
  32. package/dist/mcp/domain/workflow-transformer.js +0 -742
  33. package/dist/mcp/handlers/persona/intent.js +0 -141
  34. package/dist/mcp/handlers/workflow/analyze.js +0 -119
  35. package/dist/mcp/handlers/workflow/compare.js +0 -70
  36. package/dist/mcp/handlers/workflow/generate.js +0 -384
  37. package/dist/mcp/handlers-consolidated.js +0 -333
@@ -1,141 +0,0 @@
1
- /**
2
- * Persona INTENT mode handler
3
- *
4
- * Exposes the Intent Architect for direct invocation.
5
- * Supports iterative qualification via previous_answers.
6
- *
7
- * Applies to BOTH greenfield AND brownfield scenarios:
8
- * - Greenfield: Creating new complex personas
9
- * - Brownfield: Complex modifications to existing personas (may have ripple effects)
10
- *
11
- * Flow:
12
- * 1. First call: Analyze complexity, return questions or prompt
13
- * 2. Iterative calls: Provide answers, refine until ready
14
- * 3. Final: Return IntentSpec for downstream WorkflowSpec generation/modification
15
- */
16
- import { resolvePersona } from "../utils.js";
17
- import { runIntentArchitect, } from "../../domain/intent-architect.js";
18
- /**
19
- * Handle persona(mode="intent") - direct Intent Architect invocation
20
- *
21
- * @param args.input - Natural language description (required)
22
- * @param args.id - Existing persona ID for brownfield modifications
23
- * @param args.type - Persona type hint (voice/chat/dashboard)
24
- * @param args.previous_answers - Answers to qualification questions (iterative)
25
- * @param args.iteration - Iteration number (1 = first)
26
- * @param args.max_complexity - Cap complexity level for gradual rollout
27
- */
28
- export async function handleIntent(args, client) {
29
- const input = args.input;
30
- const existingPersonaId = (args.id ?? args.identifier);
31
- if (!input) {
32
- return {
33
- status: "error",
34
- error: "input required for intent mode",
35
- hint: "Provide natural language description: persona(mode=\"intent\", input=\"Add email capability...\")",
36
- examples: [
37
- "Greenfield: persona(mode=\"intent\", input=\"Voice AI SDR that qualifies leads\", type=\"voice\")",
38
- "Brownfield: persona(mode=\"intent\", input=\"Send emails with discussion items\", id=\"abc-123\")",
39
- ],
40
- };
41
- }
42
- // Extract parameters
43
- let personaType = args.type;
44
- const previousAnswers = args.previous_answers;
45
- const iteration = args.iteration;
46
- const maxComplexity = args.max_complexity;
47
- // Brownfield: Resolve existing persona for context
48
- let existingPersonaContext;
49
- if (existingPersonaId) {
50
- const persona = await resolvePersona(client, existingPersonaId);
51
- if (persona) {
52
- existingPersonaContext = {
53
- id: persona.id,
54
- name: persona.name ?? "Unknown",
55
- type: persona.trigger_type ?? "unknown",
56
- };
57
- // Infer type from existing persona if not specified
58
- if (!personaType && persona.trigger_type) {
59
- const typeMap = {
60
- "5": "voice", "voice": "voice",
61
- "1": "chat", "chat": "chat",
62
- "2": "dashboard", "dashboard": "dashboard",
63
- };
64
- personaType = typeMap[String(persona.trigger_type)] ?? undefined;
65
- }
66
- }
67
- }
68
- // Determine if this is greenfield or brownfield
69
- const isBrownfield = !!existingPersonaContext;
70
- // Call the canonical SDK entrypoint
71
- const result = runIntentArchitect(input, { persona_type: personaType }, {
72
- previous_answers: previousAnswers,
73
- iteration: iteration ?? 1,
74
- max_complexity: maxComplexity,
75
- });
76
- // Build base response with context
77
- const baseResponse = {
78
- assessment: result.assessment,
79
- strategy: result.strategy,
80
- context: isBrownfield ? {
81
- mode: "brownfield",
82
- existing_persona: existingPersonaContext,
83
- } : {
84
- mode: "greenfield",
85
- },
86
- };
87
- // Format response based on strategy
88
- if (result.strategy.can_proceed) {
89
- // Ready to proceed - return IntentSpec
90
- const nextStep = isBrownfield
91
- ? `Use intent_spec to modify existing persona. Steps:\n` +
92
- `1. Get current state: persona(mode="analyze", id="${existingPersonaContext?.id}")\n` +
93
- `2. LLM transforms IntentSpec + current workflow_spec → modified WorkflowSpec\n` +
94
- `3. Deploy: persona(mode="update", id="${existingPersonaContext?.id}", workflow_spec=...)`
95
- : `Use intent_spec to create WorkflowSpec. Options:\n` +
96
- `1. LLM transforms IntentSpec → WorkflowSpec (recommended)\n` +
97
- `2. Call persona(mode="create", workflow_spec=...) to deploy`;
98
- return {
99
- status: "ready",
100
- ...baseResponse,
101
- intent_spec: result.intentSpec,
102
- next_step: nextStep,
103
- };
104
- }
105
- if (result.questions && result.questions.length > 0) {
106
- // Needs qualification - return questions
107
- const idParam = isBrownfield ? `, id="${existingPersonaContext?.id}"` : "";
108
- return {
109
- status: "needs_qualification",
110
- ...baseResponse,
111
- questions: result.questions,
112
- gates_blocking: result.strategy.gates_to_ask,
113
- next_step: result.strategy.next_step,
114
- hint: `Answer the questions and call again with previous_answers:\n` +
115
- `persona(mode="intent", input="${input.substring(0, 50)}..."${idParam}, previous_answers={...}, iteration=${(iteration ?? 1) + 1})`,
116
- };
117
- }
118
- if (result.prompt_package) {
119
- // Complex - return prompt for LLM
120
- const deployStep = isBrownfield
121
- ? `3. Transform IntentSpec + existing workflow → modified WorkflowSpec\n` +
122
- `4. Deploy: persona(mode="update", id="${existingPersonaContext?.id}", workflow_spec=...)`
123
- : `3. Transform IntentSpec → WorkflowSpec → Deploy`;
124
- return {
125
- status: "needs_llm",
126
- ...baseResponse,
127
- llm_prompt: result.prompt_package,
128
- next_step: `Send llm_prompt to an LLM to generate IntentSpec. The LLM will:\n` +
129
- `1. Parse the request using Intent Architect methodology\n` +
130
- `2. Return a structured IntentSpec\n` +
131
- deployStep,
132
- };
133
- }
134
- // Fallback - unexpected state
135
- return {
136
- status: "error",
137
- ...baseResponse,
138
- error: "Unexpected state: no questions, no prompt, but can_proceed=false",
139
- hint: "This may indicate a bug in the Intent Architect logic. Please report.",
140
- };
141
- }
@@ -1,119 +0,0 @@
1
- /**
2
- * Workflow Analyze Handler
3
- *
4
- * Returns workflow DATA for LLM to analyze using rules from ema://rules/*.
5
- *
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
15
- */
16
- import { validateWorkflowConnections, parseWorkflowDef } from "../../knowledge.js";
17
- /**
18
- * Calculate workflow metrics (pure data, no analysis)
19
- */
20
- function calculateMetrics(workflow) {
21
- const actions = workflow.actions;
22
- if (!actions)
23
- return { node_count: 0, connection_count: 0 };
24
- // Count connections
25
- let connectionCount = 0;
26
- for (const action of actions) {
27
- const inputs = action.inputs;
28
- if (inputs) {
29
- connectionCount += Object.keys(inputs).length;
30
- }
31
- }
32
- // Check for HITL
33
- const hasHitl = actions.some(a => {
34
- const name = a.name?.toLowerCase() ?? "";
35
- return name.includes("hitl") || name.includes("human");
36
- });
37
- // Get trigger type
38
- const trigger = workflow.trigger;
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
- });
46
- return {
47
- node_count: actions.length,
48
- connection_count: connectionCount,
49
- has_hitl: hasHitl,
50
- has_categorizer: hasCategorizer,
51
- trigger_type: triggerType,
52
- };
53
- }
54
- /**
55
- * Handle workflow analyze mode
56
- *
57
- * Returns DATA for LLM to analyze - does NOT pre-compute issues.
58
- */
59
- export async function handleWorkflowAnalyze(args, client) {
60
- const personaId = args.persona_id;
61
- let workflow = args.workflow_def;
62
- let persona = null;
63
- // Get workflow from persona if not provided
64
- if (personaId && !workflow) {
65
- const fetchedPersona = await client.getPersonaById(personaId);
66
- if (!fetchedPersona) {
67
- return { error: `Persona not found: ${personaId}` };
68
- }
69
- persona = fetchedPersona;
70
- workflow = fetchedPersona.workflow_def;
71
- }
72
- if (!workflow) {
73
- return { error: "No workflow to analyze. Provide workflow_def or persona_id." };
74
- }
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);
81
- const result = {
82
- mode: "analyze",
83
- persona_id: personaId,
84
- persona_name: persona?.name,
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
- ],
111
- };
112
- if (persona) {
113
- result.persona = {
114
- id: persona.id,
115
- name: persona.name
116
- };
117
- }
118
- return result;
119
- }
@@ -1,70 +0,0 @@
1
- /**
2
- * Workflow Compare Handler
3
- *
4
- * Compares two workflow versions to identify differences.
5
- */
6
- /**
7
- * Compare two workflows and return differences
8
- */
9
- function compareWorkflows(workflow1, workflow2) {
10
- const differences = [];
11
- if (!workflow1 && !workflow2)
12
- return differences;
13
- if (!workflow1) {
14
- differences.push({ type: "added", path: "workflow", new_value: "entire workflow" });
15
- return differences;
16
- }
17
- if (!workflow2) {
18
- differences.push({ type: "removed", path: "workflow", old_value: "entire workflow" });
19
- return differences;
20
- }
21
- // Compare actions
22
- const actions1 = workflow1.actions || [];
23
- const actions2 = workflow2.actions || [];
24
- const actionIds1 = new Set(actions1.map(a => (a.id ?? a.name)));
25
- const actionIds2 = new Set(actions2.map(a => (a.id ?? a.name)));
26
- // Find added/removed actions
27
- for (const id of actionIds2) {
28
- if (!actionIds1.has(id)) {
29
- differences.push({ type: "added", path: `actions.${id}`, new_value: id });
30
- }
31
- }
32
- for (const id of actionIds1) {
33
- if (!actionIds2.has(id)) {
34
- differences.push({ type: "removed", path: `actions.${id}`, old_value: id });
35
- }
36
- }
37
- // Node count change
38
- if (actions1.length !== actions2.length) {
39
- differences.push({
40
- type: "changed",
41
- path: "actions.length",
42
- old_value: actions1.length,
43
- new_value: actions2.length,
44
- });
45
- }
46
- return differences;
47
- }
48
- /**
49
- * Handle workflow compare mode
50
- */
51
- export async function handleWorkflowCompare(args, client) {
52
- const personaId = args.persona_id;
53
- const compareTo = args.compare_to;
54
- if (!personaId || !compareTo) {
55
- return { error: "persona_id and compare_to required for compare mode" };
56
- }
57
- const persona1 = await client.getPersonaById(personaId);
58
- const persona2 = await client.getPersonaById(compareTo);
59
- if (!persona1 || !persona2) {
60
- return { error: "One or both personas not found" };
61
- }
62
- const workflow1 = persona1.workflow_def;
63
- const workflow2 = persona2.workflow_def;
64
- return {
65
- mode: "compare",
66
- persona_1: { id: persona1.id, name: persona1.name },
67
- persona_2: { id: persona2.id, name: persona2.name },
68
- differences: compareWorkflows(workflow1, workflow2),
69
- };
70
- }