@kadoa/mcp 0.7.2 → 0.7.4

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 +18 -7
  2. package/dist/index.js +481 -308
  3. package/package.json +1 -1
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.2",
20
+ version: "0.7.4",
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",
@@ -492,7 +492,7 @@ function normalizeDataFilters(val, ctx) {
492
492
  }
493
493
  return v;
494
494
  }
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;
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, templateInputShape, extractionInputShape, webhookAuthShape, notificationChannelsSchema, notificationsInputShape, requiredNotificationsInputShape, DATA_FILTER_OPERATORS, DATA_FILTER_VALUELESS_OPERATORS, DATA_FILTER_LIST_OPERATORS, DataFilterSchema, dataFiltersInput;
496
496
  var init_schemas = __esm(() => {
497
497
  init_portable_schema();
498
498
  UpdateIntervalSchema = z.enum([
@@ -712,6 +712,10 @@ var init_schemas = __esm(() => {
712
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.")
713
713
  };
714
714
  extractionPromptInput = z.string().optional();
715
+ templateInputShape = {
716
+ templateId: z.string().optional().describe("Instantiate from a published template. Pass templateId, source urls, optional templateVersion, and optional prompt with workflow-specific additional instructions. Entity and schema are inherited; do not copy the template's shared prompt, entity, or schema. Discover templates via list_templates and get_template."),
717
+ templateVersion: z.preprocess(coerceNumber(), z.number()).optional().describe("Specific published template version (integer) to instantiate. Defaults to the latest published version when templateId is set.")
718
+ };
715
719
  extractionInputShape = {
716
720
  name: z.string().optional().describe("Optional name for the workflow"),
717
721
  entity: z.string().optional().describe("Entity name for extraction (e.g., 'Product', 'Job Posting')"),
@@ -1377,6 +1381,254 @@ var init_assistant = __esm(() => {
1377
1381
  CLOSED_ASSISTANT_STATUSES = new Set(["failed", "errored", "aborted", "timed_out"]);
1378
1382
  });
1379
1383
 
1384
+ // src/tools/compliance.ts
1385
+ function registerComplianceTools(server, ctx, helpers) {
1386
+ const { withErrorHandling } = helpers;
1387
+ server.registerTool("list_compliance_rules", {
1388
+ title: "List Compliance Rules",
1389
+ 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.",
1390
+ inputSchema: strictSchema({}),
1391
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
1392
+ }, withErrorHandling("list_compliance_rules", async () => {
1393
+ const response = await ctx.client.axiosInstance.get(`/v4/team/${encodeURIComponent(ctx.teamId)}/compliance-rules`, { baseURL: ctx.client.baseUrl });
1394
+ const rules = response.data?.complianceRules;
1395
+ if (!Array.isArray(rules))
1396
+ throw new Error("Unexpected response from the compliance rules API.");
1397
+ return jsonResult({
1398
+ complianceRules: rules.map((rule) => ({
1399
+ ruleType: rule.ruleType,
1400
+ ruleName: rule.ruleName,
1401
+ description: rule.description ?? "",
1402
+ isEnabled: rule.isEnabled,
1403
+ configuration: rule.configuration ?? {},
1404
+ ...rule.updatedAt ? { updatedAt: rule.updatedAt } : {}
1405
+ })),
1406
+ note: rules.length === 0 ? `No compliance rules are configured for your team. ${NO_CHANGES_NOTE}` : NO_CHANGES_NOTE
1407
+ });
1408
+ }));
1409
+ }
1410
+ 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.";
1411
+ var init_compliance = __esm(() => {
1412
+ init_schemas();
1413
+ });
1414
+
1415
+ // src/tools/shared/budget.ts
1416
+ function jsonBytes(value) {
1417
+ const text = JSON.stringify(value);
1418
+ return text === undefined ? 0 : Buffer.byteLength(text, "utf8");
1419
+ }
1420
+ function truncateUtf8(text, maxBytes) {
1421
+ if (Buffer.byteLength(text, "utf8") <= maxBytes)
1422
+ return text;
1423
+ const { read } = new TextEncoder().encodeInto(text, new Uint8Array(Math.max(0, maxBytes)));
1424
+ return text.slice(0, read);
1425
+ }
1426
+ function previewValue(value, maxBytes = VALUE_PREVIEW_BYTES) {
1427
+ if (value === undefined || value === null)
1428
+ return null;
1429
+ const bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
1430
+ if (bytes <= maxBytes)
1431
+ return value;
1432
+ if (typeof value === "string")
1433
+ return `${truncateUtf8(value, maxBytes)}… [cut, ${bytes} bytes]`;
1434
+ if (Array.isArray(value))
1435
+ return `[array of ${value.length} items, ${bytes} bytes]`;
1436
+ return `[${typeof value}, ${bytes} bytes]`;
1437
+ }
1438
+ function fitItems(options) {
1439
+ const budget = options.budgetBytes ?? RESULT_BUDGET_BYTES;
1440
+ const all = [...options.items];
1441
+ if (jsonBytes(options.build(all)) <= budget)
1442
+ return { kept: all, omitted: 0, truncated: false };
1443
+ const limit = budget - TRUNCATION_RESERVE_BYTES - (options.reserveBytes ?? 0);
1444
+ let lo = 0;
1445
+ let hi = all.length - 1;
1446
+ while (lo < hi) {
1447
+ const mid = Math.ceil((lo + hi) / 2);
1448
+ if (jsonBytes(options.build(all.slice(0, mid))) <= limit)
1449
+ lo = mid;
1450
+ else
1451
+ hi = mid - 1;
1452
+ }
1453
+ return { kept: all.slice(0, lo), omitted: all.length - lo, truncated: true };
1454
+ }
1455
+ function truncationNotice(options) {
1456
+ const budgetBytes = options.budgetBytes ?? RESULT_BUDGET_BYTES;
1457
+ const total = options.kept + options.omitted;
1458
+ return {
1459
+ truncated: true,
1460
+ truncation: { kept: options.kept, omitted: options.omitted, budgetBytes },
1461
+ hint: `Result cut to ${options.kept} of ${total} ${options.noun} to stay under ${Math.round(budgetBytes / 1000)} KB. ${options.hint}`
1462
+ };
1463
+ }
1464
+ function highlyDivisibleFloor(n) {
1465
+ if (n <= 0)
1466
+ return 0;
1467
+ if (n >= 10)
1468
+ return Math.floor(n / 10) * 10;
1469
+ let p = 1;
1470
+ while (p * 2 <= n)
1471
+ p *= 2;
1472
+ return p;
1473
+ }
1474
+ function alignedKeep(offset, fit) {
1475
+ if (offset === 0)
1476
+ return highlyDivisibleFloor(fit);
1477
+ for (let keep = fit;keep >= 1; keep--) {
1478
+ if (offset % keep === 0 && offset / keep + 2 <= MAX_PAGE)
1479
+ return keep;
1480
+ }
1481
+ return 0;
1482
+ }
1483
+ function resumeNumberedPage(meta, options) {
1484
+ const { next: _replaced, totalPages: _droppedTotalPages, ...rest } = meta;
1485
+ if (options.kept === 0)
1486
+ return { ...rest, hasMore: false };
1487
+ const offset = (options.page - 1) * options.limit;
1488
+ return {
1489
+ ...rest,
1490
+ hasMore: true,
1491
+ next: nextArgs(options.args, { page: offset / options.kept + 2, limit: options.kept })
1492
+ };
1493
+ }
1494
+ function budgetNumberedPage(options) {
1495
+ const offset = (options.page - 1) * options.limit;
1496
+ const fit = fitItems({
1497
+ items: options.items,
1498
+ build: (kept) => options.build(kept, options.pagination),
1499
+ reserveBytes: jsonBytes({ pagination: { next: options.args } })
1500
+ });
1501
+ if (!fit.truncated)
1502
+ return options.build([...options.items], options.pagination);
1503
+ const buildCut = (fitCount) => {
1504
+ const keep = alignedKeep(offset, fitCount);
1505
+ const alignmentImpossible = keep === 0 && fitCount > 0;
1506
+ const pagination = resumeNumberedPage(options.pagination, {
1507
+ args: options.args,
1508
+ page: options.page,
1509
+ limit: options.limit,
1510
+ kept: keep
1511
+ });
1512
+ const hint = keep > 0 ? options.resumeHint : alignmentImpossible ? options.alignmentImpossibleHint ?? DEFAULT_ALIGNMENT_IMPOSSIBLE_HINT : options.emptyHint;
1513
+ const result = {
1514
+ ...options.build(options.items.slice(0, keep), pagination),
1515
+ ...truncationNotice({ kept: keep, omitted: options.items.length - keep, noun: options.noun, hint })
1516
+ };
1517
+ return { result, keep };
1518
+ };
1519
+ let fitCount = fit.kept.length;
1520
+ let { result, keep } = buildCut(fitCount);
1521
+ while (jsonBytes(result) > RESULT_BUDGET_BYTES && keep > 0) {
1522
+ fitCount = keep - 1;
1523
+ ({ result, keep } = buildCut(fitCount));
1524
+ }
1525
+ return result;
1526
+ }
1527
+ var RESULT_BUDGET_BYTES = 80000, TRUNCATION_RESERVE_BYTES = 1024, SCRAPE_CONTENT_BUDGET_BYTES = 70000, VALUE_PREVIEW_BYTES = 1000, DEFAULT_ALIGNMENT_IMPOSSIBLE_HINT = "Page offset too deep to resume inline at this limit; retry with a smaller limit or from an earlier page.";
1528
+ var init_budget = __esm(() => {
1529
+ init_pagination();
1530
+ });
1531
+
1532
+ // src/tools/docs.ts
1533
+ import { z as z5 } from "zod";
1534
+ function parseDocsIndex(text) {
1535
+ const pages = [];
1536
+ for (const line of text.split(`
1537
+ `)) {
1538
+ const match = INDEX_LINE.exec(line.trim());
1539
+ if (!match)
1540
+ continue;
1541
+ const [, title, url, description] = match;
1542
+ const markdownUrl = docsPageMarkdownUrl(url);
1543
+ if (!markdownUrl)
1544
+ continue;
1545
+ pages.push({
1546
+ title,
1547
+ url: markdownUrl.replace(/\.md$/, ""),
1548
+ ...description && { description: description.trim() }
1549
+ });
1550
+ }
1551
+ return pages;
1552
+ }
1553
+ function docsPageMarkdownUrl(page) {
1554
+ let path = page.trim();
1555
+ if (/^https?:\/\//i.test(path)) {
1556
+ let url;
1557
+ try {
1558
+ url = new URL(path);
1559
+ } catch {
1560
+ return;
1561
+ }
1562
+ if (url.host !== DOCS_HOST)
1563
+ return;
1564
+ path = url.pathname;
1565
+ }
1566
+ path = `/${path.replace(/^\/+/, "").replace(/\.mdx?$/, "")}`;
1567
+ if (path === "/" || !/^(\/[a-z0-9][a-z0-9._-]*)+$/i.test(path) || path.includes(".."))
1568
+ return;
1569
+ return `${DOCS_ORIGIN}${path}.md`;
1570
+ }
1571
+ async function fetchDocsText(url) {
1572
+ const response = await fetch(url, { signal: AbortSignal.timeout(DOCS_FETCH_TIMEOUT_MS) });
1573
+ return { status: response.status, ...response.ok && { text: await response.text() } };
1574
+ }
1575
+ function registerDocsTools(server, _ctx, helpers) {
1576
+ const { withErrorHandling } = helpers;
1577
+ server.registerTool("list_docs_pages", {
1578
+ title: "List Kadoa Docs Pages",
1579
+ description: "List every Kadoa documentation page with its title, URL, and one-line summary. Use this for questions " + "about how Kadoa works or where to find something in the dashboard (API keys, billing and invoices, " + "usage and slots, team settings, SSO, notifications, the SDK and API). Pick the relevant pages by meaning, " + "then read them with read_docs_page before answering.",
1580
+ inputSchema: strictSchema({}),
1581
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
1582
+ }, withErrorHandling("list_docs_pages", async () => {
1583
+ const index = await fetchDocsText(`${DOCS_ORIGIN}/llms.txt`);
1584
+ if (index.text === undefined) {
1585
+ throw new Error(`The Kadoa docs index could not be loaded (HTTP ${index.status}).`);
1586
+ }
1587
+ const pages = parseDocsIndex(index.text);
1588
+ return jsonResult({ pages, totalCount: pages.length });
1589
+ }));
1590
+ server.registerTool("read_docs_page", {
1591
+ title: "Read Kadoa Docs Page",
1592
+ description: "Read one Kadoa documentation page as markdown. Pass a page URL or path from list_docs_pages. " + "Cite the page URL when you answer from it.",
1593
+ inputSchema: strictSchema({
1594
+ page: z5.string().min(1).describe("Page URL or path from list_docs_pages, e.g. https://docs.kadoa.com/docs/api-keys.md or /docs/api-keys")
1595
+ }),
1596
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
1597
+ }, withErrorHandling("read_docs_page", async (args) => {
1598
+ const url = docsPageMarkdownUrl(args.page);
1599
+ if (!url) {
1600
+ return jsonResult({
1601
+ success: false,
1602
+ message: `"${args.page}" is not a docs.kadoa.com page. Use a URL or path returned by list_docs_pages.`
1603
+ });
1604
+ }
1605
+ const page = await fetchDocsText(url);
1606
+ if (page.text === undefined) {
1607
+ if (page.status === 404) {
1608
+ return jsonResult({
1609
+ success: false,
1610
+ message: "That docs page does not exist. Find the right page with list_docs_pages."
1611
+ });
1612
+ }
1613
+ throw new Error(`The Kadoa docs page could not be loaded (HTTP ${page.status}).`);
1614
+ }
1615
+ const content = truncateUtf8(page.text, SCRAPE_CONTENT_BUDGET_BYTES);
1616
+ const truncated = content.length < page.text.length;
1617
+ return jsonResult({
1618
+ url: url.replace(/\.md$/, ""),
1619
+ content,
1620
+ ...truncated && { truncated: true, hint: "The page was cut to fit. The full page is at the URL above." }
1621
+ });
1622
+ }));
1623
+ }
1624
+ var DOCS_ORIGIN = "https://docs.kadoa.com", DOCS_HOST, DOCS_FETCH_TIMEOUT_MS = 1e4, INDEX_LINE;
1625
+ var init_docs = __esm(() => {
1626
+ init_budget();
1627
+ init_schemas();
1628
+ DOCS_HOST = new URL(DOCS_ORIGIN).host;
1629
+ INDEX_LINE = /^- \[(.+?)\]\((\S+?)\)(?::\s*(.*))?$/;
1630
+ });
1631
+
1380
1632
  // src/notification-channels.ts
1381
1633
  import { createHash } from "node:crypto";
1382
1634
  function recipientKey(recipients) {
@@ -1463,14 +1715,14 @@ function redactChannelRefs(channels) {
1463
1715
  var init_notification_channels = () => {};
1464
1716
 
1465
1717
  // src/tools/notifications.ts
1466
- import { z as z5 } from "zod";
1718
+ import { z as z6 } from "zod";
1467
1719
  function registerNotificationChannelTools(server, ctx, helpers) {
1468
1720
  const { withErrorHandling } = helpers;
1469
1721
  server.registerTool("list_notification_channels", {
1470
1722
  title: "List Notification Channels",
1471
1723
  description: "List notification channels configured for a workflow or across the workspace. Channels are destinations (email, Slack, webhook, websocket) where notifications are sent.",
1472
1724
  inputSchema: strictSchema({
1473
- workflowId: z5.string().optional().describe("Filter channels linked to a specific workflow. Omit to list all workspace channels.")
1725
+ workflowId: z6.string().optional().describe("Filter channels linked to a specific workflow. Omit to list all workspace channels.")
1474
1726
  }),
1475
1727
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
1476
1728
  }, withErrorHandling("list_notification_channels", async (args) => {
@@ -1493,20 +1745,20 @@ function registerNotificationChannelTools(server, ctx, helpers) {
1493
1745
  title: "Create Notification Channel",
1494
1746
  description: "Create a notification channel (email, webhook, slack, or websocket). If a channel of the same type already delivers to that destination it is reused and returned with reused=true, rather than creating a duplicate. The channel can then be linked to notification settings for specific events. " + "For SLACK: use slackChannelId + slackChannelName for OAuth integration, or webhookUrl for legacy incoming webhooks. " + "For WEBHOOK: auth is optional and supports bearer, basic, or header authentication.",
1495
1747
  inputSchema: strictSchema({
1496
- channelType: z5.enum(["EMAIL", "WEBHOOK", "SLACK", "WEBSOCKET"]).describe("Type of notification channel"),
1497
- name: z5.string().optional().describe("Name for the channel (defaults to 'default')"),
1498
- config: z5.object({
1499
- recipients: z5.array(z5.string()).optional().describe("Email recipients (required for EMAIL channels)"),
1500
- webhookUrl: z5.string().optional().describe("Webhook/Slack endpoint URL (required for WEBHOOK, optional for SLACK)"),
1501
- httpMethod: z5.enum(["POST", "GET", "PUT", "PATCH"]).optional().describe("HTTP method for WEBHOOK channels (defaults to POST)"),
1502
- slackChannelId: z5.string().optional().describe("Slack channel ID from OAuth integration (for SLACK channels, e.g. C01ABCDEF)"),
1503
- slackChannelName: z5.string().optional().describe("Slack channel name (for SLACK channels, e.g. #alerts)"),
1504
- auth: z5.object({
1505
- type: z5.enum(["bearer", "basic", "header"]).describe("Authentication type"),
1506
- token: z5.string().optional().describe("Bearer token (for 'bearer' type)"),
1507
- username: z5.string().optional().describe("Username (for 'basic' type)"),
1508
- password: z5.string().optional().describe("Password (for 'basic' type)"),
1509
- headers: z5.object({}).passthrough().optional().describe("Custom headers as key-value pairs (for 'header' type)")
1748
+ channelType: z6.enum(["EMAIL", "WEBHOOK", "SLACK", "WEBSOCKET"]).describe("Type of notification channel"),
1749
+ name: z6.string().optional().describe("Name for the channel (defaults to 'default')"),
1750
+ config: z6.object({
1751
+ recipients: z6.array(z6.string()).optional().describe("Email recipients (required for EMAIL channels)"),
1752
+ webhookUrl: z6.string().optional().describe("Webhook/Slack endpoint URL (required for WEBHOOK, optional for SLACK)"),
1753
+ httpMethod: z6.enum(["POST", "GET", "PUT", "PATCH"]).optional().describe("HTTP method for WEBHOOK channels (defaults to POST)"),
1754
+ slackChannelId: z6.string().optional().describe("Slack channel ID from OAuth integration (for SLACK channels, e.g. C01ABCDEF)"),
1755
+ slackChannelName: z6.string().optional().describe("Slack channel name (for SLACK channels, e.g. #alerts)"),
1756
+ auth: z6.object({
1757
+ type: z6.enum(["bearer", "basic", "header"]).describe("Authentication type"),
1758
+ token: z6.string().optional().describe("Bearer token (for 'bearer' type)"),
1759
+ username: z6.string().optional().describe("Username (for 'basic' type)"),
1760
+ password: z6.string().optional().describe("Password (for 'basic' type)"),
1761
+ headers: z6.object({}).passthrough().optional().describe("Custom headers as key-value pairs (for 'header' type)")
1510
1762
  }).optional().describe("Authentication for WEBHOOK channels")
1511
1763
  }).optional().describe("Channel-specific configuration. " + "EMAIL: provide recipients. " + "WEBHOOK: provide webhookUrl, httpMethod, and optionally auth. " + "SLACK: provide slackChannelId + slackChannelName (OAuth) or webhookUrl (legacy). " + "WEBSOCKET: no config needed.")
1512
1764
  }),
@@ -1553,8 +1805,8 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
1553
1805
  title: "Delete Notification Channel",
1554
1806
  description: "Delete a notification channel by ID. " + "Call first without confirmed=true to preview, then with confirmed=true after user agrees.",
1555
1807
  inputSchema: strictSchema({
1556
- channelId: z5.string().min(1).describe("The channel ID to delete"),
1557
- confirmed: z5.preprocess(coerceBoolean(), z5.boolean()).optional().describe("Set to true only after the user has explicitly confirmed deletion.")
1808
+ channelId: z6.string().min(1).describe("The channel ID to delete"),
1809
+ confirmed: z6.preprocess(coerceBoolean(), z6.boolean()).optional().describe("Set to true only after the user has explicitly confirmed deletion.")
1558
1810
  }),
1559
1811
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
1560
1812
  }, withErrorHandling("delete_notification_channel", async (args) => {
@@ -1575,7 +1827,7 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
1575
1827
  title: "List Notification Settings",
1576
1828
  description: "List notification settings (event-to-channel mappings) for a workflow or workspace. Shows which events trigger notifications and through which channels.",
1577
1829
  inputSchema: strictSchema({
1578
- workflowId: z5.string().optional().describe("Filter settings for a specific workflow. Omit to list workspace-level settings."),
1830
+ workflowId: z6.string().optional().describe("Filter settings for a specific workflow. Omit to list workspace-level settings."),
1579
1831
  eventType: NotificationEventTypeSchema.optional().describe("Filter by event type")
1580
1832
  }),
1581
1833
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
@@ -1603,23 +1855,23 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
1603
1855
  title: "Configure Notifications",
1604
1856
  description: "Set up notifications for a workflow or workspace. Creates channels and settings in one step. " + "Specify which events to listen for and which channels to deliver to (email, webhook, slack, websocket).",
1605
1857
  inputSchema: strictSchema({
1606
- workflowId: z5.string().optional().describe("Workflow ID to configure notifications for. Omit for workspace-level notifications."),
1607
- events: z5.preprocess(coerceList(), z5.array(z5.enum(["all", ...NOTIFICATION_EVENT_TYPES])).min(1)).describe('Event types to notify on. Pass ["all"] for every event type, or list specific event types.'),
1608
- channels: z5.object({
1609
- email: z5.object({
1610
- recipients: z5.array(z5.string().email()).optional().describe("Email addresses to notify. Omit to use the account default email.")
1858
+ workflowId: z6.string().optional().describe("Workflow ID to configure notifications for. Omit for workspace-level notifications."),
1859
+ events: z6.preprocess(coerceList(), z6.array(z6.enum(["all", ...NOTIFICATION_EVENT_TYPES])).min(1)).describe('Event types to notify on. Pass ["all"] for every event type, or list specific event types.'),
1860
+ channels: z6.object({
1861
+ email: z6.object({
1862
+ recipients: z6.array(z6.string().email()).optional().describe("Email addresses to notify. Omit to use the account default email.")
1611
1863
  }).optional().describe("Send email notifications. Pass {} for default email or {recipients: [...]} for custom addresses."),
1612
- webhook: z5.object({
1613
- url: z5.string().url().describe("Webhook endpoint URL"),
1614
- httpMethod: z5.enum(["POST", "GET", "PUT", "PATCH"]).optional().describe("HTTP method (defaults to POST)"),
1864
+ webhook: z6.object({
1865
+ url: z6.string().url().describe("Webhook endpoint URL"),
1866
+ httpMethod: z6.enum(["POST", "GET", "PUT", "PATCH"]).optional().describe("HTTP method (defaults to POST)"),
1615
1867
  auth: webhookAuthShape
1616
1868
  }).optional().describe("Send via HTTP webhook"),
1617
- slack: z5.object({
1618
- webhookUrl: z5.string().url().optional().describe("Slack incoming webhook URL (legacy)"),
1619
- slackChannelId: z5.string().optional().describe("Slack channel ID from OAuth integration (e.g. C01ABCDEF)"),
1620
- slackChannelName: z5.string().optional().describe("Slack channel name (e.g. #alerts)")
1869
+ slack: z6.object({
1870
+ webhookUrl: z6.string().url().optional().describe("Slack incoming webhook URL (legacy)"),
1871
+ slackChannelId: z6.string().optional().describe("Slack channel ID from OAuth integration (e.g. C01ABCDEF)"),
1872
+ slackChannelName: z6.string().optional().describe("Slack channel name (e.g. #alerts)")
1621
1873
  }).optional().describe("Send to Slack channel. Use slackChannelId/slackChannelName for OAuth integration, or webhookUrl for legacy incoming webhooks."),
1622
- websocket: z5.boolean().optional().describe("Enable WebSocket notifications")
1874
+ websocket: z6.boolean().optional().describe("Enable WebSocket notifications")
1623
1875
  }).describe("At least one channel must be specified")
1624
1876
  }),
1625
1877
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
@@ -1681,8 +1933,8 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
1681
1933
  title: "Delete Notification Setting",
1682
1934
  description: "Delete a notification setting by ID. This removes the event-to-channel mapping but does not delete the channels themselves. " + "Call first without confirmed=true to preview, then with confirmed=true after user agrees.",
1683
1935
  inputSchema: strictSchema({
1684
- settingId: z5.string().min(1).describe("The notification setting ID to delete (the id from list_notification_settings)"),
1685
- confirmed: z5.preprocess(coerceBoolean(), z5.boolean()).optional().describe("Set to true only after the user has explicitly confirmed deletion.")
1936
+ settingId: z6.string().min(1).describe("The notification setting ID to delete (the id from list_notification_settings)"),
1937
+ confirmed: z6.preprocess(coerceBoolean(), z6.boolean()).optional().describe("Set to true only after the user has explicitly confirmed deletion.")
1686
1938
  }),
1687
1939
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
1688
1940
  }, withErrorHandling("delete_notification_setting", async (args) => {
@@ -1708,7 +1960,7 @@ var init_notifications = __esm(() => {
1708
1960
 
1709
1961
  // src/tools/observability.ts
1710
1962
  import { KadoaSdkException as KadoaSdkException2 } from "@kadoa/node-sdk";
1711
- import { z as z6 } from "zod";
1963
+ import { z as z7 } from "zod";
1712
1964
  function mapActivityEvent(event, includeDetails) {
1713
1965
  const details = event.details ?? {};
1714
1966
  const summary = details._changeSummary;
@@ -1781,10 +2033,10 @@ function registerObservabilityTools(server, ctx, helpers) {
1781
2033
  title: "Get Observability Metrics",
1782
2034
  description: "Workflow health for the active team over a window of days (default 30): overall uptime in percent, " + "successful and failed runs, mean time to recovery in minutes, records delivered, counts of healthy, " + "degraded, down, and idle workflows, the same numbers for the previous window, and an 'attention' list " + "of down and degraded workflows ranked worst first. This is the data behind the Observability page. " + "Use for 'how healthy are my workflows', 'what is my uptime', 'which workflows are down or failing', " + "'how fast do we recover', 'how did this month compare to last'. " + "Do NOT use it to explain why one workflow failed: call list_workflow_runs (errors per run) or " + "get_workflow for that. For what needs the user's action right now use list_workflows with " + "statusFilters group:attention. " + "Health comes from each workflow's last run in the window: healthy means the last run finished, down " + "means the last run failed, idle means no finished or failed run. uptimePercentage is null when no runs " + "happened. mttrMinutes is null when nothing failed and then recovered. openSupportRequests is a count " + "only. " + "Output is bounded: pass includeWorkflows for every workflow ranked by severity (up to limit, default " + "25, max 200) and includeDailySeries for the team-level daily uptime series; per-workflow daily series " + "are never returned. Quote workflows by name; ids are stripped from customer-facing text. Large teams " + "can take up to 90 seconds; prefer 30 or 90 days over 365.",
1783
2035
  inputSchema: strictSchema({
1784
- days: z6.preprocess(coerceNumber(), z6.number().int().min(1).max(365)).optional().describe("Window length in days, 1 to 365 (default 30). The dashboard offers 30, 90, and 365."),
1785
- includeWorkflows: z6.preprocess(coerceBoolean(), z6.boolean()).optional().describe("Also return every workflow ranked by severity, not only the down and degraded ones (default false)."),
1786
- includeDailySeries: z6.preprocess(coerceBoolean(), z6.boolean()).optional().describe("Also return the team-level daily series of successful runs, failed runs, and uptime (default false)."),
1787
- limit: z6.preprocess(coerceNumber(), z6.number().int().min(1).max(OBSERVABILITY_MAX_LIMIT)).optional().describe("Maximum workflows in the attention list and in workflows (default 25, max 200).")
2036
+ days: z7.preprocess(coerceNumber(), z7.number().int().min(1).max(365)).optional().describe("Window length in days, 1 to 365 (default 30). The dashboard offers 30, 90, and 365."),
2037
+ includeWorkflows: z7.preprocess(coerceBoolean(), z7.boolean()).optional().describe("Also return every workflow ranked by severity, not only the down and degraded ones (default false)."),
2038
+ includeDailySeries: z7.preprocess(coerceBoolean(), z7.boolean()).optional().describe("Also return the team-level daily series of successful runs, failed runs, and uptime (default false)."),
2039
+ limit: z7.preprocess(coerceNumber(), z7.number().int().min(1).max(OBSERVABILITY_MAX_LIMIT)).optional().describe("Maximum workflows in the attention list and in workflows (default 25, max 200).")
1788
2040
  }),
1789
2041
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
1790
2042
  }, withErrorHandling("get_observability", async (args) => {
@@ -1842,15 +2094,15 @@ function registerObservabilityTools(server, ctx, helpers) {
1842
2094
  inputSchema: strictSchema({
1843
2095
  limit: limitInput({ noun: "Events", defaultLimit: ACTIVITY_DEFAULT_LIMIT, max: ACTIVITY_MAX_LIMIT }),
1844
2096
  offset: offsetInput("offset", "events"),
1845
- workflowId: z6.string().optional().describe("Only events about this workflow (runs, config changes, data changes, notifications)"),
1846
- userId: z6.string().optional().describe("Only events by this user id. Resolve an email to an id from a previous list_activity result."),
1847
- startDate: z6.string().optional().describe("ISO 8601 lower bound. Ignored when relativeTime is set."),
1848
- endDate: z6.string().optional().describe("ISO 8601 upper bound. Ignored when relativeTime is set."),
1849
- relativeTime: z6.string().regex(/^\d+(m|h|d|w)$/i, "Use <number><unit> with unit m, h, d or w, e.g. 6h or 2w").optional().describe("Window counted back from now, e.g. 30m, 6h, 1d, 2w. Preferred for 'last week' style questions."),
1850
- eventTypes: z6.preprocess(coerceList({ upperCase: true }), z6.array(z6.enum(ACTIVITY_EVENT_TYPES))).optional().describe("Actions to keep. Workflow run outcomes (FINISHED, FAILED, ...) count as actions."),
1851
- interfaces: z6.preprocess(coerceList({ upperCase: true }), z6.array(z6.enum(ACTIVITY_INTERFACES))).optional().describe("Interfaces the action came from"),
1852
- resourceTypes: z6.preprocess(coerceList({ upperCase: true }), z6.array(z6.enum(ACTIVITY_RESOURCE_TYPES))).optional().describe("Resource types to keep"),
1853
- includeDetails: z6.preprocess(coerceBoolean(), z6.boolean()).optional().describe("Include sanitized previousValue/newValue snapshots per event (default false; large)")
2097
+ workflowId: z7.string().optional().describe("Only events about this workflow (runs, config changes, data changes, notifications)"),
2098
+ userId: z7.string().optional().describe("Only events by this user id. Resolve an email to an id from a previous list_activity result."),
2099
+ startDate: z7.string().optional().describe("ISO 8601 lower bound. Ignored when relativeTime is set."),
2100
+ endDate: z7.string().optional().describe("ISO 8601 upper bound. Ignored when relativeTime is set."),
2101
+ relativeTime: z7.string().regex(/^\d+(m|h|d|w)$/i, "Use <number><unit> with unit m, h, d or w, e.g. 6h or 2w").optional().describe("Window counted back from now, e.g. 30m, 6h, 1d, 2w. Preferred for 'last week' style questions."),
2102
+ eventTypes: z7.preprocess(coerceList({ upperCase: true }), z7.array(z7.enum(ACTIVITY_EVENT_TYPES))).optional().describe("Actions to keep. Workflow run outcomes (FINISHED, FAILED, ...) count as actions."),
2103
+ interfaces: z7.preprocess(coerceList({ upperCase: true }), z7.array(z7.enum(ACTIVITY_INTERFACES))).optional().describe("Interfaces the action came from"),
2104
+ resourceTypes: z7.preprocess(coerceList({ upperCase: true }), z7.array(z7.enum(ACTIVITY_RESOURCE_TYPES))).optional().describe("Resource types to keep"),
2105
+ includeDetails: z7.preprocess(coerceBoolean(), z7.boolean()).optional().describe("Include sanitized previousValue/newValue snapshots per event (default false; large)")
1854
2106
  }),
1855
2107
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
1856
2108
  }, withErrorHandling("list_activity", async (args) => {
@@ -1928,125 +2180,8 @@ var init_observability = __esm(() => {
1928
2180
  HEALTH_SEVERITY = { down: 0, degraded: 1, idle: 2, healthy: 3 };
1929
2181
  });
1930
2182
 
1931
- // src/tools/shared/budget.ts
1932
- function jsonBytes(value) {
1933
- const text = JSON.stringify(value);
1934
- return text === undefined ? 0 : Buffer.byteLength(text, "utf8");
1935
- }
1936
- function truncateUtf8(text, maxBytes) {
1937
- if (Buffer.byteLength(text, "utf8") <= maxBytes)
1938
- return text;
1939
- const { read } = new TextEncoder().encodeInto(text, new Uint8Array(Math.max(0, maxBytes)));
1940
- return text.slice(0, read);
1941
- }
1942
- function previewValue(value, maxBytes = VALUE_PREVIEW_BYTES) {
1943
- if (value === undefined || value === null)
1944
- return null;
1945
- const bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
1946
- if (bytes <= maxBytes)
1947
- return value;
1948
- if (typeof value === "string")
1949
- return `${truncateUtf8(value, maxBytes)}… [cut, ${bytes} bytes]`;
1950
- if (Array.isArray(value))
1951
- return `[array of ${value.length} items, ${bytes} bytes]`;
1952
- return `[${typeof value}, ${bytes} bytes]`;
1953
- }
1954
- function fitItems(options) {
1955
- const budget = options.budgetBytes ?? RESULT_BUDGET_BYTES;
1956
- const all = [...options.items];
1957
- if (jsonBytes(options.build(all)) <= budget)
1958
- return { kept: all, omitted: 0, truncated: false };
1959
- const limit = budget - TRUNCATION_RESERVE_BYTES - (options.reserveBytes ?? 0);
1960
- let lo = 0;
1961
- let hi = all.length - 1;
1962
- while (lo < hi) {
1963
- const mid = Math.ceil((lo + hi) / 2);
1964
- if (jsonBytes(options.build(all.slice(0, mid))) <= limit)
1965
- lo = mid;
1966
- else
1967
- hi = mid - 1;
1968
- }
1969
- return { kept: all.slice(0, lo), omitted: all.length - lo, truncated: true };
1970
- }
1971
- function truncationNotice(options) {
1972
- const budgetBytes = options.budgetBytes ?? RESULT_BUDGET_BYTES;
1973
- const total = options.kept + options.omitted;
1974
- return {
1975
- truncated: true,
1976
- truncation: { kept: options.kept, omitted: options.omitted, budgetBytes },
1977
- hint: `Result cut to ${options.kept} of ${total} ${options.noun} to stay under ${Math.round(budgetBytes / 1000)} KB. ${options.hint}`
1978
- };
1979
- }
1980
- function highlyDivisibleFloor(n) {
1981
- if (n <= 0)
1982
- return 0;
1983
- if (n >= 10)
1984
- return Math.floor(n / 10) * 10;
1985
- let p = 1;
1986
- while (p * 2 <= n)
1987
- p *= 2;
1988
- return p;
1989
- }
1990
- function alignedKeep(offset, fit) {
1991
- if (offset === 0)
1992
- return highlyDivisibleFloor(fit);
1993
- for (let keep = fit;keep >= 1; keep--) {
1994
- if (offset % keep === 0 && offset / keep + 2 <= MAX_PAGE)
1995
- return keep;
1996
- }
1997
- return 0;
1998
- }
1999
- function resumeNumberedPage(meta, options) {
2000
- const { next: _replaced, totalPages: _droppedTotalPages, ...rest } = meta;
2001
- if (options.kept === 0)
2002
- return { ...rest, hasMore: false };
2003
- const offset = (options.page - 1) * options.limit;
2004
- return {
2005
- ...rest,
2006
- hasMore: true,
2007
- next: nextArgs(options.args, { page: offset / options.kept + 2, limit: options.kept })
2008
- };
2009
- }
2010
- function budgetNumberedPage(options) {
2011
- const offset = (options.page - 1) * options.limit;
2012
- const fit = fitItems({
2013
- items: options.items,
2014
- build: (kept) => options.build(kept, options.pagination),
2015
- reserveBytes: jsonBytes({ pagination: { next: options.args } })
2016
- });
2017
- if (!fit.truncated)
2018
- return options.build([...options.items], options.pagination);
2019
- const buildCut = (fitCount) => {
2020
- const keep = alignedKeep(offset, fitCount);
2021
- const alignmentImpossible = keep === 0 && fitCount > 0;
2022
- const pagination = resumeNumberedPage(options.pagination, {
2023
- args: options.args,
2024
- page: options.page,
2025
- limit: options.limit,
2026
- kept: keep
2027
- });
2028
- const hint = keep > 0 ? options.resumeHint : alignmentImpossible ? options.alignmentImpossibleHint ?? DEFAULT_ALIGNMENT_IMPOSSIBLE_HINT : options.emptyHint;
2029
- const result = {
2030
- ...options.build(options.items.slice(0, keep), pagination),
2031
- ...truncationNotice({ kept: keep, omitted: options.items.length - keep, noun: options.noun, hint })
2032
- };
2033
- return { result, keep };
2034
- };
2035
- let fitCount = fit.kept.length;
2036
- let { result, keep } = buildCut(fitCount);
2037
- while (jsonBytes(result) > RESULT_BUDGET_BYTES && keep > 0) {
2038
- fitCount = keep - 1;
2039
- ({ result, keep } = buildCut(fitCount));
2040
- }
2041
- return result;
2042
- }
2043
- var RESULT_BUDGET_BYTES = 80000, TRUNCATION_RESERVE_BYTES = 1024, SCRAPE_CONTENT_BUDGET_BYTES = 70000, VALUE_PREVIEW_BYTES = 1000, DEFAULT_ALIGNMENT_IMPOSSIBLE_HINT = "Page offset too deep to resume inline at this limit; retry with a smaller limit or from an earlier page.";
2044
- var init_budget = __esm(() => {
2045
- init_pagination();
2046
- });
2047
-
2048
2183
  // src/tools/scrape.ts
2049
- import { z as z7 } from "zod";
2184
+ import { z as z8 } from "zod";
2050
2185
  function scrapeResult(response, format) {
2051
2186
  const { content, ...metadata } = response;
2052
2187
  if (typeof content !== "string" || content.length === 0)
@@ -2081,11 +2216,11 @@ function registerScrapeTools(server, ctx, helpers, defaultProductType) {
2081
2216
  title: "Scrape URL",
2082
2217
  description: "Immediately fetch one URL as LLM-friendly markdown or raw HTML without creating a workflow. " + "Markdown is the default. Routing is fully automatic: it starts cheap and escalates through stealth tiers on its own when a site blocks bots, and remembers the working method per host. " + (defaultProductType === "realtime" ? "Use create_realtime_monitor for monitoring, and create_workflow for one-time or scheduled structured extraction or navigation-heavy jobs. " : "Use create_workflow instead for structured extraction, recurring runs, monitoring, or navigation-heavy jobs. ") + "The result is a JSON metadata block followed by the page text as its own block, capped at 70 KB.",
2083
2218
  inputSchema: strictSchema({
2084
- url: z7.url().refine((value) => {
2219
+ url: z8.url().refine((value) => {
2085
2220
  const protocol = new URL(value).protocol;
2086
2221
  return protocol === "http:" || protocol === "https:";
2087
2222
  }, "URL must use HTTP or HTTPS").describe("HTTP or HTTPS URL to fetch"),
2088
- format: z7.enum(["html", "markdown"]).optional().describe("Output format. Defaults to markdown for compact LLM consumption; use html for raw source.")
2223
+ format: z8.enum(["html", "markdown"]).optional().describe("Output format. Defaults to markdown for compact LLM consumption; use html for raw source.")
2089
2224
  }),
2090
2225
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
2091
2226
  }, withErrorHandling("scrape", async (args) => {
@@ -2251,7 +2386,7 @@ var init_errors = () => {};
2251
2386
 
2252
2387
  // src/tools/support.ts
2253
2388
  import { KadoaHttpException as KadoaHttpException2 } from "@kadoa/node-sdk";
2254
- import { z as z8 } from "zod";
2389
+ import { z as z9 } from "zod";
2255
2390
  function supportTicketSkipMessage(reason) {
2256
2391
  return (reason && SUPPORT_TICKET_SKIP_MESSAGES[reason]) ?? "No support ticket was filed because this workflow isn't eligible for a support ticket right now.";
2257
2392
  }
@@ -2275,11 +2410,11 @@ function registerSupportTools(server, ctx, helpers) {
2275
2410
  title: "Create Support Ticket",
2276
2411
  description: "File a support ticket for one workflow so the Kadoa team investigates it. " + "Only file after the user has agreed. If the workflow already has an open ticket, that ticket is " + "returned instead of a duplicate. Filing never pauses the workflow.",
2277
2412
  inputSchema: strictSchema({
2278
- workflowId: z8.string().min(1).describe("The workflow the problem is about"),
2279
- title: z8.string().describe("One-line summary of the problem"),
2280
- description: z8.string().describe("What is wrong, what was observed, and what was already tried"),
2281
- category: z8.enum(["bug", "feature", "workflow_issue", "template_issue", "integration", "billing", "data_diff"]).optional().describe("Support category; defaults to a workflow issue"),
2282
- jobId: z8.string().optional().describe("The failing run's id (from list_workflow_runs). Required when filing from the Kadoa Assistant.")
2413
+ workflowId: z9.string().min(1).describe("The workflow the problem is about"),
2414
+ title: z9.string().describe("One-line summary of the problem"),
2415
+ description: z9.string().describe("What is wrong, what was observed, and what was already tried"),
2416
+ category: z9.enum(["bug", "feature", "workflow_issue", "template_issue", "integration", "billing", "data_diff"]).optional().describe("Support category; defaults to a workflow issue"),
2417
+ jobId: z9.string().optional().describe("The failing run's id (from list_workflow_runs). Required when filing from the Kadoa Assistant.")
2283
2418
  }),
2284
2419
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
2285
2420
  }, withErrorHandling("create_support_ticket", async (args) => {
@@ -2343,7 +2478,7 @@ var init_support = __esm(() => {
2343
2478
  });
2344
2479
 
2345
2480
  // src/tools/team.ts
2346
- import { z as z9 } from "zod";
2481
+ import { z as z10 } from "zod";
2347
2482
  function registerWhoamiTool(server, ctx, helpers) {
2348
2483
  const { withErrorHandling } = helpers;
2349
2484
  server.registerTool("whoami", {
@@ -2413,7 +2548,7 @@ function registerTeamTools(server, ctx, helpers) {
2413
2548
  title: "Switch Team",
2414
2549
  description: "Switch the active team by name or ID. After switching, all subsequent API calls will use the new team context.",
2415
2550
  inputSchema: strictSchema({
2416
- teamIdentifier: z9.string().min(1).describe("Team name or team ID to switch to")
2551
+ teamIdentifier: z10.string().min(1).describe("Team name or team ID to switch to")
2417
2552
  }),
2418
2553
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
2419
2554
  }, withErrorHandling("team_switch", async () => errorResult("team_switch is not available for this credential. Reconnect and choose the team at sign-in.")));
@@ -2424,7 +2559,7 @@ var init_team = __esm(() => {
2424
2559
  });
2425
2560
 
2426
2561
  // src/tools/templates.ts
2427
- import { z as z10 } from "zod";
2562
+ import { z as z11 } from "zod";
2428
2563
  function normalizeTemplateSchemaField(val) {
2429
2564
  if (val === null || typeof val !== "object" || Array.isArray(val))
2430
2565
  return val;
@@ -2446,7 +2581,7 @@ function registerTemplateTools(server, ctx, helpers) {
2446
2581
  const { withErrorHandling } = helpers;
2447
2582
  server.registerTool("list_templates", {
2448
2583
  title: "List Templates",
2449
- description: "List all templates in the current team. Templates define reusable configurations (prompt, schema, validation rules, notifications, and frequency). Use this to find a matching template before creating a workflow. To instantiate a template, call create_workflow with its templateId, source URLs, optional templateVersion, and optional prompt containing only workflow-specific additional instructions - never copy the template prompt, entity, or schema into the call.",
2584
+ description: "List all templates in the current team. Templates define reusable configurations (prompt, schema, validation rules, notifications, and frequency). Use this to find a matching template before creating a workflow. To instantiate a template, call create_workflow for one-time or scheduled extraction, or create_realtime_monitor for realtime monitoring, with its templateId, source URLs, optional templateVersion, and optional prompt containing only workflow-specific additional instructions - never copy the template prompt, entity, or schema into the call.",
2450
2585
  inputSchema: strictSchema({}),
2451
2586
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
2452
2587
  }, withErrorHandling("list_templates", async () => {
@@ -2455,29 +2590,29 @@ function registerTemplateTools(server, ctx, helpers) {
2455
2590
  templates: templates.map(withTemplateDashboardUrl),
2456
2591
  count: templates.length,
2457
2592
  pagination: completeList(templates.length),
2458
- instructions: "To instantiate a listed template, call create_workflow with the matching templateId, source URLs, optional templateVersion, and optional prompt containing only workflow-specific additional instructions. Do not copy the template prompt, entity, or schema into the call."
2593
+ instructions: "To instantiate a listed template, call create_workflow for one-time or scheduled extraction, or create_realtime_monitor for realtime monitoring, with the matching templateId, source URLs, optional templateVersion, and optional prompt containing only workflow-specific additional instructions. Do not copy the template prompt, entity, or schema into the call."
2459
2594
  });
2460
2595
  }));
2461
2596
  server.registerTool("get_template", {
2462
2597
  title: "Get Template",
2463
- description: "Get a template by ID, including all published versions and their schemas. After inspecting it, instantiate the template with create_workflow using templateId, source URLs, optional templateVersion, and optional prompt containing only workflow-specific additional instructions. Never copy the returned prompt, entity, or schema into the call - those shared values are inherited when templateId is passed.",
2598
+ description: "Get a template by ID, including all published versions and their schemas. After inspecting it, instantiate the template with create_workflow for one-time or scheduled extraction, or create_realtime_monitor for realtime monitoring, using templateId, source URLs, optional templateVersion, and optional prompt containing only workflow-specific additional instructions. Never copy the returned prompt, entity, or schema into the call - those shared values are inherited when templateId is passed.",
2464
2599
  inputSchema: strictSchema({
2465
- templateId: z10.string().min(1).describe("The template ID")
2600
+ templateId: z11.string().min(1).describe("The template ID")
2466
2601
  }),
2467
2602
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
2468
2603
  }, withErrorHandling("get_template", async (args) => {
2469
2604
  const template = await ctx.client.template.get(args.templateId);
2470
2605
  return jsonResult({
2471
2606
  template: withTemplateDashboardUrl(template),
2472
- instructions: `To instantiate this template, call create_workflow with templateId "${args.templateId}", source URLs, optional templateVersion, and optional prompt containing only workflow-specific additional instructions. Do not copy this template's prompt, entity, or schema into the call.`
2607
+ instructions: `To instantiate this template, call create_workflow for one-time or scheduled extraction, or create_realtime_monitor for realtime monitoring, with templateId "${args.templateId}", source URLs, optional templateVersion, and optional prompt containing only workflow-specific additional instructions. Do not copy this template's prompt, entity, or schema into the call.`
2473
2608
  });
2474
2609
  }));
2475
2610
  server.registerTool("create_template", {
2476
2611
  title: "Create Template",
2477
2612
  description: "Create a new template. After creation, use create_template_version to publish a version with prompt, schema, and notifications.",
2478
2613
  inputSchema: strictSchema({
2479
- name: z10.string().describe("Template name (1-255 chars)"),
2480
- description: z10.string().optional().describe("Template description (max 2000 chars)")
2614
+ name: z11.string().describe("Template name (1-255 chars)"),
2615
+ description: z11.string().optional().describe("Template description (max 2000 chars)")
2481
2616
  }),
2482
2617
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
2483
2618
  }, withErrorHandling("create_template", async (args) => {
@@ -2497,9 +2632,9 @@ function registerTemplateTools(server, ctx, helpers) {
2497
2632
 
2498
2633
  ` + "This tool CANNOT change what linked workflows inherit - prompt, schema, validation rules, notifications and frequency all live in template *versions*, which are immutable snapshots. " + "To change any of those, publish a new version with `create_template_version` (the version IS the edit), then roll it out to linked workflows with `apply_template_version`. " + "There is no in-place edit of a published version, and never report a prompt or schema change as impossible - versioning is the supported path.",
2499
2634
  inputSchema: strictSchema({
2500
- templateId: z10.string().min(1).describe("The template ID to update"),
2501
- name: z10.preprocess(coerceNull(), z10.string().optional()).optional().describe("New template name"),
2502
- description: z10.preprocess(coerceNull(), z10.string().optional()).optional().describe("New template description")
2635
+ templateId: z11.string().min(1).describe("The template ID to update"),
2636
+ name: z11.preprocess(coerceNull(), z11.string().optional()).optional().describe("New template name"),
2637
+ description: z11.preprocess(coerceNull(), z11.string().optional()).optional().describe("New template description")
2503
2638
  }),
2504
2639
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
2505
2640
  }, withErrorHandling("update_template", async (args) => {
@@ -2515,8 +2650,8 @@ function registerTemplateTools(server, ctx, helpers) {
2515
2650
  title: "Delete Template",
2516
2651
  description: "Delete (archive) a template. " + "You MUST first call this tool without confirmed=true to preview what will be deleted, " + "then show the user the template name and ask for confirmation, " + "then call again with confirmed=true only after the user explicitly agrees.",
2517
2652
  inputSchema: strictSchema({
2518
- templateId: z10.string().min(1).describe("The template ID to delete"),
2519
- confirmed: z10.preprocess(coerceBoolean(), z10.boolean()).optional().describe("Set to true only after the user has explicitly confirmed deletion. Omit or set to false for the initial preview call.")
2653
+ templateId: z11.string().min(1).describe("The template ID to delete"),
2654
+ confirmed: z11.preprocess(coerceBoolean(), z11.boolean()).optional().describe("Set to true only after the user has explicitly confirmed deletion. Omit or set to false for the initial preview call.")
2520
2655
  }),
2521
2656
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
2522
2657
  }, withErrorHandling("delete_template", async (args) => {
@@ -2540,15 +2675,15 @@ function registerTemplateTools(server, ctx, helpers) {
2540
2675
  });
2541
2676
  }));
2542
2677
  const TemplateSchemaFieldShape = {
2543
- name: z10.string().describe("Field name"),
2544
- description: z10.string().optional().describe("Field description"),
2545
- fieldType: z10.enum(["SCHEMA", "CLASSIFICATION"]).optional().describe("Field role: SCHEMA (default) for extracted data, CLASSIFICATION for a field with predefined categories"),
2546
- example: z10.string().optional().describe("Example value"),
2678
+ name: z11.string().describe("Field name"),
2679
+ description: z11.string().optional().describe("Field description"),
2680
+ fieldType: z11.enum(["SCHEMA", "CLASSIFICATION"]).optional().describe("Field role: SCHEMA (default) for extracted data, CLASSIFICATION for a field with predefined categories"),
2681
+ example: z11.string().optional().describe("Example value"),
2547
2682
  dataType: SchemaFieldDataTypeSchema.optional().describe("Data type for SCHEMA fields"),
2548
- isKey: z10.preprocess(coerceBoolean(), z10.boolean()).optional().describe("Whether the field is a key field"),
2549
- categories: z10.preprocess(coerceArray(), z10.array(z10.object({
2550
- title: z10.string().describe("Short title/label of the category"),
2551
- definition: z10.string().describe("Full definition/description of the category")
2683
+ isKey: z11.preprocess(coerceBoolean(), z11.boolean()).optional().describe("Whether the field is a key field"),
2684
+ categories: z11.preprocess(coerceArray(), z11.array(z11.object({
2685
+ title: z11.string().describe("Short title/label of the category"),
2686
+ definition: z11.string().describe("Full definition/description of the category")
2552
2687
  }))).optional().describe("Predefined categories for a CLASSIFICATION field ({title, definition}[]). Required for fieldType=CLASSIFICATION; omitted otherwise.")
2553
2688
  };
2554
2689
  async function latestTemplateVersionRules(templateId) {
@@ -2573,25 +2708,25 @@ function registerTemplateTools(server, ctx, helpers) {
2573
2708
 
2574
2709
  ` + "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.",
2575
2710
  inputSchema: strictSchema({
2576
- templateId: z10.string().min(1).describe("The template ID to publish a version for"),
2577
- prompt: z10.string().optional().describe("User prompt to copy into workflow config"),
2578
- schemaId: z10.string().optional().describe("Existing schema ID to reference (mutually exclusive with schemaFields)"),
2579
- 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)"),
2580
- schemaEntity: z10.string().optional().describe("Entity name for the inline schema"),
2581
- 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."),
2582
- notifications: z10.preprocess(coerceArray(), z10.array(z10.object({
2583
- eventType: z10.string().describe("Notification event type"),
2584
- eventConfiguration: z10.preprocess(coerceJson(), z10.record(z10.string(), z10.unknown())).optional(),
2585
- enabled: z10.preprocess(coerceBoolean(), z10.boolean()).optional(),
2586
- channelIds: z10.preprocess(coerceArray(), z10.array(z10.string().min(1))).optional(),
2587
- channels: z10.preprocess(coerceArray(), z10.array(z10.object({ channelId: z10.string() }))).optional()
2711
+ templateId: z11.string().min(1).describe("The template ID to publish a version for"),
2712
+ prompt: z11.string().optional().describe("User prompt to copy into workflow config"),
2713
+ schemaId: z11.string().optional().describe("Existing schema ID to reference (mutually exclusive with schemaFields)"),
2714
+ schemaFields: z11.preprocess(coerceArray(), z11.array(z11.preprocess(normalizeTemplateSchemaField, z11.object(TemplateSchemaFieldShape).strict()))).optional().describe("Inline schema fields to create a new schema (mutually exclusive with schemaId)"),
2715
+ schemaEntity: z11.string().optional().describe("Entity name for the inline schema"),
2716
+ schemaValidationRules: z11.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."),
2717
+ notifications: z11.preprocess(coerceArray(), z11.array(z11.object({
2718
+ eventType: z11.string().describe("Notification event type"),
2719
+ eventConfiguration: z11.preprocess(coerceJson(), z11.record(z11.string(), z11.unknown())).optional(),
2720
+ enabled: z11.preprocess(coerceBoolean(), z11.boolean()).optional(),
2721
+ channelIds: z11.preprocess(coerceArray(), z11.array(z11.string().min(1))).optional(),
2722
+ channels: z11.preprocess(coerceArray(), z11.array(z11.object({ channelId: z11.string() }))).optional()
2588
2723
  }))).optional().describe("Notification configuration for this version"),
2589
- frequency: z10.preprocess(coerceJson(), z10.object({
2590
- interval: z10.enum([...UpdateIntervalSchema.options, "REAL_TIME"]).optional().describe("Scheduling interval the template controls for linked workflows. Use CUSTOM with 'schedules' for cron."),
2591
- schedules: z10.preprocess(coerceArray(), z10.array(z10.object({
2592
- type: z10.enum(["repeat", "cron"]).describe("'repeat' (interval token) or 'cron' (cron expressions)"),
2593
- interval: z10.string().optional().describe("Repeat token (e.g. '1h', '1d') when type='repeat'"),
2594
- expressions: z10.preprocess(coerceArray(true), z10.array(z10.string())).optional().describe("Cron expressions when type='cron' (e.g. '0 8 * * 2')")
2724
+ frequency: z11.preprocess(coerceJson(), z11.object({
2725
+ interval: z11.enum([...UpdateIntervalSchema.options, "REAL_TIME"]).optional().describe("Scheduling interval the template controls for linked workflows. Use CUSTOM with 'schedules' for cron."),
2726
+ schedules: z11.preprocess(coerceArray(), z11.array(z11.object({
2727
+ type: z11.enum(["repeat", "cron"]).describe("'repeat' (interval token) or 'cron' (cron expressions)"),
2728
+ interval: z11.string().optional().describe("Repeat token (e.g. '1h', '1d') when type='repeat'"),
2729
+ expressions: z11.preprocess(coerceArray(true), z11.array(z11.string())).optional().describe("Cron expressions when type='cron' (e.g. '0 8 * * 2')")
2595
2730
  }))).optional().describe("Schedule specs. Required when interval='CUSTOM'.")
2596
2731
  })).optional().describe("Template-controlled scheduling for linked workflows ({interval, schedules}). Linked workflows inherit and cannot override this when set.")
2597
2732
  }),
@@ -2621,11 +2756,11 @@ function registerTemplateTools(server, ctx, helpers) {
2621
2756
  title: "Save Workflow as Template",
2622
2757
  description: "Create a template from an existing workflow's configuration (prompt, schema, notifications). " + "Provide 'name' to create a new template, or 'templateId' to add a new version to an existing template. " + "Use 'includeParts' to save only specific parts of the configuration.",
2623
2758
  inputSchema: strictSchema({
2624
- workflowId: z10.string().min(1).describe("Source workflow ID to extract config from"),
2625
- name: z10.string().optional().describe("Name for the new template (required if templateId is not set)"),
2626
- description: z10.string().optional().describe("Description for the new template"),
2627
- templateId: z10.string().optional().describe("Existing template ID to add a new version to (mutually exclusive with name)"),
2628
- includeParts: z10.preprocess(coerceArray(), z10.array(z10.enum(["prompt", "schema", "schemaValidationRules", "notifications", "frequency"])).optional()).describe("Which parts of the workflow config to include: prompt, schema, schemaValidationRules, notifications, frequency. Omit to include all. At least 1 required if provided.")
2759
+ workflowId: z11.string().min(1).describe("Source workflow ID to extract config from"),
2760
+ name: z11.string().optional().describe("Name for the new template (required if templateId is not set)"),
2761
+ description: z11.string().optional().describe("Description for the new template"),
2762
+ templateId: z11.string().optional().describe("Existing template ID to add a new version to (mutually exclusive with name)"),
2763
+ includeParts: z11.preprocess(coerceArray(), z11.array(z11.enum(["prompt", "schema", "schemaValidationRules", "notifications", "frequency"])).optional()).describe("Which parts of the workflow config to include: prompt, schema, schemaValidationRules, notifications, frequency. Omit to include all. At least 1 required if provided.")
2629
2764
  }),
2630
2765
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
2631
2766
  }, withErrorHandling("save_workflow_as_template", async (args) => {
@@ -2646,7 +2781,7 @@ function registerTemplateTools(server, ctx, helpers) {
2646
2781
  title: "List Template Schemas",
2647
2782
  description: "List schemas associated with a template.",
2648
2783
  inputSchema: strictSchema({
2649
- templateId: z10.string().min(1).describe("The template ID")
2784
+ templateId: z11.string().min(1).describe("The template ID")
2650
2785
  }),
2651
2786
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
2652
2787
  }, withErrorHandling("list_template_schemas", async (args) => {
@@ -2657,9 +2792,9 @@ function registerTemplateTools(server, ctx, helpers) {
2657
2792
  title: "Link Template to Workflows",
2658
2793
  description: "Link one or more EXISTING workflows to a template in a single call. " + "This is the bulk equivalent of creating a workflow with `templateId`: linked workflows adopt the template's configuration (prompt, schema, notifications) and stay in sync with it. " + "The template ENFORCES its schema on linked workflows - their extracted output conforms to the template's declared field names, so this is the way to make many workflows produce a consistent, canonical schema. " + "Use `list_templates`/`get_template` to find the template, and `list_workflows` to find the workflow IDs. " + "Set `force: true` to relink workflows already linked to a different template.",
2659
2794
  inputSchema: strictSchema({
2660
- templateId: z10.string().min(1).describe("The template ID to link workflows to"),
2661
- workflowIds: z10.preprocess(coerceArray(true), z10.array(z10.string().min(1)).min(1)).describe("Workflow IDs to link to the template (array of strings). Also accepts a single ID string."),
2662
- force: z10.preprocess(coerceBoolean(), z10.boolean()).optional().describe("If true, relink workflows that are already linked to another template. Defaults to false.")
2795
+ templateId: z11.string().min(1).describe("The template ID to link workflows to"),
2796
+ workflowIds: z11.preprocess(coerceArray(true), z11.array(z11.string().min(1)).min(1)).describe("Workflow IDs to link to the template (array of strings). Also accepts a single ID string."),
2797
+ force: z11.preprocess(coerceBoolean(), z11.boolean()).optional().describe("If true, relink workflows that are already linked to another template. Defaults to false.")
2663
2798
  }),
2664
2799
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
2665
2800
  }, withErrorHandling("link_template_to_workflows", async (args) => {
@@ -2686,9 +2821,9 @@ function registerTemplateTools(server, ctx, helpers) {
2686
2821
  title: "Apply Template Version",
2687
2822
  description: "Push a template version onto its linked workflows. Updates the given workflows to the template's `targetVersion`, applying the template-controlled parts (prompt, schema, validation rules, notifications, frequency). " + "Use this after publishing a new template version to bring linked workflows up to date (see list_template_workflows for which workflows are outdated). " + "Returns how many workflows were updated and which parts the template controls.",
2688
2823
  inputSchema: strictSchema({
2689
- templateId: z10.string().min(1).describe("The template ID whose version to apply"),
2690
- targetVersion: z10.preprocess(coerceNumber(), z10.number()).describe("The template version number to apply to the workflows"),
2691
- workflowIds: z10.preprocess(coerceArray(true), z10.array(z10.string().min(1)).min(1)).describe("Workflow IDs to update to the target version (array of strings). Also accepts a single ID string.")
2824
+ templateId: z11.string().min(1).describe("The template ID whose version to apply"),
2825
+ targetVersion: z11.preprocess(coerceNumber(), z11.number()).describe("The template version number to apply to the workflows"),
2826
+ workflowIds: z11.preprocess(coerceArray(true), z11.array(z11.string().min(1)).min(1)).describe("Workflow IDs to update to the target version (array of strings). Also accepts a single ID string.")
2692
2827
  }),
2693
2828
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
2694
2829
  }, withErrorHandling("apply_template_version", async (args) => {
@@ -2708,7 +2843,7 @@ function registerTemplateTools(server, ctx, helpers) {
2708
2843
  title: "List Template Workflows",
2709
2844
  description: "List the workflows linked to a template, with each workflow's pinned template version and an `isOutdated` flag (true when a newer template version exists). " + "Use this to verify which workflows are bound to a template and to find ones that need apply_template_version.",
2710
2845
  inputSchema: strictSchema({
2711
- templateId: z10.string().min(1).describe("The template ID")
2846
+ templateId: z11.string().min(1).describe("The template ID")
2712
2847
  }),
2713
2848
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
2714
2849
  }, withErrorHandling("list_template_workflows", async (args) => {
@@ -2719,8 +2854,8 @@ function registerTemplateTools(server, ctx, helpers) {
2719
2854
  title: "Unlink Template from Workflows",
2720
2855
  description: "Unlink one or more workflows from a template in a single call. " + "Unlinked workflows keep their current configuration but stop tracking the template (future template version changes no longer apply to them).",
2721
2856
  inputSchema: strictSchema({
2722
- templateId: z10.string().min(1).describe("The template ID to unlink workflows from"),
2723
- workflowIds: z10.preprocess(coerceArray(true), z10.array(z10.string().min(1)).min(1)).describe("Workflow IDs to unlink from the template (array of strings). Also accepts a single ID string.")
2857
+ templateId: z11.string().min(1).describe("The template ID to unlink workflows from"),
2858
+ workflowIds: z11.preprocess(coerceArray(true), z11.array(z11.string().min(1)).min(1)).describe("Workflow IDs to unlink from the template (array of strings). Also accepts a single ID string.")
2724
2859
  }),
2725
2860
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
2726
2861
  }, withErrorHandling("unlink_template_from_workflows", async (args) => {
@@ -2743,16 +2878,16 @@ var init_templates = __esm(() => {
2743
2878
  });
2744
2879
 
2745
2880
  // src/tools/variables.ts
2746
- import { z as z11 } from "zod";
2881
+ import { z as z12 } from "zod";
2747
2882
  function registerVariableTools(server, ctx, helpers) {
2748
2883
  const { withErrorHandling } = helpers;
2749
2884
  server.registerTool("create_variable", {
2750
2885
  title: "Create Variable",
2751
2886
  description: "Create a new variable. Variables are key-value pairs that can be referenced in workflow prompts using @variableKey syntax. Keys must be unique within the team scope.",
2752
2887
  inputSchema: strictSchema({
2753
- key: z11.string().describe("Variable key (must be unique within scope). Referenced in prompts as @key."),
2754
- value: z11.string().describe("Variable value"),
2755
- dataType: z11.enum(["STRING", "SECRET", "OBJECT", "ARRAY"]).optional().describe("Data type of the value (defaults to STRING)")
2888
+ key: z12.string().describe("Variable key (must be unique within scope). Referenced in prompts as @key."),
2889
+ value: z12.string().describe("Variable value"),
2890
+ dataType: z12.enum(["STRING", "SECRET", "OBJECT", "ARRAY"]).optional().describe("Data type of the value (defaults to STRING)")
2756
2891
  }),
2757
2892
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
2758
2893
  }, withErrorHandling("create_variable", async (args) => {
@@ -2784,7 +2919,7 @@ function registerVariableTools(server, ctx, helpers) {
2784
2919
  title: "Get Variable",
2785
2920
  description: "Get a specific variable by its ID.",
2786
2921
  inputSchema: strictSchema({
2787
- variableId: z11.string().min(1).describe("The variable ID")
2922
+ variableId: z12.string().min(1).describe("The variable ID")
2788
2923
  }),
2789
2924
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
2790
2925
  }, withErrorHandling("get_variable", async (args) => {
@@ -2795,10 +2930,10 @@ function registerVariableTools(server, ctx, helpers) {
2795
2930
  title: "Update Variable",
2796
2931
  description: "Update an existing variable. At least one field must be provided. If the variable key is referenced in workflow prompts (@variableKey), those workflows will automatically use the new value on next run.",
2797
2932
  inputSchema: strictSchema({
2798
- variableId: z11.string().min(1).describe("The variable ID to update"),
2799
- key: z11.string().optional().describe("New variable key"),
2800
- value: z11.string().optional().describe("New variable value"),
2801
- dataType: z11.enum(["STRING", "SECRET", "OBJECT", "ARRAY"]).optional().describe("New data type")
2933
+ variableId: z12.string().min(1).describe("The variable ID to update"),
2934
+ key: z12.string().optional().describe("New variable key"),
2935
+ value: z12.string().optional().describe("New variable value"),
2936
+ dataType: z12.enum(["STRING", "SECRET", "OBJECT", "ARRAY"]).optional().describe("New data type")
2802
2937
  }),
2803
2938
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
2804
2939
  }, withErrorHandling("update_variable", async (args) => {
@@ -2814,8 +2949,8 @@ function registerVariableTools(server, ctx, helpers) {
2814
2949
  title: "Delete Variable",
2815
2950
  description: "Delete a variable permanently. This is irreversible. " + "You MUST first call this tool without confirmed=true to preview what will be deleted, " + "then show the user the variable key and ask for confirmation, " + "then call again with confirmed=true only after the user explicitly agrees.",
2816
2951
  inputSchema: strictSchema({
2817
- variableId: z11.string().min(1).describe("The variable ID to delete"),
2818
- confirmed: z11.preprocess(coerceBoolean(), z11.boolean()).optional().describe("Set to true only after the user has explicitly confirmed deletion. Omit or set to false for the initial preview call.")
2952
+ variableId: z12.string().min(1).describe("The variable ID to delete"),
2953
+ confirmed: z12.preprocess(coerceBoolean(), z12.boolean()).optional().describe("Set to true only after the user has explicitly confirmed deletion. Omit or set to false for the initial preview call.")
2819
2954
  }),
2820
2955
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
2821
2956
  }, withErrorHandling("delete_variable", async (args) => {
@@ -2946,7 +3081,7 @@ var init_workflow_status = __esm(() => {
2946
3081
 
2947
3082
  // src/tools/workflows.ts
2948
3083
  import { WorkflowStatusFilter } from "@kadoa/node-sdk";
2949
- import { z as z12 } from "zod";
3084
+ import { z as z13 } from "zod";
2950
3085
  function mapRunStatus(state) {
2951
3086
  switch (state) {
2952
3087
  case "FINISHED":
@@ -3157,13 +3292,12 @@ function registerWorkflowCoreTools(server, ctx, helpers, defaultProductType) {
3157
3292
  prompt: extractionPromptInput.describe('Natural language description of what to extract (e.g., "Extract product prices and names"). Required unless templateId is provided. With templateId, use it only for workflow-specific instructions to add to the template prompt.'),
3158
3293
  ...extractionInputShape,
3159
3294
  ...urlInputShape,
3160
- templateId: z12.string().optional().describe("Instantiate this workflow from a published template. Pass the templateId, source urls, optional templateVersion, and optional prompt with workflow-specific additional instructions. Do not copy the template's shared prompt, entity, or schema into this call - they are inherited from the published template version, and the workflow's output conforms to its declared schema. Discover templates via list_templates and get_template."),
3161
- templateVersion: z12.preprocess(coerceNumber(), z12.number()).optional().describe("Specific published template version (integer) to instantiate. Defaults to the latest published version when templateId is set."),
3162
- description: z12.string().max(500).optional().describe("Description of what this workflow does (max 500 characters)"),
3163
- tags: z12.preprocess(coerceArray(true), z12.array(z12.string())).optional().describe("Tags for organizing workflows"),
3164
- limit: z12.preprocess(coerceNumber(), z12.number()).optional().describe("Maximum number of records to extract per run. Useful for limiting scope or cost control."),
3295
+ ...templateInputShape,
3296
+ description: z13.string().max(500).optional().describe("Description of what this workflow does (max 500 characters)"),
3297
+ tags: z13.preprocess(coerceArray(true), z13.array(z13.string())).optional().describe("Tags for organizing workflows"),
3298
+ limit: z13.preprocess(coerceNumber(), z13.number()).optional().describe("Maximum number of records to extract per run. Useful for limiting scope or cost control."),
3165
3299
  updateInterval: UpdateIntervalSchema.optional().describe("How often the workflow runs. Defaults to ONLY_ONCE. Use CUSTOM with the 'schedules' field for cron-based scheduling."),
3166
- schedules: z12.preprocess(coerceArray(true), z12.array(z12.string())).optional().describe("Cron expressions for CUSTOM updateInterval (e.g. '0 8 * * 2' for Tuesdays at 8am UTC). Requires updateInterval='CUSTOM'."),
3300
+ schedules: z13.preprocess(coerceArray(true), z13.array(z13.string())).optional().describe("Cron expressions for CUSTOM updateInterval (e.g. '0 8 * * 2' for Tuesdays at 8am UTC). Requires updateInterval='CUSTOM'."),
3167
3301
  ...notificationsInputShape
3168
3302
  }),
3169
3303
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
@@ -3307,13 +3441,14 @@ function registerWorkflowCoreTools(server, ctx, helpers, defaultProductType) {
3307
3441
  title: "Create Realtime Monitor",
3308
3442
  description: "Create a realtime monitoring workflow that continuously watches a page for data changes and sends alerts. " + (defaultProductType === "realtime" ? "This team's default product is realtime monitoring: use this tool whenever the user asks to monitor, track, or watch a page, or to get or extract data from it, unless they explicitly ask for a one-time run or a schedule. A request that names no frequency counts as realtime. " : "Use this when the user wants to monitor, track, or watch a page in realtime. ") + "At least one notification channel (email, webhook, slack, or websocket) is required; notification settings are persisted before Assistant creation is dispatched. " + `Realtime monitors CANNOT be converted to/from one-time or scheduled extraction workflows after creation.
3309
3443
 
3310
- ` + "If entity and schema are provided, they guide what the Assistant should monitor; otherwise the Assistant determines what to watch. " + "Creation is asynchronous and the response includes workflow, session, thread, and job IDs plus a dashboard URL. " + "Use generic workflow Assistant tools (get_workflow_assistant, answer_workflow_assistant_question, interrupt_workflow_assistant, resume_workflow_assistant, stop_workflow_assistant) for follow-up status, questions, and controls. " + "Do NOT poll or sleep-wait for completion.",
3444
+ ` + "If entity and schema are provided, they guide what the Assistant should monitor; otherwise the Assistant determines what to watch. " + "To reuse a template, pass templateId, source urls, optional templateVersion, and optional prompt containing only workflow-specific additional instructions. Entity and schema are inherited from the template; do not copy its shared prompt or schema. Templates that control a batch frequency cannot be used for realtime monitors. " + "Creation is asynchronous and the response includes workflow, session, thread, and job IDs plus a dashboard URL. " + "Use generic workflow Assistant tools (get_workflow_assistant, answer_workflow_assistant_question, interrupt_workflow_assistant, resume_workflow_assistant, stop_workflow_assistant) for follow-up status, questions, and controls. " + "Do NOT poll or sleep-wait for completion.",
3311
3445
  inputSchema: strictSchema({
3312
- prompt: extractionPromptInput.describe("Optional natural language instructions describing what to monitor and which changes matter. If omitted, the Assistant determines what to watch."),
3446
+ prompt: extractionPromptInput.describe("Optional natural language instructions describing what to monitor and which changes matter. With templateId, use it only for workflow-specific instructions to add to the template prompt. Otherwise, if omitted, the Assistant determines what to watch."),
3313
3447
  ...extractionInputShape,
3314
3448
  ...urlInputShape,
3315
- description: z12.string().max(500).optional().describe("Description of what this monitor watches (max 500 characters)"),
3316
- tags: z12.preprocess(coerceArray(true), z12.array(z12.string())).optional().describe("Tags for organizing monitors"),
3449
+ ...templateInputShape,
3450
+ description: z13.string().max(500).optional().describe("Description of what this monitor watches (max 500 characters)"),
3451
+ tags: z13.preprocess(coerceArray(true), z13.array(z13.string())).optional().describe("Tags for organizing monitors"),
3317
3452
  ...requiredNotificationsInputShape
3318
3453
  }),
3319
3454
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
@@ -3327,6 +3462,21 @@ function registerWorkflowCoreTools(server, ctx, helpers, defaultProductType) {
3327
3462
  if (!hasNotifications) {
3328
3463
  return errorResult("Real-time monitors require at least one notification channel so you get alerted when data changes. " + "Add a notifications object with one or more channels: email, webhook, slack, or websocket.");
3329
3464
  }
3465
+ let templateVersion;
3466
+ if (args.templateId) {
3467
+ if (args.entity || args.schema) {
3468
+ return errorResult("When 'templateId' is set, 'entity' and 'schema' must NOT be supplied - they are inherited from the template version. Use 'prompt' only for workflow-specific additional instructions.");
3469
+ }
3470
+ const template = await ctx.client.template.get(args.templateId);
3471
+ templateVersion = args.templateVersion ?? Math.max(...template.versions.map((version) => version.version));
3472
+ const version = template.versions.find((version) => version.version === templateVersion);
3473
+ if (!version) {
3474
+ return errorResult("Template has no published version matching this request. Use get_template to choose a published version.");
3475
+ }
3476
+ if (version.frequency?.interval || version.frequency?.schedules?.length) {
3477
+ return errorResult("This template controls a batch frequency and cannot create a realtime monitor. Use create_workflow for its configured frequency or choose a template without frequency control.");
3478
+ }
3479
+ }
3330
3480
  const channels = await ctx.client.notification.setup.setupChannels({
3331
3481
  channels: buildNotificationChannels(n)
3332
3482
  });
@@ -3336,11 +3486,26 @@ function registerWorkflowCoreTools(server, ctx, helpers, defaultProductType) {
3336
3486
  }
3337
3487
  let result;
3338
3488
  try {
3339
- result = await ctx.client.assistant.createRealtimeWorkflow({
3340
- instructions: buildRealtimeAssistantInstructions(args, urls),
3341
- notificationChannelIds,
3342
- ...args.tags?.length ? { tags: args.tags } : {}
3343
- });
3489
+ const instructions = buildRealtimeAssistantInstructions(args, urls);
3490
+ if (args.templateId) {
3491
+ const response = await ctx.client.apis.agent.v5AgentPrompt({
3492
+ agentPromptRequest: {
3493
+ prompt: instructions,
3494
+ productType: "realtime",
3495
+ templateId: args.templateId,
3496
+ templateVersion,
3497
+ notificationChannelIds,
3498
+ ...args.tags?.length ? { tags: args.tags } : {}
3499
+ }
3500
+ });
3501
+ result = response.data?.data;
3502
+ } else {
3503
+ result = await ctx.client.assistant.createRealtimeWorkflow({
3504
+ instructions,
3505
+ notificationChannelIds,
3506
+ ...args.tags?.length ? { tags: args.tags } : {}
3507
+ });
3508
+ }
3344
3509
  } catch (error) {
3345
3510
  console.error("[Tool Error] create_realtime_monitor:", safeErrorSummary(error));
3346
3511
  return isAmbiguousFailure(error) ? realtimeCreationFailure(shortErrorReason(error), notificationChannelIds, { outcomeUnknown: true }) : realtimeCreationFailure(classifyError(error, "create_realtime_monitor"), notificationChannelIds);
@@ -3370,13 +3535,13 @@ function registerWorkflowCoreTools(server, ctx, helpers, defaultProductType) {
3370
3535
  max: LIST_WORKFLOWS_MAX_LIMIT
3371
3536
  }),
3372
3537
  skip: offsetInput("skip", "workflows"),
3373
- state: z12.enum(["ACTIVE", "FAILED", "PAUSED", "PREVIEW"]).optional().describe("Filter by state (ACTIVE, FAILED, PAUSED, PREVIEW)"),
3374
- search: z12.string().optional().describe("Free-text filter matching workflow name, URL, or workflow ID"),
3375
- tags: z12.array(z12.string()).optional().describe("Filter to workflows having any of these tags"),
3376
- monitoring: z12.preprocess(coerceBoolean(), z12.boolean()).optional().describe("Filter by monitoring/real-time status"),
3377
- updateInterval: z12.enum(["HOURLY", "DAILY", "WEEKLY", "MONTHLY", "REAL_TIME"]).optional().describe("Filter by scheduled update frequency or realtime workflows"),
3378
- templateId: z12.string().optional().describe("Filter to workflows instantiated from this template ID"),
3379
- statusFilters: z12.array(z12.enum(Object.values(WorkflowStatusFilter))).optional().describe("Dashboard category filters. group:attention = workflows waiting on the requesting user (Assistant question, unfinished setup, or preview approval). Other groups: working, support, failed, active, complete, paused.")
3538
+ state: z13.enum(["ACTIVE", "FAILED", "PAUSED", "PREVIEW"]).optional().describe("Filter by state (ACTIVE, FAILED, PAUSED, PREVIEW)"),
3539
+ search: z13.string().optional().describe("Free-text filter matching workflow name, URL, or workflow ID"),
3540
+ tags: z13.array(z13.string()).optional().describe("Filter to workflows having any of these tags"),
3541
+ monitoring: z13.preprocess(coerceBoolean(), z13.boolean()).optional().describe("Filter by monitoring/real-time status"),
3542
+ updateInterval: z13.enum(["HOURLY", "DAILY", "WEEKLY", "MONTHLY", "REAL_TIME"]).optional().describe("Filter by scheduled update frequency or realtime workflows"),
3543
+ templateId: z13.string().optional().describe("Filter to workflows instantiated from this template ID"),
3544
+ statusFilters: z13.array(z13.enum(Object.values(WorkflowStatusFilter))).optional().describe("Dashboard category filters. group:attention = workflows waiting on the requesting user (Assistant question, unfinished setup, or preview approval). Other groups: working, support, failed, active, complete, paused.")
3380
3545
  }),
3381
3546
  outputSchema: ListWorkflowsOutput,
3382
3547
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
@@ -3447,7 +3612,7 @@ function registerWorkflowCoreTools(server, ctx, helpers, defaultProductType) {
3447
3612
  title: "Get Workflow",
3448
3613
  description: 'Get canonical workflow details, including Assistant/session linkage, extraction intent, template-controlled parts, job/run state, schedule timezone, location, monitoring, and realtime health. For Assistant-built workflows, extractionSpec - not the legacy prompt - is authoritative. badges[].label is exactly what the Kadoa dashboard displays (lifecycle, activity, and support badges, e.g. "Setup" + "Agent working"): use these labels when describing the workflow to the user. status is the one-line summary in dashboard wording (e.g. "Verifying data" = the latest run is held for quality review before delivery; see reviewReason for why).',
3449
3614
  inputSchema: strictSchema({
3450
- workflowId: z12.string().min(1).describe("The workflow ID")
3615
+ workflowId: z13.string().min(1).describe("The workflow ID")
3451
3616
  }),
3452
3617
  outputSchema: GetWorkflowOutput,
3453
3618
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
@@ -3536,9 +3701,9 @@ function registerWorkflowCoreTools(server, ctx, helpers, defaultProductType) {
3536
3701
  title: "Request Workflow Update",
3537
3702
  description: "Request an Assistant-driven change to an existing workflow while preserving its workflowId. " + "Use for extraction intent, navigation, pagination, data sourcing, generated-script changes, and repair after a site redesign. " + "Use update_workflow instead for deterministic metadata such as name, schedule, location, tags, and limits. " + "For template-controlled prompt or schema changes, create and apply a template version instead. " + "The update is asynchronous: do not poll or sleep-wait; use get_workflow_assistant later. Never delete and recreate the workflow as a workaround.",
3538
3703
  inputSchema: strictSchema({
3539
- workflowId: z12.string().min(1).describe("The existing workflow ID; it will not change"),
3540
- instructions: z12.string().min(1).describe("Natural-language instructions for the workflow Assistant"),
3541
- threadId: z12.string().optional().describe("Optional Assistant thread ID to continue")
3704
+ workflowId: z13.string().min(1).describe("The existing workflow ID; it will not change"),
3705
+ instructions: z13.string().min(1).describe("Natural-language instructions for the workflow Assistant"),
3706
+ threadId: z13.string().optional().describe("Optional Assistant thread ID to continue")
3542
3707
  }),
3543
3708
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
3544
3709
  }, withErrorHandling("request_workflow_update", async (args) => {
@@ -3568,14 +3733,14 @@ function registerWorkflowHistoryTools(server, ctx, helpers) {
3568
3733
  title: "Get Workflow History",
3569
3734
  description: "Get the configuration revision history (audit log) for a workflow. Each entry captures who changed the workflow, when, from which channel (UI/API/SDK/MCP/CLI/SYSTEM), a `changedFields` list, and `changes` with the before/after value of each changed field (values over 1 KB are summarized). Use this to answer questions like 'did this workflow's prompt or schema change, when, and from what to what?'. Pass includeSnapshots: true only when you need the full previousValue/newValue workflow snapshots (large). Note: CREATE entries have empty `changedFields`/`changes` and null snapshots (no initial-state snapshot is captured).",
3570
3735
  inputSchema: strictSchema({
3571
- workflowId: z12.string().min(1).describe("The workflow ID"),
3736
+ workflowId: z13.string().min(1).describe("The workflow ID"),
3572
3737
  page: pageInput(),
3573
3738
  limit: limitInput({
3574
3739
  noun: "Entries",
3575
3740
  defaultLimit: WORKFLOW_HISTORY_DEFAULT_LIMIT,
3576
3741
  max: WORKFLOW_HISTORY_MAX_LIMIT
3577
3742
  }),
3578
- includeSnapshots: z12.preprocess(coerceBoolean(), z12.boolean()).optional().describe("Include full previousValue/newValue workflow snapshots per entry (default false; large)")
3743
+ includeSnapshots: z13.preprocess(coerceBoolean(), z13.boolean()).optional().describe("Include full previousValue/newValue workflow snapshots per entry (default false; large)")
3579
3744
  }),
3580
3745
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
3581
3746
  }, withErrorHandling("get_workflow_history", async (args) => {
@@ -3627,8 +3792,8 @@ function registerWorkflowHistoryTools(server, ctx, helpers) {
3627
3792
  title: "List Workflow Runs",
3628
3793
  description: "List a workflow's execution run history - each run's status, start/finish time, " + "record count, and errors. Use to answer 'when did this last succeed?' " + "(status='success', limit=1) or 'recent success/failure pattern?'. Distinct from " + "get_workflow_history, which is the config audit log. `status` is normally " + "success | failed | in_progress; a run whose backend state this server does not " + "recognize reports that raw state verbatim, so treat any other value as unknown " + "rather than as a failure. `label` is the dashboard's wording for the run " + "(Success, Failed, Running, or 'Verifying data' = held for quality review before delivery); " + "prefer it when describing a run to the user.",
3629
3794
  inputSchema: strictSchema({
3630
- workflowId: z12.string().min(1).describe("The workflow ID"),
3631
- status: z12.enum(["success", "failed", "in_progress"]).optional().describe("Filter runs by outcome"),
3795
+ workflowId: z13.string().min(1).describe("The workflow ID"),
3796
+ status: z13.enum(["success", "failed", "in_progress"]).optional().describe("Filter runs by outcome"),
3632
3797
  page: pageInput(),
3633
3798
  limit: limitInput({ noun: "Runs", defaultLimit: WORKFLOW_RUNS_DEFAULT_LIMIT, max: WORKFLOW_RUNS_MAX_LIMIT })
3634
3799
  }),
@@ -3672,8 +3837,8 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3672
3837
  title: "Run Workflow",
3673
3838
  description: "Run a workflow to extract fresh data, including retrying a failed one-time or scheduled workflow. Preserve the existing workflow ID and configuration - do not delete and recreate the workflow solely to retry it. The run is asynchronous and may take several minutes. Do NOT poll or sleep-wait for completion. Return the workflow ID to the user and let them check status with get_workflow or fetch results later with fetch_data. Realtime workflows cannot be manually run.",
3674
3839
  inputSchema: strictSchema({
3675
- workflowId: z12.string().min(1).describe("The workflow ID to run"),
3676
- limit: z12.preprocess(coerceNumber(), z12.number()).optional().describe("Maximum number of records to extract (default: 1000)")
3840
+ workflowId: z13.string().min(1).describe("The workflow ID to run"),
3841
+ limit: z13.preprocess(coerceNumber(), z13.number()).optional().describe("Maximum number of records to extract (default: 1000)")
3677
3842
  }),
3678
3843
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
3679
3844
  }, withErrorHandling("run_workflow", async (args) => {
@@ -3696,7 +3861,7 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3696
3861
  title: "Fetch Workflow Data",
3697
3862
  description: "Get a PAGE of extracted data from a workflow. Use ONLY for previews, sorted/filtered slices, or explicit 'first N rows' / 'top N' queries (capped at 500 rows per call). Do NOT use this to retrieve a full dataset, 'all rows', or anything the user wants to analyze in Excel / pandas / duckdb - use export_data for those. Data is only available after the workflow run has completed (status is no longer 'Running' or 'Verifying data'). Do NOT poll or sleep-wait for completion.",
3698
3863
  inputSchema: strictSchema({
3699
- workflowId: z12.string().min(1).describe("The workflow ID"),
3864
+ workflowId: z13.string().min(1).describe("The workflow ID"),
3700
3865
  limit: limitInput({
3701
3866
  noun: "Rows",
3702
3867
  defaultLimit: FETCH_DATA_DEFAULT_LIMIT,
@@ -3705,8 +3870,8 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3705
3870
  }),
3706
3871
  page: pageInput(),
3707
3872
  filters: dataFiltersInput,
3708
- sortBy: z12.string().optional().describe("Field name to sort by"),
3709
- order: z12.enum(["asc", "desc"]).optional().describe("Sort order: asc or desc")
3873
+ sortBy: z13.string().optional().describe("Field name to sort by"),
3874
+ order: z13.enum(["asc", "desc"]).optional().describe("Sort order: asc or desc")
3710
3875
  }),
3711
3876
  outputSchema: FetchDataOutput,
3712
3877
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
@@ -3755,13 +3920,13 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3755
3920
  title: "Export Workflow Data",
3756
3921
  description: "PREFERRED tool for retrieving a workflow's FULL dataset. Materializes the data to object storage and returns a signed download URL. Use this whenever the user wants 'all rows', 'the full dataset', 'everything', an export, or anything destined for Excel / pandas / duckdb / a CSV file - even for small workflows. The URL is self-authenticating (open with `fetch(url)`, no Authorization header). Use fetch_data ONLY when the user explicitly asks for a small preview slice (e.g., 'first 10', 'top N sorted by X').",
3757
3922
  inputSchema: strictSchema({
3758
- workflowId: z12.string().min(1).describe("The workflow ID"),
3759
- format: z12.enum(["csv", "json"]).optional().describe("Export format. Default 'csv'."),
3760
- runId: z12.string().optional().describe("Specific run ID to export. Defaults to the latest completed run."),
3923
+ workflowId: z13.string().min(1).describe("The workflow ID"),
3924
+ format: z13.enum(["csv", "json"]).optional().describe("Export format. Default 'csv'."),
3925
+ runId: z13.string().optional().describe("Specific run ID to export. Defaults to the latest completed run."),
3761
3926
  filters: dataFiltersInput,
3762
- sortBy: z12.string().optional().describe("Field name to sort by"),
3763
- order: z12.enum(["asc", "desc"]).optional().describe("Sort order: asc or desc"),
3764
- rowIds: z12.preprocess(coerceList(), z12.array(z12.string().trim().min(1)).max(1000)).optional().describe("Row ids to include (max 1000)")
3927
+ sortBy: z13.string().optional().describe("Field name to sort by"),
3928
+ order: z13.enum(["asc", "desc"]).optional().describe("Sort order: asc or desc"),
3929
+ rowIds: z13.preprocess(coerceList(), z13.array(z13.string().trim().min(1)).max(1000)).optional().describe("Row ids to include (max 1000)")
3765
3930
  }),
3766
3931
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
3767
3932
  }, withErrorHandling("export_data", async (args) => {
@@ -3795,9 +3960,9 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3795
3960
  title: "List Detected Changes",
3796
3961
  description: "List detected data changes across one or more realtime monitoring workflows. " + "Returns structured diffs showing added, removed, and changed records. " + "Only works for workflows with realtime monitoring enabled.",
3797
3962
  inputSchema: strictSchema({
3798
- workflowIds: z12.preprocess(coerceArray(true), z12.array(z12.string().min(1))).optional().describe("Workflow IDs to filter by. If omitted, returns changes for all ACTIVE monitoring workflows."),
3799
- startDate: z12.string().optional().describe("Start date filter (ISO format, e.g. 2025-01-01)"),
3800
- endDate: z12.string().optional().describe("End date filter (ISO format, e.g. 2025-12-31)"),
3963
+ workflowIds: z13.preprocess(coerceArray(true), z13.array(z13.string().min(1))).optional().describe("Workflow IDs to filter by. If omitted, returns changes for all ACTIVE monitoring workflows."),
3964
+ startDate: z13.string().optional().describe("Start date filter (ISO format, e.g. 2025-01-01)"),
3965
+ endDate: z13.string().optional().describe("End date filter (ISO format, e.g. 2025-12-31)"),
3801
3966
  skip: offsetInput("skip", "changes"),
3802
3967
  limit: limitInput({ noun: "Changes", defaultLimit: LIST_CHANGES_DEFAULT_LIMIT, max: LIST_CHANGES_MAX_LIMIT })
3803
3968
  }),
@@ -3835,7 +4000,7 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3835
4000
  title: "Get Detected Change",
3836
4001
  description: "Get detailed information about a specific detected change, including the data snapshot and diff. Results over ~80 KB are cut to whole rows and marked truncated.",
3837
4002
  inputSchema: strictSchema({
3838
- changeId: z12.string().min(1).describe("The change ID")
4003
+ changeId: z13.string().min(1).describe("The change ID")
3839
4004
  }),
3840
4005
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
3841
4006
  }, withErrorHandling("get_change", async (args) => {
@@ -3846,8 +4011,8 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3846
4011
  title: "Delete Workflow",
3847
4012
  description: "Delete a workflow permanently. This is irreversible. " + "You MUST first call this tool without confirmed=true to preview what will be deleted, " + "then show the user the workflow name and ask for confirmation, " + "then call again with confirmed=true only after the user explicitly agrees.",
3848
4013
  inputSchema: strictSchema({
3849
- workflowId: z12.string().min(1).describe("The workflow ID to delete"),
3850
- confirmed: z12.preprocess(coerceBoolean(), z12.boolean()).optional().describe("Set to true only after the user has explicitly confirmed deletion. Omit or set to false for the initial preview call.")
4014
+ workflowId: z13.string().min(1).describe("The workflow ID to delete"),
4015
+ confirmed: z13.preprocess(coerceBoolean(), z13.boolean()).optional().describe("Set to true only after the user has explicitly confirmed deletion. Omit or set to false for the initial preview call.")
3851
4016
  }),
3852
4017
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
3853
4018
  }, withErrorHandling("delete_workflow", async (args) => {
@@ -3877,7 +4042,7 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3877
4042
  title: "Approve Workflow",
3878
4043
  description: "Approve and activate a workflow that is in PREVIEW, PAUSED, or ERROR state. Transitions the workflow to ACTIVE so it can be run.",
3879
4044
  inputSchema: strictSchema({
3880
- workflowId: z12.string().min(1).describe("The workflow ID to approve/activate")
4045
+ workflowId: z13.string().min(1).describe("The workflow ID to approve/activate")
3881
4046
  }),
3882
4047
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
3883
4048
  }, withErrorHandling("approve_workflow", async (args) => {
@@ -3892,7 +4057,7 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3892
4057
  title: "Pause Workflow",
3893
4058
  description: "Pause an ACTIVE workflow so it stops running on its schedule. Requires the workflow to be in ACTIVE state - pausing a workflow that is currently running, already paused, or in PREVIEW will return an error. Use approve_workflow to resume a paused workflow.",
3894
4059
  inputSchema: strictSchema({
3895
- workflowId: z12.string().min(1).describe("The workflow ID to pause")
4060
+ workflowId: z13.string().min(1).describe("The workflow ID to pause")
3896
4061
  }),
3897
4062
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
3898
4063
  }, withErrorHandling("pause_workflow", async (args) => {
@@ -3911,20 +4076,20 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3911
4076
 
3912
4077
  ` + "ASSISTANT-OWNED INTENT: Use request_workflow_update - not `userPrompt` - for Assistant-built workflow changes to extraction intent, navigation, pagination, data sourcing, repair, or generated scripts. The API may reject `userPrompt` because these workflows' canonical intent is owned by the Assistant. " + "Call get_workflow first. If its template.controlledParts contains the setting, create and apply a template version instead of overriding the workflow directly. NEVER delete and recreate a workflow to work around an update limitation - that changes workflowId, breaks downstream tables/connectors, and discards history.",
3913
4078
  inputSchema: strictSchema({
3914
- workflowId: z12.string().min(1).describe("The workflow ID to update"),
3915
- name: z12.string().optional().describe("New name for the workflow"),
3916
- urls: z12.preprocess(coerceArray(true), z12.array(z12.string()).min(1)).optional().describe("New target URLs for the workflow (array of strings). Also accepts a single URL string."),
3917
- entity: z12.string().optional().describe("Entity name for extraction (e.g., 'Product', 'Job Posting')"),
3918
- schema: z12.preprocess(coerceArray(), z12.array(z12.object(SchemaFieldShape).strict())).optional().describe("New extraction schema fields. This REPLACES the whole schema - list every field you want to keep, each with its isKey flag, or the omitted ones and their flags are lost. Call get_workflow first and edit the schema it returns."),
3919
- description: z12.string().max(500).optional().describe("Workflow description (max 500 characters)"),
3920
- tags: z12.preprocess(coerceArray(true), z12.array(z12.string())).optional().describe("Tags for organizing workflows"),
3921
- userPrompt: z12.string().optional().describe("Navigation prompt for agentic-navigation mode (10-5000 characters)"),
4079
+ workflowId: z13.string().min(1).describe("The workflow ID to update"),
4080
+ name: z13.string().optional().describe("New name for the workflow"),
4081
+ urls: z13.preprocess(coerceArray(true), z13.array(z13.string()).min(1)).optional().describe("New target URLs for the workflow (array of strings). Also accepts a single URL string."),
4082
+ entity: z13.string().optional().describe("Entity name for extraction (e.g., 'Product', 'Job Posting')"),
4083
+ schema: z13.preprocess(coerceArray(), z13.array(z13.object(SchemaFieldShape).strict())).optional().describe("New extraction schema fields. This REPLACES the whole schema - list every field you want to keep, each with its isKey flag, or the omitted ones and their flags are lost. Call get_workflow first and edit the schema it returns."),
4084
+ description: z13.string().max(500).optional().describe("Workflow description (max 500 characters)"),
4085
+ tags: z13.preprocess(coerceArray(true), z13.array(z13.string())).optional().describe("Tags for organizing workflows"),
4086
+ userPrompt: z13.string().optional().describe("Navigation prompt for agentic-navigation mode (10-5000 characters)"),
3922
4087
  updateInterval: UpdateIntervalSchema.optional().describe("How often the workflow should run. CUSTOM requires schedules. Note: REAL_TIME is NOT allowed here - realtime workflows must be created via create_realtime_monitor."),
3923
- schedules: z12.preprocess(coerceArray(true), z12.array(z12.string())).optional().describe("Cron expressions for CUSTOM update interval"),
3924
- 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."),
3925
- location: z12.preprocess(coerceJson(), LocationSchema).optional().describe("Scraping location: {type:'auto'} or {type:'manual', isoCode:'US'}"),
3926
- 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."),
3927
- 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.")
4088
+ schedules: z13.preprocess(coerceArray(true), z13.array(z13.string())).optional().describe("Cron expressions for CUSTOM update interval"),
4089
+ timezone: z13.string().min(1).optional().describe("IANA timezone for cron schedules, for example America/New_York. A timezone-only update preserves existing cron expressions."),
4090
+ location: z13.preprocess(coerceJson(), LocationSchema).optional().describe("Scraping location: {type:'auto'} or {type:'manual', isoCode:'US'}"),
4091
+ monitoring: z13.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."),
4092
+ limit: z13.preprocess(coerceNumber(), z13.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.")
3928
4093
  }),
3929
4094
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
3930
4095
  }, withErrorHandling("update_workflow", async (args) => {
@@ -3997,11 +4162,15 @@ function registerTools(server, ctx, capabilities) {
3997
4162
  registerSupportTools(server, ctx, helpers);
3998
4163
  registerNotificationSettingsTools(server, ctx, helpers);
3999
4164
  registerTeamTools(server, ctx, helpers);
4165
+ registerComplianceTools(server, ctx, helpers);
4000
4166
  registerVariableTools(server, ctx, helpers);
4001
4167
  registerTemplateTools(server, ctx, helpers);
4168
+ registerDocsTools(server, ctx, helpers);
4002
4169
  }
4003
4170
  var init_tools = __esm(() => {
4004
4171
  init_assistant();
4172
+ init_compliance();
4173
+ init_docs();
4005
4174
  init_notifications();
4006
4175
  init_observability();
4007
4176
  init_scrape();
@@ -4727,7 +4896,9 @@ async function createServer3(auth, options) {
4727
4896
  "Schema tips: Use descriptive field names and examples. Group related data under one entity.",
4728
4897
  "The AI agent uses the schema + prompt to understand what to extract - a detailed prompt with a comprehensive schema produces better results than multiple simple workflows.",
4729
4898
  "",
4730
- "Templates enforce their schema: a workflow created from a template (create_workflow with templateId) or linked to one (link_template_to_workflows) produces output whose field names conform to the template's declared schema. Use templates when you need many workflows to return a consistent, canonical set of fields - do NOT assume the extractor will drift field names away from a template's schema."
4899
+ "For questions about how Kadoa works or where to find something (API keys, billing and invoices, usage, settings), use list_docs_pages then read_docs_page and cite the page URL. Do not answer those from memory.",
4900
+ "",
4901
+ "Templates enforce their schema: a workflow created from a template (create_workflow or create_realtime_monitor with templateId) or linked to one (link_template_to_workflows) produces output whose field names conform to the template's declared schema. Use templates when you need many workflows to return a consistent, canonical set of fields - do NOT assume the extractor will drift field names away from a template's schema."
4731
4902
  ].join(`
4732
4903
  `)
4733
4904
  });
@@ -5205,7 +5376,9 @@ async function createServer(auth, options) {
5205
5376
  "Schema tips: Use descriptive field names and examples. Group related data under one entity.",
5206
5377
  "The AI agent uses the schema + prompt to understand what to extract - a detailed prompt with a comprehensive schema produces better results than multiple simple workflows.",
5207
5378
  "",
5208
- "Templates enforce their schema: a workflow created from a template (create_workflow with templateId) or linked to one (link_template_to_workflows) produces output whose field names conform to the template's declared schema. Use templates when you need many workflows to return a consistent, canonical set of fields - do NOT assume the extractor will drift field names away from a template's schema."
5379
+ "For questions about how Kadoa works or where to find something (API keys, billing and invoices, usage, settings), use list_docs_pages then read_docs_page and cite the page URL. Do not answer those from memory.",
5380
+ "",
5381
+ "Templates enforce their schema: a workflow created from a template (create_workflow or create_realtime_monitor with templateId) or linked to one (link_template_to_workflows) produces output whose field names conform to the template's declared schema. Use templates when you need many workflows to return a consistent, canonical set of fields - do NOT assume the extractor will drift field names away from a template's schema."
5209
5382
  ].join(`
5210
5383
  `)
5211
5384
  });