@ema.co/mcp-toolkit 1.5.1 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @ema.co/mcp-toolkit might be problematic. Click here for more details.

@@ -18,18 +18,38 @@ const ACTION_NAMESPACES = {
18
18
  document_trigger: ["triggers", "emainternal"],
19
19
  chat_categorizer: ["routing", "emainternal"],
20
20
  text_categorizer: ["routing", "emainternal"],
21
- search: ["search", "emainternal"],
22
- live_web_search: ["search", "emainternal"],
23
- respond_with_sources: ["generation", "emainternal"],
24
- call_llm: ["generation", "emainternal"],
25
- fixed_response: ["generation", "emainternal"],
26
- external_action_caller: ["external", "emainternal"],
27
- general_hitl: ["collaboration", "emainternal"],
28
- send_email_agent: ["external", "emainternal"],
29
- conversation_to_search_query: ["search", "emainternal"],
30
- entity_extraction: ["entity", "emainternal"],
31
- combine_search_results: ["search", "emainternal"],
32
- response_validator: ["validation", "emainternal"],
21
+ search: ["actions", "emainternal"], // Must be "actions" not "search"
22
+ live_web_search: ["actions", "emainternal"],
23
+ respond_with_sources: ["actions", "emainternal"], // Must be "actions" not "generation"
24
+ call_llm: ["actions", "emainternal"],
25
+ fixed_response: ["actions", "emainternal"],
26
+ external_action_caller: ["actions", "emainternal"],
27
+ general_hitl: ["actions", "emainternal"],
28
+ send_email_agent: ["actions", "emainternal"],
29
+ conversation_to_search_query: ["actions", "emainternal"],
30
+ entity_extraction: ["actions", "emainternal"],
31
+ combine_search_results: ["actions", "emainternal"],
32
+ response_validator: ["actions", "emainternal"],
33
+ };
34
+ // Version mapping for actions
35
+ const ACTION_VERSIONS = {
36
+ chat_trigger: "v1",
37
+ voice_trigger: "v1",
38
+ document_trigger: "v1",
39
+ chat_categorizer: "v1",
40
+ text_categorizer: "v1",
41
+ search: "v2",
42
+ live_web_search: "v1",
43
+ respond_with_sources: "v1",
44
+ call_llm: "v1",
45
+ fixed_response: "v1",
46
+ external_action_caller: "v1",
47
+ general_hitl: "v1",
48
+ send_email_agent: "v1",
49
+ conversation_to_search_query: "v1",
50
+ entity_extraction: "v1",
51
+ combine_search_results: "v1",
52
+ response_validator: "v1",
33
53
  };
34
54
  const OPERATOR_MAP = {
35
55
  eq: 1,
@@ -93,31 +113,42 @@ export function compileWorkflow(spec) {
93
113
  workflowName: {
94
114
  name: {
95
115
  namespaces: ["ema", "workflows"],
96
- name: spec.name.toLowerCase().replace(/\s+/g, "_"),
116
+ name: spec.name.toLowerCase().replace(/\s+/g, "_").slice(0, 50), // Limit name length
97
117
  },
98
118
  },
99
119
  actions,
120
+ enumTypes: validEnumTypes.length > 0 ? validEnumTypes : [], // Always include, even if empty
121
+ workflowInputs: {},
100
122
  results,
123
+ namedResults: {},
124
+ displayName: spec.name,
125
+ description: spec.description || "",
126
+ namedResultsEditable: false,
127
+ namedResultsEnabled: false,
101
128
  };
102
- // Only add enumTypes if there are valid ones
103
- if (validEnumTypes.length > 0) {
104
- workflowDef.enumTypes = validEnumTypes;
105
- }
106
129
  // Build proto_config
107
130
  const protoConfig = buildProtoConfig(spec);
108
131
  return { workflow_def: workflowDef, proto_config: protoConfig };
109
132
  }
110
133
  function buildAction(node, enumTypeName) {
111
- const namespaces = ACTION_NAMESPACES[node.actionType] ?? ["custom", "emainternal"];
134
+ const namespaces = ACTION_NAMESPACES[node.actionType] ?? ["actions", "emainternal"];
135
+ const version = ACTION_VERSIONS[node.actionType] ?? "v1";
112
136
  const action = {
113
137
  // CRITICAL: Use "name" not "actionName" - this is the node identifier in the workflow
114
138
  name: node.id,
115
- actionDisplayName: node.displayName,
116
- actionDescription: node.description ?? "",
117
139
  action: {
118
140
  name: { namespaces, name: node.actionType },
141
+ version,
119
142
  },
120
143
  inputs: {},
144
+ displaySettings: {
145
+ displayName: node.displayName ?? "",
146
+ description: node.description ?? "",
147
+ coordinates: { x: 800, y: 200 }, // Default position
148
+ showConfig: 0,
149
+ },
150
+ typeArguments: {},
151
+ tools: [],
121
152
  disableHumanInteraction: node.disableHitl ?? false,
122
153
  };
123
154
  // Build inputs
@@ -219,17 +250,80 @@ function buildInputBinding(binding) {
219
250
  }
220
251
  function buildProtoConfig(spec) {
221
252
  const projectType = spec.personaType === "voice" ? 5 : spec.personaType === "chat" ? 4 : 2;
253
+ // Widget format expected by Ema API:
254
+ // { name: "widgetName", type: <type_id>, widgetName: { ...config... } }
255
+ // The config is stored under a key matching the widget's name
222
256
  const widgets = [
223
- { widget_type_id: 6, widget_name: "fusionModel", widget_config: { selectedModels: ["gpt-4.1"] } },
224
- { widget_type_id: 8, widget_name: "dataProtection", widget_config: { enabled: true } },
257
+ {
258
+ name: "fusionModel",
259
+ type: 6,
260
+ fusionModel: { selectedModels: ["gpt-4.1"] }
261
+ },
262
+ {
263
+ name: "dataProtection",
264
+ type: 8,
265
+ dataProtection: { enabled: true }
266
+ },
225
267
  ];
226
268
  if (spec.personaType === "voice") {
227
- widgets.push({ widget_type_id: 38, widget_name: "voiceSettings", widget_config: { languageHints: ["en-US"], voiceModel: "default" } }, { widget_type_id: 39, widget_name: "conversationSettings", widget_config: {} }, { widget_type_id: 43, widget_name: "vadSettings", widget_config: { turnTimeout: 5, silenceEndCallTimeout: 30, maxConversationDuration: 300 } });
269
+ // Build voice settings from spec.voiceConfig with sensible defaults
270
+ const vc = spec.voiceConfig ?? {};
271
+ const defaultIdentity = `You are ${spec.name}. ${spec.description}`;
272
+ widgets.push({
273
+ name: "voiceSettings",
274
+ type: 38,
275
+ voiceSettings: {
276
+ languageHints: vc.languageHints ?? ["en-US"],
277
+ voiceModel: "default"
278
+ }
279
+ }, {
280
+ name: "conversationSettings",
281
+ type: 39,
282
+ conversationSettings: {
283
+ welcomeMessage: vc.welcomeMessage ?? `Hello, this is ${spec.name}. How can I help you today?`,
284
+ identityAndPurpose: vc.identityAndPurpose ?? defaultIdentity,
285
+ takeActionInstructions: vc.takeActionInstructions ?? "Take appropriate actions based on the user's request. Always confirm before making changes.",
286
+ hangupInstructions: vc.hangupInstructions ?? "End the call politely when the user's request is complete or they ask to hang up.",
287
+ transferCallInstructions: vc.transferInstructions ?? "",
288
+ speechCharacteristics: vc.speechCharacteristics ?? "Professional, friendly, and helpful",
289
+ systemPrompt: vc.systemPrompt ?? "",
290
+ waitMessage: vc.waitMessage ?? "One moment please while I look that up...",
291
+ }
292
+ }, {
293
+ name: "vadSettings",
294
+ type: 43,
295
+ vadSettings: {
296
+ turnTimeout: 5,
297
+ silenceEndCallTimeout: 30,
298
+ maxConversationDuration: 300
299
+ }
300
+ });
228
301
  }
229
302
  if (spec.personaType === "chat") {
230
- widgets.push({ widget_type_id: 28, widget_name: "chatbotSdkConfig", widget_config: { name: spec.name, theme: { primaryColor: "#0066cc" }, allowedDomains: ["*"] } }, { widget_type_id: 33, widget_name: "feedbackMessage", widget_config: { message: { question: "Was this response helpful?" }, feedbackFrequency: "always" } });
303
+ // Build chat settings from spec.chatConfig with sensible defaults
304
+ const cc = spec.chatConfig ?? {};
305
+ widgets.push({
306
+ name: "chatbotSdkConfig",
307
+ type: 28,
308
+ chatbotSdkConfig: {
309
+ name: cc.name ?? spec.name,
310
+ theme: { primaryColor: cc.primaryColor ?? "#0066cc" },
311
+ allowedDomains: cc.allowedDomains ?? ["*"]
312
+ }
313
+ }, {
314
+ name: "feedbackMessage",
315
+ type: 33,
316
+ feedbackMessage: {
317
+ message: { question: cc.feedbackQuestion ?? "Was this response helpful?" },
318
+ feedbackFrequency: "always"
319
+ }
320
+ });
231
321
  }
232
- widgets.push({ widget_type_id: 3, widget_name: "fileUpload", widget_config: { useChunking: true } });
322
+ widgets.push({
323
+ name: "fileUpload",
324
+ type: 3,
325
+ fileUpload: { useChunking: true }
326
+ });
233
327
  return {
234
328
  project_type: projectType,
235
329
  name: spec.name,
@@ -1433,12 +1433,21 @@ export function intentToSpec(intent) {
1433
1433
  nodeId: respondId,
1434
1434
  output: searchId ? "response_with_sources" : "response_with_sources",
1435
1435
  });
1436
+ // Build voiceConfig from intent.voice_config
1437
+ const voiceConfig = intent.persona_type === "voice" ? {
1438
+ welcomeMessage: intent.voice_config?.welcome_message,
1439
+ identityAndPurpose: intent.voice_config?.identity ?? intent.description,
1440
+ hangupInstructions: intent.voice_config?.hangup_instructions,
1441
+ // Additional fields can be extracted from intent description in the future
1442
+ } : undefined;
1436
1443
  return {
1437
1444
  name: intent.name,
1438
1445
  description: intent.description,
1439
1446
  personaType: intent.persona_type,
1440
1447
  nodes,
1441
1448
  resultMappings,
1449
+ ...(voiceConfig && { voiceConfig }),
1450
+ ...(intent.persona_type === "chat" && { chatConfig: { name: intent.name } }),
1442
1451
  };
1443
1452
  }
1444
1453
  export function parseInput(input) {
@@ -30,19 +30,29 @@ Some clients show a **prefixed name** (for example Cursor: `mcp_ema_workflow`).
30
30
 
31
31
  ### Create a new AI Employee (greenfield)
32
32
 
33
+ **Architecture**: Creates persona from template, configures settings. Template provides valid workflow.
34
+
33
35
  1. **Requirements**: `template(questions=true)` (and `template(questions=true, category="Voice")` for voice)
34
36
  2. **Pick agents/pattern**: `action(suggest="<use case>")`
35
- 3. **Get the pattern**: `template(pattern="<pattern>")`
36
- 4. **Generate workflow** (preview by default):
37
+ 3. **Create and configure in one step**:
37
38
  ```typescript
38
- workflow(input="<requirements>", type="chat|voice|dashboard")
39
+ workflow(
40
+ input="<description of what it does>",
41
+ name="My AI Employee",
42
+ type="voice|chat|dashboard",
43
+ preview=false
44
+ )
39
45
  ```
40
- 5. **Create persona** (if needed): `persona(mode="create", name="...", type="chat|voice|dashboard")`
41
- 6. **Deploy** (preview=false):
46
+ This creates the persona from template and configures proto_config (voice settings, welcome message, etc.)
47
+
48
+ 4. **Customize workflow** (if needed - AFTER creation):
42
49
  ```typescript
43
- workflow(input="<requirements>", persona_id="<id>", preview=false)
50
+ workflow(persona_id="<id>", input="add search node before responding")
44
51
  ```
45
- 7. **Review**: `workflow(mode="analyze", persona_id="...")`
52
+
53
+ 5. **Review**: `workflow(persona_id="<id>")` to analyze
54
+
55
+ **Key insight**: Don't try to generate and deploy a custom workflow in greenfield. Create from template first, then modify.
46
56
 
47
57
  ### Extend an existing AI Employee (brownfield)
48
58
 
@@ -129,10 +139,11 @@ persona(id="My Bot", mode="version_policy", auto_on_deploy=true)
129
139
 
130
140
  All modification modes default to `preview=true` for safety. Use `preview=false` to deploy.
131
141
 
132
- - **Generate (greenfield)**: Create new workflow
142
+ - **Create (greenfield)**: Create new persona from template with configured settings
133
143
  ```typescript
134
- workflow(input="IT helpdesk with KB search") // Preview
135
- workflow(input="...", persona_id="abc", preview=false) // Generate AND deploy
144
+ workflow(input="IT helpdesk chatbot", name="IT Helper", type="chat", preview=false)
145
+ // Creates persona from template, configures proto_config
146
+ // Template workflow is preserved - customize later via modify
136
147
  ```
137
148
 
138
149
  - **Extend (brownfield)**: Modify existing workflow with natural language
@@ -299,12 +310,13 @@ The MCP automatically condenses long prompts via `condensePromptForAutobuilder()
299
310
 
300
311
  ## Workflow Generation Strategy
301
312
 
302
- | Approach | When to Use | Notes |
303
- |----------|-------------|-------|
304
- | **Local compilation** | Simple/medium workflows | `workflow(mode="compile", ...)` - reliable, fast |
305
- | **Auto Builder** | Complex/exploratory workflows | Requires concise prompts (<2500 chars) |
313
+ | Scenario | Approach | How |
314
+ |----------|----------|-----|
315
+ | **New persona** (greenfield) | Create from template | `workflow(input="...", name="...", type="...", preview=false)` |
316
+ | **Modify existing** (brownfield) | LLM-native transform | `workflow(persona_id="...", input="add X")` |
317
+ | **Complex workflows** | Auto Builder | May time out with long prompts (>2500 chars) |
306
318
 
307
- Prefer local compilation for deterministic results. Use `use_autobuilder=false` flag when generating workflows.
319
+ **DO NOT** try to compile custom workflows for new personas. The template provides a valid workflow structure - customize it after creation via brownfield/modify mode.
308
320
 
309
321
  ## Legacy tools (migration only)
310
322
 
@@ -0,0 +1,196 @@
1
+ # Persona Creation Test Guide
2
+
3
+ This guide tests the workflow tool's ability to create and configure AI Employees (personas).
4
+
5
+ ## Architecture
6
+
7
+ ### Greenfield (New Personas)
8
+ 1. **Create** persona from template (provides valid workflow structure)
9
+ 2. **Fetch** the created persona (get template's structure)
10
+ 3. **Update** proto_config only (voice settings, welcome message, etc.)
11
+ 4. **Customize workflow later** via modify mode if needed
12
+
13
+ ### Brownfield (Existing Personas)
14
+ 1. **Fetch** existing persona and workflow
15
+ 2. **Decompile** workflow_def → WorkflowSpec (typed representation)
16
+ 3. **Transform** spec based on user intent (LLM-native)
17
+ 4. **Compile** back to workflow_def
18
+ 5. **Update** persona with transformed workflow
19
+
20
+ ## Prerequisites
21
+
22
+ 1. **Rebuild the code**:
23
+ ```bash
24
+ cd /path/to/ema-mcp-toolkit
25
+ npm run build
26
+ ```
27
+
28
+ 2. **Restart the MCP server** (Cursor caches code):
29
+ - `Cmd+Shift+P` → "Developer: Reload Window"
30
+
31
+ 3. Access to Ema dev environment
32
+
33
+ ---
34
+
35
+ ## Test Cases
36
+
37
+ ### Test 1: Create Voice AI Employee (Greenfield)
38
+
39
+ **Goal:** Create a new Voice AI persona from template with configured settings.
40
+
41
+ **Command:**
42
+ ```
43
+ workflow(
44
+ input="Voice AI sales development representative for Ema. Handles discovery calls, qualifies prospects, explains value propositions.",
45
+ name="SP-TEST-VOICE-GREENFIELD",
46
+ type="voice",
47
+ preview=false,
48
+ env="dev"
49
+ )
50
+ ```
51
+
52
+ **Expected Result:**
53
+ - `status: "deployed"`
54
+ - `deployed_to.created: true`
55
+ - `deployed_to.persona_id` returned
56
+ - `next_steps` explains how to customize workflow
57
+
58
+ **Verify:**
59
+ ```
60
+ persona(id="<persona_id>", include_workflow=true, env="dev")
61
+ ```
62
+
63
+ **Check:**
64
+ - [ ] Persona exists with correct name
65
+ - [ ] `proto_config.widgets` contains `conversationSettings` with:
66
+ - [ ] `welcomeMessage` - generated greeting
67
+ - [ ] `identityAndPurpose` - from description
68
+ - [ ] `workflow_def` has template workflow (trigger node + possibly more from template)
69
+
70
+ ---
71
+
72
+ ### Test 2: Create Chat AI Employee (Greenfield)
73
+
74
+ **Command:**
75
+ ```
76
+ workflow(
77
+ input="IT helpdesk chatbot that answers employee questions about password resets, VPN setup, and software installation.",
78
+ name="SP-TEST-CHAT-GREENFIELD",
79
+ type="chat",
80
+ preview=false,
81
+ env="dev"
82
+ )
83
+ ```
84
+
85
+ **Expected Result:**
86
+ - `status: "deployed"`
87
+ - `deployed_to.created: true`
88
+
89
+ **Verify:**
90
+ ```
91
+ persona(id="<persona_id>", include_workflow=true, env="dev")
92
+ ```
93
+
94
+ ---
95
+
96
+ ### Test 3: Modify Existing Workflow (Brownfield)
97
+
98
+ **Prerequisites:** Create a persona first (Test 1 or 2).
99
+
100
+ **Goal:** Add functionality to an existing persona's workflow.
101
+
102
+ **Command:**
103
+ ```
104
+ workflow(
105
+ persona_id="<persona_id_from_test_1>",
106
+ input="add a search node that queries the knowledge base before responding",
107
+ preview=true,
108
+ env="dev"
109
+ )
110
+ ```
111
+
112
+ **Expected Result:**
113
+ - `mode: "modify"`
114
+ - Shows proposed changes
115
+ - `modified_workflow` with new nodes
116
+
117
+ **Deploy the changes:**
118
+ ```
119
+ workflow(
120
+ persona_id="<persona_id>",
121
+ input="add a search node that queries the knowledge base before responding",
122
+ preview=false,
123
+ env="dev"
124
+ )
125
+ ```
126
+
127
+ ---
128
+
129
+ ### Test 4: Analyze Workflow
130
+
131
+ **Command:**
132
+ ```
133
+ workflow(persona_id="<persona_id>", env="dev")
134
+ ```
135
+
136
+ **Expected Result:**
137
+ - `mode: "analyze"`
138
+ - `issues` array (may be empty if healthy)
139
+ - `metrics` with node_count, edge_count
140
+
141
+ ---
142
+
143
+ ### Test 5: Preview Only (No Deploy)
144
+
145
+ **Goal:** Verify preview mode returns workflow without deploying.
146
+
147
+ **Command:**
148
+ ```
149
+ workflow(
150
+ input="Customer support voice AI that handles billing inquiries",
151
+ type="voice"
152
+ )
153
+ ```
154
+
155
+ **Expected Result:**
156
+ - `status: "preview"`
157
+ - `workflow_def` returned
158
+ - `proto_config` with voice settings populated
159
+ - NO persona created
160
+
161
+ ---
162
+
163
+ ## Cleanup
164
+
165
+ After testing, delete test personas via the Ema UI or leave them for inspection.
166
+
167
+ Test persona names:
168
+ - SP-TEST-VOICE-GREENFIELD
169
+ - SP-TEST-CHAT-GREENFIELD
170
+
171
+ ---
172
+
173
+ ## Troubleshooting
174
+
175
+ ### "Internal Server Error"
176
+ - Usually means workflow format is wrong
177
+ - Check if MCP server has latest code (restart Cursor)
178
+
179
+ ### "Widget name is empty"
180
+ - Old code issue - restart MCP server
181
+
182
+ ### "Workflow name does not match"
183
+ - Old code tried to replace workflow - now we don't touch workflow in greenfield
184
+
185
+ ### proto_config not updated
186
+ - Check that greenfield flow only updates proto_config, not workflow
187
+ - Widget merging should preserve template structure
188
+
189
+ ---
190
+
191
+ ## Key Code Locations
192
+
193
+ - **Greenfield flow**: `src/mcp/handlers-consolidated.ts` (search for "GREENFIELD")
194
+ - **Brownfield/modify flow**: `src/mcp/handlers-consolidated.ts` (search for "modify")
195
+ - **Workflow transformer**: `src/sdk/workflow-transformer.ts` (decompile/compile)
196
+ - **Proto config generation**: `src/sdk/workflow-generator.ts` (buildProtoConfig)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ema.co/mcp-toolkit",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
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",
@@ -0,0 +1,63 @@
1
+ # Demo Scenarios
2
+
3
+ Pre-built demo data scenarios for reliable AI Employee demonstrations.
4
+
5
+ ## Available Scenarios
6
+
7
+ | Scenario | Use Case | Persona Types |
8
+ |----------|----------|---------------|
9
+ | `sales-sdr` | Sales Development Representative | Voice, Chat |
10
+ | `support-tier1` | Customer Support Tier 1 | Voice, Chat |
11
+ | `hr-assistant` | HR Policy & Benefits Assistant | Chat, Voice |
12
+
13
+ ## Usage
14
+
15
+ Generate a demo kit:
16
+
17
+ ```
18
+ demo(mode="kit", persona_id="...", scenario="sales-sdr")
19
+ ```
20
+
21
+ Validate demo readiness:
22
+
23
+ ```
24
+ demo(mode="validate_kit", persona_id="...")
25
+ ```
26
+
27
+ ## What's Generated
28
+
29
+ Each demo kit includes:
30
+
31
+ 1. **KB Documents** - Markdown files for the knowledge base with demo-ready data
32
+ 2. **Demo Script** - Questions to ask with expected answers
33
+ 3. **Fixed Response Fallbacks** - Workflow nodes for guaranteed responses
34
+ 4. **Validation Queries** - Test queries to verify before the demo
35
+
36
+ ## Customization
37
+
38
+ Add custom Q&A pairs:
39
+
40
+ ```
41
+ demo(mode="kit", persona_id="...", scenario="sales-sdr", custom_qa=[
42
+ {"question": "Tell me about feature X", "answer": "Feature X does..."}
43
+ ])
44
+ ```
45
+
46
+ ## Creating Custom Scenarios
47
+
48
+ Create a JSON file in this directory with the following structure:
49
+
50
+ ```json
51
+ {
52
+ "id": "my-scenario",
53
+ "name": "My Custom Scenario",
54
+ "description": "Description",
55
+ "persona_types": ["voice", "chat"],
56
+ "tags": ["tag1", "tag2"],
57
+ "intents": [...],
58
+ "entities": [...],
59
+ "qa_pairs": [...]
60
+ }
61
+ ```
62
+
63
+ See existing scenarios for complete examples.