@kadoa/mcp 0.7.3 → 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 +15 -1
  2. package/dist/index.js +448 -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.3",
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')"),
@@ -1408,6 +1412,223 @@ var init_compliance = __esm(() => {
1408
1412
  init_schemas();
1409
1413
  });
1410
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
+
1411
1632
  // src/notification-channels.ts
1412
1633
  import { createHash } from "node:crypto";
1413
1634
  function recipientKey(recipients) {
@@ -1494,14 +1715,14 @@ function redactChannelRefs(channels) {
1494
1715
  var init_notification_channels = () => {};
1495
1716
 
1496
1717
  // src/tools/notifications.ts
1497
- import { z as z5 } from "zod";
1718
+ import { z as z6 } from "zod";
1498
1719
  function registerNotificationChannelTools(server, ctx, helpers) {
1499
1720
  const { withErrorHandling } = helpers;
1500
1721
  server.registerTool("list_notification_channels", {
1501
1722
  title: "List Notification Channels",
1502
1723
  description: "List notification channels configured for a workflow or across the workspace. Channels are destinations (email, Slack, webhook, websocket) where notifications are sent.",
1503
1724
  inputSchema: strictSchema({
1504
- 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.")
1505
1726
  }),
1506
1727
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
1507
1728
  }, withErrorHandling("list_notification_channels", async (args) => {
@@ -1524,20 +1745,20 @@ function registerNotificationChannelTools(server, ctx, helpers) {
1524
1745
  title: "Create Notification Channel",
1525
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.",
1526
1747
  inputSchema: strictSchema({
1527
- channelType: z5.enum(["EMAIL", "WEBHOOK", "SLACK", "WEBSOCKET"]).describe("Type of notification channel"),
1528
- name: z5.string().optional().describe("Name for the channel (defaults to 'default')"),
1529
- config: z5.object({
1530
- recipients: z5.array(z5.string()).optional().describe("Email recipients (required for EMAIL channels)"),
1531
- webhookUrl: z5.string().optional().describe("Webhook/Slack endpoint URL (required for WEBHOOK, optional for SLACK)"),
1532
- httpMethod: z5.enum(["POST", "GET", "PUT", "PATCH"]).optional().describe("HTTP method for WEBHOOK channels (defaults to POST)"),
1533
- slackChannelId: z5.string().optional().describe("Slack channel ID from OAuth integration (for SLACK channels, e.g. C01ABCDEF)"),
1534
- slackChannelName: z5.string().optional().describe("Slack channel name (for SLACK channels, e.g. #alerts)"),
1535
- auth: z5.object({
1536
- type: z5.enum(["bearer", "basic", "header"]).describe("Authentication type"),
1537
- token: z5.string().optional().describe("Bearer token (for 'bearer' type)"),
1538
- username: z5.string().optional().describe("Username (for 'basic' type)"),
1539
- password: z5.string().optional().describe("Password (for 'basic' type)"),
1540
- 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)")
1541
1762
  }).optional().describe("Authentication for WEBHOOK channels")
1542
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.")
1543
1764
  }),
@@ -1584,8 +1805,8 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
1584
1805
  title: "Delete Notification Channel",
1585
1806
  description: "Delete a notification channel by ID. " + "Call first without confirmed=true to preview, then with confirmed=true after user agrees.",
1586
1807
  inputSchema: strictSchema({
1587
- channelId: z5.string().min(1).describe("The channel ID to delete"),
1588
- 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.")
1589
1810
  }),
1590
1811
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
1591
1812
  }, withErrorHandling("delete_notification_channel", async (args) => {
@@ -1606,7 +1827,7 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
1606
1827
  title: "List Notification Settings",
1607
1828
  description: "List notification settings (event-to-channel mappings) for a workflow or workspace. Shows which events trigger notifications and through which channels.",
1608
1829
  inputSchema: strictSchema({
1609
- 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."),
1610
1831
  eventType: NotificationEventTypeSchema.optional().describe("Filter by event type")
1611
1832
  }),
1612
1833
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
@@ -1634,23 +1855,23 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
1634
1855
  title: "Configure Notifications",
1635
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).",
1636
1857
  inputSchema: strictSchema({
1637
- workflowId: z5.string().optional().describe("Workflow ID to configure notifications for. Omit for workspace-level notifications."),
1638
- 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.'),
1639
- channels: z5.object({
1640
- email: z5.object({
1641
- 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.")
1642
1863
  }).optional().describe("Send email notifications. Pass {} for default email or {recipients: [...]} for custom addresses."),
1643
- webhook: z5.object({
1644
- url: z5.string().url().describe("Webhook endpoint URL"),
1645
- 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)"),
1646
1867
  auth: webhookAuthShape
1647
1868
  }).optional().describe("Send via HTTP webhook"),
1648
- slack: z5.object({
1649
- webhookUrl: z5.string().url().optional().describe("Slack incoming webhook URL (legacy)"),
1650
- slackChannelId: z5.string().optional().describe("Slack channel ID from OAuth integration (e.g. C01ABCDEF)"),
1651
- 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)")
1652
1873
  }).optional().describe("Send to Slack channel. Use slackChannelId/slackChannelName for OAuth integration, or webhookUrl for legacy incoming webhooks."),
1653
- websocket: z5.boolean().optional().describe("Enable WebSocket notifications")
1874
+ websocket: z6.boolean().optional().describe("Enable WebSocket notifications")
1654
1875
  }).describe("At least one channel must be specified")
1655
1876
  }),
1656
1877
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
@@ -1712,8 +1933,8 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
1712
1933
  title: "Delete Notification Setting",
1713
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.",
1714
1935
  inputSchema: strictSchema({
1715
- settingId: z5.string().min(1).describe("The notification setting ID to delete (the id from list_notification_settings)"),
1716
- 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.")
1717
1938
  }),
1718
1939
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
1719
1940
  }, withErrorHandling("delete_notification_setting", async (args) => {
@@ -1739,7 +1960,7 @@ var init_notifications = __esm(() => {
1739
1960
 
1740
1961
  // src/tools/observability.ts
1741
1962
  import { KadoaSdkException as KadoaSdkException2 } from "@kadoa/node-sdk";
1742
- import { z as z6 } from "zod";
1963
+ import { z as z7 } from "zod";
1743
1964
  function mapActivityEvent(event, includeDetails) {
1744
1965
  const details = event.details ?? {};
1745
1966
  const summary = details._changeSummary;
@@ -1812,10 +2033,10 @@ function registerObservabilityTools(server, ctx, helpers) {
1812
2033
  title: "Get Observability Metrics",
1813
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.",
1814
2035
  inputSchema: strictSchema({
1815
- 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."),
1816
- includeWorkflows: z6.preprocess(coerceBoolean(), z6.boolean()).optional().describe("Also return every workflow ranked by severity, not only the down and degraded ones (default false)."),
1817
- includeDailySeries: z6.preprocess(coerceBoolean(), z6.boolean()).optional().describe("Also return the team-level daily series of successful runs, failed runs, and uptime (default false)."),
1818
- 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).")
1819
2040
  }),
1820
2041
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
1821
2042
  }, withErrorHandling("get_observability", async (args) => {
@@ -1873,15 +2094,15 @@ function registerObservabilityTools(server, ctx, helpers) {
1873
2094
  inputSchema: strictSchema({
1874
2095
  limit: limitInput({ noun: "Events", defaultLimit: ACTIVITY_DEFAULT_LIMIT, max: ACTIVITY_MAX_LIMIT }),
1875
2096
  offset: offsetInput("offset", "events"),
1876
- workflowId: z6.string().optional().describe("Only events about this workflow (runs, config changes, data changes, notifications)"),
1877
- userId: z6.string().optional().describe("Only events by this user id. Resolve an email to an id from a previous list_activity result."),
1878
- startDate: z6.string().optional().describe("ISO 8601 lower bound. Ignored when relativeTime is set."),
1879
- endDate: z6.string().optional().describe("ISO 8601 upper bound. Ignored when relativeTime is set."),
1880
- 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."),
1881
- 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."),
1882
- interfaces: z6.preprocess(coerceList({ upperCase: true }), z6.array(z6.enum(ACTIVITY_INTERFACES))).optional().describe("Interfaces the action came from"),
1883
- resourceTypes: z6.preprocess(coerceList({ upperCase: true }), z6.array(z6.enum(ACTIVITY_RESOURCE_TYPES))).optional().describe("Resource types to keep"),
1884
- 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)")
1885
2106
  }),
1886
2107
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
1887
2108
  }, withErrorHandling("list_activity", async (args) => {
@@ -1959,125 +2180,8 @@ var init_observability = __esm(() => {
1959
2180
  HEALTH_SEVERITY = { down: 0, degraded: 1, idle: 2, healthy: 3 };
1960
2181
  });
1961
2182
 
1962
- // src/tools/shared/budget.ts
1963
- function jsonBytes(value) {
1964
- const text = JSON.stringify(value);
1965
- return text === undefined ? 0 : Buffer.byteLength(text, "utf8");
1966
- }
1967
- function truncateUtf8(text, maxBytes) {
1968
- if (Buffer.byteLength(text, "utf8") <= maxBytes)
1969
- return text;
1970
- const { read } = new TextEncoder().encodeInto(text, new Uint8Array(Math.max(0, maxBytes)));
1971
- return text.slice(0, read);
1972
- }
1973
- function previewValue(value, maxBytes = VALUE_PREVIEW_BYTES) {
1974
- if (value === undefined || value === null)
1975
- return null;
1976
- const bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
1977
- if (bytes <= maxBytes)
1978
- return value;
1979
- if (typeof value === "string")
1980
- return `${truncateUtf8(value, maxBytes)}… [cut, ${bytes} bytes]`;
1981
- if (Array.isArray(value))
1982
- return `[array of ${value.length} items, ${bytes} bytes]`;
1983
- return `[${typeof value}, ${bytes} bytes]`;
1984
- }
1985
- function fitItems(options) {
1986
- const budget = options.budgetBytes ?? RESULT_BUDGET_BYTES;
1987
- const all = [...options.items];
1988
- if (jsonBytes(options.build(all)) <= budget)
1989
- return { kept: all, omitted: 0, truncated: false };
1990
- const limit = budget - TRUNCATION_RESERVE_BYTES - (options.reserveBytes ?? 0);
1991
- let lo = 0;
1992
- let hi = all.length - 1;
1993
- while (lo < hi) {
1994
- const mid = Math.ceil((lo + hi) / 2);
1995
- if (jsonBytes(options.build(all.slice(0, mid))) <= limit)
1996
- lo = mid;
1997
- else
1998
- hi = mid - 1;
1999
- }
2000
- return { kept: all.slice(0, lo), omitted: all.length - lo, truncated: true };
2001
- }
2002
- function truncationNotice(options) {
2003
- const budgetBytes = options.budgetBytes ?? RESULT_BUDGET_BYTES;
2004
- const total = options.kept + options.omitted;
2005
- return {
2006
- truncated: true,
2007
- truncation: { kept: options.kept, omitted: options.omitted, budgetBytes },
2008
- hint: `Result cut to ${options.kept} of ${total} ${options.noun} to stay under ${Math.round(budgetBytes / 1000)} KB. ${options.hint}`
2009
- };
2010
- }
2011
- function highlyDivisibleFloor(n) {
2012
- if (n <= 0)
2013
- return 0;
2014
- if (n >= 10)
2015
- return Math.floor(n / 10) * 10;
2016
- let p = 1;
2017
- while (p * 2 <= n)
2018
- p *= 2;
2019
- return p;
2020
- }
2021
- function alignedKeep(offset, fit) {
2022
- if (offset === 0)
2023
- return highlyDivisibleFloor(fit);
2024
- for (let keep = fit;keep >= 1; keep--) {
2025
- if (offset % keep === 0 && offset / keep + 2 <= MAX_PAGE)
2026
- return keep;
2027
- }
2028
- return 0;
2029
- }
2030
- function resumeNumberedPage(meta, options) {
2031
- const { next: _replaced, totalPages: _droppedTotalPages, ...rest } = meta;
2032
- if (options.kept === 0)
2033
- return { ...rest, hasMore: false };
2034
- const offset = (options.page - 1) * options.limit;
2035
- return {
2036
- ...rest,
2037
- hasMore: true,
2038
- next: nextArgs(options.args, { page: offset / options.kept + 2, limit: options.kept })
2039
- };
2040
- }
2041
- function budgetNumberedPage(options) {
2042
- const offset = (options.page - 1) * options.limit;
2043
- const fit = fitItems({
2044
- items: options.items,
2045
- build: (kept) => options.build(kept, options.pagination),
2046
- reserveBytes: jsonBytes({ pagination: { next: options.args } })
2047
- });
2048
- if (!fit.truncated)
2049
- return options.build([...options.items], options.pagination);
2050
- const buildCut = (fitCount) => {
2051
- const keep = alignedKeep(offset, fitCount);
2052
- const alignmentImpossible = keep === 0 && fitCount > 0;
2053
- const pagination = resumeNumberedPage(options.pagination, {
2054
- args: options.args,
2055
- page: options.page,
2056
- limit: options.limit,
2057
- kept: keep
2058
- });
2059
- const hint = keep > 0 ? options.resumeHint : alignmentImpossible ? options.alignmentImpossibleHint ?? DEFAULT_ALIGNMENT_IMPOSSIBLE_HINT : options.emptyHint;
2060
- const result = {
2061
- ...options.build(options.items.slice(0, keep), pagination),
2062
- ...truncationNotice({ kept: keep, omitted: options.items.length - keep, noun: options.noun, hint })
2063
- };
2064
- return { result, keep };
2065
- };
2066
- let fitCount = fit.kept.length;
2067
- let { result, keep } = buildCut(fitCount);
2068
- while (jsonBytes(result) > RESULT_BUDGET_BYTES && keep > 0) {
2069
- fitCount = keep - 1;
2070
- ({ result, keep } = buildCut(fitCount));
2071
- }
2072
- return result;
2073
- }
2074
- 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.";
2075
- var init_budget = __esm(() => {
2076
- init_pagination();
2077
- });
2078
-
2079
2183
  // src/tools/scrape.ts
2080
- import { z as z7 } from "zod";
2184
+ import { z as z8 } from "zod";
2081
2185
  function scrapeResult(response, format) {
2082
2186
  const { content, ...metadata } = response;
2083
2187
  if (typeof content !== "string" || content.length === 0)
@@ -2112,11 +2216,11 @@ function registerScrapeTools(server, ctx, helpers, defaultProductType) {
2112
2216
  title: "Scrape URL",
2113
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.",
2114
2218
  inputSchema: strictSchema({
2115
- url: z7.url().refine((value) => {
2219
+ url: z8.url().refine((value) => {
2116
2220
  const protocol = new URL(value).protocol;
2117
2221
  return protocol === "http:" || protocol === "https:";
2118
2222
  }, "URL must use HTTP or HTTPS").describe("HTTP or HTTPS URL to fetch"),
2119
- 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.")
2120
2224
  }),
2121
2225
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
2122
2226
  }, withErrorHandling("scrape", async (args) => {
@@ -2282,7 +2386,7 @@ var init_errors = () => {};
2282
2386
 
2283
2387
  // src/tools/support.ts
2284
2388
  import { KadoaHttpException as KadoaHttpException2 } from "@kadoa/node-sdk";
2285
- import { z as z8 } from "zod";
2389
+ import { z as z9 } from "zod";
2286
2390
  function supportTicketSkipMessage(reason) {
2287
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.";
2288
2392
  }
@@ -2306,11 +2410,11 @@ function registerSupportTools(server, ctx, helpers) {
2306
2410
  title: "Create Support Ticket",
2307
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.",
2308
2412
  inputSchema: strictSchema({
2309
- workflowId: z8.string().min(1).describe("The workflow the problem is about"),
2310
- title: z8.string().describe("One-line summary of the problem"),
2311
- description: z8.string().describe("What is wrong, what was observed, and what was already tried"),
2312
- category: z8.enum(["bug", "feature", "workflow_issue", "template_issue", "integration", "billing", "data_diff"]).optional().describe("Support category; defaults to a workflow issue"),
2313
- 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.")
2314
2418
  }),
2315
2419
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
2316
2420
  }, withErrorHandling("create_support_ticket", async (args) => {
@@ -2374,7 +2478,7 @@ var init_support = __esm(() => {
2374
2478
  });
2375
2479
 
2376
2480
  // src/tools/team.ts
2377
- import { z as z9 } from "zod";
2481
+ import { z as z10 } from "zod";
2378
2482
  function registerWhoamiTool(server, ctx, helpers) {
2379
2483
  const { withErrorHandling } = helpers;
2380
2484
  server.registerTool("whoami", {
@@ -2444,7 +2548,7 @@ function registerTeamTools(server, ctx, helpers) {
2444
2548
  title: "Switch Team",
2445
2549
  description: "Switch the active team by name or ID. After switching, all subsequent API calls will use the new team context.",
2446
2550
  inputSchema: strictSchema({
2447
- 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")
2448
2552
  }),
2449
2553
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
2450
2554
  }, withErrorHandling("team_switch", async () => errorResult("team_switch is not available for this credential. Reconnect and choose the team at sign-in.")));
@@ -2455,7 +2559,7 @@ var init_team = __esm(() => {
2455
2559
  });
2456
2560
 
2457
2561
  // src/tools/templates.ts
2458
- import { z as z10 } from "zod";
2562
+ import { z as z11 } from "zod";
2459
2563
  function normalizeTemplateSchemaField(val) {
2460
2564
  if (val === null || typeof val !== "object" || Array.isArray(val))
2461
2565
  return val;
@@ -2477,7 +2581,7 @@ function registerTemplateTools(server, ctx, helpers) {
2477
2581
  const { withErrorHandling } = helpers;
2478
2582
  server.registerTool("list_templates", {
2479
2583
  title: "List Templates",
2480
- 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.",
2481
2585
  inputSchema: strictSchema({}),
2482
2586
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
2483
2587
  }, withErrorHandling("list_templates", async () => {
@@ -2486,29 +2590,29 @@ function registerTemplateTools(server, ctx, helpers) {
2486
2590
  templates: templates.map(withTemplateDashboardUrl),
2487
2591
  count: templates.length,
2488
2592
  pagination: completeList(templates.length),
2489
- 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."
2490
2594
  });
2491
2595
  }));
2492
2596
  server.registerTool("get_template", {
2493
2597
  title: "Get Template",
2494
- 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.",
2495
2599
  inputSchema: strictSchema({
2496
- templateId: z10.string().min(1).describe("The template ID")
2600
+ templateId: z11.string().min(1).describe("The template ID")
2497
2601
  }),
2498
2602
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
2499
2603
  }, withErrorHandling("get_template", async (args) => {
2500
2604
  const template = await ctx.client.template.get(args.templateId);
2501
2605
  return jsonResult({
2502
2606
  template: withTemplateDashboardUrl(template),
2503
- 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.`
2504
2608
  });
2505
2609
  }));
2506
2610
  server.registerTool("create_template", {
2507
2611
  title: "Create Template",
2508
2612
  description: "Create a new template. After creation, use create_template_version to publish a version with prompt, schema, and notifications.",
2509
2613
  inputSchema: strictSchema({
2510
- name: z10.string().describe("Template name (1-255 chars)"),
2511
- 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)")
2512
2616
  }),
2513
2617
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
2514
2618
  }, withErrorHandling("create_template", async (args) => {
@@ -2528,9 +2632,9 @@ function registerTemplateTools(server, ctx, helpers) {
2528
2632
 
2529
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.",
2530
2634
  inputSchema: strictSchema({
2531
- templateId: z10.string().min(1).describe("The template ID to update"),
2532
- name: z10.preprocess(coerceNull(), z10.string().optional()).optional().describe("New template name"),
2533
- 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")
2534
2638
  }),
2535
2639
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
2536
2640
  }, withErrorHandling("update_template", async (args) => {
@@ -2546,8 +2650,8 @@ function registerTemplateTools(server, ctx, helpers) {
2546
2650
  title: "Delete Template",
2547
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.",
2548
2652
  inputSchema: strictSchema({
2549
- templateId: z10.string().min(1).describe("The template ID to delete"),
2550
- 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.")
2551
2655
  }),
2552
2656
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
2553
2657
  }, withErrorHandling("delete_template", async (args) => {
@@ -2571,15 +2675,15 @@ function registerTemplateTools(server, ctx, helpers) {
2571
2675
  });
2572
2676
  }));
2573
2677
  const TemplateSchemaFieldShape = {
2574
- name: z10.string().describe("Field name"),
2575
- description: z10.string().optional().describe("Field description"),
2576
- fieldType: z10.enum(["SCHEMA", "CLASSIFICATION"]).optional().describe("Field role: SCHEMA (default) for extracted data, CLASSIFICATION for a field with predefined categories"),
2577
- 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"),
2578
2682
  dataType: SchemaFieldDataTypeSchema.optional().describe("Data type for SCHEMA fields"),
2579
- isKey: z10.preprocess(coerceBoolean(), z10.boolean()).optional().describe("Whether the field is a key field"),
2580
- categories: z10.preprocess(coerceArray(), z10.array(z10.object({
2581
- title: z10.string().describe("Short title/label of the category"),
2582
- 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")
2583
2687
  }))).optional().describe("Predefined categories for a CLASSIFICATION field ({title, definition}[]). Required for fieldType=CLASSIFICATION; omitted otherwise.")
2584
2688
  };
2585
2689
  async function latestTemplateVersionRules(templateId) {
@@ -2604,25 +2708,25 @@ function registerTemplateTools(server, ctx, helpers) {
2604
2708
 
2605
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.",
2606
2710
  inputSchema: strictSchema({
2607
- templateId: z10.string().min(1).describe("The template ID to publish a version for"),
2608
- prompt: z10.string().optional().describe("User prompt to copy into workflow config"),
2609
- schemaId: z10.string().optional().describe("Existing schema ID to reference (mutually exclusive with schemaFields)"),
2610
- schemaFields: z10.preprocess(coerceArray(), z10.array(z10.preprocess(normalizeTemplateSchemaField, z10.object(TemplateSchemaFieldShape).strict()))).optional().describe("Inline schema fields to create a new schema (mutually exclusive with schemaId)"),
2611
- schemaEntity: z10.string().optional().describe("Entity name for the inline schema"),
2612
- schemaValidationRules: z10.preprocess((val) => stripSchemaValidationAttribution(coerceJson()(val)), SchemaValidationRulesSchema).optional().describe("Per-field schema validation rules keyed by field name. Each value is an object with a required `kind` and optional rules for that kind; unknown keys are rejected. " + "Every kind accepts presence and uniqueness, each { target: integer 0-100 in steps of 20 } (expected percentage of rows with a value / a unique value). " + "STRING: minLength, maxLength, minHtmlElements, maxHtmlElements, each { value: integer >= 0 }; format, one of " + '{ kind: "FORMAT", source: { kind: "PRESET", preset: url | email | phone | date | datetime | time | uuid | slug } or { kind: "CUSTOM", pattern: regex string } }, ' + '{ kind: "FREE_TEXT", charset: { kind: "PRESET", preset: natural_language | alphanumeric | alpha } }, or ' + '{ kind: "LIST", source: { kind: "PRESET", preset: language2 | country2 | country3 | currency3 | month3 | usState2 } or { kind: "CUSTOM", values: [non-empty strings] } }. ' + "NUMBER: minimum and maximum, each { value: number }; maxDecimalPlaces { value: integer 0-16 }. " + "OTHER: only presence and uniqueness. " + "OBJECT: properties (required), a map of nested field name to rule. " + "ARRAY: minItems, maxItems, each { value: integer >= 0 }; items, the rule for each element. " + 'Examples: { "price": { "kind": "NUMBER", "minimum": { "value": 0 } } }; { "email": { "kind": "STRING", "format": { "kind": "FORMAT", "source": { "kind": "PRESET", "preset": "email" } } } }. ' + "Edit attribution is stamped server-side, so send only rule values. Not inherited from prior versions - omitting this on a new version drops any rules the previous version had."),
2613
- notifications: z10.preprocess(coerceArray(), z10.array(z10.object({
2614
- eventType: z10.string().describe("Notification event type"),
2615
- eventConfiguration: z10.preprocess(coerceJson(), z10.record(z10.string(), z10.unknown())).optional(),
2616
- enabled: z10.preprocess(coerceBoolean(), z10.boolean()).optional(),
2617
- channelIds: z10.preprocess(coerceArray(), z10.array(z10.string().min(1))).optional(),
2618
- 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()
2619
2723
  }))).optional().describe("Notification configuration for this version"),
2620
- frequency: z10.preprocess(coerceJson(), z10.object({
2621
- interval: z10.enum([...UpdateIntervalSchema.options, "REAL_TIME"]).optional().describe("Scheduling interval the template controls for linked workflows. Use CUSTOM with 'schedules' for cron."),
2622
- schedules: z10.preprocess(coerceArray(), z10.array(z10.object({
2623
- type: z10.enum(["repeat", "cron"]).describe("'repeat' (interval token) or 'cron' (cron expressions)"),
2624
- interval: z10.string().optional().describe("Repeat token (e.g. '1h', '1d') when type='repeat'"),
2625
- 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')")
2626
2730
  }))).optional().describe("Schedule specs. Required when interval='CUSTOM'.")
2627
2731
  })).optional().describe("Template-controlled scheduling for linked workflows ({interval, schedules}). Linked workflows inherit and cannot override this when set.")
2628
2732
  }),
@@ -2652,11 +2756,11 @@ function registerTemplateTools(server, ctx, helpers) {
2652
2756
  title: "Save Workflow as Template",
2653
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.",
2654
2758
  inputSchema: strictSchema({
2655
- workflowId: z10.string().min(1).describe("Source workflow ID to extract config from"),
2656
- name: z10.string().optional().describe("Name for the new template (required if templateId is not set)"),
2657
- description: z10.string().optional().describe("Description for the new template"),
2658
- templateId: z10.string().optional().describe("Existing template ID to add a new version to (mutually exclusive with name)"),
2659
- 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.")
2660
2764
  }),
2661
2765
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
2662
2766
  }, withErrorHandling("save_workflow_as_template", async (args) => {
@@ -2677,7 +2781,7 @@ function registerTemplateTools(server, ctx, helpers) {
2677
2781
  title: "List Template Schemas",
2678
2782
  description: "List schemas associated with a template.",
2679
2783
  inputSchema: strictSchema({
2680
- templateId: z10.string().min(1).describe("The template ID")
2784
+ templateId: z11.string().min(1).describe("The template ID")
2681
2785
  }),
2682
2786
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
2683
2787
  }, withErrorHandling("list_template_schemas", async (args) => {
@@ -2688,9 +2792,9 @@ function registerTemplateTools(server, ctx, helpers) {
2688
2792
  title: "Link Template to Workflows",
2689
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.",
2690
2794
  inputSchema: strictSchema({
2691
- templateId: z10.string().min(1).describe("The template ID to link workflows to"),
2692
- 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."),
2693
- 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.")
2694
2798
  }),
2695
2799
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
2696
2800
  }, withErrorHandling("link_template_to_workflows", async (args) => {
@@ -2717,9 +2821,9 @@ function registerTemplateTools(server, ctx, helpers) {
2717
2821
  title: "Apply Template Version",
2718
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.",
2719
2823
  inputSchema: strictSchema({
2720
- templateId: z10.string().min(1).describe("The template ID whose version to apply"),
2721
- targetVersion: z10.preprocess(coerceNumber(), z10.number()).describe("The template version number to apply to the workflows"),
2722
- 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.")
2723
2827
  }),
2724
2828
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
2725
2829
  }, withErrorHandling("apply_template_version", async (args) => {
@@ -2739,7 +2843,7 @@ function registerTemplateTools(server, ctx, helpers) {
2739
2843
  title: "List Template Workflows",
2740
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.",
2741
2845
  inputSchema: strictSchema({
2742
- templateId: z10.string().min(1).describe("The template ID")
2846
+ templateId: z11.string().min(1).describe("The template ID")
2743
2847
  }),
2744
2848
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
2745
2849
  }, withErrorHandling("list_template_workflows", async (args) => {
@@ -2750,8 +2854,8 @@ function registerTemplateTools(server, ctx, helpers) {
2750
2854
  title: "Unlink Template from Workflows",
2751
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).",
2752
2856
  inputSchema: strictSchema({
2753
- templateId: z10.string().min(1).describe("The template ID to unlink workflows from"),
2754
- 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.")
2755
2859
  }),
2756
2860
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
2757
2861
  }, withErrorHandling("unlink_template_from_workflows", async (args) => {
@@ -2774,16 +2878,16 @@ var init_templates = __esm(() => {
2774
2878
  });
2775
2879
 
2776
2880
  // src/tools/variables.ts
2777
- import { z as z11 } from "zod";
2881
+ import { z as z12 } from "zod";
2778
2882
  function registerVariableTools(server, ctx, helpers) {
2779
2883
  const { withErrorHandling } = helpers;
2780
2884
  server.registerTool("create_variable", {
2781
2885
  title: "Create Variable",
2782
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.",
2783
2887
  inputSchema: strictSchema({
2784
- key: z11.string().describe("Variable key (must be unique within scope). Referenced in prompts as @key."),
2785
- value: z11.string().describe("Variable value"),
2786
- 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)")
2787
2891
  }),
2788
2892
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
2789
2893
  }, withErrorHandling("create_variable", async (args) => {
@@ -2815,7 +2919,7 @@ function registerVariableTools(server, ctx, helpers) {
2815
2919
  title: "Get Variable",
2816
2920
  description: "Get a specific variable by its ID.",
2817
2921
  inputSchema: strictSchema({
2818
- variableId: z11.string().min(1).describe("The variable ID")
2922
+ variableId: z12.string().min(1).describe("The variable ID")
2819
2923
  }),
2820
2924
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
2821
2925
  }, withErrorHandling("get_variable", async (args) => {
@@ -2826,10 +2930,10 @@ function registerVariableTools(server, ctx, helpers) {
2826
2930
  title: "Update Variable",
2827
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.",
2828
2932
  inputSchema: strictSchema({
2829
- variableId: z11.string().min(1).describe("The variable ID to update"),
2830
- key: z11.string().optional().describe("New variable key"),
2831
- value: z11.string().optional().describe("New variable value"),
2832
- 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")
2833
2937
  }),
2834
2938
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
2835
2939
  }, withErrorHandling("update_variable", async (args) => {
@@ -2845,8 +2949,8 @@ function registerVariableTools(server, ctx, helpers) {
2845
2949
  title: "Delete Variable",
2846
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.",
2847
2951
  inputSchema: strictSchema({
2848
- variableId: z11.string().min(1).describe("The variable ID to delete"),
2849
- 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.")
2850
2954
  }),
2851
2955
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
2852
2956
  }, withErrorHandling("delete_variable", async (args) => {
@@ -2977,7 +3081,7 @@ var init_workflow_status = __esm(() => {
2977
3081
 
2978
3082
  // src/tools/workflows.ts
2979
3083
  import { WorkflowStatusFilter } from "@kadoa/node-sdk";
2980
- import { z as z12 } from "zod";
3084
+ import { z as z13 } from "zod";
2981
3085
  function mapRunStatus(state) {
2982
3086
  switch (state) {
2983
3087
  case "FINISHED":
@@ -3188,13 +3292,12 @@ function registerWorkflowCoreTools(server, ctx, helpers, defaultProductType) {
3188
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.'),
3189
3293
  ...extractionInputShape,
3190
3294
  ...urlInputShape,
3191
- 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."),
3192
- 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."),
3193
- description: z12.string().max(500).optional().describe("Description of what this workflow does (max 500 characters)"),
3194
- tags: z12.preprocess(coerceArray(true), z12.array(z12.string())).optional().describe("Tags for organizing workflows"),
3195
- 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."),
3196
3299
  updateInterval: UpdateIntervalSchema.optional().describe("How often the workflow runs. Defaults to ONLY_ONCE. Use CUSTOM with the 'schedules' field for cron-based scheduling."),
3197
- 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'."),
3198
3301
  ...notificationsInputShape
3199
3302
  }),
3200
3303
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
@@ -3338,13 +3441,14 @@ function registerWorkflowCoreTools(server, ctx, helpers, defaultProductType) {
3338
3441
  title: "Create Realtime Monitor",
3339
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.
3340
3443
 
3341
- ` + "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.",
3342
3445
  inputSchema: strictSchema({
3343
- 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."),
3344
3447
  ...extractionInputShape,
3345
3448
  ...urlInputShape,
3346
- description: z12.string().max(500).optional().describe("Description of what this monitor watches (max 500 characters)"),
3347
- 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"),
3348
3452
  ...requiredNotificationsInputShape
3349
3453
  }),
3350
3454
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
@@ -3358,6 +3462,21 @@ function registerWorkflowCoreTools(server, ctx, helpers, defaultProductType) {
3358
3462
  if (!hasNotifications) {
3359
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.");
3360
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
+ }
3361
3480
  const channels = await ctx.client.notification.setup.setupChannels({
3362
3481
  channels: buildNotificationChannels(n)
3363
3482
  });
@@ -3367,11 +3486,26 @@ function registerWorkflowCoreTools(server, ctx, helpers, defaultProductType) {
3367
3486
  }
3368
3487
  let result;
3369
3488
  try {
3370
- result = await ctx.client.assistant.createRealtimeWorkflow({
3371
- instructions: buildRealtimeAssistantInstructions(args, urls),
3372
- notificationChannelIds,
3373
- ...args.tags?.length ? { tags: args.tags } : {}
3374
- });
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
+ }
3375
3509
  } catch (error) {
3376
3510
  console.error("[Tool Error] create_realtime_monitor:", safeErrorSummary(error));
3377
3511
  return isAmbiguousFailure(error) ? realtimeCreationFailure(shortErrorReason(error), notificationChannelIds, { outcomeUnknown: true }) : realtimeCreationFailure(classifyError(error, "create_realtime_monitor"), notificationChannelIds);
@@ -3401,13 +3535,13 @@ function registerWorkflowCoreTools(server, ctx, helpers, defaultProductType) {
3401
3535
  max: LIST_WORKFLOWS_MAX_LIMIT
3402
3536
  }),
3403
3537
  skip: offsetInput("skip", "workflows"),
3404
- state: z12.enum(["ACTIVE", "FAILED", "PAUSED", "PREVIEW"]).optional().describe("Filter by state (ACTIVE, FAILED, PAUSED, PREVIEW)"),
3405
- search: z12.string().optional().describe("Free-text filter matching workflow name, URL, or workflow ID"),
3406
- tags: z12.array(z12.string()).optional().describe("Filter to workflows having any of these tags"),
3407
- monitoring: z12.preprocess(coerceBoolean(), z12.boolean()).optional().describe("Filter by monitoring/real-time status"),
3408
- updateInterval: z12.enum(["HOURLY", "DAILY", "WEEKLY", "MONTHLY", "REAL_TIME"]).optional().describe("Filter by scheduled update frequency or realtime workflows"),
3409
- templateId: z12.string().optional().describe("Filter to workflows instantiated from this template ID"),
3410
- 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.")
3411
3545
  }),
3412
3546
  outputSchema: ListWorkflowsOutput,
3413
3547
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
@@ -3478,7 +3612,7 @@ function registerWorkflowCoreTools(server, ctx, helpers, defaultProductType) {
3478
3612
  title: "Get Workflow",
3479
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).',
3480
3614
  inputSchema: strictSchema({
3481
- workflowId: z12.string().min(1).describe("The workflow ID")
3615
+ workflowId: z13.string().min(1).describe("The workflow ID")
3482
3616
  }),
3483
3617
  outputSchema: GetWorkflowOutput,
3484
3618
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
@@ -3567,9 +3701,9 @@ function registerWorkflowCoreTools(server, ctx, helpers, defaultProductType) {
3567
3701
  title: "Request Workflow Update",
3568
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.",
3569
3703
  inputSchema: strictSchema({
3570
- workflowId: z12.string().min(1).describe("The existing workflow ID; it will not change"),
3571
- instructions: z12.string().min(1).describe("Natural-language instructions for the workflow Assistant"),
3572
- 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")
3573
3707
  }),
3574
3708
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
3575
3709
  }, withErrorHandling("request_workflow_update", async (args) => {
@@ -3599,14 +3733,14 @@ function registerWorkflowHistoryTools(server, ctx, helpers) {
3599
3733
  title: "Get Workflow History",
3600
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).",
3601
3735
  inputSchema: strictSchema({
3602
- workflowId: z12.string().min(1).describe("The workflow ID"),
3736
+ workflowId: z13.string().min(1).describe("The workflow ID"),
3603
3737
  page: pageInput(),
3604
3738
  limit: limitInput({
3605
3739
  noun: "Entries",
3606
3740
  defaultLimit: WORKFLOW_HISTORY_DEFAULT_LIMIT,
3607
3741
  max: WORKFLOW_HISTORY_MAX_LIMIT
3608
3742
  }),
3609
- 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)")
3610
3744
  }),
3611
3745
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
3612
3746
  }, withErrorHandling("get_workflow_history", async (args) => {
@@ -3658,8 +3792,8 @@ function registerWorkflowHistoryTools(server, ctx, helpers) {
3658
3792
  title: "List Workflow Runs",
3659
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.",
3660
3794
  inputSchema: strictSchema({
3661
- workflowId: z12.string().min(1).describe("The workflow ID"),
3662
- 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"),
3663
3797
  page: pageInput(),
3664
3798
  limit: limitInput({ noun: "Runs", defaultLimit: WORKFLOW_RUNS_DEFAULT_LIMIT, max: WORKFLOW_RUNS_MAX_LIMIT })
3665
3799
  }),
@@ -3703,8 +3837,8 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3703
3837
  title: "Run Workflow",
3704
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.",
3705
3839
  inputSchema: strictSchema({
3706
- workflowId: z12.string().min(1).describe("The workflow ID to run"),
3707
- 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)")
3708
3842
  }),
3709
3843
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
3710
3844
  }, withErrorHandling("run_workflow", async (args) => {
@@ -3727,7 +3861,7 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3727
3861
  title: "Fetch Workflow Data",
3728
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.",
3729
3863
  inputSchema: strictSchema({
3730
- workflowId: z12.string().min(1).describe("The workflow ID"),
3864
+ workflowId: z13.string().min(1).describe("The workflow ID"),
3731
3865
  limit: limitInput({
3732
3866
  noun: "Rows",
3733
3867
  defaultLimit: FETCH_DATA_DEFAULT_LIMIT,
@@ -3736,8 +3870,8 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3736
3870
  }),
3737
3871
  page: pageInput(),
3738
3872
  filters: dataFiltersInput,
3739
- sortBy: z12.string().optional().describe("Field name to sort by"),
3740
- 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")
3741
3875
  }),
3742
3876
  outputSchema: FetchDataOutput,
3743
3877
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
@@ -3786,13 +3920,13 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3786
3920
  title: "Export Workflow Data",
3787
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').",
3788
3922
  inputSchema: strictSchema({
3789
- workflowId: z12.string().min(1).describe("The workflow ID"),
3790
- format: z12.enum(["csv", "json"]).optional().describe("Export format. Default 'csv'."),
3791
- 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."),
3792
3926
  filters: dataFiltersInput,
3793
- sortBy: z12.string().optional().describe("Field name to sort by"),
3794
- order: z12.enum(["asc", "desc"]).optional().describe("Sort order: asc or desc"),
3795
- 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)")
3796
3930
  }),
3797
3931
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
3798
3932
  }, withErrorHandling("export_data", async (args) => {
@@ -3826,9 +3960,9 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3826
3960
  title: "List Detected Changes",
3827
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.",
3828
3962
  inputSchema: strictSchema({
3829
- 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."),
3830
- startDate: z12.string().optional().describe("Start date filter (ISO format, e.g. 2025-01-01)"),
3831
- 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)"),
3832
3966
  skip: offsetInput("skip", "changes"),
3833
3967
  limit: limitInput({ noun: "Changes", defaultLimit: LIST_CHANGES_DEFAULT_LIMIT, max: LIST_CHANGES_MAX_LIMIT })
3834
3968
  }),
@@ -3866,7 +4000,7 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3866
4000
  title: "Get Detected Change",
3867
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.",
3868
4002
  inputSchema: strictSchema({
3869
- changeId: z12.string().min(1).describe("The change ID")
4003
+ changeId: z13.string().min(1).describe("The change ID")
3870
4004
  }),
3871
4005
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
3872
4006
  }, withErrorHandling("get_change", async (args) => {
@@ -3877,8 +4011,8 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3877
4011
  title: "Delete Workflow",
3878
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.",
3879
4013
  inputSchema: strictSchema({
3880
- workflowId: z12.string().min(1).describe("The workflow ID to delete"),
3881
- 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.")
3882
4016
  }),
3883
4017
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
3884
4018
  }, withErrorHandling("delete_workflow", async (args) => {
@@ -3908,7 +4042,7 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3908
4042
  title: "Approve Workflow",
3909
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.",
3910
4044
  inputSchema: strictSchema({
3911
- 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")
3912
4046
  }),
3913
4047
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
3914
4048
  }, withErrorHandling("approve_workflow", async (args) => {
@@ -3923,7 +4057,7 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3923
4057
  title: "Pause Workflow",
3924
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.",
3925
4059
  inputSchema: strictSchema({
3926
- workflowId: z12.string().min(1).describe("The workflow ID to pause")
4060
+ workflowId: z13.string().min(1).describe("The workflow ID to pause")
3927
4061
  }),
3928
4062
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
3929
4063
  }, withErrorHandling("pause_workflow", async (args) => {
@@ -3942,20 +4076,20 @@ function registerWorkflowRunTools(server, ctx, helpers) {
3942
4076
 
3943
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.",
3944
4078
  inputSchema: strictSchema({
3945
- workflowId: z12.string().min(1).describe("The workflow ID to update"),
3946
- name: z12.string().optional().describe("New name for the workflow"),
3947
- 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."),
3948
- entity: z12.string().optional().describe("Entity name for extraction (e.g., 'Product', 'Job Posting')"),
3949
- 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."),
3950
- description: z12.string().max(500).optional().describe("Workflow description (max 500 characters)"),
3951
- tags: z12.preprocess(coerceArray(true), z12.array(z12.string())).optional().describe("Tags for organizing workflows"),
3952
- 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)"),
3953
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."),
3954
- schedules: z12.preprocess(coerceArray(true), z12.array(z12.string())).optional().describe("Cron expressions for CUSTOM update interval"),
3955
- timezone: z12.string().min(1).optional().describe("IANA timezone for cron schedules, for example America/New_York. A timezone-only update preserves existing cron expressions."),
3956
- location: z12.preprocess(coerceJson(), LocationSchema).optional().describe("Scraping location: {type:'auto'} or {type:'manual', isoCode:'US'}"),
3957
- monitoring: z12.preprocess(coerceJson(), MonitoringSchema).optional().describe("Complete monitoring replacement: fields to monitor and optional AND/OR conditions. Call get_workflow first because this replaces the existing monitoring configuration."),
3958
- limit: z12.preprocess(coerceNumber(), z12.number().positive().nullable()).optional().describe("Maximum records per run; must be positive (greater than 0). Pass null to clear the limit and extract all rows.")
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.")
3959
4093
  }),
3960
4094
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
3961
4095
  }, withErrorHandling("update_workflow", async (args) => {
@@ -4031,10 +4165,12 @@ function registerTools(server, ctx, capabilities) {
4031
4165
  registerComplianceTools(server, ctx, helpers);
4032
4166
  registerVariableTools(server, ctx, helpers);
4033
4167
  registerTemplateTools(server, ctx, helpers);
4168
+ registerDocsTools(server, ctx, helpers);
4034
4169
  }
4035
4170
  var init_tools = __esm(() => {
4036
4171
  init_assistant();
4037
4172
  init_compliance();
4173
+ init_docs();
4038
4174
  init_notifications();
4039
4175
  init_observability();
4040
4176
  init_scrape();
@@ -4760,7 +4896,9 @@ async function createServer3(auth, options) {
4760
4896
  "Schema tips: Use descriptive field names and examples. Group related data under one entity.",
4761
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.",
4762
4898
  "",
4763
- "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."
4764
4902
  ].join(`
4765
4903
  `)
4766
4904
  });
@@ -5238,7 +5376,9 @@ async function createServer(auth, options) {
5238
5376
  "Schema tips: Use descriptive field names and examples. Group related data under one entity.",
5239
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.",
5240
5378
  "",
5241
- "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."
5242
5382
  ].join(`
5243
5383
  `)
5244
5384
  });