@ema.co/mcp-toolkit 0.2.3 → 1.4.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.
@@ -4,6 +4,8 @@
4
4
  * Each handler dispatches based on mode/flags following Unix CLI patterns.
5
5
  */
6
6
  import { fingerprintPersona } from "../sync.js";
7
+ import { createVersionStorage } from "../sdk/version-storage.js";
8
+ import { createVersionPolicyEngine } from "../sdk/version-policy.js";
7
9
  import { AGENT_CATALOG, WORKFLOW_PATTERNS, QUALIFYING_QUESTIONS, PLATFORM_CONCEPTS, WORKFLOW_EXECUTION_MODEL, COMMON_MISTAKES, DEBUG_CHECKLIST, GUIDANCE_TOPICS, VOICE_PERSONA_TEMPLATE, getAgentByName, getWidgetsForPersonaType, checkTypeCompatibility, getQualifyingQuestionsByCategory, getRequiredQualifyingQuestions, getConceptByTerm, suggestAgentsForUseCase, validateWorkflowPrompt, detectWorkflowIssues, validateWorkflowConnections, suggestWorkflowFixes, } from "../sdk/knowledge.js";
8
10
  import { compileWorkflow } from "../sdk/workflow-generator.js";
9
11
  import { parseInput, intentToSpec } from "../sdk/workflow-intent.js";
@@ -23,7 +25,7 @@ export async function handleEnv(_args, getEnvironments) {
23
25
  // ═══════════════════════════════════════════════════════════════════════════
24
26
  // PERSONA Handler
25
27
  // ═══════════════════════════════════════════════════════════════════════════
26
- export async function handlePersona(args, client, getTemplateId, createClientForEnv) {
28
+ export async function handlePersona(args, client, getTemplateId, createClientForEnv, versionContext) {
27
29
  const id = args.id;
28
30
  const identifier = args.identifier; // deprecated alias
29
31
  const idOrName = id ?? identifier;
@@ -171,6 +173,251 @@ export async function handlePersona(args, client, getTemplateId, createClientFor
171
173
  count: templates.size,
172
174
  };
173
175
  }
176
+ // ─────────────── Version Management Modes ───────────────
177
+ case "version_create": {
178
+ if (!idOrName) {
179
+ return { error: "id required for version_create mode" };
180
+ }
181
+ if (!versionContext) {
182
+ return { error: "Version tracking not configured. Provide workspaceRoot in context." };
183
+ }
184
+ const persona = await resolvePersona(client, idOrName);
185
+ if (!persona) {
186
+ return { error: `Persona not found: ${idOrName}` };
187
+ }
188
+ // Fetch full persona with workflow
189
+ const fullPersona = await client.getPersonaById(persona.id);
190
+ if (!fullPersona) {
191
+ return { error: `Could not fetch full persona: ${persona.id}` };
192
+ }
193
+ const storage = createVersionStorage(versionContext.workspaceRoot);
194
+ const engine = createVersionPolicyEngine(storage);
195
+ const result = engine.forceCreateVersion(fullPersona, {
196
+ environment: versionContext.environment,
197
+ tenant_id: versionContext.tenant_id,
198
+ message: args.message,
199
+ created_by: "mcp-toolkit",
200
+ });
201
+ if (!result.created || !result.version) {
202
+ return { error: result.reason };
203
+ }
204
+ return {
205
+ success: true,
206
+ version: {
207
+ id: result.version.id,
208
+ version_number: result.version.version_number,
209
+ version_name: result.version.version_name,
210
+ content_hash: result.version.content_hash,
211
+ created_at: result.version.created_at,
212
+ message: result.version.message,
213
+ },
214
+ changes_from_parent: result.changes_from_parent,
215
+ versions_pruned: result.versions_pruned,
216
+ };
217
+ }
218
+ case "version_list": {
219
+ if (!idOrName) {
220
+ return { error: "id required for version_list mode" };
221
+ }
222
+ if (!versionContext) {
223
+ return { error: "Version tracking not configured. Provide workspaceRoot in context." };
224
+ }
225
+ const persona = await resolvePersona(client, idOrName);
226
+ if (!persona) {
227
+ return { error: `Persona not found: ${idOrName}` };
228
+ }
229
+ const storage = createVersionStorage(versionContext.workspaceRoot);
230
+ const engine = createVersionPolicyEngine(storage);
231
+ const versions = engine.listVersions(persona.id, {
232
+ limit: args.limit,
233
+ });
234
+ return {
235
+ persona_id: persona.id,
236
+ persona_name: persona.name,
237
+ versions,
238
+ count: versions.length,
239
+ };
240
+ }
241
+ case "version_get": {
242
+ if (!idOrName) {
243
+ return { error: "id required for version_get mode" };
244
+ }
245
+ if (!versionContext) {
246
+ return { error: "Version tracking not configured. Provide workspaceRoot in context." };
247
+ }
248
+ const persona = await resolvePersona(client, idOrName);
249
+ if (!persona) {
250
+ return { error: `Persona not found: ${idOrName}` };
251
+ }
252
+ const versionId = args.version ?? "latest";
253
+ const storage = createVersionStorage(versionContext.workspaceRoot);
254
+ const engine = createVersionPolicyEngine(storage);
255
+ const version = engine.getVersion(persona.id, versionId);
256
+ if (!version) {
257
+ return { error: `Version not found: ${versionId}` };
258
+ }
259
+ return {
260
+ persona_id: persona.id,
261
+ persona_name: persona.name,
262
+ version: {
263
+ id: version.id,
264
+ version_number: version.version_number,
265
+ version_name: version.version_name,
266
+ content_hash: version.content_hash,
267
+ created_at: version.created_at,
268
+ created_by: version.created_by,
269
+ trigger: version.trigger,
270
+ message: version.message,
271
+ changes_summary: version.changes_summary,
272
+ },
273
+ snapshot: {
274
+ display_name: version.snapshot.display_name,
275
+ description: version.snapshot.description,
276
+ workflow_id: version.snapshot.workflow_id,
277
+ trigger_type: version.snapshot.trigger_type,
278
+ embedding_enabled: version.snapshot.embedding_enabled,
279
+ has_workflow: !!version.snapshot.workflow_definition,
280
+ has_proto_config: !!version.snapshot.proto_config,
281
+ },
282
+ };
283
+ }
284
+ case "version_compare": {
285
+ if (!idOrName) {
286
+ return { error: "id required for version_compare mode" };
287
+ }
288
+ if (!versionContext) {
289
+ return { error: "Version tracking not configured. Provide workspaceRoot in context." };
290
+ }
291
+ const v1 = args.v1;
292
+ const v2 = args.v2;
293
+ if (!v1 || !v2) {
294
+ return { error: "v1 and v2 required for version_compare mode" };
295
+ }
296
+ const persona = await resolvePersona(client, idOrName);
297
+ if (!persona) {
298
+ return { error: `Persona not found: ${idOrName}` };
299
+ }
300
+ const storage = createVersionStorage(versionContext.workspaceRoot);
301
+ const engine = createVersionPolicyEngine(storage);
302
+ const result = engine.compareVersions(persona.id, v1, v2);
303
+ if (!result.success) {
304
+ return { error: result.error };
305
+ }
306
+ return {
307
+ persona_id: persona.id,
308
+ persona_name: persona.name,
309
+ comparison: {
310
+ v1: result.diff?.v1,
311
+ v2: result.diff?.v2,
312
+ identical: result.diff?.identical,
313
+ changed_fields: result.diff?.changed_fields,
314
+ workflow_diff: result.diff?.workflow_diff,
315
+ },
316
+ summary: result.summary,
317
+ };
318
+ }
319
+ case "version_restore": {
320
+ if (!idOrName) {
321
+ return { error: "id required for version_restore mode" };
322
+ }
323
+ if (!versionContext) {
324
+ return { error: "Version tracking not configured. Provide workspaceRoot in context." };
325
+ }
326
+ const versionId = args.version;
327
+ if (!versionId) {
328
+ return { error: "version required for version_restore mode" };
329
+ }
330
+ const persona = await resolvePersona(client, idOrName);
331
+ if (!persona) {
332
+ return { error: `Persona not found: ${idOrName}` };
333
+ }
334
+ const storage = createVersionStorage(versionContext.workspaceRoot);
335
+ const engine = createVersionPolicyEngine(storage);
336
+ const restoreData = engine.getRestoreData(persona.id, versionId);
337
+ if (!restoreData.success || !restoreData.restore_payload) {
338
+ return { error: restoreData.error ?? "Failed to get restore data" };
339
+ }
340
+ // Create a version snapshot before restoring (audit trail)
341
+ const fullPersona = await client.getPersonaById(persona.id);
342
+ if (fullPersona) {
343
+ engine.forceCreateVersion(fullPersona, {
344
+ environment: versionContext.environment,
345
+ tenant_id: versionContext.tenant_id,
346
+ message: `Before restore to ${restoreData.version?.version_name}`,
347
+ created_by: "mcp-toolkit",
348
+ });
349
+ }
350
+ // Apply the restore
351
+ const payload = restoreData.restore_payload;
352
+ await client.updateAiEmployee({
353
+ persona_id: payload.persona_id,
354
+ name: payload.name,
355
+ description: payload.description,
356
+ proto_config: payload.proto_config,
357
+ workflow: payload.workflow ?? undefined,
358
+ welcome_messages: payload.welcome_messages ?? undefined,
359
+ embedding_enabled: payload.embedding_enabled ?? undefined,
360
+ });
361
+ // Create post-restore version
362
+ const restoredPersona = await client.getPersonaById(persona.id);
363
+ if (restoredPersona) {
364
+ engine.forceCreateVersion(restoredPersona, {
365
+ environment: versionContext.environment,
366
+ tenant_id: versionContext.tenant_id,
367
+ message: `Restored to ${restoreData.version?.version_name}`,
368
+ created_by: "mcp-toolkit",
369
+ });
370
+ }
371
+ return {
372
+ success: true,
373
+ persona_id: persona.id,
374
+ restored_to: {
375
+ version_id: restoreData.version?.id,
376
+ version_name: restoreData.version?.version_name,
377
+ version_number: restoreData.version?.version_number,
378
+ },
379
+ message: `Persona restored to ${restoreData.version?.version_name}`,
380
+ };
381
+ }
382
+ case "version_policy": {
383
+ if (!idOrName) {
384
+ return { error: "id required for version_policy mode" };
385
+ }
386
+ if (!versionContext) {
387
+ return { error: "Version tracking not configured. Provide workspaceRoot in context." };
388
+ }
389
+ const persona = await resolvePersona(client, idOrName);
390
+ if (!persona) {
391
+ return { error: `Persona not found: ${idOrName}` };
392
+ }
393
+ const storage = createVersionStorage(versionContext.workspaceRoot);
394
+ const engine = createVersionPolicyEngine(storage);
395
+ // Check if we're updating or just getting
396
+ const hasUpdates = args.auto_on_deploy !== undefined ||
397
+ args.auto_on_sync !== undefined ||
398
+ args.max_versions !== undefined;
399
+ if (hasUpdates) {
400
+ const updated = engine.updatePolicy(persona.id, {
401
+ auto_version_on_deploy: args.auto_on_deploy,
402
+ auto_version_on_sync: args.auto_on_sync,
403
+ max_versions: args.max_versions,
404
+ });
405
+ return {
406
+ success: true,
407
+ persona_id: persona.id,
408
+ persona_name: persona.name,
409
+ policy: updated,
410
+ message: "Policy updated",
411
+ };
412
+ }
413
+ // Just get current policy
414
+ const policy = engine.getPolicy(persona.id);
415
+ return {
416
+ persona_id: persona.id,
417
+ persona_name: persona.name,
418
+ policy,
419
+ };
420
+ }
174
421
  default:
175
422
  return { error: `Unknown mode: ${effectiveMode}` };
176
423
  }
@@ -315,6 +562,15 @@ export async function handleWorkflow(args, client) {
315
562
  if (!persona) {
316
563
  return { error: `Persona not found: ${personaId}` };
317
564
  }
565
+ // Check persona status - deployment works on disabled personas but we should inform user
566
+ const personaStatus = persona.status?.toLowerCase();
567
+ const isDisabled = personaStatus === "inactive" || personaStatus === "disabled";
568
+ const statusWarnings = [];
569
+ if (isDisabled) {
570
+ statusWarnings.push(`⚠️ Persona "${persona.name}" is currently DISABLED (status: ${persona.status}). ` +
571
+ `Workflow will be deployed but won't be active until enabled. ` +
572
+ `Use persona(mode="update", id="${personaId}", enabled=true) to enable.`);
573
+ }
318
574
  let deployWorkflow = workflowDef || persona.workflow_def;
319
575
  // Validate if requested (default: true)
320
576
  if (args.validate !== false) {
@@ -325,6 +581,7 @@ export async function handleWorkflow(args, client) {
325
581
  error: "Workflow has errors",
326
582
  issues: errors,
327
583
  hint: "Set auto_fix=true to attempt automatic fixes",
584
+ ...(statusWarnings.length > 0 && { status_warnings: statusWarnings }),
328
585
  };
329
586
  }
330
587
  // Auto-fix if enabled
@@ -341,7 +598,13 @@ export async function handleWorkflow(args, client) {
341
598
  return {
342
599
  success: true,
343
600
  persona_id: personaId,
601
+ persona_name: persona.name,
602
+ persona_status: persona.status,
344
603
  deployed: true,
604
+ ...(isDisabled && {
605
+ warning: `Persona is DISABLED - workflow deployed but inactive. Enable with: persona(mode="update", id="${personaId}", enabled=true)`,
606
+ }),
607
+ ...(statusWarnings.length > 0 && { status_warnings: statusWarnings }),
345
608
  };
346
609
  }
347
610
  case "compare": {
@@ -74,9 +74,16 @@ Identify what the user is asking for:
74
74
  | **Data Sources** | ✓/✗ | Knowledge base, web search, APIs? |
75
75
  | **External Tools** | ✓/✗ | ServiceNow, Salesforce, email? |
76
76
  | **Routing Logic** | ✓/✗ | How to handle different intents? |
77
- | **HITL Requirements** | ✓/✗ | When is human approval needed? |
77
+ | **HITL Requirements** | ✓/✗ | DEFAULT: No approval (auto-proceed). Only add if explicitly requested. |
78
78
  | **Fallback Handling** | ✓/✗ | What if no match? |
79
79
 
80
+ #### HITL Policy (Default: Auto-Proceed)
81
+ For external side-effect actions (send_email, create_ticket, update_record):
82
+ - **Default**: Proceed WITHOUT approval gate unless explicitly requested
83
+ - **Add HITL only when**: User says "confirm before", "approval required", "human review"
84
+ - **Skip HITL when**: User says "auto", "directly", "no approval", or specifies direct flow
85
+ - **If ambiguous**: ASK "Should [action] require approval, or auto-proceed?"
86
+
80
87
  #### For BROWNFIELD (extend existing):
81
88
  | Dimension | Status | Missing Info |
82
89
  |-----------|--------|--------------|
@@ -251,7 +258,7 @@ Verify you have answers for:
251
258
  - Intents: ${args.intents || "(to be determined)"}
252
259
  - Data Sources: (ask if not clear from use case)
253
260
  - Actions: (ask if not clear from use case)
254
- - Approvals/HITL: (ask if external actions involved)
261
+ - Approvals/HITL: DEFAULT is no approval (auto-proceed). Only ask if user seems to want approval gates.
255
262
 
256
263
  ### Step 2: Select Pattern + Agents
257
264
  Call \`action(suggest="${args.use_case}")\` to get recommended agents and a suggested workflow pattern.
@@ -1073,6 +1080,8 @@ Where \`<requirements>\` includes:
1073
1080
  ${args.intents ? `- Intents: ${args.intents}` : ""}
1074
1081
  ${args.tools ? `- Tools: ${args.tools}` : ""}
1075
1082
 
1083
+ **HITL Policy**: DEFAULT is no approval gates. Only add HITL if user explicitly requests approval/confirmation before external actions.
1084
+
1076
1085
  If the tool returns \`status="needs_input"\`, ask the missing questions, then call \`workflow(...)\` again with the additional details.
1077
1086
 
1078
1087
  ### Step 2: (Optional) Validate the generated workflow