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