@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.
- package/.context/public/guides/ema-user-guide.md +12 -16
- package/.context/public/guides/mcp-tools-guide.md +203 -334
- package/dist/mcp/domain/loop-detection.js +97 -0
- package/dist/mcp/domain/structural-rules.js +4 -5
- package/dist/mcp/domain/validation-rules.js +5 -5
- package/dist/mcp/domain/workflow-graph.js +3 -5
- package/dist/mcp/guidance.js +9 -29
- package/dist/mcp/handlers/feedback/index.js +1 -1
- package/dist/mcp/handlers/persona/index.js +237 -8
- package/dist/mcp/handlers/persona/schema.js +27 -0
- package/dist/mcp/handlers/reference/index.js +6 -4
- package/dist/mcp/handlers/workflow/index.js +15 -19
- package/dist/mcp/handlers/workflow/validation.js +1 -1
- package/dist/mcp/knowledge-types.js +7 -0
- package/dist/mcp/knowledge.js +61 -800
- package/dist/mcp/resources.js +217 -1
- package/dist/mcp/server.js +195 -2152
- package/dist/mcp/tools.js +2 -3
- package/dist/sdk/generated/agent-catalog.js +615 -0
- package/dist/sdk/generated/widget-catalog.js +60 -0
- package/docs/README.md +17 -9
- package/package.json +1 -1
- package/.context/public/guides/dashboard-operations.md +0 -349
- package/.context/public/guides/email-patterns.md +0 -125
- package/.context/public/guides/workflow-builder-patterns.md +0 -708
- package/dist/mcp/domain/intent-architect.js +0 -914
- package/dist/mcp/domain/quality-gates.js +0 -110
- package/dist/mcp/domain/workflow-execution-analyzer.js +0 -412
- package/dist/mcp/domain/workflow-intent.js +0 -1806
- package/dist/mcp/domain/workflow-merge.js +0 -449
- package/dist/mcp/domain/workflow-tracer.js +0 -648
- package/dist/mcp/domain/workflow-transformer.js +0 -742
- package/dist/mcp/handlers/persona/intent.js +0 -141
- package/dist/mcp/handlers/workflow/analyze.js +0 -119
- package/dist/mcp/handlers/workflow/compare.js +0 -70
- package/dist/mcp/handlers/workflow/generate.js +0 -384
- 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
|
-
}
|