@ema.co/mcp-toolkit 1.5.1 → 1.6.0
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-consolidated.js +400 -14
- package/dist/mcp/prompts.js +80 -123
- package/dist/mcp/server.js +134 -209
- package/dist/mcp/tools-consolidated.js +212 -150
- package/dist/sdk/action-registry.js +128 -0
- package/dist/sdk/client.js +58 -90
- package/dist/sdk/demo-generator.js +978 -0
- package/dist/sdk/generated/api-types.js +11 -0
- package/dist/sdk/index.js +15 -1
- package/dist/sdk/knowledge.js +38 -8
- package/dist/sdk/quality-gates.js +386 -0
- package/dist/sdk/structural-rules.js +290 -0
- package/dist/sdk/workflow-generator.js +187 -39
- package/dist/sdk/workflow-intent.js +246 -24
- package/dist/sdk/workflow-optimizer.js +665 -0
- package/dist/sdk/workflow-tracer.js +648 -0
- package/dist/sdk/workflow-transformer.js +10 -0
- package/dist/sdk/workflow-validator.js +391 -0
- package/docs/.temp/datasource-attach.har +198369 -0
- package/docs/.temp/grpcweb.gar +1 -0
- package/docs/local-generation.md +508 -0
- package/docs/mcp-flow-diagram.md +135 -0
- package/docs/mcp-tools-guide.md +163 -197
- package/docs/openapi.json +8000 -0
- package/docs/release-process.md +153 -0
- package/docs/test-persona-creation.md +196 -0
- package/docs/tool-consolidation-proposal.md +166 -378
- package/package.json +3 -1
- package/resources/templates/demo-scenarios/README.md +63 -0
|
@@ -550,7 +550,7 @@ export function parseNaturalLanguage(text) {
|
|
|
550
550
|
});
|
|
551
551
|
}
|
|
552
552
|
}
|
|
553
|
-
// Detect tools
|
|
553
|
+
// Detect tools from text
|
|
554
554
|
const tools = [];
|
|
555
555
|
for (const { pattern, namespace, action } of TOOL_PATTERNS) {
|
|
556
556
|
if (pattern.test(text)) {
|
|
@@ -1297,8 +1297,9 @@ export function summarizeIntentConfidence(confidence) {
|
|
|
1297
1297
|
export function intentToSpec(intent) {
|
|
1298
1298
|
const nodes = [];
|
|
1299
1299
|
const resultMappings = [];
|
|
1300
|
-
// 1. Add trigger
|
|
1300
|
+
// 1. Add trigger - Voice and Chat both use chat_trigger, Dashboard uses document_trigger
|
|
1301
1301
|
const triggerId = "trigger";
|
|
1302
|
+
// Note: voice_trigger doesn't exist in API - Voice AI uses chat_trigger with voiceSettings in proto_config
|
|
1302
1303
|
const triggerType = intent.persona_type === "dashboard" ? "document_trigger" : "chat_trigger";
|
|
1303
1304
|
nodes.push({
|
|
1304
1305
|
id: triggerId,
|
|
@@ -1349,6 +1350,11 @@ export function intentToSpec(intent) {
|
|
|
1349
1350
|
actionName: triggerId,
|
|
1350
1351
|
output: "user_query",
|
|
1351
1352
|
},
|
|
1353
|
+
// REQUIRED: Widget binding to fileUpload data sources (uses multiBinding.elements format)
|
|
1354
|
+
datastore_configs: {
|
|
1355
|
+
type: "widget_config_array",
|
|
1356
|
+
widgetNames: ["fileUpload"],
|
|
1357
|
+
},
|
|
1352
1358
|
},
|
|
1353
1359
|
});
|
|
1354
1360
|
}
|
|
@@ -1370,26 +1376,13 @@ export function intentToSpec(intent) {
|
|
|
1370
1376
|
});
|
|
1371
1377
|
}
|
|
1372
1378
|
// 5. Add tool caller if tools defined
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
inputs: {
|
|
1381
|
-
conversation: {
|
|
1382
|
-
type: "action_output",
|
|
1383
|
-
actionName: triggerId,
|
|
1384
|
-
output: "chat_conversation",
|
|
1385
|
-
},
|
|
1386
|
-
},
|
|
1387
|
-
tools: intent.tools.map((t) => ({
|
|
1388
|
-
name: t.action,
|
|
1389
|
-
namespace: t.namespace,
|
|
1390
|
-
})),
|
|
1391
|
-
});
|
|
1392
|
-
}
|
|
1379
|
+
// DISABLED: external_action_caller format causes 500 errors
|
|
1380
|
+
// TODO: Fix tool format and re-enable - tools are stored in intent for future use
|
|
1381
|
+
const toolCallerId = undefined;
|
|
1382
|
+
// if (intent.tools && intent.tools.length > 0) {
|
|
1383
|
+
// toolCallerId = "tool_caller";
|
|
1384
|
+
// nodes.push({...});
|
|
1385
|
+
// }
|
|
1393
1386
|
// 6. Add HITL if required
|
|
1394
1387
|
let hitlId;
|
|
1395
1388
|
if (intent.constraints?.require_hitl && toolCallerId) {
|
|
@@ -1415,9 +1408,16 @@ export function intentToSpec(intent) {
|
|
|
1415
1408
|
actionName: triggerId,
|
|
1416
1409
|
output: "user_query",
|
|
1417
1410
|
},
|
|
1411
|
+
// REQUIRED: Widget binding to fusionModel for LLM selection
|
|
1412
|
+
model_config: {
|
|
1413
|
+
type: "widget_config",
|
|
1414
|
+
widgetName: "fusionModel",
|
|
1415
|
+
},
|
|
1418
1416
|
};
|
|
1419
1417
|
if (searchId) {
|
|
1420
|
-
|
|
1418
|
+
// call_llm uses "named_inputs" not "search_results"
|
|
1419
|
+
// (respond_with_sources doesn't exist in API - mapped to call_llm)
|
|
1420
|
+
respondInputs.named_inputs = {
|
|
1421
1421
|
type: "action_output",
|
|
1422
1422
|
actionName: searchId,
|
|
1423
1423
|
output: "search_results",
|
|
@@ -1425,7 +1425,8 @@ export function intentToSpec(intent) {
|
|
|
1425
1425
|
}
|
|
1426
1426
|
nodes.push({
|
|
1427
1427
|
id: respondId,
|
|
1428
|
-
|
|
1428
|
+
// respond_with_sources doesn't exist in API - always use call_llm
|
|
1429
|
+
actionType: "call_llm",
|
|
1429
1430
|
displayName: "Response",
|
|
1430
1431
|
inputs: respondInputs,
|
|
1431
1432
|
});
|
|
@@ -1433,12 +1434,27 @@ export function intentToSpec(intent) {
|
|
|
1433
1434
|
nodeId: respondId,
|
|
1434
1435
|
output: searchId ? "response_with_sources" : "response_with_sources",
|
|
1435
1436
|
});
|
|
1437
|
+
// NOTE: Action chains (email_with_validation, doc_to_email, etc.) are NOT processed here.
|
|
1438
|
+
// Complex workflows with action chains should be generated via LLM using:
|
|
1439
|
+
// 1. generateWorkflowPrompt(intent, availableActions) - builds LLM prompt
|
|
1440
|
+
// 2. Pass to Auto Builder or external LLM
|
|
1441
|
+
// 3. Parse the LLM response as WorkflowSpec
|
|
1442
|
+
// This keeps intentToSpec simple (basic RAG workflow) and lets LLM handle complexity.
|
|
1443
|
+
// Build voiceConfig from intent.voice_config
|
|
1444
|
+
const voiceConfig = intent.persona_type === "voice" ? {
|
|
1445
|
+
welcomeMessage: intent.voice_config?.welcome_message,
|
|
1446
|
+
identityAndPurpose: intent.voice_config?.identity ?? intent.description,
|
|
1447
|
+
hangupInstructions: intent.voice_config?.hangup_instructions,
|
|
1448
|
+
// Additional fields can be extracted from intent description in the future
|
|
1449
|
+
} : undefined;
|
|
1436
1450
|
return {
|
|
1437
1451
|
name: intent.name,
|
|
1438
1452
|
description: intent.description,
|
|
1439
1453
|
personaType: intent.persona_type,
|
|
1440
1454
|
nodes,
|
|
1441
1455
|
resultMappings,
|
|
1456
|
+
...(voiceConfig && { voiceConfig }),
|
|
1457
|
+
...(intent.persona_type === "chat" && { chatConfig: { name: intent.name } }),
|
|
1442
1458
|
};
|
|
1443
1459
|
}
|
|
1444
1460
|
export function parseInput(input) {
|
|
@@ -1583,3 +1599,209 @@ export function generateContentInstructions(semantics) {
|
|
|
1583
1599
|
}
|
|
1584
1600
|
return instructions.join("\n");
|
|
1585
1601
|
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Determine if intent requires LLM-driven generation or can use simple intentToSpec.
|
|
1604
|
+
*
|
|
1605
|
+
* Simple (use intentToSpec):
|
|
1606
|
+
* - Basic Q&A with search
|
|
1607
|
+
* - Single intent without delivery
|
|
1608
|
+
*
|
|
1609
|
+
* Complex (use LLM):
|
|
1610
|
+
* - Action chains (email, document generation)
|
|
1611
|
+
* - Multiple intents with routing
|
|
1612
|
+
* - HITL requirements
|
|
1613
|
+
* - External tool calls
|
|
1614
|
+
*/
|
|
1615
|
+
export function needsLLMGeneration(intent) {
|
|
1616
|
+
// Check for action chains
|
|
1617
|
+
if (intent.action_chains && intent.action_chains.length > 0) {
|
|
1618
|
+
return true;
|
|
1619
|
+
}
|
|
1620
|
+
// Check for email/document delivery
|
|
1621
|
+
if (intent.delivery_config?.methods?.some(m => m.type === "email" || m.type === "document")) {
|
|
1622
|
+
return true;
|
|
1623
|
+
}
|
|
1624
|
+
// Check for HITL requirements
|
|
1625
|
+
if (intent.constraints?.require_hitl) {
|
|
1626
|
+
return true;
|
|
1627
|
+
}
|
|
1628
|
+
// Check for multiple intents (needs categorizer with complex routing)
|
|
1629
|
+
if (intent.intents && intent.intents.length > 2) {
|
|
1630
|
+
return true;
|
|
1631
|
+
}
|
|
1632
|
+
// Check for tools/external actions
|
|
1633
|
+
if (intent.tools && intent.tools.length > 0) {
|
|
1634
|
+
return true;
|
|
1635
|
+
}
|
|
1636
|
+
return false;
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* Generate a workflow spec or LLM prompt based on intent complexity.
|
|
1640
|
+
*
|
|
1641
|
+
* For simple intents: returns spec directly
|
|
1642
|
+
* For complex intents: returns LLM prompt with full context
|
|
1643
|
+
*
|
|
1644
|
+
* @param intent - Parsed workflow intent
|
|
1645
|
+
* @param availableActions - Actions from ListActions API (optional)
|
|
1646
|
+
* @returns Generation result with either spec or LLM prompt
|
|
1647
|
+
*/
|
|
1648
|
+
export function generateWorkflow(intent, availableActions) {
|
|
1649
|
+
const complexity = {
|
|
1650
|
+
has_action_chains: !!(intent.action_chains && intent.action_chains.length > 0),
|
|
1651
|
+
has_email: !!(intent.delivery_config?.methods?.some(m => m.type === "email")),
|
|
1652
|
+
has_hitl: !!intent.constraints?.require_hitl,
|
|
1653
|
+
has_multi_intent: !!(intent.intents && intent.intents.length > 2),
|
|
1654
|
+
chain_ids: intent.action_chains?.map(c => c.id) ?? [],
|
|
1655
|
+
};
|
|
1656
|
+
// Simple workflow - use intentToSpec
|
|
1657
|
+
if (!needsLLMGeneration(intent)) {
|
|
1658
|
+
return {
|
|
1659
|
+
needs_llm: false,
|
|
1660
|
+
spec: intentToSpec(intent),
|
|
1661
|
+
reason: "Simple workflow (basic RAG pattern) - no LLM needed",
|
|
1662
|
+
complexity,
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
// Complex workflow - generate LLM prompt
|
|
1666
|
+
const systemPrompt = buildWorkflowGenerationSystemPrompt(availableActions);
|
|
1667
|
+
const userPrompt = buildWorkflowGenerationUserPrompt(intent);
|
|
1668
|
+
return {
|
|
1669
|
+
needs_llm: true,
|
|
1670
|
+
llm_prompt: {
|
|
1671
|
+
system: systemPrompt,
|
|
1672
|
+
user: userPrompt,
|
|
1673
|
+
},
|
|
1674
|
+
reason: `Complex workflow requires LLM: ${complexity.chain_ids.length > 0 ? `chains: ${complexity.chain_ids.join(', ')}` : ''} ${complexity.has_email ? 'email' : ''} ${complexity.has_hitl ? 'HITL' : ''}`.trim(),
|
|
1675
|
+
complexity,
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
/**
|
|
1679
|
+
* Build system prompt for LLM workflow generation.
|
|
1680
|
+
*/
|
|
1681
|
+
function buildWorkflowGenerationSystemPrompt(availableActions) {
|
|
1682
|
+
const actionList = availableActions
|
|
1683
|
+
? availableActions.map(a => `- ${a.name}: ${a.description} (inputs: ${a.inputs.join(', ')})`).join('\n')
|
|
1684
|
+
: 'Use standard Ema actions: chat_trigger, search, call_llm, entity_extraction, general_hitl, send_email, chat_categorizer';
|
|
1685
|
+
return `You are an expert workflow designer for the Ema AI platform.
|
|
1686
|
+
|
|
1687
|
+
Your task is to generate a WorkflowSpec JSON that implements the user's requirements.
|
|
1688
|
+
|
|
1689
|
+
## Available Actions
|
|
1690
|
+
${actionList}
|
|
1691
|
+
|
|
1692
|
+
## WorkflowSpec Schema
|
|
1693
|
+
\`\`\`typescript
|
|
1694
|
+
interface WorkflowSpec {
|
|
1695
|
+
name: string;
|
|
1696
|
+
description: string;
|
|
1697
|
+
personaType: "voice" | "chat" | "dashboard";
|
|
1698
|
+
nodes: Node[];
|
|
1699
|
+
resultMappings: { nodeId: string; output: string }[];
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
interface Node {
|
|
1703
|
+
id: string; // Unique identifier (snake_case)
|
|
1704
|
+
actionType: string; // Action name from available actions
|
|
1705
|
+
displayName: string; // Human-readable name
|
|
1706
|
+
inputs?: Record<string, InputBinding>;
|
|
1707
|
+
runIf?: { actionName: string; output: string; enumValue: string }; // Conditional execution
|
|
1708
|
+
categories?: Category[]; // For categorizer nodes
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
interface InputBinding {
|
|
1712
|
+
type: "action_output" | "inline_string" | "widget_config";
|
|
1713
|
+
actionName?: string; // For action_output
|
|
1714
|
+
output?: string; // For action_output
|
|
1715
|
+
value?: string; // For inline_string
|
|
1716
|
+
widgetName?: string; // For widget_config
|
|
1717
|
+
}
|
|
1718
|
+
\`\`\`
|
|
1719
|
+
|
|
1720
|
+
## Critical Rules
|
|
1721
|
+
1. ALWAYS start with a trigger node (chat_trigger for voice/chat, document_trigger for dashboard)
|
|
1722
|
+
2. Wire data correctly: each input must reference a valid upstream output
|
|
1723
|
+
3. For EMAIL: extract recipient via entity_extraction FIRST, add HITL before send_email
|
|
1724
|
+
4. Use runIf for conditional execution (after HITL approval)
|
|
1725
|
+
5. All email recipients must come from entity_extraction, NOT from LLM-generated text
|
|
1726
|
+
|
|
1727
|
+
## Example: Email with HITL
|
|
1728
|
+
\`\`\`json
|
|
1729
|
+
{
|
|
1730
|
+
"nodes": [
|
|
1731
|
+
{ "id": "trigger", "actionType": "chat_trigger", "displayName": "Trigger" },
|
|
1732
|
+
{ "id": "extract", "actionType": "entity_extraction", "displayName": "Extract Email",
|
|
1733
|
+
"inputs": { "conversation": { "type": "action_output", "actionName": "trigger", "output": "chat_conversation" } } },
|
|
1734
|
+
{ "id": "respond", "actionType": "call_llm", "displayName": "Generate Response",
|
|
1735
|
+
"inputs": { "query": { "type": "action_output", "actionName": "trigger", "output": "user_query" } } },
|
|
1736
|
+
{ "id": "hitl", "actionType": "general_hitl", "displayName": "Approve Email",
|
|
1737
|
+
"inputs": { "query": { "type": "action_output", "actionName": "respond", "output": "response" } } },
|
|
1738
|
+
{ "id": "send_email", "actionType": "send_email", "displayName": "Send Email",
|
|
1739
|
+
"inputs": {
|
|
1740
|
+
"email_to": { "type": "action_output", "actionName": "extract", "output": "email_address" },
|
|
1741
|
+
"email_body": { "type": "action_output", "actionName": "respond", "output": "response" }
|
|
1742
|
+
},
|
|
1743
|
+
"runIf": { "actionName": "hitl", "output": "hitl_status", "enumValue": "HITL Success" } }
|
|
1744
|
+
],
|
|
1745
|
+
"resultMappings": [{ "nodeId": "send_email", "output": "email_sent" }]
|
|
1746
|
+
}
|
|
1747
|
+
\`\`\`
|
|
1748
|
+
|
|
1749
|
+
Respond with ONLY valid JSON - no markdown, no explanation.`;
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* Build user prompt with intent details.
|
|
1753
|
+
*/
|
|
1754
|
+
function buildWorkflowGenerationUserPrompt(intent) {
|
|
1755
|
+
const sections = [];
|
|
1756
|
+
sections.push(`## User Request\n${intent.description}`);
|
|
1757
|
+
sections.push(`## Persona Type\n${intent.persona_type ?? 'chat'}`);
|
|
1758
|
+
if (intent.action_chains && intent.action_chains.length > 0) {
|
|
1759
|
+
sections.push(`## Detected Patterns\n${intent.action_chains.map(c => `- ${c.name}: ${c.description}`).join('\n')}`);
|
|
1760
|
+
}
|
|
1761
|
+
if (intent.intents && intent.intents.length > 0) {
|
|
1762
|
+
sections.push(`## User Intents\n${intent.intents.map(i => `- ${i.name}: ${i.description}`).join('\n')}`);
|
|
1763
|
+
}
|
|
1764
|
+
if (intent.entities && intent.entities.length > 0) {
|
|
1765
|
+
sections.push(`## Entities to Extract\n${intent.entities.map(e => `- ${e.name}: ${e.type}`).join('\n')}`);
|
|
1766
|
+
}
|
|
1767
|
+
if (intent.data_sources && intent.data_sources.length > 0) {
|
|
1768
|
+
sections.push(`## Data Sources\n${intent.data_sources.map(d => `- ${d.type}${d.instructions ? `: ${d.instructions}` : ''}`).join('\n')}`);
|
|
1769
|
+
}
|
|
1770
|
+
if (intent.constraints) {
|
|
1771
|
+
const constraints = [];
|
|
1772
|
+
if (intent.constraints.require_hitl)
|
|
1773
|
+
constraints.push('- Require human approval before side effects');
|
|
1774
|
+
if (intent.constraints.require_validation)
|
|
1775
|
+
constraints.push('- Require validation before actions');
|
|
1776
|
+
if (intent.constraints.enable_web_search)
|
|
1777
|
+
constraints.push('- Enable web search');
|
|
1778
|
+
if (constraints.length > 0) {
|
|
1779
|
+
sections.push(`## Constraints\n${constraints.join('\n')}`);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
sections.push('\nGenerate a WorkflowSpec JSON that implements this workflow.');
|
|
1783
|
+
return sections.join('\n\n');
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Parse LLM response into WorkflowSpec.
|
|
1787
|
+
* Handles markdown code blocks and validates structure.
|
|
1788
|
+
*/
|
|
1789
|
+
export function parseWorkflowSpecFromLLM(llmResponse) {
|
|
1790
|
+
try {
|
|
1791
|
+
// Extract JSON from markdown code block if present
|
|
1792
|
+
let json = llmResponse;
|
|
1793
|
+
const codeBlockMatch = llmResponse.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
1794
|
+
if (codeBlockMatch) {
|
|
1795
|
+
json = codeBlockMatch[1];
|
|
1796
|
+
}
|
|
1797
|
+
const parsed = JSON.parse(json.trim());
|
|
1798
|
+
// Basic validation
|
|
1799
|
+
if (!parsed.nodes || !Array.isArray(parsed.nodes)) {
|
|
1800
|
+
return null;
|
|
1801
|
+
}
|
|
1802
|
+
return parsed;
|
|
1803
|
+
}
|
|
1804
|
+
catch {
|
|
1805
|
+
return null;
|
|
1806
|
+
}
|
|
1807
|
+
}
|