@ema.co/mcp-toolkit 2026.2.5 → 2026.2.19

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/LICENSE +29 -21
  4. package/README.md +58 -35
  5. package/dist/mcp/domain/loop-detection.js +97 -0
  6. package/dist/mcp/domain/proto-constraints.js +284 -0
  7. package/dist/mcp/domain/structural-rules.js +12 -5
  8. package/dist/mcp/domain/validation-rules.js +107 -20
  9. package/dist/mcp/domain/workflow-graph-optimizer.js +235 -0
  10. package/dist/mcp/domain/workflow-graph-transforms.js +808 -0
  11. package/dist/mcp/domain/workflow-graph.js +374 -0
  12. package/dist/mcp/domain/workflow-optimizer.js +10 -4
  13. package/dist/mcp/guidance.js +54 -31
  14. package/dist/mcp/handlers/feedback/index.js +139 -0
  15. package/dist/mcp/handlers/feedback/store.js +262 -0
  16. package/dist/mcp/handlers/persona/index.js +237 -8
  17. package/dist/mcp/handlers/persona/schema.js +27 -0
  18. package/dist/mcp/handlers/reference/index.js +6 -4
  19. package/dist/mcp/handlers/workflow/index.js +25 -28
  20. package/dist/mcp/handlers/workflow/optimize.js +73 -33
  21. package/dist/mcp/handlers/workflow/validation.js +1 -1
  22. package/dist/mcp/knowledge-types.js +7 -0
  23. package/dist/mcp/knowledge.js +146 -834
  24. package/dist/mcp/resources.js +610 -18
  25. package/dist/mcp/server.js +233 -2156
  26. package/dist/mcp/tools.js +91 -5
  27. package/dist/sdk/generated/agent-catalog.js +615 -0
  28. package/dist/sdk/generated/deprecated-actions.js +182 -96
  29. package/dist/sdk/generated/proto-fields.js +2 -1
  30. package/dist/sdk/generated/protos/service/agent_qa/v1/agent_qa_pb.js +460 -21
  31. package/dist/sdk/generated/protos/service/auth/v1/auth_pb.js +11 -1
  32. package/dist/sdk/generated/protos/service/dataingest/v1/dataingest_pb.js +173 -66
  33. package/dist/sdk/generated/protos/service/feedback/v1/feedback_pb.js +43 -1
  34. package/dist/sdk/generated/protos/service/llmservice/v1/llmservice_pb.js +26 -21
  35. package/dist/sdk/generated/protos/service/persona/v1/persona_config_pb.js +100 -89
  36. package/dist/sdk/generated/protos/service/persona/v1/persona_pb.js +126 -116
  37. package/dist/sdk/generated/protos/service/persona/v1/shared_widgets/widget_types_pb.js +33 -1
  38. package/dist/sdk/generated/protos/service/persona/v1/voicebot_widgets/widget_types_pb.js +60 -11
  39. package/dist/sdk/generated/protos/service/tenant/v1/tenant_pb.js +1 -1
  40. package/dist/sdk/generated/protos/service/user/v1/user_pb.js +1 -1
  41. package/dist/sdk/generated/protos/service/utils/v1/agent_qa_pb.js +35 -0
  42. package/dist/sdk/generated/protos/service/workflows/v1/action_registry_pb.js +1 -1
  43. package/dist/sdk/generated/protos/service/workflows/v1/action_type_pb.js +6 -1
  44. package/dist/sdk/generated/protos/service/workflows/v1/chatbot_pb.js +106 -11
  45. package/dist/sdk/generated/protos/service/workflows/v1/common_forms_pb.js +1 -1
  46. package/dist/sdk/generated/protos/service/workflows/v1/coordinator_pb.js +1 -1
  47. package/dist/sdk/generated/protos/service/workflows/v1/external_actions_pb.js +31 -1
  48. package/dist/sdk/generated/protos/service/workflows/v1/well_known_pb.js +5 -1
  49. package/dist/sdk/generated/protos/service/workflows/v1/workflow_pb.js +1 -1
  50. package/dist/sdk/generated/protos/util/tracking_metadata_pb.js +1 -1
  51. package/dist/sdk/generated/widget-catalog.js +60 -0
  52. package/docs/README.md +17 -9
  53. package/package.json +2 -2
  54. package/.context/public/guides/dashboard-operations.md +0 -286
  55. package/.context/public/guides/email-patterns.md +0 -125
  56. package/dist/mcp/domain/intent-architect.js +0 -914
  57. package/dist/mcp/domain/quality-gates.js +0 -110
  58. package/dist/mcp/domain/workflow-execution-analyzer.js +0 -412
  59. package/dist/mcp/domain/workflow-intent.js +0 -1806
  60. package/dist/mcp/domain/workflow-merge.js +0 -449
  61. package/dist/mcp/domain/workflow-tracer.js +0 -648
  62. package/dist/mcp/domain/workflow-transformer.js +0 -742
  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
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Feedback Store - JSONL file-based storage for feedback and telemetry
3
+ *
4
+ * Provides append-only JSONL storage with rotation to prevent unbounded growth.
5
+ * Data is stored in `.feedback/` directory relative to the toolkit root.
6
+ *
7
+ * Two files:
8
+ * - feedback.jsonl - Explicit agent feedback (gaps, confusion, suggestions)
9
+ * - telemetry.jsonl - Passive telemetry (tool calls, resource fetches, errors)
10
+ */
11
+ import { promises as fs } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { randomUUID } from "node:crypto";
14
+ import { getToolkitRoot } from "../../../sdk/paths.js";
15
+ // ─────────────────────────────────────────────────────────────────────────────
16
+ // Constants
17
+ // ─────────────────────────────────────────────────────────────────────────────
18
+ const FEEDBACK_DIR = ".feedback";
19
+ const FEEDBACK_FILE = "feedback.jsonl";
20
+ const TELEMETRY_FILE = "telemetry.jsonl";
21
+ const MAX_TELEMETRY_ENTRIES = 1000;
22
+ const MAX_FEEDBACK_ENTRIES = 500;
23
+ // ─────────────────────────────────────────────────────────────────────────────
24
+ // Store
25
+ // ─────────────────────────────────────────────────────────────────────────────
26
+ /**
27
+ * Get the feedback directory path, creating it if necessary.
28
+ */
29
+ async function ensureFeedbackDir(rootOverride) {
30
+ const root = rootOverride ?? getToolkitRoot();
31
+ const dir = join(root, FEEDBACK_DIR);
32
+ await fs.mkdir(dir, { recursive: true });
33
+ return dir;
34
+ }
35
+ /**
36
+ * Append a single JSON line to a JSONL file.
37
+ */
38
+ async function appendJsonl(filePath, entry) {
39
+ const line = JSON.stringify(entry) + "\n";
40
+ await fs.appendFile(filePath, line, "utf-8");
41
+ }
42
+ /**
43
+ * Read all entries from a JSONL file.
44
+ * Returns empty array if file doesn't exist.
45
+ */
46
+ async function readJsonl(filePath) {
47
+ try {
48
+ const content = await fs.readFile(filePath, "utf-8");
49
+ return content
50
+ .split("\n")
51
+ .filter((line) => line.trim().length > 0)
52
+ .map((line) => {
53
+ try {
54
+ return JSON.parse(line);
55
+ }
56
+ catch {
57
+ // Skip corrupted lines (e.g., from crash mid-write)
58
+ return null;
59
+ }
60
+ })
61
+ .filter((entry) => entry !== null);
62
+ }
63
+ catch (err) {
64
+ if (err.code === "ENOENT") {
65
+ return [];
66
+ }
67
+ throw err;
68
+ }
69
+ }
70
+ /**
71
+ * Rotate a JSONL file by keeping only the last N entries.
72
+ */
73
+ async function rotateJsonl(filePath, maxEntries) {
74
+ const entries = await readJsonl(filePath);
75
+ if (entries.length <= maxEntries)
76
+ return;
77
+ const kept = entries.slice(entries.length - maxEntries);
78
+ const content = kept.map((e) => JSON.stringify(e)).join("\n") + "\n";
79
+ await fs.writeFile(filePath, content, "utf-8");
80
+ }
81
+ // ─────────────────────────────────────────────────────────────────────────────
82
+ // Public API
83
+ // ─────────────────────────────────────────────────────────────────────────────
84
+ /**
85
+ * Submit a feedback entry from an agent.
86
+ */
87
+ export async function submitFeedback(entry, rootOverride) {
88
+ const dir = await ensureFeedbackDir(rootOverride);
89
+ const filePath = join(dir, FEEDBACK_FILE);
90
+ const full = {
91
+ id: randomUUID(),
92
+ ts: new Date().toISOString(),
93
+ ...entry,
94
+ };
95
+ await appendJsonl(filePath, full);
96
+ // Log to stderr for visibility (stdout is the MCP stdio transport - never write there)
97
+ console.error(`[FEEDBACK] ${full.category}: ${full.message}`);
98
+ return full;
99
+ }
100
+ /**
101
+ * Record a telemetry event (passive, fire-and-forget).
102
+ */
103
+ export async function recordTelemetry(entry, rootOverride) {
104
+ try {
105
+ const dir = await ensureFeedbackDir(rootOverride);
106
+ const filePath = join(dir, TELEMETRY_FILE);
107
+ const full = {
108
+ ts: new Date().toISOString(),
109
+ ...entry,
110
+ };
111
+ await appendJsonl(filePath, full);
112
+ // Rotate periodically (check every 100 writes based on simple modulo of time)
113
+ // We use a lightweight check: rotate if file > MAX * 1.5 entries
114
+ const now = Date.now();
115
+ if (now % 100 < 5) {
116
+ await rotateJsonl(filePath, MAX_TELEMETRY_ENTRIES);
117
+ }
118
+ }
119
+ catch {
120
+ // Telemetry is fire-and-forget; never block tool execution
121
+ }
122
+ }
123
+ /**
124
+ * List recent feedback entries.
125
+ */
126
+ export async function listFeedback(options, rootOverride) {
127
+ const dir = await ensureFeedbackDir(rootOverride);
128
+ const filePath = join(dir, FEEDBACK_FILE);
129
+ let entries = await readJsonl(filePath);
130
+ if (options?.category) {
131
+ entries = entries.filter((e) => e.category === options.category);
132
+ }
133
+ const limit = options?.limit ?? 50;
134
+ return entries.slice(-limit);
135
+ }
136
+ /**
137
+ * List recent telemetry entries.
138
+ */
139
+ export async function listTelemetry(options, rootOverride) {
140
+ const dir = await ensureFeedbackDir(rootOverride);
141
+ const filePath = join(dir, TELEMETRY_FILE);
142
+ let entries = await readJsonl(filePath);
143
+ if (options?.type) {
144
+ entries = entries.filter((e) => e.type === options.type);
145
+ }
146
+ const limit = options?.limit ?? 100;
147
+ return entries.slice(-limit);
148
+ }
149
+ /**
150
+ * Analyze feedback and telemetry to produce actionable insights.
151
+ */
152
+ export async function analyzeFeedback(rootOverride) {
153
+ const dir = await ensureFeedbackDir(rootOverride);
154
+ const feedback = await readJsonl(join(dir, FEEDBACK_FILE));
155
+ const telemetry = await readJsonl(join(dir, TELEMETRY_FILE));
156
+ // Category breakdown
157
+ const categoryBreakdown = {};
158
+ for (const entry of feedback) {
159
+ categoryBreakdown[entry.category] = (categoryBreakdown[entry.category] ?? 0) + 1;
160
+ }
161
+ // Hot spots - which tools/operations have the most issues
162
+ const issuesByTool = {};
163
+ const issuesByOperation = {};
164
+ const negativeFeedback = feedback.filter((e) => e.category !== "success");
165
+ for (const entry of negativeFeedback) {
166
+ if (entry.tool) {
167
+ issuesByTool[entry.tool] = (issuesByTool[entry.tool] ?? 0) + 1;
168
+ }
169
+ if (entry.operation) {
170
+ issuesByOperation[entry.operation] = (issuesByOperation[entry.operation] ?? 0) + 1;
171
+ }
172
+ }
173
+ // Tool usage from telemetry
174
+ const toolUsage = {};
175
+ for (const entry of telemetry.filter((t) => t.type === "tool_call")) {
176
+ const key = entry.tool ?? "unknown";
177
+ if (!toolUsage[key]) {
178
+ toolUsage[key] = { total: 0, errors: 0 };
179
+ }
180
+ toolUsage[key].total++;
181
+ if (!entry.ok) {
182
+ toolUsage[key].errors++;
183
+ }
184
+ }
185
+ // Resource usage from telemetry
186
+ const resourceUsage = {};
187
+ for (const entry of telemetry.filter((t) => t.type === "resource_fetch")) {
188
+ const uri = entry.resource_uri ?? "unknown";
189
+ resourceUsage[uri] = (resourceUsage[uri] ?? 0) + 1;
190
+ }
191
+ // Error patterns
192
+ const errorMessages = {};
193
+ for (const entry of telemetry.filter((t) => t.type === "error" && t.error_message)) {
194
+ const msg = entry.error_message;
195
+ // Normalize error messages by truncating at 100 chars
196
+ const normalized = msg.length > 100 ? msg.slice(0, 100) + "..." : msg;
197
+ errorMessages[normalized] = (errorMessages[normalized] ?? 0) + 1;
198
+ }
199
+ // High-severity items
200
+ const highSeverity = feedback.filter((e) => e.severity === "high");
201
+ // Actionable items
202
+ const actionableItems = [];
203
+ // Tools with high error rates
204
+ for (const [tool, usage] of Object.entries(toolUsage)) {
205
+ const errorRate = usage.errors / usage.total;
206
+ if (errorRate > 0.3 && usage.total >= 5) {
207
+ actionableItems.push(`Tool "${tool}" has ${Math.round(errorRate * 100)}% error rate (${usage.errors}/${usage.total}) - investigate error handling and documentation`);
208
+ }
209
+ }
210
+ // Gaps are high-priority feedback
211
+ const gaps = feedback.filter((e) => e.category === "gap");
212
+ for (const gap of gaps) {
213
+ actionableItems.push(`Documentation gap: ${gap.message}${gap.tool ? ` (tool: ${gap.tool})` : ""}`);
214
+ }
215
+ // Confusion items suggest unclear docs
216
+ const confusions = feedback.filter((e) => e.category === "confusion");
217
+ for (const confusion of confusions) {
218
+ actionableItems.push(`Unclear guidance: ${confusion.message}${confusion.tool ? ` (tool: ${confusion.tool})` : ""}`);
219
+ }
220
+ return {
221
+ summary: {
222
+ total_feedback: feedback.length,
223
+ total_telemetry: telemetry.length,
224
+ feedback_period: feedback.length > 0
225
+ ? { from: feedback[0].ts, to: feedback[feedback.length - 1].ts }
226
+ : null,
227
+ telemetry_period: telemetry.length > 0
228
+ ? { from: telemetry[0].ts, to: telemetry[telemetry.length - 1].ts }
229
+ : null,
230
+ },
231
+ category_breakdown: categoryBreakdown,
232
+ hot_spots: {
233
+ by_tool: sortDescending(issuesByTool),
234
+ by_operation: sortDescending(issuesByOperation),
235
+ },
236
+ tool_usage: toolUsage,
237
+ resource_usage: sortDescending(resourceUsage),
238
+ error_patterns: sortDescending(errorMessages),
239
+ high_severity: highSeverity,
240
+ actionable_items: actionableItems,
241
+ };
242
+ }
243
+ /**
244
+ * Rotate both feedback and telemetry files to prevent unbounded growth.
245
+ */
246
+ export async function rotateLogs(rootOverride) {
247
+ const dir = await ensureFeedbackDir(rootOverride);
248
+ await rotateJsonl(join(dir, FEEDBACK_FILE), MAX_FEEDBACK_ENTRIES);
249
+ await rotateJsonl(join(dir, TELEMETRY_FILE), MAX_TELEMETRY_ENTRIES);
250
+ const feedbackEntries = await readJsonl(join(dir, FEEDBACK_FILE));
251
+ const telemetryEntries = await readJsonl(join(dir, TELEMETRY_FILE));
252
+ return {
253
+ feedback_kept: feedbackEntries.length,
254
+ telemetry_kept: telemetryEntries.length,
255
+ };
256
+ }
257
+ // ─────────────────────────────────────────────────────────────────────────────
258
+ // Helpers
259
+ // ─────────────────────────────────────────────────────────────────────────────
260
+ function sortDescending(record) {
261
+ return Object.fromEntries(Object.entries(record).sort((a, b) => b[1] - a[1]));
262
+ }
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Persona Handler - Dispatch Table Pattern
2
+ * Persona Handler - Dispatch Table + Top-Level Router
3
3
  *
4
4
  * This module exports persona mode handlers using a dispatch table
5
- * instead of a giant switch statement. Each mode is in its own file
6
- * for better testability and maintainability.
5
+ * and provides the top-level `handlePersona` function that was previously
6
+ * (previously in the now-deleted handlers-consolidated.ts).
7
7
  *
8
8
  * Persona modes:
9
9
  * - get: Fetch single persona (LLM then analyzes the data)
@@ -13,7 +13,7 @@
13
13
  * - update: Update persona config
14
14
  * - delete: Delete persona (with confirmation)
15
15
  * - create/clone: Create new persona from template or clone existing
16
- * - intent: Direct Intent Architect invocation for qualification
16
+ * - schema: Get persona input schema (dashboard columns, types)
17
17
  *
18
18
  * REMOVED (LLM does these):
19
19
  * - analyze: LLM analyzes data from 'get'
@@ -23,6 +23,10 @@
23
23
  * - snapshot/version_create, history/version_list, version_get
24
24
  * - version_compare, restore/version_restore, version_policy
25
25
  */
26
+ import { resolvePersona } from "../utils.js";
27
+ import { checkDeprecatedParams, addDeprecationWarnings } from "../deprecation.js";
28
+ import { handleData } from "../data/index.js";
29
+ import { handleWorkflow } from "../workflow/index.js";
26
30
  // Import mode handlers
27
31
  import { handleGet } from "./get.js";
28
32
  import { handleList } from "./list.js";
@@ -31,7 +35,7 @@ import { handleSanitize } from "./sanitize.js";
31
35
  import { handleUpdate } from "./update.js";
32
36
  import { handleDelete } from "./delete.js";
33
37
  import { handleCreate } from "./create.js";
34
- import { handleIntent } from "./intent.js";
38
+ import { handleSchema } from "./schema.js";
35
39
  /**
36
40
  * Dispatch table for persona modes
37
41
  *
@@ -48,7 +52,7 @@ export const PERSONA_MODE_HANDLERS = {
48
52
  delete: handleDelete,
49
53
  create: handleCreate,
50
54
  clone: handleCreate, // Clone uses same handler as create
51
- intent: handleIntent,
55
+ schema: handleSchema,
52
56
  };
53
57
  /**
54
58
  * Check if a mode has been extracted to this module
@@ -70,7 +74,232 @@ export { handleSanitize } from "./sanitize.js";
70
74
  export { handleUpdate } from "./update.js";
71
75
  export { handleDelete } from "./delete.js";
72
76
  export { handleCreate } from "./create.js";
73
- export { handleIntent } from "./intent.js";
74
- // REMOVED: handleCompare, handleAnalyze - LLM does analysis/comparison
77
+ export { handleSchema } from "./schema.js";
75
78
  // Version management
76
79
  export { handleVersion, isVersionMode } from "./version.js";
80
+ // ═══════════════════════════════════════════════════════════════════════════
81
+ // Top-level Persona Router
82
+ // ═══════════════════════════════════════════════════════════════════════════
83
+ import { handleVersion } from "./version.js";
84
+ /**
85
+ * Top-level persona handler that routes to mode-specific handlers.
86
+ *
87
+ * Handles:
88
+ * - Data sub-resource routing (data={method:...})
89
+ * - Legacy workflow routing (optimize flag, workflow_def without update mode)
90
+ * - Mode resolution and dispatch
91
+ * - Version management modes
92
+ */
93
+ export async function handlePersona(args, client, getTemplateId, createClientForEnv, versionContext) {
94
+ const deprecationWarnings = checkDeprecatedParams(args);
95
+ for (const warning of deprecationWarnings) {
96
+ console.warn(`[persona] Deprecation: ${warning}`);
97
+ }
98
+ const id = args.id;
99
+ const identifier = args.identifier;
100
+ const idOrName = id ?? identifier;
101
+ // Support both 'method' (new) and 'mode' (legacy) - method takes precedence
102
+ const method = args.method;
103
+ const mode = method ?? args.mode;
104
+ // ═══════════════════════════════════════════════════════════════════════════
105
+ // DATA SUB-RESOURCE: Handle data={method:...} early
106
+ // ═══════════════════════════════════════════════════════════════════════════
107
+ const dataArg = args.data;
108
+ if (dataArg && typeof dataArg === "object") {
109
+ if (!idOrName) {
110
+ return {
111
+ error: "Data operations require persona id",
112
+ hint: "Use persona(id=\"abc\", data={method:\"list\"})",
113
+ };
114
+ }
115
+ const dataMethod = dataArg.method;
116
+ if (!dataMethod) {
117
+ return {
118
+ error: "Data operations require explicit method",
119
+ hint: "Use data={method:\"list|schema|upload|copy|delete|embed|search\", ...}",
120
+ valid_methods: ["list", "schema", "upload", "copy", "delete", "embed", "search"],
121
+ };
122
+ }
123
+ const methodToMode = {
124
+ "list": "list",
125
+ "schema": "schema",
126
+ "upload": "upload",
127
+ "copy": "dashboard_clone",
128
+ "delete": "delete",
129
+ "embed": "embed",
130
+ "search": "search",
131
+ };
132
+ const mappedMode = methodToMode[dataMethod];
133
+ if (!mappedMode) {
134
+ return {
135
+ error: `Unknown data method: ${dataMethod}`,
136
+ valid_methods: Object.keys(methodToMode),
137
+ };
138
+ }
139
+ const dataArgs = {
140
+ persona_id: idOrName,
141
+ mode: mappedMode,
142
+ };
143
+ if (dataArg.from)
144
+ dataArgs.source_persona_id = dataArg.from;
145
+ if (dataArg.sanitize !== undefined)
146
+ dataArgs.sanitize = dataArg.sanitize;
147
+ if (dataArg.path)
148
+ dataArgs.file_path = dataArg.path;
149
+ if (dataArg.content)
150
+ dataArgs.content = dataArg.content;
151
+ if (dataArg.file_id)
152
+ dataArgs.file_id = dataArg.file_id;
153
+ else if (dataArg.id)
154
+ dataArgs.file_id = dataArg.id;
155
+ if (dataArg.enabled !== undefined)
156
+ dataArgs.embed = dataArg.enabled;
157
+ if (dataArg.query)
158
+ dataArgs.query = dataArg.query;
159
+ const readFile = async (path) => {
160
+ const fs = await import("fs/promises");
161
+ return fs.readFile(path);
162
+ };
163
+ return handleData(dataArgs, client, readFile);
164
+ }
165
+ // ═══════════════════════════════════════════════════════════════════════════
166
+ // ROUTING: Persona operations
167
+ // ═══════════════════════════════════════════════════════════════════════════
168
+ // LLM-DRIVEN ARCHITECTURE:
169
+ // - persona tool: config changes only (name, description, widgets)
170
+ // - workflow tool: ALL workflow changes (get → modify → deploy)
171
+ // mode="update" → modular handleUpdate (config changes, workflow_spec for rewire/remove only)
172
+ if (mode === "update" && idOrName) {
173
+ return getPersonaModeHandler("update")(args, client);
174
+ }
175
+ // Legacy: optimize flag routes to workflow handler
176
+ const optimize = args.optimize;
177
+ const workflowDef = args.workflow_def ?? args.workflow;
178
+ if (optimize || (idOrName && workflowDef && mode !== "update")) {
179
+ const workflowArgs = {
180
+ ...args,
181
+ persona_id: idOrName,
182
+ };
183
+ delete workflowArgs.id;
184
+ delete workflowArgs.identifier;
185
+ return handleWorkflow(workflowArgs, client, getTemplateId);
186
+ }
187
+ // ═══════════════════════════════════════════════════════════════════════════
188
+ // Standard persona operations (get, list, compare, version management)
189
+ // ═══════════════════════════════════════════════════════════════════════════
190
+ const fromParam = args.from;
191
+ const cloneFromParam = args.clone_from;
192
+ const templateIdParam = args.template_id;
193
+ const hasBase = fromParam || cloneFromParam || templateIdParam;
194
+ let effectiveMode = mode;
195
+ if (!effectiveMode) {
196
+ const availableMethods = ["list", "get", "create", "update", "delete", "analyze", "sanitize", "snapshot", "history", "restore", "compare"];
197
+ if (idOrName && args.proto_config) {
198
+ return {
199
+ error: "Explicit method required",
200
+ message: "You provided id and proto_config. Did you want to update this persona?",
201
+ suggested_method: "update",
202
+ hint: "Use method='update' to apply changes",
203
+ example: `persona(method="update", id="${idOrName}", config={...})`,
204
+ };
205
+ }
206
+ else if (idOrName) {
207
+ return {
208
+ error: "Explicit method required",
209
+ message: "You provided a persona id/name. What operation would you like to perform?",
210
+ valid_methods: ["get", "update", "delete", "sanitize", "snapshot", "history", "restore"],
211
+ hint: "LLM does analysis/comparison. Use method='get' to fetch data, then reason about it.",
212
+ example: `persona(method="get", id="${idOrName}")`,
213
+ };
214
+ }
215
+ else if (hasBase || (args.name && args.type)) {
216
+ return {
217
+ error: "Explicit method required",
218
+ message: "You provided creation parameters. Did you want to create a new persona?",
219
+ suggested_method: "create",
220
+ hint: "Use method='create'",
221
+ example: `persona(method="create", name="...", type="voice")`,
222
+ };
223
+ }
224
+ else {
225
+ return {
226
+ error: "Explicit method required",
227
+ message: "All persona operations require explicit method parameter",
228
+ valid_methods: availableMethods,
229
+ examples: [
230
+ 'persona(method="list") - list all personas',
231
+ 'persona(method="get", id="...") - get specific persona',
232
+ 'persona(method="create", name="...", type="voice") - create new',
233
+ 'persona(method="update", id="...", config={...}) - update config',
234
+ 'persona(method="delete", id="...", confirm=true) - delete',
235
+ ],
236
+ };
237
+ }
238
+ }
239
+ // ─────────────────────────────────────────────────────────────────────────
240
+ // Dispatch to handlers via dispatch table
241
+ // ─────────────────────────────────────────────────────────────────────────
242
+ if (hasExtractedHandler(effectiveMode)) {
243
+ const handler = getPersonaModeHandler(effectiveMode);
244
+ if (handler) {
245
+ let extraContext = undefined;
246
+ if (effectiveMode === "create" || effectiveMode === "clone") {
247
+ extraContext = getTemplateId;
248
+ }
249
+ else if (effectiveMode === "compare") {
250
+ extraContext = createClientForEnv;
251
+ }
252
+ const result = await handler(args, client, extraContext);
253
+ return addDeprecationWarnings(result, deprecationWarnings);
254
+ }
255
+ }
256
+ switch (effectiveMode) {
257
+ // Fallback cases for modes already in dispatch table (should not be reached)
258
+ case "get":
259
+ case "list":
260
+ case "templates":
261
+ case "sanitize":
262
+ case "update":
263
+ case "delete": {
264
+ return { error: `Mode "${effectiveMode}" should be handled by extracted handler` };
265
+ }
266
+ // Clone and create - handled by dispatch table
267
+ case "clone":
268
+ case "create": {
269
+ return { error: `Mode "${effectiveMode}" should be handled by extracted handler` };
270
+ }
271
+ // Analyze and compare - LLM does this, not MCP
272
+ case "analyze":
273
+ case "compare": {
274
+ return {
275
+ error: `Method "${effectiveMode}" removed - LLM does analysis/comparison`,
276
+ hint: "Use method='get' to fetch persona data, then do your own analysis/comparison.",
277
+ example: `persona(method="get", id="...", include_workflow=true)`,
278
+ };
279
+ }
280
+ // ─────────────── Version Management Modes ───────────────
281
+ case "snapshot":
282
+ case "history":
283
+ case "restore":
284
+ case "version_create":
285
+ case "version_list":
286
+ case "version_get":
287
+ case "version_compare":
288
+ case "version_restore":
289
+ case "version_policy": {
290
+ if (!idOrName) {
291
+ return { error: `id required for ${effectiveMode} mode` };
292
+ }
293
+ if (!versionContext) {
294
+ return { error: "Version tracking not configured. Provide workspaceRoot in context." };
295
+ }
296
+ const persona = await resolvePersona(client, idOrName);
297
+ if (!persona) {
298
+ return { error: `Persona not found: ${idOrName}` };
299
+ }
300
+ return handleVersion(effectiveMode, args, client, persona, versionContext);
301
+ }
302
+ default:
303
+ return { error: `Unknown mode: ${effectiveMode}` };
304
+ }
305
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Schema Handler - Get persona input schema (dashboard columns, types)
3
+ */
4
+ import { resolvePersona } from "../utils.js";
5
+ export async function handleSchema(args, client) {
6
+ const id = (args.id ?? args.identifier);
7
+ if (!id) {
8
+ return { error: "id required for schema mode" };
9
+ }
10
+ const persona = await resolvePersona(client, id);
11
+ if (!persona) {
12
+ return { error: `Persona not found: ${id}` };
13
+ }
14
+ try {
15
+ const schema = await client.getDashboardSchema(persona.id, persona.id);
16
+ return {
17
+ mode: "schema",
18
+ persona_id: persona.id,
19
+ persona_name: persona.name,
20
+ schema: schema,
21
+ column_count: schema.columns?.length ?? 0,
22
+ };
23
+ }
24
+ catch (error) {
25
+ return { error: `Failed to get schema: ${error instanceof Error ? error.message : String(error)}` };
26
+ }
27
+ }
@@ -4,7 +4,7 @@
4
4
  * Unified reference tool for envs, actions, templates, patterns, concepts, guidance.
5
5
  */
6
6
  import { errorResult } from "../types.js";
7
- import { AGENT_CATALOG, WORKFLOW_PATTERNS, QUALIFYING_QUESTIONS, PLATFORM_CONCEPTS, WORKFLOW_EXECUTION_MODEL, COMMON_MISTAKES, DEBUG_CHECKLIST, GUIDANCE_TOPICS, getAgentByName, getWidgetsForPersonaType, checkTypeCompatibility, getQualifyingQuestionsByCategory, getRequiredQualifyingQuestions, getConceptByTerm, suggestAgentsForUseCase, validateWorkflowPrompt, } from "../../knowledge.js";
7
+ import { AGENT_CATALOG, WORKFLOW_PATTERNS, QUALIFYING_QUESTIONS, PLATFORM_CONCEPTS, WORKFLOW_EXECUTION_MODEL, COMMON_MISTAKES, DEBUG_CHECKLIST, GUIDANCE_TOPICS, getAgentByName, getWidgetsForPersonaType, checkTypeCompatibility, getQualifyingQuestionsByCategory, getRequiredQualifyingQuestions, getConceptByTerm, suggestAgentsForUseCase, } from "../../knowledge.js";
8
8
  /**
9
9
  * Handle reference tool requests - unified lookup for all reference data
10
10
  */
@@ -284,10 +284,12 @@ export async function handleReference(args, context) {
284
284
  note: result?.note,
285
285
  };
286
286
  }
287
- // Validate prompt
287
+ // Validate prompt (deprecated — LLM responsibility)
288
288
  if (args.validate_prompt) {
289
- const result = validateWorkflowPrompt(args.validate_prompt);
290
- return result;
289
+ return {
290
+ error: "Workflow prompt validation is the LLM's responsibility. Use workflow(mode='validate') for structural validation.",
291
+ _deprecated: true,
292
+ };
291
293
  }
292
294
  // ─────────────────────────────────────────────────────────────────────────
293
295
  // type="demo_kits" or demo_kits=true - List demo kits