@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.

@@ -1,379 +1,48 @@
1
1
  /**
2
2
  * Workflow Fixer (SDK)
3
3
  *
4
- * Self-contained auto-detection and fixing of workflow issues.
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
- * Automatically fixes:
8
- * - Orphan node removal
9
- * - Email recipient rewiring to entity_extraction
10
- * - Missing fallback addition
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
- * NOTE: This is DIFFERENT from server.ts applyWorkflowFixes():
11
+ * And then make structured modifications via:
12
+ * - workflow(mode="deploy", workflow_def={...})
13
+ * - persona(mode="update", operations=[...])
15
14
  *
16
- * - autoFixWorkflow() (HERE): Self-contained, auto-detects issues, no persona
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
- // FIX FUNCTIONS
18
+ // DEPRECATED FUNCTIONS (no-ops)
29
19
  // ═══════════════════════════════════════════════════════════════════════════
30
20
  /**
31
- * Remove orphan nodes (respecting protected nodes used by other fixes)
32
- */
33
- function fixOrphanNodes(workflowDef, issues, protectedNodes) {
34
- const fixes = [];
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 fixWrongInputSource(workflowDef, issues) {
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
- def: { ...workflowDef, [key]: allActions },
237
- fixes,
238
- usedNodes,
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
- * Add fallback category to categorizers missing one
243
- */
244
- function fixMissingFallback(workflowDef, issues) {
245
- const fixes = [];
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 fixUngatedResponders(workflowDef, multiResponderIssues) {
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
- originalIssueCount,
343
- fixesApplied: allFixes,
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
- * Type compatibility rules - what outputs can connect to what inputs
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 TYPE_COMPATIBILITY = {
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
- // Additional fields can be extracted from intent description in the future
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
  ```
@@ -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
@@ -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.26-4",
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",