@ema.co/mcp-toolkit 2026.2.23-2 → 2026.2.24
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/demo-generator.js +177 -0
- package/dist/mcp/domain/validation-rules.js +13 -0
- package/dist/mcp/handlers/data/index.js +74 -15
- package/dist/mcp/handlers/reference/index.js +34 -11
- package/dist/mcp/knowledge.js +174 -13
- package/dist/mcp/resources-dynamic.js +5 -0
- package/dist/mcp/tools.js +1 -1
- package/package.json +1 -1
|
@@ -301,6 +301,134 @@ export const DEMO_SCENARIOS = {
|
|
|
301
301
|
},
|
|
302
302
|
],
|
|
303
303
|
},
|
|
304
|
+
"finance-dunning": {
|
|
305
|
+
id: "finance-dunning",
|
|
306
|
+
name: "Finance Automated Dunning",
|
|
307
|
+
description: "AR dunning assistant: balance inquiries, payment links, disputes, payment plans, and escalation",
|
|
308
|
+
persona_types: ["chat", "voice"],
|
|
309
|
+
tags: ["finance", "ar", "dunning", "collections", "receivables", "billing"],
|
|
310
|
+
intents: [
|
|
311
|
+
{
|
|
312
|
+
name: "balance_inquiry",
|
|
313
|
+
description: "What do I owe? Current balance and aging",
|
|
314
|
+
example_questions: ["What's my balance?", "Do I have any overdue invoices?", "What do I owe?"],
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: "payment_made",
|
|
318
|
+
description: "Customer says they already paid",
|
|
319
|
+
example_questions: ["I paid last week", "I already sent the wire", "Check your records"],
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: "dispute",
|
|
323
|
+
description: "Dispute invoice or amount",
|
|
324
|
+
example_questions: ["This invoice is wrong", "We never received that order", "I'm disputing this charge"],
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: "payment_plan",
|
|
328
|
+
description: "Request installments or extension",
|
|
329
|
+
example_questions: ["Can I pay in two installments?", "I need 30 more days", "Can we set up a payment plan?"],
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: "how_to_pay",
|
|
333
|
+
description: "How to pay or payment link",
|
|
334
|
+
example_questions: ["How do I pay?", "Where's the payment link?", "What payment methods do you accept?"],
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: "escalate",
|
|
338
|
+
description: "Request to speak to a person",
|
|
339
|
+
example_questions: ["I want to talk to someone", "Transfer me to collections", "I need to speak to AR"],
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: "Fallback",
|
|
343
|
+
description: "Anything else",
|
|
344
|
+
example_questions: ["What's the weather?", "Tell me about your company"],
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
entities: [
|
|
348
|
+
{
|
|
349
|
+
type: "customer",
|
|
350
|
+
count: 3,
|
|
351
|
+
template: {
|
|
352
|
+
id: "CUST-AR-{{id}}",
|
|
353
|
+
name: "{{name}}",
|
|
354
|
+
email: "{{email}}",
|
|
355
|
+
payment_terms_days: 30,
|
|
356
|
+
payment_plan_active: false,
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
type: "invoice",
|
|
361
|
+
count: 3,
|
|
362
|
+
template: {
|
|
363
|
+
invoice_id: "INV-{{id}}",
|
|
364
|
+
customer_id: "CUST-AR-{{cust}}",
|
|
365
|
+
amount_due: "{{amount}}",
|
|
366
|
+
due_date: "{{due_date}}",
|
|
367
|
+
days_past_due: "{{days}}",
|
|
368
|
+
dunning_level: "{{level}}",
|
|
369
|
+
payment_link: "{{payment_link}}",
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
type: "dunning_policy",
|
|
374
|
+
count: 1,
|
|
375
|
+
template: {
|
|
376
|
+
reminder_1_days: "1-7",
|
|
377
|
+
reminder_2_days: "8-14",
|
|
378
|
+
final_notice_days: "15-30",
|
|
379
|
+
escalation_days: "31+",
|
|
380
|
+
payment_plan_note: "Payment plan requests are reviewed by AR within 1 business day.",
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
qa_pairs: [
|
|
385
|
+
{
|
|
386
|
+
phase: "intro",
|
|
387
|
+
question: "Hi, I have a question about my invoice.",
|
|
388
|
+
answer_template: "Hi! I'm here to help with your invoice and payment questions. I can look up your balance, share the payment link, or help with a dispute or payment plan request. What would you like to do?",
|
|
389
|
+
intent: "balance_inquiry",
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
phase: "main",
|
|
393
|
+
question: "What do I owe?",
|
|
394
|
+
answer_template: "Your current balance is {{amount_due}} (invoice {{invoice_id}}, due {{due_date}}). You can pay here: {{payment_link}}. If you need a payment plan or have already paid, tell me and I'll help.",
|
|
395
|
+
intent: "balance_inquiry",
|
|
396
|
+
required_entity_type: "invoice",
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
phase: "main",
|
|
400
|
+
question: "How do I pay?",
|
|
401
|
+
answer_template: "You can pay online using this link: {{payment_link}}. We accept major credit cards and ACH. If you've already paid, please have your payment reference or date ready and I can help verify.",
|
|
402
|
+
intent: "how_to_pay",
|
|
403
|
+
required_entity_type: "invoice",
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
phase: "main",
|
|
407
|
+
question: "I already paid last week.",
|
|
408
|
+
answer_template: "Thanks for letting us know. Our AR team will verify the payment. Can you share the payment reference number or the date you paid? We'll follow up if anything doesn't match.",
|
|
409
|
+
intent: "payment_made",
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
phase: "advanced",
|
|
413
|
+
question: "This invoice is wrong.",
|
|
414
|
+
answer_template: "I'm sorry to hear that. I'm not able to adjust invoices here. I've escalated this to our AR team—they'll contact you to resolve the dispute. You should hear back within 1 business day.",
|
|
415
|
+
intent: "dispute",
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
phase: "advanced",
|
|
419
|
+
question: "Can I pay in two installments?",
|
|
420
|
+
answer_template: "Payment plan requests are reviewed by our AR team. I've submitted your request; someone will get back to you within 1 business day with options.",
|
|
421
|
+
intent: "payment_plan",
|
|
422
|
+
required_entity_type: "dunning_policy",
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
phase: "closing",
|
|
426
|
+
question: "I want to talk to someone.",
|
|
427
|
+
answer_template: "No problem. I've requested that our AR team contact you. You should hear from someone within 1 business day. Is there anything else I can help with in the meantime?",
|
|
428
|
+
intent: "escalate",
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
},
|
|
304
432
|
};
|
|
305
433
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
306
434
|
// Sample Entity Data
|
|
@@ -473,6 +601,55 @@ export const SAMPLE_ENTITIES = {
|
|
|
473
601
|
manager: "Bob Director",
|
|
474
602
|
},
|
|
475
603
|
],
|
|
604
|
+
invoice: [
|
|
605
|
+
{
|
|
606
|
+
id: "INV-2025-001234",
|
|
607
|
+
invoice_id: "INV-2025-001234",
|
|
608
|
+
customer_id: "CUST-AR-001",
|
|
609
|
+
amount_due: 12500.0,
|
|
610
|
+
currency: "USD",
|
|
611
|
+
due_date: "2025-01-20",
|
|
612
|
+
days_past_due: 34,
|
|
613
|
+
dunning_level: 3,
|
|
614
|
+
payment_link: "https://pay.example.com/INV-2025-001234",
|
|
615
|
+
line_items_summary: "Subscription Q1 2025",
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
id: "INV-2025-005678",
|
|
619
|
+
invoice_id: "INV-2025-005678",
|
|
620
|
+
customer_id: "CUST-AR-002",
|
|
621
|
+
amount_due: 4850.0,
|
|
622
|
+
currency: "USD",
|
|
623
|
+
due_date: "2025-02-01",
|
|
624
|
+
days_past_due: 22,
|
|
625
|
+
dunning_level: 3,
|
|
626
|
+
payment_link: "https://pay.example.com/INV-2025-005678",
|
|
627
|
+
line_items_summary: "Professional services February 2025",
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
id: "INV-2025-009999",
|
|
631
|
+
invoice_id: "INV-2025-009999",
|
|
632
|
+
customer_id: "CUST-AR-003",
|
|
633
|
+
amount_due: 899.0,
|
|
634
|
+
currency: "USD",
|
|
635
|
+
due_date: "2025-02-15",
|
|
636
|
+
days_past_due: 8,
|
|
637
|
+
dunning_level: 2,
|
|
638
|
+
payment_link: "https://pay.example.com/INV-2025-009999",
|
|
639
|
+
line_items_summary: "Monthly license March 2025",
|
|
640
|
+
},
|
|
641
|
+
],
|
|
642
|
+
dunning_policy: [
|
|
643
|
+
{
|
|
644
|
+
id: "dunning-policy",
|
|
645
|
+
name: "Dunning Policy",
|
|
646
|
+
reminder_1_days: "1-7",
|
|
647
|
+
reminder_2_days: "8-14",
|
|
648
|
+
final_notice_days: "15-30",
|
|
649
|
+
escalation_days: "31+",
|
|
650
|
+
payment_plan_note: "Payment plan requests are reviewed by AR within 1 business day. Do not send reminders to customers on active payment plans.",
|
|
651
|
+
},
|
|
652
|
+
],
|
|
476
653
|
benefit: [
|
|
477
654
|
{
|
|
478
655
|
id: "BEN-001",
|
|
@@ -68,6 +68,19 @@ export const INPUT_SOURCE_RULES = [
|
|
|
68
68
|
severity: "warning",
|
|
69
69
|
fix: "Use user_query for query, pass chat_conversation via named_inputs if needed",
|
|
70
70
|
},
|
|
71
|
+
// Rule validation inputs
|
|
72
|
+
{
|
|
73
|
+
actionPattern: "rule_validation_with_documents",
|
|
74
|
+
recommended: "entity_extraction.extraction_columns for map_of_extracted_columns, workflowInput.document for primary_docs",
|
|
75
|
+
avoid: ["omitting primary_docs", "wiring phases in parallel instead of sequentially"],
|
|
76
|
+
reason: "rule_validation_with_documents requires both extracted columns and original documents. " +
|
|
77
|
+
"primary_docs gives the validator context from the original document. " +
|
|
78
|
+
"When chaining multiple validation phases, wire them sequentially (phase1.ruleset_output → phase2.map_of_extracted_columns) " +
|
|
79
|
+
"so each phase builds on prior validation results.",
|
|
80
|
+
severity: "critical",
|
|
81
|
+
fix: "Wire entity_extraction.extraction_columns → rule_validation.map_of_extracted_columns AND " +
|
|
82
|
+
"workflowInput.document → rule_validation.primary_docs. For multi-phase: chain sequentially, not in parallel.",
|
|
83
|
+
},
|
|
71
84
|
// Email-specific rules - GUIDANCE ONLY (not hard backend constraints)
|
|
72
85
|
// NOTE: Backend accepts TEXT_WITH_SOURCES for email_to, so this is soft guidance
|
|
73
86
|
// The correct pattern is: entity_extraction → fixed_response with {{email}} template → send_email
|
|
@@ -191,8 +191,8 @@ export async function handleData(args, client, readFile) {
|
|
|
191
191
|
}
|
|
192
192
|
if (filePath) {
|
|
193
193
|
// File upload - use provided readFile or fall back to fs
|
|
194
|
+
let fileContent;
|
|
194
195
|
try {
|
|
195
|
-
let fileContent;
|
|
196
196
|
if (readFile) {
|
|
197
197
|
fileContent = await readFile(filePath);
|
|
198
198
|
}
|
|
@@ -200,25 +200,84 @@ export async function handleData(args, client, readFile) {
|
|
|
200
200
|
const fs = await import("fs/promises");
|
|
201
201
|
fileContent = await fs.readFile(filePath);
|
|
202
202
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
return { error: `Failed to read file: ${error instanceof Error ? error.message : String(error)}` };
|
|
206
|
+
}
|
|
207
|
+
const path = await import("path");
|
|
208
|
+
const filename = path.basename(filePath);
|
|
209
|
+
const effectiveWidgetName = widgetName ?? "fileUpload";
|
|
210
|
+
let result;
|
|
211
|
+
try {
|
|
212
|
+
result = await client.uploadDataSource(personaId, fileContent, filename, {
|
|
213
|
+
widgetName: effectiveWidgetName,
|
|
207
214
|
});
|
|
208
|
-
return {
|
|
209
|
-
method: "upload",
|
|
210
|
-
persona_id: personaId,
|
|
211
|
-
path: filePath,
|
|
212
|
-
widget_name: widgetName ?? "fileUpload",
|
|
213
|
-
...result,
|
|
214
|
-
_warning: "IMPORTANT: Uploaded documents will NOT be used unless your workflow has a search node (search/v2).",
|
|
215
|
-
_next_step: `Verify workflow has search: workflow(mode='get', persona_id='${personaId}') → check for search node. If missing, add one.`,
|
|
216
|
-
_validation: "Deploy will BLOCK if you have documents but no search node in your workflow.",
|
|
217
|
-
};
|
|
218
215
|
}
|
|
219
216
|
catch (error) {
|
|
220
217
|
return { error: `Failed to upload file: ${error instanceof Error ? error.message : String(error)}` };
|
|
221
218
|
}
|
|
219
|
+
// Auto-create the widget in proto_config if it doesn't exist yet.
|
|
220
|
+
// The upload API stores the file under the given widget_name/tags, but the
|
|
221
|
+
// widget entry in proto_config is what makes it searchable from a workflow.
|
|
222
|
+
// Flow from HAR: POST /api/v2/upload/files → update_persona adds widget entry.
|
|
223
|
+
const existingWidgets = (protoConfig?.widgets ?? []);
|
|
224
|
+
const widgetAlreadyExists = existingWidgets.some(w => w.name === effectiveWidgetName);
|
|
225
|
+
let widgetCreated = false;
|
|
226
|
+
if (!widgetAlreadyExists) {
|
|
227
|
+
const newWidget = {
|
|
228
|
+
name: effectiveWidgetName,
|
|
229
|
+
type: 3,
|
|
230
|
+
title: effectiveWidgetName,
|
|
231
|
+
editable: true,
|
|
232
|
+
fileUpload: {
|
|
233
|
+
localFiles: [],
|
|
234
|
+
tags: [effectiveWidgetName],
|
|
235
|
+
useChunking: true,
|
|
236
|
+
mergeFiles: [],
|
|
237
|
+
transforms: [],
|
|
238
|
+
fileTagMappings: [],
|
|
239
|
+
acceptedMimeTypes: [],
|
|
240
|
+
},
|
|
241
|
+
subtitle: "",
|
|
242
|
+
required: false,
|
|
243
|
+
subProjectType: 0,
|
|
244
|
+
};
|
|
245
|
+
const updatedProtoConfig = {
|
|
246
|
+
...(protoConfig ?? {}),
|
|
247
|
+
widgets: [...existingWidgets, newWidget],
|
|
248
|
+
};
|
|
249
|
+
try {
|
|
250
|
+
await client.updateAiEmployee({
|
|
251
|
+
persona_id: personaId,
|
|
252
|
+
proto_config: updatedProtoConfig,
|
|
253
|
+
workflow: persona?.workflow_def,
|
|
254
|
+
});
|
|
255
|
+
widgetCreated = true;
|
|
256
|
+
}
|
|
257
|
+
catch (widgetError) {
|
|
258
|
+
return {
|
|
259
|
+
method: "upload",
|
|
260
|
+
persona_id: personaId,
|
|
261
|
+
path: filePath,
|
|
262
|
+
uploaded: true,
|
|
263
|
+
widget_created: false,
|
|
264
|
+
...result,
|
|
265
|
+
error: `File uploaded successfully but widget registration failed: ${widgetError instanceof Error ? widgetError.message : String(widgetError)}`,
|
|
266
|
+
_tip: "The file is stored. Retry the upload with the same path — it will re-attempt widget creation.",
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
method: "upload",
|
|
272
|
+
persona_id: personaId,
|
|
273
|
+
path: filePath,
|
|
274
|
+
widget_name: effectiveWidgetName,
|
|
275
|
+
widget_created: widgetCreated,
|
|
276
|
+
...result,
|
|
277
|
+
_warning: "IMPORTANT: Uploaded documents will NOT be used unless your workflow has a search node (search/v2).",
|
|
278
|
+
_next_step: `Verify workflow has search: workflow(mode='get', persona_id='${personaId}') → check for search node wired to widget '${effectiveWidgetName}'. If missing, add one.`,
|
|
279
|
+
_validation: "Deploy will BLOCK if you have documents but no search node in your workflow.",
|
|
280
|
+
};
|
|
222
281
|
}
|
|
223
282
|
else if (items && items.length > 0) {
|
|
224
283
|
// Dashboard row upload (LLM-generated content or file attachments)
|
|
@@ -187,24 +187,23 @@ export async function handleReference(args, context) {
|
|
|
187
187
|
// type="patterns" - Workflow patterns
|
|
188
188
|
// ─────────────────────────────────────────────────────────────────────────
|
|
189
189
|
if (type === "patterns") {
|
|
190
|
-
// Get specific pattern
|
|
190
|
+
// Get specific pattern by name
|
|
191
191
|
if (args.pattern) {
|
|
192
|
-
const
|
|
193
|
-
const pattern = WORKFLOW_PATTERNS[patternName];
|
|
192
|
+
const pattern = WORKFLOW_PATTERNS.find(p => p.name === args.pattern);
|
|
194
193
|
if (!pattern) {
|
|
195
|
-
return { error: `Pattern not found: ${args.pattern}
|
|
194
|
+
return { error: `Pattern not found: ${args.pattern}`, available: WORKFLOW_PATTERNS.map(p => p.name) };
|
|
196
195
|
}
|
|
197
196
|
return pattern;
|
|
198
197
|
}
|
|
199
|
-
// List patterns
|
|
200
|
-
let patterns =
|
|
198
|
+
// List patterns (optionally filtered by persona_type)
|
|
199
|
+
let patterns = [...WORKFLOW_PATTERNS];
|
|
201
200
|
if (args.persona_type) {
|
|
202
|
-
patterns = patterns.filter(
|
|
201
|
+
patterns = patterns.filter(p => !p.personaType || p.personaType === args.persona_type);
|
|
203
202
|
}
|
|
204
203
|
return {
|
|
205
204
|
count: patterns.length,
|
|
206
|
-
patterns: patterns.map(
|
|
207
|
-
name,
|
|
205
|
+
patterns: patterns.map(p => ({
|
|
206
|
+
name: p.name,
|
|
208
207
|
description: p.description,
|
|
209
208
|
use_case: p.useCase,
|
|
210
209
|
persona_type: p.personaType,
|
|
@@ -321,8 +320,16 @@ export async function handleReference(args, context) {
|
|
|
321
320
|
tags: ["support", "voice", "chat"],
|
|
322
321
|
description: "Tier 1 customer support automation",
|
|
323
322
|
},
|
|
323
|
+
{
|
|
324
|
+
id: "finance-dunning",
|
|
325
|
+
name: "Finance - Automated Dunning",
|
|
326
|
+
domain: "finance",
|
|
327
|
+
personas: 2,
|
|
328
|
+
tags: ["finance", "ar", "dunning", "collections", "receivables"],
|
|
329
|
+
description: "AR dunning assistant: balance inquiries, payment links, disputes, payment plans, escalation.",
|
|
330
|
+
},
|
|
324
331
|
],
|
|
325
|
-
count:
|
|
332
|
+
count: 4,
|
|
326
333
|
_tip: "Use reference(demo_kit=\"finance-ap\") to get full kit details",
|
|
327
334
|
};
|
|
328
335
|
}
|
|
@@ -370,7 +377,23 @@ export async function handleReference(args, context) {
|
|
|
370
377
|
_tip: "Use persona(method=\"analyze\", id=\"...\") to analyze each persona's workflow",
|
|
371
378
|
};
|
|
372
379
|
}
|
|
373
|
-
|
|
380
|
+
if (kitId === "finance-dunning") {
|
|
381
|
+
return {
|
|
382
|
+
id: "finance-dunning",
|
|
383
|
+
name: "Finance - Automated Dunning",
|
|
384
|
+
version: "1.0.0",
|
|
385
|
+
domain: "finance",
|
|
386
|
+
tags: ["finance", "ar", "dunning", "collections", "receivables", "billing"],
|
|
387
|
+
description: "AR dunning assistant: balance inquiries, payment links, disputes, payment plans, escalation. Chat or voice.",
|
|
388
|
+
intents: ["balance_inquiry", "payment_made", "dispute", "payment_plan", "how_to_pay", "escalate", "Fallback"],
|
|
389
|
+
persona_types: ["chat", "voice"],
|
|
390
|
+
demo_script: "docs/demos/finance-dunning.md",
|
|
391
|
+
design_doc: ".context/core/designs/2026-02-23-finance-automated-dunning.md",
|
|
392
|
+
scenario_id: "finance-dunning",
|
|
393
|
+
_tip: "Use demo(mode=\"kit\", persona_id=\"<id>\", scenario=\"finance-dunning\") to generate KB docs and demo script for a dunning persona",
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
return { error: `Demo kit not found: ${kitId}`, available: ["finance-ap", "finance-dunning", "sales-sdr", "support-tier1"] };
|
|
374
397
|
}
|
|
375
398
|
// ─────────────────────────────────────────────────────────────────────────
|
|
376
399
|
// tags=true - Get tagging taxonomy
|
package/dist/mcp/knowledge.js
CHANGED
|
@@ -183,6 +183,11 @@ export const WORKFLOW_PATTERNS = [
|
|
|
183
183
|
"respond_for_external_actions.response → WORKFLOW_OUTPUT",
|
|
184
184
|
],
|
|
185
185
|
useCase: "FAQ bot, documentation assistant, policy lookup",
|
|
186
|
+
antiPatterns: [
|
|
187
|
+
"Using call_llm instead of respond_for_external_actions (loses citation and conversation awareness)",
|
|
188
|
+
"Connecting chat_conversation directly to search.query (type mismatch — use conversation_to_search_query)",
|
|
189
|
+
"Forgetting to upload data sources to the persona (search returns empty results)",
|
|
190
|
+
],
|
|
186
191
|
},
|
|
187
192
|
{
|
|
188
193
|
name: "intent-routing",
|
|
@@ -223,6 +228,11 @@ export const WORKFLOW_PATTERNS = [
|
|
|
223
228
|
"respond_for_external_actions.response → WORKFLOW_OUTPUT",
|
|
224
229
|
],
|
|
225
230
|
useCase: "Research assistant needing both internal docs and current web info",
|
|
231
|
+
antiPatterns: [
|
|
232
|
+
"Using web search as the primary/only data source (slower, less reliable, uncontrolled content)",
|
|
233
|
+
"Not wiring combine_search_results.combined_results to response node (combined results go unused)",
|
|
234
|
+
"Forgetting to upload internal KB documents (search returns empty, only web results used)",
|
|
235
|
+
],
|
|
226
236
|
},
|
|
227
237
|
{
|
|
228
238
|
name: "tool-calling",
|
|
@@ -241,17 +251,20 @@ export const WORKFLOW_PATTERNS = [
|
|
|
241
251
|
antiPatterns: [
|
|
242
252
|
"Creating duplicate records on follow-up questions",
|
|
243
253
|
"Not checking conversation history before actions",
|
|
254
|
+
"Forgetting voice-specific widgets (conversationSettings, voiceSettings, callSettings, vadSettings)",
|
|
255
|
+
"external_action_caller does NOT support HITL — cannot gate tool calls with human approval",
|
|
244
256
|
],
|
|
245
257
|
},
|
|
246
258
|
{
|
|
247
259
|
name: "hitl-approval",
|
|
248
260
|
personaType: "chat",
|
|
249
261
|
description: "Human-in-the-loop approval — enable HITL flag on send_email_agent or entity_extraction_with_documents (only nodes that support HITL)",
|
|
250
|
-
nodes: ["chat_trigger", "
|
|
262
|
+
nodes: ["chat_trigger", "entity_extraction_with_documents", "send_email_agent", "respond_for_external_actions"],
|
|
251
263
|
connections: [
|
|
252
|
-
"chat_trigger.user_query →
|
|
253
|
-
"chat_trigger.chat_conversation →
|
|
254
|
-
"
|
|
264
|
+
"chat_trigger.user_query → entity_extraction_with_documents.query",
|
|
265
|
+
"chat_trigger.chat_conversation → entity_extraction_with_documents.conversation",
|
|
266
|
+
"entity_extraction_with_documents.extraction_columns → send_email_agent (HITL flag enabled: disable_human_interaction: false)",
|
|
267
|
+
"send_email_agent.confirmation → respond_for_external_actions.external_action_result",
|
|
255
268
|
"chat_trigger.user_query → respond_for_external_actions.query",
|
|
256
269
|
"chat_trigger.chat_conversation → respond_for_external_actions.conversation",
|
|
257
270
|
"respond_for_external_actions.response → WORKFLOW_OUTPUT",
|
|
@@ -260,6 +273,8 @@ export const WORKFLOW_PATTERNS = [
|
|
|
260
273
|
antiPatterns: [
|
|
261
274
|
"Adding general_hitl as a standalone node (it is NOT deployable)",
|
|
262
275
|
"Not wiring conversation context to response node",
|
|
276
|
+
"Using external_action_caller for HITL — it does NOT support the HITL flag",
|
|
277
|
+
"Only send_email_agent and entity_extraction_with_documents support HITL (disable_human_interaction: false)",
|
|
263
278
|
],
|
|
264
279
|
},
|
|
265
280
|
{
|
|
@@ -272,31 +287,38 @@ export const WORKFLOW_PATTERNS = [
|
|
|
272
287
|
"entity_extraction_with_documents.extraction_columns → rule_validation_with_documents.map_of_extracted_columns",
|
|
273
288
|
"workflowInput.document-mmf2 → rule_validation_with_documents.primary_docs",
|
|
274
289
|
"rule_validation_with_documents.ruleset_output → call_llm.named_inputs_Validation_Results",
|
|
275
|
-
"
|
|
276
|
-
"
|
|
277
|
-
"call_llm.llm_output → results (dot-notation
|
|
290
|
+
"entity_extraction_with_documents.extraction_columns → results (dot-notation: '<nodeId>.extraction_columns')",
|
|
291
|
+
"rule_validation_with_documents.ruleset_output → results (dot-notation: '<nodeId>.ruleset_output')",
|
|
292
|
+
"call_llm.llm_output → results (dot-notation: '<nodeId>.llm_output')",
|
|
278
293
|
],
|
|
279
294
|
useCase: "Invoice processing, contract analysis, compliance checking",
|
|
295
|
+
antiPatterns: [
|
|
296
|
+
"Not mapping extraction/validation outputs to results (dashboard columns won't appear)",
|
|
297
|
+
"Missing primary_docs on rule_validation_with_documents (validator needs original documents for context)",
|
|
298
|
+
"Using a single call_llm without passing validation results (analysis lacks validation context)",
|
|
299
|
+
],
|
|
280
300
|
},
|
|
281
301
|
{
|
|
282
302
|
name: "dashboard-email-notification",
|
|
283
303
|
personaType: "dashboard",
|
|
284
|
-
description: "Document upload → extraction → email notification (with intermediary for type conversion)",
|
|
285
|
-
nodes: ["document_trigger", "entity_extraction_with_documents", "json_mapper", "fixed_response", "
|
|
304
|
+
description: "Document upload → extraction → email notification (with intermediary for type conversion). Production workflows often add a body generator (call_llm/custom_agent) and dual send paths (auto + HITL) with CC config.",
|
|
305
|
+
nodes: ["document_trigger", "entity_extraction_with_documents", "json_mapper", "fixed_response", "call_llm (body generator)", "send_email_agent"],
|
|
286
306
|
connections: [
|
|
287
307
|
"workflowInput.document-mmf2 → entity_extraction_with_documents.documents",
|
|
288
308
|
"entity_extraction_with_documents.extraction_columns → json_mapper.input_json",
|
|
289
309
|
"json_mapper.output_json → fixed_response.named_inputs_Extracted_Data (template: '{{to}}', '{{subject}}', etc.)",
|
|
290
310
|
"fixed_response.response → send_email_agent.email_to (one fixed_response per email field)",
|
|
291
|
-
"
|
|
292
|
-
"
|
|
293
|
-
"
|
|
311
|
+
"entity_extraction_with_documents.extraction_columns → call_llm.named_inputs_Extracted_Data (for body generation)",
|
|
312
|
+
"call_llm.llm_output → send_email_agent.email_body (LLM-generated body)",
|
|
313
|
+
"send_email_agent.confirmation → results (dot-notation: '<nodeId>.confirmation')",
|
|
314
|
+
"entity_extraction_with_documents.extraction_columns → results (dot-notation: '<nodeId>.extraction_columns')",
|
|
294
315
|
],
|
|
295
|
-
useCase: "Invoice receipt notification, contract alerts, document-triggered emails",
|
|
316
|
+
useCase: "Invoice receipt notification, contract alerts, document-triggered emails, payment confirmations",
|
|
296
317
|
antiPatterns: [
|
|
297
318
|
"DO NOT wire entity_extraction directly to send_email — type mismatch (ANY vs TEXT_WITH_SOURCES)",
|
|
298
319
|
"Use json_mapper + fixed_response as intermediary for type conversion",
|
|
299
320
|
"Enable HITL flag on send_email_agent (disable_human_interaction: false) if approval needed",
|
|
321
|
+
"For CC/BCC: extract additional recipients via entity_extraction columns, route through separate fixed_response nodes",
|
|
300
322
|
],
|
|
301
323
|
},
|
|
302
324
|
{
|
|
@@ -314,6 +336,11 @@ export const WORKFLOW_PATTERNS = [
|
|
|
314
336
|
"response_validator.abstain_reason → [conditional: if invalid] → abstain_action → WORKFLOW_OUTPUT",
|
|
315
337
|
],
|
|
316
338
|
useCase: "Regulated industries, compliance-sensitive responses",
|
|
339
|
+
antiPatterns: [
|
|
340
|
+
"Not connecting both response and abstain paths to WORKFLOW_OUTPUT",
|
|
341
|
+
"Using guardrails without search results (validator has nothing to check against)",
|
|
342
|
+
"Skipping the abstain_action fallback (invalid responses return nothing to user)",
|
|
343
|
+
],
|
|
317
344
|
},
|
|
318
345
|
{
|
|
319
346
|
name: "externalized-instructions",
|
|
@@ -357,6 +384,140 @@ export const WORKFLOW_PATTERNS = [
|
|
|
357
384
|
"Not gating fallback separately (fixed_response should handle fallback, not the LLM)",
|
|
358
385
|
],
|
|
359
386
|
},
|
|
387
|
+
// ─── Composite Dashboard Patterns (from FX2 production analysis) ──────────
|
|
388
|
+
{
|
|
389
|
+
name: "multi-phase-validation",
|
|
390
|
+
personaType: "dashboard",
|
|
391
|
+
description: "Document upload → extraction → N sequential rule_validation_with_documents phases. Each phase checks a different concern (format → compliance → cross-reference) and feeds its output to the next. All phase outputs mapped to dashboard columns for per-phase visibility.",
|
|
392
|
+
nodes: ["document_trigger", "entity_extraction_with_documents", "rule_validation_phase_1", "rule_validation_phase_2", "rule_validation_phase_3", "call_llm"],
|
|
393
|
+
connections: [
|
|
394
|
+
"workflowInput.document-mmf2 → entity_extraction_with_documents.documents",
|
|
395
|
+
"entity_extraction_with_documents.extraction_columns → rule_validation_phase_1.map_of_extracted_columns",
|
|
396
|
+
"workflowInput.document-mmf2 → rule_validation_phase_1.primary_docs",
|
|
397
|
+
"rule_validation_phase_1.ruleset_output → rule_validation_phase_2.map_of_extracted_columns",
|
|
398
|
+
"workflowInput.document-mmf2 → rule_validation_phase_2.primary_docs",
|
|
399
|
+
"rule_validation_phase_2.ruleset_output → rule_validation_phase_3.map_of_extracted_columns",
|
|
400
|
+
"workflowInput.document-mmf2 → rule_validation_phase_3.primary_docs",
|
|
401
|
+
"rule_validation_phase_3.ruleset_output → call_llm.named_inputs_Final_Validation",
|
|
402
|
+
"entity_extraction_with_documents.extraction_columns → results (dot-notation: '<nodeId>.extraction_columns')",
|
|
403
|
+
"rule_validation_phase_1.ruleset_output → results (dot-notation: '<nodeId>.ruleset_output')",
|
|
404
|
+
"rule_validation_phase_2.ruleset_output → results (dot-notation: '<nodeId>.ruleset_output')",
|
|
405
|
+
"rule_validation_phase_3.ruleset_output → results (dot-notation: '<nodeId>.ruleset_output')",
|
|
406
|
+
"call_llm.llm_output → results (dot-notation: '<nodeId>.llm_output')",
|
|
407
|
+
],
|
|
408
|
+
useCase: "Invoice processing with multi-step validation (format → compliance → PO matching), regulatory document review, dunning letter compliance checks",
|
|
409
|
+
antiPatterns: [
|
|
410
|
+
"Running all validation rules in a single phase (loses granularity, hard to debug which phase failed)",
|
|
411
|
+
"Not passing primary_docs to each validation phase (validator needs original documents for context)",
|
|
412
|
+
"Forgetting to map intermediate phase outputs to results (dashboard won't show per-phase status)",
|
|
413
|
+
],
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
name: "confidence-dual-path",
|
|
417
|
+
personaType: "dashboard",
|
|
418
|
+
description: "Dashboard: after extraction and validation, fork into AUTO path (send_email_agent without HITL) and ESCALATE path (send_email_agent with HITL enabled). A confidence/risk score from validation determines which path fires via runIf conditions.",
|
|
419
|
+
nodes: ["document_trigger", "entity_extraction_with_documents", "rule_validation_with_documents", "call_llm (confidence scorer)", "send_email_auto (no HITL)", "send_email_escalate (HITL enabled)"],
|
|
420
|
+
connections: [
|
|
421
|
+
"workflowInput.document-mmf2 → entity_extraction_with_documents.documents",
|
|
422
|
+
"entity_extraction_with_documents.extraction_columns → rule_validation_with_documents.map_of_extracted_columns",
|
|
423
|
+
"workflowInput.document-mmf2 → rule_validation_with_documents.primary_docs",
|
|
424
|
+
"rule_validation_with_documents.ruleset_output → call_llm.named_inputs_Validation_Results",
|
|
425
|
+
"call_llm.llm_output → send_email_auto (runIf: validation_status == PASS)",
|
|
426
|
+
"call_llm.llm_output → send_email_escalate (runIf: validation_status != PASS)",
|
|
427
|
+
"send_email_auto.confirmation → results (dot-notation: '<nodeId>.confirmation')",
|
|
428
|
+
"send_email_escalate.confirmation → results (dot-notation: '<nodeId>.confirmation')",
|
|
429
|
+
"entity_extraction_with_documents.extraction_columns → results (dot-notation: '<nodeId>.extraction_columns')",
|
|
430
|
+
"rule_validation_with_documents.ruleset_output → results (dot-notation: '<nodeId>.ruleset_output')",
|
|
431
|
+
],
|
|
432
|
+
useCase: "AP invoice processing (clean invoices auto-process, exceptions need human review), dunning workflows (high-confidence auto-send, ambiguous escalate), contract approvals",
|
|
433
|
+
antiPatterns: [
|
|
434
|
+
"Using a single send_email_agent for both paths (loses ability to gate high-risk sends separately)",
|
|
435
|
+
"Not having the ESCALATE path (all documents auto-process with no human oversight for edge cases)",
|
|
436
|
+
"Hardcoding threshold in node config — use validation rules output to drive the routing decision",
|
|
437
|
+
"Forgetting to use intermediary (json_mapper + fixed_response) between extraction and send_email inputs",
|
|
438
|
+
],
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
name: "document-intake-resolution",
|
|
442
|
+
personaType: "dashboard",
|
|
443
|
+
description: "Dashboard: full document intake pipeline — extract entities → convert/normalize → search knowledge base for matching records → LLM resolves/reconciles against master data. The resolution chain ensures extracted entities are validated against existing records before downstream processing.",
|
|
444
|
+
nodes: ["document_trigger", "entity_extraction_with_documents", "json_mapper", "search", "call_llm (resolver)"],
|
|
445
|
+
connections: [
|
|
446
|
+
"workflowInput.document-mmf2 → entity_extraction_with_documents.documents",
|
|
447
|
+
"entity_extraction_with_documents.extraction_columns → json_mapper.input_json",
|
|
448
|
+
"json_mapper.output_json → search.query (lookup extracted entity in KB)",
|
|
449
|
+
"search.search_results → call_llm.named_inputs_Matching_Records",
|
|
450
|
+
"entity_extraction_with_documents.extraction_columns → call_llm.named_inputs_Extracted_Data",
|
|
451
|
+
"call_llm.llm_output → results (dot-notation: '<nodeId>.llm_output' — resolution status + matched record)",
|
|
452
|
+
"entity_extraction_with_documents.extraction_columns → results (dot-notation: '<nodeId>.extraction_columns')",
|
|
453
|
+
],
|
|
454
|
+
useCase: "Invoice vendor matching against vendor master, contract party resolution, employee onboarding verification against HR records, PO line-item matching",
|
|
455
|
+
antiPatterns: [
|
|
456
|
+
"Skipping the search/resolution step (extracted data goes unvalidated against master data)",
|
|
457
|
+
"Using entity_extraction output directly as the resolved entity (extraction ≠ resolution)",
|
|
458
|
+
"Not handling 'no match found' case in the resolver LLM (must surface unresolved items)",
|
|
459
|
+
],
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
name: "hitl-decision-form",
|
|
463
|
+
personaType: "dashboard",
|
|
464
|
+
description: "Dashboard: use entity_extraction_with_documents with HITL flag as a human review/decision form — presenting processed data for human verification or correction before downstream actions. The extraction columns define the form fields the reviewer sees and can modify.",
|
|
465
|
+
nodes: ["document_trigger", "entity_extraction_with_documents (processing)", "call_llm (prepare review)", "entity_extraction_with_documents (HITL review form)", "send_email_agent"],
|
|
466
|
+
connections: [
|
|
467
|
+
"workflowInput.document-mmf2 → entity_extraction_processing.documents",
|
|
468
|
+
"entity_extraction_processing.extraction_columns → call_llm.named_inputs_Extracted_Data",
|
|
469
|
+
"call_llm.llm_output → entity_extraction_review.named_inputs_Summary (HITL enabled)",
|
|
470
|
+
"entity_extraction_review.extraction_columns → send_email_agent (human-verified data)",
|
|
471
|
+
"entity_extraction_review.extraction_columns → results (dot-notation: '<nodeId>.extraction_columns')",
|
|
472
|
+
],
|
|
473
|
+
useCase: "Invoice approval where reviewer corrects extracted amounts before payment, contract review where legal team verifies extracted terms, compliance review with sign-off",
|
|
474
|
+
antiPatterns: [
|
|
475
|
+
"Using general_hitl (NOT deployable) — use HITL flag on entity_extraction_with_documents",
|
|
476
|
+
"Confusing extraction-for-processing with extraction-as-review-form (different roles, different column configs)",
|
|
477
|
+
"Not passing processed data to the review form's named_inputs (reviewer sees empty form)",
|
|
478
|
+
],
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
name: "document-generation-pipeline",
|
|
482
|
+
personaType: "dashboard",
|
|
483
|
+
description: "Dashboard: generate formatted documents from processed data — extraction → LLM drafts content → generate_document creates formatted output (PDF) → send_email_agent delivers as attachment.",
|
|
484
|
+
nodes: ["document_trigger", "entity_extraction_with_documents", "call_llm (content drafter)", "generate_document", "send_email_agent"],
|
|
485
|
+
connections: [
|
|
486
|
+
"workflowInput.document-mmf2 → entity_extraction_with_documents.documents",
|
|
487
|
+
"entity_extraction_with_documents.extraction_columns → call_llm.named_inputs_Extracted_Data",
|
|
488
|
+
"call_llm.llm_output → generate_document.markdown_file_contents",
|
|
489
|
+
"generate_document.document_link → send_email_agent.named_inputs_Attachment",
|
|
490
|
+
"send_email_agent.confirmation → results (dot-notation: '<nodeId>.confirmation')",
|
|
491
|
+
"entity_extraction_with_documents.extraction_columns → results (dot-notation: '<nodeId>.extraction_columns')",
|
|
492
|
+
],
|
|
493
|
+
useCase: "Dunning letter generation, invoice creation from PO data, compliance report generation, customer correspondence, welcome packets",
|
|
494
|
+
antiPatterns: [
|
|
495
|
+
"Putting the full template in call_llm instructions (use data source templates for strict regulatory formats)",
|
|
496
|
+
"Skipping generate_document and sending raw LLM text as attachment (no formatting, no PDF)",
|
|
497
|
+
"Not including extracted entity data in the LLM's named_inputs (generated document lacks specifics)",
|
|
498
|
+
],
|
|
499
|
+
},
|
|
500
|
+
// ─── Voice Patterns ──────────────────────────────────────────────────────
|
|
501
|
+
{
|
|
502
|
+
name: "voice-kb-search",
|
|
503
|
+
personaType: "voice",
|
|
504
|
+
description: "Voice AI with knowledge base search only — no external actions or side effects. Clean 4-node pattern for informational help desks. Requires voice-specific widgets.",
|
|
505
|
+
nodes: ["chat_trigger", "conversation_to_search_query", "search", "respond_for_external_actions"],
|
|
506
|
+
connections: [
|
|
507
|
+
"chat_trigger.chat_conversation → conversation_to_search_query.conversation",
|
|
508
|
+
"conversation_to_search_query.summarized_conversation → search.query",
|
|
509
|
+
"search.search_results → respond_for_external_actions.external_action_result",
|
|
510
|
+
"chat_trigger.user_query → respond_for_external_actions.query",
|
|
511
|
+
"chat_trigger.chat_conversation → respond_for_external_actions.conversation",
|
|
512
|
+
"respond_for_external_actions.response → WORKFLOW_OUTPUT",
|
|
513
|
+
],
|
|
514
|
+
useCase: "FX rate inquiries, policy Q&A hotline, product information line, internal help desk for common questions",
|
|
515
|
+
antiPatterns: [
|
|
516
|
+
"Adding external_action_caller when no side effects are needed (over-engineering)",
|
|
517
|
+
"Using call_llm instead of respond_for_external_actions (loses citation and conversation awareness)",
|
|
518
|
+
"Forgetting voice-specific widgets (conversationSettings, voiceSettings, callSettings, vadSettings)",
|
|
519
|
+
],
|
|
520
|
+
},
|
|
360
521
|
];
|
|
361
522
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
362
523
|
// Qualifying Questions
|
|
@@ -1523,8 +1523,13 @@ persona(method="update", id="<ID>", config={widgets: [...]})
|
|
|
1523
1523
|
|
|
1524
1524
|
### 6. Upload Knowledge
|
|
1525
1525
|
\`\`\`
|
|
1526
|
+
# Default widget ('fileUpload') — exists on every chat/voice persona
|
|
1526
1527
|
persona(id="<ID>", data={method:"upload", path:"your-data.txt"})
|
|
1528
|
+
|
|
1529
|
+
# Custom widget — auto-created in proto_config on first upload (widget_created: true in response)
|
|
1530
|
+
persona(id="<ID>", data={method:"upload", path:"policies.pdf", widget_name:"policies"})
|
|
1527
1531
|
\`\`\`
|
|
1532
|
+
Wire any custom widget to a search node: \`search/v2\` → \`datastore_configs\` → \`widgetConfig: { widgetName: "<widget_name>" }\`.
|
|
1528
1533
|
|
|
1529
1534
|
## Hard Requirements
|
|
1530
1535
|
|
package/dist/mcp/tools.js
CHANGED
|
@@ -278,7 +278,7 @@ persona(
|
|
|
278
278
|
// Especially important for Document Generation personas with multiple upload widgets
|
|
279
279
|
widget_name: {
|
|
280
280
|
type: "string",
|
|
281
|
-
description: "Target widget for upload OR filter for stats. For Document Proposal Manager: 'upload' (Content Repository), 'upload1' (Service Line Docs), 'upload2' (Style Guide).
|
|
281
|
+
description: "Target widget for upload OR filter for stats. Default: 'fileUpload'. If the named widget doesn't exist in the persona's proto_config, it is auto-created (type 3, fileUpload). Use a custom name to create a second knowledge base alongside the default one. For Document Proposal Manager: 'upload' (Content Repository), 'upload1' (Service Line Docs), 'upload2' (Style Guide). See catalog(type='widgets') for reference."
|
|
282
282
|
},
|
|
283
283
|
// delete params
|
|
284
284
|
file_id: { type: "string", description: "File/item ID to delete (for method=delete)" },
|
package/package.json
CHANGED