ema-mcp-toolkit 0.2.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.
Files changed (49) hide show
  1. package/README.md +338 -0
  2. package/config.example.yaml +32 -0
  3. package/dist/cli/index.js +333 -0
  4. package/dist/config.js +136 -0
  5. package/dist/emaClient.js +398 -0
  6. package/dist/index.js +109 -0
  7. package/dist/mcp/handlers-consolidated.js +851 -0
  8. package/dist/mcp/index.js +15 -0
  9. package/dist/mcp/prompts.js +1753 -0
  10. package/dist/mcp/resources.js +624 -0
  11. package/dist/mcp/server.js +4585 -0
  12. package/dist/mcp/tools-consolidated.js +590 -0
  13. package/dist/mcp/tools-legacy.js +736 -0
  14. package/dist/models.js +8 -0
  15. package/dist/scheduler.js +21 -0
  16. package/dist/sdk/client.js +788 -0
  17. package/dist/sdk/config.js +136 -0
  18. package/dist/sdk/contracts.js +429 -0
  19. package/dist/sdk/generation-schema.js +189 -0
  20. package/dist/sdk/index.js +39 -0
  21. package/dist/sdk/knowledge.js +2780 -0
  22. package/dist/sdk/models.js +8 -0
  23. package/dist/sdk/state.js +88 -0
  24. package/dist/sdk/sync-options.js +216 -0
  25. package/dist/sdk/sync.js +220 -0
  26. package/dist/sdk/validation-rules.js +355 -0
  27. package/dist/sdk/workflow-generator.js +291 -0
  28. package/dist/sdk/workflow-intent.js +1585 -0
  29. package/dist/state.js +88 -0
  30. package/dist/sync.js +416 -0
  31. package/dist/syncOptions.js +216 -0
  32. package/dist/ui.js +334 -0
  33. package/docs/advisor-comms-assistant-fixes.md +175 -0
  34. package/docs/api-contracts.md +216 -0
  35. package/docs/auto-builder-analysis.md +271 -0
  36. package/docs/data-architecture.md +166 -0
  37. package/docs/ema-auto-builder-guide.html +394 -0
  38. package/docs/ema-user-guide.md +1121 -0
  39. package/docs/mcp-tools-guide.md +149 -0
  40. package/docs/naming-conventions.md +218 -0
  41. package/docs/tool-consolidation-proposal.md +427 -0
  42. package/package.json +95 -0
  43. package/resources/templates/chat-ai/README.md +119 -0
  44. package/resources/templates/chat-ai/persona-config.json +111 -0
  45. package/resources/templates/dashboard-ai/README.md +156 -0
  46. package/resources/templates/dashboard-ai/persona-config.json +180 -0
  47. package/resources/templates/voice-ai/README.md +123 -0
  48. package/resources/templates/voice-ai/persona-config.json +74 -0
  49. package/resources/templates/voice-ai/workflow-prompt.md +120 -0
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Environment Configuration
3
+ *
4
+ * Defines Ema API environments and service settings.
5
+ * Used by both MCP server and CLI/service modes.
6
+ */
7
+ import fs from "node:fs";
8
+ import yaml from "js-yaml";
9
+ import { z } from "zod";
10
+ // ─────────────────────────────────────────────────────────────────────────────
11
+ // Schema
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
+ const EnvironmentSchema = z.object({
14
+ name: z.string().min(1),
15
+ baseUrl: z.string().url(),
16
+ bearerTokenEnv: z.string().min(1),
17
+ isMaster: z.boolean().optional().default(false),
18
+ // Deprecated but accepted for backward compatibility
19
+ userId: z.string().optional(),
20
+ templateIdMap: z.record(z.string()).optional(),
21
+ });
22
+ const SchedulerSchema = z.object({
23
+ intervalSeconds: z.number().int().positive().optional(),
24
+ cron: z.string().min(1).optional(),
25
+ }).strict().refine((v) => v.cron || v.intervalSeconds, { message: "scheduler must set either intervalSeconds or cron" });
26
+ const ServiceSchema = z.object({
27
+ stateDbPath: z.string().min(1).optional().default("./ema-state.sqlite3"),
28
+ scheduler: SchedulerSchema.optional(),
29
+ }).strict();
30
+ // Legacy schema for backward compatibility
31
+ const LegacyRouteRuleSchema = z.object({
32
+ personaIds: z.array(z.string()).default([]),
33
+ targetEnvs: z.array(z.string()).default([]),
34
+ nameGlobs: z.array(z.string()).optional().default([]),
35
+ nameRegexes: z.array(z.string()).optional().default([]),
36
+ namePrefixes: z.array(z.string()).optional().default([]),
37
+ }).passthrough();
38
+ const AppConfigSchema = z.object({
39
+ environments: z.array(EnvironmentSchema).min(1),
40
+ service: ServiceSchema.optional(),
41
+ dryRun: z.boolean().optional().default(false),
42
+ verbose: z.boolean().optional().default(false),
43
+ // Legacy fields (kept for backward compatibility)
44
+ routing: z.array(LegacyRouteRuleSchema).optional(),
45
+ scheduler: SchedulerSchema.optional(),
46
+ stateDbPath: z.string().optional(),
47
+ eventSharedSecretEnv: z.string().optional(),
48
+ }).refine((cfg) => {
49
+ // Validate at most one master
50
+ const masters = cfg.environments.filter((e) => e.isMaster);
51
+ return masters.length <= 1;
52
+ }, { message: "At most one environment can be isMaster: true" });
53
+ // ─────────────────────────────────────────────────────────────────────────────
54
+ // Loading functions
55
+ // ─────────────────────────────────────────────────────────────────────────────
56
+ export function loadConfig(path) {
57
+ const raw = fs.readFileSync(path, "utf8");
58
+ const parsed = yaml.load(raw);
59
+ if (!parsed || typeof parsed !== "object") {
60
+ throw new Error("Invalid config YAML");
61
+ }
62
+ const res = AppConfigSchema.safeParse(parsed);
63
+ if (!res.success) {
64
+ const errors = res.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`);
65
+ throw new Error(`Invalid config: ${errors.join("; ")}`);
66
+ }
67
+ return res.data;
68
+ }
69
+ export function loadConfigOptional(path) {
70
+ try {
71
+ return loadConfig(path);
72
+ }
73
+ catch (e) {
74
+ if (e && typeof e === "object" && "code" in e && e.code === "ENOENT") {
75
+ return null;
76
+ }
77
+ const msg = e instanceof Error ? e.message : String(e);
78
+ if (msg.includes("ENOENT"))
79
+ return null;
80
+ throw e;
81
+ }
82
+ }
83
+ export function loadConfigFromJsonEnv() {
84
+ const raw = process.env.EMA_CONFIG_JSON ?? process.env.EMA_AGENT_SYNC_CONFIG_JSON;
85
+ if (!raw)
86
+ return null;
87
+ let parsed;
88
+ try {
89
+ parsed = JSON.parse(raw);
90
+ }
91
+ catch {
92
+ throw new Error("EMA_CONFIG_JSON is not valid JSON");
93
+ }
94
+ const res = validateConfig(parsed);
95
+ if (!res.ok) {
96
+ throw new Error(`Invalid EMA_CONFIG_JSON: ${res.errors.join("; ")}`);
97
+ }
98
+ return res.config;
99
+ }
100
+ export function validateConfig(input) {
101
+ const res = AppConfigSchema.safeParse(input);
102
+ if (!res.success) {
103
+ return { ok: false, errors: res.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`) };
104
+ }
105
+ return { ok: true, config: res.data };
106
+ }
107
+ export function configToYaml(cfg) {
108
+ return yaml.dump(cfg, { noRefs: true, sortKeys: true });
109
+ }
110
+ // ─────────────────────────────────────────────────────────────────────────────
111
+ // Utilities
112
+ // ─────────────────────────────────────────────────────────────────────────────
113
+ export function resolveBearerToken(envVarName) {
114
+ const v = process.env[envVarName];
115
+ if (!v)
116
+ throw new Error(`Missing bearer token env var: ${envVarName}`);
117
+ // Strip "Bearer " prefix if user included it
118
+ const trimmed = v.trim();
119
+ if (trimmed.toLowerCase().startsWith("bearer ")) {
120
+ return trimmed.slice(7).trim();
121
+ }
122
+ return trimmed;
123
+ }
124
+ export function assertEnvVarsPresent(cfg) {
125
+ for (const e of cfg.environments) {
126
+ if (!process.env[e.bearerTokenEnv]) {
127
+ throw new Error(`Missing required env var for ${e.name}: ${e.bearerTokenEnv}`);
128
+ }
129
+ }
130
+ }
131
+ export function getMasterEnv(cfg) {
132
+ return cfg.environments.find((e) => e.isMaster);
133
+ }
134
+ export function getEnvByName(cfg, name) {
135
+ return cfg.environments.find((e) => e.name === name);
136
+ }
@@ -0,0 +1,429 @@
1
+ /**
2
+ * API Contract Validation using Zod
3
+ *
4
+ * This module defines comprehensive Zod schemas for validating API responses at runtime.
5
+ * These schemas should match the OpenAPI spec (auto-generated from Pydantic).
6
+ *
7
+ * IMPORTANT: Keep these schemas in sync with the backend Pydantic models.
8
+ * Run `npm run check:contracts` to detect drift.
9
+ *
10
+ * Benefits:
11
+ * - Runtime type validation (catch malformed API responses)
12
+ * - Better error messages than TypeScript alone
13
+ * - Can generate TypeScript types with z.infer<>
14
+ *
15
+ * Usage:
16
+ * import { PersonaDTOSchema, validateResponse } from "./contracts";
17
+ *
18
+ * const result = validateResponse(PersonaDTOSchema, apiResponse);
19
+ * // result is typed as PersonaDTO
20
+ */
21
+ import { z } from "zod";
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+ // Base Types & Utilities
24
+ // ─────────────────────────────────────────────────────────────────────────────
25
+ /** UUID string format - validates format but not actual UUID validity */
26
+ export const UUIDSchema = z.string().min(1);
27
+ /** ISO 8601 datetime string */
28
+ export const DateTimeSchema = z.string().refine((val) => !isNaN(Date.parse(val)), { message: "Invalid datetime format" });
29
+ /** Nullable wrapper for optional fields that can be explicitly null */
30
+ export const nullable = (schema) => schema.nullable().optional();
31
+ // ─────────────────────────────────────────────────────────────────────────────
32
+ // Enums (match backend PersonaTriggerTypeEnum, PersonaStateEnum, etc.)
33
+ // ─────────────────────────────────────────────────────────────────────────────
34
+ /** Persona trigger types - how the AI Employee is activated */
35
+ export const PersonaTriggerTypeSchema = z.enum([
36
+ "TRIGGER_TYPE_CHATBOT",
37
+ "TRIGGER_TYPE_WEBAPP",
38
+ "TRIGGER_TYPE_GCHAT",
39
+ "TRIGGER_TYPE_SLACK",
40
+ "TRIGGER_TYPE_MSTEAMS",
41
+ "TRIGGER_TYPE_EMAIL",
42
+ "TRIGGER_TYPE_VOICE",
43
+ "TRIGGER_TYPE_DOCUMENT",
44
+ "TRIGGER_TYPE_DASHBOARD",
45
+ "TRIGGER_TYPE_ORCHESTRATION",
46
+ "TRIGGER_TYPE_API",
47
+ ]);
48
+ /** Persona state - current operational status */
49
+ export const PersonaStateSchema = z.enum([
50
+ "DISABLED_BY_USER",
51
+ "DISABLED_BY_SYSTEM",
52
+ "READY",
53
+ "DRAFT",
54
+ ]);
55
+ /** Persona access level - who can use the AI Employee */
56
+ export const PersonaAccessLevelSchema = z.enum([
57
+ "PERSONA_ACCESS_LEVEL_PRIVATE",
58
+ "PERSONA_ACCESS_LEVEL_TEAM",
59
+ "PERSONA_ACCESS_LEVEL_PUBLIC",
60
+ ]);
61
+ /** Conversation source - where the conversation originated */
62
+ export const ConversationSourceSchema = z.enum([
63
+ "webapp",
64
+ "chatbot",
65
+ "gchat",
66
+ "msteams",
67
+ "slack",
68
+ "email",
69
+ "voice",
70
+ ]);
71
+ /** Message sentiment for feedback */
72
+ export const SentimentSchema = z.enum(["positive", "negative", "neutral"]);
73
+ /** Processing result status */
74
+ export const ProcessingResultSchema = z.enum([
75
+ "success",
76
+ "warning",
77
+ "error",
78
+ "in_progress",
79
+ "indexing",
80
+ ]);
81
+ // ─────────────────────────────────────────────────────────────────────────────
82
+ // Welcome Messages
83
+ // ─────────────────────────────────────────────────────────────────────────────
84
+ export const WelcomeMessageSingleSchema = z.object({
85
+ display_text: z.string().optional(),
86
+ actual_prompt: z.string().optional(),
87
+ }).passthrough();
88
+ export const WelcomeMessageSectionSchema = z.object({
89
+ title: z.string().optional(),
90
+ messages: z.array(WelcomeMessageSingleSchema).optional(),
91
+ }).passthrough();
92
+ export const WelcomeMessageSchema = z.object({
93
+ sections: z.array(WelcomeMessageSectionSchema).optional(),
94
+ is_default: z.boolean().optional(),
95
+ }).passthrough();
96
+ // ─────────────────────────────────────────────────────────────────────────────
97
+ // Persona (AI Employee)
98
+ // ─────────────────────────────────────────────────────────────────────────────
99
+ /** AI Employee (Persona) data transfer object */
100
+ export const PersonaDTOSchema = z.object({
101
+ /** Unique identifier for the AI Employee */
102
+ id: z.string(),
103
+ /** ID of the template this persona was created from */
104
+ template_id: z.string().optional().nullable(),
105
+ templateId: z.string().optional().nullable(), // Legacy camelCase variant
106
+ /** ID of the workflow powering this AI Employee */
107
+ workflow_id: z.string().optional().nullable(),
108
+ /** Complete workflow definition */
109
+ workflow_def: z.record(z.unknown()).optional().nullable(),
110
+ /** Workflow interface defining inputs and outputs */
111
+ workflow_interface: z.record(z.unknown()).optional().nullable(),
112
+ /** Display name of the AI Employee */
113
+ name: z.string().optional(),
114
+ display_name: z.string().optional(),
115
+ /** Human-readable description */
116
+ description: z.string().optional().nullable(),
117
+ /** Protocol buffer configuration */
118
+ proto_config: z.record(z.unknown()).optional().nullable(),
119
+ /** Current state (READY, DRAFT, DISABLED_BY_USER, DISABLED_BY_SYSTEM) */
120
+ status: z.string().optional(),
121
+ computed_state: PersonaStateSchema.optional(),
122
+ /** Log of status changes and metadata */
123
+ status_log: z.record(z.unknown()).optional().nullable(),
124
+ /** Welcome messages displayed when starting a conversation */
125
+ welcome_messages: WelcomeMessageSchema.optional().nullable(),
126
+ /** Whether this persona supports projects */
127
+ has_project_template: z.boolean().optional(),
128
+ /** Access level controlling who can use this AI Employee */
129
+ access_level: z.string().optional(),
130
+ /** How this AI Employee is triggered */
131
+ trigger_type: z.string().optional(),
132
+ /** ID of the associated workflow dashboard */
133
+ workflow_dashboard_id: z.string().optional().nullable(),
134
+ /** Type of interaction supported */
135
+ persona_interaction_type: z.string().optional(),
136
+ /** Whether this AI Employee can be embedded */
137
+ embedding_enabled: z.boolean().optional(),
138
+ /** Whether user has enabled this persona */
139
+ enabled_by_user: z.boolean().optional(),
140
+ /** Creation timestamp */
141
+ created_at: DateTimeSchema.optional().nullable(),
142
+ /** Last update timestamp */
143
+ updated_at: DateTimeSchema.optional().nullable(),
144
+ }).passthrough();
145
+ /** Response from list personas endpoint */
146
+ export const GetPersonasForTenantResponseSchema = z.object({
147
+ personas: z.array(PersonaDTOSchema).optional(),
148
+ configs: z.array(PersonaDTOSchema).optional(), // Legacy field
149
+ }).passthrough();
150
+ /** Request to create an AI Employee */
151
+ export const CreatePersonaRequestSchema = z.object({
152
+ name: z.string(),
153
+ description: z.string().optional(),
154
+ template_id: z.string().optional(),
155
+ source_persona_id: z.string().optional(),
156
+ proto_config: z.record(z.unknown()).optional(),
157
+ welcome_messages: WelcomeMessageSchema.optional(),
158
+ trigger_type: z.string().optional(),
159
+ });
160
+ /** Response from create persona endpoint */
161
+ export const CreatePersonaResponseSchema = z.object({
162
+ persona_id: z.string().optional(),
163
+ id: z.string().optional(),
164
+ status: z.string().optional(),
165
+ }).passthrough();
166
+ /** Request to update an AI Employee */
167
+ export const UpdatePersonaRequestSchema = z.object({
168
+ persona_id: z.string(),
169
+ name: z.string().optional(),
170
+ description: z.string().optional(),
171
+ proto_config: z.record(z.unknown()).optional(),
172
+ welcome_messages: WelcomeMessageSchema.optional(),
173
+ workflow: z.record(z.unknown()).optional(),
174
+ embedding_enabled: z.boolean().optional(),
175
+ enabled_by_user: z.boolean().optional(),
176
+ status_log: z.record(z.unknown()).optional(),
177
+ });
178
+ // ─────────────────────────────────────────────────────────────────────────────
179
+ // Actions (Agents in UI)
180
+ // ─────────────────────────────────────────────────────────────────────────────
181
+ export const ActionParameterTypeSchema = z.object({
182
+ well_known_type: z.number().optional(),
183
+ wellKnownType: z.number().optional(),
184
+ is_list: z.boolean().optional(),
185
+ isList: z.boolean().optional(),
186
+ }).passthrough();
187
+ export const ActionParameterSchema = z.object({
188
+ name: z.string().optional(),
189
+ description: z.string().optional(),
190
+ type: ActionParameterTypeSchema.optional(),
191
+ required: z.boolean().optional(),
192
+ default_value: z.unknown().optional(),
193
+ }).passthrough();
194
+ export const ActionDTOSchema = z.object({
195
+ /** Unique identifier for the action */
196
+ id: z.string(),
197
+ /** Display name of the action (shown as "Agent" in UI) */
198
+ name: z.string().optional(),
199
+ /** Description of what the action does */
200
+ description: z.string().optional(),
201
+ /** Category or type of the action */
202
+ category: z.string().optional(),
203
+ /** Input parameters for the action */
204
+ inputs: z.array(ActionParameterSchema).optional(),
205
+ /** Output parameters from the action */
206
+ outputs: z.array(ActionParameterSchema).optional(),
207
+ /** Whether the action is enabled */
208
+ enabled: z.boolean().optional(),
209
+ /** Whether the action is deprecated */
210
+ deprecated: z.boolean().optional(),
211
+ /** Version of the action */
212
+ version: z.string().optional(),
213
+ /** Icon URL or identifier */
214
+ icon: z.string().optional(),
215
+ /** Tags for categorization */
216
+ tags: z.array(z.string()).optional(),
217
+ /** The workflow this action belongs to */
218
+ workflow_id: z.string().optional(),
219
+ /** Creation timestamp */
220
+ created_at: DateTimeSchema.optional(),
221
+ /** Last update timestamp */
222
+ updated_at: DateTimeSchema.optional(),
223
+ }).passthrough();
224
+ export const ListActionsResponseSchema = z.object({
225
+ actions: z.array(ActionDTOSchema).optional(),
226
+ }).passthrough();
227
+ // ─────────────────────────────────────────────────────────────────────────────
228
+ // Conversations
229
+ // ─────────────────────────────────────────────────────────────────────────────
230
+ export const ConversationDTOSchema = z.object({
231
+ id: z.string(),
232
+ display_name: z.string().optional().nullable(),
233
+ created_at: DateTimeSchema.optional().nullable(),
234
+ updated_at: DateTimeSchema.optional().nullable(),
235
+ is_blocked: z.boolean().optional().nullable(),
236
+ is_deleted: z.boolean().optional().nullable(),
237
+ persona_id: z.string().optional().nullable(),
238
+ persona_display_name: z.string().optional().nullable(),
239
+ persona_project_id: z.string().optional().nullable(),
240
+ persona_project_display_name: z.string().optional().nullable(),
241
+ status: z.string().optional().nullable(),
242
+ status_desc: z.string().optional().nullable(),
243
+ }).passthrough();
244
+ // ─────────────────────────────────────────────────────────────────────────────
245
+ // Messages
246
+ // ─────────────────────────────────────────────────────────────────────────────
247
+ export const SnippetDTOSchema = z.object({
248
+ id: z.string().optional(),
249
+ type: z.string().optional(),
250
+ content: z.record(z.unknown()).optional(),
251
+ context: z.string().optional(),
252
+ }).passthrough();
253
+ export const MessageDTOSchema = z.object({
254
+ id: z.string(),
255
+ content_payload: z.record(z.string()).optional().nullable(),
256
+ sender_id: z.string().optional().nullable(),
257
+ created_at: DateTimeSchema.optional().nullable(),
258
+ updated_at: DateTimeSchema.optional().nullable(),
259
+ sentiment: SentimentSchema.optional().nullable(),
260
+ feedback_content: z.string().optional().nullable(),
261
+ snippets: z.array(SnippetDTOSchema).optional().nullable(),
262
+ processing_status: z.string().optional().nullable(),
263
+ }).passthrough();
264
+ // ─────────────────────────────────────────────────────────────────────────────
265
+ // Pagination
266
+ // ─────────────────────────────────────────────────────────────────────────────
267
+ export const PaginationMetaSchema = z.object({
268
+ total: z.number(),
269
+ next_token: z.string().optional().nullable(),
270
+ prev_token: z.string().optional().nullable(),
271
+ });
272
+ export const PaginatedConversationsSchema = z.object({
273
+ conversations: z.array(ConversationDTOSchema),
274
+ meta: PaginationMetaSchema,
275
+ });
276
+ export const PaginatedMessagesSchema = z.object({
277
+ messages: z.array(MessageDTOSchema),
278
+ meta: PaginationMetaSchema,
279
+ });
280
+ // ─────────────────────────────────────────────────────────────────────────────
281
+ // Projects
282
+ // ─────────────────────────────────────────────────────────────────────────────
283
+ export const ProjectDTOSchema = z.object({
284
+ id: z.string(),
285
+ persona_id: z.string().optional(),
286
+ display_name: z.string().optional(),
287
+ description: z.string().optional().nullable(),
288
+ state: z.string().optional(),
289
+ created_at: DateTimeSchema.optional().nullable(),
290
+ updated_at: DateTimeSchema.optional().nullable(),
291
+ settings: z.record(z.unknown()).optional().nullable(),
292
+ }).passthrough();
293
+ // ─────────────────────────────────────────────────────────────────────────────
294
+ // Templates
295
+ // ─────────────────────────────────────────────────────────────────────────────
296
+ export const PersonaTemplateDTOSchema = z.object({
297
+ id: z.string(),
298
+ name: z.string().optional(),
299
+ description: z.string().optional().nullable(),
300
+ category: z.string().optional().nullable(),
301
+ agent_name: z.string().optional().nullable(),
302
+ trigger_type: z.string().optional().nullable(),
303
+ proto_config: z.record(z.unknown()).optional().nullable(),
304
+ workflow_def: z.record(z.unknown()).optional().nullable(),
305
+ has_project_template: z.boolean().optional(),
306
+ about_template: z.string().optional().nullable(),
307
+ is_system_template: z.boolean().optional().nullable(),
308
+ }).passthrough();
309
+ export const GetPersonaTemplatesResponseSchema = z.object({
310
+ templates: z.array(PersonaTemplateDTOSchema),
311
+ category_options: z.array(z.object({
312
+ value: z.string(),
313
+ name: z.string(),
314
+ })).optional(),
315
+ });
316
+ // ─────────────────────────────────────────────────────────────────────────────
317
+ // Chat / Chatbot
318
+ // ─────────────────────────────────────────────────────────────────────────────
319
+ export const ChatbotMessageSchema = z.object({
320
+ message_id: z.string().optional(),
321
+ message_type: z.number().optional(),
322
+ content: z.string().optional(),
323
+ role: z.string().optional(),
324
+ }).passthrough();
325
+ export const ChatbotResponseSchema = z.object({
326
+ conversation_id: z.string().optional(),
327
+ message: ChatbotMessageSchema.optional(),
328
+ success: z.boolean().optional(),
329
+ }).passthrough();
330
+ export const ChatbotSDKConfigResponseSchema = z.object({
331
+ persona_id: z.string().optional(),
332
+ theme: z.record(z.unknown()).optional(),
333
+ header: z.record(z.unknown()).optional(),
334
+ welcome_message: z.record(z.unknown()).optional(),
335
+ }).passthrough();
336
+ // ─────────────────────────────────────────────────────────────────────────────
337
+ // Error Responses
338
+ // ─────────────────────────────────────────────────────────────────────────────
339
+ export const ErrorDetailSchema = z.object({
340
+ field: z.string().optional().nullable(),
341
+ message: z.string(),
342
+ code: z.string().optional().nullable(),
343
+ });
344
+ export const ErrorResponseSchema = z.object({
345
+ error: z.string(),
346
+ message: z.string(),
347
+ details: z.array(ErrorDetailSchema).optional().nullable(),
348
+ request_id: z.string().optional().nullable(),
349
+ timestamp: DateTimeSchema.optional(),
350
+ path: z.string().optional().nullable(),
351
+ });
352
+ // ─────────────────────────────────────────────────────────────────────────────
353
+ // Sync Metadata (MCP Toolkit internal)
354
+ // ─────────────────────────────────────────────────────────────────────────────
355
+ export const SyncMetadataSchema = z.object({
356
+ master_env: z.string(),
357
+ master_id: z.string(),
358
+ synced_at: z.string(),
359
+ master_fingerprint: z.string().optional(),
360
+ sync_run_id: z.string().optional(),
361
+ });
362
+ // ─────────────────────────────────────────────────────────────────────────────
363
+ // Validation Helpers
364
+ // ─────────────────────────────────────────────────────────────────────────────
365
+ /**
366
+ * Validate an API response against a schema.
367
+ * Returns the parsed data if valid, throws ZodError if invalid.
368
+ *
369
+ * @example
370
+ * const persona = validateResponse(PersonaDTOSchema, apiResponse);
371
+ */
372
+ export function validateResponse(schema, data) {
373
+ return schema.parse(data);
374
+ }
375
+ /**
376
+ * Safely validate an API response.
377
+ * Returns { success: true, data } or { success: false, error }.
378
+ *
379
+ * @example
380
+ * const result = safeValidateResponse(PersonaDTOSchema, apiResponse);
381
+ * if (result.success) {
382
+ * console.log(result.data.name);
383
+ * } else {
384
+ * console.error(result.error.issues);
385
+ * }
386
+ */
387
+ export function safeValidateResponse(schema, data) {
388
+ return schema.safeParse(data);
389
+ }
390
+ /**
391
+ * Validate a list of items, returning only the valid ones.
392
+ * Invalid items are logged as warnings but don't cause the entire validation to fail.
393
+ *
394
+ * @example
395
+ * const personas = validateList(PersonaDTOSchema, apiResponse.personas, "personas");
396
+ */
397
+ export function validateList(schema, items, context) {
398
+ const valid = [];
399
+ for (let i = 0; i < items.length; i++) {
400
+ const result = schema.safeParse(items[i]);
401
+ if (result.success) {
402
+ valid.push(result.data);
403
+ }
404
+ else {
405
+ console.warn(`[contracts] Invalid item at index ${i}${context ? ` in ${context}` : ""}:`, result.error.issues.map(issue => `${issue.path.join(".")}: ${issue.message}`).join(", "));
406
+ }
407
+ }
408
+ return valid;
409
+ }
410
+ /**
411
+ * Type guard to check if a value matches a schema.
412
+ *
413
+ * @example
414
+ * if (isValid(PersonaDTOSchema, data)) {
415
+ * // data is typed as PersonaDTO here
416
+ * }
417
+ */
418
+ export function isValid(schema, data) {
419
+ return schema.safeParse(data).success;
420
+ }
421
+ /**
422
+ * Extract validation errors in a user-friendly format.
423
+ */
424
+ export function formatValidationErrors(error) {
425
+ return error.issues.map(issue => {
426
+ const path = issue.path.join(".");
427
+ return path ? `${path}: ${issue.message}` : issue.message;
428
+ });
429
+ }