@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.
- package/README.md +15 -1
- package/dist/index.js +448 -308
- 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.
|
|
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
|
|
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:
|
|
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:
|
|
1528
|
-
name:
|
|
1529
|
-
config:
|
|
1530
|
-
recipients:
|
|
1531
|
-
webhookUrl:
|
|
1532
|
-
httpMethod:
|
|
1533
|
-
slackChannelId:
|
|
1534
|
-
slackChannelName:
|
|
1535
|
-
auth:
|
|
1536
|
-
type:
|
|
1537
|
-
token:
|
|
1538
|
-
username:
|
|
1539
|
-
password:
|
|
1540
|
-
headers:
|
|
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:
|
|
1588
|
-
confirmed:
|
|
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:
|
|
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:
|
|
1638
|
-
events:
|
|
1639
|
-
channels:
|
|
1640
|
-
email:
|
|
1641
|
-
recipients:
|
|
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:
|
|
1644
|
-
url:
|
|
1645
|
-
httpMethod:
|
|
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:
|
|
1649
|
-
webhookUrl:
|
|
1650
|
-
slackChannelId:
|
|
1651
|
-
slackChannelName:
|
|
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:
|
|
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:
|
|
1716
|
-
confirmed:
|
|
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
|
|
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:
|
|
1816
|
-
includeWorkflows:
|
|
1817
|
-
includeDailySeries:
|
|
1818
|
-
limit:
|
|
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:
|
|
1877
|
-
userId:
|
|
1878
|
-
startDate:
|
|
1879
|
-
endDate:
|
|
1880
|
-
relativeTime:
|
|
1881
|
-
eventTypes:
|
|
1882
|
-
interfaces:
|
|
1883
|
-
resourceTypes:
|
|
1884
|
-
includeDetails:
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
2310
|
-
title:
|
|
2311
|
-
description:
|
|
2312
|
-
category:
|
|
2313
|
-
jobId:
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
2511
|
-
description:
|
|
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:
|
|
2532
|
-
name:
|
|
2533
|
-
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:
|
|
2550
|
-
confirmed:
|
|
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:
|
|
2575
|
-
description:
|
|
2576
|
-
fieldType:
|
|
2577
|
-
example:
|
|
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:
|
|
2580
|
-
categories:
|
|
2581
|
-
title:
|
|
2582
|
-
definition:
|
|
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:
|
|
2608
|
-
prompt:
|
|
2609
|
-
schemaId:
|
|
2610
|
-
schemaFields:
|
|
2611
|
-
schemaEntity:
|
|
2612
|
-
schemaValidationRules:
|
|
2613
|
-
notifications:
|
|
2614
|
-
eventType:
|
|
2615
|
-
eventConfiguration:
|
|
2616
|
-
enabled:
|
|
2617
|
-
channelIds:
|
|
2618
|
-
channels:
|
|
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:
|
|
2621
|
-
interval:
|
|
2622
|
-
schedules:
|
|
2623
|
-
type:
|
|
2624
|
-
interval:
|
|
2625
|
-
expressions:
|
|
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:
|
|
2656
|
-
name:
|
|
2657
|
-
description:
|
|
2658
|
-
templateId:
|
|
2659
|
-
includeParts:
|
|
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:
|
|
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:
|
|
2692
|
-
workflowIds:
|
|
2693
|
-
force:
|
|
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:
|
|
2721
|
-
targetVersion:
|
|
2722
|
-
workflowIds:
|
|
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:
|
|
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:
|
|
2754
|
-
workflowIds:
|
|
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
|
|
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:
|
|
2785
|
-
value:
|
|
2786
|
-
dataType:
|
|
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:
|
|
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:
|
|
2830
|
-
key:
|
|
2831
|
-
value:
|
|
2832
|
-
dataType:
|
|
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:
|
|
2849
|
-
confirmed:
|
|
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
|
|
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
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
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:
|
|
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.
|
|
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
|
-
|
|
3347
|
-
|
|
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
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
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:
|
|
3405
|
-
search:
|
|
3406
|
-
tags:
|
|
3407
|
-
monitoring:
|
|
3408
|
-
updateInterval:
|
|
3409
|
-
templateId:
|
|
3410
|
-
statusFilters:
|
|
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:
|
|
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:
|
|
3571
|
-
instructions:
|
|
3572
|
-
threadId:
|
|
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:
|
|
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:
|
|
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:
|
|
3662
|
-
status:
|
|
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:
|
|
3707
|
-
limit:
|
|
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:
|
|
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:
|
|
3740
|
-
order:
|
|
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:
|
|
3790
|
-
format:
|
|
3791
|
-
runId:
|
|
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:
|
|
3794
|
-
order:
|
|
3795
|
-
rowIds:
|
|
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:
|
|
3830
|
-
startDate:
|
|
3831
|
-
endDate:
|
|
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:
|
|
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:
|
|
3881
|
-
confirmed:
|
|
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:
|
|
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:
|
|
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:
|
|
3946
|
-
name:
|
|
3947
|
-
urls:
|
|
3948
|
-
entity:
|
|
3949
|
-
schema:
|
|
3950
|
-
description:
|
|
3951
|
-
tags:
|
|
3952
|
-
userPrompt:
|
|
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:
|
|
3955
|
-
timezone:
|
|
3956
|
-
location:
|
|
3957
|
-
monitoring:
|
|
3958
|
-
limit:
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
});
|