@kadoa/mcp 0.7.1 → 0.7.3

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 (3) hide show
  1. package/README.md +4 -7
  2. package/dist/index.js +576 -366
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -163,6 +163,7 @@ Point your client to `https://mcp.kadoa.com/mcp` with OAuth authentication.
163
163
  |------|-------------|
164
164
  | `whoami` | Show current user details, auth method, and team memberships |
165
165
  | `team_list` | List all teams you belong to and see which is active |
166
+ | `list_compliance_rules` | Read the compliance rules applied to your team (robots.txt, terms of service, blocked sources, PII, and similar), with each rule's enabled status and configuration. Read-only |
166
167
 
167
168
  Team switching (a team_switch tool) exists in the code but is currently
168
169
  disabled and hidden from every client's tool list — a credential is fixed to
@@ -544,15 +545,13 @@ A typical feature touches three repos — **`kadoa-backend` → `kadoa-sdks` →
544
545
  | Backend endpoint | `kadoa-backend/` | The HTTP API. Source of truth; the OpenAPI spec is generated from here. |
545
546
  | SDK low-level | `kadoa-sdks/sdks/node/src/generated/api/*` | Auto-generated axios client. Raw HTTP, typed from the OpenAPI spec. **Do not hand-edit.** |
546
547
  | SDK domain (high-level) | `kadoa-sdks/sdks/node/src/domains/<thing>/<thing>.service.ts` | Thin domain wrapper over the generated client. `client.<thing>.<action>(...)`. This is what callers use. |
547
- | CLI | `kadoa-cli/src/commands/<thing>.ts` | Commander action: flag parsing, spinner, table/json output. Calls SDK domain. |
548
548
  | MCP (this repo) | `kadoa-mcp/src/tools/<domain>.ts` | Zod input schema + tool description tuned for LLMs. Calls SDK domain. |
549
549
 
550
550
  Mental model:
551
551
  - **SDK** = pure function over HTTP.
552
- - **CLI** = SDK + human UX (flags, table output).
553
552
  - **MCP** = SDK + LLM UX (zod schema, prose description).
554
553
 
555
- CLI and MCP both call **identical** SDK domain methods. A new feature added to the SDK is picked up by both clients for free — as long as we keep the layering honest.
554
+ MCP calls SDK domain methods, never raw HTTP. Any other SDK consumer gets new features for free — as long as we keep the layering honest.
556
555
 
557
556
  ### Adding a new feature
558
557
 
@@ -560,7 +559,7 @@ CLI and MCP both call **identical** SDK domain methods. A new feature added to t
560
559
  - Add or modify the HTTP endpoint.
561
560
  - Make sure the OpenAPI spec reflects the change (paths, request/response schemas).
562
561
 
563
- 2. **SDK** (`kadoa-sdks`) — always do this **before** MCP/CLI work (the SDK-first rule). No bandaid `axios` calls in MCP.
562
+ 2. **SDK** (`kadoa-sdks`) — always do this **before** MCP work (the SDK-first rule). No bandaid `axios` calls in MCP.
564
563
  - Regenerate the low-level client:
565
564
  ```bash
566
565
  bun kadoa-codegen fetch-spec -e https://api.kadoa.com/openapi -f
@@ -578,11 +577,9 @@ CLI and MCP both call **identical** SDK domain methods. A new feature added to t
578
577
  - Description string written **for the LLM**: when to call it, what inputs mean, what the response shape is.
579
578
  - Implementation calls `ctx.client.<thing>.<action>(...)` — never raw HTTP.
580
579
  - Add a unit test under `tests/unit/`.
581
- - If the change intentionally alters a tool's name, title, description, schema, outputSchema or annotations, regenerate the snapshot: `UPDATE_TOOLS_SNAPSHOT=1 BUN_TEST=1 bun test tests/unit/tools-list-snapshot.test.ts`, then `./node_modules/.bin/biome format --write tests/unit/fixtures/tools-list.snapshot.json`.
580
+ - If the change intentionally alters a tool's name, title, description, schema, outputSchema or annotations, regenerate the snapshot: `UPDATE_TOOLS_SNAPSHOT=1 BUN_TEST=1 bun test tests/unit/tools-list-snapshot.test.ts`, then `./node_modules/.bin/biome format --write tests/unit/fixtures/tools-list.snapshot.json`. Advertised input and output schemas are normalised by `src/tools/shared/portable-schema.ts` (no `$ref`/`$defs`, `const`, `oneOf`, `additionalProperties`, `$schema`, etc.), so strict clients such as Gemini accept them; zod validation is unchanged.
582
581
  - Use a Conventional Commit message (`feat(mcp): ...`, `fix(mcp): ...`). Release Please will cut the next version on merge to `main`.
583
582
 
584
- 4. **CLI** (`kadoa-cli`), if the feature is user-facing on the command line: mirror the MCP step in `src/commands/<thing>.ts`. Same SDK call, different UX.
585
-
586
583
  ### Anti-patterns
587
584
 
588
585
  - ❌ Raw `axios` calls or hand-rolled HTTP in `src/tools/`. Always go through `ctx.client.<thing>`.
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ var package_default;
17
17
  var init_package = __esm(() => {
18
18
  package_default = {
19
19
  name: "@kadoa/mcp",
20
- version: "0.7.1",
20
+ version: "0.7.3",
21
21
  description: "Kadoa MCP Server — manage workflows from Claude Desktop, Cursor, and other MCP clients",
22
22
  type: "module",
23
23
  main: "dist/index.js",
@@ -212,257 +212,187 @@ function withVariableDashboardUrl(variable) {
212
212
  return { ...variable, dashboardUrl: variableSettingsDashboardUrl() };
213
213
  }
214
214
 
215
- // src/tools/shared/output-schemas.ts
216
- import { z } from "zod";
217
- var apiField, nullableString, PaginationOutput, workflowStatusFields, WorkflowRowOutput, ListWorkflowsOutput, GetWorkflowOutput, FetchDataOutput, ASSISTANT_ACTIVITIES, WorkflowAssistantOutput, AssistantTimelineOutput, AssistantAnswerOutput, AssistantControlOutput, WorkflowStrategyOutput;
218
- var init_output_schemas = __esm(() => {
219
- apiField = z.unknown().optional();
220
- nullableString = z.string().nullable();
221
- PaginationOutput = z.object({
222
- totalCount: z.number().optional(),
223
- hasMore: z.boolean(),
224
- limit: z.number().optional(),
225
- skip: z.number().optional(),
226
- offset: z.number().optional(),
227
- page: z.number().optional(),
228
- totalPages: z.number().optional(),
229
- nextCursor: nullableString.optional(),
230
- next: z.record(z.string(), z.unknown()).optional()
231
- }).loose();
232
- workflowStatusFields = {
233
- status: z.string(),
234
- badges: z.array(z.object({ kind: z.string(), status: z.string(), label: z.string() }).loose()),
235
- group: nullableString,
236
- reviewReason: z.object({ code: z.string(), explanation: z.string() }).loose().nullable(),
237
- execution: apiField,
238
- assistantState: apiField,
239
- workspaceActivity: apiField
240
- };
241
- WorkflowRowOutput = z.object({
242
- id: apiField,
243
- name: apiField,
244
- dashboardUrl: z.string(),
245
- ...workflowStatusFields,
246
- state: nullableString,
247
- displayState: nullableString,
248
- runState: nullableString,
249
- urls: apiField,
250
- totalRecords: apiField,
251
- startedAt: apiField,
252
- finishedAt: apiField,
253
- createdAt: apiField,
254
- assistant: z.object({
255
- eligible: z.boolean(),
256
- session: z.object({ id: z.string(), threadId: apiField, status: apiField }).loose().nullable()
257
- }).loose(),
258
- template: z.object({ id: z.string(), pinnedVersion: apiField, latestVersion: apiField, isOutdated: z.boolean() }).loose().nullable(),
259
- realtime: z.object({ enabled: z.boolean(), lastDataChangedAt: apiField, health: apiField }).loose(),
260
- awaitingUserInput: apiField
261
- }).loose();
262
- ListWorkflowsOutput = z.object({ workflows: z.array(WorkflowRowOutput), pagination: PaginationOutput }).loose();
263
- GetWorkflowOutput = z.object({
264
- id: z.string(),
265
- name: apiField,
266
- dashboardUrl: z.string(),
267
- description: apiField,
268
- ...workflowStatusFields,
269
- state: nullableString,
270
- displayState: nullableString,
271
- runState: nullableString,
272
- urls: apiField,
273
- prompt: nullableString,
274
- intentSource: z.enum(["extraction_spec", "legacy_prompt"]).nullable(),
275
- extractionSpec: apiField,
276
- entity: apiField,
277
- schema: apiField,
278
- navigationMode: apiField,
279
- assistantEligible: z.boolean(),
280
- assistantSessionId: apiField,
281
- assistantThreadId: apiField,
282
- assistantSessionStatus: apiField,
283
- assistant: z.object({ eligible: z.boolean(), sessionId: apiField, threadId: apiField, sessionStatus: apiField }).loose(),
284
- template: z.object({
285
- id: apiField,
286
- name: apiField,
287
- version: apiField,
288
- controlledParts: z.array(z.string()),
289
- hasSchemaValidationRules: apiField
290
- }).loose().nullable(),
291
- updateInterval: apiField,
292
- schedules: apiField,
293
- scheduleTimezone: apiField,
294
- nextInvocation: apiField,
295
- location: apiField,
296
- limit: apiField,
297
- tags: apiField,
298
- totalRecords: apiField,
299
- job: z.object({ id: apiField }).loose().nullable(),
300
- startedAt: apiField,
301
- finishedAt: apiField,
302
- estimatedFinishedAtUtc: apiField,
303
- errors: apiField,
304
- createdAt: apiField,
305
- notificationConfig: apiField,
306
- channelCounts: apiField,
307
- realtime: z.object({ lastDataChangedAt: apiField, observerHealth: apiField }).loose().nullable()
308
- }).loose();
309
- FetchDataOutput = z.object({
310
- data: z.array(z.record(z.string(), z.unknown())),
311
- pagination: PaginationOutput,
312
- totalCount: z.number().optional(),
313
- hint: z.string().optional()
314
- }).loose();
315
- ASSISTANT_ACTIVITIES = [
316
- "unsupported",
317
- "not_started",
318
- "working",
319
- "waiting_for_input",
320
- "idle",
321
- "interrupted",
322
- "failed",
323
- "closed",
324
- "unknown"
325
- ];
326
- WorkflowAssistantOutput = z.object({
327
- workflowId: z.string(),
328
- assistantEligible: z.boolean(),
329
- sessionId: nullableString,
330
- threadId: apiField,
331
- sessionStatus: apiField,
332
- activity: z.enum(ASSISTANT_ACTIVITIES),
333
- canAcceptUpdate: z.boolean(),
334
- isTerminal: z.boolean(),
335
- paused: apiField,
336
- pause: apiField,
337
- pendingQuestion: apiField
338
- }).loose();
339
- AssistantTimelineOutput = z.object({
340
- workflowId: z.string(),
341
- sessionId: apiField,
342
- items: z.array(z.unknown()),
343
- pagination: PaginationOutput,
344
- dashboardUrl: z.string()
345
- }).loose();
346
- AssistantAnswerOutput = z.object({
347
- success: z.literal(true),
348
- workflowId: z.string(),
349
- sessionId: z.string(),
350
- threadId: apiField,
351
- questionId: z.string(),
352
- message: z.string()
353
- }).loose();
354
- AssistantControlOutput = z.object({
355
- success: z.boolean(),
356
- status: z.literal("accepted"),
357
- action: z.enum(["interrupt", "resume", "stop"]),
358
- workflowId: z.string(),
359
- sessionId: z.string(),
360
- delivery: apiField,
361
- jobId: apiField,
362
- message: z.string()
363
- }).loose();
364
- WorkflowStrategyOutput = z.object({
365
- workflowId: z.string(),
366
- sessionId: nullableString,
367
- source: z.enum(["assistant_build", "workflow_metadata"]).nullable(),
368
- status: z.enum(["ready", "building", "unavailable"]),
369
- strategy: apiField,
370
- message: z.string().optional()
371
- }).loose();
372
- });
373
-
374
- // src/tools/shared/pagination.ts
375
- import { z as z2 } from "zod";
376
- function limitInput(options) {
377
- const range = `limit must be a whole number from 1 to ${options.max}`;
378
- const overMax = `${range}. ${options.overMaxHint ?? "Page with pagination.next."}`;
379
- return z2.preprocess(coerceNumber(), z2.number().int(range).min(1, range).max(options.max, overMax)).optional().describe(`${options.noun} per page, 1-${options.max} (default ${options.defaultLimit})`);
380
- }
381
- function offsetInput(param, noun) {
382
- const range = `${param} must be a whole number from 0 to ${MAX_OFFSET}`;
383
- return z2.preprocess(coerceNumber(), z2.number().int(range).min(0, range).max(MAX_OFFSET, range)).optional().describe(`Number of ${noun} to skip (default 0)`);
384
- }
385
- function pageInput() {
386
- const range = `page must be a whole number from 1 to ${MAX_PAGE}`;
387
- return z2.preprocess(coerceNumber(), z2.number().int(range).min(1, range).max(MAX_PAGE, range)).optional().describe("Page number, 1-based (default 1)");
388
- }
389
- function nextArgs(args, patch) {
390
- const next = {};
391
- for (const [key, value] of Object.entries({ ...args, ...patch })) {
392
- if (value !== undefined)
393
- next[key] = value;
215
+ // src/tools/shared/portable-schema.ts
216
+ function lookupRef(ref, defs) {
217
+ if (typeof ref !== "string")
218
+ return;
219
+ const match = /^#\/(?:\$defs|definitions)\/(.+)$/.exec(ref);
220
+ if (!match)
221
+ return;
222
+ const name = match[1].replace(/~1/g, "/").replace(/~0/g, "~");
223
+ const def = defs[name];
224
+ return isObject(def) ? { name, def } : undefined;
225
+ }
226
+ function mergeSchemas(base, extra) {
227
+ const merged = { ...extra, ...base };
228
+ if (isObject(base.properties) || isObject(extra.properties)) {
229
+ merged.properties = {
230
+ ...isObject(extra.properties) ? extra.properties : {},
231
+ ...isObject(base.properties) ? base.properties : {}
232
+ };
394
233
  }
395
- return next;
396
- }
397
- function withTotal(totalCount, hasMore) {
398
- return totalCount !== undefined ? { totalCount, hasMore } : { hasMore };
399
- }
400
- function offsetPage(options) {
401
- const nextOffset = options.offset + options.returned;
402
- const more = options.hasMore ?? (options.totalCount !== undefined ? nextOffset < options.totalCount : options.returned >= options.limit);
403
- const hasMore = options.returned > 0 && more;
404
- const meta = withTotal(options.totalCount, hasMore);
405
- meta.limit = options.limit;
406
- meta[options.param] = options.offset;
407
- if (hasMore)
408
- meta.next = nextArgs(options.args, { [options.param]: nextOffset, limit: options.limit });
409
- return meta;
410
- }
411
- function numberedPage(options) {
412
- const more = options.totalPages !== undefined ? options.page < options.totalPages : options.totalCount !== undefined ? options.page * options.limit < options.totalCount : options.returned >= options.limit;
413
- const hasMore = options.returned > 0 && more;
414
- const meta = withTotal(options.totalCount, hasMore);
415
- meta.limit = options.limit;
416
- meta.page = options.page;
417
- if (options.totalPages !== undefined)
418
- meta.totalPages = options.totalPages;
419
- if (hasMore)
420
- meta.next = nextArgs(options.args, { page: options.page + 1, limit: options.limit });
421
- return meta;
422
- }
423
- function cursorPage(options) {
424
- const cursor = typeof options.nextCursor === "string" && options.nextCursor.length > 0 ? options.nextCursor : null;
425
- const meta = { hasMore: options.hasMore && cursor !== null, nextCursor: cursor };
426
- if (meta.hasMore)
427
- meta.next = nextArgs(options.args, { cursor });
428
- return meta;
429
- }
430
- function completeList(totalCount) {
431
- return { totalCount, hasMore: false };
432
- }
433
- var MAX_OFFSET = 1e6, MAX_PAGE = 1e5;
434
- var init_pagination = () => {};
435
-
436
- // src/tools/shared/results.ts
437
- function asStructuredObject(data) {
438
- return data !== null && !Array.isArray(data) ? data : { result: data };
439
- }
440
- function structuredResult(data, extra = []) {
441
- const structuredContent = asStructuredObject(data);
442
- return {
443
- structuredContent,
444
- content: [{ type: "text", text: JSON.stringify(structuredContent) }, ...extra]
445
- };
446
- }
447
- function jsonResult(data) {
448
- return structuredResult(data);
449
- }
450
- function textOnlyResult(data, extra = []) {
451
- const structuredContent = asStructuredObject(data);
452
- return { content: [{ type: "text", text: JSON.stringify(structuredContent) }, ...extra] };
234
+ if (Array.isArray(base.required) || Array.isArray(extra.required)) {
235
+ const required = [
236
+ ...Array.isArray(extra.required) ? extra.required : [],
237
+ ...Array.isArray(base.required) ? base.required : []
238
+ ];
239
+ merged.required = [...new Set(required)];
240
+ }
241
+ return merged;
242
+ }
243
+ function walk(node, defs, refCounts, io) {
244
+ if (!isObject(node))
245
+ return node;
246
+ let current = { ...node };
247
+ let counts = refCounts;
248
+ while ("$ref" in current) {
249
+ const { $ref, ...site } = current;
250
+ const target = lookupRef($ref, defs);
251
+ if (!target) {
252
+ current = site;
253
+ break;
254
+ }
255
+ const seen = counts.get(target.name) ?? 0;
256
+ counts = new Map(counts).set(target.name, seen + 1);
257
+ if (seen >= MAX_REF_DEPTH) {
258
+ const description = typeof target.def.description === "string" ? target.def.description : STUB_DESCRIPTION;
259
+ const type = typeof target.def.type === "string" ? target.def.type : io === "input" ? "object" : undefined;
260
+ current = { ...type ? { type } : {}, description, ...site };
261
+ break;
262
+ }
263
+ current = { ...target.def, ...site };
264
+ }
265
+ for (const keyword of DROPPED_KEYWORDS)
266
+ delete current[keyword];
267
+ if ("const" in current) {
268
+ const { const: value, ...rest } = current;
269
+ current = typeof value === "string" ? { ...rest, enum: [value] } : rest;
270
+ if (current.type === undefined && ["string", "number", "boolean"].includes(typeof value)) {
271
+ current.type = typeof value;
272
+ }
273
+ }
274
+ if (typeof current.exclusiveMinimum === "number") {
275
+ const bound = current.exclusiveMinimum;
276
+ current.minimum = typeof current.minimum === "number" ? Math.max(current.minimum, bound) : bound;
277
+ }
278
+ if (typeof current.exclusiveMaximum === "number") {
279
+ const bound = current.exclusiveMaximum;
280
+ current.maximum = typeof current.maximum === "number" ? Math.min(current.maximum, bound) : bound;
281
+ }
282
+ delete current.exclusiveMinimum;
283
+ delete current.exclusiveMaximum;
284
+ if (io === "input" && Array.isArray(current.enum) && Array.isArray(current.type) && current.type.includes("null")) {
285
+ current.enum = current.enum.filter((member) => member !== null);
286
+ }
287
+ if (Array.isArray(current.enum) && current.enum.some((member) => typeof member !== "string")) {
288
+ delete current.enum;
289
+ }
290
+ if (Array.isArray(current.prefixItems)) {
291
+ const { prefixItems, ...rest } = current;
292
+ current = rest;
293
+ if (current.items === undefined && prefixItems.length > 0) {
294
+ current.items = prefixItems.length === 1 ? prefixItems[0] : { anyOf: prefixItems };
295
+ }
296
+ }
297
+ if (isObject(current.properties)) {
298
+ current.properties = Object.fromEntries(Object.entries(current.properties).map(([name, value]) => [name, walk(value, defs, counts, io)]));
299
+ }
300
+ if (isObject(current.items))
301
+ current.items = walk(current.items, defs, counts, io);
302
+ if (Array.isArray(current.oneOf)) {
303
+ current.anyOf = [...Array.isArray(current.anyOf) ? current.anyOf : [], ...current.oneOf];
304
+ delete current.oneOf;
305
+ }
306
+ if (Array.isArray(current.anyOf))
307
+ current.anyOf = current.anyOf.map((member) => walk(member, defs, counts, io));
308
+ if (Array.isArray(current.allOf)) {
309
+ const { allOf, ...rest } = current;
310
+ current = rest;
311
+ for (const member of allOf) {
312
+ const walked = walk(member, defs, counts, io);
313
+ if (isObject(walked))
314
+ current = mergeSchemas(current, walked);
315
+ }
316
+ }
317
+ if (Array.isArray(current.type)) {
318
+ const types = current.type.filter((entry) => entry !== "null");
319
+ const nullable = types.length !== current.type.length;
320
+ if (types.length === 0) {
321
+ current.type = "null";
322
+ } else if (types.length === 1 && !(nullable && io === "output")) {
323
+ current.type = types[0];
324
+ if (nullable)
325
+ current.nullable = true;
326
+ } else {
327
+ const members = current.type.map((type) => ({ type }));
328
+ delete current.type;
329
+ current.anyOf = members;
330
+ }
331
+ }
332
+ if (Array.isArray(current.anyOf) && current.anyOf.some(isNullSchema)) {
333
+ const members = current.anyOf.filter((member) => !isNullSchema(member));
334
+ const { anyOf: _anyOf, ...rest } = current;
335
+ if (members.length === 0) {
336
+ current = { ...rest, type: "null" };
337
+ } else if (io === "input" && members.length === 1 && isObject(members[0])) {
338
+ const merged = mergeSchemas(rest, members[0]);
339
+ if (typeof merged.type === "string")
340
+ current = { ...merged, nullable: true };
341
+ }
342
+ }
343
+ return current;
453
344
  }
454
- function errorResult(message, details = {}) {
455
- return {
456
- content: [{ type: "text", text: JSON.stringify({ error: message, ...details }) }],
457
- isError: true
345
+ function toPortableJsonSchema(schema, options = {}) {
346
+ const io = options.io ?? "input";
347
+ const defs = {
348
+ ...isObject(schema.definitions) ? schema.definitions : {},
349
+ ...isObject(schema.$defs) ? schema.$defs : {}
458
350
  };
459
- }
460
- function withExtraContent(result, ...blocks) {
461
- return { ...result, content: [...result.content, ...blocks] };
462
- }
351
+ return walk(schema, defs, new Map, io);
352
+ }
353
+ var NON_PORTABLE_KEYWORDS, DROPPED_KEYWORDS, MAX_REF_DEPTH = 3, STUB_DESCRIPTION = "Nested value of the same shape", isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value), isNullSchema = (schema) => isObject(schema) && schema.type === "null";
354
+ var init_portable_schema = __esm(() => {
355
+ NON_PORTABLE_KEYWORDS = new Set([
356
+ "$ref",
357
+ "$defs",
358
+ "definitions",
359
+ "const",
360
+ "oneOf",
361
+ "allOf",
362
+ "exclusiveMinimum",
363
+ "exclusiveMaximum",
364
+ "prefixItems",
365
+ "$schema",
366
+ "additionalProperties",
367
+ "propertyNames",
368
+ "multipleOf",
369
+ "uniqueItems",
370
+ "id",
371
+ "$id",
372
+ "contentEncoding",
373
+ "contentMediaType",
374
+ "dependentRequired",
375
+ "patternProperties"
376
+ ]);
377
+ DROPPED_KEYWORDS = [
378
+ "$schema",
379
+ "$id",
380
+ "id",
381
+ "$defs",
382
+ "definitions",
383
+ "additionalProperties",
384
+ "propertyNames",
385
+ "patternProperties",
386
+ "multipleOf",
387
+ "uniqueItems",
388
+ "contentEncoding",
389
+ "contentMediaType",
390
+ "dependentRequired"
391
+ ];
392
+ });
463
393
 
464
394
  // src/tools/shared/schemas.ts
465
- import { z as z3 } from "zod";
395
+ import { z } from "zod";
466
396
  function isPlainObject(value) {
467
397
  return value !== null && typeof value === "object" && !Array.isArray(value);
468
398
  }
@@ -524,18 +454,14 @@ function stampSchemaValidationRules(rules, attribution, previous) {
524
454
  return Object.fromEntries(Object.entries(rules).map(([name, field]) => [name, stampField(field, previousFields[name])]));
525
455
  }
526
456
  function strictSchema(shape) {
527
- return z3.object(shape).strict();
457
+ return portable(z.object(shape).strict());
528
458
  }
529
- function withoutDefIds(schema) {
459
+ function portable(schema) {
530
460
  const jsonSchema = schema["~standard"].jsonSchema;
531
461
  const emitInput = jsonSchema.input;
532
- jsonSchema.input = (params) => {
533
- const result = emitInput(params);
534
- const defs = result.$defs;
535
- for (const def of Object.values(defs ?? {}))
536
- delete def.id;
537
- return result;
538
- };
462
+ const emitOutput = jsonSchema.output;
463
+ jsonSchema.input = (params) => toPortableJsonSchema(emitInput(params), { io: "input" });
464
+ jsonSchema.output = (params) => toPortableJsonSchema(emitOutput(params), { io: "output" });
539
465
  return schema;
540
466
  }
541
467
  function normalizeDataFilters(val, ctx) {
@@ -560,7 +486,7 @@ function normalizeDataFilters(val, ctx) {
560
486
  code: "custom",
561
487
  message: "Grouped filters ({logicalOperator, conditions}) are not supported. Send a flat array of " + "{field, operator, value} filters instead; multiple filters are combined with OR."
562
488
  });
563
- return z3.NEVER;
489
+ return z.NEVER;
564
490
  }
565
491
  return [v];
566
492
  }
@@ -568,7 +494,8 @@ function normalizeDataFilters(val, ctx) {
568
494
  }
569
495
  var UpdateIntervalSchema, NOTIFICATION_EVENT_TYPES, NotificationEventTypeSchema, SchemaFieldDataTypeSchema, SchemaFieldShape, SchemaValidationPresenceRule, SchemaValidationUniquenessRule, SchemaValidationBoundRule, SchemaValidationStringFormatRule, SchemaValidationFieldRulesSchema, SchemaValidationRulesSchema, SCHEMA_VALIDATION_ATTRIBUTION_KEYS, LocationSchema, MonitoringValueOperators, MonitoringValuelessOperators, MonitoringConditionOperatorSchema, MonitoringConditionSchema, MonitoringSchema, urlInputShape, extractionPromptInput, extractionInputShape, webhookAuthShape, notificationChannelsSchema, notificationsInputShape, requiredNotificationsInputShape, DATA_FILTER_OPERATORS, DATA_FILTER_VALUELESS_OPERATORS, DATA_FILTER_LIST_OPERATORS, DataFilterSchema, dataFiltersInput;
570
496
  var init_schemas = __esm(() => {
571
- UpdateIntervalSchema = z3.enum([
497
+ init_portable_schema();
498
+ UpdateIntervalSchema = z.enum([
572
499
  "ONLY_ONCE",
573
500
  "EVERY_10_MINUTES",
574
501
  "HALF_HOURLY",
@@ -602,8 +529,8 @@ var init_schemas = __esm(() => {
602
529
  "credits_low",
603
530
  "free_trial_ending"
604
531
  ];
605
- NotificationEventTypeSchema = z3.enum(NOTIFICATION_EVENT_TYPES);
606
- SchemaFieldDataTypeSchema = z3.enum([
532
+ NotificationEventTypeSchema = z.enum(NOTIFICATION_EVENT_TYPES);
533
+ SchemaFieldDataTypeSchema = z.enum([
607
534
  "STRING",
608
535
  "NUMBER",
609
536
  "BOOLEAN",
@@ -616,61 +543,61 @@ var init_schemas = __esm(() => {
616
543
  "ARRAY"
617
544
  ]);
618
545
  SchemaFieldShape = {
619
- name: z3.string().describe("Field name"),
620
- description: z3.string().optional().describe("What this field contains"),
621
- example: z3.string().describe("Example value"),
546
+ name: z.string().describe("Field name"),
547
+ description: z.string().optional().describe("What this field contains"),
548
+ example: z.string().describe("Example value"),
622
549
  dataType: SchemaFieldDataTypeSchema.optional().describe("Data type for the field"),
623
- isKey: z3.preprocess(coerceBoolean(), z3.boolean()).optional().describe("Marks this field as a key field - the stable identity used to match records across runs. Change detection diffs records by their key fields, so a monitored or real-time workflow without one cannot tell an updated record from a new one. Set it on whatever uniquely identifies a row (a detail URL, an ID, a ticker).")
550
+ isKey: z.preprocess(coerceBoolean(), z.boolean()).optional().describe("Marks this field as a key field - the stable identity used to match records across runs. Change detection diffs records by their key fields, so a monitored or real-time workflow without one cannot tell an updated record from a new one. Set it on whatever uniquely identifies a row (a detail URL, an ID, a ticker).")
624
551
  };
625
- SchemaValidationPresenceRule = z3.object({
626
- target: z3.number().int().min(0).max(100).step(20).describe("Expected percentage of rows with a value")
552
+ SchemaValidationPresenceRule = z.object({
553
+ target: z.number().int().min(0).max(100).step(20).describe("Expected percentage of rows with a value")
627
554
  }).strict().meta({ id: "PresenceRule" });
628
- SchemaValidationUniquenessRule = z3.object({
629
- target: z3.number().int().min(0).max(100).step(20).describe("Expected percentage of rows with a unique value")
555
+ SchemaValidationUniquenessRule = z.object({
556
+ target: z.number().int().min(0).max(100).step(20).describe("Expected percentage of rows with a unique value")
630
557
  }).strict().meta({ id: "UniquenessRule" });
631
- SchemaValidationBoundRule = z3.object({
632
- value: z3.number().int().min(0).describe("Length or count bound")
558
+ SchemaValidationBoundRule = z.object({
559
+ value: z.number().int().min(0).describe("Length or count bound")
633
560
  }).strict().meta({ id: "BoundRule" });
634
- SchemaValidationStringFormatRule = z3.discriminatedUnion("kind", [
635
- z3.object({
636
- kind: z3.literal("FREE_TEXT"),
637
- charset: z3.discriminatedUnion("kind", [
638
- z3.object({
639
- kind: z3.literal("PRESET"),
640
- preset: z3.enum(["natural_language", "alphanumeric", "alpha"])
561
+ SchemaValidationStringFormatRule = z.discriminatedUnion("kind", [
562
+ z.object({
563
+ kind: z.literal("FREE_TEXT"),
564
+ charset: z.discriminatedUnion("kind", [
565
+ z.object({
566
+ kind: z.literal("PRESET"),
567
+ preset: z.enum(["natural_language", "alphanumeric", "alpha"])
641
568
  }).strict()
642
569
  ])
643
570
  }).strict(),
644
- z3.object({
645
- kind: z3.literal("FORMAT"),
646
- source: z3.discriminatedUnion("kind", [
647
- z3.object({
648
- kind: z3.literal("PRESET"),
649
- preset: z3.enum(["url", "email", "phone", "date", "datetime", "time", "uuid", "slug"])
571
+ z.object({
572
+ kind: z.literal("FORMAT"),
573
+ source: z.discriminatedUnion("kind", [
574
+ z.object({
575
+ kind: z.literal("PRESET"),
576
+ preset: z.enum(["url", "email", "phone", "date", "datetime", "time", "uuid", "slug"])
650
577
  }).strict(),
651
- z3.object({
652
- kind: z3.literal("CUSTOM"),
653
- pattern: z3.string().min(1).describe("Regular expression pattern")
578
+ z.object({
579
+ kind: z.literal("CUSTOM"),
580
+ pattern: z.string().min(1).describe("Regular expression pattern")
654
581
  }).strict()
655
582
  ])
656
583
  }).strict(),
657
- z3.object({
658
- kind: z3.literal("LIST"),
659
- source: z3.discriminatedUnion("kind", [
660
- z3.object({
661
- kind: z3.literal("PRESET"),
662
- preset: z3.enum(["language2", "country2", "country3", "currency3", "month3", "usState2"])
584
+ z.object({
585
+ kind: z.literal("LIST"),
586
+ source: z.discriminatedUnion("kind", [
587
+ z.object({
588
+ kind: z.literal("PRESET"),
589
+ preset: z.enum(["language2", "country2", "country3", "currency3", "month3", "usState2"])
663
590
  }).strict(),
664
- z3.object({
665
- kind: z3.literal("CUSTOM"),
666
- values: z3.array(z3.string().min(1)).min(1).describe("Allowed string values")
591
+ z.object({
592
+ kind: z.literal("CUSTOM"),
593
+ values: z.array(z.string().min(1)).min(1).describe("Allowed string values")
667
594
  }).strict()
668
595
  ])
669
596
  }).strict()
670
597
  ]);
671
- SchemaValidationFieldRulesSchema = z3.lazy(() => z3.discriminatedUnion("kind", [
672
- z3.object({
673
- kind: z3.literal("STRING"),
598
+ SchemaValidationFieldRulesSchema = z.lazy(() => z.discriminatedUnion("kind", [
599
+ z.object({
600
+ kind: z.literal("STRING"),
674
601
  presence: SchemaValidationPresenceRule.optional(),
675
602
  uniqueness: SchemaValidationUniquenessRule.optional(),
676
603
  minLength: SchemaValidationBoundRule.optional(),
@@ -679,27 +606,27 @@ var init_schemas = __esm(() => {
679
606
  maxHtmlElements: SchemaValidationBoundRule.optional(),
680
607
  format: SchemaValidationStringFormatRule.optional()
681
608
  }).strict(),
682
- z3.object({
683
- kind: z3.literal("NUMBER"),
609
+ z.object({
610
+ kind: z.literal("NUMBER"),
684
611
  presence: SchemaValidationPresenceRule.optional(),
685
612
  uniqueness: SchemaValidationUniquenessRule.optional(),
686
- minimum: z3.object({ value: z3.number().describe("Minimum numeric value") }).strict().optional(),
687
- maximum: z3.object({ value: z3.number().describe("Maximum numeric value") }).strict().optional(),
688
- maxDecimalPlaces: z3.object({ value: z3.number().int().min(0).max(16) }).strict().optional()
613
+ minimum: z.object({ value: z.number().describe("Minimum numeric value") }).strict().optional(),
614
+ maximum: z.object({ value: z.number().describe("Maximum numeric value") }).strict().optional(),
615
+ maxDecimalPlaces: z.object({ value: z.number().int().min(0).max(16) }).strict().optional()
689
616
  }).strict(),
690
- z3.object({
691
- kind: z3.literal("OTHER"),
617
+ z.object({
618
+ kind: z.literal("OTHER"),
692
619
  presence: SchemaValidationPresenceRule.optional(),
693
620
  uniqueness: SchemaValidationUniquenessRule.optional()
694
621
  }).strict(),
695
- z3.object({
696
- kind: z3.literal("OBJECT"),
622
+ z.object({
623
+ kind: z.literal("OBJECT"),
697
624
  presence: SchemaValidationPresenceRule.optional(),
698
625
  uniqueness: SchemaValidationUniquenessRule.optional(),
699
- properties: z3.record(z3.string(), SchemaValidationFieldRulesSchema)
626
+ properties: z.record(z.string(), SchemaValidationFieldRulesSchema)
700
627
  }).strict(),
701
- z3.object({
702
- kind: z3.literal("ARRAY"),
628
+ z.object({
629
+ kind: z.literal("ARRAY"),
703
630
  presence: SchemaValidationPresenceRule.optional(),
704
631
  uniqueness: SchemaValidationUniquenessRule.optional(),
705
632
  minItems: SchemaValidationBoundRule.optional(),
@@ -707,21 +634,21 @@ var init_schemas = __esm(() => {
707
634
  items: SchemaValidationFieldRulesSchema.optional()
708
635
  }).strict()
709
636
  ]));
710
- SchemaValidationRulesSchema = z3.record(z3.string(), SchemaValidationFieldRulesSchema);
637
+ SchemaValidationRulesSchema = z.record(z.string(), SchemaValidationFieldRulesSchema);
711
638
  SCHEMA_VALIDATION_ATTRIBUTION_KEYS = new Set(["editedBy", "editedByLabel", "editedAt"]);
712
- LocationSchema = z3.object({
713
- type: z3.enum(["auto", "manual"]),
714
- isoCode: z3.string().trim().min(2).optional()
639
+ LocationSchema = z.object({
640
+ type: z.enum(["auto", "manual"]),
641
+ isoCode: z.string().trim().min(2).optional()
715
642
  }).strict().superRefine((location, ctx) => {
716
643
  if (location.type === "manual" && !location.isoCode) {
717
644
  ctx.addIssue({
718
- code: z3.ZodIssueCode.custom,
645
+ code: z.ZodIssueCode.custom,
719
646
  path: ["isoCode"],
720
647
  message: "isoCode is required for manual location"
721
648
  });
722
649
  }
723
650
  if (location.type === "auto" && location.isoCode) {
724
- ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["isoCode"], message: "omit isoCode for auto location" });
651
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["isoCode"], message: "omit isoCode for auto location" });
725
652
  }
726
653
  });
727
654
  MonitoringValueOperators = [
@@ -743,81 +670,81 @@ var init_schemas = __esm(() => {
743
670
  "WITHIN_LAST_DAYS"
744
671
  ];
745
672
  MonitoringValuelessOperators = ["IS_NULL", "IS_NOT_NULL", "IS_EMPTY", "IS_NOT_EMPTY"];
746
- MonitoringConditionOperatorSchema = z3.enum([...MonitoringValueOperators, ...MonitoringValuelessOperators]);
747
- MonitoringConditionSchema = z3.object({
748
- type: z3.enum(["SINGLE", "ANY"]),
749
- field: z3.string().trim().min(1).optional(),
673
+ MonitoringConditionOperatorSchema = z.enum([...MonitoringValueOperators, ...MonitoringValuelessOperators]);
674
+ MonitoringConditionSchema = z.object({
675
+ type: z.enum(["SINGLE", "ANY"]),
676
+ field: z.string().trim().min(1).optional(),
750
677
  operator: MonitoringConditionOperatorSchema,
751
- value: z3.union([z3.string(), z3.number(), z3.boolean()]).optional()
678
+ value: z.union([z.string(), z.number(), z.boolean()]).optional()
752
679
  }).strict().superRefine((condition, ctx) => {
753
680
  if (condition.type === "SINGLE" && !condition.field) {
754
681
  ctx.addIssue({
755
- code: z3.ZodIssueCode.custom,
682
+ code: z.ZodIssueCode.custom,
756
683
  path: ["field"],
757
684
  message: "field is required for SINGLE conditions"
758
685
  });
759
686
  }
760
687
  const needsValue = MonitoringValueOperators.includes(condition.operator);
761
688
  if (needsValue && condition.value === undefined) {
762
- ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["value"], message: `${condition.operator} requires a value` });
689
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["value"], message: `${condition.operator} requires a value` });
763
690
  }
764
691
  if (!needsValue && condition.value !== undefined) {
765
692
  ctx.addIssue({
766
- code: z3.ZodIssueCode.custom,
693
+ code: z.ZodIssueCode.custom,
767
694
  path: ["value"],
768
695
  message: `${condition.operator} does not accept a value`
769
696
  });
770
697
  }
771
698
  });
772
- MonitoringSchema = z3.object({
773
- fields: z3.preprocess(coerceArray(), z3.array(z3.object({
774
- fieldName: z3.string().trim().min(1),
775
- operator: z3.enum(["changed", "added", "removed"]),
776
- isKeyField: z3.preprocess(coerceBoolean(), z3.boolean()).optional()
699
+ MonitoringSchema = z.object({
700
+ fields: z.preprocess(coerceArray(), z.array(z.object({
701
+ fieldName: z.string().trim().min(1),
702
+ operator: z.enum(["changed", "added", "removed"]),
703
+ isKeyField: z.preprocess(coerceBoolean(), z.boolean()).optional()
777
704
  }).strict()).min(1)),
778
- conditions: z3.object({
779
- logicalOperator: z3.enum(["AND", "OR"]),
780
- conditions: z3.preprocess(coerceArray(), z3.array(MonitoringConditionSchema).min(1))
705
+ conditions: z.object({
706
+ logicalOperator: z.enum(["AND", "OR"]),
707
+ conditions: z.preprocess(coerceArray(), z.array(MonitoringConditionSchema).min(1))
781
708
  }).strict().optional()
782
709
  }).strict();
783
710
  urlInputShape = {
784
- url: z3.string().optional().describe("Single URL - prefer using 'urls' instead. If both are provided, 'urls' takes precedence."),
785
- urls: z3.preprocess(coerceArray(true), z3.array(z3.string()).min(1)).optional().describe("Starting URLs for the workflow (array of strings). Also accepts a single URL string.")
711
+ url: z.string().optional().describe("Single URL - prefer using 'urls' instead. If both are provided, 'urls' takes precedence."),
712
+ urls: z.preprocess(coerceArray(true), z.array(z.string()).min(1)).optional().describe("Starting URLs for the workflow (array of strings). Also accepts a single URL string.")
786
713
  };
787
- extractionPromptInput = z3.string().optional();
714
+ extractionPromptInput = z.string().optional();
788
715
  extractionInputShape = {
789
- name: z3.string().optional().describe("Optional name for the workflow"),
790
- entity: z3.string().optional().describe("Entity name for extraction (e.g., 'Product', 'Job Posting')"),
791
- schema: z3.preprocess(coerceArray(), z3.array(z3.object(SchemaFieldShape).strict())).optional().describe("Extraction schema fields. If omitted, the AI agent auto-detects the schema. When you do supply fields, mark the one that identifies a record with isKey - change detection needs it.")
716
+ name: z.string().optional().describe("Optional name for the workflow"),
717
+ entity: z.string().optional().describe("Entity name for extraction (e.g., 'Product', 'Job Posting')"),
718
+ schema: z.preprocess(coerceArray(), z.array(z.object(SchemaFieldShape).strict())).optional().describe("Extraction schema fields. If omitted, the AI agent auto-detects the schema. When you do supply fields, mark the one that identifies a record with isKey - change detection needs it.")
792
719
  };
793
- webhookAuthShape = z3.object({
794
- type: z3.enum(["bearer", "basic", "header"]).describe("Authentication type"),
795
- token: z3.string().optional().describe("Bearer token (required for 'bearer' type)"),
796
- username: z3.string().optional().describe("Username (required for 'basic' type)"),
797
- password: z3.string().optional().describe("Password (required for 'basic' type)"),
798
- headers: z3.object({}).passthrough().optional().describe("Custom headers as key-value pairs (required for 'header' type)")
720
+ webhookAuthShape = z.object({
721
+ type: z.enum(["bearer", "basic", "header"]).describe("Authentication type"),
722
+ token: z.string().optional().describe("Bearer token (required for 'bearer' type)"),
723
+ username: z.string().optional().describe("Username (required for 'basic' type)"),
724
+ password: z.string().optional().describe("Password (required for 'basic' type)"),
725
+ headers: z.object({}).passthrough().optional().describe("Custom headers as key-value pairs (required for 'header' type)")
799
726
  }).optional().describe("Authentication configuration for the webhook endpoint");
800
- notificationChannelsSchema = z3.object({
801
- email: z3.object({
802
- recipients: z3.array(z3.string().email()).optional().describe("Email addresses to notify. Omit to use account default email.")
727
+ notificationChannelsSchema = z.object({
728
+ email: z.object({
729
+ recipients: z.array(z.string().email()).optional().describe("Email addresses to notify. Omit to use account default email.")
803
730
  }).optional().describe("Send email notifications. Pass {} for account default email, or {recipients: [...]} for custom addresses."),
804
- webhook: z3.object({
805
- url: z3.string().url().describe("Webhook endpoint URL"),
806
- httpMethod: z3.enum(["POST", "GET", "PUT", "PATCH"]).optional().describe("HTTP method (defaults to POST)"),
731
+ webhook: z.object({
732
+ url: z.string().url().describe("Webhook endpoint URL"),
733
+ httpMethod: z.enum(["POST", "GET", "PUT", "PATCH"]).optional().describe("HTTP method (defaults to POST)"),
807
734
  auth: webhookAuthShape
808
735
  }).optional().describe("Send notifications via HTTP webhook"),
809
- slack: z3.object({
810
- webhookUrl: z3.string().url().optional().describe("Slack incoming webhook URL (legacy)"),
811
- slackChannelId: z3.string().optional().describe("Slack channel ID from OAuth integration (e.g. C01ABCDEF)"),
812
- slackChannelName: z3.string().optional().describe("Slack channel name (e.g. #alerts)")
736
+ slack: z.object({
737
+ webhookUrl: z.string().url().optional().describe("Slack incoming webhook URL (legacy)"),
738
+ slackChannelId: z.string().optional().describe("Slack channel ID from OAuth integration (e.g. C01ABCDEF)"),
739
+ slackChannelName: z.string().optional().describe("Slack channel name (e.g. #alerts)")
813
740
  }).optional().describe("Send notifications to a Slack channel. Use slackChannelId/slackChannelName for OAuth integration, or webhookUrl for legacy incoming webhooks."),
814
- websocket: z3.boolean().optional().describe("Enable WebSocket notifications for programmatic real-time consumption")
741
+ websocket: z.boolean().optional().describe("Enable WebSocket notifications for programmatic real-time consumption")
815
742
  });
816
743
  notificationsInputShape = {
817
- notifications: z3.preprocess(coerceJson(), notificationChannelsSchema.optional()).optional().describe("Optional notification channels to alert when data changes.")
744
+ notifications: z.preprocess(coerceJson(), notificationChannelsSchema.optional()).optional().describe("Optional notification channels to alert when data changes.")
818
745
  };
819
746
  requiredNotificationsInputShape = {
820
- notifications: z3.preprocess(coerceJson(), notificationChannelsSchema).describe("Notification channels to alert when data changes.")
747
+ notifications: z.preprocess(coerceJson(), notificationChannelsSchema).describe("Notification channels to alert when data changes.")
821
748
  };
822
749
  DATA_FILTER_OPERATORS = [
823
750
  "EQUALS",
@@ -842,10 +769,10 @@ var init_schemas = __esm(() => {
842
769
  ];
843
770
  DATA_FILTER_VALUELESS_OPERATORS = new Set(["IS_NULL", "IS_NOT_NULL", "IS_EMPTY", "IS_NOT_EMPTY"]);
844
771
  DATA_FILTER_LIST_OPERATORS = new Set(["IN", "NOT_IN"]);
845
- DataFilterSchema = z3.object({
846
- field: z3.string().trim().min(1).describe("Field name from the workflow schema; one nested level is allowed, e.g. classification.match"),
847
- operator: z3.preprocess((val) => typeof val === "string" ? val.trim().toUpperCase() : val, z3.enum(DATA_FILTER_OPERATORS)),
848
- value: z3.union([z3.string(), z3.number(), z3.boolean(), z3.array(z3.union([z3.string(), z3.number()]))]).optional().describe("Omit for IS_NULL, IS_NOT_NULL, IS_EMPTY, IS_NOT_EMPTY; an array for IN/NOT_IN; days for WITHIN_LAST_DAYS")
772
+ DataFilterSchema = z.object({
773
+ field: z.string().trim().min(1).describe("Field name from the workflow schema; one nested level is allowed, e.g. classification.match"),
774
+ operator: z.preprocess((val) => typeof val === "string" ? val.trim().toUpperCase() : val, z.enum(DATA_FILTER_OPERATORS)),
775
+ value: z.union([z.string(), z.number(), z.boolean(), z.array(z.union([z.string(), z.number()]))]).optional().describe("Omit for IS_NULL, IS_NOT_NULL, IS_EMPTY, IS_NOT_EMPTY; an array for IN/NOT_IN; days for WITHIN_LAST_DAYS")
849
776
  }).strict().superRefine((filter, ctx) => {
850
777
  if (DATA_FILTER_VALUELESS_OPERATORS.has(filter.operator)) {
851
778
  if (filter.value !== undefined) {
@@ -870,9 +797,259 @@ var init_schemas = __esm(() => {
870
797
  });
871
798
  }
872
799
  });
873
- dataFiltersInput = z3.preprocess(normalizeDataFilters, z3.array(DataFilterSchema).optional()).optional().describe('Row filters, e.g. [{"field":"title","operator":"CONTAINS","value":"Manager"}]. Multiple filters are combined with OR. BEFORE, AFTER and WITHIN_LAST_DAYS only apply to DATE/DATETIME fields.');
800
+ dataFiltersInput = z.preprocess(normalizeDataFilters, z.array(DataFilterSchema).optional()).optional().describe('Row filters, e.g. [{"field":"title","operator":"CONTAINS","value":"Manager"}]. Multiple filters are combined with OR. BEFORE, AFTER and WITHIN_LAST_DAYS only apply to DATE/DATETIME fields.');
801
+ });
802
+
803
+ // src/tools/shared/output-schemas.ts
804
+ import { z as z2 } from "zod";
805
+ var apiField, nullableString, PaginationOutput, workflowStatusFields, WorkflowRowOutput, ListWorkflowsOutput, GetWorkflowOutput, FetchDataOutput, ASSISTANT_ACTIVITIES, WorkflowAssistantOutput, AssistantTimelineOutput, AssistantAnswerOutput, AssistantControlOutput, WorkflowStrategyOutput;
806
+ var init_output_schemas = __esm(() => {
807
+ init_schemas();
808
+ apiField = z2.unknown().optional();
809
+ nullableString = z2.string().nullable();
810
+ PaginationOutput = portable(z2.object({
811
+ totalCount: z2.number().optional(),
812
+ hasMore: z2.boolean(),
813
+ limit: z2.number().optional(),
814
+ skip: z2.number().optional(),
815
+ offset: z2.number().optional(),
816
+ page: z2.number().optional(),
817
+ totalPages: z2.number().optional(),
818
+ nextCursor: nullableString.optional(),
819
+ next: z2.record(z2.string(), z2.unknown()).optional()
820
+ }).loose());
821
+ workflowStatusFields = {
822
+ status: z2.string(),
823
+ badges: z2.array(z2.object({ kind: z2.string(), status: z2.string(), label: z2.string() }).loose()),
824
+ group: nullableString,
825
+ reviewReason: z2.object({ code: z2.string(), explanation: z2.string() }).loose().nullable(),
826
+ execution: apiField,
827
+ assistantState: apiField,
828
+ workspaceActivity: apiField
829
+ };
830
+ WorkflowRowOutput = z2.object({
831
+ id: apiField,
832
+ name: apiField,
833
+ dashboardUrl: z2.string(),
834
+ ...workflowStatusFields,
835
+ state: nullableString,
836
+ displayState: nullableString,
837
+ runState: nullableString,
838
+ urls: apiField,
839
+ totalRecords: apiField,
840
+ startedAt: apiField,
841
+ finishedAt: apiField,
842
+ createdAt: apiField,
843
+ assistant: z2.object({
844
+ eligible: z2.boolean(),
845
+ session: z2.object({ id: z2.string(), threadId: apiField, status: apiField }).loose().nullable()
846
+ }).loose(),
847
+ template: z2.object({ id: z2.string(), pinnedVersion: apiField, latestVersion: apiField, isOutdated: z2.boolean() }).loose().nullable(),
848
+ realtime: z2.object({ enabled: z2.boolean(), lastDataChangedAt: apiField, health: apiField }).loose(),
849
+ awaitingUserInput: apiField
850
+ }).loose();
851
+ ListWorkflowsOutput = portable(z2.object({ workflows: z2.array(WorkflowRowOutput), pagination: PaginationOutput }).loose());
852
+ GetWorkflowOutput = portable(z2.object({
853
+ id: z2.string(),
854
+ name: apiField,
855
+ dashboardUrl: z2.string(),
856
+ description: apiField,
857
+ ...workflowStatusFields,
858
+ state: nullableString,
859
+ displayState: nullableString,
860
+ runState: nullableString,
861
+ urls: apiField,
862
+ prompt: nullableString,
863
+ intentSource: z2.enum(["extraction_spec", "legacy_prompt"]).nullable(),
864
+ extractionSpec: apiField,
865
+ entity: apiField,
866
+ schema: apiField,
867
+ navigationMode: apiField,
868
+ assistantEligible: z2.boolean(),
869
+ assistantSessionId: apiField,
870
+ assistantThreadId: apiField,
871
+ assistantSessionStatus: apiField,
872
+ assistant: z2.object({ eligible: z2.boolean(), sessionId: apiField, threadId: apiField, sessionStatus: apiField }).loose(),
873
+ template: z2.object({
874
+ id: apiField,
875
+ name: apiField,
876
+ version: apiField,
877
+ controlledParts: z2.array(z2.string()),
878
+ hasSchemaValidationRules: apiField
879
+ }).loose().nullable(),
880
+ updateInterval: apiField,
881
+ schedules: apiField,
882
+ scheduleTimezone: apiField,
883
+ nextInvocation: apiField,
884
+ location: apiField,
885
+ limit: apiField,
886
+ tags: apiField,
887
+ totalRecords: apiField,
888
+ job: z2.object({ id: apiField }).loose().nullable(),
889
+ startedAt: apiField,
890
+ finishedAt: apiField,
891
+ estimatedFinishedAtUtc: apiField,
892
+ errors: apiField,
893
+ createdAt: apiField,
894
+ notificationConfig: apiField,
895
+ channelCounts: apiField,
896
+ realtime: z2.object({ lastDataChangedAt: apiField, observerHealth: apiField }).loose().nullable()
897
+ }).loose());
898
+ FetchDataOutput = portable(z2.object({
899
+ data: z2.array(z2.record(z2.string(), z2.unknown())),
900
+ pagination: PaginationOutput,
901
+ totalCount: z2.number().optional(),
902
+ hint: z2.string().optional()
903
+ }).loose());
904
+ ASSISTANT_ACTIVITIES = [
905
+ "unsupported",
906
+ "not_started",
907
+ "working",
908
+ "waiting_for_input",
909
+ "idle",
910
+ "interrupted",
911
+ "failed",
912
+ "closed",
913
+ "unknown"
914
+ ];
915
+ WorkflowAssistantOutput = portable(z2.object({
916
+ workflowId: z2.string(),
917
+ assistantEligible: z2.boolean(),
918
+ sessionId: nullableString,
919
+ threadId: apiField,
920
+ sessionStatus: apiField,
921
+ activity: z2.enum(ASSISTANT_ACTIVITIES),
922
+ canAcceptUpdate: z2.boolean(),
923
+ isTerminal: z2.boolean(),
924
+ paused: apiField,
925
+ pause: apiField,
926
+ pendingQuestion: apiField
927
+ }).loose());
928
+ AssistantTimelineOutput = portable(z2.object({
929
+ workflowId: z2.string(),
930
+ sessionId: apiField,
931
+ items: z2.array(z2.unknown()),
932
+ pagination: PaginationOutput,
933
+ dashboardUrl: z2.string()
934
+ }).loose());
935
+ AssistantAnswerOutput = portable(z2.object({
936
+ success: z2.literal(true),
937
+ workflowId: z2.string(),
938
+ sessionId: z2.string(),
939
+ threadId: apiField,
940
+ questionId: z2.string(),
941
+ message: z2.string()
942
+ }).loose());
943
+ AssistantControlOutput = portable(z2.object({
944
+ success: z2.boolean(),
945
+ status: z2.literal("accepted"),
946
+ action: z2.enum(["interrupt", "resume", "stop"]),
947
+ workflowId: z2.string(),
948
+ sessionId: z2.string(),
949
+ delivery: apiField,
950
+ jobId: apiField,
951
+ message: z2.string()
952
+ }).loose());
953
+ WorkflowStrategyOutput = portable(z2.object({
954
+ workflowId: z2.string(),
955
+ sessionId: nullableString,
956
+ source: z2.enum(["assistant_build", "workflow_metadata"]).nullable(),
957
+ status: z2.enum(["ready", "building", "unavailable"]),
958
+ strategy: apiField,
959
+ message: z2.string().optional()
960
+ }).loose());
874
961
  });
875
962
 
963
+ // src/tools/shared/pagination.ts
964
+ import { z as z3 } from "zod";
965
+ function limitInput(options) {
966
+ const range = `limit must be a whole number from 1 to ${options.max}`;
967
+ const overMax = `${range}. ${options.overMaxHint ?? "Page with pagination.next."}`;
968
+ return z3.preprocess(coerceNumber(), z3.number().int(range).min(1, range).max(options.max, overMax)).optional().describe(`${options.noun} per page, 1-${options.max} (default ${options.defaultLimit})`);
969
+ }
970
+ function offsetInput(param, noun) {
971
+ const range = `${param} must be a whole number from 0 to ${MAX_OFFSET}`;
972
+ return z3.preprocess(coerceNumber(), z3.number().int(range).min(0, range).max(MAX_OFFSET, range)).optional().describe(`Number of ${noun} to skip (default 0)`);
973
+ }
974
+ function pageInput() {
975
+ const range = `page must be a whole number from 1 to ${MAX_PAGE}`;
976
+ return z3.preprocess(coerceNumber(), z3.number().int(range).min(1, range).max(MAX_PAGE, range)).optional().describe("Page number, 1-based (default 1)");
977
+ }
978
+ function nextArgs(args, patch) {
979
+ const next = {};
980
+ for (const [key, value] of Object.entries({ ...args, ...patch })) {
981
+ if (value !== undefined)
982
+ next[key] = value;
983
+ }
984
+ return next;
985
+ }
986
+ function withTotal(totalCount, hasMore) {
987
+ return totalCount !== undefined ? { totalCount, hasMore } : { hasMore };
988
+ }
989
+ function offsetPage(options) {
990
+ const nextOffset = options.offset + options.returned;
991
+ const more = options.hasMore ?? (options.totalCount !== undefined ? nextOffset < options.totalCount : options.returned >= options.limit);
992
+ const hasMore = options.returned > 0 && more;
993
+ const meta = withTotal(options.totalCount, hasMore);
994
+ meta.limit = options.limit;
995
+ meta[options.param] = options.offset;
996
+ if (hasMore)
997
+ meta.next = nextArgs(options.args, { [options.param]: nextOffset, limit: options.limit });
998
+ return meta;
999
+ }
1000
+ function numberedPage(options) {
1001
+ const more = options.totalPages !== undefined ? options.page < options.totalPages : options.totalCount !== undefined ? options.page * options.limit < options.totalCount : options.returned >= options.limit;
1002
+ const hasMore = options.returned > 0 && more;
1003
+ const meta = withTotal(options.totalCount, hasMore);
1004
+ meta.limit = options.limit;
1005
+ meta.page = options.page;
1006
+ if (options.totalPages !== undefined)
1007
+ meta.totalPages = options.totalPages;
1008
+ if (hasMore)
1009
+ meta.next = nextArgs(options.args, { page: options.page + 1, limit: options.limit });
1010
+ return meta;
1011
+ }
1012
+ function cursorPage(options) {
1013
+ const cursor = typeof options.nextCursor === "string" && options.nextCursor.length > 0 ? options.nextCursor : null;
1014
+ const meta = { hasMore: options.hasMore && cursor !== null, nextCursor: cursor };
1015
+ if (meta.hasMore)
1016
+ meta.next = nextArgs(options.args, { cursor });
1017
+ return meta;
1018
+ }
1019
+ function completeList(totalCount) {
1020
+ return { totalCount, hasMore: false };
1021
+ }
1022
+ var MAX_OFFSET = 1e6, MAX_PAGE = 1e5;
1023
+ var init_pagination = () => {};
1024
+
1025
+ // src/tools/shared/results.ts
1026
+ function asStructuredObject(data) {
1027
+ return data !== null && !Array.isArray(data) ? data : { result: data };
1028
+ }
1029
+ function structuredResult(data, extra = []) {
1030
+ const structuredContent = asStructuredObject(data);
1031
+ return {
1032
+ structuredContent,
1033
+ content: [{ type: "text", text: JSON.stringify(structuredContent) }, ...extra]
1034
+ };
1035
+ }
1036
+ function jsonResult(data) {
1037
+ return structuredResult(data);
1038
+ }
1039
+ function textOnlyResult(data, extra = []) {
1040
+ const structuredContent = asStructuredObject(data);
1041
+ return { content: [{ type: "text", text: JSON.stringify(structuredContent) }, ...extra] };
1042
+ }
1043
+ function errorResult(message, details = {}) {
1044
+ return {
1045
+ content: [{ type: "text", text: JSON.stringify({ error: message, ...details }) }],
1046
+ isError: true
1047
+ };
1048
+ }
1049
+ function withExtraContent(result, ...blocks) {
1050
+ return { ...result, content: [...result.content, ...blocks] };
1051
+ }
1052
+
876
1053
  // src/tools/assistant.ts
877
1054
  import { FREEFORM_ASSISTANT_ANSWER_KEY } from "@kadoa/node-sdk";
878
1055
  import { z as z4 } from "zod";
@@ -1200,6 +1377,37 @@ var init_assistant = __esm(() => {
1200
1377
  CLOSED_ASSISTANT_STATUSES = new Set(["failed", "errored", "aborted", "timed_out"]);
1201
1378
  });
1202
1379
 
1380
+ // src/tools/compliance.ts
1381
+ function registerComplianceTools(server, ctx, helpers) {
1382
+ const { withErrorHandling } = helpers;
1383
+ server.registerTool("list_compliance_rules", {
1384
+ title: "List Compliance Rules",
1385
+ description: "List the compliance rules that apply to your team: rule type, name, description, whether it is enabled, and its configuration. " + "Rules are set at the organization level and apply to every team in it. " + "Use this for questions about robots.txt, terms of service, blocked sources or countries, PII handling, captchas, or why a workflow went to compliance review. " + "Read-only: it cannot change rules or grant exceptions; for that, contact Kadoa support.",
1386
+ inputSchema: strictSchema({}),
1387
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
1388
+ }, withErrorHandling("list_compliance_rules", async () => {
1389
+ const response = await ctx.client.axiosInstance.get(`/v4/team/${encodeURIComponent(ctx.teamId)}/compliance-rules`, { baseURL: ctx.client.baseUrl });
1390
+ const rules = response.data?.complianceRules;
1391
+ if (!Array.isArray(rules))
1392
+ throw new Error("Unexpected response from the compliance rules API.");
1393
+ return jsonResult({
1394
+ complianceRules: rules.map((rule) => ({
1395
+ ruleType: rule.ruleType,
1396
+ ruleName: rule.ruleName,
1397
+ description: rule.description ?? "",
1398
+ isEnabled: rule.isEnabled,
1399
+ configuration: rule.configuration ?? {},
1400
+ ...rule.updatedAt ? { updatedAt: rule.updatedAt } : {}
1401
+ })),
1402
+ note: rules.length === 0 ? `No compliance rules are configured for your team. ${NO_CHANGES_NOTE}` : NO_CHANGES_NOTE
1403
+ });
1404
+ }));
1405
+ }
1406
+ var NO_CHANGES_NOTE = "Rules are set by Kadoa for your organization and are read-only here. To change a rule or request an exception, contact Kadoa support.";
1407
+ var init_compliance = __esm(() => {
1408
+ init_schemas();
1409
+ });
1410
+
1203
1411
  // src/notification-channels.ts
1204
1412
  import { createHash } from "node:crypto";
1205
1413
  function recipientKey(recipients) {
@@ -2395,13 +2603,13 @@ function registerTemplateTools(server, ctx, helpers) {
2395
2603
  description: `Publish a new version of a template. Versions capture the full workflow config: prompt, schema, and notifications. All fields are optional - include only what this version should set.
2396
2604
 
2397
2605
  ` + "THIS IS HOW YOU EDIT A TEMPLATE'S PROMPT OR SCHEMA. Published versions are immutable, so 'changing the template prompt' means publishing a new version here and then calling `apply_template_version` to push it onto linked workflows. " + "`update_template` only renames a template; it cannot touch prompt or schema.",
2398
- inputSchema: withoutDefIds(strictSchema({
2606
+ inputSchema: strictSchema({
2399
2607
  templateId: z10.string().min(1).describe("The template ID to publish a version for"),
2400
2608
  prompt: z10.string().optional().describe("User prompt to copy into workflow config"),
2401
2609
  schemaId: z10.string().optional().describe("Existing schema ID to reference (mutually exclusive with schemaFields)"),
2402
2610
  schemaFields: z10.preprocess(coerceArray(), z10.array(z10.preprocess(normalizeTemplateSchemaField, z10.object(TemplateSchemaFieldShape).strict()))).optional().describe("Inline schema fields to create a new schema (mutually exclusive with schemaId)"),
2403
2611
  schemaEntity: z10.string().optional().describe("Entity name for the inline schema"),
2404
- schemaValidationRules: z10.preprocess((val) => stripSchemaValidationAttribution(coerceJson()(val)), SchemaValidationRulesSchema).optional().describe('Per-field schema validation rules keyed by field name. Each rule uses kind STRING, NUMBER, OTHER, OBJECT, or ARRAY and nested rules are supported. Edit attribution is stamped server-side, so send only rule values. Example: { "price": { "kind": "NUMBER", "minimum": { "value": 0 } } }. Not inherited from prior versions - omitting this on a new version drops any rules the previous version had.'),
2612
+ schemaValidationRules: z10.preprocess((val) => stripSchemaValidationAttribution(coerceJson()(val)), SchemaValidationRulesSchema).optional().describe("Per-field schema validation rules keyed by field name. Each value is an object with a required `kind` and optional rules for that kind; unknown keys are rejected. " + "Every kind accepts presence and uniqueness, each { target: integer 0-100 in steps of 20 } (expected percentage of rows with a value / a unique value). " + "STRING: minLength, maxLength, minHtmlElements, maxHtmlElements, each { value: integer >= 0 }; format, one of " + '{ kind: "FORMAT", source: { kind: "PRESET", preset: url | email | phone | date | datetime | time | uuid | slug } or { kind: "CUSTOM", pattern: regex string } }, ' + '{ kind: "FREE_TEXT", charset: { kind: "PRESET", preset: natural_language | alphanumeric | alpha } }, or ' + '{ kind: "LIST", source: { kind: "PRESET", preset: language2 | country2 | country3 | currency3 | month3 | usState2 } or { kind: "CUSTOM", values: [non-empty strings] } }. ' + "NUMBER: minimum and maximum, each { value: number }; maxDecimalPlaces { value: integer 0-16 }. " + "OTHER: only presence and uniqueness. " + "OBJECT: properties (required), a map of nested field name to rule. " + "ARRAY: minItems, maxItems, each { value: integer >= 0 }; items, the rule for each element. " + 'Examples: { "price": { "kind": "NUMBER", "minimum": { "value": 0 } } }; { "email": { "kind": "STRING", "format": { "kind": "FORMAT", "source": { "kind": "PRESET", "preset": "email" } } } }. ' + "Edit attribution is stamped server-side, so send only rule values. Not inherited from prior versions - omitting this on a new version drops any rules the previous version had."),
2405
2613
  notifications: z10.preprocess(coerceArray(), z10.array(z10.object({
2406
2614
  eventType: z10.string().describe("Notification event type"),
2407
2615
  eventConfiguration: z10.preprocess(coerceJson(), z10.record(z10.string(), z10.unknown())).optional(),
@@ -2417,7 +2625,7 @@ function registerTemplateTools(server, ctx, helpers) {
2417
2625
  expressions: z10.preprocess(coerceArray(true), z10.array(z10.string())).optional().describe("Cron expressions when type='cron' (e.g. '0 8 * * 2')")
2418
2626
  }))).optional().describe("Schedule specs. Required when interval='CUSTOM'.")
2419
2627
  })).optional().describe("Template-controlled scheduling for linked workflows ({interval, schedules}). Linked workflows inherit and cannot override this when set.")
2420
- })),
2628
+ }),
2421
2629
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
2422
2630
  }, withErrorHandling("create_template_version", async (args) => {
2423
2631
  const { templateId, schemaValidationRules, ...rest } = args;
@@ -3747,7 +3955,7 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3747
3955
  timezone: z12.string().min(1).optional().describe("IANA timezone for cron schedules, for example America/New_York. A timezone-only update preserves existing cron expressions."),
3748
3956
  location: z12.preprocess(coerceJson(), LocationSchema).optional().describe("Scraping location: {type:'auto'} or {type:'manual', isoCode:'US'}"),
3749
3957
  monitoring: z12.preprocess(coerceJson(), MonitoringSchema).optional().describe("Complete monitoring replacement: fields to monitor and optional AND/OR conditions. Call get_workflow first because this replaces the existing monitoring configuration."),
3750
- limit: z12.preprocess(coerceNumber(), z12.number().positive().nullable()).optional().describe("Maximum records per run. Pass null to clear the limit and extract all rows.")
3958
+ limit: z12.preprocess(coerceNumber(), z12.number().positive().nullable()).optional().describe("Maximum records per run; must be positive (greater than 0). Pass null to clear the limit and extract all rows.")
3751
3959
  }),
3752
3960
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
3753
3961
  }, withErrorHandling("update_workflow", async (args) => {
@@ -3820,11 +4028,13 @@ function registerTools(server, ctx, capabilities) {
3820
4028
  registerSupportTools(server, ctx, helpers);
3821
4029
  registerNotificationSettingsTools(server, ctx, helpers);
3822
4030
  registerTeamTools(server, ctx, helpers);
4031
+ registerComplianceTools(server, ctx, helpers);
3823
4032
  registerVariableTools(server, ctx, helpers);
3824
4033
  registerTemplateTools(server, ctx, helpers);
3825
4034
  }
3826
4035
  var init_tools = __esm(() => {
3827
4036
  init_assistant();
4037
+ init_compliance();
3828
4038
  init_notifications();
3829
4039
  init_observability();
3830
4040
  init_scrape();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kadoa/mcp",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "Kadoa MCP Server — manage workflows from Claude Desktop, Cursor, and other MCP clients",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",