@ema.co/mcp-toolkit 2026.2.13 → 2026.2.23-1

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 (67) hide show
  1. package/.context/public/guides/ema-user-guide.md +12 -16
  2. package/.context/public/guides/mcp-tools-guide.md +203 -334
  3. package/dist/cli/index.js +2 -2
  4. package/dist/mcp/domain/loop-detection.js +89 -0
  5. package/dist/mcp/domain/sanitizer.js +1 -1
  6. package/dist/mcp/domain/structural-rules.js +4 -5
  7. package/dist/mcp/domain/validation-rules.js +5 -5
  8. package/dist/mcp/domain/workflow-graph.js +3 -5
  9. package/dist/mcp/domain/workflow-path-enumerator.js +7 -4
  10. package/dist/mcp/guidance.js +62 -29
  11. package/dist/mcp/handlers/debug/adapter.js +15 -0
  12. package/dist/mcp/handlers/debug/formatters.js +282 -0
  13. package/dist/mcp/handlers/debug/index.js +133 -0
  14. package/dist/mcp/handlers/demo/adapter.js +180 -0
  15. package/dist/mcp/handlers/env/config.js +2 -2
  16. package/dist/mcp/handlers/feedback/index.js +1 -1
  17. package/dist/mcp/handlers/index.js +0 -1
  18. package/dist/mcp/handlers/persona/adapter.js +135 -0
  19. package/dist/mcp/handlers/persona/index.js +237 -8
  20. package/dist/mcp/handlers/persona/schema.js +27 -0
  21. package/dist/mcp/handlers/reference/index.js +6 -4
  22. package/dist/mcp/handlers/sync/adapter.js +200 -0
  23. package/dist/mcp/handlers/workflow/adapter.js +174 -0
  24. package/dist/mcp/handlers/workflow/fix.js +11 -12
  25. package/dist/mcp/handlers/workflow/index.js +12 -40
  26. package/dist/mcp/handlers/workflow/validation.js +1 -1
  27. package/dist/mcp/knowledge-guidance-topics.js +615 -0
  28. package/dist/mcp/knowledge-types.js +7 -0
  29. package/dist/mcp/knowledge.js +75 -1403
  30. package/dist/mcp/resources-dynamic.js +2395 -0
  31. package/dist/mcp/resources-validation.js +408 -0
  32. package/dist/mcp/resources.js +72 -2508
  33. package/dist/mcp/server.js +69 -2825
  34. package/dist/mcp/tools.js +106 -5
  35. package/dist/sdk/client-adapter.js +265 -24
  36. package/dist/sdk/ema-client.js +100 -9
  37. package/dist/sdk/generated/agent-catalog.js +615 -0
  38. package/dist/sdk/generated/api-client/client/client.gen.js +3 -3
  39. package/dist/sdk/generated/api-client/client/index.js +5 -5
  40. package/dist/sdk/generated/api-client/client/utils.gen.js +4 -4
  41. package/dist/sdk/generated/api-client/client.gen.js +1 -1
  42. package/dist/sdk/generated/api-client/core/utils.gen.js +1 -1
  43. package/dist/sdk/generated/api-client/index.js +1 -1
  44. package/dist/sdk/generated/api-client/sdk.gen.js +2 -2
  45. package/dist/sdk/generated/well-known-types.js +99 -0
  46. package/dist/sdk/generated/widget-catalog.js +60 -0
  47. package/dist/sdk/grpc-client.js +115 -1
  48. package/dist/sync/sdk.js +2 -2
  49. package/dist/sync.js +4 -3
  50. package/docs/README.md +17 -9
  51. package/package.json +4 -3
  52. package/.context/public/guides/dashboard-operations.md +0 -349
  53. package/.context/public/guides/email-patterns.md +0 -125
  54. package/.context/public/guides/workflow-builder-patterns.md +0 -708
  55. package/dist/mcp/domain/intent-architect.js +0 -914
  56. package/dist/mcp/domain/quality-gates.js +0 -110
  57. package/dist/mcp/domain/workflow-execution-analyzer.js +0 -412
  58. package/dist/mcp/domain/workflow-intent.js +0 -1806
  59. package/dist/mcp/domain/workflow-merge.js +0 -449
  60. package/dist/mcp/domain/workflow-tracer.js +0 -648
  61. package/dist/mcp/domain/workflow-transformer.js +0 -742
  62. package/dist/mcp/handlers/knowledge/index.js +0 -54
  63. package/dist/mcp/handlers/persona/intent.js +0 -141
  64. package/dist/mcp/handlers/workflow/analyze.js +0 -119
  65. package/dist/mcp/handlers/workflow/compare.js +0 -70
  66. package/dist/mcp/handlers/workflow/generate.js +0 -384
  67. package/dist/mcp/handlers-consolidated.js +0 -333
@@ -1,384 +0,0 @@
1
- /**
2
- * Workflow Generate Handler
3
- *
4
- * Handles greenfield workflow generation from natural language input.
5
- * Supports:
6
- * - Simple workflows (direct generation)
7
- * - Complex workflows (returns LLM prompt for agent to complete)
8
- * - Intent Architect integration for qualification questions
9
- * - Auto-deploy to new or existing personas
10
- */
11
- import { parseInput, intentToSpec, generateWorkflow } from "../../domain/workflow-intent.js";
12
- import { compileWorkflow } from "../../domain/workflow-generator.js";
13
- // Detection removed - LLM analyzes with rules from ema://rules/*
14
- import { runIntentArchitect } from "../../domain/intent-architect.js";
15
- import { ensureActionRegistry } from "../../../sdk/action-registry.js";
16
- import { ensureSchemaRegistry, validateWorkflowSpec, generateActionCatalogForLLM } from "../../domain/workflow-validator.js";
17
- import { getTemplates, normalizeTriggerType } from "../../handlers/index.js";
18
- import { validateSearchDataSourceConsistency, validationToHandlerResult } from "./validation.js";
19
- /**
20
- * Get persona widget context for workflow bindings
21
- */
22
- async function getPersonaWidgets(personaId, client) {
23
- if (!personaId)
24
- return [];
25
- try {
26
- const persona = await client.getPersonaById(personaId);
27
- const protoConfig = persona?.proto_config;
28
- if (protoConfig?.widgets) {
29
- return protoConfig.widgets
30
- .filter(w => typeof w.name === "string" && w.name.trim().length > 0)
31
- .map(w => ({
32
- name: w.name,
33
- type: String(w.type ?? "unknown"),
34
- title: w.title,
35
- }));
36
- }
37
- }
38
- catch {
39
- // Persona not found or no widgets - continue without context
40
- }
41
- return [];
42
- }
43
- /**
44
- * Enhance an LLM prompt with action catalog and widget context
45
- */
46
- function enhancePromptWithContext(prompt, schemaRegistry, personaWidgets) {
47
- let enhancedPrompt = prompt;
48
- let availableActions = [];
49
- let availableTemplates = [];
50
- if (!prompt) {
51
- return { prompt: enhancedPrompt, availableActions, availableTemplates };
52
- }
53
- let systemAdditions = "";
54
- // Add action catalog if available
55
- if (schemaRegistry) {
56
- const actionCatalog = generateActionCatalogForLLM(schemaRegistry);
57
- systemAdditions += "\n\n## Available Actions\n" + actionCatalog;
58
- availableActions = schemaRegistry.getAllActions().map((a) => a.name);
59
- availableTemplates = schemaRegistry.getAllTemplates().map((t) => ({ id: t.id, name: t.name, type: t.type }));
60
- }
61
- // Add persona widget context if available
62
- if (personaWidgets.length > 0) {
63
- systemAdditions += "\n\n## Available Persona Widgets\n";
64
- systemAdditions += "Use these exact widget names in workflow bindings (widgetName field):\n";
65
- for (const w of personaWidgets) {
66
- systemAdditions += `- \`${w.name}\` (${w.type})${w.title ? ` - "${w.title}"` : ""}\n`;
67
- }
68
- }
69
- if (systemAdditions) {
70
- enhancedPrompt = {
71
- system: prompt.system + systemAdditions,
72
- user: prompt.user,
73
- };
74
- }
75
- return { prompt: enhancedPrompt, availableActions, availableTemplates };
76
- }
77
- /**
78
- * Deploy workflow to a new persona (greenfield creation)
79
- */
80
- async function deployToNewPersona(args, client, compiled, actionRegistry, getTemplateId) {
81
- const personaName = args.name;
82
- if (!personaName) {
83
- return { success: false, hint: "Provide name to create new persona, or persona_id to deploy to existing persona" };
84
- }
85
- const personaType = args.type || "chat";
86
- // Dynamic template lookup
87
- const templates = await getTemplates(client);
88
- const matchingTemplate = templates.find(t => normalizeTriggerType(t.trigger_type) === personaType.toLowerCase());
89
- const templateFromRegistry = actionRegistry.getTemplateForType(personaType);
90
- const templateId = getTemplateId?.(personaType) || templateFromRegistry?.id || matchingTemplate?.id;
91
- if (!templateId) {
92
- const availableTypes = [...new Set(templates.map(t => normalizeTriggerType(t.trigger_type)).filter(Boolean))];
93
- return {
94
- success: false,
95
- error: `No template found for type "${personaType}".`,
96
- hint: `Available types: ${availableTypes.join(", ")}. Provide template_id directly or use one of the available types.`,
97
- };
98
- }
99
- // Step 1: Create the persona from template
100
- const createResult = await client.createAiEmployee({
101
- name: personaName,
102
- description: args.description,
103
- template_id: templateId,
104
- });
105
- const newPersonaId = createResult.persona_id ?? createResult.id;
106
- if (!newPersonaId) {
107
- return { success: false, error: "Failed to create persona: no ID returned" };
108
- }
109
- // Step 2: Fetch the newly created persona
110
- const newPersona = await client.getPersonaById(newPersonaId);
111
- if (!newPersona) {
112
- return { success: false, error: `Failed to fetch newly created persona: ${newPersonaId}` };
113
- }
114
- // Step 3: Merge proto_config
115
- const existingProtoConfig = (newPersona.proto_config ?? {});
116
- const generatedProtoConfig = args.proto_config || compiled.proto_config || {};
117
- const existingWidgets = (existingProtoConfig.widgets ?? []);
118
- const generatedWidgets = (generatedProtoConfig.widgets ?? []);
119
- const widgetMap = new Map();
120
- for (const w of existingWidgets) {
121
- if (typeof w.name === "string" && w.name.trim().length > 0) {
122
- widgetMap.set(w.name, w);
123
- }
124
- }
125
- for (const genWidget of generatedWidgets) {
126
- const widgetName = genWidget.name;
127
- if (typeof widgetName === "string" && widgetName.trim().length > 0) {
128
- const existing = widgetMap.get(widgetName);
129
- if (existing) {
130
- const merged = { ...existing };
131
- if (genWidget[widgetName]) {
132
- merged[widgetName] = { ...(existing[widgetName] || {}), ...genWidget[widgetName] };
133
- }
134
- widgetMap.set(widgetName, merged);
135
- }
136
- else {
137
- widgetMap.set(widgetName, genWidget);
138
- }
139
- }
140
- }
141
- const mergedProtoConfig = {
142
- ...existingProtoConfig,
143
- widgets: Array.from(widgetMap.values()),
144
- };
145
- // Step 4: Validate and Deploy workflow
146
- // Validate search/data source consistency before deployment
147
- const validation = await validateSearchDataSourceConsistency(compiled.workflow_def, newPersonaId, client);
148
- if (!validation.valid) {
149
- // Return validation error but keep persona (user can fix and retry)
150
- return {
151
- success: false,
152
- personaId: newPersonaId,
153
- personaName,
154
- validationError: validation.error,
155
- _fix: validation._fix,
156
- workflowAttempted: compiled.workflow_def,
157
- };
158
- }
159
- // NOTE: The SDK's updateAiEmployee() handles workflowName namespace automatically.
160
- // It will copy from existing workflow if present, or generate a valid namespace if not.
161
- // It also fixes the results format. No need to manually manipulate these here.
162
- try {
163
- await client.updateAiEmployee({
164
- persona_id: newPersonaId,
165
- workflow: compiled.workflow_def,
166
- proto_config: mergedProtoConfig,
167
- }, { verbose: true });
168
- return { success: true, personaId: newPersonaId, personaName };
169
- }
170
- catch (deployError) {
171
- const errMsg = deployError instanceof Error ? deployError.message : String(deployError);
172
- // Still set proto_config
173
- await client.updateAiEmployee({
174
- persona_id: newPersonaId,
175
- proto_config: mergedProtoConfig,
176
- });
177
- return {
178
- success: false,
179
- personaId: newPersonaId,
180
- personaName,
181
- workflowDeployError: errMsg,
182
- workflowAttempted: compiled.workflow_def,
183
- };
184
- }
185
- }
186
- /**
187
- * Handle workflow generate mode
188
- */
189
- export async function handleWorkflowGenerate(args, client, getTemplateId) {
190
- const personaId = args.persona_id;
191
- const input = args.input;
192
- const preview = args.preview !== false;
193
- if (!input) {
194
- return { error: "input required for generate mode" };
195
- }
196
- // Load action registry
197
- const actionRegistry = await ensureActionRegistry(client);
198
- // Parse input
199
- const parseResult = parseInput(input);
200
- if (!parseResult.validation.complete) {
201
- return {
202
- status: "incomplete",
203
- input_type: parseResult.input_type,
204
- missing: parseResult.validation.missing,
205
- questions: parseResult.validation.questions,
206
- };
207
- }
208
- // Override persona_type from args.type if provided
209
- if (args.type) {
210
- parseResult.intent.persona_type = args.type;
211
- }
212
- // Load schema registry
213
- let schemaRegistry = null;
214
- try {
215
- schemaRegistry = await ensureSchemaRegistry(client);
216
- }
217
- catch {
218
- // Schema registry unavailable - skip API validation
219
- }
220
- // Get persona widgets for context
221
- const personaWidgets = await getPersonaWidgets(personaId, client);
222
- // Run Intent Architect for complexity assessment
223
- const maxComplexity = args.max_complexity || undefined;
224
- const architectResult = runIntentArchitect(input, {
225
- persona_type: parseResult.intent.persona_type,
226
- available_integrations: schemaRegistry?.getAllActions().slice(0, 20).map((a) => a.displayName || a.name),
227
- }, { max_complexity: maxComplexity });
228
- // For moderate/complex: return Intent Architect result
229
- if (!architectResult.strategy.can_proceed) {
230
- const enhanced = enhancePromptWithContext(architectResult.prompt_package, schemaRegistry, personaWidgets);
231
- return {
232
- status: "needs_intent_architect",
233
- available_widgets: personaWidgets.length > 0 ? personaWidgets : undefined,
234
- assessment: architectResult.assessment,
235
- strategy: architectResult.strategy,
236
- questions: architectResult.questions,
237
- llm_prompt: enhanced.prompt,
238
- hint: architectResult.strategy.next_step,
239
- reason: architectResult.legacy?.signals.reason,
240
- complexity: architectResult.legacy?.complexity,
241
- approach: architectResult.strategy.approach,
242
- gates_to_ask: architectResult.strategy.gates_to_ask,
243
- fallback_spec: intentToSpec(parseResult.intent),
244
- available_actions: enhanced.availableActions,
245
- available_templates: enhanced.availableTemplates,
246
- };
247
- }
248
- // SIMPLE complexity: generate directly
249
- const genResult = generateWorkflow(parseResult.intent);
250
- if (genResult.needs_llm) {
251
- const enhanced = enhancePromptWithContext(genResult.llm_prompt, schemaRegistry, personaWidgets);
252
- return {
253
- status: "needs_llm_generation",
254
- reason: genResult.reason,
255
- complexity: genResult.complexity,
256
- llm_prompt: enhanced.prompt,
257
- hint: "Send llm_prompt to an LLM, then call persona(workflow_def=<parsed_response>) to deploy.",
258
- fallback_spec: intentToSpec(parseResult.intent),
259
- available_actions: enhanced.availableActions,
260
- available_templates: enhanced.availableTemplates,
261
- available_widgets: personaWidgets.length > 0 ? personaWidgets : undefined,
262
- };
263
- }
264
- // Simple workflow - compile
265
- const spec = genResult.spec;
266
- if (args.name) {
267
- spec.name = args.name;
268
- }
269
- if (args.description) {
270
- spec.description = args.description;
271
- }
272
- // Validate spec before compiling
273
- let specValidation = null;
274
- if (schemaRegistry) {
275
- specValidation = validateWorkflowSpec(spec, schemaRegistry);
276
- if (!specValidation.valid) {
277
- return {
278
- status: "validation_failed",
279
- errors: specValidation.errors,
280
- warnings: specValidation.warnings,
281
- action_coverage: specValidation.action_coverage,
282
- hint: "Fix the validation errors and try again. Unknown actions may need to be checked against ListActions.",
283
- spec_attempted: spec,
284
- };
285
- }
286
- }
287
- const compiled = compileWorkflow(spec, { registry: actionRegistry });
288
- const result = {
289
- mode: "generate",
290
- status: preview ? "preview" : "deployed",
291
- workflow_def: compiled.workflow_def,
292
- proto_config: compiled.proto_config,
293
- validation: parseResult.validation,
294
- // LLM should analyze with ema://rules/anti-patterns
295
- _analysis_hint: "Use ema://rules/anti-patterns to check for issues",
296
- };
297
- if (specValidation) {
298
- result.api_validation = {
299
- valid: specValidation.valid,
300
- warnings: specValidation.warnings,
301
- action_coverage: specValidation.action_coverage,
302
- };
303
- }
304
- // Deploy if not preview
305
- if (!preview && personaId) {
306
- const persona = await client.getPersonaById(personaId);
307
- if (!persona) {
308
- return { error: `Persona not found: ${personaId}` };
309
- }
310
- // Validate search/data source consistency before deployment
311
- const validation = await validateSearchDataSourceConsistency(compiled.workflow_def, personaId, client);
312
- if (!validation.valid) {
313
- return validationToHandlerResult(validation, {
314
- mode: "generate",
315
- persona_id: personaId,
316
- persona_name: persona.name,
317
- workflow_preview: compiled.workflow_def,
318
- });
319
- }
320
- await client.updateAiEmployee({
321
- persona_id: personaId,
322
- workflow: compiled.workflow_def,
323
- proto_config: args.proto_config || compiled.proto_config || persona.proto_config,
324
- });
325
- result.deployed_to = { persona_id: personaId, persona_name: persona.name };
326
- }
327
- else if (!preview && !personaId) {
328
- // Greenfield: create new persona
329
- const deployResult = await deployToNewPersona(args, client, compiled, actionRegistry, getTemplateId);
330
- if (deployResult.validationError) {
331
- // Validation failed - persona created but workflow not deployed
332
- return {
333
- error: deployResult.validationError,
334
- _fix: deployResult._fix,
335
- persona_id: deployResult.personaId,
336
- persona_name: deployResult.personaName,
337
- persona_created: true,
338
- workflow_deployed: false,
339
- workflow_attempted: deployResult.workflowAttempted,
340
- hint: "Persona created, but workflow validation failed. Fix the issue and retry deployment.",
341
- };
342
- }
343
- else if (deployResult.workflowDeployError) {
344
- result.workflow_deploy_error = deployResult.workflowDeployError;
345
- result.workflow_attempted = deployResult.workflowAttempted;
346
- result.status = "partial";
347
- result.hint = "Persona created, config set, but workflow deploy failed. Check workflow_attempted for details.";
348
- result.deployed_to = {
349
- persona_id: deployResult.personaId,
350
- persona_name: deployResult.personaName,
351
- created: true,
352
- workflow_deployed: false,
353
- };
354
- return result;
355
- }
356
- else if (deployResult.success) {
357
- result.deployed_to = {
358
- persona_id: deployResult.personaId,
359
- persona_name: deployResult.personaName,
360
- created: true,
361
- };
362
- result.status = "deployed";
363
- result.next_steps = [
364
- `Persona "${deployResult.personaName}" created with template workflow.`,
365
- `To customize: workflow(mode="get", persona_id="${deployResult.personaId}") → modify workflow_def → workflow(mode="deploy", ...)`,
366
- ];
367
- }
368
- else if (deployResult.error) {
369
- return { error: deployResult.error, hint: deployResult.hint };
370
- }
371
- else {
372
- result.hint = deployResult.hint;
373
- }
374
- }
375
- if (preview) {
376
- result.next_steps = [
377
- "Review the generated workflow_def",
378
- personaId
379
- ? `Deploy with: workflow(mode="get", persona_id="${personaId}") → workflow(mode="deploy", persona_id="${personaId}", base_fingerprint="<fingerprint>", workflow_def={...}) (or workflow_def_path="/path/to/wf.json")`
380
- : "Create persona first: persona(method='create', name='...', type='...')",
381
- ];
382
- }
383
- return result;
384
- }