@ema.co/mcp-toolkit 2026.1.26-4 → 2026.1.27
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/dist/mcp/handlers/action/index.js +14 -2
- package/dist/mcp/handlers/data/index.js +72 -6
- package/dist/mcp/handlers/env/index.js +3 -3
- package/dist/mcp/handlers/reference/index.js +15 -2
- package/dist/mcp/handlers/workflow/analyze.js +53 -105
- package/dist/mcp/handlers/workflow/deploy.js +15 -0
- package/dist/mcp/handlers/workflow/generate.js +3 -6
- package/dist/mcp/handlers/workflow/index.js +18 -3
- package/dist/mcp/handlers/workflow/modify.js +9 -29
- package/dist/mcp/handlers/workflow/optimize.js +22 -108
- package/dist/mcp/prompts.js +12 -15
- package/dist/mcp/resources.js +8 -0
- package/dist/mcp/server.js +196 -250
- package/dist/mcp/tools.js +25 -8
- package/dist/sdk/action-schema-parser.js +11 -5
- package/dist/sdk/client.js +1 -1
- package/dist/sdk/guidance.js +58 -35
- package/dist/sdk/index.js +8 -7
- package/dist/sdk/knowledge.js +99 -1938
- package/dist/sdk/quality-gates.js +60 -336
- package/dist/sdk/validation-rules.js +33 -0
- package/dist/sdk/workflow-fixer.js +29 -360
- package/dist/sdk/workflow-intent.js +43 -3
- package/docs/dashboard-operations.md +35 -0
- package/docs/ema-user-guide.md +66 -0
- package/docs/mcp-tools-guide.md +40 -3
- package/package.json +1 -2
- package/resources/action-schema.json +0 -5678
|
@@ -1,379 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Workflow Fixer (SDK)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Call autoFixWorkflow() with just a workflow_def for one-call fixing.
|
|
4
|
+
* DEPRECATED: This module used detectWorkflowIssues which has been removed.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
* - Multiple responder detection
|
|
12
|
-
* - Ungated responder detection
|
|
6
|
+
* The LLM should analyze workflows using rules from:
|
|
7
|
+
* - ema://rules/anti-patterns
|
|
8
|
+
* - ema://rules/input-sources
|
|
9
|
+
* - ema://rules/optimizations
|
|
13
10
|
*
|
|
14
|
-
*
|
|
11
|
+
* And then make structured modifications via:
|
|
12
|
+
* - workflow(mode="deploy", workflow_def={...})
|
|
13
|
+
* - persona(mode="update", operations=[...])
|
|
15
14
|
*
|
|
16
|
-
*
|
|
17
|
-
* context needed. Best for SDK consumers who want autonomous fixing.
|
|
18
|
-
*
|
|
19
|
-
* - applyWorkflowFixes() (MCP): Takes pre-detected issues AND persona for
|
|
20
|
-
* Voice AI specific fixes (results mapping, HITL paths). Used by MCP handlers.
|
|
21
|
-
*
|
|
22
|
-
* These are COMPLEMENTARY, not duplicates. Use autoFixWorkflow() for SDK usage,
|
|
23
|
-
* use applyWorkflowFixes() when you have persona context and pre-analyzed issues.
|
|
15
|
+
* This file is kept for backwards compatibility with minimal functionality.
|
|
24
16
|
*/
|
|
25
|
-
import { detectWorkflowIssues } from "./knowledge.js";
|
|
26
|
-
import { detectMultipleResponders } from "./workflow-execution-analyzer.js";
|
|
27
17
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
28
|
-
//
|
|
18
|
+
// DEPRECATED FUNCTIONS (no-ops)
|
|
29
19
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
30
20
|
/**
|
|
31
|
-
*
|
|
32
|
-
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
const orphanIssues = issues.filter(i => i.type === 'orphan');
|
|
36
|
-
if (orphanIssues.length === 0)
|
|
37
|
-
return { def: workflowDef, fixes };
|
|
38
|
-
// Get actions array (Ema format)
|
|
39
|
-
const actions = (workflowDef.actions || workflowDef.nodes || []);
|
|
40
|
-
const nodesToRemove = new Set();
|
|
41
|
-
for (const issue of orphanIssues) {
|
|
42
|
-
const nodeName = issue.node;
|
|
43
|
-
if (!nodeName)
|
|
44
|
-
continue;
|
|
45
|
-
if (protectedNodes.has(nodeName)) {
|
|
46
|
-
fixes.push({
|
|
47
|
-
applied: false,
|
|
48
|
-
issueType: 'orphan',
|
|
49
|
-
nodeName,
|
|
50
|
-
description: `Skipped - node used by another fix`,
|
|
51
|
-
});
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
nodesToRemove.add(nodeName);
|
|
55
|
-
fixes.push({
|
|
56
|
-
applied: true,
|
|
57
|
-
issueType: 'orphan',
|
|
58
|
-
nodeName,
|
|
59
|
-
description: `Removed orphan node "${nodeName}"`,
|
|
60
|
-
change: 'removed',
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
const filteredActions = actions.filter((a) => {
|
|
64
|
-
const action = a;
|
|
65
|
-
return !nodesToRemove.has(action.name || '');
|
|
66
|
-
});
|
|
67
|
-
const key = workflowDef.actions ? 'actions' : 'nodes';
|
|
68
|
-
return {
|
|
69
|
-
def: { ...workflowDef, [key]: filteredActions },
|
|
70
|
-
fixes,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Generate a unique node ID with random suffix
|
|
75
|
-
*/
|
|
76
|
-
function generateNodeId(prefix) {
|
|
77
|
-
const suffix = Math.random().toString(36).substring(2, 8);
|
|
78
|
-
return `${prefix}_${suffix}`;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Create a convert_to_text action node
|
|
82
|
-
* This bridges EXTRACTION_COLUMN → TEXT_WITH_SOURCES
|
|
83
|
-
*/
|
|
84
|
-
function createConvertToTextNode(nodeId, sourceActionName, sourceOutput, displayName) {
|
|
85
|
-
return {
|
|
86
|
-
name: nodeId,
|
|
87
|
-
action: {
|
|
88
|
-
name: { namespaces: ['actions', 'emainternal'], name: 'convert_to_text' },
|
|
89
|
-
version: 'v0',
|
|
90
|
-
},
|
|
91
|
-
inputs: {
|
|
92
|
-
named_inputs: {
|
|
93
|
-
inline: {
|
|
94
|
-
array: {
|
|
95
|
-
values: [{
|
|
96
|
-
named: {
|
|
97
|
-
name: 'email_address',
|
|
98
|
-
value: {
|
|
99
|
-
actionOutput: {
|
|
100
|
-
actionName: sourceActionName,
|
|
101
|
-
output: sourceOutput,
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
description: 'Email address from entity extraction',
|
|
105
|
-
},
|
|
106
|
-
}],
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
displaySettings: {
|
|
112
|
-
displayName,
|
|
113
|
-
description: 'Convert extraction output to text for email',
|
|
114
|
-
coordinates: { x: 600, y: 300 },
|
|
115
|
-
showConfig: 0,
|
|
116
|
-
},
|
|
117
|
-
typeArguments: {},
|
|
118
|
-
tools: [],
|
|
119
|
-
disableHumanInteraction: true,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Find email column name from entity_extraction node configuration
|
|
124
|
-
*/
|
|
125
|
-
function findEmailColumnName(extractionNode) {
|
|
126
|
-
const inputs = extractionNode.inputs;
|
|
127
|
-
if (!inputs)
|
|
128
|
-
return undefined;
|
|
129
|
-
const extractionColumnsInput = inputs.extraction_columns;
|
|
130
|
-
const columns = extractionColumnsInput?.inline?.array?.values || [];
|
|
131
|
-
for (const col of columns) {
|
|
132
|
-
const name = col.wellKnown?.extractionColumn?.name?.toLowerCase() || '';
|
|
133
|
-
if (name.includes('email') || name.includes('mail')) {
|
|
134
|
-
return col.wellKnown?.extractionColumn?.name;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return undefined;
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Fix wrong email recipient sources by inserting convert_to_text nodes
|
|
21
|
+
* DEPRECATED: Auto-fix workflow issues.
|
|
22
|
+
*
|
|
23
|
+
* This function now returns the workflow unchanged with a deprecation warning.
|
|
24
|
+
* Use LLM analysis with ema://rules/* instead.
|
|
141
25
|
*/
|
|
142
|
-
function
|
|
143
|
-
const fixes = [];
|
|
144
|
-
const usedNodes = new Set();
|
|
145
|
-
const wrongInputIssues = issues.filter(i => i.type === 'wrong_input_source');
|
|
146
|
-
if (wrongInputIssues.length === 0)
|
|
147
|
-
return { def: workflowDef, fixes, usedNodes };
|
|
148
|
-
const actionsSource = workflowDef.actions || workflowDef.nodes || [];
|
|
149
|
-
const actions = (Array.isArray(actionsSource) ? [...actionsSource] : []);
|
|
150
|
-
const newNodes = [];
|
|
151
|
-
// Find entity extraction nodes
|
|
152
|
-
const entityExtractionNodes = actions.filter((a) => {
|
|
153
|
-
const action = a;
|
|
154
|
-
return action.action?.name?.name === 'entity_extraction' ||
|
|
155
|
-
action.action?.name?.name === 'entity_extraction_with_documents';
|
|
156
|
-
});
|
|
157
|
-
const entityExtractionNodeNames = entityExtractionNodes
|
|
158
|
-
.map((a) => a.name)
|
|
159
|
-
.filter(Boolean);
|
|
160
|
-
if (entityExtractionNodeNames.length === 0) {
|
|
161
|
-
fixes.push({
|
|
162
|
-
applied: false,
|
|
163
|
-
issueType: 'wrong_input_source',
|
|
164
|
-
nodeName: 'N/A',
|
|
165
|
-
description: `No entity_extraction node found to rewire email to`,
|
|
166
|
-
});
|
|
167
|
-
return { def: workflowDef, fixes, usedNodes };
|
|
168
|
-
}
|
|
169
|
-
// Track which convert_to_text nodes we've created (to reuse for same source)
|
|
170
|
-
const converterNodes = new Map(); // "source.output" -> converterId
|
|
171
|
-
const updatedActions = actions.map((a) => {
|
|
172
|
-
const action = a;
|
|
173
|
-
const nodeIssue = wrongInputIssues.find(i => i.node === action.name);
|
|
174
|
-
if (!nodeIssue)
|
|
175
|
-
return a;
|
|
176
|
-
const isEmailNode = ['send_email_agent', 'send_communications_handler'].includes(action.action?.name?.name || '');
|
|
177
|
-
if (!isEmailNode)
|
|
178
|
-
return a;
|
|
179
|
-
const inputs = { ...action.inputs };
|
|
180
|
-
let anyFixed = false;
|
|
181
|
-
for (const [inputName, binding] of Object.entries(inputs)) {
|
|
182
|
-
if (!['to_email', 'email_to', 'recipient'].includes(inputName.toLowerCase()))
|
|
183
|
-
continue;
|
|
184
|
-
const b = binding;
|
|
185
|
-
if (!b.actionOutput?.actionName)
|
|
186
|
-
continue;
|
|
187
|
-
const targetExtractionName = entityExtractionNodeNames[0];
|
|
188
|
-
const targetExtractionNode = entityExtractionNodes.find((n) => n.name === targetExtractionName);
|
|
189
|
-
// Find the email column name from the extraction node config
|
|
190
|
-
const emailColumnName = findEmailColumnName(targetExtractionNode) || 'Email';
|
|
191
|
-
// Create or reuse a convert_to_text node
|
|
192
|
-
const converterKey = `${targetExtractionName}.${emailColumnName}`;
|
|
193
|
-
let converterId = converterNodes.get(converterKey);
|
|
194
|
-
if (!converterId) {
|
|
195
|
-
converterId = generateNodeId('convert_email');
|
|
196
|
-
converterNodes.set(converterKey, converterId);
|
|
197
|
-
// Create the new convert_to_text node
|
|
198
|
-
const converterNode = createConvertToTextNode(converterId, targetExtractionName, emailColumnName, `Convert ${emailColumnName} to Text`);
|
|
199
|
-
newNodes.push(converterNode);
|
|
200
|
-
}
|
|
201
|
-
// Rewire the email input to use the converter's output
|
|
202
|
-
inputs[inputName] = {
|
|
203
|
-
actionOutput: {
|
|
204
|
-
actionName: converterId,
|
|
205
|
-
output: 'text_with_sources',
|
|
206
|
-
},
|
|
207
|
-
};
|
|
208
|
-
usedNodes.add(targetExtractionName);
|
|
209
|
-
anyFixed = true;
|
|
210
|
-
fixes.push({
|
|
211
|
-
applied: true,
|
|
212
|
-
issueType: 'wrong_input_source',
|
|
213
|
-
nodeName: action.name || '',
|
|
214
|
-
description: `Inserted convert_to_text node and rewired ${inputName}`,
|
|
215
|
-
change: `${targetExtractionName}.${emailColumnName} → ${converterId} → ${action.name}.${inputName}`,
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
if (!anyFixed && Object.keys(inputs).length > 0) {
|
|
219
|
-
// Only report if we couldn't find any email inputs to fix
|
|
220
|
-
const emailInputs = Object.keys(inputs).filter(k => ['to_email', 'email_to', 'recipient'].includes(k.toLowerCase()));
|
|
221
|
-
if (emailInputs.length === 0) {
|
|
222
|
-
fixes.push({
|
|
223
|
-
applied: false,
|
|
224
|
-
issueType: 'wrong_input_source',
|
|
225
|
-
nodeName: action.name || '',
|
|
226
|
-
description: `No standard email input field found to rewire`,
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
return { ...action, inputs };
|
|
231
|
-
});
|
|
232
|
-
// Add the new converter nodes to the workflow
|
|
233
|
-
const allActions = [...updatedActions, ...newNodes];
|
|
234
|
-
const key = workflowDef.actions ? 'actions' : 'nodes';
|
|
26
|
+
export function autoFixWorkflow(workflowDef) {
|
|
235
27
|
return {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
28
|
+
workflowDef: workflowDef,
|
|
29
|
+
fixesApplied: [],
|
|
30
|
+
success: true,
|
|
31
|
+
warnings: [
|
|
32
|
+
"DEPRECATED: autoFixWorkflow() no longer auto-fixes issues.",
|
|
33
|
+
"Use LLM analysis with ema://rules/anti-patterns instead.",
|
|
34
|
+
],
|
|
239
35
|
};
|
|
240
36
|
}
|
|
241
37
|
/**
|
|
242
|
-
*
|
|
243
|
-
|
|
244
|
-
function
|
|
245
|
-
|
|
246
|
-
const fallbackIssues = issues.filter(i => i.type === 'missing_fallback');
|
|
247
|
-
if (fallbackIssues.length === 0)
|
|
248
|
-
return { def: workflowDef, fixes };
|
|
249
|
-
const actions = (workflowDef.actions || workflowDef.nodes || []);
|
|
250
|
-
for (const issue of fallbackIssues) {
|
|
251
|
-
const categorizer = actions.find((a) => a.name === issue.node);
|
|
252
|
-
if (!categorizer?.action?.typeArguments?.categories)
|
|
253
|
-
continue;
|
|
254
|
-
const categories = categorizer.action.typeArguments.categories;
|
|
255
|
-
if (categories.some(c => c.toLowerCase() === 'fallback' || c.toLowerCase() === 'other')) {
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
categorizer.action.typeArguments.categories = [...categories, 'Fallback'];
|
|
259
|
-
fixes.push({
|
|
260
|
-
applied: true,
|
|
261
|
-
issueType: 'missing_fallback',
|
|
262
|
-
nodeName: issue.node || '',
|
|
263
|
-
description: `Added "Fallback" category`,
|
|
264
|
-
change: `categories += "Fallback"`,
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
return { def: workflowDef, fixes };
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* NEW: Fix ungated responders by adding runIf conditions
|
|
38
|
+
* DEPRECATED: Suggest fixes for workflow issues.
|
|
39
|
+
*
|
|
40
|
+
* This function now returns empty results with a deprecation warning.
|
|
41
|
+
* Use LLM analysis with ema://rules/* instead.
|
|
271
42
|
*/
|
|
272
|
-
function
|
|
273
|
-
const fixes = [];
|
|
274
|
-
const ungatedIssues = multiResponderIssues.filter(i => i.type === 'ungated_responder');
|
|
275
|
-
if (ungatedIssues.length === 0)
|
|
276
|
-
return { def: workflowDef, fixes };
|
|
277
|
-
const actions = (workflowDef.actions || workflowDef.nodes || []);
|
|
278
|
-
// Find categorizer to use for gating
|
|
279
|
-
const categorizer = actions.find((a) => {
|
|
280
|
-
const action = a;
|
|
281
|
-
return action.action?.name?.name === 'chat_categorizer';
|
|
282
|
-
});
|
|
283
|
-
if (!categorizer) {
|
|
284
|
-
fixes.push({
|
|
285
|
-
applied: false,
|
|
286
|
-
issueType: 'ungated_responder',
|
|
287
|
-
nodeName: 'N/A',
|
|
288
|
-
description: `No categorizer found to add gating condition from`,
|
|
289
|
-
});
|
|
290
|
-
return { def: workflowDef, fixes };
|
|
291
|
-
}
|
|
292
|
-
// For now, just report what SHOULD be done
|
|
293
|
-
for (const issue of ungatedIssues) {
|
|
294
|
-
for (const nodeId of issue.nodes) {
|
|
295
|
-
fixes.push({
|
|
296
|
-
applied: false, // Manual fix recommended
|
|
297
|
-
issueType: 'ungated_responder',
|
|
298
|
-
nodeName: nodeId,
|
|
299
|
-
description: `Should add runIf condition from "${categorizer.name}"`,
|
|
300
|
-
change: `Add: runIf: { enum: { enumType: "category", enumValue: "<specific_category>" } }`,
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
return { def: workflowDef, fixes };
|
|
305
|
-
}
|
|
306
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
307
|
-
// MAIN AUTO-FIX FUNCTION
|
|
308
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
309
|
-
export function autoFixWorkflow(workflowDef) {
|
|
310
|
-
const def = workflowDef;
|
|
311
|
-
const originalIssues = detectWorkflowIssues(workflowDef);
|
|
312
|
-
const multiResponderIssues = detectMultipleResponders(workflowDef);
|
|
313
|
-
const originalIssueCount = originalIssues.length + multiResponderIssues.length;
|
|
314
|
-
const allFixes = [];
|
|
315
|
-
const warnings = [];
|
|
316
|
-
let currentDef = { ...def };
|
|
317
|
-
// 1. Fix wrong input sources first (protects nodes)
|
|
318
|
-
const inputSourceResult = fixWrongInputSource(currentDef, originalIssues);
|
|
319
|
-
currentDef = inputSourceResult.def;
|
|
320
|
-
allFixes.push(...inputSourceResult.fixes);
|
|
321
|
-
const protectedNodes = inputSourceResult.usedNodes;
|
|
322
|
-
// 2. Fix missing fallback
|
|
323
|
-
const fallbackResult = fixMissingFallback(currentDef, originalIssues);
|
|
324
|
-
currentDef = fallbackResult.def;
|
|
325
|
-
allFixes.push(...fallbackResult.fixes);
|
|
326
|
-
// 3. Fix orphan nodes (respecting protected)
|
|
327
|
-
const orphanResult = fixOrphanNodes(currentDef, originalIssues, protectedNodes);
|
|
328
|
-
currentDef = orphanResult.def;
|
|
329
|
-
allFixes.push(...orphanResult.fixes);
|
|
330
|
-
// 4. NEW: Report ungated responder fixes needed
|
|
331
|
-
const ungatedResult = fixUngatedResponders(currentDef, multiResponderIssues);
|
|
332
|
-
const multipleResponderFixes = ungatedResult.fixes;
|
|
333
|
-
// Re-detect remaining issues
|
|
334
|
-
const remainingIssues = detectWorkflowIssues(currentDef);
|
|
335
|
-
const remainingMultiResponder = detectMultipleResponders(currentDef);
|
|
336
|
-
const remainingIssueCount = remainingIssues.length + remainingMultiResponder.length;
|
|
337
|
-
const appliedCount = allFixes.filter(f => f.applied).length;
|
|
338
|
-
if (multipleResponderFixes.length > 0) {
|
|
339
|
-
warnings.push(`${multipleResponderFixes.length} ungated responder(s) need manual gating to prevent duplicate responses`);
|
|
340
|
-
}
|
|
43
|
+
export function suggestFixes(_workflowDef) {
|
|
341
44
|
return {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
remainingIssueCount,
|
|
345
|
-
workflowDef: currentDef,
|
|
346
|
-
success: appliedCount > 0,
|
|
347
|
-
warnings,
|
|
348
|
-
multipleResponderFixes,
|
|
45
|
+
standardFixes: [],
|
|
46
|
+
multiResponderFixes: [],
|
|
349
47
|
};
|
|
350
48
|
}
|
|
351
|
-
/**
|
|
352
|
-
* Suggest fixes without applying (dry run)
|
|
353
|
-
*/
|
|
354
|
-
export function suggestFixes(workflowDef) {
|
|
355
|
-
const issues = detectWorkflowIssues(workflowDef);
|
|
356
|
-
const multiResponderIssues = detectMultipleResponders(workflowDef);
|
|
357
|
-
const standardFixes = [];
|
|
358
|
-
const multiResponderFixes = [];
|
|
359
|
-
for (const issue of issues) {
|
|
360
|
-
if (!issue.auto_fixable)
|
|
361
|
-
continue;
|
|
362
|
-
standardFixes.push({
|
|
363
|
-
applied: false,
|
|
364
|
-
issueType: issue.type,
|
|
365
|
-
nodeName: issue.node || 'N/A',
|
|
366
|
-
description: issue.reason || `Fix ${issue.type}`,
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
for (const issue of multiResponderIssues) {
|
|
370
|
-
multiResponderFixes.push({
|
|
371
|
-
applied: false,
|
|
372
|
-
issueType: issue.type,
|
|
373
|
-
nodeName: issue.nodes.join(', '),
|
|
374
|
-
description: issue.description,
|
|
375
|
-
change: issue.fixSuggestion,
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
return { standardFixes, multiResponderFixes };
|
|
379
|
-
}
|
|
@@ -445,9 +445,14 @@ const ACTION_CHAIN_PATTERNS = [
|
|
|
445
445
|
},
|
|
446
446
|
];
|
|
447
447
|
/**
|
|
448
|
-
*
|
|
448
|
+
* Intent routing type compatibility - determines how outputs wire to inputs during intent processing.
|
|
449
|
+
*
|
|
450
|
+
* NOTE: This is SEPARATE from knowledge.ts TYPE_COMPATIBILITY which is user-facing documentation.
|
|
451
|
+
* This focuses on the "can_connect_to" and "use_named_inputs_for" routing logic.
|
|
452
|
+
*
|
|
453
|
+
* Canonical type documentation: src/sdk/knowledge.ts → TYPE_COMPATIBILITY
|
|
449
454
|
*/
|
|
450
|
-
export const
|
|
455
|
+
export const INTENT_TYPE_ROUTING = {
|
|
451
456
|
WELL_KNOWN_TYPE_CHAT_CONVERSATION: {
|
|
452
457
|
can_connect_to: ["conversation"],
|
|
453
458
|
use_named_inputs_for: [],
|
|
@@ -1312,6 +1317,37 @@ export function summarizeIntentConfidence(confidence) {
|
|
|
1312
1317
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1313
1318
|
// Intent to Spec Conversion
|
|
1314
1319
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1320
|
+
/**
|
|
1321
|
+
* Generate takeActionInstructions from tools for Voice AI.
|
|
1322
|
+
*
|
|
1323
|
+
* This populates the "Define what actions your AI Employee can perform" section
|
|
1324
|
+
* in the Voice persona config. Format follows the Ema Voice API spec.
|
|
1325
|
+
*/
|
|
1326
|
+
function generateTakeActionInstructions(tools) {
|
|
1327
|
+
if (!tools || tools.length === 0) {
|
|
1328
|
+
return "";
|
|
1329
|
+
}
|
|
1330
|
+
const cases = tools.map((tool, index) => {
|
|
1331
|
+
const caseNum = index + 1;
|
|
1332
|
+
const toolName = tool.display_name ?? tool.action ?? `Action ${caseNum}`;
|
|
1333
|
+
const triggerCondition = tool.trigger_condition ?? tool.description ?? "Perform this action when requested";
|
|
1334
|
+
// Extract required parameters from tool schema if available
|
|
1335
|
+
const requiredParams = tool.required_inputs ?? [];
|
|
1336
|
+
const paramsObj = requiredParams.length > 0
|
|
1337
|
+
? `{ ${requiredParams.map(p => `"${p}": ""`).join(", ")} }`
|
|
1338
|
+
: "{ }";
|
|
1339
|
+
return `</Case ${caseNum}>
|
|
1340
|
+
${toolName}
|
|
1341
|
+
|
|
1342
|
+
Trigger When: ${triggerCondition}
|
|
1343
|
+
|
|
1344
|
+
Intent for tool call: "User requests ${toolName.toLowerCase()}"
|
|
1345
|
+
|
|
1346
|
+
Required parameters: ${paramsObj}
|
|
1347
|
+
</Case ${caseNum}>`;
|
|
1348
|
+
});
|
|
1349
|
+
return cases.join("\n\n");
|
|
1350
|
+
}
|
|
1315
1351
|
export function intentToSpec(intent) {
|
|
1316
1352
|
const nodes = [];
|
|
1317
1353
|
const resultMappings = [];
|
|
@@ -1463,7 +1499,11 @@ export function intentToSpec(intent) {
|
|
|
1463
1499
|
welcomeMessage: intent.voice_config?.welcome_message,
|
|
1464
1500
|
identityAndPurpose: intent.voice_config?.identity ?? intent.description,
|
|
1465
1501
|
hangupInstructions: intent.voice_config?.hangup_instructions,
|
|
1466
|
-
|
|
1502
|
+
transferInstructions: intent.voice_config?.transfer_instructions,
|
|
1503
|
+
waitMessage: intent.voice_config?.wait_message,
|
|
1504
|
+
// Generate takeActionInstructions from tools if not explicitly provided
|
|
1505
|
+
takeActionInstructions: intent.voice_config?.take_action_instructions ??
|
|
1506
|
+
generateTakeActionInstructions(intent.tools),
|
|
1467
1507
|
} : undefined;
|
|
1468
1508
|
return {
|
|
1469
1509
|
name: intent.name,
|
|
@@ -93,6 +93,41 @@ persona(id="persona-uuid", data={method:"list"})
|
|
|
93
93
|
persona(id="persona-uuid", data={method:"schema"})
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
### Upload Dashboard Rows with Files
|
|
97
|
+
|
|
98
|
+
Create new dashboard rows with file attachments (triggers workflow execution):
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
persona(id="dashboard-uuid", data={
|
|
102
|
+
method: "upload",
|
|
103
|
+
items: [
|
|
104
|
+
{
|
|
105
|
+
"Input Document": { file: "/path/to/invoice.pdf" },
|
|
106
|
+
"Customer Name": "Acme Corp",
|
|
107
|
+
"Amount": 1500.00,
|
|
108
|
+
"Priority": true
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Supported value types in `items`:**
|
|
115
|
+
|
|
116
|
+
| Type | Format | Result |
|
|
117
|
+
|------|--------|--------|
|
|
118
|
+
| String | `"value"` | `string_value` |
|
|
119
|
+
| Number | `123.45` | `number_value` |
|
|
120
|
+
| Boolean | `true` | `boolean_value` |
|
|
121
|
+
| File | `{ file: "/path/to/doc.pdf" }` | `document_value` (auto base64) |
|
|
122
|
+
| Inline Doc | `{ contents: "...", mime_type: "text/plain" }` | `document_value` |
|
|
123
|
+
|
|
124
|
+
**Key differences from knowledge base upload:**
|
|
125
|
+
|
|
126
|
+
| Operation | Target | Use Case |
|
|
127
|
+
|-----------|--------|----------|
|
|
128
|
+
| `data={method:"upload", path:"..."}` | Knowledge Base | Chat personas, RAG search |
|
|
129
|
+
| `data={method:"upload", items:[...]}` | Dashboard Rows | Dashboard personas, per-row workflow |
|
|
130
|
+
|
|
96
131
|
### Legacy Syntax (still works)
|
|
97
132
|
|
|
98
133
|
```
|
package/docs/ema-user-guide.md
CHANGED
|
@@ -1003,6 +1003,72 @@ tools:
|
|
|
1003
1003
|
|
|
1004
1004
|
---
|
|
1005
1005
|
|
|
1006
|
+
## Workflow Analysis (MCP Users)
|
|
1007
|
+
|
|
1008
|
+
When using the MCP toolkit to analyze workflows, the analysis is **LLM-driven** using rule resources.
|
|
1009
|
+
|
|
1010
|
+
### Analysis Pattern
|
|
1011
|
+
|
|
1012
|
+
```
|
|
1013
|
+
1. Get workflow data: workflow(mode="get", persona_id="...")
|
|
1014
|
+
2. Fetch analysis rules: ema://rules/anti-patterns
|
|
1015
|
+
ema://rules/structural-invariants
|
|
1016
|
+
3. LLM checks workflow against rules
|
|
1017
|
+
4. LLM reports issues (MCP does NOT pre-compute)
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
### Key Resources for Analysis
|
|
1021
|
+
|
|
1022
|
+
| Resource | Purpose |
|
|
1023
|
+
|----------|---------|
|
|
1024
|
+
| `ema://rules/anti-patterns` | Common workflow mistakes (missing fallback, stateless response, etc.) |
|
|
1025
|
+
| `ema://rules/structural-invariants` | Hard constraints (no cycles, reachability, WORKFLOW_OUTPUT) |
|
|
1026
|
+
| `ema://rules/input-sources` | Which inputs accept which data types |
|
|
1027
|
+
| `ema://rules/optimizations` | Performance improvements |
|
|
1028
|
+
|
|
1029
|
+
### Voice AI: Automatic Action Instructions
|
|
1030
|
+
|
|
1031
|
+
When creating Voice AI personas with tools, `takeActionInstructions` is auto-generated:
|
|
1032
|
+
|
|
1033
|
+
```
|
|
1034
|
+
// Tools defined in workflow → Auto-generates:
|
|
1035
|
+
</Case 1>
|
|
1036
|
+
Create Ticket
|
|
1037
|
+
|
|
1038
|
+
Trigger When: User reports an issue
|
|
1039
|
+
|
|
1040
|
+
Required parameters: { "title": "", "description": "" }
|
|
1041
|
+
</Case 1>
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
### Dashboard: File Attachments in Rows
|
|
1045
|
+
|
|
1046
|
+
Dashboard rows can include file attachments via MCP:
|
|
1047
|
+
|
|
1048
|
+
```javascript
|
|
1049
|
+
persona(id="dashboard-id", data={
|
|
1050
|
+
method: "upload",
|
|
1051
|
+
items: [{
|
|
1052
|
+
"Input Document": { file: "/path/to/invoice.pdf" },
|
|
1053
|
+
"Customer Name": "Acme Corp"
|
|
1054
|
+
}]
|
|
1055
|
+
})
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
### Critical: Data Sources Before Search
|
|
1059
|
+
|
|
1060
|
+
Workflows with `search` nodes require documents to be uploaded first:
|
|
1061
|
+
|
|
1062
|
+
```
|
|
1063
|
+
1. Upload: persona(id="...", data={method:"upload", path:"/docs/faq.pdf"})
|
|
1064
|
+
2. Verify: persona(id="...", data={method:"stats"}) // success > 0
|
|
1065
|
+
3. Deploy: workflow(mode="deploy", ...)
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
Without documents, search returns empty results.
|
|
1069
|
+
|
|
1070
|
+
---
|
|
1071
|
+
|
|
1006
1072
|
## Troubleshooting & Gotchas
|
|
1007
1073
|
|
|
1008
1074
|
### Common Mistakes
|
package/docs/mcp-tools-guide.md
CHANGED
|
@@ -380,6 +380,20 @@ data(persona_id="abc", mode="upload", file="/path/to/file.pdf")
|
|
|
380
380
|
data(persona_id="abc", mode="delete", file_id="file-123")
|
|
381
381
|
```
|
|
382
382
|
|
|
383
|
+
**Upload to specific widget (Document Generation personas):**
|
|
384
|
+
```typescript
|
|
385
|
+
// Document Proposal Manager widget names (verified from template):
|
|
386
|
+
data(persona_id="abc", mode="upload", file="/path/to/company.pdf", widget_name="upload") // Content Repository
|
|
387
|
+
data(persona_id="abc", mode="upload", file="/path/to/service-line.pdf", widget_name="upload1") // Service Line Documents
|
|
388
|
+
data(persona_id="abc", mode="upload", file="/path/to/style-guide.pdf", widget_name="upload2") // Style Guide
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
> **Important for Document Generation (Proposal Writer) personas:** Files must be uploaded to the correct widget to appear in the right repository in the UI. Without `widget_name`, files go to the default `fileUpload` widget which may not be visible in the Configuration tab.
|
|
392
|
+
>
|
|
393
|
+
> **Document Proposal Manager widgets:** `upload` (Content Repository), `upload1` (Service Line Documents), `upload2` (Style Guide)
|
|
394
|
+
>
|
|
395
|
+
> **For other templates:** Call `persona(id="abc", include_workflow=true)` and inspect `proto_config.widgets[].name` to find the exact widget names.
|
|
396
|
+
|
|
383
397
|
**Generate content (via Document Generation API):**
|
|
384
398
|
```typescript
|
|
385
399
|
// From a template
|
|
@@ -445,22 +459,45 @@ Start with:
|
|
|
445
459
|
**MCP provides data and schema. LLM generates workflow_def.**
|
|
446
460
|
|
|
447
461
|
```typescript
|
|
448
|
-
// 1. Get current workflow + schema
|
|
462
|
+
// 1. Get current workflow + schema (includes deprecation_warnings!)
|
|
449
463
|
result = workflow(mode="get", persona_id="abc")
|
|
450
|
-
// Returns: workflow_def, generation_schema, example_workflow, requirements
|
|
464
|
+
// Returns: workflow_def, generation_schema, example_workflow, requirements, deprecation_warnings
|
|
451
465
|
|
|
452
466
|
// 2. LLM analyzes and generates new workflow_def
|
|
453
467
|
// (LLM uses schema and examples to build valid workflow)
|
|
468
|
+
// ⚠️ FIX any deprecated actions FIRST - check deprecation_warnings!
|
|
454
469
|
|
|
455
|
-
// 3. Deploy LLM's result
|
|
470
|
+
// 3. Deploy LLM's result (preview first!)
|
|
471
|
+
workflow(mode="deploy", persona_id="abc", workflow_def={...}, preview=true)
|
|
456
472
|
workflow(mode="deploy", persona_id="abc", workflow_def={...})
|
|
457
473
|
```
|
|
458
474
|
|
|
475
|
+
### ⚠️ CRITICAL: Check Deprecated Actions First
|
|
476
|
+
|
|
477
|
+
**BEFORE generating any workflow:**
|
|
478
|
+
|
|
479
|
+
1. Check `deprecation_warnings` in `workflow(mode="get")` response
|
|
480
|
+
2. Use `reference(type="actions")` to verify current versions
|
|
481
|
+
3. **NEVER** copy workflow patterns from existing personas (they may use deprecated actions!)
|
|
482
|
+
|
|
483
|
+
**Correct workflow pattern:**
|
|
484
|
+
```
|
|
485
|
+
chat_trigger → search/v2 → respond_for_external_actions → WORKFLOW_OUTPUT
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
**Deprecated (DO NOT USE):**
|
|
489
|
+
```
|
|
490
|
+
search/v0 → Use search/v2
|
|
491
|
+
respond_with_sources → Use respond_for_external_actions
|
|
492
|
+
call_llm/v0 → Use call_llm/v2
|
|
493
|
+
```
|
|
494
|
+
|
|
459
495
|
**Common issues to check when generating:**
|
|
460
496
|
- Missing `WORKFLOW_OUTPUT` in results
|
|
461
497
|
- Missing Fallback category in categorizers
|
|
462
498
|
- Orphan nodes not connected to output
|
|
463
499
|
- Type mismatches between connected nodes
|
|
500
|
+
- **Using deprecated actions** (check deprecation_warnings!)
|
|
464
501
|
|
|
465
502
|
## HITL (Human-in-the-Loop) Policy
|
|
466
503
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ema.co/mcp-toolkit",
|
|
3
|
-
"version": "2026.1.
|
|
3
|
+
"version": "2026.1.27",
|
|
4
4
|
"description": "Ema AI Employee toolkit - MCP server, CLI, and SDK for managing AI Employees across environments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -58,7 +58,6 @@
|
|
|
58
58
|
"generate:api-client": "npx @hey-api/openapi-ts -i src/sdk/generated/openapi.json -o src/sdk/generated/api-client -c @hey-api/client-fetch",
|
|
59
59
|
"generate:protos": "buf generate ../ema-repos/protos --template buf.gen.yaml",
|
|
60
60
|
"generate:proto-fields": "tsx scripts/generate-proto-fields.ts",
|
|
61
|
-
"generate:action-schema": "tsx scripts/generate-action-schema.ts",
|
|
62
61
|
"generate:deprecated-actions": "tsx scripts/generate-deprecated-actions.ts",
|
|
63
62
|
"generate:skill": "tsx scripts/generate-skill.ts",
|
|
64
63
|
"generate:skill:check": "tsx scripts/generate-skill.ts --check",
|