@ema.co/mcp-toolkit 2026.2.19 → 2026.2.23
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/cli/index.js +2 -2
- package/dist/mcp/domain/loop-detection.js +46 -54
- package/dist/mcp/domain/sanitizer.js +1 -1
- package/dist/mcp/domain/workflow-graph.js +2 -2
- package/dist/mcp/domain/workflow-path-enumerator.js +7 -4
- package/dist/mcp/guidance.js +53 -0
- package/dist/mcp/handlers/debug/adapter.js +15 -0
- package/dist/mcp/handlers/debug/formatters.js +282 -0
- package/dist/mcp/handlers/debug/index.js +133 -0
- package/dist/mcp/handlers/demo/adapter.js +180 -0
- package/dist/mcp/handlers/env/config.js +2 -2
- package/dist/mcp/handlers/index.js +0 -1
- package/dist/mcp/handlers/persona/adapter.js +135 -0
- package/dist/mcp/handlers/sync/adapter.js +200 -0
- package/dist/mcp/handlers/workflow/adapter.js +174 -0
- package/dist/mcp/handlers/workflow/fix.js +11 -12
- package/dist/mcp/handlers/workflow/index.js +0 -24
- package/dist/mcp/knowledge-guidance-topics.js +615 -0
- package/dist/mcp/knowledge.js +23 -612
- package/dist/mcp/resources-dynamic.js +2395 -0
- package/dist/mcp/resources-validation.js +408 -0
- package/dist/mcp/resources.js +72 -2724
- package/dist/mcp/server.js +33 -832
- package/dist/mcp/tools.js +104 -2
- package/dist/sdk/client-adapter.js +265 -24
- package/dist/sdk/ema-client.js +100 -9
- package/dist/sdk/generated/well-known-types.js +99 -0
- package/dist/sdk/grpc-client.js +115 -1
- package/dist/sync/sdk.js +2 -2
- package/dist/sync.js +4 -3
- package/package.json +3 -2
- package/dist/mcp/handlers/knowledge/index.js +0 -54
package/dist/mcp/server.js
CHANGED
|
@@ -24,34 +24,30 @@ export { TOOLKIT_NAME, TOOLKIT_VERSION, TOOLKIT_COMMIT };
|
|
|
24
24
|
import { PromptRegistry, isPromptError } from "./prompts.js";
|
|
25
25
|
import { ResourceRegistry, isResourceError } from "./resources.js";
|
|
26
26
|
import { generateServerInstructions, getContextualTip, TOOL_GUIDANCE } from "./guidance.js";
|
|
27
|
-
import { resolveSyncBehavior, loadSyncOptions } from "../sync/sync-options.js";
|
|
28
|
-
import { fingerprintPersona } from "../sync.js";
|
|
29
|
-
// Direct Sync (Config-less) - extracted to handlers/sync/direct.ts
|
|
30
|
-
import { directSyncPersona, directSyncPersonaById, directSyncAll } from "./handlers/sync/direct.js";
|
|
31
|
-
import { createVersionStorage } from "../sync/version-storage.js";
|
|
32
|
-
import { createVersionPolicyEngine } from "../sync/version-policy.js";
|
|
33
27
|
// V2 Tools
|
|
34
28
|
import { generateTools } from "./tools.js";
|
|
35
|
-
|
|
29
|
+
// Handler imports (simple 1-2 line handlers stay inline)
|
|
36
30
|
import { handleEnv } from "./handlers/env/index.js";
|
|
37
31
|
import { handleAction } from "./handlers/action/index.js";
|
|
38
32
|
import { handleTemplate } from "./handlers/template/index.js";
|
|
39
|
-
import { handleKnowledge } from "./handlers/knowledge/index.js";
|
|
40
33
|
import { handleReference } from "./handlers/reference/index.js";
|
|
41
|
-
// Import extracted handlers
|
|
42
|
-
import { handleWorkflow } from "./handlers/workflow/index.js";
|
|
43
34
|
import { handleCatalog } from "./handlers/catalog/index.js";
|
|
44
35
|
import { handleFeedback } from "./handlers/feedback/index.js";
|
|
45
36
|
import { recordTelemetry } from "./handlers/feedback/store.js";
|
|
46
|
-
|
|
37
|
+
// V2 adapters (extracted from server.ts — each owns its routing/transformation logic)
|
|
38
|
+
import { handlePersonaAdapter } from "./handlers/persona/adapter.js";
|
|
39
|
+
import { handleWorkflowAdapter } from "./handlers/workflow/adapter.js";
|
|
40
|
+
import { handleSyncAdapter } from "./handlers/sync/adapter.js";
|
|
41
|
+
import { handleDemoAdapter } from "./handlers/demo/adapter.js";
|
|
42
|
+
import { handleDebugAdapter } from "./handlers/debug/adapter.js";
|
|
47
43
|
// Start token initialization in background (non-blocking)
|
|
48
44
|
void initializeApiKeyTokens();
|
|
49
45
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
50
46
|
// Tool Definitions
|
|
51
47
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
52
48
|
//
|
|
53
|
-
// V2 TOOLS (
|
|
54
|
-
// - env, persona, catalog, workflow, sync
|
|
49
|
+
// V2 TOOLS (7 tools) - LLM-optimized minimal interface
|
|
50
|
+
// - env, persona, catalog, workflow, sync, toolkit_feedback, debug
|
|
55
51
|
// - Defined in: ./tools.ts
|
|
56
52
|
//
|
|
57
53
|
// NAMING CONVENTION:
|
|
@@ -62,7 +58,7 @@ void initializeApiKeyTokens();
|
|
|
62
58
|
/**
|
|
63
59
|
* Generate all available tools
|
|
64
60
|
*
|
|
65
|
-
* V2:
|
|
61
|
+
* V2: 7 tools (persona, catalog, workflow, sync, env, toolkit_feedback, debug) - LLM-optimized
|
|
66
62
|
*
|
|
67
63
|
* Why V2:
|
|
68
64
|
* - Minimal tool count optimizes LLM tool selection
|
|
@@ -88,224 +84,7 @@ const toolHandlers = {
|
|
|
88
84
|
})), { name: TOOLKIT_NAME, version: TOOLKIT_VERSION, commit: TOOLKIT_COMMIT });
|
|
89
85
|
},
|
|
90
86
|
persona: async (args) => {
|
|
91
|
-
|
|
92
|
-
const client = createClient(targetEnv);
|
|
93
|
-
// Build version context for version management modes
|
|
94
|
-
const versionContext = {
|
|
95
|
-
// Store versions in the caller's workspace, not the toolkit install dir.
|
|
96
|
-
// This must be writable under typical MCP usage (e.g. Cursor workspace).
|
|
97
|
-
workspaceRoot: process.cwd(),
|
|
98
|
-
environment: targetEnv,
|
|
99
|
-
tenant_id: targetEnv, // Use env name as tenant identifier
|
|
100
|
-
};
|
|
101
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
102
|
-
// V2 Parameter Transformation
|
|
103
|
-
// Convert v2 structure to v1 mode-based structure for handler compatibility
|
|
104
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
105
|
-
const transformedArgs = { ...args };
|
|
106
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
107
|
-
// Explicit Method API (takes priority over flag-based args)
|
|
108
|
-
// persona(method="create|get|list|update|delete|sanitize|analyze|compare|clone")
|
|
109
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
110
|
-
if (args.method) {
|
|
111
|
-
const method = String(args.method);
|
|
112
|
-
// Map explicit methods to internal modes
|
|
113
|
-
const methodToMode = {
|
|
114
|
-
create: "create",
|
|
115
|
-
get: "get",
|
|
116
|
-
list: "list",
|
|
117
|
-
update: "update",
|
|
118
|
-
delete: "delete",
|
|
119
|
-
sanitize: "sanitize",
|
|
120
|
-
analyze: "analyze",
|
|
121
|
-
compare: "compare",
|
|
122
|
-
schema: "schema", // get persona input schema (dashboard columns)
|
|
123
|
-
clone: "create", // clone is create with from
|
|
124
|
-
snapshot: "version_create",
|
|
125
|
-
history: "version_list",
|
|
126
|
-
restore: "version_restore",
|
|
127
|
-
};
|
|
128
|
-
const mode = methodToMode[method];
|
|
129
|
-
if (!mode) {
|
|
130
|
-
return { error: `Unknown method: ${method}`, valid_methods: Object.keys(methodToMode) };
|
|
131
|
-
}
|
|
132
|
-
transformedArgs.mode = mode;
|
|
133
|
-
delete transformedArgs.method;
|
|
134
|
-
// Handle action composition if present
|
|
135
|
-
if (args.actions && Array.isArray(args.actions)) {
|
|
136
|
-
// Store actions for post-processing after main operation
|
|
137
|
-
transformedArgs._actions = args.actions;
|
|
138
|
-
delete transformedArgs.actions;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
142
|
-
// Flag-based API (legacy compatibility)
|
|
143
|
-
// ONLY applies when explicit `method` parameter was NOT provided
|
|
144
|
-
// When method is explicit, flags like sanitize=true are passed through
|
|
145
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
146
|
-
// Guard: Skip flag-based API if explicit method was provided
|
|
147
|
-
const skipFlagBasedApi = !!args.method;
|
|
148
|
-
// Create: persona(create={name, type, from, input})
|
|
149
|
-
if (!skipFlagBasedApi && args.create && typeof args.create === "object") {
|
|
150
|
-
const create = args.create;
|
|
151
|
-
transformedArgs.mode = "create";
|
|
152
|
-
transformedArgs.name = create.name;
|
|
153
|
-
transformedArgs.type = create.type;
|
|
154
|
-
transformedArgs.from = create.from;
|
|
155
|
-
transformedArgs.input = create.input;
|
|
156
|
-
transformedArgs.preview = create.preview ?? true;
|
|
157
|
-
delete transformedArgs.create;
|
|
158
|
-
}
|
|
159
|
-
// Update: persona(id, update={config, input, workflow_spec})
|
|
160
|
-
else if (!skipFlagBasedApi && args.update && typeof args.update === "object") {
|
|
161
|
-
const update = args.update;
|
|
162
|
-
transformedArgs.mode = "update";
|
|
163
|
-
transformedArgs.proto_config = update.config;
|
|
164
|
-
transformedArgs.input = update.input;
|
|
165
|
-
transformedArgs.workflow_spec = update.workflow_spec;
|
|
166
|
-
transformedArgs.preview = update.preview ?? false; // Default to deploy, not preview
|
|
167
|
-
delete transformedArgs.update;
|
|
168
|
-
}
|
|
169
|
-
// Delete: persona(id, delete=true)
|
|
170
|
-
else if (!skipFlagBasedApi && args.delete === true) {
|
|
171
|
-
transformedArgs.mode = "delete";
|
|
172
|
-
delete transformedArgs.delete;
|
|
173
|
-
}
|
|
174
|
-
// Analyze: persona(id, analyze=true, fix=true)
|
|
175
|
-
else if (!skipFlagBasedApi && args.analyze === true) {
|
|
176
|
-
transformedArgs.mode = "analyze";
|
|
177
|
-
// fix is already in args
|
|
178
|
-
delete transformedArgs.analyze;
|
|
179
|
-
}
|
|
180
|
-
// Sanitize: persona(id, sanitize=true)
|
|
181
|
-
// ONLY converts to mode="sanitize" when method is NOT explicit
|
|
182
|
-
// When method="create" + sanitize=true, sanitize is a FLAG for post-creation sanitization
|
|
183
|
-
else if (!skipFlagBasedApi && args.sanitize === true) {
|
|
184
|
-
transformedArgs.mode = "sanitize";
|
|
185
|
-
delete transformedArgs.sanitize;
|
|
186
|
-
}
|
|
187
|
-
// Snapshot: persona(id, snapshot="message")
|
|
188
|
-
else if (!skipFlagBasedApi && typeof args.snapshot === "string") {
|
|
189
|
-
transformedArgs.mode = "version_create";
|
|
190
|
-
transformedArgs.message = args.snapshot;
|
|
191
|
-
delete transformedArgs.snapshot;
|
|
192
|
-
}
|
|
193
|
-
// History: persona(id, history=true)
|
|
194
|
-
else if (!skipFlagBasedApi && args.history === true) {
|
|
195
|
-
transformedArgs.mode = "version_list";
|
|
196
|
-
delete transformedArgs.history;
|
|
197
|
-
}
|
|
198
|
-
// Restore: persona(id, restore="v3")
|
|
199
|
-
else if (!skipFlagBasedApi && typeof args.restore === "string") {
|
|
200
|
-
transformedArgs.mode = "version_restore";
|
|
201
|
-
transformedArgs.version = args.restore;
|
|
202
|
-
delete transformedArgs.restore;
|
|
203
|
-
}
|
|
204
|
-
// Compare: persona(id, compare="other-id")
|
|
205
|
-
else if (!skipFlagBasedApi && typeof args.compare === "string") {
|
|
206
|
-
transformedArgs.mode = "compare";
|
|
207
|
-
transformedArgs.compare_to = args.compare;
|
|
208
|
-
delete transformedArgs.compare;
|
|
209
|
-
}
|
|
210
|
-
// Data operations: persona(id, data={method:"...", ...})
|
|
211
|
-
// Supports both explicit method format and legacy flag format
|
|
212
|
-
else if (args.data && typeof args.data === "object") {
|
|
213
|
-
const data = args.data;
|
|
214
|
-
const personaId = args.id;
|
|
215
|
-
const fs = await import("fs/promises");
|
|
216
|
-
// EXPLICIT METHOD FORMAT (preferred): data={method:"list/stats/upload/copy/replicate/delete/search/refresh/regenerate/replace"}
|
|
217
|
-
if (typeof data.method === "string") {
|
|
218
|
-
// Import the new data handler - pass data object as part of args
|
|
219
|
-
const { handleData: handleDataNew } = await import("./handlers/data/index.js");
|
|
220
|
-
return handleDataNew({ persona_id: personaId, env: args.env, data }, client);
|
|
221
|
-
}
|
|
222
|
-
// LEGACY FLAG FORMAT (backwards compatibility) - use extracted handler
|
|
223
|
-
const { handleData: handleDataExtracted } = await import("./handlers/data/index.js");
|
|
224
|
-
const readFileFn = (path) => fs.readFile(path);
|
|
225
|
-
if (data.list === true) {
|
|
226
|
-
return handleDataExtracted({ method: "list", persona_id: personaId, env: args.env }, client, readFileFn);
|
|
227
|
-
}
|
|
228
|
-
if (typeof data.upload === "string") {
|
|
229
|
-
return handleDataExtracted({ method: "upload", persona_id: personaId, data: { path: data.upload }, env: args.env }, client, readFileFn);
|
|
230
|
-
}
|
|
231
|
-
if (typeof data.delete === "string") {
|
|
232
|
-
return handleDataExtracted({ method: "delete", persona_id: personaId, data: { file_id: data.delete }, env: args.env }, client, readFileFn);
|
|
233
|
-
}
|
|
234
|
-
if (typeof data.generate === "string" || data.template) {
|
|
235
|
-
return handleDataExtracted({
|
|
236
|
-
method: "generate",
|
|
237
|
-
persona_id: personaId,
|
|
238
|
-
data: {
|
|
239
|
-
input: data.generate,
|
|
240
|
-
from: data.template,
|
|
241
|
-
count: data.count,
|
|
242
|
-
},
|
|
243
|
-
env: args.env
|
|
244
|
-
}, client, readFileFn);
|
|
245
|
-
}
|
|
246
|
-
if (typeof data.embed === "boolean") {
|
|
247
|
-
return handleDataExtracted({ method: "embedding", persona_id: personaId, data: { enabled: data.embed }, env: args.env }, client, readFileFn);
|
|
248
|
-
}
|
|
249
|
-
if (typeof data.search === "string") {
|
|
250
|
-
return handleKnowledge({ mode: "search", persona_id: personaId, query: data.search, env: args.env }, client, (path) => fs.readFile(path));
|
|
251
|
-
}
|
|
252
|
-
return {
|
|
253
|
-
error: "Unknown data operation",
|
|
254
|
-
hint: "Use data={method:'list'} format (explicit method)",
|
|
255
|
-
available_methods: ["list", "stats", "upload", "copy", "replicate", "delete", "search", "refresh", "regenerate", "replace"],
|
|
256
|
-
legacy_flags: ["list", "upload", "delete", "generate", "embed", "search"],
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
// Get: persona(id) with no mutation flags
|
|
260
|
-
else if (args.id && !transformedArgs.mode) {
|
|
261
|
-
transformedArgs.mode = "get";
|
|
262
|
-
}
|
|
263
|
-
// List: persona() or persona(type, status, query) with no id
|
|
264
|
-
else if (!args.id && !transformedArgs.mode) {
|
|
265
|
-
transformedArgs.mode = "list";
|
|
266
|
-
}
|
|
267
|
-
// Templates are tenant-specific - don't use hardcoded IDs
|
|
268
|
-
// The handler will use dynamic template lookup from API
|
|
269
|
-
const result = await handlePersona(transformedArgs, client, () => undefined, // Dynamic lookup in handler
|
|
270
|
-
(env) => createClient(env), versionContext);
|
|
271
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
272
|
-
// Action Composition Post-Processing
|
|
273
|
-
// Execute actions array after main operation completes
|
|
274
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
275
|
-
const actions = transformedArgs._actions;
|
|
276
|
-
if (actions && actions.length > 0) {
|
|
277
|
-
// Get the target persona ID from result
|
|
278
|
-
const resultObj = result;
|
|
279
|
-
const targetId = resultObj.id ??
|
|
280
|
-
resultObj.persona_id;
|
|
281
|
-
const sourceId = args.from;
|
|
282
|
-
if (!targetId) {
|
|
283
|
-
return {
|
|
284
|
-
...(typeof result === "object" && result !== null ? result : {}),
|
|
285
|
-
_actions_error: "No persona ID available for action execution",
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
// Import and execute actions
|
|
289
|
-
const actionExecutor = await import("./handlers/action-executor.js");
|
|
290
|
-
const context = {
|
|
291
|
-
source: sourceId,
|
|
292
|
-
target: targetId,
|
|
293
|
-
env: targetEnv,
|
|
294
|
-
originalArgs: args,
|
|
295
|
-
};
|
|
296
|
-
const actionsResult = await actionExecutor.executeActions(actions, context, client);
|
|
297
|
-
return {
|
|
298
|
-
...(typeof result === "object" && result !== null ? result : {}),
|
|
299
|
-
_actions: actionsResult,
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
return result;
|
|
303
|
-
},
|
|
304
|
-
// Consolidated workflow handler (replaces legacy inline handler)
|
|
305
|
-
workflow: async (args) => {
|
|
306
|
-
const client = createClient(args.env);
|
|
307
|
-
// Templates are tenant-specific - dynamic lookup in handler
|
|
308
|
-
return handleWorkflow(args, client, () => undefined);
|
|
87
|
+
return handlePersonaAdapter(args, createClient, getDefaultEnvName);
|
|
309
88
|
},
|
|
310
89
|
action: async (args) => {
|
|
311
90
|
const client = createClient(args.env);
|
|
@@ -314,12 +93,6 @@ const toolHandlers = {
|
|
|
314
93
|
template: async (args) => {
|
|
315
94
|
return handleTemplate(args);
|
|
316
95
|
},
|
|
317
|
-
knowledge: async (args) => {
|
|
318
|
-
const client = createClient(args.env);
|
|
319
|
-
const fs = await import("fs/promises");
|
|
320
|
-
return handleKnowledge(args, client, (path) => fs.readFile(path));
|
|
321
|
-
},
|
|
322
|
-
// v2: data is an alias for knowledge with simplified interface
|
|
323
96
|
data: async (args) => {
|
|
324
97
|
const client = createClient(args.env);
|
|
325
98
|
const fs = await import("fs/promises");
|
|
@@ -352,572 +125,19 @@ const toolHandlers = {
|
|
|
352
125
|
toolkit_feedback: async (args) => {
|
|
353
126
|
return handleFeedback(args);
|
|
354
127
|
},
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if (scope === "all" || !identifier) {
|
|
369
|
-
const sdk = getSyncSDK();
|
|
370
|
-
if (sdk) {
|
|
371
|
-
try {
|
|
372
|
-
const result = await sdk.runSync();
|
|
373
|
-
return { success: true, mode: "config", ...result };
|
|
374
|
-
}
|
|
375
|
-
finally {
|
|
376
|
-
sdk.close();
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
// Config-less mode
|
|
380
|
-
try {
|
|
381
|
-
const result = await directSyncAll({ targetEnv, dryRun });
|
|
382
|
-
return { success: true, mode: "tags", ...result };
|
|
383
|
-
}
|
|
384
|
-
catch (e) {
|
|
385
|
-
return { success: false, error: e instanceof Error ? e.message : String(e) };
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
// Sync single persona
|
|
389
|
-
const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(identifier);
|
|
390
|
-
const behavior = resolveSyncBehavior({
|
|
391
|
-
personaName: isUUID ? undefined : identifier,
|
|
392
|
-
targetEnv,
|
|
393
|
-
overrides: {
|
|
394
|
-
dry_run: dryRun ? true : undefined,
|
|
395
|
-
sync_status: includeStatus ? true : undefined,
|
|
396
|
-
},
|
|
397
|
-
});
|
|
398
|
-
try {
|
|
399
|
-
const result = isUUID
|
|
400
|
-
? await directSyncPersonaById({
|
|
401
|
-
personaId: identifier,
|
|
402
|
-
sourceEnv,
|
|
403
|
-
targetEnv,
|
|
404
|
-
dryRun: behavior.dry_run,
|
|
405
|
-
syncStatus: behavior.sync_status,
|
|
406
|
-
})
|
|
407
|
-
: await directSyncPersona({
|
|
408
|
-
name: identifier,
|
|
409
|
-
sourceEnv,
|
|
410
|
-
targetEnv,
|
|
411
|
-
dryRun: behavior.dry_run,
|
|
412
|
-
syncStatus: behavior.sync_status,
|
|
413
|
-
});
|
|
414
|
-
return { ...result, resolved_behavior: behavior };
|
|
415
|
-
}
|
|
416
|
-
catch (e) {
|
|
417
|
-
return { success: false, error: e instanceof Error ? e.message : String(e) };
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
async function syncInfoImpl(args) {
|
|
421
|
-
const client = args.env ? createClient(args.env) : undefined;
|
|
422
|
-
// Check if persona is synced
|
|
423
|
-
if (args.persona_id) {
|
|
424
|
-
if (!client)
|
|
425
|
-
throw new Error("env required when checking persona sync status");
|
|
426
|
-
const personaId = String(args.persona_id);
|
|
427
|
-
const personas = await client.getPersonasForTenant();
|
|
428
|
-
const persona = personas.find((p) => p.id === personaId);
|
|
429
|
-
if (!persona)
|
|
430
|
-
throw new Error(`AI Employee not found: ${personaId}`);
|
|
431
|
-
const meta = client.getSyncMetadata(persona);
|
|
432
|
-
return {
|
|
433
|
-
environment: client["env"].name,
|
|
434
|
-
persona_id: personaId,
|
|
435
|
-
persona_name: persona.name,
|
|
436
|
-
is_synced: !!meta,
|
|
437
|
-
sync_metadata: meta,
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
// Check by persona name
|
|
441
|
-
if (args.persona_name) {
|
|
442
|
-
const sdk = getSyncSDK();
|
|
443
|
-
if (!sdk)
|
|
444
|
-
return { error: "No sync config found. Set EMA_AGENT_SYNC_CONFIG." };
|
|
445
|
-
try {
|
|
446
|
-
const persona = await sdk.getMasterPersonaByName(String(args.persona_name));
|
|
447
|
-
if (!persona)
|
|
448
|
-
return { error: `Persona not found: ${args.persona_name}` };
|
|
449
|
-
return await sdk.getPersonaSyncStatus(persona.id);
|
|
450
|
-
}
|
|
451
|
-
finally {
|
|
452
|
-
sdk.close();
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
// List all synced personas
|
|
456
|
-
if (args.list_synced === true) {
|
|
457
|
-
if (!client)
|
|
458
|
-
throw new Error("env required when listing synced personas");
|
|
459
|
-
const personas = await client.getPersonasForTenant();
|
|
460
|
-
const masterEnvFilter = args.master_env ? String(args.master_env).toLowerCase() : undefined;
|
|
461
|
-
const synced = [];
|
|
462
|
-
for (const p of personas) {
|
|
463
|
-
const meta = client.getSyncMetadata(p);
|
|
464
|
-
if (meta) {
|
|
465
|
-
if (masterEnvFilter && meta.master_env.toLowerCase() !== masterEnvFilter)
|
|
466
|
-
continue;
|
|
467
|
-
synced.push({ persona_id: p.id, persona_name: p.name, sync_metadata: meta });
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
return { environment: client["env"].name, count: synced.length, synced_personas: synced };
|
|
471
|
-
}
|
|
472
|
-
// Default: return overall sync config/status
|
|
473
|
-
const sdk = getSyncSDK();
|
|
474
|
-
const options = args.include_options === true ? loadSyncOptions() : undefined;
|
|
475
|
-
if (!sdk) {
|
|
476
|
-
return {
|
|
477
|
-
configured: false,
|
|
478
|
-
error: "No sync config found. Set EMA_AGENT_SYNC_CONFIG.",
|
|
479
|
-
options,
|
|
480
|
-
};
|
|
481
|
-
}
|
|
482
|
-
try {
|
|
483
|
-
const master = sdk.getMasterEnvironment();
|
|
484
|
-
const envs = sdk.getEnvironments();
|
|
485
|
-
const personas = await sdk.listMasterPersonas();
|
|
486
|
-
return {
|
|
487
|
-
configured: true,
|
|
488
|
-
master_environment: { name: master.name, url: master.baseUrl },
|
|
489
|
-
target_environments: envs.filter((e) => !e.isMaster).map((e) => ({ name: e.name, url: e.baseUrl })),
|
|
490
|
-
total_personas: personas.length,
|
|
491
|
-
options,
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
finally {
|
|
495
|
-
sdk.close();
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
499
|
-
// V2 Tool Adapters (contract ↔ implementation)
|
|
500
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
501
|
-
//
|
|
502
|
-
// The tool schemas in tools.ts are the public MCP contract.
|
|
503
|
-
// These adapters ensure the V2 tool surface behaves as documented,
|
|
504
|
-
// while routing to the extracted handler implementations.
|
|
505
|
-
// Workflow tool: MCP provides data (get) and executes (deploy). LLM does all thinking.
|
|
506
|
-
toolHandlers.workflow = async (args) => {
|
|
507
|
-
const normalizedArgs = { ...(args ?? {}) };
|
|
508
|
-
const personaId = normalizedArgs.persona_id ? String(normalizedArgs.persona_id) : undefined;
|
|
509
|
-
let workflowDef = normalizedArgs.workflow_def;
|
|
510
|
-
const workflowDefPath = normalizedArgs.workflow_def_path;
|
|
511
|
-
const mode = normalizedArgs.mode ? String(normalizedArgs.mode) : undefined;
|
|
512
|
-
const baseFingerprint = normalizedArgs.base_fingerprint;
|
|
513
|
-
const force = normalizedArgs.force;
|
|
514
|
-
// For deploy: resolve workflow_def from file when workflow_def_path is provided (large payloads)
|
|
515
|
-
// Also handle the common agent mistake: passing {"workflow_def_path": "/path"} as workflow_def
|
|
516
|
-
// (happens when the agent's tool schema doesn't expose workflow_def_path as a separate param)
|
|
517
|
-
let effectivePath = workflowDefPath;
|
|
518
|
-
if (mode === "deploy" && workflowDef && !effectivePath) {
|
|
519
|
-
const keys = Object.keys(workflowDef);
|
|
520
|
-
if (keys.length === 1 && keys[0] === "workflow_def_path" && typeof workflowDef.workflow_def_path === "string") {
|
|
521
|
-
effectivePath = workflowDef.workflow_def_path;
|
|
522
|
-
workflowDef = undefined;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
if (mode === "deploy" && !workflowDef && effectivePath) {
|
|
526
|
-
try {
|
|
527
|
-
// Guardrails: reduce risk of accidental secret exfiltration
|
|
528
|
-
const path = await import("path");
|
|
529
|
-
if (!path.isAbsolute(effectivePath)) {
|
|
530
|
-
return { error: "workflow_def_path must be an absolute path on the MCP server host", path: effectivePath };
|
|
531
|
-
}
|
|
532
|
-
if (!effectivePath.toLowerCase().endsWith(".json")) {
|
|
533
|
-
return { error: "workflow_def_path must point to a .json file", path: effectivePath };
|
|
534
|
-
}
|
|
535
|
-
const fs = await import("fs/promises");
|
|
536
|
-
const stat = await fs.stat(effectivePath);
|
|
537
|
-
const MAX_WORKFLOW_DEF_BYTES = 1024 * 1024; // 1MB
|
|
538
|
-
if (stat.size > MAX_WORKFLOW_DEF_BYTES) {
|
|
539
|
-
return { error: `workflow_def_path file too large (${stat.size} bytes)`, max_bytes: MAX_WORKFLOW_DEF_BYTES, path: effectivePath };
|
|
540
|
-
}
|
|
541
|
-
const raw = await fs.readFile(effectivePath, "utf-8");
|
|
542
|
-
const parsed = JSON.parse(raw);
|
|
543
|
-
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
544
|
-
workflowDef = parsed;
|
|
545
|
-
}
|
|
546
|
-
else {
|
|
547
|
-
return { error: "workflow_def_path must point to a JSON object", path: effectivePath };
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
catch (err) {
|
|
551
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
552
|
-
return { error: `Failed to read workflow_def from path: ${msg}`, path: effectivePath };
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
// Route to handleWorkflow for get/deploy (the only public modes)
|
|
556
|
-
const client = createClient(normalizedArgs.env);
|
|
557
|
-
switch (mode) {
|
|
558
|
-
case "get": {
|
|
559
|
-
// Return workflow data for LLM to analyze/modify
|
|
560
|
-
return handleWorkflow({
|
|
561
|
-
mode: "get",
|
|
562
|
-
persona_id: personaId,
|
|
563
|
-
env: normalizedArgs.env,
|
|
564
|
-
}, client, () => undefined);
|
|
565
|
-
}
|
|
566
|
-
case "validate": {
|
|
567
|
-
// Static validation with path enumeration
|
|
568
|
-
return handleWorkflow({
|
|
569
|
-
mode: "validate",
|
|
570
|
-
persona_id: personaId,
|
|
571
|
-
workflow_def: normalizedArgs.workflow_def,
|
|
572
|
-
workflow_spec: normalizedArgs.workflow_spec,
|
|
573
|
-
validation_type: normalizedArgs.validation_type,
|
|
574
|
-
max_paths: normalizedArgs.max_paths,
|
|
575
|
-
timeout_ms: normalizedArgs.timeout_ms,
|
|
576
|
-
env: normalizedArgs.env,
|
|
577
|
-
}, client, () => undefined);
|
|
578
|
-
}
|
|
579
|
-
case "deploy": {
|
|
580
|
-
if (!personaId) {
|
|
581
|
-
return { error: 'persona_id is required for workflow(mode="deploy")' };
|
|
582
|
-
}
|
|
583
|
-
// Pre-deploy snapshot + stale-state protection (out-of-band changes)
|
|
584
|
-
const targetEnv = normalizedArgs.env ?? getDefaultEnvName();
|
|
585
|
-
let versionCreated;
|
|
586
|
-
try {
|
|
587
|
-
const personaBefore = await client.getPersonaById(personaId);
|
|
588
|
-
if (!personaBefore) {
|
|
589
|
-
return { error: `Persona not found: ${personaId}` };
|
|
590
|
-
}
|
|
591
|
-
const currentFp = fingerprintPersona(personaBefore);
|
|
592
|
-
if (!force && !baseFingerprint) {
|
|
593
|
-
return {
|
|
594
|
-
error: "base_fingerprint is required for workflow deploy (stale-state protection)",
|
|
595
|
-
persona_id: personaId,
|
|
596
|
-
current_fingerprint: currentFp,
|
|
597
|
-
hint: "Run workflow(mode='get', persona_id='...') immediately before deploying and pass fingerprint as base_fingerprint. Use force=true only for emergency overrides.",
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
if (!force && baseFingerprint && baseFingerprint !== currentFp) {
|
|
601
|
-
return {
|
|
602
|
-
error: "Persona changed since you last fetched it (fingerprint mismatch)",
|
|
603
|
-
persona_id: personaId,
|
|
604
|
-
base_fingerprint: baseFingerprint,
|
|
605
|
-
current_fingerprint: currentFp,
|
|
606
|
-
hint: "Re-run workflow(mode='get') to fetch the latest workflow_def, re-apply your edits, then deploy again. Use force=true only if you intend to overwrite out-of-band changes.",
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
const storage = createVersionStorage(process.cwd());
|
|
610
|
-
const engine = createVersionPolicyEngine(storage);
|
|
611
|
-
const snap = engine.forceCreateVersion(personaBefore, {
|
|
612
|
-
environment: targetEnv,
|
|
613
|
-
tenant_id: targetEnv,
|
|
614
|
-
message: "Pre-deploy snapshot",
|
|
615
|
-
created_by: "mcp-toolkit",
|
|
616
|
-
});
|
|
617
|
-
if (!snap.created || !snap.version) {
|
|
618
|
-
if (!force) {
|
|
619
|
-
return {
|
|
620
|
-
error: "Failed to create pre-deploy snapshot (required before deploy)",
|
|
621
|
-
persona_id: personaId,
|
|
622
|
-
details: snap.reason,
|
|
623
|
-
hint: "Fix snapshotting (workspace storage) or retry with force=true for emergency override.",
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
else {
|
|
628
|
-
versionCreated = { id: snap.version.id, version_name: snap.version.version_name };
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
catch {
|
|
632
|
-
// Snapshotting is required unless force=true
|
|
633
|
-
if (!force) {
|
|
634
|
-
return {
|
|
635
|
-
error: "Failed to create pre-deploy snapshot (required before deploy)",
|
|
636
|
-
persona_id: personaId,
|
|
637
|
-
hint: "Retry after fixing local workspace write access, or use force=true for emergency override.",
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
// Route to handleWorkflow deploy
|
|
642
|
-
const deployResult = await handleWorkflow({
|
|
643
|
-
mode: "deploy",
|
|
644
|
-
persona_id: personaId,
|
|
645
|
-
workflow_def: workflowDef,
|
|
646
|
-
proto_config: normalizedArgs.proto_config,
|
|
647
|
-
env: normalizedArgs.env,
|
|
648
|
-
force: force,
|
|
649
|
-
strict_validation: normalizedArgs.strict_validation,
|
|
650
|
-
}, client, () => undefined);
|
|
651
|
-
// Add version info to result if created
|
|
652
|
-
if (versionCreated && deployResult && typeof deployResult === "object") {
|
|
653
|
-
deployResult.version_snapshot = versionCreated;
|
|
654
|
-
}
|
|
655
|
-
return deployResult;
|
|
656
|
-
}
|
|
657
|
-
// REMOVED modes - LLM does these
|
|
658
|
-
case "analyze":
|
|
659
|
-
case "compare":
|
|
660
|
-
case "compile":
|
|
661
|
-
case "optimize":
|
|
662
|
-
case "generate": {
|
|
663
|
-
return {
|
|
664
|
-
error: `Mode "${mode}" removed - LLM does this thinking`,
|
|
665
|
-
hint: "Use workflow(mode='get') to fetch data, then analyze/generate in your reasoning. Deploy with workflow(mode='deploy').",
|
|
666
|
-
valid_modes: ["get", "validate", "deploy"],
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
default: {
|
|
670
|
-
// No mode or unknown mode - require explicit mode
|
|
671
|
-
return {
|
|
672
|
-
error: `Mode required. Valid modes: get, validate, deploy`,
|
|
673
|
-
hint: "workflow(mode='get') returns data for LLM. workflow(mode='validate') validates specs. workflow(mode='deploy') executes LLM's workflow_def.",
|
|
674
|
-
example: `workflow(mode="get", persona_id="...")`,
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
};
|
|
679
|
-
// Unify sync modes: run | status | config
|
|
680
|
-
toolHandlers.sync = async (args) => {
|
|
681
|
-
const normalizedArgs = { ...(args ?? {}) };
|
|
682
|
-
// Tool definition uses "method" (preview/execute/status), but legacy callers use "mode" (run/status/config)
|
|
683
|
-
const rawMethod = normalizedArgs.method ? String(normalizedArgs.method) : normalizedArgs.mode ? String(normalizedArgs.mode) : "run";
|
|
684
|
-
// Map tool method values to internal mode values
|
|
685
|
-
const mode = rawMethod === "preview" ? "run" : rawMethod === "execute" ? "run" : rawMethod;
|
|
686
|
-
// "preview" implies dry_run
|
|
687
|
-
if (rawMethod === "preview") {
|
|
688
|
-
normalizedArgs.dry_run = true;
|
|
689
|
-
}
|
|
690
|
-
// Support both old and new arg names
|
|
691
|
-
const target = (normalizedArgs.target ?? normalizedArgs.target_env);
|
|
692
|
-
const source = (normalizedArgs.source ?? normalizedArgs.source_env);
|
|
693
|
-
const id = normalizedArgs.id;
|
|
694
|
-
const identifier = normalizedArgs.identifier; // deprecated alias
|
|
695
|
-
const idOrIdentifier = id ?? identifier;
|
|
696
|
-
if (mode === "config") {
|
|
697
|
-
return syncInfoImpl({ include_options: true });
|
|
698
|
-
}
|
|
699
|
-
if (mode === "status") {
|
|
700
|
-
const env = normalizedArgs.env;
|
|
701
|
-
if (normalizedArgs.list_synced === true) {
|
|
702
|
-
if (!env)
|
|
703
|
-
throw new Error('env is required for sync(mode="status", list_synced=true)');
|
|
704
|
-
return syncInfoImpl({ list_synced: true, master_env: normalizedArgs.master_env, env });
|
|
705
|
-
}
|
|
706
|
-
if (idOrIdentifier) {
|
|
707
|
-
if (!env)
|
|
708
|
-
throw new Error('env is required for sync(mode="status", id="...")');
|
|
709
|
-
const identifierToResolve = String(idOrIdentifier);
|
|
710
|
-
const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(identifierToResolve);
|
|
711
|
-
if (isUUID) {
|
|
712
|
-
return syncInfoImpl({ persona_id: identifierToResolve, env });
|
|
713
|
-
}
|
|
714
|
-
// Name lookup: resolve to ID in env, then reuse persona_id path
|
|
715
|
-
const client = createClient(env);
|
|
716
|
-
const personas = await client.getPersonasForTenant();
|
|
717
|
-
const match = personas.find((p) => p.name === identifierToResolve);
|
|
718
|
-
if (!match)
|
|
719
|
-
throw new Error(`AI Employee not found by name in ${env}: ${identifierToResolve}`);
|
|
720
|
-
return syncInfoImpl({ persona_id: match.id, env });
|
|
721
|
-
}
|
|
722
|
-
// Default: overall sync status/config summary
|
|
723
|
-
return syncInfoImpl({ include_options: normalizedArgs.include_options === true });
|
|
724
|
-
}
|
|
725
|
-
// mode === "run" (default)
|
|
726
|
-
if (!target) {
|
|
727
|
-
throw new Error('target (or target_env) is required for sync(mode="run")');
|
|
728
|
-
}
|
|
729
|
-
return syncRunImpl({
|
|
730
|
-
identifier: idOrIdentifier,
|
|
731
|
-
target_env: target,
|
|
732
|
-
source_env: source,
|
|
733
|
-
scope: normalizedArgs.scope,
|
|
734
|
-
dry_run: normalizedArgs.dry_run,
|
|
735
|
-
include_status: normalizedArgs.include_status,
|
|
736
|
-
});
|
|
737
|
-
};
|
|
738
|
-
// Consolidated demo tool: kit | validate_kit | scenarios | consolidate | generate | validate | template
|
|
739
|
-
toolHandlers.demo = async (args) => {
|
|
740
|
-
const normalizedArgs = { ...(args ?? {}) };
|
|
741
|
-
const mode = normalizedArgs.mode ? String(normalizedArgs.mode) : "template";
|
|
742
|
-
// Deprecation warning - added to all responses
|
|
743
|
-
const deprecationWarning = {
|
|
744
|
-
_deprecation: {
|
|
745
|
-
message: "The 'demo' tool is deprecated. Please use 'data' and 'persona' tools instead.",
|
|
746
|
-
migration: {
|
|
747
|
-
"demo(mode='kit')": "persona(from='demo-sales-sdr', include_data=true)",
|
|
748
|
-
"demo(mode='generate')": "data(mode='generate', from='customer', count=5)",
|
|
749
|
-
"demo(mode='scenarios')": "data(mode='templates')",
|
|
750
|
-
"demo(mode='template')": "data(mode='templates', template='customer')",
|
|
751
|
-
},
|
|
752
|
-
},
|
|
753
|
-
};
|
|
754
|
-
switch (mode) {
|
|
755
|
-
case "kit": {
|
|
756
|
-
// Generate complete demo kit for a persona
|
|
757
|
-
const personaId = String(normalizedArgs.persona_id ?? "");
|
|
758
|
-
const scenarioId = String(normalizedArgs.scenario ?? "sales-sdr");
|
|
759
|
-
if (!personaId) {
|
|
760
|
-
throw new Error('demo(mode="kit") requires: persona_id');
|
|
761
|
-
}
|
|
762
|
-
// Import demo generator
|
|
763
|
-
const { generateDemoKit, DEMO_SCENARIOS, generateDemoScriptMarkdown, validateDemoKit } = await import("./demo-generator.js");
|
|
764
|
-
// Get scenario
|
|
765
|
-
const scenario = DEMO_SCENARIOS[scenarioId];
|
|
766
|
-
if (!scenario) {
|
|
767
|
-
throw new Error(`Unknown scenario: ${scenarioId}. Available: ${Object.keys(DEMO_SCENARIOS).join(", ")}`);
|
|
768
|
-
}
|
|
769
|
-
// Get persona and workflow
|
|
770
|
-
const client = await createClient(normalizedArgs.env);
|
|
771
|
-
const persona = await client.getPersonaById(personaId);
|
|
772
|
-
if (!persona) {
|
|
773
|
-
throw new Error(`Persona not found: ${personaId}`);
|
|
774
|
-
}
|
|
775
|
-
const workflowDef = persona.workflow_def || {};
|
|
776
|
-
const customQA = normalizedArgs.custom_qa;
|
|
777
|
-
// Generate kit
|
|
778
|
-
const kit = generateDemoKit(personaId, persona.name || personaId, workflowDef, scenario, customQA);
|
|
779
|
-
// Generate markdown script
|
|
780
|
-
const demoScript = generateDemoScriptMarkdown(kit);
|
|
781
|
-
// Validate
|
|
782
|
-
const validation = validateDemoKit(kit);
|
|
783
|
-
return {
|
|
784
|
-
success: true,
|
|
785
|
-
persona_id: personaId,
|
|
786
|
-
persona_name: persona.name,
|
|
787
|
-
scenario: scenarioId,
|
|
788
|
-
kit_summary: {
|
|
789
|
-
kb_documents: kit.kb_documents.length,
|
|
790
|
-
demo_questions: kit.demo_script.length,
|
|
791
|
-
fixed_responses: kit.fixed_responses.length,
|
|
792
|
-
validation_queries: kit.validation_queries.length,
|
|
793
|
-
},
|
|
794
|
-
validation,
|
|
795
|
-
demo_script_preview: demoScript.slice(0, 2000) + (demoScript.length > 2000 ? "\n\n... (truncated)" : ""),
|
|
796
|
-
kit,
|
|
797
|
-
instructions: [
|
|
798
|
-
"1. Upload KB documents to the persona's knowledge base",
|
|
799
|
-
"2. Review the demo script and practice the questions",
|
|
800
|
-
"3. Optionally apply fixed_responses for guaranteed fallbacks",
|
|
801
|
-
"4. Run validation queries to verify demo readiness",
|
|
802
|
-
"5. Conduct the demo with confidence!",
|
|
803
|
-
],
|
|
804
|
-
};
|
|
805
|
-
}
|
|
806
|
-
case "validate_kit": {
|
|
807
|
-
// Validate a persona's demo readiness
|
|
808
|
-
const personaId = String(normalizedArgs.persona_id ?? "");
|
|
809
|
-
if (!personaId) {
|
|
810
|
-
throw new Error('demo(mode="validate_kit") requires: persona_id');
|
|
811
|
-
}
|
|
812
|
-
const { analyzeWorkflowForDemo, DEMO_SCENARIOS } = await import("./demo-generator.js");
|
|
813
|
-
const client = await createClient(normalizedArgs.env);
|
|
814
|
-
const persona = await client.getPersonaById(personaId);
|
|
815
|
-
if (!persona) {
|
|
816
|
-
throw new Error(`Persona not found: ${personaId}`);
|
|
817
|
-
}
|
|
818
|
-
const analysis = analyzeWorkflowForDemo(persona.workflow_def || {});
|
|
819
|
-
// Check data sources
|
|
820
|
-
const dataSourcesResult = await client.listDataSourceFiles(personaId);
|
|
821
|
-
const dataSources = dataSourcesResult.files || [];
|
|
822
|
-
const hasKnowledgeBase = dataSources.length > 0;
|
|
823
|
-
const issues = [];
|
|
824
|
-
if (!hasKnowledgeBase) {
|
|
825
|
-
issues.push("No knowledge base documents uploaded - RAG search will fail");
|
|
826
|
-
}
|
|
827
|
-
if (analysis.intents.length === 0) {
|
|
828
|
-
issues.push("No categorizer intents detected - workflow may not route correctly");
|
|
829
|
-
}
|
|
830
|
-
if (!analysis.has_search) {
|
|
831
|
-
issues.push("No search nodes detected - cannot retrieve KB data");
|
|
832
|
-
}
|
|
833
|
-
// Suggest best scenario
|
|
834
|
-
let suggestedScenario = "sales-sdr";
|
|
835
|
-
for (const [id, scenario] of Object.entries(DEMO_SCENARIOS)) {
|
|
836
|
-
const intentOverlap = scenario.intents.filter(i => analysis.intents.some(ai => ai.toLowerCase().includes(i.name.toLowerCase()))).length;
|
|
837
|
-
if (intentOverlap > 0) {
|
|
838
|
-
suggestedScenario = id;
|
|
839
|
-
break;
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
return {
|
|
843
|
-
persona_id: personaId,
|
|
844
|
-
persona_name: persona.name,
|
|
845
|
-
ready: issues.length === 0,
|
|
846
|
-
issues,
|
|
847
|
-
workflow_analysis: analysis,
|
|
848
|
-
knowledge_base: {
|
|
849
|
-
has_documents: hasKnowledgeBase,
|
|
850
|
-
document_count: dataSources.length,
|
|
851
|
-
},
|
|
852
|
-
suggested_scenario: suggestedScenario,
|
|
853
|
-
next_steps: issues.length > 0
|
|
854
|
-
? issues.map((issue, i) => `${i + 1}. Fix: ${issue}`)
|
|
855
|
-
: [`Generate demo kit: demo(mode="kit", persona_id="${personaId}", scenario="${suggestedScenario}")`],
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
case "scenarios": {
|
|
859
|
-
// List available demo scenarios
|
|
860
|
-
const { DEMO_SCENARIOS } = await import("./demo-generator.js");
|
|
861
|
-
return {
|
|
862
|
-
scenarios: Object.entries(DEMO_SCENARIOS).map(([id, scenario]) => ({
|
|
863
|
-
id,
|
|
864
|
-
name: scenario.name,
|
|
865
|
-
description: scenario.description,
|
|
866
|
-
persona_types: scenario.persona_types,
|
|
867
|
-
tags: scenario.tags,
|
|
868
|
-
intent_count: scenario.intents.length,
|
|
869
|
-
qa_count: scenario.qa_pairs.length,
|
|
870
|
-
entity_types: scenario.entities.map(e => e.type),
|
|
871
|
-
})),
|
|
872
|
-
usage: 'demo(mode="kit", persona_id="...", scenario="<scenario_id>")',
|
|
873
|
-
};
|
|
874
|
-
}
|
|
875
|
-
case "consolidate": {
|
|
876
|
-
const source = String(normalizedArgs.source ?? "");
|
|
877
|
-
const output = String(normalizedArgs.output ?? "");
|
|
878
|
-
const entity = String(normalizedArgs.entity ?? "");
|
|
879
|
-
if (!source || !output || !entity) {
|
|
880
|
-
throw new Error('demo(mode="consolidate") requires: source, output, entity');
|
|
881
|
-
}
|
|
882
|
-
return handleConsolidateDemoData({
|
|
883
|
-
source_dir: source,
|
|
884
|
-
output_dir: output,
|
|
885
|
-
entity_type: entity,
|
|
886
|
-
primary_file: normalizedArgs.primary ?? `${entity}s.json`,
|
|
887
|
-
joins: normalizedArgs.joins ?? [],
|
|
888
|
-
tags: normalizedArgs.tags,
|
|
889
|
-
});
|
|
890
|
-
}
|
|
891
|
-
case "generate": {
|
|
892
|
-
const entity = String(normalizedArgs.entity ?? "");
|
|
893
|
-
if (!entity)
|
|
894
|
-
throw new Error('demo(mode="generate") requires: entity');
|
|
895
|
-
return handleGenerateDemoDocument({
|
|
896
|
-
entity_type: entity,
|
|
897
|
-
data: normalizedArgs.data ?? {},
|
|
898
|
-
related_data: normalizedArgs.related ?? {},
|
|
899
|
-
output_path: normalizedArgs.output,
|
|
900
|
-
tags: normalizedArgs.tags,
|
|
901
|
-
});
|
|
902
|
-
}
|
|
903
|
-
case "validate": {
|
|
904
|
-
return handleValidateDemoDocument({
|
|
905
|
-
file_path: normalizedArgs.file,
|
|
906
|
-
content: normalizedArgs.content,
|
|
907
|
-
});
|
|
908
|
-
}
|
|
909
|
-
case "template": {
|
|
910
|
-
const entity = String(normalizedArgs.entity ?? "");
|
|
911
|
-
if (!entity)
|
|
912
|
-
throw new Error('demo(mode="template") requires: entity');
|
|
913
|
-
return handleGetDemoDataTemplate({
|
|
914
|
-
entity_type: entity,
|
|
915
|
-
include_example: normalizedArgs.include_example,
|
|
916
|
-
});
|
|
917
|
-
}
|
|
918
|
-
default:
|
|
919
|
-
throw new Error(`Unknown demo mode: ${mode}`);
|
|
920
|
-
}
|
|
128
|
+
// V2 adapters — routing + transformation extracted to handler files
|
|
129
|
+
workflow: async (args) => {
|
|
130
|
+
return handleWorkflowAdapter(args, createClient, getDefaultEnvName);
|
|
131
|
+
},
|
|
132
|
+
sync: async (args) => {
|
|
133
|
+
return handleSyncAdapter(args, createClient, getDefaultEnvName, getSyncSDK);
|
|
134
|
+
},
|
|
135
|
+
demo: async (args) => {
|
|
136
|
+
return handleDemoAdapter(args, createClient);
|
|
137
|
+
},
|
|
138
|
+
debug: async (args) => {
|
|
139
|
+
return handleDebugAdapter(args, createClient, getDefaultEnvName);
|
|
140
|
+
},
|
|
921
141
|
};
|
|
922
142
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
923
143
|
// Helpers
|
|
@@ -928,24 +148,8 @@ toolHandlers.demo = async (args) => {
|
|
|
928
148
|
*/
|
|
929
149
|
function determineOperation(toolName, args) {
|
|
930
150
|
if (toolName === "persona") {
|
|
931
|
-
if (args.
|
|
932
|
-
return
|
|
933
|
-
if (args.update)
|
|
934
|
-
return "update";
|
|
935
|
-
if (args.create)
|
|
936
|
-
return "create";
|
|
937
|
-
if (args.delete)
|
|
938
|
-
return "delete";
|
|
939
|
-
if (args.snapshot)
|
|
940
|
-
return "snapshot";
|
|
941
|
-
if (args.history)
|
|
942
|
-
return "history";
|
|
943
|
-
if (args.restore)
|
|
944
|
-
return "restore";
|
|
945
|
-
if (args.compare)
|
|
946
|
-
return "compare";
|
|
947
|
-
if (args.sanitize)
|
|
948
|
-
return "sanitize";
|
|
151
|
+
if (args.method)
|
|
152
|
+
return String(args.method);
|
|
949
153
|
if (args.data)
|
|
950
154
|
return "data";
|
|
951
155
|
if (args.id)
|
|
@@ -953,24 +157,21 @@ function determineOperation(toolName, args) {
|
|
|
953
157
|
return "list";
|
|
954
158
|
}
|
|
955
159
|
if (toolName === "catalog") {
|
|
956
|
-
if (args.
|
|
957
|
-
return
|
|
958
|
-
if (args.for)
|
|
959
|
-
return "recommend"; // V2 schema uses 'for' parameter
|
|
960
|
-
if (args.query)
|
|
961
|
-
return "search";
|
|
160
|
+
if (args.method)
|
|
161
|
+
return String(args.method);
|
|
962
162
|
return "list";
|
|
963
163
|
}
|
|
964
164
|
if (toolName === "sync") {
|
|
965
|
-
if (args.
|
|
966
|
-
return
|
|
967
|
-
if (args.status)
|
|
968
|
-
return "status";
|
|
165
|
+
if (args.method)
|
|
166
|
+
return String(args.method);
|
|
969
167
|
return "preview";
|
|
970
168
|
}
|
|
971
169
|
if (toolName === "toolkit_feedback") {
|
|
972
170
|
return args.method ? String(args.method) : "submit";
|
|
973
171
|
}
|
|
172
|
+
if (toolName === "debug") {
|
|
173
|
+
return args.method ? String(args.method) : "conversations";
|
|
174
|
+
}
|
|
974
175
|
return toolName;
|
|
975
176
|
}
|
|
976
177
|
// ─────────────────────────────────────────────────────────────────────────────
|