@ema.co/mcp-toolkit 2026.1.25 → 2026.1.26-4

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.

Files changed (87) hide show
  1. package/README.md +10 -2
  2. package/dist/mcp/handlers/action/index.js +3 -18
  3. package/dist/mcp/handlers/data/index.js +385 -41
  4. package/dist/mcp/handlers/data/templates.js +107 -0
  5. package/dist/mcp/handlers/deprecation.js +50 -0
  6. package/dist/mcp/handlers/env/index.js +8 -4
  7. package/dist/mcp/handlers/knowledge/index.js +44 -237
  8. package/dist/mcp/handlers/persona/create.js +47 -18
  9. package/dist/mcp/handlers/persona/index.js +14 -11
  10. package/dist/mcp/handlers/persona/update.js +4 -2
  11. package/dist/mcp/handlers/persona/version.js +234 -0
  12. package/dist/mcp/handlers/sync/index.js +3 -18
  13. package/dist/mcp/handlers/template/index.js +75 -10
  14. package/dist/mcp/handlers/workflow/analyze.js +171 -0
  15. package/dist/mcp/handlers/workflow/compare.js +70 -0
  16. package/dist/mcp/handlers/workflow/deploy.js +73 -0
  17. package/dist/mcp/handlers/workflow/generate.js +350 -0
  18. package/dist/mcp/handlers/workflow/index.js +294 -0
  19. package/dist/mcp/handlers/workflow/modify.js +456 -0
  20. package/dist/mcp/handlers/workflow/optimize.js +136 -0
  21. package/dist/mcp/handlers/workflow/types.js +4 -0
  22. package/dist/mcp/handlers/workflow/utils.js +30 -0
  23. package/dist/mcp/handlers-consolidated.js +73 -2696
  24. package/dist/mcp/prompts.js +83 -43
  25. package/dist/mcp/resources.js +382 -57
  26. package/dist/mcp/server.js +199 -391
  27. package/dist/mcp/{tools-v2.js → tools.js} +20 -54
  28. package/dist/mcp/workflow-operations.js +2 -2
  29. package/dist/sdk/client-adapter.js +267 -32
  30. package/dist/sdk/client.js +45 -16
  31. package/dist/sdk/ema-client.js +183 -0
  32. package/dist/sdk/generated/deprecated-actions.js +171 -0
  33. package/dist/sdk/generated/template-fallbacks.js +123 -0
  34. package/dist/sdk/guidance.js +65 -11
  35. package/dist/sdk/index.js +3 -1
  36. package/dist/sdk/knowledge.js +139 -86
  37. package/dist/sdk/workflow-intent.js +27 -0
  38. package/dist/sdk/workflow-transformer.js +0 -342
  39. package/docs/mcp-tools-guide.md +37 -45
  40. package/package.json +10 -4
  41. package/dist/mcp/handlers/persona/analyze.js +0 -275
  42. package/dist/mcp/handlers/persona/compare.js +0 -32
  43. package/dist/mcp/tools-consolidated.js +0 -875
  44. package/dist/mcp/tools-legacy.js +0 -736
  45. package/docs/CODEBASE-ANALYSIS-2026-01-23.md +0 -936
  46. package/docs/CODEBASE-ANALYSIS-PRIORITIZED.md +0 -774
  47. package/docs/api-contracts.md +0 -216
  48. package/docs/auto-builder-analysis.md +0 -271
  49. package/docs/blog/mcp-tool-design-lessons.md +0 -309
  50. package/docs/data-architecture.md +0 -166
  51. package/docs/demos/ap-invoice-generation.md +0 -347
  52. package/docs/demos/ap-invoice-processing.md +0 -271
  53. package/docs/ema-auto-builder-guide.html +0 -394
  54. package/docs/lessons-learned.md +0 -209
  55. package/docs/llm-native-workflow-design.md +0 -252
  56. package/docs/local-generation.md +0 -508
  57. package/docs/mcp-flow-diagram.md +0 -135
  58. package/docs/migration/action-composition-migration.md +0 -270
  59. package/docs/naming-conventions.md +0 -278
  60. package/docs/proposals/HANDOFF-tool-restructure.md +0 -526
  61. package/docs/proposals/action-composition.md +0 -490
  62. package/docs/proposals/explicit-method-restructure.md +0 -328
  63. package/docs/proposals/mcp-tool-restructure-2026-01.md +0 -366
  64. package/docs/proposals/self-contained-guidance.md +0 -427
  65. package/docs/proto-sdk-generation.md +0 -242
  66. package/docs/release-impact.md +0 -102
  67. package/docs/release-process.md +0 -157
  68. package/docs/staging.RULE.md +0 -142
  69. package/docs/test-persona-creation.md +0 -196
  70. package/docs/tool-consolidation-v2.md +0 -225
  71. package/docs/tool-response-standards.md +0 -256
  72. package/resources/demo-kits/README.md +0 -175
  73. package/resources/demo-kits/finance-ap/manifest.json +0 -150
  74. package/resources/demo-kits/tags.json +0 -91
  75. package/resources/docs/getting-started.md +0 -97
  76. package/resources/templates/auto-builder-rules.md +0 -224
  77. package/resources/templates/chat-ai/README.md +0 -119
  78. package/resources/templates/chat-ai/persona-config.json +0 -111
  79. package/resources/templates/dashboard-ai/README.md +0 -156
  80. package/resources/templates/dashboard-ai/persona-config.json +0 -180
  81. package/resources/templates/demo-scenarios/README.md +0 -63
  82. package/resources/templates/demo-scenarios/test-published-package.md +0 -116
  83. package/resources/templates/document-gen-ai/README.md +0 -132
  84. package/resources/templates/document-gen-ai/persona-config.json +0 -316
  85. package/resources/templates/voice-ai/README.md +0 -123
  86. package/resources/templates/voice-ai/persona-config.json +0 -74
  87. package/resources/templates/voice-ai/workflow-prompt.md +0 -121
@@ -19,7 +19,7 @@ import { generateInputDescription, SUPPORTED_OPERATIONS, LIMITATIONS } from "./w
19
19
  /**
20
20
  * Generate the v2 tool set
21
21
  */
22
- export function generateToolsV2(envNames, defaultEnv) {
22
+ export function generateTools(envNames, defaultEnv) {
23
23
  const envDescription = envNames.length > 0
24
24
  ? `Environment: ${envNames.join(", ")}. Default: ${defaultEnv}`
25
25
  : "Environment name";
@@ -127,8 +127,8 @@ persona(
127
127
  // === EXPLICIT METHOD (required) ===
128
128
  method: {
129
129
  type: "string",
130
- enum: ["list", "get", "create", "update", "delete", "analyze", "sanitize", "schema", "snapshot", "history", "restore", "compare"],
131
- description: "Operation to perform (required unless using data sub-resource)"
130
+ enum: ["list", "get", "create", "update", "delete", "sanitize", "schema", "snapshot", "history", "restore"],
131
+ description: "Operation to perform. LLM does analysis/comparison - use 'get' to retrieve data, then reason about it."
132
132
  },
133
133
  // === Identity ===
134
134
  id: {
@@ -197,11 +197,6 @@ persona(
197
197
  type: "string",
198
198
  description: "Version to restore, e.g. 'v3' (for method=restore)"
199
199
  },
200
- // === Compare params (for method=compare) ===
201
- to: {
202
- type: "string",
203
- description: "Persona ID or snapshot to compare to (for method=compare)"
204
- },
205
200
  // === Data sub-resource ===
206
201
  data: {
207
202
  type: "object",
@@ -359,76 +354,47 @@ persona(
359
354
  },
360
355
  },
361
356
  // ═══════════════════════════════════════════════════════════════════════════
362
- // 3. WORKFLOW - Complex workflow generation (separate from persona modifications)
357
+ // 3. WORKFLOW - Data retrieval and deployment ONLY (LLM does all thinking)
363
358
  // ═══════════════════════════════════════════════════════════════════════════
364
359
  {
365
360
  name: "workflow",
366
- description: `Generate, analyze, or compile complex multi-node workflows.
367
-
368
- Use this for COMPLEX workflows (categorizers, multi-path routing, entity extraction chains).
369
- For SIMPLE modifications to existing personas, use persona(method="update", input="...") instead.
370
-
371
- ## Generate (from requirements)
372
- - \`workflow(mode="generate", input="Voice AI for support with routing")\` - new workflow
373
- - \`workflow(mode="generate", input="...", persona_id="abc")\` - with widget context from existing persona
374
- - Returns \`llm_prompt\` for complex workflows - includes available widgets if persona_id provided
375
- - Returns \`available_widgets\` array showing what widget names to use in bindings
361
+ description: `Get workflow data or deploy LLM-generated workflows.
376
362
 
377
- ## Analyze (existing workflow)
378
- - \`workflow(mode="analyze", persona_id="abc")\` - detect issues, get WorkflowSpec
363
+ **MCP provides data. LLM does the thinking.**
379
364
 
380
- ## Compile (spec to def)
381
- - \`workflow(mode="compile", workflow_spec={...})\` - convert WorkflowSpec to workflow_def
365
+ ## Get (return data for LLM to work with)
366
+ - \`workflow(mode="get", persona_id="abc")\` - returns workflow_def, schema, patterns, widgets
367
+ - LLM analyzes, compares, and generates workflows using this data
382
368
 
383
- ## Deploy (to persona)
384
- - \`workflow(mode="deploy", persona_id="abc", workflow_def={...})\` - apply workflow to persona
369
+ ## Deploy (execute LLM's result)
370
+ - \`workflow(mode="deploy", persona_id="abc", workflow_def={...})\` - deploy LLM-generated workflow
385
371
 
386
- **Workflow for complex creation:**
387
- 1. \`workflow(mode="generate", input="...", persona_id="abc")\` → get llm_prompt + available_widgets
388
- 2. Use LLM to generate WorkflowSpec (use widget names from available_widgets)
389
- 3. \`workflow(mode="compile", workflow_spec={...})\` → get workflow_def
390
- 4. \`persona(method="update", id="abc", workflow_def={...})\` → deploy
391
-
392
- **IMPORTANT**: Workflows reference widgets by name (widgetName). Pass persona_id to get available widget names.`,
372
+ **Workflow for creation:**
373
+ 1. \`workflow(mode="get", persona_id="abc")\` → get schema, patterns, current workflow
374
+ 2. LLM generates workflow_def (LLM does the work)
375
+ 3. \`workflow(mode="deploy", persona_id="abc", workflow_def={...})\` → MCP deploys`,
393
376
  inputSchema: {
394
377
  type: "object",
395
378
  properties: {
396
379
  mode: {
397
380
  type: "string",
398
- enum: ["generate", "analyze", "compile", "deploy", "optimize"],
399
- description: "Operation to perform",
400
- },
401
- input: {
402
- type: "string",
403
- description: "Natural language requirements (for mode=generate)",
381
+ enum: ["get", "deploy"],
382
+ description: "get = return data for LLM, deploy = execute LLM's workflow_def",
404
383
  },
405
384
  persona_id: {
406
385
  type: "string",
407
- description: "Persona ID (for mode=analyze, deploy, optimize)",
408
- },
409
- workflow_spec: {
410
- type: "object",
411
- description: "WorkflowSpec object (for mode=compile)",
386
+ description: "Persona ID (required)",
412
387
  },
413
388
  workflow_def: {
414
389
  type: "object",
415
- description: "workflow_def JSON (for mode=deploy)",
416
- },
417
- type: {
418
- type: "string",
419
- enum: ["voice", "chat", "dashboard"],
420
- description: "Persona type (for mode=generate)",
421
- },
422
- preview: {
423
- type: "boolean",
424
- description: "Preview without deploying (default: true)",
390
+ description: "workflow_def JSON (for mode=deploy) - LLM generates this",
425
391
  },
426
392
  env: {
427
393
  type: "string",
428
394
  description: envDescription,
429
395
  },
430
396
  },
431
- required: ["mode"],
397
+ required: ["mode", "persona_id"],
432
398
  },
433
399
  },
434
400
  // ═══════════════════════════════════════════════════════════════════════════
@@ -4,7 +4,7 @@
4
4
  * This file defines supported workflow modification operations.
5
5
  * Used by:
6
6
  * - analyzeModificationRequest() in handlers-consolidated.ts
7
- * - Tool descriptions in tools-v2.ts
7
+ * - Tool descriptions in tools.ts
8
8
  * - Tests to verify capabilities
9
9
  *
10
10
  * Add new operations here, and they'll automatically appear in tool docs.
@@ -64,7 +64,7 @@ export const ALTERNATIVES = {
64
64
  };
65
65
  /**
66
66
  * Generate tool description for the `input` parameter.
67
- * Called by tools-v2.ts to ensure description matches actual capabilities.
67
+ * Called by tools.ts to ensure description matches actual capabilities.
68
68
  */
69
69
  export function generateInputDescription() {
70
70
  const ops = SUPPORTED_OPERATIONS.map(op => op.example).join("', '");
@@ -1,34 +1,80 @@
1
1
  /**
2
- * EmaClientAdapter - Adapter providing legacy EmaClient interface over EmaClientV2
2
+ * EmaClientAdapter - Drop-in replacement for legacy EmaClient using EmaClientV2
3
3
  *
4
- * This adapter enables parity testing between the old hand-written EmaClient
5
- * and the new auto-generated EmaClientV2. It wraps EmaClientV2 and exposes
6
- * the same interface as EmaClient for seamless comparison.
4
+ * This adapter provides full compatibility with the legacy EmaClient interface
5
+ * while using the new auto-generated EmaClientV2 under the hood.
7
6
  *
8
7
  * Usage:
9
8
  * ```typescript
10
- * const adapter = new EmaClientAdapter({
9
+ * // Replace: import { EmaClient } from './client.js';
10
+ * // With: import { EmaClientAdapter as EmaClient } from './client-adapter.js';
11
+ *
12
+ * const client = new EmaClientAdapter({
11
13
  * name: 'dev',
12
14
  * baseUrl: 'https://api.dev.ema.co',
13
15
  * bearerToken: 'xxx',
14
16
  * });
15
17
  *
16
18
  * // Same API as EmaClient
17
- * const personas = await adapter.getPersonasForTenant();
19
+ * const personas = await client.getPersonasForTenant();
18
20
  * ```
19
21
  */
20
22
  import { EmaClientV2 } from './ema-client.js';
21
23
  /**
22
24
  * Adapter that wraps EmaClientV2 with the legacy EmaClient interface.
23
25
  *
24
- * This enables parity testing between the old and new client implementations.
26
+ * This is a drop-in replacement for EmaClient.
25
27
  */
26
28
  export class EmaClientAdapter {
27
29
  client;
28
30
  env;
29
- constructor(env) {
31
+ tokenRefreshConfig;
32
+ refreshIntervalId;
33
+ constructor(env, opts) {
30
34
  this.env = env;
31
35
  this.client = new EmaClientV2(env);
36
+ this.tokenRefreshConfig = opts?.tokenRefresh;
37
+ // Start background refresh if configured
38
+ if (this.tokenRefreshConfig?.backgroundRefresh !== false && this.tokenRefreshConfig?.refreshCallback) {
39
+ this.startBackgroundRefresh();
40
+ }
41
+ }
42
+ /** Start background token refresh */
43
+ startBackgroundRefresh() {
44
+ const intervalMs = this.tokenRefreshConfig?.refreshIntervalMs ?? 50 * 60 * 1000; // 50 minutes default
45
+ this.refreshIntervalId = setInterval(async () => {
46
+ try {
47
+ await this.refreshToken();
48
+ }
49
+ catch (e) {
50
+ console.error('[EmaClientAdapter] Background token refresh failed:', e);
51
+ }
52
+ }, intervalMs);
53
+ }
54
+ /** Stop background token refresh */
55
+ stopBackgroundRefresh() {
56
+ if (this.refreshIntervalId) {
57
+ clearInterval(this.refreshIntervalId);
58
+ this.refreshIntervalId = undefined;
59
+ }
60
+ }
61
+ /** Manually trigger token refresh */
62
+ async refreshToken() {
63
+ if (!this.tokenRefreshConfig?.refreshCallback) {
64
+ return false;
65
+ }
66
+ try {
67
+ const newToken = await this.tokenRefreshConfig.refreshCallback();
68
+ this.updateToken(newToken);
69
+ return true;
70
+ }
71
+ catch {
72
+ return false;
73
+ }
74
+ }
75
+ /** Access the underlying EmaClientV2 for advanced use */
76
+ get v2Client() {
77
+ return this.client;
32
78
  }
33
79
  /**
34
80
  * Get the environment name
@@ -54,28 +100,23 @@ export class EmaClientAdapter {
54
100
  updateToken(newToken) {
55
101
  this.client.updateToken(newToken);
56
102
  }
57
- // ─────────────────────────────────────────────────────────────────────────────
58
- // Persona Operations (delegated to EmaClientV2)
59
- // ─────────────────────────────────────────────────────────────────────────────
103
+ // ═══════════════════════════════════════════════════════════════════════════
104
+ // Persona Operations
105
+ // ═══════════════════════════════════════════════════════════════════════════
60
106
  /**
61
107
  * List all personas for the current tenant
62
108
  */
63
109
  async getPersonasForTenant() {
64
110
  const personas = await this.client.listPersonas();
65
- // Map generated PersonaDto to legacy PersonaDTO interface
66
111
  return personas.map(p => this.mapToLegacyPersona(p));
67
112
  }
68
- /**
69
- * Alias for getPersonasForTenant
70
- */
113
+ /** Alias for getPersonasForTenant */
71
114
  async getAiEmployeesForTenant() {
72
115
  return this.getPersonasForTenant();
73
116
  }
74
117
  /**
75
118
  * Get a single persona by ID
76
- *
77
- * Returns null for non-existent personas (matching legacy EmaClient behavior).
78
- * The API may return 404 or 422 for invalid IDs - both are treated as "not found".
119
+ * Returns null for non-existent personas.
79
120
  */
80
121
  async getPersonaById(personaId) {
81
122
  try {
@@ -85,7 +126,6 @@ export class EmaClientAdapter {
85
126
  return this.mapToLegacyPersona(persona);
86
127
  }
87
128
  catch (error) {
88
- // Handle 404 (not found) and 422 (invalid ID format) as null
89
129
  if (error instanceof Error) {
90
130
  const apiError = error;
91
131
  if (apiError.statusCode === 404 || apiError.statusCode === 422) {
@@ -95,26 +135,220 @@ export class EmaClientAdapter {
95
135
  throw error;
96
136
  }
97
137
  }
98
- // ─────────────────────────────────────────────────────────────────────────────
99
- // Template Operations (delegated to EmaClientV2)
100
- // ─────────────────────────────────────────────────────────────────────────────
138
+ /**
139
+ * Create a new AI Employee
140
+ */
141
+ async createAiEmployee(req) {
142
+ // persona_template_id is required by the API
143
+ const templateId = req.persona_template_id ?? req.template_id;
144
+ if (!templateId) {
145
+ throw new Error('persona_template_id or template_id is required');
146
+ }
147
+ const result = await this.client.createAiEmployee({
148
+ name: req.name,
149
+ persona_template_id: templateId,
150
+ description: req.description,
151
+ });
152
+ return {
153
+ persona_id: result.persona_id ?? '',
154
+ success: !!result.persona_id,
155
+ };
156
+ }
157
+ /**
158
+ * Update an existing AI Employee
159
+ */
160
+ async updateAiEmployee(req) {
161
+ const result = await this.client.updateAiEmployee({
162
+ persona_id: req.persona_id,
163
+ workflow: req.workflow,
164
+ proto_config: req.proto_config,
165
+ name: req.name,
166
+ description: req.description,
167
+ });
168
+ return result;
169
+ }
170
+ /**
171
+ * Delete a persona
172
+ */
173
+ async deletePersona(personaId) {
174
+ return this.client.deletePersona(personaId);
175
+ }
176
+ // ═══════════════════════════════════════════════════════════════════════════
177
+ // Template Operations
178
+ // ═══════════════════════════════════════════════════════════════════════════
101
179
  /**
102
180
  * List all persona templates
103
181
  */
104
182
  async getPersonaTemplates() {
105
183
  const templates = await this.client.listPersonaTemplates();
106
- // Map generated PersonaTemplateDto to legacy PersonaTemplateDTO interface
107
184
  return templates.map(t => this.mapToLegacyTemplate(t));
108
185
  }
109
- // ─────────────────────────────────────────────────────────────────────────────
110
- // Private Helper Methods
111
- // ─────────────────────────────────────────────────────────────────────────────
186
+ // ═══════════════════════════════════════════════════════════════════════════
187
+ // Action Operations
188
+ // ═══════════════════════════════════════════════════════════════════════════
189
+ /**
190
+ * List all available actions
191
+ */
192
+ async listActions() {
193
+ const response = await this.client.listActions();
194
+ // Map gRPC ActionType to ActionDTO
195
+ return (response.actions ?? []).map(a => {
196
+ const typeName = a.typeName;
197
+ const name = typeName?.name?.name ?? '';
198
+ return {
199
+ id: name,
200
+ name: name,
201
+ displayName: a.displayName ?? name,
202
+ description: a.description ?? '',
203
+ category: String(a.category ?? ''),
204
+ typeName: typeName,
205
+ // inputs/outputs are complex proto maps - keep as raw for now
206
+ inputs: a.inputs,
207
+ outputs: a.outputs,
208
+ };
209
+ });
210
+ }
211
+ /** Alias for listActions */
212
+ async listAgents() {
213
+ return this.listActions();
214
+ }
215
+ /**
216
+ * List actions used in a specific workflow
217
+ */
218
+ async listActionsFromWorkflow(workflowId) {
219
+ const response = await this.client.listActionsFromWorkflow(workflowId);
220
+ // Map ActionType[] to string[] of action names
221
+ return (response.actions ?? []).map(a => {
222
+ const typeName = a.typeName;
223
+ return typeName?.name?.name ?? '';
224
+ }).filter(Boolean);
225
+ }
226
+ /** Alias for listActionsFromWorkflow */
227
+ async listAgentsFromWorkflow(workflowId) {
228
+ return this.listActionsFromWorkflow(workflowId);
229
+ }
230
+ // ═══════════════════════════════════════════════════════════════════════════
231
+ // Dashboard Operations
232
+ // ═══════════════════════════════════════════════════════════════════════════
233
+ /**
234
+ * Get dashboard schema
235
+ */
236
+ async getDashboardSchema(personaId, _dashboardId) {
237
+ const response = await this.client.getDashboardSchema();
238
+ // Extract columns from proto response
239
+ const schema = response;
240
+ return {
241
+ columns: schema.schema?.columns ?? [],
242
+ };
243
+ }
244
+ // ═══════════════════════════════════════════════════════════════════════════
245
+ // Data Source Operations
246
+ // ═══════════════════════════════════════════════════════════════════════════
247
+ /**
248
+ * Upload a data source file
249
+ */
250
+ async uploadDataSource(personaId, fileContent, filename, opts) {
251
+ return this.client.uploadDataSource(personaId, fileContent, filename, opts);
252
+ }
253
+ /**
254
+ * Delete a data source file
255
+ */
256
+ async deleteDataSource(personaId, fileId) {
257
+ return this.client.deleteDataSource(personaId, fileId);
258
+ }
259
+ /**
260
+ * List data source files
261
+ */
262
+ async listDataSourceFiles(personaId, opts) {
263
+ const response = await this.client.listDataSourceFiles(personaId, opts);
264
+ // Map proto ContentNode to simplified file object
265
+ const nodes = response.nodes ?? [];
266
+ return {
267
+ files: nodes.map(n => ({
268
+ id: n.id ?? '',
269
+ name: n.name ?? '',
270
+ size: n.size ?? 0,
271
+ uploadedAt: n.createdAt ?? '',
272
+ })),
273
+ };
274
+ }
275
+ /**
276
+ * Get data source aggregates
277
+ */
278
+ async getDataSourceAggregates(personaId, widgetName) {
279
+ const response = await this.client.getDataSourceAggregates(personaId, widgetName);
280
+ // Map proto aggregates
281
+ const agg = response;
282
+ const aggregates = agg.aggregates ?? [];
283
+ let total = 0, processed = 0, failed = 0;
284
+ for (const a of aggregates) {
285
+ total += a.count ?? 0;
286
+ if (a.status === 'processed' || a.status === 'PROCESSED')
287
+ processed += a.count ?? 0;
288
+ if (a.status === 'failed' || a.status === 'FAILED')
289
+ failed += a.count ?? 0;
290
+ }
291
+ return { total, processed, failed };
292
+ }
293
+ /**
294
+ * Replicate data between personas
295
+ */
296
+ async replicateData(sourcePersonaId, targetPersonaId, widgetMappings) {
297
+ const response = await this.client.replicateData(sourcePersonaId, targetPersonaId, widgetMappings);
298
+ const rep = response;
299
+ return { requestId: rep.requestId ?? '' };
300
+ }
301
+ /**
302
+ * Get replication status
303
+ */
304
+ async getReplicationStatus(requestId, _personaId) {
305
+ const response = await this.client.getReplicationStatus(requestId);
306
+ const rep = response;
307
+ const statusMap = {
308
+ 0: 'unknown',
309
+ 1: 'in_progress',
310
+ 2: 'completed',
311
+ 3: 'failed',
312
+ };
313
+ return {
314
+ status: statusMap[rep.status ?? 0] ?? 'unknown',
315
+ progress: rep.progress,
316
+ failedReason: rep.failedReason,
317
+ };
318
+ }
319
+ /**
320
+ * Wait for replication to complete
321
+ */
322
+ async waitForReplication(requestId, personaId, opts) {
323
+ return this.client.waitForReplication(requestId, personaId, opts);
324
+ }
325
+ // ═══════════════════════════════════════════════════════════════════════════
326
+ // Sync Metadata Operations
327
+ // ═══════════════════════════════════════════════════════════════════════════
328
+ /**
329
+ * Get sync metadata from a persona
330
+ */
331
+ getSyncMetadata(persona) {
332
+ // Sync metadata is stored in custom_metadata
333
+ const customMeta = persona.custom_metadata;
334
+ if (!customMeta)
335
+ return null;
336
+ const syncData = customMeta['ema_mcp_sync'];
337
+ if (!syncData || typeof syncData !== 'object')
338
+ return null;
339
+ return syncData;
340
+ }
341
+ /**
342
+ * Check if persona was synced from another environment
343
+ */
344
+ isSyncedPersona(persona) {
345
+ return this.getSyncMetadata(persona) !== null;
346
+ }
347
+ // ═══════════════════════════════════════════════════════════════════════════
348
+ // Private Helpers
349
+ // ═══════════════════════════════════════════════════════════════════════════
112
350
  /**
113
351
  * Map generated PersonaDto to legacy PersonaDTO interface
114
- *
115
- * The generated type has nullable fields (string | null | undefined)
116
- * while the legacy type expects (string | undefined). This method
117
- * normalizes null to undefined for compatibility.
118
352
  */
119
353
  mapToLegacyPersona(p) {
120
354
  return {
@@ -122,10 +356,10 @@ export class EmaClientAdapter {
122
356
  name: this.nullToUndefined(p.name),
123
357
  description: this.nullToUndefined(p.description),
124
358
  template_id: this.nullToUndefined(p.template_id),
125
- templateId: this.nullToUndefined(p.template_id), // legacy alias
359
+ templateId: this.nullToUndefined(p.template_id),
126
360
  workflow_id: this.nullToUndefined(p.workflow_id),
127
361
  workflow_def: p.workflow_def,
128
- workflow: p.workflow_def, // legacy alias
362
+ workflow: p.workflow_def,
129
363
  workflow_interface: p.workflow_interface,
130
364
  proto_config: p.proto_config,
131
365
  status: this.nullToUndefined(p.status),
@@ -137,6 +371,7 @@ export class EmaClientAdapter {
137
371
  workflow_dashboard_id: this.nullToUndefined(p.workflow_dashboard_id),
138
372
  persona_interaction_type: this.nullToUndefined(p.persona_interaction_type),
139
373
  embedding_enabled: p.embedding_enabled,
374
+ custom_metadata: p.custom_metadata,
140
375
  };
141
376
  }
142
377
  /**
@@ -393,27 +393,43 @@ export class EmaClient {
393
393
  const existingPersona = await this.getPersonaById(req.persona_id);
394
394
  const existingWorkflow = existingPersona?.workflow_def;
395
395
  const existingWfName = existingWorkflow?.workflowName;
396
+ // Deep clone workflow for modifications
397
+ const fixedWorkflow = JSON.parse(JSON.stringify(req.workflow));
396
398
  if (existingWfName?.name) {
397
- // Deep clone workflow and fix the namespace
398
- const fixedWorkflow = JSON.parse(JSON.stringify(req.workflow));
399
+ // Copy namespace from existing workflow
399
400
  fixedWorkflow.workflowName = existingWfName;
400
- // Also fix results format if needed - API expects "<actionName>.<outputName>" keys
401
- const results = fixedWorkflow.results;
402
- if (results) {
403
- const fixedResults = {};
404
- for (const [key, value] of Object.entries(results)) {
405
- if (value.actionName && value.outputName) {
406
- const correctKey = `${value.actionName}.${value.outputName}`;
407
- fixedResults[correctKey] = value;
408
- }
409
- }
410
- fixedWorkflow.results = fixedResults;
401
+ if (opts?.verbose || process.env.EMA_DEBUG) {
402
+ console.error("[EmaClient] Copied workflow namespace from existing:", existingWfName.name);
411
403
  }
412
- finalReq = { ...req, workflow: fixedWorkflow };
404
+ }
405
+ else {
406
+ // CRITICAL FIX: Generate a valid namespace for personas without existing workflows.
407
+ // The API requires workflowName to match the persona's namespace format.
408
+ // Format: ["ema", "personas", "<persona_id>"] with name "workflow"
409
+ const generatedWfName = {
410
+ name: {
411
+ namespaces: ["ema", "personas", req.persona_id],
412
+ name: "workflow",
413
+ },
414
+ };
415
+ fixedWorkflow.workflowName = generatedWfName;
413
416
  if (opts?.verbose || process.env.EMA_DEBUG) {
414
- console.error("[EmaClient] Fixed workflow namespace:", existingWfName.name);
417
+ console.error("[EmaClient] Generated workflow namespace for new persona:", generatedWfName.name);
415
418
  }
416
419
  }
420
+ // Also fix results format if needed - API expects "<actionName>.<outputName>" keys
421
+ const results = fixedWorkflow.results;
422
+ if (results) {
423
+ const fixedResults = {};
424
+ for (const [key, value] of Object.entries(results)) {
425
+ if (value.actionName && value.outputName) {
426
+ const correctKey = `${value.actionName}.${value.outputName}`;
427
+ fixedResults[correctKey] = value;
428
+ }
429
+ }
430
+ fixedWorkflow.results = fixedResults;
431
+ }
432
+ finalReq = { ...req, workflow: fixedWorkflow };
417
433
  }
418
434
  // Debug logging for troubleshooting
419
435
  if (opts?.verbose || process.env.EMA_DEBUG) {
@@ -534,10 +550,23 @@ export class EmaClient {
534
550
  });
535
551
  if (!resp.ok) {
536
552
  const body = await resp.text();
553
+ // Try to extract error details from response
554
+ let errorDetail = "";
555
+ try {
556
+ const parsed = JSON.parse(body);
557
+ errorDetail = parsed.error || parsed.message || parsed.detail || "";
558
+ }
559
+ catch {
560
+ errorDetail = body.slice(0, 200);
561
+ }
562
+ // Build informative error message
563
+ const message = errorDetail
564
+ ? `create_ai_employee failed (${this.env.name}): ${errorDetail}`
565
+ : `create_ai_employee failed (${this.env.name}) - status ${resp.status}`;
537
566
  throw new EmaApiError({
538
567
  statusCode: resp.status,
539
568
  body,
540
- message: `create_ai_employee failed (${this.env.name})`,
569
+ message,
541
570
  });
542
571
  }
543
572
  return (await resp.json());