@kadoa/mcp 0.7.0-rc.1 → 0.7.0
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 +2 -2
- package/dist/index.js +1093 -472
- 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.0
|
|
20
|
+
version: "0.7.0",
|
|
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",
|
|
@@ -104,6 +104,11 @@ function createKadoaClient(auth) {
|
|
|
104
104
|
var MCP_SCRAPE_REQUEST_TIMEOUT_MS = 310000;
|
|
105
105
|
var init_client = () => {};
|
|
106
106
|
|
|
107
|
+
// src/default-product.ts
|
|
108
|
+
function parseDefaultProductType(value) {
|
|
109
|
+
return value === "realtime" || value === "workflow" ? value : undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
107
112
|
// src/error-summary.ts
|
|
108
113
|
function redactCredentialText(value) {
|
|
109
114
|
return value.replace(/Bearer\s+[^\s,;]+/gi, "Bearer [redacted]").replace(/\b(?:kcs_|tk-)[A-Za-z0-9._~-]+\b/g, "[redacted]").replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, "[redacted]");
|
|
@@ -207,19 +212,252 @@ function withVariableDashboardUrl(variable) {
|
|
|
207
212
|
return { ...variable, dashboardUrl: variableSettingsDashboardUrl() };
|
|
208
213
|
}
|
|
209
214
|
|
|
215
|
+
// src/tools/shared/output-schemas.ts
|
|
216
|
+
import { z } from "zod";
|
|
217
|
+
var apiField, nullableString, PaginationOutput, WorkflowRowOutput, ListWorkflowsOutput, GetWorkflowOutput, FetchDataOutput, ASSISTANT_ACTIVITIES, WorkflowAssistantOutput, AssistantTimelineOutput, AssistantAnswerOutput, AssistantControlOutput, WorkflowStrategyOutput;
|
|
218
|
+
var init_output_schemas = __esm(() => {
|
|
219
|
+
apiField = z.unknown().optional();
|
|
220
|
+
nullableString = z.string().nullable();
|
|
221
|
+
PaginationOutput = z.object({
|
|
222
|
+
totalCount: z.number().optional(),
|
|
223
|
+
hasMore: z.boolean(),
|
|
224
|
+
limit: z.number().optional(),
|
|
225
|
+
skip: z.number().optional(),
|
|
226
|
+
offset: z.number().optional(),
|
|
227
|
+
page: z.number().optional(),
|
|
228
|
+
totalPages: z.number().optional(),
|
|
229
|
+
nextCursor: nullableString.optional(),
|
|
230
|
+
next: z.record(z.string(), z.unknown()).optional()
|
|
231
|
+
}).loose();
|
|
232
|
+
WorkflowRowOutput = z.object({
|
|
233
|
+
id: apiField,
|
|
234
|
+
name: apiField,
|
|
235
|
+
dashboardUrl: z.string(),
|
|
236
|
+
status: z.string(),
|
|
237
|
+
state: nullableString,
|
|
238
|
+
displayState: nullableString,
|
|
239
|
+
runState: nullableString,
|
|
240
|
+
urls: apiField,
|
|
241
|
+
totalRecords: apiField,
|
|
242
|
+
startedAt: apiField,
|
|
243
|
+
finishedAt: apiField,
|
|
244
|
+
createdAt: apiField,
|
|
245
|
+
assistant: z.object({
|
|
246
|
+
eligible: z.boolean(),
|
|
247
|
+
dataStale: z.boolean(),
|
|
248
|
+
session: z.object({ id: z.string(), threadId: apiField, status: apiField }).loose().nullable()
|
|
249
|
+
}).loose(),
|
|
250
|
+
template: z.object({ id: z.string(), pinnedVersion: apiField, latestVersion: apiField, isOutdated: z.boolean() }).loose().nullable(),
|
|
251
|
+
realtime: z.object({ enabled: z.boolean(), lastDataChangedAt: apiField, health: apiField }).loose(),
|
|
252
|
+
awaitingUserInput: apiField
|
|
253
|
+
}).loose();
|
|
254
|
+
ListWorkflowsOutput = z.object({ workflows: z.array(WorkflowRowOutput), pagination: PaginationOutput }).loose();
|
|
255
|
+
GetWorkflowOutput = z.object({
|
|
256
|
+
id: z.string(),
|
|
257
|
+
name: apiField,
|
|
258
|
+
dashboardUrl: z.string(),
|
|
259
|
+
description: apiField,
|
|
260
|
+
status: z.string(),
|
|
261
|
+
state: nullableString,
|
|
262
|
+
displayState: nullableString,
|
|
263
|
+
runState: nullableString,
|
|
264
|
+
urls: apiField,
|
|
265
|
+
prompt: nullableString,
|
|
266
|
+
intentSource: z.enum(["extraction_spec", "legacy_prompt"]).nullable(),
|
|
267
|
+
extractionSpec: apiField,
|
|
268
|
+
entity: apiField,
|
|
269
|
+
schema: apiField,
|
|
270
|
+
navigationMode: apiField,
|
|
271
|
+
dataStale: apiField,
|
|
272
|
+
assistantEligible: z.boolean(),
|
|
273
|
+
assistantSessionId: apiField,
|
|
274
|
+
assistantThreadId: apiField,
|
|
275
|
+
assistantSessionStatus: apiField,
|
|
276
|
+
assistant: z.object({ eligible: z.boolean(), sessionId: apiField, threadId: apiField, sessionStatus: apiField }).loose(),
|
|
277
|
+
template: z.object({
|
|
278
|
+
id: apiField,
|
|
279
|
+
name: apiField,
|
|
280
|
+
version: apiField,
|
|
281
|
+
controlledParts: z.array(z.string()),
|
|
282
|
+
hasSchemaValidationRules: apiField
|
|
283
|
+
}).loose().nullable(),
|
|
284
|
+
updateInterval: apiField,
|
|
285
|
+
schedules: apiField,
|
|
286
|
+
scheduleTimezone: apiField,
|
|
287
|
+
nextInvocation: apiField,
|
|
288
|
+
location: apiField,
|
|
289
|
+
limit: apiField,
|
|
290
|
+
tags: apiField,
|
|
291
|
+
totalRecords: apiField,
|
|
292
|
+
job: z.object({ id: apiField }).loose().nullable(),
|
|
293
|
+
startedAt: apiField,
|
|
294
|
+
finishedAt: apiField,
|
|
295
|
+
estimatedFinishedAtUtc: apiField,
|
|
296
|
+
errors: apiField,
|
|
297
|
+
createdAt: apiField,
|
|
298
|
+
notificationConfig: apiField,
|
|
299
|
+
channelCounts: apiField,
|
|
300
|
+
dataValidation: apiField,
|
|
301
|
+
validationStatistics: apiField,
|
|
302
|
+
realtime: z.object({ lastDataChangedAt: apiField, observerHealth: apiField }).loose().nullable()
|
|
303
|
+
}).loose();
|
|
304
|
+
FetchDataOutput = z.object({
|
|
305
|
+
data: z.array(z.record(z.string(), z.unknown())),
|
|
306
|
+
pagination: PaginationOutput,
|
|
307
|
+
totalCount: z.number().optional(),
|
|
308
|
+
hint: z.string().optional()
|
|
309
|
+
}).loose();
|
|
310
|
+
ASSISTANT_ACTIVITIES = [
|
|
311
|
+
"unsupported",
|
|
312
|
+
"not_started",
|
|
313
|
+
"working",
|
|
314
|
+
"waiting_for_input",
|
|
315
|
+
"idle",
|
|
316
|
+
"interrupted",
|
|
317
|
+
"failed",
|
|
318
|
+
"closed",
|
|
319
|
+
"unknown"
|
|
320
|
+
];
|
|
321
|
+
WorkflowAssistantOutput = z.object({
|
|
322
|
+
workflowId: z.string(),
|
|
323
|
+
assistantEligible: z.boolean(),
|
|
324
|
+
sessionId: nullableString,
|
|
325
|
+
threadId: apiField,
|
|
326
|
+
sessionStatus: apiField,
|
|
327
|
+
activity: z.enum(ASSISTANT_ACTIVITIES),
|
|
328
|
+
canAcceptUpdate: z.boolean(),
|
|
329
|
+
isTerminal: z.boolean(),
|
|
330
|
+
paused: apiField,
|
|
331
|
+
pause: apiField,
|
|
332
|
+
pendingQuestion: apiField
|
|
333
|
+
}).loose();
|
|
334
|
+
AssistantTimelineOutput = z.object({
|
|
335
|
+
workflowId: z.string(),
|
|
336
|
+
sessionId: apiField,
|
|
337
|
+
items: z.array(z.unknown()),
|
|
338
|
+
pagination: PaginationOutput,
|
|
339
|
+
dashboardUrl: z.string()
|
|
340
|
+
}).loose();
|
|
341
|
+
AssistantAnswerOutput = z.object({
|
|
342
|
+
success: z.literal(true),
|
|
343
|
+
workflowId: z.string(),
|
|
344
|
+
sessionId: z.string(),
|
|
345
|
+
threadId: apiField,
|
|
346
|
+
questionId: z.string(),
|
|
347
|
+
message: z.string()
|
|
348
|
+
}).loose();
|
|
349
|
+
AssistantControlOutput = z.object({
|
|
350
|
+
success: z.boolean(),
|
|
351
|
+
status: z.literal("accepted"),
|
|
352
|
+
action: z.enum(["interrupt", "resume", "stop"]),
|
|
353
|
+
workflowId: z.string(),
|
|
354
|
+
sessionId: z.string(),
|
|
355
|
+
delivery: apiField,
|
|
356
|
+
jobId: apiField,
|
|
357
|
+
message: z.string()
|
|
358
|
+
}).loose();
|
|
359
|
+
WorkflowStrategyOutput = z.object({
|
|
360
|
+
workflowId: z.string(),
|
|
361
|
+
sessionId: nullableString,
|
|
362
|
+
source: z.enum(["assistant_build", "workflow_metadata"]).nullable(),
|
|
363
|
+
status: z.enum(["ready", "building", "unavailable"]),
|
|
364
|
+
strategy: apiField,
|
|
365
|
+
message: z.string().optional()
|
|
366
|
+
}).loose();
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// src/tools/shared/pagination.ts
|
|
370
|
+
import { z as z2 } from "zod";
|
|
371
|
+
function limitInput(options) {
|
|
372
|
+
const range = `limit must be a whole number from 1 to ${options.max}`;
|
|
373
|
+
const overMax = `${range}. ${options.overMaxHint ?? "Page with pagination.next."}`;
|
|
374
|
+
return z2.preprocess(coerceNumber(), z2.number().int(range).min(1, range).max(options.max, overMax)).optional().describe(`${options.noun} per page, 1-${options.max} (default ${options.defaultLimit})`);
|
|
375
|
+
}
|
|
376
|
+
function offsetInput(param, noun) {
|
|
377
|
+
const range = `${param} must be a whole number from 0 to ${MAX_OFFSET}`;
|
|
378
|
+
return z2.preprocess(coerceNumber(), z2.number().int(range).min(0, range).max(MAX_OFFSET, range)).optional().describe(`Number of ${noun} to skip (default 0)`);
|
|
379
|
+
}
|
|
380
|
+
function pageInput() {
|
|
381
|
+
const range = `page must be a whole number from 1 to ${MAX_PAGE}`;
|
|
382
|
+
return z2.preprocess(coerceNumber(), z2.number().int(range).min(1, range).max(MAX_PAGE, range)).optional().describe("Page number, 1-based (default 1)");
|
|
383
|
+
}
|
|
384
|
+
function nextArgs(args, patch) {
|
|
385
|
+
const next = {};
|
|
386
|
+
for (const [key, value] of Object.entries({ ...args, ...patch })) {
|
|
387
|
+
if (value !== undefined)
|
|
388
|
+
next[key] = value;
|
|
389
|
+
}
|
|
390
|
+
return next;
|
|
391
|
+
}
|
|
392
|
+
function withTotal(totalCount, hasMore) {
|
|
393
|
+
return totalCount !== undefined ? { totalCount, hasMore } : { hasMore };
|
|
394
|
+
}
|
|
395
|
+
function offsetPage(options) {
|
|
396
|
+
const nextOffset = options.offset + options.returned;
|
|
397
|
+
const more = options.hasMore ?? (options.totalCount !== undefined ? nextOffset < options.totalCount : options.returned >= options.limit);
|
|
398
|
+
const hasMore = options.returned > 0 && more;
|
|
399
|
+
const meta = withTotal(options.totalCount, hasMore);
|
|
400
|
+
meta.limit = options.limit;
|
|
401
|
+
meta[options.param] = options.offset;
|
|
402
|
+
if (hasMore)
|
|
403
|
+
meta.next = nextArgs(options.args, { [options.param]: nextOffset, limit: options.limit });
|
|
404
|
+
return meta;
|
|
405
|
+
}
|
|
406
|
+
function numberedPage(options) {
|
|
407
|
+
const more = options.totalPages !== undefined ? options.page < options.totalPages : options.totalCount !== undefined ? options.page * options.limit < options.totalCount : options.returned >= options.limit;
|
|
408
|
+
const hasMore = options.returned > 0 && more;
|
|
409
|
+
const meta = withTotal(options.totalCount, hasMore);
|
|
410
|
+
meta.limit = options.limit;
|
|
411
|
+
meta.page = options.page;
|
|
412
|
+
if (options.totalPages !== undefined)
|
|
413
|
+
meta.totalPages = options.totalPages;
|
|
414
|
+
if (hasMore)
|
|
415
|
+
meta.next = nextArgs(options.args, { page: options.page + 1, limit: options.limit });
|
|
416
|
+
return meta;
|
|
417
|
+
}
|
|
418
|
+
function cursorPage(options) {
|
|
419
|
+
const cursor = typeof options.nextCursor === "string" && options.nextCursor.length > 0 ? options.nextCursor : null;
|
|
420
|
+
const meta = { hasMore: options.hasMore && cursor !== null, nextCursor: cursor };
|
|
421
|
+
if (meta.hasMore)
|
|
422
|
+
meta.next = nextArgs(options.args, { cursor });
|
|
423
|
+
return meta;
|
|
424
|
+
}
|
|
425
|
+
function completeList(totalCount) {
|
|
426
|
+
return { totalCount, hasMore: false };
|
|
427
|
+
}
|
|
428
|
+
var MAX_OFFSET = 1e6, MAX_PAGE = 1e5;
|
|
429
|
+
var init_pagination = () => {};
|
|
430
|
+
|
|
210
431
|
// src/tools/shared/results.ts
|
|
432
|
+
function asStructuredObject(data) {
|
|
433
|
+
return data !== null && !Array.isArray(data) ? data : { result: data };
|
|
434
|
+
}
|
|
435
|
+
function structuredResult(data, extra = []) {
|
|
436
|
+
const structuredContent = asStructuredObject(data);
|
|
437
|
+
return {
|
|
438
|
+
structuredContent,
|
|
439
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent) }, ...extra]
|
|
440
|
+
};
|
|
441
|
+
}
|
|
211
442
|
function jsonResult(data) {
|
|
212
|
-
return
|
|
443
|
+
return structuredResult(data);
|
|
444
|
+
}
|
|
445
|
+
function textOnlyResult(data, extra = []) {
|
|
446
|
+
const structuredContent = asStructuredObject(data);
|
|
447
|
+
return { content: [{ type: "text", text: JSON.stringify(structuredContent) }, ...extra] };
|
|
213
448
|
}
|
|
214
|
-
function errorResult(message) {
|
|
449
|
+
function errorResult(message, details = {}) {
|
|
215
450
|
return {
|
|
216
|
-
content: [{ type: "text", text: JSON.stringify({ error: message
|
|
451
|
+
content: [{ type: "text", text: JSON.stringify({ error: message, ...details }) }],
|
|
217
452
|
isError: true
|
|
218
453
|
};
|
|
219
454
|
}
|
|
455
|
+
function withExtraContent(result, ...blocks) {
|
|
456
|
+
return { ...result, content: [...result.content, ...blocks] };
|
|
457
|
+
}
|
|
220
458
|
|
|
221
459
|
// src/tools/shared/schemas.ts
|
|
222
|
-
import { z } from "zod";
|
|
460
|
+
import { z as z3 } from "zod";
|
|
223
461
|
function isPlainObject(value) {
|
|
224
462
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
225
463
|
}
|
|
@@ -281,7 +519,7 @@ function stampSchemaValidationRules(rules, attribution, previous) {
|
|
|
281
519
|
return Object.fromEntries(Object.entries(rules).map(([name, field]) => [name, stampField(field, previousFields[name])]));
|
|
282
520
|
}
|
|
283
521
|
function strictSchema(shape) {
|
|
284
|
-
return
|
|
522
|
+
return z3.object(shape).strict();
|
|
285
523
|
}
|
|
286
524
|
function withoutDefIds(schema) {
|
|
287
525
|
const jsonSchema = schema["~standard"].jsonSchema;
|
|
@@ -317,7 +555,7 @@ function normalizeDataFilters(val, ctx) {
|
|
|
317
555
|
code: "custom",
|
|
318
556
|
message: "Grouped filters ({logicalOperator, conditions}) are not supported. Send a flat array of " + "{field, operator, value} filters instead; multiple filters are combined with OR."
|
|
319
557
|
});
|
|
320
|
-
return
|
|
558
|
+
return z3.NEVER;
|
|
321
559
|
}
|
|
322
560
|
return [v];
|
|
323
561
|
}
|
|
@@ -325,7 +563,7 @@ function normalizeDataFilters(val, ctx) {
|
|
|
325
563
|
}
|
|
326
564
|
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;
|
|
327
565
|
var init_schemas = __esm(() => {
|
|
328
|
-
UpdateIntervalSchema =
|
|
566
|
+
UpdateIntervalSchema = z3.enum([
|
|
329
567
|
"ONLY_ONCE",
|
|
330
568
|
"EVERY_10_MINUTES",
|
|
331
569
|
"HALF_HOURLY",
|
|
@@ -359,8 +597,8 @@ var init_schemas = __esm(() => {
|
|
|
359
597
|
"credits_low",
|
|
360
598
|
"free_trial_ending"
|
|
361
599
|
];
|
|
362
|
-
NotificationEventTypeSchema =
|
|
363
|
-
SchemaFieldDataTypeSchema =
|
|
600
|
+
NotificationEventTypeSchema = z3.enum(NOTIFICATION_EVENT_TYPES);
|
|
601
|
+
SchemaFieldDataTypeSchema = z3.enum([
|
|
364
602
|
"STRING",
|
|
365
603
|
"NUMBER",
|
|
366
604
|
"BOOLEAN",
|
|
@@ -373,61 +611,61 @@ var init_schemas = __esm(() => {
|
|
|
373
611
|
"ARRAY"
|
|
374
612
|
]);
|
|
375
613
|
SchemaFieldShape = {
|
|
376
|
-
name:
|
|
377
|
-
description:
|
|
378
|
-
example:
|
|
614
|
+
name: z3.string().describe("Field name"),
|
|
615
|
+
description: z3.string().optional().describe("What this field contains"),
|
|
616
|
+
example: z3.string().describe("Example value"),
|
|
379
617
|
dataType: SchemaFieldDataTypeSchema.optional().describe("Data type for the field"),
|
|
380
|
-
isKey:
|
|
618
|
+
isKey: z3.preprocess(coerceBoolean(), z3.boolean()).optional().describe("Marks this field as a key field - the stable identity used to match records across runs. Change detection diffs records by their key fields, so a monitored or real-time workflow without one cannot tell an updated record from a new one. Set it on whatever uniquely identifies a row (a detail URL, an ID, a ticker).")
|
|
381
619
|
};
|
|
382
|
-
SchemaValidationPresenceRule =
|
|
383
|
-
target:
|
|
620
|
+
SchemaValidationPresenceRule = z3.object({
|
|
621
|
+
target: z3.number().int().min(0).max(100).step(20).describe("Expected percentage of rows with a value")
|
|
384
622
|
}).strict().meta({ id: "PresenceRule" });
|
|
385
|
-
SchemaValidationUniquenessRule =
|
|
386
|
-
target:
|
|
623
|
+
SchemaValidationUniquenessRule = z3.object({
|
|
624
|
+
target: z3.number().int().min(0).max(100).step(20).describe("Expected percentage of rows with a unique value")
|
|
387
625
|
}).strict().meta({ id: "UniquenessRule" });
|
|
388
|
-
SchemaValidationBoundRule =
|
|
389
|
-
value:
|
|
626
|
+
SchemaValidationBoundRule = z3.object({
|
|
627
|
+
value: z3.number().int().min(0).describe("Length or count bound")
|
|
390
628
|
}).strict().meta({ id: "BoundRule" });
|
|
391
|
-
SchemaValidationStringFormatRule =
|
|
392
|
-
|
|
393
|
-
kind:
|
|
394
|
-
charset:
|
|
395
|
-
|
|
396
|
-
kind:
|
|
397
|
-
preset:
|
|
629
|
+
SchemaValidationStringFormatRule = z3.discriminatedUnion("kind", [
|
|
630
|
+
z3.object({
|
|
631
|
+
kind: z3.literal("FREE_TEXT"),
|
|
632
|
+
charset: z3.discriminatedUnion("kind", [
|
|
633
|
+
z3.object({
|
|
634
|
+
kind: z3.literal("PRESET"),
|
|
635
|
+
preset: z3.enum(["natural_language", "alphanumeric", "alpha"])
|
|
398
636
|
}).strict()
|
|
399
637
|
])
|
|
400
638
|
}).strict(),
|
|
401
|
-
|
|
402
|
-
kind:
|
|
403
|
-
source:
|
|
404
|
-
|
|
405
|
-
kind:
|
|
406
|
-
preset:
|
|
639
|
+
z3.object({
|
|
640
|
+
kind: z3.literal("FORMAT"),
|
|
641
|
+
source: z3.discriminatedUnion("kind", [
|
|
642
|
+
z3.object({
|
|
643
|
+
kind: z3.literal("PRESET"),
|
|
644
|
+
preset: z3.enum(["url", "email", "phone", "date", "datetime", "time", "uuid", "slug"])
|
|
407
645
|
}).strict(),
|
|
408
|
-
|
|
409
|
-
kind:
|
|
410
|
-
pattern:
|
|
646
|
+
z3.object({
|
|
647
|
+
kind: z3.literal("CUSTOM"),
|
|
648
|
+
pattern: z3.string().min(1).describe("Regular expression pattern")
|
|
411
649
|
}).strict()
|
|
412
650
|
])
|
|
413
651
|
}).strict(),
|
|
414
|
-
|
|
415
|
-
kind:
|
|
416
|
-
source:
|
|
417
|
-
|
|
418
|
-
kind:
|
|
419
|
-
preset:
|
|
652
|
+
z3.object({
|
|
653
|
+
kind: z3.literal("LIST"),
|
|
654
|
+
source: z3.discriminatedUnion("kind", [
|
|
655
|
+
z3.object({
|
|
656
|
+
kind: z3.literal("PRESET"),
|
|
657
|
+
preset: z3.enum(["language2", "country2", "country3", "currency3", "month3", "usState2"])
|
|
420
658
|
}).strict(),
|
|
421
|
-
|
|
422
|
-
kind:
|
|
423
|
-
values:
|
|
659
|
+
z3.object({
|
|
660
|
+
kind: z3.literal("CUSTOM"),
|
|
661
|
+
values: z3.array(z3.string().min(1)).min(1).describe("Allowed string values")
|
|
424
662
|
}).strict()
|
|
425
663
|
])
|
|
426
664
|
}).strict()
|
|
427
665
|
]);
|
|
428
|
-
SchemaValidationFieldRulesSchema =
|
|
429
|
-
|
|
430
|
-
kind:
|
|
666
|
+
SchemaValidationFieldRulesSchema = z3.lazy(() => z3.discriminatedUnion("kind", [
|
|
667
|
+
z3.object({
|
|
668
|
+
kind: z3.literal("STRING"),
|
|
431
669
|
presence: SchemaValidationPresenceRule.optional(),
|
|
432
670
|
uniqueness: SchemaValidationUniquenessRule.optional(),
|
|
433
671
|
minLength: SchemaValidationBoundRule.optional(),
|
|
@@ -436,27 +674,27 @@ var init_schemas = __esm(() => {
|
|
|
436
674
|
maxHtmlElements: SchemaValidationBoundRule.optional(),
|
|
437
675
|
format: SchemaValidationStringFormatRule.optional()
|
|
438
676
|
}).strict(),
|
|
439
|
-
|
|
440
|
-
kind:
|
|
677
|
+
z3.object({
|
|
678
|
+
kind: z3.literal("NUMBER"),
|
|
441
679
|
presence: SchemaValidationPresenceRule.optional(),
|
|
442
680
|
uniqueness: SchemaValidationUniquenessRule.optional(),
|
|
443
|
-
minimum:
|
|
444
|
-
maximum:
|
|
445
|
-
maxDecimalPlaces:
|
|
681
|
+
minimum: z3.object({ value: z3.number().describe("Minimum numeric value") }).strict().optional(),
|
|
682
|
+
maximum: z3.object({ value: z3.number().describe("Maximum numeric value") }).strict().optional(),
|
|
683
|
+
maxDecimalPlaces: z3.object({ value: z3.number().int().min(0).max(16) }).strict().optional()
|
|
446
684
|
}).strict(),
|
|
447
|
-
|
|
448
|
-
kind:
|
|
685
|
+
z3.object({
|
|
686
|
+
kind: z3.literal("OTHER"),
|
|
449
687
|
presence: SchemaValidationPresenceRule.optional(),
|
|
450
688
|
uniqueness: SchemaValidationUniquenessRule.optional()
|
|
451
689
|
}).strict(),
|
|
452
|
-
|
|
453
|
-
kind:
|
|
690
|
+
z3.object({
|
|
691
|
+
kind: z3.literal("OBJECT"),
|
|
454
692
|
presence: SchemaValidationPresenceRule.optional(),
|
|
455
693
|
uniqueness: SchemaValidationUniquenessRule.optional(),
|
|
456
|
-
properties:
|
|
694
|
+
properties: z3.record(z3.string(), SchemaValidationFieldRulesSchema)
|
|
457
695
|
}).strict(),
|
|
458
|
-
|
|
459
|
-
kind:
|
|
696
|
+
z3.object({
|
|
697
|
+
kind: z3.literal("ARRAY"),
|
|
460
698
|
presence: SchemaValidationPresenceRule.optional(),
|
|
461
699
|
uniqueness: SchemaValidationUniquenessRule.optional(),
|
|
462
700
|
minItems: SchemaValidationBoundRule.optional(),
|
|
@@ -464,21 +702,21 @@ var init_schemas = __esm(() => {
|
|
|
464
702
|
items: SchemaValidationFieldRulesSchema.optional()
|
|
465
703
|
}).strict()
|
|
466
704
|
]));
|
|
467
|
-
SchemaValidationRulesSchema =
|
|
705
|
+
SchemaValidationRulesSchema = z3.record(z3.string(), SchemaValidationFieldRulesSchema);
|
|
468
706
|
SCHEMA_VALIDATION_ATTRIBUTION_KEYS = new Set(["editedBy", "editedByLabel", "editedAt"]);
|
|
469
|
-
LocationSchema =
|
|
470
|
-
type:
|
|
471
|
-
isoCode:
|
|
707
|
+
LocationSchema = z3.object({
|
|
708
|
+
type: z3.enum(["auto", "manual"]),
|
|
709
|
+
isoCode: z3.string().trim().min(2).optional()
|
|
472
710
|
}).strict().superRefine((location, ctx) => {
|
|
473
711
|
if (location.type === "manual" && !location.isoCode) {
|
|
474
712
|
ctx.addIssue({
|
|
475
|
-
code:
|
|
713
|
+
code: z3.ZodIssueCode.custom,
|
|
476
714
|
path: ["isoCode"],
|
|
477
715
|
message: "isoCode is required for manual location"
|
|
478
716
|
});
|
|
479
717
|
}
|
|
480
718
|
if (location.type === "auto" && location.isoCode) {
|
|
481
|
-
ctx.addIssue({ code:
|
|
719
|
+
ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["isoCode"], message: "omit isoCode for auto location" });
|
|
482
720
|
}
|
|
483
721
|
});
|
|
484
722
|
MonitoringValueOperators = [
|
|
@@ -500,81 +738,81 @@ var init_schemas = __esm(() => {
|
|
|
500
738
|
"WITHIN_LAST_DAYS"
|
|
501
739
|
];
|
|
502
740
|
MonitoringValuelessOperators = ["IS_NULL", "IS_NOT_NULL", "IS_EMPTY", "IS_NOT_EMPTY"];
|
|
503
|
-
MonitoringConditionOperatorSchema =
|
|
504
|
-
MonitoringConditionSchema =
|
|
505
|
-
type:
|
|
506
|
-
field:
|
|
741
|
+
MonitoringConditionOperatorSchema = z3.enum([...MonitoringValueOperators, ...MonitoringValuelessOperators]);
|
|
742
|
+
MonitoringConditionSchema = z3.object({
|
|
743
|
+
type: z3.enum(["SINGLE", "ANY"]),
|
|
744
|
+
field: z3.string().trim().min(1).optional(),
|
|
507
745
|
operator: MonitoringConditionOperatorSchema,
|
|
508
|
-
value:
|
|
746
|
+
value: z3.union([z3.string(), z3.number(), z3.boolean()]).optional()
|
|
509
747
|
}).strict().superRefine((condition, ctx) => {
|
|
510
748
|
if (condition.type === "SINGLE" && !condition.field) {
|
|
511
749
|
ctx.addIssue({
|
|
512
|
-
code:
|
|
750
|
+
code: z3.ZodIssueCode.custom,
|
|
513
751
|
path: ["field"],
|
|
514
752
|
message: "field is required for SINGLE conditions"
|
|
515
753
|
});
|
|
516
754
|
}
|
|
517
755
|
const needsValue = MonitoringValueOperators.includes(condition.operator);
|
|
518
756
|
if (needsValue && condition.value === undefined) {
|
|
519
|
-
ctx.addIssue({ code:
|
|
757
|
+
ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["value"], message: `${condition.operator} requires a value` });
|
|
520
758
|
}
|
|
521
759
|
if (!needsValue && condition.value !== undefined) {
|
|
522
760
|
ctx.addIssue({
|
|
523
|
-
code:
|
|
761
|
+
code: z3.ZodIssueCode.custom,
|
|
524
762
|
path: ["value"],
|
|
525
763
|
message: `${condition.operator} does not accept a value`
|
|
526
764
|
});
|
|
527
765
|
}
|
|
528
766
|
});
|
|
529
|
-
MonitoringSchema =
|
|
530
|
-
fields:
|
|
531
|
-
fieldName:
|
|
532
|
-
operator:
|
|
533
|
-
isKeyField:
|
|
767
|
+
MonitoringSchema = z3.object({
|
|
768
|
+
fields: z3.preprocess(coerceArray(), z3.array(z3.object({
|
|
769
|
+
fieldName: z3.string().trim().min(1),
|
|
770
|
+
operator: z3.enum(["changed", "added", "removed"]),
|
|
771
|
+
isKeyField: z3.preprocess(coerceBoolean(), z3.boolean()).optional()
|
|
534
772
|
}).strict()).min(1)),
|
|
535
|
-
conditions:
|
|
536
|
-
logicalOperator:
|
|
537
|
-
conditions:
|
|
773
|
+
conditions: z3.object({
|
|
774
|
+
logicalOperator: z3.enum(["AND", "OR"]),
|
|
775
|
+
conditions: z3.preprocess(coerceArray(), z3.array(MonitoringConditionSchema).min(1))
|
|
538
776
|
}).strict().optional()
|
|
539
777
|
}).strict();
|
|
540
778
|
urlInputShape = {
|
|
541
|
-
url:
|
|
542
|
-
urls:
|
|
779
|
+
url: z3.string().optional().describe("Single URL - prefer using 'urls' instead. If both are provided, 'urls' takes precedence."),
|
|
780
|
+
urls: z3.preprocess(coerceArray(true), z3.array(z3.string()).min(1)).optional().describe("Starting URLs for the workflow (array of strings). Also accepts a single URL string.")
|
|
543
781
|
};
|
|
544
|
-
extractionPromptInput =
|
|
782
|
+
extractionPromptInput = z3.string().optional();
|
|
545
783
|
extractionInputShape = {
|
|
546
|
-
name:
|
|
547
|
-
entity:
|
|
548
|
-
schema:
|
|
784
|
+
name: z3.string().optional().describe("Optional name for the workflow"),
|
|
785
|
+
entity: z3.string().optional().describe("Entity name for extraction (e.g., 'Product', 'Job Posting')"),
|
|
786
|
+
schema: z3.preprocess(coerceArray(), z3.array(z3.object(SchemaFieldShape).strict())).optional().describe("Extraction schema fields. If omitted, the AI agent auto-detects the schema. When you do supply fields, mark the one that identifies a record with isKey - change detection needs it.")
|
|
549
787
|
};
|
|
550
|
-
webhookAuthShape =
|
|
551
|
-
type:
|
|
552
|
-
token:
|
|
553
|
-
username:
|
|
554
|
-
password:
|
|
555
|
-
headers:
|
|
788
|
+
webhookAuthShape = z3.object({
|
|
789
|
+
type: z3.enum(["bearer", "basic", "header"]).describe("Authentication type"),
|
|
790
|
+
token: z3.string().optional().describe("Bearer token (required for 'bearer' type)"),
|
|
791
|
+
username: z3.string().optional().describe("Username (required for 'basic' type)"),
|
|
792
|
+
password: z3.string().optional().describe("Password (required for 'basic' type)"),
|
|
793
|
+
headers: z3.object({}).passthrough().optional().describe("Custom headers as key-value pairs (required for 'header' type)")
|
|
556
794
|
}).optional().describe("Authentication configuration for the webhook endpoint");
|
|
557
|
-
notificationChannelsSchema =
|
|
558
|
-
email:
|
|
559
|
-
recipients:
|
|
795
|
+
notificationChannelsSchema = z3.object({
|
|
796
|
+
email: z3.object({
|
|
797
|
+
recipients: z3.array(z3.string().email()).optional().describe("Email addresses to notify. Omit to use account default email.")
|
|
560
798
|
}).optional().describe("Send email notifications. Pass {} for account default email, or {recipients: [...]} for custom addresses."),
|
|
561
|
-
webhook:
|
|
562
|
-
url:
|
|
563
|
-
httpMethod:
|
|
799
|
+
webhook: z3.object({
|
|
800
|
+
url: z3.string().url().describe("Webhook endpoint URL"),
|
|
801
|
+
httpMethod: z3.enum(["POST", "GET", "PUT", "PATCH"]).optional().describe("HTTP method (defaults to POST)"),
|
|
564
802
|
auth: webhookAuthShape
|
|
565
803
|
}).optional().describe("Send notifications via HTTP webhook"),
|
|
566
|
-
slack:
|
|
567
|
-
webhookUrl:
|
|
568
|
-
slackChannelId:
|
|
569
|
-
slackChannelName:
|
|
804
|
+
slack: z3.object({
|
|
805
|
+
webhookUrl: z3.string().url().optional().describe("Slack incoming webhook URL (legacy)"),
|
|
806
|
+
slackChannelId: z3.string().optional().describe("Slack channel ID from OAuth integration (e.g. C01ABCDEF)"),
|
|
807
|
+
slackChannelName: z3.string().optional().describe("Slack channel name (e.g. #alerts)")
|
|
570
808
|
}).optional().describe("Send notifications to a Slack channel. Use slackChannelId/slackChannelName for OAuth integration, or webhookUrl for legacy incoming webhooks."),
|
|
571
|
-
websocket:
|
|
809
|
+
websocket: z3.boolean().optional().describe("Enable WebSocket notifications for programmatic real-time consumption")
|
|
572
810
|
});
|
|
573
811
|
notificationsInputShape = {
|
|
574
|
-
notifications:
|
|
812
|
+
notifications: z3.preprocess(coerceJson(), notificationChannelsSchema.optional()).optional().describe("Optional notification channels to alert when data changes.")
|
|
575
813
|
};
|
|
576
814
|
requiredNotificationsInputShape = {
|
|
577
|
-
notifications:
|
|
815
|
+
notifications: z3.preprocess(coerceJson(), notificationChannelsSchema).describe("Notification channels to alert when data changes.")
|
|
578
816
|
};
|
|
579
817
|
DATA_FILTER_OPERATORS = [
|
|
580
818
|
"EQUALS",
|
|
@@ -599,10 +837,10 @@ var init_schemas = __esm(() => {
|
|
|
599
837
|
];
|
|
600
838
|
DATA_FILTER_VALUELESS_OPERATORS = new Set(["IS_NULL", "IS_NOT_NULL", "IS_EMPTY", "IS_NOT_EMPTY"]);
|
|
601
839
|
DATA_FILTER_LIST_OPERATORS = new Set(["IN", "NOT_IN"]);
|
|
602
|
-
DataFilterSchema =
|
|
603
|
-
field:
|
|
604
|
-
operator:
|
|
605
|
-
value:
|
|
840
|
+
DataFilterSchema = z3.object({
|
|
841
|
+
field: z3.string().trim().min(1).describe("Field name from the workflow schema; one nested level is allowed, e.g. classification.match"),
|
|
842
|
+
operator: z3.preprocess((val) => typeof val === "string" ? val.trim().toUpperCase() : val, z3.enum(DATA_FILTER_OPERATORS)),
|
|
843
|
+
value: z3.union([z3.string(), z3.number(), z3.boolean(), z3.array(z3.union([z3.string(), z3.number()]))]).optional().describe("Omit for IS_NULL, IS_NOT_NULL, IS_EMPTY, IS_NOT_EMPTY; an array for IN/NOT_IN; days for WITHIN_LAST_DAYS")
|
|
606
844
|
}).strict().superRefine((filter, ctx) => {
|
|
607
845
|
if (DATA_FILTER_VALUELESS_OPERATORS.has(filter.operator)) {
|
|
608
846
|
if (filter.value !== undefined) {
|
|
@@ -627,12 +865,12 @@ var init_schemas = __esm(() => {
|
|
|
627
865
|
});
|
|
628
866
|
}
|
|
629
867
|
});
|
|
630
|
-
dataFiltersInput =
|
|
868
|
+
dataFiltersInput = z3.preprocess(normalizeDataFilters, z3.array(DataFilterSchema).optional()).optional().describe('Row filters, e.g. [{"field":"title","operator":"CONTAINS","value":"Manager"}]. Multiple filters are combined with OR. BEFORE, AFTER and WITHIN_LAST_DAYS only apply to DATE/DATETIME fields.');
|
|
631
869
|
});
|
|
632
870
|
|
|
633
871
|
// src/tools/assistant.ts
|
|
634
872
|
import { FREEFORM_ASSISTANT_ANSWER_KEY } from "@kadoa/node-sdk";
|
|
635
|
-
import { z as
|
|
873
|
+
import { z as z4 } from "zod";
|
|
636
874
|
function isAssistantSessionResumable(status) {
|
|
637
875
|
return status != null && RESUMABLE_ASSISTANT_STATUSES.has(status);
|
|
638
876
|
}
|
|
@@ -700,8 +938,9 @@ function registerAssistantTools(server, ctx, helpers) {
|
|
|
700
938
|
title: "Get Workflow Assistant",
|
|
701
939
|
description: "Get the current Assistant lifecycle for a workflow, including whether it is working, waiting for input, idle, interrupted, or closed and any pending customer question. Works for one-time or scheduled extraction workflows and realtime monitoring workflows.",
|
|
702
940
|
inputSchema: strictSchema({
|
|
703
|
-
workflowId:
|
|
941
|
+
workflowId: z4.string().min(1).describe("The workflow ID")
|
|
704
942
|
}),
|
|
943
|
+
outputSchema: WorkflowAssistantOutput,
|
|
705
944
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
706
945
|
}, withErrorHandling("get_workflow_assistant", async (args) => {
|
|
707
946
|
const workflow = await ctx.client.workflow.get(args.workflowId);
|
|
@@ -733,10 +972,11 @@ function registerAssistantTools(server, ctx, helpers) {
|
|
|
733
972
|
title: "Get Workflow Assistant Timeline",
|
|
734
973
|
description: "Read the customer-visible conversation history for a workflow Assistant, including user messages, Assistant messages, and clarification questions with answers. " + "Use this when the user asks what the Assistant said or asked, how a clarification was answered, or to show the latest conversation for a workflow. " + "Supports one-time or scheduled extraction workflows and realtime monitoring workflows. Use get_workflow_history instead for configuration revisions. " + "Results are chronological within each page; use nextCursor to request older items when hasMore is true.",
|
|
735
974
|
inputSchema: strictSchema({
|
|
736
|
-
workflowId:
|
|
737
|
-
cursor:
|
|
738
|
-
limit:
|
|
975
|
+
workflowId: z4.string().min(1).describe("The workflow ID"),
|
|
976
|
+
cursor: z4.string().min(1).optional().describe("Opaque nextCursor from the previous page"),
|
|
977
|
+
limit: limitInput({ noun: "History items", defaultLimit: 50, max: 100 })
|
|
739
978
|
}),
|
|
979
|
+
outputSchema: AssistantTimelineOutput,
|
|
740
980
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
741
981
|
}, withErrorHandling("get_workflow_assistant_timeline", async (args) => {
|
|
742
982
|
const timeline = await ctx.client.assistant.getTimeline(args.workflowId, {
|
|
@@ -749,8 +989,12 @@ function registerAssistantTools(server, ctx, helpers) {
|
|
|
749
989
|
return jsonResult({
|
|
750
990
|
workflowId: timeline.workflowId,
|
|
751
991
|
sessionId: timeline.sessionId,
|
|
752
|
-
items: timeline.items,
|
|
753
|
-
pagination:
|
|
992
|
+
items: timeline.items ?? [],
|
|
993
|
+
pagination: cursorPage({
|
|
994
|
+
args,
|
|
995
|
+
nextCursor: timeline.pagination?.nextCursor,
|
|
996
|
+
hasMore: timeline.pagination?.hasMore === true
|
|
997
|
+
}),
|
|
754
998
|
dashboardUrl: workflowDashboardUrl(timeline.workflowId)
|
|
755
999
|
});
|
|
756
1000
|
}));
|
|
@@ -758,11 +1002,12 @@ function registerAssistantTools(server, ctx, helpers) {
|
|
|
758
1002
|
title: "Answer Assistant Question",
|
|
759
1003
|
description: "Answer the workflow Assistant's currently pending clarification question and resume it. " + "Use `answer` for a freeform response, or `answers` for the structured key/value response described by get_workflow_assistant. Provide exactly one. The question ID must still be current.",
|
|
760
1004
|
inputSchema: strictSchema({
|
|
761
|
-
workflowId:
|
|
762
|
-
questionId:
|
|
763
|
-
answer:
|
|
764
|
-
answers:
|
|
1005
|
+
workflowId: z4.string().min(1).describe("The workflow ID"),
|
|
1006
|
+
questionId: z4.string().min(1).describe("The pending question ID returned by get_workflow_assistant"),
|
|
1007
|
+
answer: z4.string().min(1).optional().describe("Freeform answer"),
|
|
1008
|
+
answers: z4.preprocess(coerceJson(), z4.record(z4.string(), z4.string())).optional().describe("Structured answer map; keys identify questions and values contain the selected/freeform answers")
|
|
765
1009
|
}),
|
|
1010
|
+
outputSchema: AssistantAnswerOutput,
|
|
766
1011
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
767
1012
|
}, withErrorHandling("answer_workflow_assistant_question", async (args) => {
|
|
768
1013
|
const hasFreeform = args.answer != null;
|
|
@@ -804,8 +1049,9 @@ function registerAssistantTools(server, ctx, helpers) {
|
|
|
804
1049
|
title: "Interrupt Workflow Assistant",
|
|
805
1050
|
description: "Safely interrupt the active Assistant working on a workflow. This pauses Assistant reasoning/update work; it does not pause the workflow's extraction schedule. Use pause_workflow for scheduling instead.",
|
|
806
1051
|
inputSchema: strictSchema({
|
|
807
|
-
workflowId:
|
|
1052
|
+
workflowId: z4.string().min(1).describe("Workflow whose Assistant session should be interrupted")
|
|
808
1053
|
}),
|
|
1054
|
+
outputSchema: AssistantControlOutput,
|
|
809
1055
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
810
1056
|
}, withErrorHandling("interrupt_workflow_assistant", async (args) => {
|
|
811
1057
|
const workflow = await ctx.client.workflow.get(args.workflowId);
|
|
@@ -838,8 +1084,9 @@ function registerAssistantTools(server, ctx, helpers) {
|
|
|
838
1084
|
title: "Resume Workflow Assistant",
|
|
839
1085
|
description: "Resume an idle or interrupted workflow Assistant using the role and conversation persisted on its session. Works for one-time or scheduled extraction workflows and realtime monitoring workflows. This does not resume a paused workflow schedule; use approve_workflow for that.",
|
|
840
1086
|
inputSchema: strictSchema({
|
|
841
|
-
workflowId:
|
|
1087
|
+
workflowId: z4.string().min(1).describe("Workflow whose Assistant session should resume")
|
|
842
1088
|
}),
|
|
1089
|
+
outputSchema: AssistantControlOutput,
|
|
843
1090
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
844
1091
|
}, withErrorHandling("resume_workflow_assistant", async (args) => {
|
|
845
1092
|
const workflow = await ctx.client.workflow.get(args.workflowId);
|
|
@@ -872,8 +1119,9 @@ function registerAssistantTools(server, ctx, helpers) {
|
|
|
872
1119
|
title: "Stop Workflow Assistant",
|
|
873
1120
|
description: "Stop the active Assistant update for a workflow. This cancels Assistant work but does not delete, pause, or replace the workflow and does not change workflowId. Use pause_workflow separately to stop scheduled extraction runs.",
|
|
874
1121
|
inputSchema: strictSchema({
|
|
875
|
-
workflowId:
|
|
1122
|
+
workflowId: z4.string().min(1).describe("Workflow whose active Assistant session should stop")
|
|
876
1123
|
}),
|
|
1124
|
+
outputSchema: AssistantControlOutput,
|
|
877
1125
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }
|
|
878
1126
|
}, withErrorHandling("stop_workflow_assistant", async (args) => {
|
|
879
1127
|
const workflow = await ctx.client.workflow.get(args.workflowId);
|
|
@@ -905,8 +1153,9 @@ function registerAssistantTools(server, ctx, helpers) {
|
|
|
905
1153
|
title: "Get Workflow Strategy",
|
|
906
1154
|
description: "Get the current customer-safe extraction/build strategy for a workflow: source approach, endpoints, interaction style, selectors, and realtime interval when applicable. Returns building rather than an error when an Assistant session has no build strategy yet.",
|
|
907
1155
|
inputSchema: strictSchema({
|
|
908
|
-
workflowId:
|
|
1156
|
+
workflowId: z4.string().min(1).describe("The workflow ID")
|
|
909
1157
|
}),
|
|
1158
|
+
outputSchema: WorkflowStrategyOutput,
|
|
910
1159
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
911
1160
|
}, withErrorHandling("get_workflow_strategy", async (args) => {
|
|
912
1161
|
const workflow = await ctx.client.workflow.get(args.workflowId);
|
|
@@ -937,6 +1186,8 @@ function registerAssistantTools(server, ctx, helpers) {
|
|
|
937
1186
|
}
|
|
938
1187
|
var RESUMABLE_ASSISTANT_STATUSES, ACTIVE_ASSISTANT_STATUSES, IDLE_ASSISTANT_STATUSES, CLOSED_ASSISTANT_STATUSES;
|
|
939
1188
|
var init_assistant = __esm(() => {
|
|
1189
|
+
init_output_schemas();
|
|
1190
|
+
init_pagination();
|
|
940
1191
|
init_schemas();
|
|
941
1192
|
RESUMABLE_ASSISTANT_STATUSES = new Set(["pending", "created", "active", "idle", "inactive", "interrupted"]);
|
|
942
1193
|
ACTIVE_ASSISTANT_STATUSES = new Set(["pending", "created", "active"]);
|
|
@@ -945,6 +1196,7 @@ var init_assistant = __esm(() => {
|
|
|
945
1196
|
});
|
|
946
1197
|
|
|
947
1198
|
// src/notification-channels.ts
|
|
1199
|
+
import { createHash } from "node:crypto";
|
|
948
1200
|
function recipientKey(recipients) {
|
|
949
1201
|
return [...new Set(recipients.map((r) => r.trim().toLowerCase()))].sort().join(",");
|
|
950
1202
|
}
|
|
@@ -968,6 +1220,14 @@ function destinationOf(channelType, rawConfig) {
|
|
|
968
1220
|
return `url:${webhookUrl}`;
|
|
969
1221
|
return null;
|
|
970
1222
|
}
|
|
1223
|
+
function mcpChannelName(channelType, config) {
|
|
1224
|
+
const name = `mcp-${channelType.toLowerCase()}`;
|
|
1225
|
+
const slackName = config.slackChannelName;
|
|
1226
|
+
const destination = destinationOf(channelType, config) ?? (channelType === "SLACK" && typeof slackName === "string" && slackName.trim() ? `slack-name:${slackName.trim().toLowerCase()}` : null);
|
|
1227
|
+
if (destination === null)
|
|
1228
|
+
return name;
|
|
1229
|
+
return `${name}-${createHash("sha256").update(destination).digest("hex").slice(0, 8)}`;
|
|
1230
|
+
}
|
|
971
1231
|
function findReusableChannel(channels, channelType, config) {
|
|
972
1232
|
const wanted = destinationOf(channelType, config);
|
|
973
1233
|
if (wanted === null)
|
|
@@ -1012,16 +1272,23 @@ function redactChannelConfig(config) {
|
|
|
1012
1272
|
...auth !== undefined && { auth: redactAuth(auth) }
|
|
1013
1273
|
};
|
|
1014
1274
|
}
|
|
1275
|
+
function redactChannelRefs(channels) {
|
|
1276
|
+
return channels?.map((channel) => {
|
|
1277
|
+
const { config } = channel;
|
|
1278
|
+
return config === undefined ? channel : { ...channel, config: redactChannelConfig(config) };
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
var init_notification_channels = () => {};
|
|
1015
1282
|
|
|
1016
1283
|
// src/tools/notifications.ts
|
|
1017
|
-
import { z as
|
|
1284
|
+
import { z as z5 } from "zod";
|
|
1018
1285
|
function registerNotificationChannelTools(server, ctx, helpers) {
|
|
1019
1286
|
const { withErrorHandling } = helpers;
|
|
1020
1287
|
server.registerTool("list_notification_channels", {
|
|
1021
1288
|
title: "List Notification Channels",
|
|
1022
1289
|
description: "List notification channels configured for a workflow or across the workspace. Channels are destinations (email, Slack, webhook, websocket) where notifications are sent.",
|
|
1023
1290
|
inputSchema: strictSchema({
|
|
1024
|
-
workflowId:
|
|
1291
|
+
workflowId: z5.string().optional().describe("Filter channels linked to a specific workflow. Omit to list all workspace channels.")
|
|
1025
1292
|
}),
|
|
1026
1293
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
1027
1294
|
}, withErrorHandling("list_notification_channels", async (args) => {
|
|
@@ -1036,27 +1303,28 @@ function registerNotificationChannelTools(server, ctx, helpers) {
|
|
|
1036
1303
|
createdAt: ch.createdAt,
|
|
1037
1304
|
dashboardUrl: notificationSettingsDashboardUrl()
|
|
1038
1305
|
})),
|
|
1039
|
-
total: channels.length
|
|
1306
|
+
total: channels.length,
|
|
1307
|
+
pagination: completeList(channels.length)
|
|
1040
1308
|
});
|
|
1041
1309
|
}));
|
|
1042
1310
|
server.registerTool("create_notification_channel", {
|
|
1043
1311
|
title: "Create Notification Channel",
|
|
1044
1312
|
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.",
|
|
1045
1313
|
inputSchema: strictSchema({
|
|
1046
|
-
channelType:
|
|
1047
|
-
name:
|
|
1048
|
-
config:
|
|
1049
|
-
recipients:
|
|
1050
|
-
webhookUrl:
|
|
1051
|
-
httpMethod:
|
|
1052
|
-
slackChannelId:
|
|
1053
|
-
slackChannelName:
|
|
1054
|
-
auth:
|
|
1055
|
-
type:
|
|
1056
|
-
token:
|
|
1057
|
-
username:
|
|
1058
|
-
password:
|
|
1059
|
-
headers:
|
|
1314
|
+
channelType: z5.enum(["EMAIL", "WEBHOOK", "SLACK", "WEBSOCKET"]).describe("Type of notification channel"),
|
|
1315
|
+
name: z5.string().optional().describe("Name for the channel (defaults to 'default')"),
|
|
1316
|
+
config: z5.object({
|
|
1317
|
+
recipients: z5.array(z5.string()).optional().describe("Email recipients (required for EMAIL channels)"),
|
|
1318
|
+
webhookUrl: z5.string().optional().describe("Webhook/Slack endpoint URL (required for WEBHOOK, optional for SLACK)"),
|
|
1319
|
+
httpMethod: z5.enum(["POST", "GET", "PUT", "PATCH"]).optional().describe("HTTP method for WEBHOOK channels (defaults to POST)"),
|
|
1320
|
+
slackChannelId: z5.string().optional().describe("Slack channel ID from OAuth integration (for SLACK channels, e.g. C01ABCDEF)"),
|
|
1321
|
+
slackChannelName: z5.string().optional().describe("Slack channel name (for SLACK channels, e.g. #alerts)"),
|
|
1322
|
+
auth: z5.object({
|
|
1323
|
+
type: z5.enum(["bearer", "basic", "header"]).describe("Authentication type"),
|
|
1324
|
+
token: z5.string().optional().describe("Bearer token (for 'bearer' type)"),
|
|
1325
|
+
username: z5.string().optional().describe("Username (for 'basic' type)"),
|
|
1326
|
+
password: z5.string().optional().describe("Password (for 'basic' type)"),
|
|
1327
|
+
headers: z5.object({}).passthrough().optional().describe("Custom headers as key-value pairs (for 'header' type)")
|
|
1060
1328
|
}).optional().describe("Authentication for WEBHOOK channels")
|
|
1061
1329
|
}).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.")
|
|
1062
1330
|
}),
|
|
@@ -1103,8 +1371,8 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
|
|
|
1103
1371
|
title: "Delete Notification Channel",
|
|
1104
1372
|
description: "Delete a notification channel by ID. " + "Call first without confirmed=true to preview, then with confirmed=true after user agrees.",
|
|
1105
1373
|
inputSchema: strictSchema({
|
|
1106
|
-
channelId:
|
|
1107
|
-
confirmed:
|
|
1374
|
+
channelId: z5.string().min(1).describe("The channel ID to delete"),
|
|
1375
|
+
confirmed: z5.preprocess(coerceBoolean(), z5.boolean()).optional().describe("Set to true only after the user has explicitly confirmed deletion.")
|
|
1108
1376
|
}),
|
|
1109
1377
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
1110
1378
|
}, withErrorHandling("delete_notification_channel", async (args) => {
|
|
@@ -1125,7 +1393,7 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
|
|
|
1125
1393
|
title: "List Notification Settings",
|
|
1126
1394
|
description: "List notification settings (event-to-channel mappings) for a workflow or workspace. Shows which events trigger notifications and through which channels.",
|
|
1127
1395
|
inputSchema: strictSchema({
|
|
1128
|
-
workflowId:
|
|
1396
|
+
workflowId: z5.string().optional().describe("Filter settings for a specific workflow. Omit to list workspace-level settings."),
|
|
1129
1397
|
eventType: NotificationEventTypeSchema.optional().describe("Filter by event type")
|
|
1130
1398
|
}),
|
|
1131
1399
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
@@ -1142,33 +1410,34 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
|
|
|
1142
1410
|
workflowId: s.workflowId,
|
|
1143
1411
|
eventType: s.eventType,
|
|
1144
1412
|
enabled: s.enabled,
|
|
1145
|
-
channels: s.channels,
|
|
1413
|
+
channels: redactChannelRefs(s.channels),
|
|
1146
1414
|
createdAt: s.createdAt
|
|
1147
1415
|
})),
|
|
1148
|
-
total: settings.length
|
|
1416
|
+
total: settings.length,
|
|
1417
|
+
pagination: completeList(settings.length)
|
|
1149
1418
|
});
|
|
1150
1419
|
}));
|
|
1151
1420
|
server.registerTool("configure_notifications", {
|
|
1152
1421
|
title: "Configure Notifications",
|
|
1153
1422
|
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).",
|
|
1154
1423
|
inputSchema: strictSchema({
|
|
1155
|
-
workflowId:
|
|
1156
|
-
events:
|
|
1157
|
-
channels:
|
|
1158
|
-
email:
|
|
1159
|
-
recipients:
|
|
1424
|
+
workflowId: z5.string().optional().describe("Workflow ID to configure notifications for. Omit for workspace-level notifications."),
|
|
1425
|
+
events: z5.preprocess(coerceList(), z5.array(z5.enum(["all", ...NOTIFICATION_EVENT_TYPES])).min(1)).describe('Event types to notify on. Pass ["all"] for every event type, or list specific event types.'),
|
|
1426
|
+
channels: z5.object({
|
|
1427
|
+
email: z5.object({
|
|
1428
|
+
recipients: z5.array(z5.string().email()).optional().describe("Email addresses to notify. Omit to use the account default email.")
|
|
1160
1429
|
}).optional().describe("Send email notifications. Pass {} for default email or {recipients: [...]} for custom addresses."),
|
|
1161
|
-
webhook:
|
|
1162
|
-
url:
|
|
1163
|
-
httpMethod:
|
|
1430
|
+
webhook: z5.object({
|
|
1431
|
+
url: z5.string().url().describe("Webhook endpoint URL"),
|
|
1432
|
+
httpMethod: z5.enum(["POST", "GET", "PUT", "PATCH"]).optional().describe("HTTP method (defaults to POST)"),
|
|
1164
1433
|
auth: webhookAuthShape
|
|
1165
1434
|
}).optional().describe("Send via HTTP webhook"),
|
|
1166
|
-
slack:
|
|
1167
|
-
webhookUrl:
|
|
1168
|
-
slackChannelId:
|
|
1169
|
-
slackChannelName:
|
|
1435
|
+
slack: z5.object({
|
|
1436
|
+
webhookUrl: z5.string().url().optional().describe("Slack incoming webhook URL (legacy)"),
|
|
1437
|
+
slackChannelId: z5.string().optional().describe("Slack channel ID from OAuth integration (e.g. C01ABCDEF)"),
|
|
1438
|
+
slackChannelName: z5.string().optional().describe("Slack channel name (e.g. #alerts)")
|
|
1170
1439
|
}).optional().describe("Send to Slack channel. Use slackChannelId/slackChannelName for OAuth integration, or webhookUrl for legacy incoming webhooks."),
|
|
1171
|
-
websocket:
|
|
1440
|
+
websocket: z5.boolean().optional().describe("Enable WebSocket notifications")
|
|
1172
1441
|
}).describe("At least one channel must be specified")
|
|
1173
1442
|
}),
|
|
1174
1443
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
@@ -1178,7 +1447,7 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
|
|
|
1178
1447
|
const ch = args.channels;
|
|
1179
1448
|
if (ch.email) {
|
|
1180
1449
|
if (ch.email.recipients?.length) {
|
|
1181
|
-
channelSetup.EMAIL = { name: "
|
|
1450
|
+
channelSetup.EMAIL = { name: mcpChannelName("EMAIL", ch.email), recipients: ch.email.recipients };
|
|
1182
1451
|
} else {
|
|
1183
1452
|
channelSetup.EMAIL = true;
|
|
1184
1453
|
}
|
|
@@ -1187,7 +1456,7 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
|
|
|
1187
1456
|
channelSetup.WEBSOCKET = true;
|
|
1188
1457
|
if (ch.webhook) {
|
|
1189
1458
|
const webhookChannel = {
|
|
1190
|
-
name: "
|
|
1459
|
+
name: mcpChannelName("WEBHOOK", { webhookUrl: ch.webhook.url }),
|
|
1191
1460
|
webhookUrl: ch.webhook.url,
|
|
1192
1461
|
httpMethod: ch.webhook.httpMethod || "POST"
|
|
1193
1462
|
};
|
|
@@ -1196,7 +1465,7 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
|
|
|
1196
1465
|
channelSetup.WEBHOOK = webhookChannel;
|
|
1197
1466
|
}
|
|
1198
1467
|
if (ch.slack) {
|
|
1199
|
-
const slackChannel = { name: "
|
|
1468
|
+
const slackChannel = { name: mcpChannelName("SLACK", ch.slack) };
|
|
1200
1469
|
if (ch.slack.webhookUrl)
|
|
1201
1470
|
slackChannel.webhookUrl = ch.slack.webhookUrl;
|
|
1202
1471
|
if (ch.slack.slackChannelId)
|
|
@@ -1222,7 +1491,7 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
|
|
|
1222
1491
|
id: s.id,
|
|
1223
1492
|
eventType: s.eventType,
|
|
1224
1493
|
enabled: s.enabled,
|
|
1225
|
-
channels: s.channels
|
|
1494
|
+
channels: redactChannelRefs(s.channels)
|
|
1226
1495
|
}))
|
|
1227
1496
|
});
|
|
1228
1497
|
}));
|
|
@@ -1230,8 +1499,8 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
|
|
|
1230
1499
|
title: "Delete Notification Setting",
|
|
1231
1500
|
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.",
|
|
1232
1501
|
inputSchema: strictSchema({
|
|
1233
|
-
settingId:
|
|
1234
|
-
confirmed:
|
|
1502
|
+
settingId: z5.string().min(1).describe("The notification setting ID to delete (the id from list_notification_settings)"),
|
|
1503
|
+
confirmed: z5.preprocess(coerceBoolean(), z5.boolean()).optional().describe("Set to true only after the user has explicitly confirmed deletion.")
|
|
1235
1504
|
}),
|
|
1236
1505
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
1237
1506
|
}, withErrorHandling("delete_notification_setting", async (args) => {
|
|
@@ -1250,17 +1519,14 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
|
|
|
1250
1519
|
}));
|
|
1251
1520
|
}
|
|
1252
1521
|
var init_notifications = __esm(() => {
|
|
1522
|
+
init_notification_channels();
|
|
1523
|
+
init_pagination();
|
|
1253
1524
|
init_schemas();
|
|
1254
1525
|
});
|
|
1255
1526
|
|
|
1256
1527
|
// src/tools/observability.ts
|
|
1257
1528
|
import { KadoaSdkException as KadoaSdkException2 } from "@kadoa/node-sdk";
|
|
1258
|
-
import { z as
|
|
1259
|
-
function boundActivityLimit(limit) {
|
|
1260
|
-
if (limit === undefined || Number.isNaN(limit))
|
|
1261
|
-
return ACTIVITY_DEFAULT_LIMIT;
|
|
1262
|
-
return Math.min(Math.max(Math.trunc(limit), 1), ACTIVITY_MAX_LIMIT);
|
|
1263
|
-
}
|
|
1529
|
+
import { z as z6 } from "zod";
|
|
1264
1530
|
function mapActivityEvent(event, includeDetails) {
|
|
1265
1531
|
const details = event.details ?? {};
|
|
1266
1532
|
const summary = details._changeSummary;
|
|
@@ -1333,10 +1599,10 @@ function registerObservabilityTools(server, ctx, helpers) {
|
|
|
1333
1599
|
title: "Get Observability Metrics",
|
|
1334
1600
|
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.",
|
|
1335
1601
|
inputSchema: strictSchema({
|
|
1336
|
-
days:
|
|
1337
|
-
includeWorkflows:
|
|
1338
|
-
includeDailySeries:
|
|
1339
|
-
limit:
|
|
1602
|
+
days: z6.preprocess(coerceNumber(), z6.number().int().min(1).max(365)).optional().describe("Window length in days, 1 to 365 (default 30). The dashboard offers 30, 90, and 365."),
|
|
1603
|
+
includeWorkflows: z6.preprocess(coerceBoolean(), z6.boolean()).optional().describe("Also return every workflow ranked by severity, not only the down and degraded ones (default false)."),
|
|
1604
|
+
includeDailySeries: z6.preprocess(coerceBoolean(), z6.boolean()).optional().describe("Also return the team-level daily series of successful runs, failed runs, and uptime (default false)."),
|
|
1605
|
+
limit: z6.preprocess(coerceNumber(), z6.number().int().min(1).max(OBSERVABILITY_MAX_LIMIT)).optional().describe("Maximum workflows in the attention list and in workflows (default 25, max 200).")
|
|
1340
1606
|
}),
|
|
1341
1607
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
1342
1608
|
}, withErrorHandling("get_observability", async (args) => {
|
|
@@ -1392,22 +1658,23 @@ function registerObservabilityTools(server, ctx, helpers) {
|
|
|
1392
1658
|
title: "List Activity",
|
|
1393
1659
|
description: "List the team's Activity log (audit trail): who did what, when, and from which interface, across " + "every workflow and resource in the active team. Each event carries the actor's email, the resource " + "name and type, the action (CREATED, UPDATED, DELETED, APPROVED, FINISHED, FAILED, DATA_CHANGED, ...), " + "the interface label, and a changeSummary when the backend recorded one. Use it for team-wide questions " + "such as 'who changed anything last week?', 'what did alice do yesterday?', or 'which workflows were " + "deleted this month?'. For one workflow's configuration revisions with field-level before/after diffs " + "(prompt, schema, schedule), use get_workflow_history instead. Newest events come first. Refer to " + "people by email and resources by name in answers; ids are for follow-up tool calls.",
|
|
1394
1660
|
inputSchema: strictSchema({
|
|
1395
|
-
limit:
|
|
1396
|
-
offset:
|
|
1397
|
-
workflowId:
|
|
1398
|
-
userId:
|
|
1399
|
-
startDate:
|
|
1400
|
-
endDate:
|
|
1401
|
-
relativeTime:
|
|
1402
|
-
eventTypes:
|
|
1403
|
-
interfaces:
|
|
1404
|
-
resourceTypes:
|
|
1405
|
-
includeDetails:
|
|
1661
|
+
limit: limitInput({ noun: "Events", defaultLimit: ACTIVITY_DEFAULT_LIMIT, max: ACTIVITY_MAX_LIMIT }),
|
|
1662
|
+
offset: offsetInput("offset", "events"),
|
|
1663
|
+
workflowId: z6.string().optional().describe("Only events about this workflow (runs, config changes, data changes, notifications)"),
|
|
1664
|
+
userId: z6.string().optional().describe("Only events by this user id. Resolve an email to an id from a previous list_activity result."),
|
|
1665
|
+
startDate: z6.string().optional().describe("ISO 8601 lower bound. Ignored when relativeTime is set."),
|
|
1666
|
+
endDate: z6.string().optional().describe("ISO 8601 upper bound. Ignored when relativeTime is set."),
|
|
1667
|
+
relativeTime: z6.string().regex(/^\d+(m|h|d|w)$/i, "Use <number><unit> with unit m, h, d or w, e.g. 6h or 2w").optional().describe("Window counted back from now, e.g. 30m, 6h, 1d, 2w. Preferred for 'last week' style questions."),
|
|
1668
|
+
eventTypes: z6.preprocess(coerceList({ upperCase: true }), z6.array(z6.enum(ACTIVITY_EVENT_TYPES))).optional().describe("Actions to keep. Workflow run outcomes (FINISHED, FAILED, ...) count as actions."),
|
|
1669
|
+
interfaces: z6.preprocess(coerceList({ upperCase: true }), z6.array(z6.enum(ACTIVITY_INTERFACES))).optional().describe("Interfaces the action came from"),
|
|
1670
|
+
resourceTypes: z6.preprocess(coerceList({ upperCase: true }), z6.array(z6.enum(ACTIVITY_RESOURCE_TYPES))).optional().describe("Resource types to keep"),
|
|
1671
|
+
includeDetails: z6.preprocess(coerceBoolean(), z6.boolean()).optional().describe("Include sanitized previousValue/newValue snapshots per event (default false; large)")
|
|
1406
1672
|
}),
|
|
1407
1673
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
1408
1674
|
}, withErrorHandling("list_activity", async (args) => {
|
|
1675
|
+
const limit = args.limit ?? ACTIVITY_DEFAULT_LIMIT;
|
|
1409
1676
|
const result = await ctx.client.activity.list({
|
|
1410
|
-
limit
|
|
1677
|
+
limit,
|
|
1411
1678
|
offset: args.offset,
|
|
1412
1679
|
workflowId: args.workflowId,
|
|
1413
1680
|
userId: args.userId,
|
|
@@ -1421,7 +1688,15 @@ function registerObservabilityTools(server, ctx, helpers) {
|
|
|
1421
1688
|
const events = result.events.map((event) => mapActivityEvent(event, args.includeDetails === true));
|
|
1422
1689
|
return jsonResult({
|
|
1423
1690
|
events,
|
|
1424
|
-
pagination:
|
|
1691
|
+
pagination: offsetPage({
|
|
1692
|
+
args,
|
|
1693
|
+
param: "offset",
|
|
1694
|
+
offset: args.offset ?? 0,
|
|
1695
|
+
limit,
|
|
1696
|
+
returned: events.length,
|
|
1697
|
+
totalCount: result.pagination?.total,
|
|
1698
|
+
hasMore: result.pagination?.hasMore
|
|
1699
|
+
}),
|
|
1425
1700
|
...events.length === 0 ? {
|
|
1426
1701
|
hint: "No activity matched these filters. Widen relativeTime or drop a filter before concluding nothing happened."
|
|
1427
1702
|
} : {}
|
|
@@ -1430,6 +1705,7 @@ function registerObservabilityTools(server, ctx, helpers) {
|
|
|
1430
1705
|
}
|
|
1431
1706
|
var ACTIVITY_DEFAULT_LIMIT = 25, ACTIVITY_MAX_LIMIT = 200, ACTIVITY_EVENT_TYPES, ACTIVITY_INTERFACES, ACTIVITY_RESOURCE_TYPES, HEALTH_SEVERITY;
|
|
1432
1707
|
var init_observability = __esm(() => {
|
|
1708
|
+
init_pagination();
|
|
1433
1709
|
init_schemas();
|
|
1434
1710
|
ACTIVITY_EVENT_TYPES = [
|
|
1435
1711
|
"CREATED",
|
|
@@ -1470,30 +1746,174 @@ var init_observability = __esm(() => {
|
|
|
1470
1746
|
HEALTH_SEVERITY = { down: 0, degraded: 1, idle: 2, healthy: 3 };
|
|
1471
1747
|
});
|
|
1472
1748
|
|
|
1749
|
+
// src/tools/shared/budget.ts
|
|
1750
|
+
function jsonBytes(value) {
|
|
1751
|
+
const text = JSON.stringify(value);
|
|
1752
|
+
return text === undefined ? 0 : Buffer.byteLength(text, "utf8");
|
|
1753
|
+
}
|
|
1754
|
+
function truncateUtf8(text, maxBytes) {
|
|
1755
|
+
if (Buffer.byteLength(text, "utf8") <= maxBytes)
|
|
1756
|
+
return text;
|
|
1757
|
+
const { read } = new TextEncoder().encodeInto(text, new Uint8Array(Math.max(0, maxBytes)));
|
|
1758
|
+
return text.slice(0, read);
|
|
1759
|
+
}
|
|
1760
|
+
function previewValue(value, maxBytes = VALUE_PREVIEW_BYTES) {
|
|
1761
|
+
if (value === undefined || value === null)
|
|
1762
|
+
return null;
|
|
1763
|
+
const bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
1764
|
+
if (bytes <= maxBytes)
|
|
1765
|
+
return value;
|
|
1766
|
+
if (typeof value === "string")
|
|
1767
|
+
return `${truncateUtf8(value, maxBytes)}… [cut, ${bytes} bytes]`;
|
|
1768
|
+
if (Array.isArray(value))
|
|
1769
|
+
return `[array of ${value.length} items, ${bytes} bytes]`;
|
|
1770
|
+
return `[${typeof value}, ${bytes} bytes]`;
|
|
1771
|
+
}
|
|
1772
|
+
function fitItems(options) {
|
|
1773
|
+
const budget = options.budgetBytes ?? RESULT_BUDGET_BYTES;
|
|
1774
|
+
const all = [...options.items];
|
|
1775
|
+
if (jsonBytes(options.build(all)) <= budget)
|
|
1776
|
+
return { kept: all, omitted: 0, truncated: false };
|
|
1777
|
+
const limit = budget - TRUNCATION_RESERVE_BYTES - (options.reserveBytes ?? 0);
|
|
1778
|
+
let lo = 0;
|
|
1779
|
+
let hi = all.length - 1;
|
|
1780
|
+
while (lo < hi) {
|
|
1781
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
1782
|
+
if (jsonBytes(options.build(all.slice(0, mid))) <= limit)
|
|
1783
|
+
lo = mid;
|
|
1784
|
+
else
|
|
1785
|
+
hi = mid - 1;
|
|
1786
|
+
}
|
|
1787
|
+
return { kept: all.slice(0, lo), omitted: all.length - lo, truncated: true };
|
|
1788
|
+
}
|
|
1789
|
+
function truncationNotice(options) {
|
|
1790
|
+
const budgetBytes = options.budgetBytes ?? RESULT_BUDGET_BYTES;
|
|
1791
|
+
const total = options.kept + options.omitted;
|
|
1792
|
+
return {
|
|
1793
|
+
truncated: true,
|
|
1794
|
+
truncation: { kept: options.kept, omitted: options.omitted, budgetBytes },
|
|
1795
|
+
hint: `Result cut to ${options.kept} of ${total} ${options.noun} to stay under ${Math.round(budgetBytes / 1000)} KB. ${options.hint}`
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
function highlyDivisibleFloor(n) {
|
|
1799
|
+
if (n <= 0)
|
|
1800
|
+
return 0;
|
|
1801
|
+
if (n >= 10)
|
|
1802
|
+
return Math.floor(n / 10) * 10;
|
|
1803
|
+
let p = 1;
|
|
1804
|
+
while (p * 2 <= n)
|
|
1805
|
+
p *= 2;
|
|
1806
|
+
return p;
|
|
1807
|
+
}
|
|
1808
|
+
function alignedKeep(offset, fit) {
|
|
1809
|
+
if (offset === 0)
|
|
1810
|
+
return highlyDivisibleFloor(fit);
|
|
1811
|
+
for (let keep = fit;keep >= 1; keep--) {
|
|
1812
|
+
if (offset % keep === 0 && offset / keep + 2 <= MAX_PAGE)
|
|
1813
|
+
return keep;
|
|
1814
|
+
}
|
|
1815
|
+
return 0;
|
|
1816
|
+
}
|
|
1817
|
+
function resumeNumberedPage(meta, options) {
|
|
1818
|
+
const { next: _replaced, totalPages: _droppedTotalPages, ...rest } = meta;
|
|
1819
|
+
if (options.kept === 0)
|
|
1820
|
+
return { ...rest, hasMore: false };
|
|
1821
|
+
const offset = (options.page - 1) * options.limit;
|
|
1822
|
+
return {
|
|
1823
|
+
...rest,
|
|
1824
|
+
hasMore: true,
|
|
1825
|
+
next: nextArgs(options.args, { page: offset / options.kept + 2, limit: options.kept })
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
function budgetNumberedPage(options) {
|
|
1829
|
+
const offset = (options.page - 1) * options.limit;
|
|
1830
|
+
const fit = fitItems({
|
|
1831
|
+
items: options.items,
|
|
1832
|
+
build: (kept) => options.build(kept, options.pagination),
|
|
1833
|
+
reserveBytes: jsonBytes({ pagination: { next: options.args } })
|
|
1834
|
+
});
|
|
1835
|
+
if (!fit.truncated)
|
|
1836
|
+
return options.build([...options.items], options.pagination);
|
|
1837
|
+
const buildCut = (fitCount) => {
|
|
1838
|
+
const keep = alignedKeep(offset, fitCount);
|
|
1839
|
+
const alignmentImpossible = keep === 0 && fitCount > 0;
|
|
1840
|
+
const pagination = resumeNumberedPage(options.pagination, {
|
|
1841
|
+
args: options.args,
|
|
1842
|
+
page: options.page,
|
|
1843
|
+
limit: options.limit,
|
|
1844
|
+
kept: keep
|
|
1845
|
+
});
|
|
1846
|
+
const hint = keep > 0 ? options.resumeHint : alignmentImpossible ? options.alignmentImpossibleHint ?? DEFAULT_ALIGNMENT_IMPOSSIBLE_HINT : options.emptyHint;
|
|
1847
|
+
const result = {
|
|
1848
|
+
...options.build(options.items.slice(0, keep), pagination),
|
|
1849
|
+
...truncationNotice({ kept: keep, omitted: options.items.length - keep, noun: options.noun, hint })
|
|
1850
|
+
};
|
|
1851
|
+
return { result, keep };
|
|
1852
|
+
};
|
|
1853
|
+
let fitCount = fit.kept.length;
|
|
1854
|
+
let { result, keep } = buildCut(fitCount);
|
|
1855
|
+
while (jsonBytes(result) > RESULT_BUDGET_BYTES && keep > 0) {
|
|
1856
|
+
fitCount = keep - 1;
|
|
1857
|
+
({ result, keep } = buildCut(fitCount));
|
|
1858
|
+
}
|
|
1859
|
+
return result;
|
|
1860
|
+
}
|
|
1861
|
+
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.";
|
|
1862
|
+
var init_budget = __esm(() => {
|
|
1863
|
+
init_pagination();
|
|
1864
|
+
});
|
|
1865
|
+
|
|
1473
1866
|
// src/tools/scrape.ts
|
|
1474
|
-
import { z as
|
|
1475
|
-
function
|
|
1867
|
+
import { z as z7 } from "zod";
|
|
1868
|
+
function scrapeResult(response, format) {
|
|
1869
|
+
const { content, ...metadata } = response;
|
|
1870
|
+
if (typeof content !== "string" || content.length === 0)
|
|
1871
|
+
return textOnlyResult(metadata);
|
|
1872
|
+
const contentBytes = Buffer.byteLength(content, "utf8");
|
|
1873
|
+
if (response.isBase64) {
|
|
1874
|
+
return textOnlyResult({
|
|
1875
|
+
...metadata,
|
|
1876
|
+
contentBytes,
|
|
1877
|
+
contentOmitted: "binary",
|
|
1878
|
+
hint: `The URL returned binary content (${response.contentType ?? "unknown type"}), which scrape does not return inline.`
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1881
|
+
const text = truncateUtf8(content, SCRAPE_CONTENT_BUDGET_BYTES);
|
|
1882
|
+
const keptBytes = Buffer.byteLength(text, "utf8");
|
|
1883
|
+
const cut = keptBytes < contentBytes;
|
|
1884
|
+
const hint = format === "html" ? "Page HTML was cut at 70 KB. Retry with format markdown (much smaller), or use create_workflow to extract structured data from the whole page." : "Page markdown was cut at 70 KB. Use create_workflow to extract structured data from the whole page.";
|
|
1885
|
+
const meta = {
|
|
1886
|
+
...metadata,
|
|
1887
|
+
contentBytes,
|
|
1888
|
+
...cut && {
|
|
1889
|
+
truncated: true,
|
|
1890
|
+
truncation: { keptBytes, omittedBytes: contentBytes - keptBytes, budgetBytes: SCRAPE_CONTENT_BUDGET_BYTES },
|
|
1891
|
+
hint
|
|
1892
|
+
}
|
|
1893
|
+
};
|
|
1894
|
+
return withExtraContent(textOnlyResult(meta), { type: "text", text });
|
|
1895
|
+
}
|
|
1896
|
+
function registerScrapeTools(server, ctx, helpers, defaultProductType) {
|
|
1476
1897
|
const { withErrorHandling } = helpers;
|
|
1477
1898
|
server.registerTool("scrape", {
|
|
1478
1899
|
title: "Scrape URL",
|
|
1479
|
-
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. " + "Use create_workflow instead for structured extraction, recurring runs, monitoring, or navigation-heavy jobs.",
|
|
1900
|
+
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.",
|
|
1480
1901
|
inputSchema: strictSchema({
|
|
1481
|
-
url:
|
|
1902
|
+
url: z7.url().refine((value) => {
|
|
1482
1903
|
const protocol = new URL(value).protocol;
|
|
1483
1904
|
return protocol === "http:" || protocol === "https:";
|
|
1484
1905
|
}, "URL must use HTTP or HTTPS").describe("HTTP or HTTPS URL to fetch"),
|
|
1485
|
-
format:
|
|
1906
|
+
format: z7.enum(["html", "markdown"]).optional().describe("Output format. Defaults to markdown for compact LLM consumption; use html for raw source.")
|
|
1486
1907
|
}),
|
|
1487
1908
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1488
1909
|
}, withErrorHandling("scrape", async (args) => {
|
|
1489
|
-
const
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
});
|
|
1493
|
-
return jsonResult(response);
|
|
1910
|
+
const format = args.format ?? "markdown";
|
|
1911
|
+
const response = await ctx.client.scrape.fetch({ url: args.url, format });
|
|
1912
|
+
return scrapeResult(response, format);
|
|
1494
1913
|
}));
|
|
1495
1914
|
}
|
|
1496
1915
|
var init_scrape = __esm(() => {
|
|
1916
|
+
init_budget();
|
|
1497
1917
|
init_schemas();
|
|
1498
1918
|
});
|
|
1499
1919
|
|
|
@@ -1649,7 +2069,7 @@ var init_errors = () => {};
|
|
|
1649
2069
|
|
|
1650
2070
|
// src/tools/support.ts
|
|
1651
2071
|
import { KadoaHttpException as KadoaHttpException2 } from "@kadoa/node-sdk";
|
|
1652
|
-
import { z as
|
|
2072
|
+
import { z as z8 } from "zod";
|
|
1653
2073
|
function supportTicketSkipMessage(reason) {
|
|
1654
2074
|
return (reason && SUPPORT_TICKET_SKIP_MESSAGES[reason]) ?? "No support ticket was filed because this workflow isn't eligible for a support ticket right now.";
|
|
1655
2075
|
}
|
|
@@ -1673,11 +2093,11 @@ function registerSupportTools(server, ctx, helpers) {
|
|
|
1673
2093
|
title: "Create Support Ticket",
|
|
1674
2094
|
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.",
|
|
1675
2095
|
inputSchema: strictSchema({
|
|
1676
|
-
workflowId:
|
|
1677
|
-
title:
|
|
1678
|
-
description:
|
|
1679
|
-
category:
|
|
1680
|
-
jobId:
|
|
2096
|
+
workflowId: z8.string().min(1).describe("The workflow the problem is about"),
|
|
2097
|
+
title: z8.string().describe("One-line summary of the problem"),
|
|
2098
|
+
description: z8.string().describe("What is wrong, what was observed, and what was already tried"),
|
|
2099
|
+
category: z8.enum(["bug", "feature", "workflow_issue", "template_issue", "integration", "billing", "data_diff"]).optional().describe("Support category; defaults to a workflow issue"),
|
|
2100
|
+
jobId: z8.string().optional().describe("The failing run's id (from list_workflow_runs). Required when filing from the Kadoa Assistant.")
|
|
1681
2101
|
}),
|
|
1682
2102
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
1683
2103
|
}, withErrorHandling("create_support_ticket", async (args) => {
|
|
@@ -1741,29 +2161,36 @@ var init_support = __esm(() => {
|
|
|
1741
2161
|
});
|
|
1742
2162
|
|
|
1743
2163
|
// src/tools/team.ts
|
|
1744
|
-
import { z as
|
|
2164
|
+
import { z as z9 } from "zod";
|
|
1745
2165
|
function registerWhoamiTool(server, ctx, helpers) {
|
|
1746
2166
|
const { withErrorHandling } = helpers;
|
|
1747
2167
|
server.registerTool("whoami", {
|
|
1748
2168
|
title: "Show Current User",
|
|
1749
|
-
description: "Show current user details: email, authentication method, and team
|
|
2169
|
+
description: "Show current user details: email, authentication method, team memberships, and the team's default product",
|
|
1750
2170
|
inputSchema: strictSchema({}),
|
|
1751
2171
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
1752
2172
|
}, withErrorHandling("whoami", async () => {
|
|
2173
|
+
const defaultProductTypePromise = ctx.client.user.getFeatures().then((features) => parseDefaultProductType(features.defaultProductType), () => {
|
|
2174
|
+
return;
|
|
2175
|
+
});
|
|
1753
2176
|
if (ctx.principal.connector) {
|
|
1754
|
-
const teams = await
|
|
2177
|
+
const [teams, defaultProductType] = await Promise.all([
|
|
2178
|
+
ctx.client.listTeams(),
|
|
2179
|
+
defaultProductTypePromise
|
|
2180
|
+
]);
|
|
1755
2181
|
const activeTeam = teams.find((team) => team.id === ctx.principal.teamId);
|
|
1756
2182
|
if (!activeTeam) {
|
|
1757
2183
|
throw new Error("The active team is unavailable for this credential. Reconnect and choose a team.");
|
|
1758
2184
|
}
|
|
1759
2185
|
return jsonResult({
|
|
1760
2186
|
authMethod: "Shared organization service account",
|
|
1761
|
-
team: { name: activeTeam.name }
|
|
2187
|
+
team: { name: activeTeam.name, ...defaultProductType ? { defaultProductType } : {} }
|
|
1762
2188
|
});
|
|
1763
2189
|
}
|
|
1764
|
-
const [user, teams] = await Promise.all([
|
|
2190
|
+
const [user, teams, defaultProductType] = await Promise.all([
|
|
1765
2191
|
ctx.client.user.getCurrentUser(),
|
|
1766
|
-
ctx.client.listTeams()
|
|
2192
|
+
ctx.client.listTeams(),
|
|
2193
|
+
defaultProductTypePromise
|
|
1767
2194
|
]);
|
|
1768
2195
|
const activeTeam = teams.find((team) => team.id === ctx.principal.teamId);
|
|
1769
2196
|
if (!activeTeam) {
|
|
@@ -1774,7 +2201,8 @@ function registerWhoamiTool(server, ctx, helpers) {
|
|
|
1774
2201
|
authMethod: ctx.principal.actor ? `Kadoa Assistant (delegated via ${ctx.principal.actor.sub})` : "Kadoa access token",
|
|
1775
2202
|
team: {
|
|
1776
2203
|
name: activeTeam.name,
|
|
1777
|
-
...activeTeam.memberRole ? { memberRole: activeTeam.memberRole } : {}
|
|
2204
|
+
...activeTeam.memberRole ? { memberRole: activeTeam.memberRole } : {},
|
|
2205
|
+
...defaultProductType ? { defaultProductType } : {}
|
|
1778
2206
|
},
|
|
1779
2207
|
scopes: ctx.principal.scopes
|
|
1780
2208
|
});
|
|
@@ -1803,7 +2231,7 @@ function registerTeamTools(server, ctx, helpers) {
|
|
|
1803
2231
|
title: "Switch Team",
|
|
1804
2232
|
description: "Switch the active team by name or ID. After switching, all subsequent API calls will use the new team context.",
|
|
1805
2233
|
inputSchema: strictSchema({
|
|
1806
|
-
teamIdentifier:
|
|
2234
|
+
teamIdentifier: z9.string().min(1).describe("Team name or team ID to switch to")
|
|
1807
2235
|
}),
|
|
1808
2236
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
1809
2237
|
}, withErrorHandling("team_switch", async () => errorResult("team_switch is not available for this credential. Reconnect and choose the team at sign-in.")));
|
|
@@ -1814,7 +2242,7 @@ var init_team = __esm(() => {
|
|
|
1814
2242
|
});
|
|
1815
2243
|
|
|
1816
2244
|
// src/tools/templates.ts
|
|
1817
|
-
import { z as
|
|
2245
|
+
import { z as z10 } from "zod";
|
|
1818
2246
|
function normalizeTemplateSchemaField(val) {
|
|
1819
2247
|
if (val === null || typeof val !== "object" || Array.isArray(val))
|
|
1820
2248
|
return val;
|
|
@@ -1844,6 +2272,7 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
1844
2272
|
return jsonResult({
|
|
1845
2273
|
templates: templates.map(withTemplateDashboardUrl),
|
|
1846
2274
|
count: templates.length,
|
|
2275
|
+
pagination: completeList(templates.length),
|
|
1847
2276
|
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."
|
|
1848
2277
|
});
|
|
1849
2278
|
}));
|
|
@@ -1851,7 +2280,7 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
1851
2280
|
title: "Get Template",
|
|
1852
2281
|
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.",
|
|
1853
2282
|
inputSchema: strictSchema({
|
|
1854
|
-
templateId:
|
|
2283
|
+
templateId: z10.string().min(1).describe("The template ID")
|
|
1855
2284
|
}),
|
|
1856
2285
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
1857
2286
|
}, withErrorHandling("get_template", async (args) => {
|
|
@@ -1865,8 +2294,8 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
1865
2294
|
title: "Create Template",
|
|
1866
2295
|
description: "Create a new template. After creation, use create_template_version to publish a version with prompt, schema, and notifications.",
|
|
1867
2296
|
inputSchema: strictSchema({
|
|
1868
|
-
name:
|
|
1869
|
-
description:
|
|
2297
|
+
name: z10.string().describe("Template name (1-255 chars)"),
|
|
2298
|
+
description: z10.string().optional().describe("Template description (max 2000 chars)")
|
|
1870
2299
|
}),
|
|
1871
2300
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
1872
2301
|
}, withErrorHandling("create_template", async (args) => {
|
|
@@ -1886,9 +2315,9 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
1886
2315
|
|
|
1887
2316
|
` + "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.",
|
|
1888
2317
|
inputSchema: strictSchema({
|
|
1889
|
-
templateId:
|
|
1890
|
-
name:
|
|
1891
|
-
description:
|
|
2318
|
+
templateId: z10.string().min(1).describe("The template ID to update"),
|
|
2319
|
+
name: z10.preprocess(coerceNull(), z10.string().optional()).optional().describe("New template name"),
|
|
2320
|
+
description: z10.preprocess(coerceNull(), z10.string().optional()).optional().describe("New template description")
|
|
1892
2321
|
}),
|
|
1893
2322
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
1894
2323
|
}, withErrorHandling("update_template", async (args) => {
|
|
@@ -1904,8 +2333,8 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
1904
2333
|
title: "Delete Template",
|
|
1905
2334
|
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.",
|
|
1906
2335
|
inputSchema: strictSchema({
|
|
1907
|
-
templateId:
|
|
1908
|
-
confirmed:
|
|
2336
|
+
templateId: z10.string().min(1).describe("The template ID to delete"),
|
|
2337
|
+
confirmed: z10.preprocess(coerceBoolean(), z10.boolean()).optional().describe("Set to true only after the user has explicitly confirmed deletion. Omit or set to false for the initial preview call.")
|
|
1909
2338
|
}),
|
|
1910
2339
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
1911
2340
|
}, withErrorHandling("delete_template", async (args) => {
|
|
@@ -1929,15 +2358,15 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
1929
2358
|
});
|
|
1930
2359
|
}));
|
|
1931
2360
|
const TemplateSchemaFieldShape = {
|
|
1932
|
-
name:
|
|
1933
|
-
description:
|
|
1934
|
-
fieldType:
|
|
1935
|
-
example:
|
|
2361
|
+
name: z10.string().describe("Field name"),
|
|
2362
|
+
description: z10.string().optional().describe("Field description"),
|
|
2363
|
+
fieldType: z10.enum(["SCHEMA", "CLASSIFICATION"]).optional().describe("Field role: SCHEMA (default) for extracted data, CLASSIFICATION for a field with predefined categories"),
|
|
2364
|
+
example: z10.string().optional().describe("Example value"),
|
|
1936
2365
|
dataType: SchemaFieldDataTypeSchema.optional().describe("Data type for SCHEMA fields"),
|
|
1937
|
-
isKey:
|
|
1938
|
-
categories:
|
|
1939
|
-
title:
|
|
1940
|
-
definition:
|
|
2366
|
+
isKey: z10.preprocess(coerceBoolean(), z10.boolean()).optional().describe("Whether the field is a key field"),
|
|
2367
|
+
categories: z10.preprocess(coerceArray(), z10.array(z10.object({
|
|
2368
|
+
title: z10.string().describe("Short title/label of the category"),
|
|
2369
|
+
definition: z10.string().describe("Full definition/description of the category")
|
|
1941
2370
|
}))).optional().describe("Predefined categories for a CLASSIFICATION field ({title, definition}[]). Required for fieldType=CLASSIFICATION; omitted otherwise.")
|
|
1942
2371
|
};
|
|
1943
2372
|
async function latestTemplateVersionRules(templateId) {
|
|
@@ -1962,25 +2391,25 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
1962
2391
|
|
|
1963
2392
|
` + "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.",
|
|
1964
2393
|
inputSchema: withoutDefIds(strictSchema({
|
|
1965
|
-
templateId:
|
|
1966
|
-
prompt:
|
|
1967
|
-
schemaId:
|
|
1968
|
-
schemaFields:
|
|
1969
|
-
schemaEntity:
|
|
1970
|
-
schemaValidationRules:
|
|
1971
|
-
notifications:
|
|
1972
|
-
eventType:
|
|
1973
|
-
eventConfiguration:
|
|
1974
|
-
enabled:
|
|
1975
|
-
channelIds:
|
|
1976
|
-
channels:
|
|
2394
|
+
templateId: z10.string().min(1).describe("The template ID to publish a version for"),
|
|
2395
|
+
prompt: z10.string().optional().describe("User prompt to copy into workflow config"),
|
|
2396
|
+
schemaId: z10.string().optional().describe("Existing schema ID to reference (mutually exclusive with schemaFields)"),
|
|
2397
|
+
schemaFields: z10.preprocess(coerceArray(), z10.array(z10.preprocess(normalizeTemplateSchemaField, z10.object(TemplateSchemaFieldShape).strict()))).optional().describe("Inline schema fields to create a new schema (mutually exclusive with schemaId)"),
|
|
2398
|
+
schemaEntity: z10.string().optional().describe("Entity name for the inline schema"),
|
|
2399
|
+
schemaValidationRules: z10.preprocess((val) => stripSchemaValidationAttribution(coerceJson()(val)), SchemaValidationRulesSchema).optional().describe('Per-field schema validation rules keyed by field name. Each rule uses kind STRING, NUMBER, OTHER, OBJECT, or ARRAY and nested rules are supported. Edit attribution is stamped server-side, so send only rule values. Example: { "price": { "kind": "NUMBER", "minimum": { "value": 0 } } }. Not inherited from prior versions - omitting this on a new version drops any rules the previous version had.'),
|
|
2400
|
+
notifications: z10.preprocess(coerceArray(), z10.array(z10.object({
|
|
2401
|
+
eventType: z10.string().describe("Notification event type"),
|
|
2402
|
+
eventConfiguration: z10.preprocess(coerceJson(), z10.record(z10.string(), z10.unknown())).optional(),
|
|
2403
|
+
enabled: z10.preprocess(coerceBoolean(), z10.boolean()).optional(),
|
|
2404
|
+
channelIds: z10.preprocess(coerceArray(), z10.array(z10.string().min(1))).optional(),
|
|
2405
|
+
channels: z10.preprocess(coerceArray(), z10.array(z10.object({ channelId: z10.string() }))).optional()
|
|
1977
2406
|
}))).optional().describe("Notification configuration for this version"),
|
|
1978
|
-
frequency:
|
|
1979
|
-
interval:
|
|
1980
|
-
schedules:
|
|
1981
|
-
type:
|
|
1982
|
-
interval:
|
|
1983
|
-
expressions:
|
|
2407
|
+
frequency: z10.preprocess(coerceJson(), z10.object({
|
|
2408
|
+
interval: z10.enum([...UpdateIntervalSchema.options, "REAL_TIME"]).optional().describe("Scheduling interval the template controls for linked workflows. Use CUSTOM with 'schedules' for cron."),
|
|
2409
|
+
schedules: z10.preprocess(coerceArray(), z10.array(z10.object({
|
|
2410
|
+
type: z10.enum(["repeat", "cron"]).describe("'repeat' (interval token) or 'cron' (cron expressions)"),
|
|
2411
|
+
interval: z10.string().optional().describe("Repeat token (e.g. '1h', '1d') when type='repeat'"),
|
|
2412
|
+
expressions: z10.preprocess(coerceArray(true), z10.array(z10.string())).optional().describe("Cron expressions when type='cron' (e.g. '0 8 * * 2')")
|
|
1984
2413
|
}))).optional().describe("Schedule specs. Required when interval='CUSTOM'.")
|
|
1985
2414
|
})).optional().describe("Template-controlled scheduling for linked workflows ({interval, schedules}). Linked workflows inherit and cannot override this when set.")
|
|
1986
2415
|
})),
|
|
@@ -2010,11 +2439,11 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
2010
2439
|
title: "Save Workflow as Template",
|
|
2011
2440
|
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.",
|
|
2012
2441
|
inputSchema: strictSchema({
|
|
2013
|
-
workflowId:
|
|
2014
|
-
name:
|
|
2015
|
-
description:
|
|
2016
|
-
templateId:
|
|
2017
|
-
includeParts:
|
|
2442
|
+
workflowId: z10.string().min(1).describe("Source workflow ID to extract config from"),
|
|
2443
|
+
name: z10.string().optional().describe("Name for the new template (required if templateId is not set)"),
|
|
2444
|
+
description: z10.string().optional().describe("Description for the new template"),
|
|
2445
|
+
templateId: z10.string().optional().describe("Existing template ID to add a new version to (mutually exclusive with name)"),
|
|
2446
|
+
includeParts: z10.preprocess(coerceArray(), z10.array(z10.enum(["prompt", "schema", "schemaValidationRules", "notifications", "frequency"])).optional()).describe("Which parts of the workflow config to include: prompt, schema, schemaValidationRules, notifications, frequency. Omit to include all. At least 1 required if provided.")
|
|
2018
2447
|
}),
|
|
2019
2448
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
2020
2449
|
}, withErrorHandling("save_workflow_as_template", async (args) => {
|
|
@@ -2035,20 +2464,20 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
2035
2464
|
title: "List Template Schemas",
|
|
2036
2465
|
description: "List schemas associated with a template.",
|
|
2037
2466
|
inputSchema: strictSchema({
|
|
2038
|
-
templateId:
|
|
2467
|
+
templateId: z10.string().min(1).describe("The template ID")
|
|
2039
2468
|
}),
|
|
2040
2469
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2041
2470
|
}, withErrorHandling("list_template_schemas", async (args) => {
|
|
2042
2471
|
const schemas = await ctx.client.template.listSchemas(args.templateId);
|
|
2043
|
-
return jsonResult({ schemas, count: schemas.length });
|
|
2472
|
+
return jsonResult({ schemas, count: schemas.length, pagination: completeList(schemas.length) });
|
|
2044
2473
|
}));
|
|
2045
2474
|
server.registerTool("link_template_to_workflows", {
|
|
2046
2475
|
title: "Link Template to Workflows",
|
|
2047
2476
|
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.",
|
|
2048
2477
|
inputSchema: strictSchema({
|
|
2049
|
-
templateId:
|
|
2050
|
-
workflowIds:
|
|
2051
|
-
force:
|
|
2478
|
+
templateId: z10.string().min(1).describe("The template ID to link workflows to"),
|
|
2479
|
+
workflowIds: z10.preprocess(coerceArray(true), z10.array(z10.string().min(1)).min(1)).describe("Workflow IDs to link to the template (array of strings). Also accepts a single ID string."),
|
|
2480
|
+
force: z10.preprocess(coerceBoolean(), z10.boolean()).optional().describe("If true, relink workflows that are already linked to another template. Defaults to false.")
|
|
2052
2481
|
}),
|
|
2053
2482
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
2054
2483
|
}, withErrorHandling("link_template_to_workflows", async (args) => {
|
|
@@ -2075,9 +2504,9 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
2075
2504
|
title: "Apply Template Version",
|
|
2076
2505
|
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.",
|
|
2077
2506
|
inputSchema: strictSchema({
|
|
2078
|
-
templateId:
|
|
2079
|
-
targetVersion:
|
|
2080
|
-
workflowIds:
|
|
2507
|
+
templateId: z10.string().min(1).describe("The template ID whose version to apply"),
|
|
2508
|
+
targetVersion: z10.preprocess(coerceNumber(), z10.number()).describe("The template version number to apply to the workflows"),
|
|
2509
|
+
workflowIds: z10.preprocess(coerceArray(true), z10.array(z10.string().min(1)).min(1)).describe("Workflow IDs to update to the target version (array of strings). Also accepts a single ID string.")
|
|
2081
2510
|
}),
|
|
2082
2511
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
2083
2512
|
}, withErrorHandling("apply_template_version", async (args) => {
|
|
@@ -2097,19 +2526,19 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
2097
2526
|
title: "List Template Workflows",
|
|
2098
2527
|
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.",
|
|
2099
2528
|
inputSchema: strictSchema({
|
|
2100
|
-
templateId:
|
|
2529
|
+
templateId: z10.string().min(1).describe("The template ID")
|
|
2101
2530
|
}),
|
|
2102
2531
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2103
2532
|
}, withErrorHandling("list_template_workflows", async (args) => {
|
|
2104
2533
|
const workflows = await ctx.client.template.getLinkedWorkflows(args.templateId);
|
|
2105
|
-
return jsonResult({ workflows, count: workflows.length });
|
|
2534
|
+
return jsonResult({ workflows, count: workflows.length, pagination: completeList(workflows.length) });
|
|
2106
2535
|
}));
|
|
2107
2536
|
server.registerTool("unlink_template_from_workflows", {
|
|
2108
2537
|
title: "Unlink Template from Workflows",
|
|
2109
2538
|
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).",
|
|
2110
2539
|
inputSchema: strictSchema({
|
|
2111
|
-
templateId:
|
|
2112
|
-
workflowIds:
|
|
2540
|
+
templateId: z10.string().min(1).describe("The template ID to unlink workflows from"),
|
|
2541
|
+
workflowIds: z10.preprocess(coerceArray(true), z10.array(z10.string().min(1)).min(1)).describe("Workflow IDs to unlink from the template (array of strings). Also accepts a single ID string.")
|
|
2113
2542
|
}),
|
|
2114
2543
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2115
2544
|
}, withErrorHandling("unlink_template_from_workflows", async (args) => {
|
|
@@ -2126,21 +2555,22 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
2126
2555
|
}
|
|
2127
2556
|
var LEGACY_DATA_TYPE_ALIASES;
|
|
2128
2557
|
var init_templates = __esm(() => {
|
|
2558
|
+
init_pagination();
|
|
2129
2559
|
init_schemas();
|
|
2130
2560
|
LEGACY_DATA_TYPE_ALIASES = { TEXT: "STRING", URL: "LINK" };
|
|
2131
2561
|
});
|
|
2132
2562
|
|
|
2133
2563
|
// src/tools/variables.ts
|
|
2134
|
-
import { z as
|
|
2564
|
+
import { z as z11 } from "zod";
|
|
2135
2565
|
function registerVariableTools(server, ctx, helpers) {
|
|
2136
2566
|
const { withErrorHandling } = helpers;
|
|
2137
2567
|
server.registerTool("create_variable", {
|
|
2138
2568
|
title: "Create Variable",
|
|
2139
2569
|
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.",
|
|
2140
2570
|
inputSchema: strictSchema({
|
|
2141
|
-
key:
|
|
2142
|
-
value:
|
|
2143
|
-
dataType:
|
|
2571
|
+
key: z11.string().describe("Variable key (must be unique within scope). Referenced in prompts as @key."),
|
|
2572
|
+
value: z11.string().describe("Variable value"),
|
|
2573
|
+
dataType: z11.enum(["STRING", "SECRET", "OBJECT", "ARRAY"]).optional().describe("Data type of the value (defaults to STRING)")
|
|
2144
2574
|
}),
|
|
2145
2575
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
2146
2576
|
}, withErrorHandling("create_variable", async (args) => {
|
|
@@ -2164,14 +2594,15 @@ function registerVariableTools(server, ctx, helpers) {
|
|
|
2164
2594
|
const variables = await ctx.client.variable.list();
|
|
2165
2595
|
return jsonResult({
|
|
2166
2596
|
variables: variables.map(withVariableDashboardUrl),
|
|
2167
|
-
count: variables.length
|
|
2597
|
+
count: variables.length,
|
|
2598
|
+
pagination: completeList(variables.length)
|
|
2168
2599
|
});
|
|
2169
2600
|
}));
|
|
2170
2601
|
server.registerTool("get_variable", {
|
|
2171
2602
|
title: "Get Variable",
|
|
2172
2603
|
description: "Get a specific variable by its ID.",
|
|
2173
2604
|
inputSchema: strictSchema({
|
|
2174
|
-
variableId:
|
|
2605
|
+
variableId: z11.string().min(1).describe("The variable ID")
|
|
2175
2606
|
}),
|
|
2176
2607
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2177
2608
|
}, withErrorHandling("get_variable", async (args) => {
|
|
@@ -2182,10 +2613,10 @@ function registerVariableTools(server, ctx, helpers) {
|
|
|
2182
2613
|
title: "Update Variable",
|
|
2183
2614
|
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.",
|
|
2184
2615
|
inputSchema: strictSchema({
|
|
2185
|
-
variableId:
|
|
2186
|
-
key:
|
|
2187
|
-
value:
|
|
2188
|
-
dataType:
|
|
2616
|
+
variableId: z11.string().min(1).describe("The variable ID to update"),
|
|
2617
|
+
key: z11.string().optional().describe("New variable key"),
|
|
2618
|
+
value: z11.string().optional().describe("New variable value"),
|
|
2619
|
+
dataType: z11.enum(["STRING", "SECRET", "OBJECT", "ARRAY"]).optional().describe("New data type")
|
|
2189
2620
|
}),
|
|
2190
2621
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
2191
2622
|
}, withErrorHandling("update_variable", async (args) => {
|
|
@@ -2201,8 +2632,8 @@ function registerVariableTools(server, ctx, helpers) {
|
|
|
2201
2632
|
title: "Delete Variable",
|
|
2202
2633
|
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.",
|
|
2203
2634
|
inputSchema: strictSchema({
|
|
2204
|
-
variableId:
|
|
2205
|
-
confirmed:
|
|
2635
|
+
variableId: z11.string().min(1).describe("The variable ID to delete"),
|
|
2636
|
+
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.")
|
|
2206
2637
|
}),
|
|
2207
2638
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
2208
2639
|
}, withErrorHandling("delete_variable", async (args) => {
|
|
@@ -2227,12 +2658,13 @@ function registerVariableTools(server, ctx, helpers) {
|
|
|
2227
2658
|
}));
|
|
2228
2659
|
}
|
|
2229
2660
|
var init_variables = __esm(() => {
|
|
2661
|
+
init_pagination();
|
|
2230
2662
|
init_schemas();
|
|
2231
2663
|
});
|
|
2232
2664
|
|
|
2233
2665
|
// src/tools/workflows.ts
|
|
2234
2666
|
import { WorkflowStatusFilter } from "@kadoa/node-sdk";
|
|
2235
|
-
import { z as
|
|
2667
|
+
import { z as z12 } from "zod";
|
|
2236
2668
|
function isRealTimeInterval(interval) {
|
|
2237
2669
|
return interval?.toUpperCase().replace("-", "_") === "REAL_TIME";
|
|
2238
2670
|
}
|
|
@@ -2289,6 +2721,49 @@ function mapRunStatus(state) {
|
|
|
2289
2721
|
return state ?? "unknown";
|
|
2290
2722
|
}
|
|
2291
2723
|
}
|
|
2724
|
+
function changeResult(change) {
|
|
2725
|
+
const build = (data, differences) => ({
|
|
2726
|
+
id: change.id,
|
|
2727
|
+
workflowId: change.workflowId,
|
|
2728
|
+
url: change.url,
|
|
2729
|
+
summary: change.summary,
|
|
2730
|
+
screenshotUrl: change.screenshotUrl,
|
|
2731
|
+
data,
|
|
2732
|
+
differences,
|
|
2733
|
+
createdAt: change.createdAt
|
|
2734
|
+
});
|
|
2735
|
+
const full = build(change.data, change.differences);
|
|
2736
|
+
if (jsonBytes(full) <= RESULT_BUDGET_BYTES)
|
|
2737
|
+
return full;
|
|
2738
|
+
const rows = change.data ?? [];
|
|
2739
|
+
const exportHint = `For the full current dataset use export_data with workflowId ${change.workflowId}.`;
|
|
2740
|
+
if (jsonBytes(build([], change.differences)) <= RESULT_BUDGET_BYTES - TRUNCATION_RESERVE_BYTES) {
|
|
2741
|
+
const fit = fitItems({ items: rows, build: (kept) => build(kept, change.differences) });
|
|
2742
|
+
if (!fit.truncated)
|
|
2743
|
+
return build(fit.kept, change.differences);
|
|
2744
|
+
return {
|
|
2745
|
+
...build(fit.kept, change.differences),
|
|
2746
|
+
...truncationNotice({
|
|
2747
|
+
kept: fit.kept.length,
|
|
2748
|
+
omitted: fit.omitted,
|
|
2749
|
+
noun: "data rows",
|
|
2750
|
+
hint: `differences is complete. ${exportHint}`
|
|
2751
|
+
})
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
const fit = fitItems({ items: change.differences ?? [], build: (kept) => build([], kept) });
|
|
2755
|
+
if (!fit.truncated)
|
|
2756
|
+
return build([], fit.kept);
|
|
2757
|
+
return {
|
|
2758
|
+
...build([], fit.kept),
|
|
2759
|
+
...truncationNotice({
|
|
2760
|
+
kept: fit.kept.length,
|
|
2761
|
+
omitted: fit.omitted,
|
|
2762
|
+
noun: "differences",
|
|
2763
|
+
hint: `All ${rows.length} data rows omitted. ${exportHint}`
|
|
2764
|
+
})
|
|
2765
|
+
};
|
|
2766
|
+
}
|
|
2292
2767
|
function jsonEq(a, b) {
|
|
2293
2768
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
2294
2769
|
}
|
|
@@ -2302,13 +2777,32 @@ function diffWorkflowAuditEntry(entry) {
|
|
|
2302
2777
|
if (!jsonEq(prev[key], next[key]))
|
|
2303
2778
|
changed.push(key);
|
|
2304
2779
|
}
|
|
2305
|
-
|
|
2306
|
-
const nextPrompt = next["additionalData"]?.["userPrompt"];
|
|
2307
|
-
if (!jsonEq(prevPrompt, nextPrompt))
|
|
2780
|
+
if (!jsonEq(auditFieldValue(prev, "additionalData.userPrompt"), auditFieldValue(next, "additionalData.userPrompt"))) {
|
|
2308
2781
|
changed.push("additionalData.userPrompt");
|
|
2782
|
+
}
|
|
2309
2783
|
return changed;
|
|
2310
2784
|
}
|
|
2311
|
-
function
|
|
2785
|
+
function auditFieldValue(snapshot, field) {
|
|
2786
|
+
if (field === "additionalData.userPrompt") {
|
|
2787
|
+
return snapshot["additionalData"]?.["userPrompt"];
|
|
2788
|
+
}
|
|
2789
|
+
return snapshot[field];
|
|
2790
|
+
}
|
|
2791
|
+
function summarizeWorkflowAuditEntry(entry) {
|
|
2792
|
+
const changedFields = diffWorkflowAuditEntry(entry);
|
|
2793
|
+
const prev = entry.previousValue ?? {};
|
|
2794
|
+
const next = entry.newValue ?? {};
|
|
2795
|
+
const changes = changedFields.map((field) => ({
|
|
2796
|
+
field,
|
|
2797
|
+
before: previewValue(auditFieldValue(prev, field)),
|
|
2798
|
+
after: previewValue(auditFieldValue(next, field))
|
|
2799
|
+
}));
|
|
2800
|
+
return { changedFields, changes };
|
|
2801
|
+
}
|
|
2802
|
+
function historyEmptyHint(includeSnapshots) {
|
|
2803
|
+
return includeSnapshots ? "A single entry is too large to return inline. Retry without includeSnapshots for the diff summary." : "A single entry is too large to return inline even without snapshots. Retry with a narrower page (e.g. limit=1) or fewer fields.";
|
|
2804
|
+
}
|
|
2805
|
+
function registerWorkflowCoreTools(server, ctx, helpers, defaultProductType) {
|
|
2312
2806
|
const { withErrorHandling } = helpers;
|
|
2313
2807
|
function resolveUrls(args) {
|
|
2314
2808
|
const urls = args.urls ?? (args.url ? [args.url] : []);
|
|
@@ -2359,7 +2853,7 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2359
2853
|
const channels = {};
|
|
2360
2854
|
if (n.email) {
|
|
2361
2855
|
if (typeof n.email === "object" && n.email.recipients?.length) {
|
|
2362
|
-
channels.EMAIL = { name: "
|
|
2856
|
+
channels.EMAIL = { name: mcpChannelName("EMAIL", n.email), recipients: n.email.recipients };
|
|
2363
2857
|
} else {
|
|
2364
2858
|
channels.EMAIL = true;
|
|
2365
2859
|
}
|
|
@@ -2368,7 +2862,7 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2368
2862
|
channels.WEBSOCKET = true;
|
|
2369
2863
|
if (n.webhook) {
|
|
2370
2864
|
const webhookChannel = {
|
|
2371
|
-
name: "
|
|
2865
|
+
name: mcpChannelName("WEBHOOK", { webhookUrl: n.webhook.url }),
|
|
2372
2866
|
webhookUrl: n.webhook.url,
|
|
2373
2867
|
httpMethod: n.webhook.httpMethod || "POST"
|
|
2374
2868
|
};
|
|
@@ -2377,7 +2871,7 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2377
2871
|
channels.WEBHOOK = webhookChannel;
|
|
2378
2872
|
}
|
|
2379
2873
|
if (n.slack) {
|
|
2380
|
-
const slackChannel = { name: "
|
|
2874
|
+
const slackChannel = { name: mcpChannelName("SLACK", n.slack) };
|
|
2381
2875
|
if (n.slack.webhookUrl)
|
|
2382
2876
|
slackChannel.webhookUrl = n.slack.webhookUrl;
|
|
2383
2877
|
if (n.slack.slackChannelId)
|
|
@@ -2416,18 +2910,18 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2416
2910
|
|
|
2417
2911
|
` + "Create a data extraction workflow using agentic navigation. Supports one-time or scheduled runs. " + "If entity and schema are provided, they guide the extraction; otherwise the AI agent auto-detects the schema from the page. " + "The workflow runs asynchronously and may take several minutes. Do NOT poll or sleep-wait for completion. " + `Return the workflow ID to the user and let them check back later with get_workflow or fetch_data.
|
|
2418
2912
|
|
|
2419
|
-
` + "PREFER TEMPLATES: If the user's request matches an existing template, instantiate it via `templateId` instead of writing a fresh prompt/schema. " + "Use `list_templates` to discover available templates and `get_template` to inspect schemas before deciding. " + "When creating from a template, call this tool with the template's `templateId`, the source `urls`, optional `templateVersion`, and optional `prompt` containing only workflow-specific additional instructions. Never copy the template prompt, entity, or schema into the call, and never silently match or rewrite inline configuration.\n\n" + "NOTE: This tool is for one-time or scheduled extraction ONLY. " + "For continuous realtime monitoring that watches a page for data changes and sends alerts, use the create_realtime_monitor tool instead.",
|
|
2913
|
+
` + "PREFER TEMPLATES: If the user's request matches an existing template, instantiate it via `templateId` instead of writing a fresh prompt/schema. " + "Use `list_templates` to discover available templates and `get_template` to inspect schemas before deciding. " + "When creating from a template, call this tool with the template's `templateId`, the source `urls`, optional `templateVersion`, and optional `prompt` containing only workflow-specific additional instructions. Never copy the template prompt, entity, or schema into the call, and never silently match or rewrite inline configuration.\n\n" + "NOTE: This tool is for one-time or scheduled extraction ONLY. " + (defaultProductType === "realtime" ? `This team's default product is realtime monitoring. Do not call this tool unless the user explicitly asked for a one-time run (e.g. "once", "just the current list") or a schedule (e.g. "daily"). A request to get, extract, or scrape data that names no frequency is not explicit: call create_realtime_monitor instead.` : "For continuous realtime monitoring that watches a page for data changes and sends alerts, use the create_realtime_monitor tool instead."),
|
|
2420
2914
|
inputSchema: strictSchema({
|
|
2421
2915
|
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.'),
|
|
2422
2916
|
...extractionInputShape,
|
|
2423
2917
|
...urlInputShape,
|
|
2424
|
-
templateId:
|
|
2425
|
-
templateVersion:
|
|
2426
|
-
description:
|
|
2427
|
-
tags:
|
|
2428
|
-
limit:
|
|
2918
|
+
templateId: z12.string().optional().describe("Instantiate this workflow from a published template. Pass the templateId, source urls, optional templateVersion, and optional prompt with workflow-specific additional instructions. Do not copy the template's shared prompt, entity, or schema into this call - they are inherited from the published template version, and the workflow's output conforms to its declared schema. Discover templates via list_templates and get_template."),
|
|
2919
|
+
templateVersion: z12.preprocess(coerceNumber(), z12.number()).optional().describe("Specific published template version (integer) to instantiate. Defaults to the latest published version when templateId is set."),
|
|
2920
|
+
description: z12.string().max(500).optional().describe("Description of what this workflow does (max 500 characters)"),
|
|
2921
|
+
tags: z12.preprocess(coerceArray(true), z12.array(z12.string())).optional().describe("Tags for organizing workflows"),
|
|
2922
|
+
limit: z12.preprocess(coerceNumber(), z12.number()).optional().describe("Maximum number of records to extract per run. Useful for limiting scope or cost control."),
|
|
2429
2923
|
updateInterval: UpdateIntervalSchema.optional().describe("How often the workflow runs. Defaults to ONLY_ONCE. Use CUSTOM with the 'schedules' field for cron-based scheduling."),
|
|
2430
|
-
schedules:
|
|
2924
|
+
schedules: z12.preprocess(coerceArray(true), z12.array(z12.string())).optional().describe("Cron expressions for CUSTOM updateInterval (e.g. '0 8 * * 2' for Tuesdays at 8am UTC). Requires updateInterval='CUSTOM'."),
|
|
2431
2925
|
...notificationsInputShape
|
|
2432
2926
|
}),
|
|
2433
2927
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
@@ -2441,13 +2935,25 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2441
2935
|
}
|
|
2442
2936
|
const n = args.notifications;
|
|
2443
2937
|
const hasNotifications = !!(n && (n.email || n.webhook || n.slack || n.websocket));
|
|
2938
|
+
async function createOrUnknown(create) {
|
|
2939
|
+
try {
|
|
2940
|
+
return await create();
|
|
2941
|
+
} catch (error) {
|
|
2942
|
+
if (!isAmbiguousFailure(error))
|
|
2943
|
+
throw error;
|
|
2944
|
+
console.error("[Tool Error] create_workflow:", safeErrorSummary(error));
|
|
2945
|
+
return {
|
|
2946
|
+
outcomeUnknown: errorResult(`Workflow creation failed (${shortErrorReason(error)}). It is unknown whether the workflow was created. Check list_workflows for it before retrying, and do NOT create it again if it exists.`)
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2444
2950
|
let workflowId;
|
|
2445
2951
|
const warnings = [];
|
|
2446
2952
|
if (args.templateId) {
|
|
2447
2953
|
if (args.entity || args.schema) {
|
|
2448
2954
|
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.");
|
|
2449
2955
|
}
|
|
2450
|
-
const
|
|
2956
|
+
const created = await createOrUnknown(() => ctx.client.workflow.create({
|
|
2451
2957
|
urls,
|
|
2452
2958
|
name: args.name || "Untitled Workflow",
|
|
2453
2959
|
description: args.description,
|
|
@@ -2458,8 +2964,10 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2458
2964
|
templateId: args.templateId,
|
|
2459
2965
|
templateVersion: args.templateVersion,
|
|
2460
2966
|
userPrompt: args.prompt
|
|
2461
|
-
});
|
|
2462
|
-
|
|
2967
|
+
}));
|
|
2968
|
+
if ("outcomeUnknown" in created)
|
|
2969
|
+
return created.outcomeUnknown;
|
|
2970
|
+
workflowId = created.id;
|
|
2463
2971
|
} else {
|
|
2464
2972
|
if (!args.prompt) {
|
|
2465
2973
|
return errorResult("'prompt' is required unless 'templateId' is provided.");
|
|
@@ -2473,7 +2981,9 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2473
2981
|
description: args.description,
|
|
2474
2982
|
schedules: args.schedules
|
|
2475
2983
|
});
|
|
2476
|
-
const workflow = await builder.create();
|
|
2984
|
+
const workflow = await createOrUnknown(() => builder.create());
|
|
2985
|
+
if ("outcomeUnknown" in workflow)
|
|
2986
|
+
return workflow.outcomeUnknown;
|
|
2477
2987
|
workflowId = workflow.workflowId;
|
|
2478
2988
|
const needsUpdate = args.limit !== undefined || args.tags && args.tags.length > 0;
|
|
2479
2989
|
if (needsUpdate) {
|
|
@@ -2546,31 +3056,22 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2546
3056
|
} else {
|
|
2547
3057
|
text = `${cause}. The realtime monitor was not created. ${channels} Delete one with delete_notification_channel only after confirming via list_notification_settings that no other workflow uses it.`;
|
|
2548
3058
|
}
|
|
2549
|
-
return {
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
text: JSON.stringify({
|
|
2554
|
-
error: text,
|
|
2555
|
-
notificationChannelIds,
|
|
2556
|
-
...workflowId ? { workflowId, dashboardUrl: workflowDashboardUrl(workflowId) } : {}
|
|
2557
|
-
}, null, 2)
|
|
2558
|
-
}
|
|
2559
|
-
],
|
|
2560
|
-
isError: true
|
|
2561
|
-
};
|
|
3059
|
+
return errorResult(text, {
|
|
3060
|
+
notificationChannelIds,
|
|
3061
|
+
...workflowId ? { workflowId, dashboardUrl: workflowDashboardUrl(workflowId) } : {}
|
|
3062
|
+
});
|
|
2562
3063
|
}
|
|
2563
3064
|
server.registerTool("create_realtime_monitor", {
|
|
2564
3065
|
title: "Create Realtime Monitor",
|
|
2565
|
-
description: "Create a realtime monitoring workflow that continuously watches a page for data changes and sends alerts. " + "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.
|
|
3066
|
+
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.
|
|
2566
3067
|
|
|
2567
3068
|
` + "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.",
|
|
2568
3069
|
inputSchema: strictSchema({
|
|
2569
3070
|
prompt: extractionPromptInput.describe("Optional natural language instructions describing what to monitor and which changes matter. If omitted, the Assistant determines what to watch."),
|
|
2570
3071
|
...extractionInputShape,
|
|
2571
3072
|
...urlInputShape,
|
|
2572
|
-
description:
|
|
2573
|
-
tags:
|
|
3073
|
+
description: z12.string().max(500).optional().describe("Description of what this monitor watches (max 500 characters)"),
|
|
3074
|
+
tags: z12.preprocess(coerceArray(true), z12.array(z12.string())).optional().describe("Tags for organizing monitors"),
|
|
2574
3075
|
...requiredNotificationsInputShape
|
|
2575
3076
|
}),
|
|
2576
3077
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
@@ -2621,20 +3122,27 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2621
3122
|
title: "List Workflows",
|
|
2622
3123
|
description: `List workflows with customer-visible status, raw workflow and run states, Assistant session availability, template version drift, realtime freshness or health, and awaitingUserInput when the requesting user must act. Uses one paginated list request; use get_workflow only when you need full details for a selected workflow. To answer "what needs my attention", pass statusFilters: ["group:attention"]; this returns exactly the dashboard's "Needs your attention" group. In group:attention, a row with awaitingUserInput.prompt is an Assistant question: use get_workflow_assistant then answer_workflow_assistant_question. A row with awaitingUserInput but no prompt is a setup the user left unfinished: use get_workflow_assistant to see where it stopped, then resume_workflow_assistant or request_workflow_update. A row with displayState PREVIEW and no awaitingUserInput is preview data: use approve_workflow. Workflows with an open support ticket are excluded from group:attention and appear under group:support.`,
|
|
2623
3124
|
inputSchema: strictSchema({
|
|
2624
|
-
limit:
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
3125
|
+
limit: limitInput({
|
|
3126
|
+
noun: "Workflows",
|
|
3127
|
+
defaultLimit: LIST_WORKFLOWS_DEFAULT_LIMIT,
|
|
3128
|
+
max: LIST_WORKFLOWS_MAX_LIMIT
|
|
3129
|
+
}),
|
|
3130
|
+
skip: offsetInput("skip", "workflows"),
|
|
3131
|
+
state: z12.enum(["ACTIVE", "FAILED", "PAUSED", "PREVIEW"]).optional().describe("Filter by state (ACTIVE, FAILED, PAUSED, PREVIEW)"),
|
|
3132
|
+
search: z12.string().optional().describe("Free-text filter matching workflow name, URL, or workflow ID"),
|
|
3133
|
+
tags: z12.array(z12.string()).optional().describe("Filter to workflows having any of these tags"),
|
|
3134
|
+
monitoring: z12.preprocess(coerceBoolean(), z12.boolean()).optional().describe("Filter by monitoring/real-time status"),
|
|
3135
|
+
updateInterval: z12.enum(["HOURLY", "DAILY", "WEEKLY", "MONTHLY", "REAL_TIME"]).optional().describe("Filter by scheduled update frequency or realtime workflows"),
|
|
3136
|
+
templateId: z12.string().optional().describe("Filter to workflows instantiated from this template ID"),
|
|
3137
|
+
statusFilters: z12.array(z12.enum(Object.values(WorkflowStatusFilter))).optional().describe("Dashboard category filters. group:attention = workflows waiting on the requesting user (Assistant question, unfinished setup, or preview approval). Other groups: working, support, failed, active, complete, paused.")
|
|
2633
3138
|
}),
|
|
3139
|
+
outputSchema: ListWorkflowsOutput,
|
|
2634
3140
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2635
3141
|
}, withErrorHandling("list_workflows", async (args) => {
|
|
2636
|
-
const
|
|
2637
|
-
|
|
3142
|
+
const limit = args.limit ?? LIST_WORKFLOWS_DEFAULT_LIMIT;
|
|
3143
|
+
const skip = args.skip ?? 0;
|
|
3144
|
+
const rows = await ctx.client.workflow.list({
|
|
3145
|
+
limit: limit + 1,
|
|
2638
3146
|
state: args.state,
|
|
2639
3147
|
...args.skip !== undefined && { skip: args.skip },
|
|
2640
3148
|
...args.search !== undefined && { search: args.search },
|
|
@@ -2644,6 +3152,7 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2644
3152
|
...args.templateId !== undefined && { templateId: args.templateId },
|
|
2645
3153
|
...args.statusFilters !== undefined && { statusFilters: args.statusFilters }
|
|
2646
3154
|
});
|
|
3155
|
+
const workflows = rows.slice(0, limit);
|
|
2647
3156
|
return jsonResult({
|
|
2648
3157
|
workflows: workflows.map((w) => ({
|
|
2649
3158
|
id: w.id,
|
|
@@ -2680,15 +3189,23 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2680
3189
|
},
|
|
2681
3190
|
awaitingUserInput: w.awaitingUserInput ?? null
|
|
2682
3191
|
})),
|
|
2683
|
-
|
|
3192
|
+
pagination: offsetPage({
|
|
3193
|
+
args,
|
|
3194
|
+
param: "skip",
|
|
3195
|
+
offset: skip,
|
|
3196
|
+
limit,
|
|
3197
|
+
returned: workflows.length,
|
|
3198
|
+
hasMore: rows.length > limit
|
|
3199
|
+
})
|
|
2684
3200
|
});
|
|
2685
3201
|
}));
|
|
2686
3202
|
server.registerTool("get_workflow", {
|
|
2687
3203
|
title: "Get Workflow",
|
|
2688
3204
|
description: "Get canonical workflow details, including Assistant/session linkage, extraction intent, template-controlled parts, stale-data state, job/run state, schedule timezone, location, monitoring, validation, and realtime health. For Assistant-built workflows, extractionSpec - not the legacy prompt - is authoritative.",
|
|
2689
3205
|
inputSchema: strictSchema({
|
|
2690
|
-
workflowId:
|
|
3206
|
+
workflowId: z12.string().min(1).describe("The workflow ID")
|
|
2691
3207
|
}),
|
|
3208
|
+
outputSchema: GetWorkflowOutput,
|
|
2692
3209
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2693
3210
|
}, withErrorHandling("get_workflow", async (args) => {
|
|
2694
3211
|
const workflow = await ctx.client.workflow.get(args.workflowId);
|
|
@@ -2705,9 +3222,9 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2705
3222
|
const hasTemplate = workflow.templateId != null || workflow.templateVersion != null;
|
|
2706
3223
|
const isRealtime = workflow.isRealTime === true || isRealTimeInterval(workflow.updateInterval);
|
|
2707
3224
|
return jsonResult({
|
|
2708
|
-
id: workflow.id,
|
|
3225
|
+
id: workflow.id ?? args.workflowId,
|
|
2709
3226
|
name: workflow.name,
|
|
2710
|
-
dashboardUrl: workflowDashboardUrl(workflow.id),
|
|
3227
|
+
dashboardUrl: workflowDashboardUrl(workflow.id ?? args.workflowId),
|
|
2711
3228
|
description: workflow.description,
|
|
2712
3229
|
status: deriveStatusLabel(workflow),
|
|
2713
3230
|
state: workflow.state ?? null,
|
|
@@ -2775,9 +3292,9 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2775
3292
|
title: "Request Workflow Update",
|
|
2776
3293
|
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.",
|
|
2777
3294
|
inputSchema: strictSchema({
|
|
2778
|
-
workflowId:
|
|
2779
|
-
instructions:
|
|
2780
|
-
threadId:
|
|
3295
|
+
workflowId: z12.string().min(1).describe("The existing workflow ID; it will not change"),
|
|
3296
|
+
instructions: z12.string().min(1).describe("Natural-language instructions for the workflow Assistant"),
|
|
3297
|
+
threadId: z12.string().optional().describe("Optional Assistant thread ID to continue")
|
|
2781
3298
|
}),
|
|
2782
3299
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
|
|
2783
3300
|
}, withErrorHandling("request_workflow_update", async (args) => {
|
|
@@ -2805,69 +3322,102 @@ function registerWorkflowHistoryTools(server, ctx, helpers) {
|
|
|
2805
3322
|
const { withErrorHandling } = helpers;
|
|
2806
3323
|
server.registerTool("get_workflow_history", {
|
|
2807
3324
|
title: "Get Workflow History",
|
|
2808
|
-
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),
|
|
3325
|
+
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).",
|
|
2809
3326
|
inputSchema: strictSchema({
|
|
2810
|
-
workflowId:
|
|
2811
|
-
page:
|
|
2812
|
-
limit:
|
|
3327
|
+
workflowId: z12.string().min(1).describe("The workflow ID"),
|
|
3328
|
+
page: pageInput(),
|
|
3329
|
+
limit: limitInput({
|
|
3330
|
+
noun: "Entries",
|
|
3331
|
+
defaultLimit: WORKFLOW_HISTORY_DEFAULT_LIMIT,
|
|
3332
|
+
max: WORKFLOW_HISTORY_MAX_LIMIT
|
|
3333
|
+
}),
|
|
3334
|
+
includeSnapshots: z12.preprocess(coerceBoolean(), z12.boolean()).optional().describe("Include full previousValue/newValue workflow snapshots per entry (default false; large)")
|
|
2813
3335
|
}),
|
|
2814
3336
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2815
3337
|
}, withErrorHandling("get_workflow_history", async (args) => {
|
|
2816
|
-
const
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
const entries = (response.logEntries ?? []).map((e) =>
|
|
2821
|
-
|
|
2822
|
-
operationType: e.operationType,
|
|
2823
|
-
createdAt: e.createdAt,
|
|
2824
|
-
userEmail: e.userEmail,
|
|
2825
|
-
userId: e.userId,
|
|
2826
|
-
requestSource: e.requestSource,
|
|
2827
|
-
authMethod: e.authMethod,
|
|
2828
|
-
changedFields: diffWorkflowAuditEntry({
|
|
3338
|
+
const page = args.page ?? 1;
|
|
3339
|
+
const limit = args.limit ?? WORKFLOW_HISTORY_DEFAULT_LIMIT;
|
|
3340
|
+
const response = await ctx.client.workflow.getAuditLog(args.workflowId, { page, limit });
|
|
3341
|
+
const includeSnapshots = args.includeSnapshots === true;
|
|
3342
|
+
const entries = (response.logEntries ?? []).map((e) => {
|
|
3343
|
+
const summary = summarizeWorkflowAuditEntry({
|
|
2829
3344
|
previousValue: e.previousValue,
|
|
2830
3345
|
newValue: e.newValue
|
|
2831
|
-
})
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
3346
|
+
});
|
|
3347
|
+
return {
|
|
3348
|
+
id: e.id,
|
|
3349
|
+
operationType: e.operationType,
|
|
3350
|
+
createdAt: e.createdAt,
|
|
3351
|
+
userEmail: e.userEmail,
|
|
3352
|
+
userId: e.userId,
|
|
3353
|
+
requestSource: e.requestSource,
|
|
3354
|
+
authMethod: e.authMethod,
|
|
3355
|
+
changedFields: summary.changedFields,
|
|
3356
|
+
changes: summary.changes,
|
|
3357
|
+
...includeSnapshots && { previousValue: e.previousValue ?? null, newValue: e.newValue ?? null }
|
|
3358
|
+
};
|
|
3359
|
+
});
|
|
3360
|
+
const totalCount = response.pagination?.totalCount ?? response.logEntriesCount;
|
|
3361
|
+
const totalPages = response.pagination?.totalPages;
|
|
3362
|
+
const result = budgetNumberedPage({
|
|
3363
|
+
items: entries,
|
|
3364
|
+
pagination: numberedPage({ args, page, limit, returned: entries.length, totalCount, totalPages }),
|
|
3365
|
+
args,
|
|
3366
|
+
page,
|
|
3367
|
+
limit,
|
|
3368
|
+
noun: "entries",
|
|
3369
|
+
resumeHint: includeSnapshots ? "Call again with pagination.next for the following entries, or drop includeSnapshots to get diff summaries only." : "Call again with pagination.next for the following entries.",
|
|
3370
|
+
emptyHint: historyEmptyHint(includeSnapshots),
|
|
3371
|
+
build: (kept, meta) => ({
|
|
3372
|
+
workflowId: response.id,
|
|
3373
|
+
...totalCount !== undefined && { totalCount },
|
|
3374
|
+
page,
|
|
3375
|
+
...totalPages !== undefined && kept.length === entries.length && { totalPages },
|
|
3376
|
+
entries: kept,
|
|
3377
|
+
pagination: meta
|
|
3378
|
+
})
|
|
2841
3379
|
});
|
|
3380
|
+
return jsonResult(result);
|
|
2842
3381
|
}));
|
|
2843
3382
|
server.registerTool("list_workflow_runs", {
|
|
2844
3383
|
title: "List Workflow Runs",
|
|
2845
3384
|
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.",
|
|
2846
3385
|
inputSchema: strictSchema({
|
|
2847
|
-
workflowId:
|
|
2848
|
-
status:
|
|
2849
|
-
page:
|
|
2850
|
-
limit:
|
|
3386
|
+
workflowId: z12.string().min(1).describe("The workflow ID"),
|
|
3387
|
+
status: z12.enum(["success", "failed", "in_progress"]).optional().describe("Filter runs by outcome"),
|
|
3388
|
+
page: pageInput(),
|
|
3389
|
+
limit: limitInput({ noun: "Runs", defaultLimit: WORKFLOW_RUNS_DEFAULT_LIMIT, max: WORKFLOW_RUNS_MAX_LIMIT })
|
|
2851
3390
|
}),
|
|
2852
3391
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2853
3392
|
}, withErrorHandling("list_workflow_runs", async (args) => {
|
|
3393
|
+
const page = args.page ?? 1;
|
|
3394
|
+
const limit = args.limit ?? WORKFLOW_RUNS_DEFAULT_LIMIT;
|
|
2854
3395
|
const response = await ctx.client.workflow.listWorkflowRuns(args.workflowId, {
|
|
2855
|
-
page
|
|
2856
|
-
limit
|
|
3396
|
+
page,
|
|
3397
|
+
limit,
|
|
2857
3398
|
status: args.status
|
|
2858
3399
|
});
|
|
3400
|
+
const runs = (response.workflowRuns ?? []).map((r) => ({
|
|
3401
|
+
id: r.id,
|
|
3402
|
+
status: mapRunStatus(r.state),
|
|
3403
|
+
startedAt: r.startedAt,
|
|
3404
|
+
finishedAt: r.finishedAt,
|
|
3405
|
+
records: r.records,
|
|
3406
|
+
errors: r.errors
|
|
3407
|
+
}));
|
|
3408
|
+
const totalCount = response.totalCount ?? response.pagination?.totalCount;
|
|
2859
3409
|
return jsonResult({
|
|
2860
3410
|
workflowId: response.workflowId,
|
|
2861
|
-
runs
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
3411
|
+
runs,
|
|
3412
|
+
...totalCount !== undefined && { totalCount },
|
|
3413
|
+
pagination: numberedPage({
|
|
3414
|
+
args,
|
|
3415
|
+
page,
|
|
3416
|
+
limit,
|
|
3417
|
+
returned: runs.length,
|
|
3418
|
+
totalCount,
|
|
3419
|
+
totalPages: response.pagination?.totalPages
|
|
3420
|
+
})
|
|
2871
3421
|
});
|
|
2872
3422
|
}));
|
|
2873
3423
|
}
|
|
@@ -2877,8 +3427,8 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
2877
3427
|
title: "Run Workflow",
|
|
2878
3428
|
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.",
|
|
2879
3429
|
inputSchema: strictSchema({
|
|
2880
|
-
workflowId:
|
|
2881
|
-
limit:
|
|
3430
|
+
workflowId: z12.string().min(1).describe("The workflow ID to run"),
|
|
3431
|
+
limit: z12.preprocess(coerceNumber(), z12.number()).optional().describe("Maximum number of records to extract (default: 1000)")
|
|
2882
3432
|
}),
|
|
2883
3433
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
2884
3434
|
}, withErrorHandling("run_workflow", async (args) => {
|
|
@@ -2901,49 +3451,72 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
2901
3451
|
title: "Fetch Workflow Data",
|
|
2902
3452
|
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 'Validating'). Do NOT poll or sleep-wait for completion.",
|
|
2903
3453
|
inputSchema: strictSchema({
|
|
2904
|
-
workflowId:
|
|
2905
|
-
limit:
|
|
2906
|
-
|
|
3454
|
+
workflowId: z12.string().min(1).describe("The workflow ID"),
|
|
3455
|
+
limit: limitInput({
|
|
3456
|
+
noun: "Rows",
|
|
3457
|
+
defaultLimit: FETCH_DATA_DEFAULT_LIMIT,
|
|
3458
|
+
max: FETCH_DATA_MAX_LIMIT,
|
|
3459
|
+
overMaxHint: "Use export_data for full datasets."
|
|
3460
|
+
}),
|
|
3461
|
+
page: pageInput(),
|
|
2907
3462
|
filters: dataFiltersInput,
|
|
2908
|
-
sortBy:
|
|
2909
|
-
order:
|
|
3463
|
+
sortBy: z12.string().optional().describe("Field name to sort by"),
|
|
3464
|
+
order: z12.enum(["asc", "desc"]).optional().describe("Sort order: asc or desc")
|
|
2910
3465
|
}),
|
|
3466
|
+
outputSchema: FetchDataOutput,
|
|
2911
3467
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2912
3468
|
}, withErrorHandling("fetch_data", async (args) => {
|
|
2913
|
-
const
|
|
2914
|
-
|
|
2915
|
-
return errorResult(`limit ${requestedLimit} exceeds max ${FETCH_DATA_MAX_LIMIT}. Use export_data for full datasets.`);
|
|
2916
|
-
}
|
|
3469
|
+
const limit = args.limit ?? FETCH_DATA_DEFAULT_LIMIT;
|
|
3470
|
+
const page = args.page ?? 1;
|
|
2917
3471
|
const data = await ctx.client.extraction.fetchData({
|
|
2918
3472
|
workflowId: args.workflowId,
|
|
2919
|
-
limit
|
|
2920
|
-
page
|
|
3473
|
+
limit,
|
|
3474
|
+
page,
|
|
2921
3475
|
filters: args.filters === undefined ? undefined : JSON.stringify(args.filters),
|
|
2922
3476
|
sortBy: args.sortBy,
|
|
2923
3477
|
order: args.order
|
|
2924
3478
|
});
|
|
2925
|
-
const totalCount = data.pagination?.totalCount
|
|
2926
|
-
const
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
}
|
|
3479
|
+
const totalCount = data.pagination?.totalCount;
|
|
3480
|
+
const pagination = numberedPage({
|
|
3481
|
+
args,
|
|
3482
|
+
page,
|
|
3483
|
+
limit,
|
|
3484
|
+
returned: data.data.length,
|
|
3485
|
+
totalCount,
|
|
3486
|
+
totalPages: data.pagination?.totalPages
|
|
3487
|
+
});
|
|
3488
|
+
const result = budgetNumberedPage({
|
|
3489
|
+
items: data.data,
|
|
3490
|
+
pagination,
|
|
3491
|
+
args,
|
|
3492
|
+
page,
|
|
3493
|
+
limit,
|
|
3494
|
+
noun: "rows",
|
|
3495
|
+
resumeHint: "Call fetch_data again with pagination.next for the following rows, or use export_data for the full dataset.",
|
|
3496
|
+
emptyHint: "Rows are too large to return inline. Use export_data for this data.",
|
|
3497
|
+
alignmentImpossibleHint: "Page offset too deep to resume inline; use export_data instead.",
|
|
3498
|
+
build: (rows, meta) => ({
|
|
3499
|
+
data: rows,
|
|
3500
|
+
pagination: meta,
|
|
3501
|
+
...totalCount !== undefined && { totalCount },
|
|
3502
|
+
...totalCount !== undefined && rows.length < totalCount && {
|
|
3503
|
+
hint: `Showing ${rows.length} of ${totalCount} rows. Use export_data for the full dataset.`
|
|
3504
|
+
}
|
|
3505
|
+
})
|
|
3506
|
+
});
|
|
2934
3507
|
return jsonResult(result);
|
|
2935
3508
|
}));
|
|
2936
3509
|
server.registerTool("export_data", {
|
|
2937
3510
|
title: "Export Workflow Data",
|
|
2938
3511
|
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').",
|
|
2939
3512
|
inputSchema: strictSchema({
|
|
2940
|
-
workflowId:
|
|
2941
|
-
format:
|
|
2942
|
-
runId:
|
|
3513
|
+
workflowId: z12.string().min(1).describe("The workflow ID"),
|
|
3514
|
+
format: z12.enum(["csv", "json"]).optional().describe("Export format. Default 'csv'."),
|
|
3515
|
+
runId: z12.string().optional().describe("Specific run ID to export. Defaults to the latest completed run."),
|
|
2943
3516
|
filters: dataFiltersInput,
|
|
2944
|
-
sortBy:
|
|
2945
|
-
order:
|
|
2946
|
-
rowIds:
|
|
3517
|
+
sortBy: z12.string().optional().describe("Field name to sort by"),
|
|
3518
|
+
order: z12.enum(["asc", "desc"]).optional().describe("Sort order: asc or desc"),
|
|
3519
|
+
rowIds: z12.preprocess(coerceList(), z12.array(z12.string().trim().min(1)).max(1000)).optional().describe("Row ids to include (max 1000)")
|
|
2947
3520
|
}),
|
|
2948
3521
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2949
3522
|
}, withErrorHandling("export_data", async (args) => {
|
|
@@ -2956,7 +3529,7 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
2956
3529
|
order: args.order,
|
|
2957
3530
|
rowIds: joinList(args.rowIds)
|
|
2958
3531
|
});
|
|
2959
|
-
return jsonResult({
|
|
3532
|
+
return withExtraContent(jsonResult({
|
|
2960
3533
|
url: result.url,
|
|
2961
3534
|
rowCount: result.rowCount,
|
|
2962
3535
|
format: result.format,
|
|
@@ -2964,26 +3537,34 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
2964
3537
|
workflowId: result.workflowId,
|
|
2965
3538
|
runId: result.runId,
|
|
2966
3539
|
hint: `Full dataset ready at the URL above. Open in Excel via 'Get Data from Web' (no auth needed), ` + `or load with code execution (pandas.read_csv(url) / pandas.read_json(url) / duckdb.read_csv(url)). ` + `URL expires ${result.expiresAt}.`
|
|
3540
|
+
}), {
|
|
3541
|
+
type: "resource_link",
|
|
3542
|
+
uri: result.url,
|
|
3543
|
+
name: `${result.workflowId}-${result.runId}.${result.format}`,
|
|
3544
|
+
title: `Workflow export (${result.rowCount} rows)`,
|
|
3545
|
+
mimeType: result.format === "json" ? "application/json" : "text/csv",
|
|
3546
|
+
description: `Signed download URL, no auth needed. Expires ${result.expiresAt}.`
|
|
2967
3547
|
});
|
|
2968
3548
|
}));
|
|
2969
3549
|
server.registerTool("list_changes", {
|
|
2970
3550
|
title: "List Detected Changes",
|
|
2971
3551
|
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.",
|
|
2972
3552
|
inputSchema: strictSchema({
|
|
2973
|
-
workflowIds:
|
|
2974
|
-
startDate:
|
|
2975
|
-
endDate:
|
|
2976
|
-
skip:
|
|
2977
|
-
limit:
|
|
3553
|
+
workflowIds: z12.preprocess(coerceArray(true), z12.array(z12.string().min(1))).optional().describe("Workflow IDs to filter by. If omitted, returns changes for all ACTIVE monitoring workflows."),
|
|
3554
|
+
startDate: z12.string().optional().describe("Start date filter (ISO format, e.g. 2025-01-01)"),
|
|
3555
|
+
endDate: z12.string().optional().describe("End date filter (ISO format, e.g. 2025-12-31)"),
|
|
3556
|
+
skip: offsetInput("skip", "changes"),
|
|
3557
|
+
limit: limitInput({ noun: "Changes", defaultLimit: LIST_CHANGES_DEFAULT_LIMIT, max: LIST_CHANGES_MAX_LIMIT })
|
|
2978
3558
|
}),
|
|
2979
3559
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2980
3560
|
}, withErrorHandling("list_changes", async (args) => {
|
|
3561
|
+
const limit = args.limit ?? LIST_CHANGES_DEFAULT_LIMIT;
|
|
2981
3562
|
const result = await ctx.client.changes.list({
|
|
2982
3563
|
workflowIds: args.workflowIds?.join(","),
|
|
2983
3564
|
startDate: args.startDate,
|
|
2984
3565
|
endDate: args.endDate,
|
|
2985
3566
|
skip: args.skip,
|
|
2986
|
-
limit
|
|
3567
|
+
limit
|
|
2987
3568
|
});
|
|
2988
3569
|
return jsonResult({
|
|
2989
3570
|
changes: result.changes.map((c) => ({
|
|
@@ -2994,36 +3575,34 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
2994
3575
|
differences: c.differences,
|
|
2995
3576
|
createdAt: c.createdAt
|
|
2996
3577
|
})),
|
|
2997
|
-
pagination:
|
|
3578
|
+
pagination: offsetPage({
|
|
3579
|
+
args,
|
|
3580
|
+
param: "skip",
|
|
3581
|
+
offset: args.skip ?? 0,
|
|
3582
|
+
limit,
|
|
3583
|
+
returned: result.changes.length,
|
|
3584
|
+
totalCount: result.pagination?.totalCount
|
|
3585
|
+
}),
|
|
2998
3586
|
changesCount: result.changesCount
|
|
2999
3587
|
});
|
|
3000
3588
|
}));
|
|
3001
3589
|
server.registerTool("get_change", {
|
|
3002
3590
|
title: "Get Detected Change",
|
|
3003
|
-
description: "Get detailed information about a specific detected change, including the
|
|
3591
|
+
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.",
|
|
3004
3592
|
inputSchema: strictSchema({
|
|
3005
|
-
changeId:
|
|
3593
|
+
changeId: z12.string().min(1).describe("The change ID")
|
|
3006
3594
|
}),
|
|
3007
3595
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
3008
3596
|
}, withErrorHandling("get_change", async (args) => {
|
|
3009
3597
|
const change = await ctx.client.changes.get(args.changeId);
|
|
3010
|
-
return jsonResult(
|
|
3011
|
-
id: change.id,
|
|
3012
|
-
workflowId: change.workflowId,
|
|
3013
|
-
url: change.url,
|
|
3014
|
-
summary: change.summary,
|
|
3015
|
-
screenshotUrl: change.screenshotUrl,
|
|
3016
|
-
data: change.data,
|
|
3017
|
-
differences: change.differences,
|
|
3018
|
-
createdAt: change.createdAt
|
|
3019
|
-
});
|
|
3598
|
+
return jsonResult(changeResult(change));
|
|
3020
3599
|
}));
|
|
3021
3600
|
server.registerTool("delete_workflow", {
|
|
3022
3601
|
title: "Delete Workflow",
|
|
3023
3602
|
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.",
|
|
3024
3603
|
inputSchema: strictSchema({
|
|
3025
|
-
workflowId:
|
|
3026
|
-
confirmed:
|
|
3604
|
+
workflowId: z12.string().min(1).describe("The workflow ID to delete"),
|
|
3605
|
+
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.")
|
|
3027
3606
|
}),
|
|
3028
3607
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
3029
3608
|
}, withErrorHandling("delete_workflow", async (args) => {
|
|
@@ -3053,7 +3632,7 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
3053
3632
|
title: "Approve Workflow",
|
|
3054
3633
|
description: "Approve and activate a workflow that is in PREVIEW, PAUSED, or ERROR state. Transitions the workflow to ACTIVE so it can be run.",
|
|
3055
3634
|
inputSchema: strictSchema({
|
|
3056
|
-
workflowId:
|
|
3635
|
+
workflowId: z12.string().min(1).describe("The workflow ID to approve/activate")
|
|
3057
3636
|
}),
|
|
3058
3637
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
3059
3638
|
}, withErrorHandling("approve_workflow", async (args) => {
|
|
@@ -3068,7 +3647,7 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
3068
3647
|
title: "Pause Workflow",
|
|
3069
3648
|
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.",
|
|
3070
3649
|
inputSchema: strictSchema({
|
|
3071
|
-
workflowId:
|
|
3650
|
+
workflowId: z12.string().min(1).describe("The workflow ID to pause")
|
|
3072
3651
|
}),
|
|
3073
3652
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
3074
3653
|
}, withErrorHandling("pause_workflow", async (args) => {
|
|
@@ -3087,20 +3666,20 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
3087
3666
|
|
|
3088
3667
|
` + "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.",
|
|
3089
3668
|
inputSchema: strictSchema({
|
|
3090
|
-
workflowId:
|
|
3091
|
-
name:
|
|
3092
|
-
urls:
|
|
3093
|
-
entity:
|
|
3094
|
-
schema:
|
|
3095
|
-
description:
|
|
3096
|
-
tags:
|
|
3097
|
-
userPrompt:
|
|
3669
|
+
workflowId: z12.string().min(1).describe("The workflow ID to update"),
|
|
3670
|
+
name: z12.string().optional().describe("New name for the workflow"),
|
|
3671
|
+
urls: z12.preprocess(coerceArray(true), z12.array(z12.string()).min(1)).optional().describe("New target URLs for the workflow (array of strings). Also accepts a single URL string."),
|
|
3672
|
+
entity: z12.string().optional().describe("Entity name for extraction (e.g., 'Product', 'Job Posting')"),
|
|
3673
|
+
schema: z12.preprocess(coerceArray(), z12.array(z12.object(SchemaFieldShape).strict())).optional().describe("New extraction schema fields. This REPLACES the whole schema - list every field you want to keep, each with its isKey flag, or the omitted ones and their flags are lost. Call get_workflow first and edit the schema it returns."),
|
|
3674
|
+
description: z12.string().max(500).optional().describe("Workflow description (max 500 characters)"),
|
|
3675
|
+
tags: z12.preprocess(coerceArray(true), z12.array(z12.string())).optional().describe("Tags for organizing workflows"),
|
|
3676
|
+
userPrompt: z12.string().optional().describe("Navigation prompt for agentic-navigation mode (10-5000 characters)"),
|
|
3098
3677
|
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."),
|
|
3099
|
-
schedules:
|
|
3100
|
-
timezone:
|
|
3101
|
-
location:
|
|
3102
|
-
monitoring:
|
|
3103
|
-
limit:
|
|
3678
|
+
schedules: z12.preprocess(coerceArray(true), z12.array(z12.string())).optional().describe("Cron expressions for CUSTOM update interval"),
|
|
3679
|
+
timezone: z12.string().min(1).optional().describe("IANA timezone for cron schedules, for example America/New_York. A timezone-only update preserves existing cron expressions."),
|
|
3680
|
+
location: z12.preprocess(coerceJson(), LocationSchema).optional().describe("Scraping location: {type:'auto'} or {type:'manual', isoCode:'US'}"),
|
|
3681
|
+
monitoring: z12.preprocess(coerceJson(), MonitoringSchema).optional().describe("Complete monitoring replacement: fields to monitor and optional AND/OR conditions. Call get_workflow first because this replaces the existing monitoring configuration."),
|
|
3682
|
+
limit: z12.preprocess(coerceNumber(), z12.number().positive().nullable()).optional().describe("Maximum records per run. Pass null to clear the limit and extract all rows.")
|
|
3104
3683
|
}),
|
|
3105
3684
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
3106
3685
|
}, withErrorHandling("update_workflow", async (args) => {
|
|
@@ -3136,9 +3715,13 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
3136
3715
|
});
|
|
3137
3716
|
}));
|
|
3138
3717
|
}
|
|
3139
|
-
var WORKFLOW_AUDIT_WATCHED_KEYS;
|
|
3718
|
+
var WORKFLOW_AUDIT_WATCHED_KEYS, LIST_WORKFLOWS_DEFAULT_LIMIT = 50, LIST_WORKFLOWS_MAX_LIMIT = 100, LIST_CHANGES_DEFAULT_LIMIT = 25, LIST_CHANGES_MAX_LIMIT = 100, WORKFLOW_HISTORY_DEFAULT_LIMIT = 25, WORKFLOW_HISTORY_MAX_LIMIT = 50, WORKFLOW_RUNS_DEFAULT_LIMIT = 25, WORKFLOW_RUNS_MAX_LIMIT = 100;
|
|
3140
3719
|
var init_workflows = __esm(() => {
|
|
3720
|
+
init_notification_channels();
|
|
3721
|
+
init_budget();
|
|
3141
3722
|
init_errors();
|
|
3723
|
+
init_output_schemas();
|
|
3724
|
+
init_pagination();
|
|
3142
3725
|
init_schemas();
|
|
3143
3726
|
WORKFLOW_AUDIT_WATCHED_KEYS = [
|
|
3144
3727
|
"name",
|
|
@@ -3157,9 +3740,9 @@ var init_workflows = __esm(() => {
|
|
|
3157
3740
|
function registerTools(server, ctx, capabilities) {
|
|
3158
3741
|
const helpers = { withErrorHandling: createWithErrorHandling(ctx) };
|
|
3159
3742
|
if (capabilities.scrape)
|
|
3160
|
-
registerScrapeTools(server, ctx, helpers);
|
|
3743
|
+
registerScrapeTools(server, ctx, helpers, capabilities.defaultProductType);
|
|
3161
3744
|
registerWhoamiTool(server, ctx, helpers);
|
|
3162
|
-
registerWorkflowCoreTools(server, ctx, helpers);
|
|
3745
|
+
registerWorkflowCoreTools(server, ctx, helpers, capabilities.defaultProductType);
|
|
3163
3746
|
registerAssistantTools(server, ctx, helpers);
|
|
3164
3747
|
registerWorkflowHistoryTools(server, ctx, helpers);
|
|
3165
3748
|
registerObservabilityTools(server, ctx, helpers);
|
|
@@ -3843,12 +4426,41 @@ var init_request_log = __esm(() => {
|
|
|
3843
4426
|
|
|
3844
4427
|
// src/server.ts
|
|
3845
4428
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/server";
|
|
4429
|
+
function lifecycleInstruction2(defaultProductType) {
|
|
4430
|
+
return defaultProductType === "realtime" ? "For one-time or scheduled workflows, the lifecycle is create_workflow → get_workflow (check status) → fetch_data (get results). Workflows run asynchronously - never poll or sleep-wait." : "Workflow lifecycle: create_workflow → get_workflow (check status) → fetch_data (get results). Workflows run asynchronously - never poll or sleep-wait.";
|
|
4431
|
+
}
|
|
4432
|
+
function routingInstructions2(defaultProductType) {
|
|
4433
|
+
if (defaultProductType === "realtime") {
|
|
4434
|
+
return [
|
|
4435
|
+
`This team's default product is realtime monitoring. Use create_realtime_monitor 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 (e.g. "once") or a schedule (e.g. "daily"). A request that names no frequency is not explicit: default to realtime without asking the user to choose. It first persists notification channels, then asynchronously returns workflow/session/thread/job IDs; use the workflow Assistant tools for follow-up status, questions, and controls.`,
|
|
4436
|
+
"Use create_workflow only when the user explicitly asks for a one-time run or a schedule. Use scrape for an immediate raw HTML or markdown fetch from one URL."
|
|
4437
|
+
];
|
|
4438
|
+
}
|
|
4439
|
+
return [
|
|
4440
|
+
"Use create_realtime_monitor only when the user wants continuous realtime change detection with alerts. It first persists notification channels, then asynchronously returns workflow/session/thread/job IDs; use the workflow Assistant tools for follow-up status, questions, and controls.",
|
|
4441
|
+
"For one-time or scheduled extraction, use create_workflow. Use scrape for an immediate raw HTML or markdown fetch from one URL. Use create_workflow for structured extraction, recurring runs, monitoring, or navigation-heavy jobs."
|
|
4442
|
+
];
|
|
4443
|
+
}
|
|
3846
4444
|
async function createServer3(auth, options) {
|
|
3847
4445
|
const ctx = {
|
|
3848
4446
|
client: createKadoaClient(auth),
|
|
3849
4447
|
principal: auth.principal,
|
|
3850
4448
|
teamId: auth.principal.teamId
|
|
3851
4449
|
};
|
|
4450
|
+
let capabilities = options?.capabilities;
|
|
4451
|
+
if (!capabilities) {
|
|
4452
|
+
try {
|
|
4453
|
+
const response = await ctx.client.user.getFeatures();
|
|
4454
|
+
capabilities = {
|
|
4455
|
+
scrape: response.features.scrape,
|
|
4456
|
+
defaultProductType: parseDefaultProductType(response.defaultProductType)
|
|
4457
|
+
};
|
|
4458
|
+
} catch (error) {
|
|
4459
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
4460
|
+
console.error(`[CAPABILITIES] Failed to resolve team capabilities; gated tools will be hidden: ${message}`);
|
|
4461
|
+
capabilities = { scrape: false };
|
|
4462
|
+
}
|
|
4463
|
+
}
|
|
3852
4464
|
const server = new McpServer2({ name: "kadoa", version: package_default.version }, {
|
|
3853
4465
|
instructions: [
|
|
3854
4466
|
"IMPORTANT: One workflow = one source. A single workflow can extract many different fields, tables, and sections from the same URL(s) using a rich schema.",
|
|
@@ -3857,12 +4469,13 @@ async function createServer3(auth, options) {
|
|
|
3857
4469
|
"Kadoa workflows use agentic navigation: the AI agent can browse pages, click buttons, fill forms, select dropdowns, paginate, open detail pages, and handle multi-step interactions.",
|
|
3858
4470
|
"Describe the full navigation steps in the prompt (e.g., 'select year 2022 from the dropdown, click Search, extract the table, then repeat for 2023').",
|
|
3859
4471
|
"",
|
|
3860
|
-
|
|
4472
|
+
lifecycleInstruction2(capabilities.defaultProductType),
|
|
3861
4473
|
"",
|
|
3862
|
-
|
|
3863
|
-
"For one-time or scheduled extraction, use create_workflow. Use scrape for an immediate raw HTML or markdown fetch from one URL. Use create_workflow for structured extraction, recurring runs, monitoring, or navigation-heavy jobs.",
|
|
4474
|
+
...routingInstructions2(capabilities.defaultProductType),
|
|
3864
4475
|
"If a one-time or scheduled workflow fails, retry it with run_workflow using the existing workflow ID. Do NOT delete and recreate the workflow just to retry it - preserve its workflow ID and configuration. Realtime workflows cannot be manually run.",
|
|
3865
4476
|
"Use list_changes and get_change to retrieve detected diffs from realtime monitoring workflows.",
|
|
4477
|
+
"List tools, plus fetch_data, get_workflow_history, and get_workflow_assistant_timeline, all return pagination {totalCount?, hasMore, next?}. When hasMore is true, call the same tool again with pagination.next as its arguments to get the next page. totalCount is present only when the total is known; never treat the page size as the total.",
|
|
4478
|
+
"Large results (fetch_data, get_workflow_history, get_change) are cut to whole rows or entries under about 80 KB and marked truncated: true with a hint; follow pagination.next, narrow the filters, or use export_data, and never conclude the data ends there.",
|
|
3866
4479
|
"When the user asks what needs their attention, call list_workflows with statusFilters group:attention. A row with awaitingUserInput.prompt is the Assistant's pending question: answer it with get_workflow_assistant then answer_workflow_assistant_question. A row with awaitingUserInput but no prompt is a setup the user left unfinished: use get_workflow_assistant to see where it stopped, then resume_workflow_assistant or request_workflow_update. A row with displayState PREVIEW and no awaitingUserInput is preview data to review or approve with approve_workflow. Workflows with an open support ticket are excluded from group:attention and appear under group:support.",
|
|
3867
4480
|
"",
|
|
3868
4481
|
"Schema tips: Use descriptive field names and examples. Group related data under one entity.",
|
|
@@ -3872,17 +4485,6 @@ async function createServer3(auth, options) {
|
|
|
3872
4485
|
].join(`
|
|
3873
4486
|
`)
|
|
3874
4487
|
});
|
|
3875
|
-
let capabilities = options?.capabilities;
|
|
3876
|
-
if (!capabilities) {
|
|
3877
|
-
try {
|
|
3878
|
-
const response = await ctx.client.user.getFeatures();
|
|
3879
|
-
capabilities = { scrape: response.features.scrape };
|
|
3880
|
-
} catch (error) {
|
|
3881
|
-
const message = error instanceof Error ? error.message : "unknown error";
|
|
3882
|
-
console.error(`[CAPABILITIES] Failed to resolve team capabilities; gated tools will be hidden: ${message}`);
|
|
3883
|
-
capabilities = { scrape: false };
|
|
3884
|
-
}
|
|
3885
|
-
}
|
|
3886
4488
|
registerTools(server, ctx, capabilities);
|
|
3887
4489
|
server.server.onerror = (error) => console.error("[MCP Error]", safeErrorSummary(error));
|
|
3888
4490
|
return server;
|
|
@@ -3962,7 +4564,7 @@ async function requestNeedsCapabilityLookup(request) {
|
|
|
3962
4564
|
return false;
|
|
3963
4565
|
}
|
|
3964
4566
|
}
|
|
3965
|
-
return method === "tools/list" || method === "tools/call" && name === "scrape";
|
|
4567
|
+
return method === "initialize" || method === "server/discover" || method === "tools/list" || method === "tools/call" && name === "scrape";
|
|
3966
4568
|
}
|
|
3967
4569
|
function isClientCausedHandlerError(error) {
|
|
3968
4570
|
if (error instanceof ProtocolError)
|
|
@@ -4302,12 +4904,41 @@ init_package();
|
|
|
4302
4904
|
init_client();
|
|
4303
4905
|
init_tools();
|
|
4304
4906
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
4907
|
+
function lifecycleInstruction(defaultProductType) {
|
|
4908
|
+
return defaultProductType === "realtime" ? "For one-time or scheduled workflows, the lifecycle is create_workflow → get_workflow (check status) → fetch_data (get results). Workflows run asynchronously - never poll or sleep-wait." : "Workflow lifecycle: create_workflow → get_workflow (check status) → fetch_data (get results). Workflows run asynchronously - never poll or sleep-wait.";
|
|
4909
|
+
}
|
|
4910
|
+
function routingInstructions(defaultProductType) {
|
|
4911
|
+
if (defaultProductType === "realtime") {
|
|
4912
|
+
return [
|
|
4913
|
+
`This team's default product is realtime monitoring. Use create_realtime_monitor 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 (e.g. "once") or a schedule (e.g. "daily"). A request that names no frequency is not explicit: default to realtime without asking the user to choose. It first persists notification channels, then asynchronously returns workflow/session/thread/job IDs; use the workflow Assistant tools for follow-up status, questions, and controls.`,
|
|
4914
|
+
"Use create_workflow only when the user explicitly asks for a one-time run or a schedule. Use scrape for an immediate raw HTML or markdown fetch from one URL."
|
|
4915
|
+
];
|
|
4916
|
+
}
|
|
4917
|
+
return [
|
|
4918
|
+
"Use create_realtime_monitor only when the user wants continuous realtime change detection with alerts. It first persists notification channels, then asynchronously returns workflow/session/thread/job IDs; use the workflow Assistant tools for follow-up status, questions, and controls.",
|
|
4919
|
+
"For one-time or scheduled extraction, use create_workflow. Use scrape for an immediate raw HTML or markdown fetch from one URL. Use create_workflow for structured extraction, recurring runs, monitoring, or navigation-heavy jobs."
|
|
4920
|
+
];
|
|
4921
|
+
}
|
|
4305
4922
|
async function createServer(auth, options) {
|
|
4306
4923
|
const ctx = {
|
|
4307
4924
|
client: createKadoaClient(auth),
|
|
4308
4925
|
principal: auth.principal,
|
|
4309
4926
|
teamId: auth.principal.teamId
|
|
4310
4927
|
};
|
|
4928
|
+
let capabilities = options?.capabilities;
|
|
4929
|
+
if (!capabilities) {
|
|
4930
|
+
try {
|
|
4931
|
+
const response = await ctx.client.user.getFeatures();
|
|
4932
|
+
capabilities = {
|
|
4933
|
+
scrape: response.features.scrape,
|
|
4934
|
+
defaultProductType: parseDefaultProductType(response.defaultProductType)
|
|
4935
|
+
};
|
|
4936
|
+
} catch (error) {
|
|
4937
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
4938
|
+
console.error(`[CAPABILITIES] Failed to resolve team capabilities; gated tools will be hidden: ${message}`);
|
|
4939
|
+
capabilities = { scrape: false };
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4311
4942
|
const server = new McpServer({ name: "kadoa", version: package_default.version }, {
|
|
4312
4943
|
instructions: [
|
|
4313
4944
|
"IMPORTANT: One workflow = one source. A single workflow can extract many different fields, tables, and sections from the same URL(s) using a rich schema.",
|
|
@@ -4316,12 +4947,13 @@ async function createServer(auth, options) {
|
|
|
4316
4947
|
"Kadoa workflows use agentic navigation: the AI agent can browse pages, click buttons, fill forms, select dropdowns, paginate, open detail pages, and handle multi-step interactions.",
|
|
4317
4948
|
"Describe the full navigation steps in the prompt (e.g., 'select year 2022 from the dropdown, click Search, extract the table, then repeat for 2023').",
|
|
4318
4949
|
"",
|
|
4319
|
-
|
|
4950
|
+
lifecycleInstruction(capabilities.defaultProductType),
|
|
4320
4951
|
"",
|
|
4321
|
-
|
|
4322
|
-
"For one-time or scheduled extraction, use create_workflow. Use scrape for an immediate raw HTML or markdown fetch from one URL. Use create_workflow for structured extraction, recurring runs, monitoring, or navigation-heavy jobs.",
|
|
4952
|
+
...routingInstructions(capabilities.defaultProductType),
|
|
4323
4953
|
"If a one-time or scheduled workflow fails, retry it with run_workflow using the existing workflow ID. Do NOT delete and recreate the workflow just to retry it - preserve its workflow ID and configuration. Realtime workflows cannot be manually run.",
|
|
4324
4954
|
"Use list_changes and get_change to retrieve detected diffs from realtime monitoring workflows.",
|
|
4955
|
+
"List tools, plus fetch_data, get_workflow_history, and get_workflow_assistant_timeline, all return pagination {totalCount?, hasMore, next?}. When hasMore is true, call the same tool again with pagination.next as its arguments to get the next page. totalCount is present only when the total is known; never treat the page size as the total.",
|
|
4956
|
+
"Large results (fetch_data, get_workflow_history, get_change) are cut to whole rows or entries under about 80 KB and marked truncated: true with a hint; follow pagination.next, narrow the filters, or use export_data, and never conclude the data ends there.",
|
|
4325
4957
|
"When the user asks what needs their attention, call list_workflows with statusFilters group:attention. A row with awaitingUserInput.prompt is the Assistant's pending question: answer it with get_workflow_assistant then answer_workflow_assistant_question. A row with awaitingUserInput but no prompt is a setup the user left unfinished: use get_workflow_assistant to see where it stopped, then resume_workflow_assistant or request_workflow_update. A row with displayState PREVIEW and no awaitingUserInput is preview data to review or approve with approve_workflow. Workflows with an open support ticket are excluded from group:attention and appear under group:support.",
|
|
4326
4958
|
"",
|
|
4327
4959
|
"Schema tips: Use descriptive field names and examples. Group related data under one entity.",
|
|
@@ -4331,17 +4963,6 @@ async function createServer(auth, options) {
|
|
|
4331
4963
|
].join(`
|
|
4332
4964
|
`)
|
|
4333
4965
|
});
|
|
4334
|
-
let capabilities = options?.capabilities;
|
|
4335
|
-
if (!capabilities) {
|
|
4336
|
-
try {
|
|
4337
|
-
const response = await ctx.client.user.getFeatures();
|
|
4338
|
-
capabilities = { scrape: response.features.scrape };
|
|
4339
|
-
} catch (error) {
|
|
4340
|
-
const message = error instanceof Error ? error.message : "unknown error";
|
|
4341
|
-
console.error(`[CAPABILITIES] Failed to resolve team capabilities; gated tools will be hidden: ${message}`);
|
|
4342
|
-
capabilities = { scrape: false };
|
|
4343
|
-
}
|
|
4344
|
-
}
|
|
4345
4966
|
registerTools(server, ctx, capabilities);
|
|
4346
4967
|
server.server.onerror = (error) => console.error("[MCP Error]", safeErrorSummary(error));
|
|
4347
4968
|
return server;
|