@ema.co/mcp-toolkit 2026.1.27 → 2026.1.28-2

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 (35) hide show
  1. package/dist/mcp/handlers/data/index.js +3 -0
  2. package/dist/mcp/handlers/persona/create.js +16 -0
  3. package/dist/mcp/handlers/persona/list.js +9 -4
  4. package/dist/mcp/handlers/persona/update.js +24 -2
  5. package/dist/mcp/handlers/workflow/deploy.js +20 -2
  6. package/dist/mcp/handlers/workflow/generate.js +39 -2
  7. package/dist/mcp/handlers/workflow/index.js +8 -3
  8. package/dist/mcp/handlers/workflow/modify.js +34 -7
  9. package/dist/mcp/handlers/workflow/validate.js +85 -0
  10. package/dist/mcp/handlers/workflow/validation.js +160 -0
  11. package/dist/mcp/resources.js +286 -4
  12. package/dist/mcp/server.js +16 -3
  13. package/dist/mcp/tools.js +32 -11
  14. package/dist/sdk/client.js +36 -9
  15. package/dist/sdk/ema-client.js +32 -4
  16. package/dist/sdk/index.js +3 -1
  17. package/dist/sdk/knowledge.js +5 -5
  18. package/dist/sdk/structural-rules.js +498 -0
  19. package/dist/sdk/workflow-generator.js +2 -1
  20. package/dist/sdk/workflow-intent.js +28 -96
  21. package/dist/sdk/workflow-path-enumerator.js +278 -0
  22. package/dist/sdk/workflow-static-validator.js +291 -0
  23. package/dist/sdk/workflow-validation-types.js +7 -0
  24. package/docs/README.md +14 -0
  25. package/docs/go-validator-analysis.md +323 -0
  26. package/docs/rule-format-specification.md +346 -0
  27. package/docs/validation-contract.md +397 -0
  28. package/docs/validation-error-format.md +326 -0
  29. package/package.json +1 -1
  30. package/dist/mcp/workflow-operations.js +0 -100
  31. package/dist/sdk/workflow-fixer.js +0 -48
  32. package/docs/dashboard-operations.md +0 -281
  33. package/docs/ema-user-guide.md +0 -1201
  34. package/docs/email-patterns.md +0 -120
  35. package/docs/mcp-tools-guide.md +0 -575
@@ -156,22 +156,8 @@ export function detectInputType(input) {
156
156
  // ─────────────────────────────────────────────────────────────────────────────
157
157
  // Natural Language Parsing
158
158
  // ─────────────────────────────────────────────────────────────────────────────
159
- const INTENT_PATTERNS = [
160
- { pattern: /password\s*reset/i, name: "Password Reset", handler: "llm" },
161
- { pattern: /billing|payment|invoice/i, name: "Billing", handler: "search" },
162
- { pattern: /technical|support|help/i, name: "Technical Support", handler: "search" },
163
- { pattern: /schedule|appointment|book/i, name: "Scheduling", handler: "tool" },
164
- { pattern: /ticket|incident|request/i, name: "Ticket Creation", handler: "tool" },
165
- { pattern: /status|check|lookup/i, name: "Status Check", handler: "search" },
166
- ];
167
- const TOOL_PATTERNS = [
168
- { pattern: /servicenow/i, namespace: "service_now", action: "Create_Incident" },
169
- { pattern: /salesforce/i, namespace: "salesforce", action: "Create_Case" },
170
- { pattern: /jira/i, namespace: "jira", action: "Create_Issue" },
171
- { pattern: /slack/i, namespace: "slack", action: "Send_Message" },
172
- { pattern: /email/i, namespace: "email", action: "Send_Email" },
173
- { pattern: /calendar|schedule/i, namespace: "calendar", action: "Check_Availability" },
174
- ];
159
+ // INTENT_PATTERNS and TOOL_PATTERNS removed - were only used by parseNaturalLanguage()
160
+ // which violated LLM-driven architecture. Agent should understand intent naturally.
175
161
  // ─────────────────────────────────────────────────────────────────────────────
176
162
  // Action Chain Patterns - End-to-End Semantic Flows
177
163
  // ─────────────────────────────────────────────────────────────────────────────
@@ -529,83 +515,26 @@ export function detectActionChains(text) {
529
515
  }
530
516
  return detected.filter(Boolean);
531
517
  }
532
- const PERSONA_TYPE_PATTERNS = [
533
- { pattern: /voice|call|phone/i, type: "voice" },
534
- { pattern: /document|batch|upload/i, type: "dashboard" },
535
- ];
518
+ // PERSONA_TYPE_PATTERNS removed - no longer used after parseNaturalLanguage() removal
536
519
  /**
537
- * @deprecated Use LLM-based intent extraction instead of regex parsing.
520
+ * REMOVED: parseNaturalLanguage() - regex-based NL parsing violates LLM-driven architecture
538
521
  *
539
- * This function uses regex patterns to parse natural language, which is an
540
- * anti-pattern. The LLM (Agent) should understand user intent naturally
541
- * and provide structured data directly.
522
+ * This function was removed because it violates the core principle:
523
+ * "THE AGENT (LLM) DOES THE THINKING. THE MCP PROVIDES CONTEXT AND EXECUTES."
542
524
  *
543
- * For greenfield workflow generation, consider using:
544
- * - Intent Architect for complex requirements analysis
545
- * - Direct structured input (WorkflowSpec) from the Agent
525
+ * Natural language input should be handled by:
526
+ * 1. Intent Architect (runIntentArchitect) - analyzes complexity and returns LLM prompts
527
+ * 2. generateWorkflow() - takes WorkflowIntent and returns LLM prompts for complex cases
528
+ * 3. Direct structured input (WorkflowSpec) from the Agent
546
529
  *
547
- * This function is retained for backward compatibility but should not be
548
- * used in new code paths.
530
+ * For natural language input, create a minimal WorkflowIntent and let Intent Architect
531
+ * or generateWorkflow() handle the complexity analysis.
549
532
  *
550
533
  * @see src/mcp/AGENTS.md for the LLM-driven architecture guidelines
551
534
  */
552
- export function parseNaturalLanguage(text) {
553
- console.warn("[DEPRECATED] parseNaturalLanguage() uses regex parsing which is an anti-pattern. " +
554
- "Consider using LLM-based intent extraction or structured input instead.");
555
- // Detect persona type (default: chat)
556
- let personaType = "chat";
557
- for (const { pattern, type } of PERSONA_TYPE_PATTERNS) {
558
- if (pattern.test(text)) {
559
- personaType = type;
560
- break;
561
- }
562
- }
563
- // Detect intents
564
- const intents = [];
565
- const seenIntents = new Set();
566
- for (const { pattern, name, handler } of INTENT_PATTERNS) {
567
- if (pattern.test(text) && !seenIntents.has(name)) {
568
- seenIntents.add(name);
569
- intents.push({
570
- name,
571
- description: `User requests ${name.toLowerCase()}`,
572
- handler,
573
- });
574
- }
575
- }
576
- // Detect tools from text
577
- const tools = [];
578
- for (const { pattern, namespace, action } of TOOL_PATTERNS) {
579
- if (pattern.test(text)) {
580
- tools.push({ namespace, action });
581
- }
582
- }
583
- // Detect data sources
584
- const dataSources = [];
585
- if (/search|knowledge|faq|document|lookup|kb/i.test(text)) {
586
- dataSources.push({ type: "knowledge_base" });
587
- }
588
- if (/web|internet|online|live/i.test(text)) {
589
- dataSources.push({ type: "web_search" });
590
- }
591
- if (dataSources.length === 0) {
592
- // Default to KB search for most use cases
593
- dataSources.push({ type: "knowledge_base" });
594
- }
595
- // Detect constraints
596
- const constraints = {};
597
- if (/approv|human|review|escalat|hitl/i.test(text)) {
598
- constraints.require_hitl = true;
599
- }
600
- if (/validat|check|verify/i.test(text)) {
601
- constraints.require_validation = true;
602
- }
603
- // Detect action chains - end-to-end semantic flows
604
- const actionChains = detectActionChains(text);
605
- // Detect entities to extract
606
- const entities = detectEntities(text);
607
- // Detect delivery configuration
608
- const deliveryConfig = detectDeliveryConfig(text);
535
+ function createMinimalIntentFromText(text, personaType = "chat") {
536
+ // Create minimal intent - no regex parsing
537
+ // Intent Architect and generateWorkflow() will handle complexity analysis
609
538
  // Extract a name from the text (first sentence or first N words)
610
539
  const sentences = text.split(/[.!?]/);
611
540
  const name = sentences[0].trim().slice(0, 50) || "AI Employee";
@@ -613,13 +542,6 @@ export function parseNaturalLanguage(text) {
613
542
  name,
614
543
  description: text,
615
544
  persona_type: personaType,
616
- intents: intents.length > 0 ? intents : undefined,
617
- tools: tools.length > 0 ? tools : undefined,
618
- data_sources: dataSources,
619
- constraints: Object.keys(constraints).length > 0 ? constraints : undefined,
620
- action_chains: actionChains.length > 0 ? actionChains : undefined,
621
- entities: entities.length > 0 ? entities : undefined,
622
- delivery_config: deliveryConfig,
623
545
  };
624
546
  }
625
547
  /**
@@ -1527,9 +1449,19 @@ export function parseInput(input) {
1527
1449
  let intent;
1528
1450
  switch (inputType) {
1529
1451
  case "natural_language":
1530
- // DEPRECATED: This path uses regex-based parsing.
1531
- // Prefer structured input from the Agent instead.
1532
- intent = parseNaturalLanguage(String(input));
1452
+ // Create minimal intent - no regex parsing
1453
+ // Intent Architect and generateWorkflow() will handle complexity
1454
+ const text = String(input);
1455
+ // Detect persona type from args if available, otherwise default to chat
1456
+ let personaType = "chat";
1457
+ // Simple detection - can be overridden by args.type in handler
1458
+ if (/voice|call|phone/i.test(text)) {
1459
+ personaType = "voice";
1460
+ }
1461
+ else if (/document|batch|upload/i.test(text)) {
1462
+ personaType = "dashboard";
1463
+ }
1464
+ intent = createMinimalIntentFromText(text, personaType);
1533
1465
  break;
1534
1466
  case "partial_spec":
1535
1467
  intent = parsePartialSpec(input);
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Workflow Path Enumeration
3
+ *
4
+ * Implements the Go validator's path enumeration algorithm for static validation.
5
+ * Enumerates all possible execution paths in a workflow by:
6
+ * 1. Traversing workflow graph topologically
7
+ * 2. Forking sessions on branching actions (categorizers, enums, booleans)
8
+ * 3. Tracking completed paths with their outputs
9
+ *
10
+ * Based on: workflow-engine/pkg/engine/validator.go
11
+ */
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
+ // Path Enumeration
14
+ // ─────────────────────────────────────────────────────────────────────────────
15
+ /**
16
+ * Enumerate all possible execution paths in a workflow.
17
+ *
18
+ * Algorithm:
19
+ * 1. Create initial session
20
+ * 2. Queue initial session
21
+ * 3. While queue not empty:
22
+ * a. Dequeue session
23
+ * b. Run validation on session
24
+ * c. If branching: fork for each possible value, queue forked sessions
25
+ * d. If completed: validate named results, store path info
26
+ * 4. Return completed paths
27
+ */
28
+ export function enumeratePaths(workflow, options) {
29
+ const maxPaths = options?.max_paths ?? 1000;
30
+ const timeout = options?.timeout_ms ?? 100;
31
+ const startTime = Date.now();
32
+ const completedPaths = [];
33
+ const sessionsToProcess = [createInitialSession(workflow)];
34
+ while (sessionsToProcess.length > 0) {
35
+ // Check timeout
36
+ if (Date.now() - startTime > timeout) {
37
+ throw new Error(`Path enumeration timeout after ${timeout}ms`);
38
+ }
39
+ // Check max paths
40
+ if (completedPaths.length >= maxPaths) {
41
+ throw new Error(`Path enumeration exceeded max paths limit: ${maxPaths}`);
42
+ }
43
+ // Dequeue session
44
+ const session = sessionsToProcess.shift();
45
+ // Run validation on session
46
+ const result = runValidationOnSession(session, workflow);
47
+ if (result.outcome === "branch") {
48
+ // Fork session for each possible value
49
+ if (!result.branch_point) {
50
+ throw new Error("Branch outcome without branch_point");
51
+ }
52
+ for (const value of result.branch_point.possible_values) {
53
+ const forkedSession = forkSessionForBranching(session, result.branch_point, value);
54
+ sessionsToProcess.push(forkedSession);
55
+ }
56
+ }
57
+ else if (result.outcome === "completed") {
58
+ // Store completed path
59
+ const pathInfo = {
60
+ completed_actions: result.completed_actions || [],
61
+ final_outputs: result.final_outputs || {},
62
+ action_name: session.last_branching_action?.action_that_branched ||
63
+ (result.completed_actions?.[result.completed_actions.length - 1] || ""),
64
+ enumerable_output: session.last_branching_action?.branching_output_name,
65
+ chosen_enumerable_value: session.last_branching_action?.chosen_value,
66
+ validation_failed: result.validation_failed || false,
67
+ };
68
+ completedPaths.push(pathInfo);
69
+ }
70
+ }
71
+ return completedPaths;
72
+ }
73
+ // ─────────────────────────────────────────────────────────────────────────────
74
+ // Session Management
75
+ // ─────────────────────────────────────────────────────────────────────────────
76
+ function createInitialSession(workflow) {
77
+ return {
78
+ completed_actions: [],
79
+ available_outputs: new Map(),
80
+ action_states: new Map(),
81
+ };
82
+ }
83
+ function forkSessionForBranching(session, branchInfo, chosenValue) {
84
+ // Deep copy session
85
+ const forked = {
86
+ completed_actions: [...session.completed_actions],
87
+ available_outputs: new Map(session.available_outputs),
88
+ action_states: new Map(session.action_states),
89
+ last_branching_action: {
90
+ action_that_branched: branchInfo.action_that_branched,
91
+ branching_output_name: branchInfo.branching_output_name,
92
+ possible_values: branchInfo.possible_values,
93
+ chosen_value: chosenValue,
94
+ },
95
+ };
96
+ // Mark branching action as completed
97
+ forked.action_states.set(branchInfo.action_that_branched, "completed");
98
+ forked.completed_actions.push(branchInfo.action_that_branched);
99
+ // Add branching output to available outputs
100
+ const outputKey = `${branchInfo.action_that_branched}.${branchInfo.branching_output_name}`;
101
+ forked.available_outputs.set(outputKey, {
102
+ node_id: branchInfo.action_that_branched,
103
+ output_name: branchInfo.branching_output_name,
104
+ });
105
+ // TODO: Generate dummy outputs for other outputs of branching action
106
+ // TODO: Generate dummy outputs for non-branching actions
107
+ return forked;
108
+ }
109
+ // ─────────────────────────────────────────────────────────────────────────────
110
+ // Validation Execution
111
+ // ─────────────────────────────────────────────────────────────────────────────
112
+ function runValidationOnSession(session, workflow) {
113
+ // Find next actions to execute (topological order)
114
+ const nextActions = findExecutableActions(session, workflow);
115
+ if (nextActions.length === 0) {
116
+ // No more actions to execute - path is complete
117
+ return {
118
+ outcome: "completed",
119
+ completed_actions: session.completed_actions,
120
+ final_outputs: extractFinalOutputs(session),
121
+ validation_failed: false,
122
+ };
123
+ }
124
+ // Check for branching actions
125
+ for (const action of nextActions) {
126
+ const branchInfo = detectBranchingAction(action, workflow);
127
+ if (branchInfo) {
128
+ return {
129
+ outcome: "branch",
130
+ branch_point: branchInfo,
131
+ };
132
+ }
133
+ }
134
+ // Execute non-branching actions and track outputs
135
+ for (const action of nextActions) {
136
+ session.action_states.set(action.id, "completed");
137
+ session.completed_actions.push(action.id);
138
+ // Track outputs from this action
139
+ // For validation, we mark that this node produces outputs
140
+ // The actual output name depends on action type - use common patterns
141
+ addActionOutputs(session, action);
142
+ }
143
+ // Continue traversal
144
+ return runValidationOnSession(session, workflow);
145
+ }
146
+ /**
147
+ * Add outputs from an action to the session's available_outputs.
148
+ * Maps action types to their known outputs.
149
+ */
150
+ function addActionOutputs(session, action) {
151
+ // Common outputs by action type (simplified - LLM knows the full schema)
152
+ const outputsByType = {
153
+ chat_trigger: ["user_query", "conversation_id"],
154
+ document_trigger: ["document_content"],
155
+ search: ["search_results", "sources"],
156
+ call_llm: ["llm_response", "response"],
157
+ respond_with_sources: ["response_with_sources"],
158
+ fixed_response: ["response"],
159
+ external_action_caller: ["action_result"],
160
+ chat_categorizer: ["category", "confidence"],
161
+ text_categorizer: ["category", "confidence"],
162
+ };
163
+ const outputs = outputsByType[action.actionType] || ["output"];
164
+ for (const outputName of outputs) {
165
+ const key = `${action.id}.${outputName}`;
166
+ session.available_outputs.set(key, {
167
+ node_id: action.id,
168
+ output_name: outputName,
169
+ });
170
+ }
171
+ }
172
+ // ─────────────────────────────────────────────────────────────────────────────
173
+ // Helper Functions
174
+ // ─────────────────────────────────────────────────────────────────────────────
175
+ function findExecutableActions(session, workflow) {
176
+ // Find actions whose inputs are all satisfied
177
+ const executable = [];
178
+ for (const node of workflow.nodes || []) {
179
+ if (session.action_states.get(node.id) === "completed") {
180
+ continue; // Already executed
181
+ }
182
+ // Check if all inputs are available
183
+ const allInputsSatisfied = checkInputsSatisfied(node, session, workflow);
184
+ if (allInputsSatisfied) {
185
+ executable.push(node);
186
+ }
187
+ }
188
+ return executable;
189
+ }
190
+ function checkInputsSatisfied(node, session, _workflow) {
191
+ // Triggers have no dependencies - always executable first
192
+ if (node.actionType.includes("trigger")) {
193
+ return session.completed_actions.length === 0; // Only at start
194
+ }
195
+ // Check if all input sources are completed
196
+ if (!node.inputs) {
197
+ return true; // No inputs required
198
+ }
199
+ for (const [_inputName, binding] of Object.entries(node.inputs)) {
200
+ // Literal/inline inputs are always available
201
+ if (binding.type === "literal" ||
202
+ binding.type === "inline_string" ||
203
+ binding.type === "inline_number" ||
204
+ binding.type === "inline_bool" ||
205
+ binding.type === "llm_inferred") {
206
+ continue;
207
+ }
208
+ // Action output - check if source action is completed
209
+ if (binding.type === "action_output" && binding.actionName) {
210
+ if (session.action_states.get(binding.actionName) !== "completed") {
211
+ return false;
212
+ }
213
+ }
214
+ }
215
+ return true;
216
+ }
217
+ function detectBranchingAction(node, _workflow) {
218
+ // Categorizers branch based on category selection
219
+ // LLM already knows this from rules - this is just the automated check
220
+ if (node.actionType === "chat_categorizer" ||
221
+ node.actionType === "text_categorizer") {
222
+ const categories = node.categories || [];
223
+ if (categories.length > 0) {
224
+ return {
225
+ action_that_branched: node.id,
226
+ branching_output_name: "category",
227
+ possible_values: categories.map((c) => c.name),
228
+ chosen_value: undefined,
229
+ };
230
+ }
231
+ }
232
+ // Note: Boolean/enum outputs could also branch, but categorizers are the
233
+ // primary branching mechanism in workflows. LLM handles other cases via rules.
234
+ return null;
235
+ }
236
+ function extractFinalOutputs(session) {
237
+ const outputs = {};
238
+ // Keys are already in "nodeId.outputName" format from addActionOutputs
239
+ for (const [key, value] of session.available_outputs.entries()) {
240
+ outputs[key] = { node_id: value.node_id, output_name: value.output_name };
241
+ }
242
+ return outputs;
243
+ }
244
+ // ─────────────────────────────────────────────────────────────────────────────
245
+ // Branching Detection
246
+ // ─────────────────────────────────────────────────────────────────────────────
247
+ /**
248
+ * Check if an output type is enumerable (can be used for branching).
249
+ * Enumerable types: enum, boolean
250
+ */
251
+ export function isOutputTypeEnumerable(outputType) {
252
+ // Enum types
253
+ if (outputType.includes("enum") || outputType.includes("Enum")) {
254
+ return true;
255
+ }
256
+ // Boolean types
257
+ if (outputType === "WELL_KNOWN_TYPE_BOOL" || outputType === "bool" || outputType === "boolean") {
258
+ return true;
259
+ }
260
+ return false;
261
+ }
262
+ /**
263
+ * Get all possible values for an enumerable output.
264
+ * For enums: all enum options
265
+ * For booleans: [true, false]
266
+ */
267
+ export function getAllPossibleValuesForEnumerableOutput(outputType, enumType) {
268
+ if (outputType.includes("enum") || outputType.includes("Enum")) {
269
+ if (enumType?.options) {
270
+ return enumType.options.map(opt => opt.name);
271
+ }
272
+ return [];
273
+ }
274
+ if (outputType === "WELL_KNOWN_TYPE_BOOL" || outputType === "bool" || outputType === "boolean") {
275
+ return [true, false];
276
+ }
277
+ return [];
278
+ }