@kadoa/mcp 0.7.0-rc.1 → 0.7.0-rc.2
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 +1005 -431
- 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-rc.
|
|
20
|
+
version: "0.7.0-rc.2",
|
|
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",
|
|
@@ -207,19 +207,252 @@ function withVariableDashboardUrl(variable) {
|
|
|
207
207
|
return { ...variable, dashboardUrl: variableSettingsDashboardUrl() };
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
// src/tools/shared/output-schemas.ts
|
|
211
|
+
import { z } from "zod";
|
|
212
|
+
var apiField, nullableString, PaginationOutput, WorkflowRowOutput, ListWorkflowsOutput, GetWorkflowOutput, FetchDataOutput, ASSISTANT_ACTIVITIES, WorkflowAssistantOutput, AssistantTimelineOutput, AssistantAnswerOutput, AssistantControlOutput, WorkflowStrategyOutput;
|
|
213
|
+
var init_output_schemas = __esm(() => {
|
|
214
|
+
apiField = z.unknown().optional();
|
|
215
|
+
nullableString = z.string().nullable();
|
|
216
|
+
PaginationOutput = z.object({
|
|
217
|
+
totalCount: z.number().optional(),
|
|
218
|
+
hasMore: z.boolean(),
|
|
219
|
+
limit: z.number().optional(),
|
|
220
|
+
skip: z.number().optional(),
|
|
221
|
+
offset: z.number().optional(),
|
|
222
|
+
page: z.number().optional(),
|
|
223
|
+
totalPages: z.number().optional(),
|
|
224
|
+
nextCursor: nullableString.optional(),
|
|
225
|
+
next: z.record(z.string(), z.unknown()).optional()
|
|
226
|
+
}).loose();
|
|
227
|
+
WorkflowRowOutput = z.object({
|
|
228
|
+
id: apiField,
|
|
229
|
+
name: apiField,
|
|
230
|
+
dashboardUrl: z.string(),
|
|
231
|
+
status: z.string(),
|
|
232
|
+
state: nullableString,
|
|
233
|
+
displayState: nullableString,
|
|
234
|
+
runState: nullableString,
|
|
235
|
+
urls: apiField,
|
|
236
|
+
totalRecords: apiField,
|
|
237
|
+
startedAt: apiField,
|
|
238
|
+
finishedAt: apiField,
|
|
239
|
+
createdAt: apiField,
|
|
240
|
+
assistant: z.object({
|
|
241
|
+
eligible: z.boolean(),
|
|
242
|
+
dataStale: z.boolean(),
|
|
243
|
+
session: z.object({ id: z.string(), threadId: apiField, status: apiField }).loose().nullable()
|
|
244
|
+
}).loose(),
|
|
245
|
+
template: z.object({ id: z.string(), pinnedVersion: apiField, latestVersion: apiField, isOutdated: z.boolean() }).loose().nullable(),
|
|
246
|
+
realtime: z.object({ enabled: z.boolean(), lastDataChangedAt: apiField, health: apiField }).loose(),
|
|
247
|
+
awaitingUserInput: apiField
|
|
248
|
+
}).loose();
|
|
249
|
+
ListWorkflowsOutput = z.object({ workflows: z.array(WorkflowRowOutput), pagination: PaginationOutput }).loose();
|
|
250
|
+
GetWorkflowOutput = z.object({
|
|
251
|
+
id: z.string(),
|
|
252
|
+
name: apiField,
|
|
253
|
+
dashboardUrl: z.string(),
|
|
254
|
+
description: apiField,
|
|
255
|
+
status: z.string(),
|
|
256
|
+
state: nullableString,
|
|
257
|
+
displayState: nullableString,
|
|
258
|
+
runState: nullableString,
|
|
259
|
+
urls: apiField,
|
|
260
|
+
prompt: nullableString,
|
|
261
|
+
intentSource: z.enum(["extraction_spec", "legacy_prompt"]).nullable(),
|
|
262
|
+
extractionSpec: apiField,
|
|
263
|
+
entity: apiField,
|
|
264
|
+
schema: apiField,
|
|
265
|
+
navigationMode: apiField,
|
|
266
|
+
dataStale: apiField,
|
|
267
|
+
assistantEligible: z.boolean(),
|
|
268
|
+
assistantSessionId: apiField,
|
|
269
|
+
assistantThreadId: apiField,
|
|
270
|
+
assistantSessionStatus: apiField,
|
|
271
|
+
assistant: z.object({ eligible: z.boolean(), sessionId: apiField, threadId: apiField, sessionStatus: apiField }).loose(),
|
|
272
|
+
template: z.object({
|
|
273
|
+
id: apiField,
|
|
274
|
+
name: apiField,
|
|
275
|
+
version: apiField,
|
|
276
|
+
controlledParts: z.array(z.string()),
|
|
277
|
+
hasSchemaValidationRules: apiField
|
|
278
|
+
}).loose().nullable(),
|
|
279
|
+
updateInterval: apiField,
|
|
280
|
+
schedules: apiField,
|
|
281
|
+
scheduleTimezone: apiField,
|
|
282
|
+
nextInvocation: apiField,
|
|
283
|
+
location: apiField,
|
|
284
|
+
limit: apiField,
|
|
285
|
+
tags: apiField,
|
|
286
|
+
totalRecords: apiField,
|
|
287
|
+
job: z.object({ id: apiField }).loose().nullable(),
|
|
288
|
+
startedAt: apiField,
|
|
289
|
+
finishedAt: apiField,
|
|
290
|
+
estimatedFinishedAtUtc: apiField,
|
|
291
|
+
errors: apiField,
|
|
292
|
+
createdAt: apiField,
|
|
293
|
+
notificationConfig: apiField,
|
|
294
|
+
channelCounts: apiField,
|
|
295
|
+
dataValidation: apiField,
|
|
296
|
+
validationStatistics: apiField,
|
|
297
|
+
realtime: z.object({ lastDataChangedAt: apiField, observerHealth: apiField }).loose().nullable()
|
|
298
|
+
}).loose();
|
|
299
|
+
FetchDataOutput = z.object({
|
|
300
|
+
data: z.array(z.record(z.string(), z.unknown())),
|
|
301
|
+
pagination: PaginationOutput,
|
|
302
|
+
totalCount: z.number().optional(),
|
|
303
|
+
hint: z.string().optional()
|
|
304
|
+
}).loose();
|
|
305
|
+
ASSISTANT_ACTIVITIES = [
|
|
306
|
+
"unsupported",
|
|
307
|
+
"not_started",
|
|
308
|
+
"working",
|
|
309
|
+
"waiting_for_input",
|
|
310
|
+
"idle",
|
|
311
|
+
"interrupted",
|
|
312
|
+
"failed",
|
|
313
|
+
"closed",
|
|
314
|
+
"unknown"
|
|
315
|
+
];
|
|
316
|
+
WorkflowAssistantOutput = z.object({
|
|
317
|
+
workflowId: z.string(),
|
|
318
|
+
assistantEligible: z.boolean(),
|
|
319
|
+
sessionId: nullableString,
|
|
320
|
+
threadId: apiField,
|
|
321
|
+
sessionStatus: apiField,
|
|
322
|
+
activity: z.enum(ASSISTANT_ACTIVITIES),
|
|
323
|
+
canAcceptUpdate: z.boolean(),
|
|
324
|
+
isTerminal: z.boolean(),
|
|
325
|
+
paused: apiField,
|
|
326
|
+
pause: apiField,
|
|
327
|
+
pendingQuestion: apiField
|
|
328
|
+
}).loose();
|
|
329
|
+
AssistantTimelineOutput = z.object({
|
|
330
|
+
workflowId: z.string(),
|
|
331
|
+
sessionId: apiField,
|
|
332
|
+
items: z.array(z.unknown()),
|
|
333
|
+
pagination: PaginationOutput,
|
|
334
|
+
dashboardUrl: z.string()
|
|
335
|
+
}).loose();
|
|
336
|
+
AssistantAnswerOutput = z.object({
|
|
337
|
+
success: z.literal(true),
|
|
338
|
+
workflowId: z.string(),
|
|
339
|
+
sessionId: z.string(),
|
|
340
|
+
threadId: apiField,
|
|
341
|
+
questionId: z.string(),
|
|
342
|
+
message: z.string()
|
|
343
|
+
}).loose();
|
|
344
|
+
AssistantControlOutput = z.object({
|
|
345
|
+
success: z.boolean(),
|
|
346
|
+
status: z.literal("accepted"),
|
|
347
|
+
action: z.enum(["interrupt", "resume", "stop"]),
|
|
348
|
+
workflowId: z.string(),
|
|
349
|
+
sessionId: z.string(),
|
|
350
|
+
delivery: apiField,
|
|
351
|
+
jobId: apiField,
|
|
352
|
+
message: z.string()
|
|
353
|
+
}).loose();
|
|
354
|
+
WorkflowStrategyOutput = z.object({
|
|
355
|
+
workflowId: z.string(),
|
|
356
|
+
sessionId: nullableString,
|
|
357
|
+
source: z.enum(["assistant_build", "workflow_metadata"]).nullable(),
|
|
358
|
+
status: z.enum(["ready", "building", "unavailable"]),
|
|
359
|
+
strategy: apiField,
|
|
360
|
+
message: z.string().optional()
|
|
361
|
+
}).loose();
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// src/tools/shared/pagination.ts
|
|
365
|
+
import { z as z2 } from "zod";
|
|
366
|
+
function limitInput(options) {
|
|
367
|
+
const range = `limit must be a whole number from 1 to ${options.max}`;
|
|
368
|
+
const overMax = `${range}. ${options.overMaxHint ?? "Page with pagination.next."}`;
|
|
369
|
+
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})`);
|
|
370
|
+
}
|
|
371
|
+
function offsetInput(param, noun) {
|
|
372
|
+
const range = `${param} must be a whole number from 0 to ${MAX_OFFSET}`;
|
|
373
|
+
return z2.preprocess(coerceNumber(), z2.number().int(range).min(0, range).max(MAX_OFFSET, range)).optional().describe(`Number of ${noun} to skip (default 0)`);
|
|
374
|
+
}
|
|
375
|
+
function pageInput() {
|
|
376
|
+
const range = `page must be a whole number from 1 to ${MAX_PAGE}`;
|
|
377
|
+
return z2.preprocess(coerceNumber(), z2.number().int(range).min(1, range).max(MAX_PAGE, range)).optional().describe("Page number, 1-based (default 1)");
|
|
378
|
+
}
|
|
379
|
+
function nextArgs(args, patch) {
|
|
380
|
+
const next = {};
|
|
381
|
+
for (const [key, value] of Object.entries({ ...args, ...patch })) {
|
|
382
|
+
if (value !== undefined)
|
|
383
|
+
next[key] = value;
|
|
384
|
+
}
|
|
385
|
+
return next;
|
|
386
|
+
}
|
|
387
|
+
function withTotal(totalCount, hasMore) {
|
|
388
|
+
return totalCount !== undefined ? { totalCount, hasMore } : { hasMore };
|
|
389
|
+
}
|
|
390
|
+
function offsetPage(options) {
|
|
391
|
+
const nextOffset = options.offset + options.returned;
|
|
392
|
+
const more = options.hasMore ?? (options.totalCount !== undefined ? nextOffset < options.totalCount : options.returned >= options.limit);
|
|
393
|
+
const hasMore = options.returned > 0 && more;
|
|
394
|
+
const meta = withTotal(options.totalCount, hasMore);
|
|
395
|
+
meta.limit = options.limit;
|
|
396
|
+
meta[options.param] = options.offset;
|
|
397
|
+
if (hasMore)
|
|
398
|
+
meta.next = nextArgs(options.args, { [options.param]: nextOffset, limit: options.limit });
|
|
399
|
+
return meta;
|
|
400
|
+
}
|
|
401
|
+
function numberedPage(options) {
|
|
402
|
+
const more = options.totalPages !== undefined ? options.page < options.totalPages : options.totalCount !== undefined ? options.page * options.limit < options.totalCount : options.returned >= options.limit;
|
|
403
|
+
const hasMore = options.returned > 0 && more;
|
|
404
|
+
const meta = withTotal(options.totalCount, hasMore);
|
|
405
|
+
meta.limit = options.limit;
|
|
406
|
+
meta.page = options.page;
|
|
407
|
+
if (options.totalPages !== undefined)
|
|
408
|
+
meta.totalPages = options.totalPages;
|
|
409
|
+
if (hasMore)
|
|
410
|
+
meta.next = nextArgs(options.args, { page: options.page + 1, limit: options.limit });
|
|
411
|
+
return meta;
|
|
412
|
+
}
|
|
413
|
+
function cursorPage(options) {
|
|
414
|
+
const cursor = typeof options.nextCursor === "string" && options.nextCursor.length > 0 ? options.nextCursor : null;
|
|
415
|
+
const meta = { hasMore: options.hasMore && cursor !== null, nextCursor: cursor };
|
|
416
|
+
if (meta.hasMore)
|
|
417
|
+
meta.next = nextArgs(options.args, { cursor });
|
|
418
|
+
return meta;
|
|
419
|
+
}
|
|
420
|
+
function completeList(totalCount) {
|
|
421
|
+
return { totalCount, hasMore: false };
|
|
422
|
+
}
|
|
423
|
+
var MAX_OFFSET = 1e6, MAX_PAGE = 1e5;
|
|
424
|
+
var init_pagination = () => {};
|
|
425
|
+
|
|
210
426
|
// src/tools/shared/results.ts
|
|
427
|
+
function asStructuredObject(data) {
|
|
428
|
+
return data !== null && !Array.isArray(data) ? data : { result: data };
|
|
429
|
+
}
|
|
430
|
+
function structuredResult(data, extra = []) {
|
|
431
|
+
const structuredContent = asStructuredObject(data);
|
|
432
|
+
return {
|
|
433
|
+
structuredContent,
|
|
434
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent) }, ...extra]
|
|
435
|
+
};
|
|
436
|
+
}
|
|
211
437
|
function jsonResult(data) {
|
|
212
|
-
return
|
|
438
|
+
return structuredResult(data);
|
|
439
|
+
}
|
|
440
|
+
function textOnlyResult(data, extra = []) {
|
|
441
|
+
const structuredContent = asStructuredObject(data);
|
|
442
|
+
return { content: [{ type: "text", text: JSON.stringify(structuredContent) }, ...extra] };
|
|
213
443
|
}
|
|
214
|
-
function errorResult(message) {
|
|
444
|
+
function errorResult(message, details = {}) {
|
|
215
445
|
return {
|
|
216
|
-
content: [{ type: "text", text: JSON.stringify({ error: message
|
|
446
|
+
content: [{ type: "text", text: JSON.stringify({ error: message, ...details }) }],
|
|
217
447
|
isError: true
|
|
218
448
|
};
|
|
219
449
|
}
|
|
450
|
+
function withExtraContent(result, ...blocks) {
|
|
451
|
+
return { ...result, content: [...result.content, ...blocks] };
|
|
452
|
+
}
|
|
220
453
|
|
|
221
454
|
// src/tools/shared/schemas.ts
|
|
222
|
-
import { z } from "zod";
|
|
455
|
+
import { z as z3 } from "zod";
|
|
223
456
|
function isPlainObject(value) {
|
|
224
457
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
225
458
|
}
|
|
@@ -281,7 +514,7 @@ function stampSchemaValidationRules(rules, attribution, previous) {
|
|
|
281
514
|
return Object.fromEntries(Object.entries(rules).map(([name, field]) => [name, stampField(field, previousFields[name])]));
|
|
282
515
|
}
|
|
283
516
|
function strictSchema(shape) {
|
|
284
|
-
return
|
|
517
|
+
return z3.object(shape).strict();
|
|
285
518
|
}
|
|
286
519
|
function withoutDefIds(schema) {
|
|
287
520
|
const jsonSchema = schema["~standard"].jsonSchema;
|
|
@@ -317,7 +550,7 @@ function normalizeDataFilters(val, ctx) {
|
|
|
317
550
|
code: "custom",
|
|
318
551
|
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
552
|
});
|
|
320
|
-
return
|
|
553
|
+
return z3.NEVER;
|
|
321
554
|
}
|
|
322
555
|
return [v];
|
|
323
556
|
}
|
|
@@ -325,7 +558,7 @@ function normalizeDataFilters(val, ctx) {
|
|
|
325
558
|
}
|
|
326
559
|
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
560
|
var init_schemas = __esm(() => {
|
|
328
|
-
UpdateIntervalSchema =
|
|
561
|
+
UpdateIntervalSchema = z3.enum([
|
|
329
562
|
"ONLY_ONCE",
|
|
330
563
|
"EVERY_10_MINUTES",
|
|
331
564
|
"HALF_HOURLY",
|
|
@@ -359,8 +592,8 @@ var init_schemas = __esm(() => {
|
|
|
359
592
|
"credits_low",
|
|
360
593
|
"free_trial_ending"
|
|
361
594
|
];
|
|
362
|
-
NotificationEventTypeSchema =
|
|
363
|
-
SchemaFieldDataTypeSchema =
|
|
595
|
+
NotificationEventTypeSchema = z3.enum(NOTIFICATION_EVENT_TYPES);
|
|
596
|
+
SchemaFieldDataTypeSchema = z3.enum([
|
|
364
597
|
"STRING",
|
|
365
598
|
"NUMBER",
|
|
366
599
|
"BOOLEAN",
|
|
@@ -373,61 +606,61 @@ var init_schemas = __esm(() => {
|
|
|
373
606
|
"ARRAY"
|
|
374
607
|
]);
|
|
375
608
|
SchemaFieldShape = {
|
|
376
|
-
name:
|
|
377
|
-
description:
|
|
378
|
-
example:
|
|
609
|
+
name: z3.string().describe("Field name"),
|
|
610
|
+
description: z3.string().optional().describe("What this field contains"),
|
|
611
|
+
example: z3.string().describe("Example value"),
|
|
379
612
|
dataType: SchemaFieldDataTypeSchema.optional().describe("Data type for the field"),
|
|
380
|
-
isKey:
|
|
613
|
+
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
614
|
};
|
|
382
|
-
SchemaValidationPresenceRule =
|
|
383
|
-
target:
|
|
615
|
+
SchemaValidationPresenceRule = z3.object({
|
|
616
|
+
target: z3.number().int().min(0).max(100).step(20).describe("Expected percentage of rows with a value")
|
|
384
617
|
}).strict().meta({ id: "PresenceRule" });
|
|
385
|
-
SchemaValidationUniquenessRule =
|
|
386
|
-
target:
|
|
618
|
+
SchemaValidationUniquenessRule = z3.object({
|
|
619
|
+
target: z3.number().int().min(0).max(100).step(20).describe("Expected percentage of rows with a unique value")
|
|
387
620
|
}).strict().meta({ id: "UniquenessRule" });
|
|
388
|
-
SchemaValidationBoundRule =
|
|
389
|
-
value:
|
|
621
|
+
SchemaValidationBoundRule = z3.object({
|
|
622
|
+
value: z3.number().int().min(0).describe("Length or count bound")
|
|
390
623
|
}).strict().meta({ id: "BoundRule" });
|
|
391
|
-
SchemaValidationStringFormatRule =
|
|
392
|
-
|
|
393
|
-
kind:
|
|
394
|
-
charset:
|
|
395
|
-
|
|
396
|
-
kind:
|
|
397
|
-
preset:
|
|
624
|
+
SchemaValidationStringFormatRule = z3.discriminatedUnion("kind", [
|
|
625
|
+
z3.object({
|
|
626
|
+
kind: z3.literal("FREE_TEXT"),
|
|
627
|
+
charset: z3.discriminatedUnion("kind", [
|
|
628
|
+
z3.object({
|
|
629
|
+
kind: z3.literal("PRESET"),
|
|
630
|
+
preset: z3.enum(["natural_language", "alphanumeric", "alpha"])
|
|
398
631
|
}).strict()
|
|
399
632
|
])
|
|
400
633
|
}).strict(),
|
|
401
|
-
|
|
402
|
-
kind:
|
|
403
|
-
source:
|
|
404
|
-
|
|
405
|
-
kind:
|
|
406
|
-
preset:
|
|
634
|
+
z3.object({
|
|
635
|
+
kind: z3.literal("FORMAT"),
|
|
636
|
+
source: z3.discriminatedUnion("kind", [
|
|
637
|
+
z3.object({
|
|
638
|
+
kind: z3.literal("PRESET"),
|
|
639
|
+
preset: z3.enum(["url", "email", "phone", "date", "datetime", "time", "uuid", "slug"])
|
|
407
640
|
}).strict(),
|
|
408
|
-
|
|
409
|
-
kind:
|
|
410
|
-
pattern:
|
|
641
|
+
z3.object({
|
|
642
|
+
kind: z3.literal("CUSTOM"),
|
|
643
|
+
pattern: z3.string().min(1).describe("Regular expression pattern")
|
|
411
644
|
}).strict()
|
|
412
645
|
])
|
|
413
646
|
}).strict(),
|
|
414
|
-
|
|
415
|
-
kind:
|
|
416
|
-
source:
|
|
417
|
-
|
|
418
|
-
kind:
|
|
419
|
-
preset:
|
|
647
|
+
z3.object({
|
|
648
|
+
kind: z3.literal("LIST"),
|
|
649
|
+
source: z3.discriminatedUnion("kind", [
|
|
650
|
+
z3.object({
|
|
651
|
+
kind: z3.literal("PRESET"),
|
|
652
|
+
preset: z3.enum(["language2", "country2", "country3", "currency3", "month3", "usState2"])
|
|
420
653
|
}).strict(),
|
|
421
|
-
|
|
422
|
-
kind:
|
|
423
|
-
values:
|
|
654
|
+
z3.object({
|
|
655
|
+
kind: z3.literal("CUSTOM"),
|
|
656
|
+
values: z3.array(z3.string().min(1)).min(1).describe("Allowed string values")
|
|
424
657
|
}).strict()
|
|
425
658
|
])
|
|
426
659
|
}).strict()
|
|
427
660
|
]);
|
|
428
|
-
SchemaValidationFieldRulesSchema =
|
|
429
|
-
|
|
430
|
-
kind:
|
|
661
|
+
SchemaValidationFieldRulesSchema = z3.lazy(() => z3.discriminatedUnion("kind", [
|
|
662
|
+
z3.object({
|
|
663
|
+
kind: z3.literal("STRING"),
|
|
431
664
|
presence: SchemaValidationPresenceRule.optional(),
|
|
432
665
|
uniqueness: SchemaValidationUniquenessRule.optional(),
|
|
433
666
|
minLength: SchemaValidationBoundRule.optional(),
|
|
@@ -436,27 +669,27 @@ var init_schemas = __esm(() => {
|
|
|
436
669
|
maxHtmlElements: SchemaValidationBoundRule.optional(),
|
|
437
670
|
format: SchemaValidationStringFormatRule.optional()
|
|
438
671
|
}).strict(),
|
|
439
|
-
|
|
440
|
-
kind:
|
|
672
|
+
z3.object({
|
|
673
|
+
kind: z3.literal("NUMBER"),
|
|
441
674
|
presence: SchemaValidationPresenceRule.optional(),
|
|
442
675
|
uniqueness: SchemaValidationUniquenessRule.optional(),
|
|
443
|
-
minimum:
|
|
444
|
-
maximum:
|
|
445
|
-
maxDecimalPlaces:
|
|
676
|
+
minimum: z3.object({ value: z3.number().describe("Minimum numeric value") }).strict().optional(),
|
|
677
|
+
maximum: z3.object({ value: z3.number().describe("Maximum numeric value") }).strict().optional(),
|
|
678
|
+
maxDecimalPlaces: z3.object({ value: z3.number().int().min(0).max(16) }).strict().optional()
|
|
446
679
|
}).strict(),
|
|
447
|
-
|
|
448
|
-
kind:
|
|
680
|
+
z3.object({
|
|
681
|
+
kind: z3.literal("OTHER"),
|
|
449
682
|
presence: SchemaValidationPresenceRule.optional(),
|
|
450
683
|
uniqueness: SchemaValidationUniquenessRule.optional()
|
|
451
684
|
}).strict(),
|
|
452
|
-
|
|
453
|
-
kind:
|
|
685
|
+
z3.object({
|
|
686
|
+
kind: z3.literal("OBJECT"),
|
|
454
687
|
presence: SchemaValidationPresenceRule.optional(),
|
|
455
688
|
uniqueness: SchemaValidationUniquenessRule.optional(),
|
|
456
|
-
properties:
|
|
689
|
+
properties: z3.record(z3.string(), SchemaValidationFieldRulesSchema)
|
|
457
690
|
}).strict(),
|
|
458
|
-
|
|
459
|
-
kind:
|
|
691
|
+
z3.object({
|
|
692
|
+
kind: z3.literal("ARRAY"),
|
|
460
693
|
presence: SchemaValidationPresenceRule.optional(),
|
|
461
694
|
uniqueness: SchemaValidationUniquenessRule.optional(),
|
|
462
695
|
minItems: SchemaValidationBoundRule.optional(),
|
|
@@ -464,21 +697,21 @@ var init_schemas = __esm(() => {
|
|
|
464
697
|
items: SchemaValidationFieldRulesSchema.optional()
|
|
465
698
|
}).strict()
|
|
466
699
|
]));
|
|
467
|
-
SchemaValidationRulesSchema =
|
|
700
|
+
SchemaValidationRulesSchema = z3.record(z3.string(), SchemaValidationFieldRulesSchema);
|
|
468
701
|
SCHEMA_VALIDATION_ATTRIBUTION_KEYS = new Set(["editedBy", "editedByLabel", "editedAt"]);
|
|
469
|
-
LocationSchema =
|
|
470
|
-
type:
|
|
471
|
-
isoCode:
|
|
702
|
+
LocationSchema = z3.object({
|
|
703
|
+
type: z3.enum(["auto", "manual"]),
|
|
704
|
+
isoCode: z3.string().trim().min(2).optional()
|
|
472
705
|
}).strict().superRefine((location, ctx) => {
|
|
473
706
|
if (location.type === "manual" && !location.isoCode) {
|
|
474
707
|
ctx.addIssue({
|
|
475
|
-
code:
|
|
708
|
+
code: z3.ZodIssueCode.custom,
|
|
476
709
|
path: ["isoCode"],
|
|
477
710
|
message: "isoCode is required for manual location"
|
|
478
711
|
});
|
|
479
712
|
}
|
|
480
713
|
if (location.type === "auto" && location.isoCode) {
|
|
481
|
-
ctx.addIssue({ code:
|
|
714
|
+
ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["isoCode"], message: "omit isoCode for auto location" });
|
|
482
715
|
}
|
|
483
716
|
});
|
|
484
717
|
MonitoringValueOperators = [
|
|
@@ -500,81 +733,81 @@ var init_schemas = __esm(() => {
|
|
|
500
733
|
"WITHIN_LAST_DAYS"
|
|
501
734
|
];
|
|
502
735
|
MonitoringValuelessOperators = ["IS_NULL", "IS_NOT_NULL", "IS_EMPTY", "IS_NOT_EMPTY"];
|
|
503
|
-
MonitoringConditionOperatorSchema =
|
|
504
|
-
MonitoringConditionSchema =
|
|
505
|
-
type:
|
|
506
|
-
field:
|
|
736
|
+
MonitoringConditionOperatorSchema = z3.enum([...MonitoringValueOperators, ...MonitoringValuelessOperators]);
|
|
737
|
+
MonitoringConditionSchema = z3.object({
|
|
738
|
+
type: z3.enum(["SINGLE", "ANY"]),
|
|
739
|
+
field: z3.string().trim().min(1).optional(),
|
|
507
740
|
operator: MonitoringConditionOperatorSchema,
|
|
508
|
-
value:
|
|
741
|
+
value: z3.union([z3.string(), z3.number(), z3.boolean()]).optional()
|
|
509
742
|
}).strict().superRefine((condition, ctx) => {
|
|
510
743
|
if (condition.type === "SINGLE" && !condition.field) {
|
|
511
744
|
ctx.addIssue({
|
|
512
|
-
code:
|
|
745
|
+
code: z3.ZodIssueCode.custom,
|
|
513
746
|
path: ["field"],
|
|
514
747
|
message: "field is required for SINGLE conditions"
|
|
515
748
|
});
|
|
516
749
|
}
|
|
517
750
|
const needsValue = MonitoringValueOperators.includes(condition.operator);
|
|
518
751
|
if (needsValue && condition.value === undefined) {
|
|
519
|
-
ctx.addIssue({ code:
|
|
752
|
+
ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["value"], message: `${condition.operator} requires a value` });
|
|
520
753
|
}
|
|
521
754
|
if (!needsValue && condition.value !== undefined) {
|
|
522
755
|
ctx.addIssue({
|
|
523
|
-
code:
|
|
756
|
+
code: z3.ZodIssueCode.custom,
|
|
524
757
|
path: ["value"],
|
|
525
758
|
message: `${condition.operator} does not accept a value`
|
|
526
759
|
});
|
|
527
760
|
}
|
|
528
761
|
});
|
|
529
|
-
MonitoringSchema =
|
|
530
|
-
fields:
|
|
531
|
-
fieldName:
|
|
532
|
-
operator:
|
|
533
|
-
isKeyField:
|
|
762
|
+
MonitoringSchema = z3.object({
|
|
763
|
+
fields: z3.preprocess(coerceArray(), z3.array(z3.object({
|
|
764
|
+
fieldName: z3.string().trim().min(1),
|
|
765
|
+
operator: z3.enum(["changed", "added", "removed"]),
|
|
766
|
+
isKeyField: z3.preprocess(coerceBoolean(), z3.boolean()).optional()
|
|
534
767
|
}).strict()).min(1)),
|
|
535
|
-
conditions:
|
|
536
|
-
logicalOperator:
|
|
537
|
-
conditions:
|
|
768
|
+
conditions: z3.object({
|
|
769
|
+
logicalOperator: z3.enum(["AND", "OR"]),
|
|
770
|
+
conditions: z3.preprocess(coerceArray(), z3.array(MonitoringConditionSchema).min(1))
|
|
538
771
|
}).strict().optional()
|
|
539
772
|
}).strict();
|
|
540
773
|
urlInputShape = {
|
|
541
|
-
url:
|
|
542
|
-
urls:
|
|
774
|
+
url: z3.string().optional().describe("Single URL - prefer using 'urls' instead. If both are provided, 'urls' takes precedence."),
|
|
775
|
+
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
776
|
};
|
|
544
|
-
extractionPromptInput =
|
|
777
|
+
extractionPromptInput = z3.string().optional();
|
|
545
778
|
extractionInputShape = {
|
|
546
|
-
name:
|
|
547
|
-
entity:
|
|
548
|
-
schema:
|
|
779
|
+
name: z3.string().optional().describe("Optional name for the workflow"),
|
|
780
|
+
entity: z3.string().optional().describe("Entity name for extraction (e.g., 'Product', 'Job Posting')"),
|
|
781
|
+
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
782
|
};
|
|
550
|
-
webhookAuthShape =
|
|
551
|
-
type:
|
|
552
|
-
token:
|
|
553
|
-
username:
|
|
554
|
-
password:
|
|
555
|
-
headers:
|
|
783
|
+
webhookAuthShape = z3.object({
|
|
784
|
+
type: z3.enum(["bearer", "basic", "header"]).describe("Authentication type"),
|
|
785
|
+
token: z3.string().optional().describe("Bearer token (required for 'bearer' type)"),
|
|
786
|
+
username: z3.string().optional().describe("Username (required for 'basic' type)"),
|
|
787
|
+
password: z3.string().optional().describe("Password (required for 'basic' type)"),
|
|
788
|
+
headers: z3.object({}).passthrough().optional().describe("Custom headers as key-value pairs (required for 'header' type)")
|
|
556
789
|
}).optional().describe("Authentication configuration for the webhook endpoint");
|
|
557
|
-
notificationChannelsSchema =
|
|
558
|
-
email:
|
|
559
|
-
recipients:
|
|
790
|
+
notificationChannelsSchema = z3.object({
|
|
791
|
+
email: z3.object({
|
|
792
|
+
recipients: z3.array(z3.string().email()).optional().describe("Email addresses to notify. Omit to use account default email.")
|
|
560
793
|
}).optional().describe("Send email notifications. Pass {} for account default email, or {recipients: [...]} for custom addresses."),
|
|
561
|
-
webhook:
|
|
562
|
-
url:
|
|
563
|
-
httpMethod:
|
|
794
|
+
webhook: z3.object({
|
|
795
|
+
url: z3.string().url().describe("Webhook endpoint URL"),
|
|
796
|
+
httpMethod: z3.enum(["POST", "GET", "PUT", "PATCH"]).optional().describe("HTTP method (defaults to POST)"),
|
|
564
797
|
auth: webhookAuthShape
|
|
565
798
|
}).optional().describe("Send notifications via HTTP webhook"),
|
|
566
|
-
slack:
|
|
567
|
-
webhookUrl:
|
|
568
|
-
slackChannelId:
|
|
569
|
-
slackChannelName:
|
|
799
|
+
slack: z3.object({
|
|
800
|
+
webhookUrl: z3.string().url().optional().describe("Slack incoming webhook URL (legacy)"),
|
|
801
|
+
slackChannelId: z3.string().optional().describe("Slack channel ID from OAuth integration (e.g. C01ABCDEF)"),
|
|
802
|
+
slackChannelName: z3.string().optional().describe("Slack channel name (e.g. #alerts)")
|
|
570
803
|
}).optional().describe("Send notifications to a Slack channel. Use slackChannelId/slackChannelName for OAuth integration, or webhookUrl for legacy incoming webhooks."),
|
|
571
|
-
websocket:
|
|
804
|
+
websocket: z3.boolean().optional().describe("Enable WebSocket notifications for programmatic real-time consumption")
|
|
572
805
|
});
|
|
573
806
|
notificationsInputShape = {
|
|
574
|
-
notifications:
|
|
807
|
+
notifications: z3.preprocess(coerceJson(), notificationChannelsSchema.optional()).optional().describe("Optional notification channels to alert when data changes.")
|
|
575
808
|
};
|
|
576
809
|
requiredNotificationsInputShape = {
|
|
577
|
-
notifications:
|
|
810
|
+
notifications: z3.preprocess(coerceJson(), notificationChannelsSchema).describe("Notification channels to alert when data changes.")
|
|
578
811
|
};
|
|
579
812
|
DATA_FILTER_OPERATORS = [
|
|
580
813
|
"EQUALS",
|
|
@@ -599,10 +832,10 @@ var init_schemas = __esm(() => {
|
|
|
599
832
|
];
|
|
600
833
|
DATA_FILTER_VALUELESS_OPERATORS = new Set(["IS_NULL", "IS_NOT_NULL", "IS_EMPTY", "IS_NOT_EMPTY"]);
|
|
601
834
|
DATA_FILTER_LIST_OPERATORS = new Set(["IN", "NOT_IN"]);
|
|
602
|
-
DataFilterSchema =
|
|
603
|
-
field:
|
|
604
|
-
operator:
|
|
605
|
-
value:
|
|
835
|
+
DataFilterSchema = z3.object({
|
|
836
|
+
field: z3.string().trim().min(1).describe("Field name from the workflow schema; one nested level is allowed, e.g. classification.match"),
|
|
837
|
+
operator: z3.preprocess((val) => typeof val === "string" ? val.trim().toUpperCase() : val, z3.enum(DATA_FILTER_OPERATORS)),
|
|
838
|
+
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
839
|
}).strict().superRefine((filter, ctx) => {
|
|
607
840
|
if (DATA_FILTER_VALUELESS_OPERATORS.has(filter.operator)) {
|
|
608
841
|
if (filter.value !== undefined) {
|
|
@@ -627,12 +860,12 @@ var init_schemas = __esm(() => {
|
|
|
627
860
|
});
|
|
628
861
|
}
|
|
629
862
|
});
|
|
630
|
-
dataFiltersInput =
|
|
863
|
+
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
864
|
});
|
|
632
865
|
|
|
633
866
|
// src/tools/assistant.ts
|
|
634
867
|
import { FREEFORM_ASSISTANT_ANSWER_KEY } from "@kadoa/node-sdk";
|
|
635
|
-
import { z as
|
|
868
|
+
import { z as z4 } from "zod";
|
|
636
869
|
function isAssistantSessionResumable(status) {
|
|
637
870
|
return status != null && RESUMABLE_ASSISTANT_STATUSES.has(status);
|
|
638
871
|
}
|
|
@@ -700,8 +933,9 @@ function registerAssistantTools(server, ctx, helpers) {
|
|
|
700
933
|
title: "Get Workflow Assistant",
|
|
701
934
|
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
935
|
inputSchema: strictSchema({
|
|
703
|
-
workflowId:
|
|
936
|
+
workflowId: z4.string().min(1).describe("The workflow ID")
|
|
704
937
|
}),
|
|
938
|
+
outputSchema: WorkflowAssistantOutput,
|
|
705
939
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
706
940
|
}, withErrorHandling("get_workflow_assistant", async (args) => {
|
|
707
941
|
const workflow = await ctx.client.workflow.get(args.workflowId);
|
|
@@ -733,10 +967,11 @@ function registerAssistantTools(server, ctx, helpers) {
|
|
|
733
967
|
title: "Get Workflow Assistant Timeline",
|
|
734
968
|
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
969
|
inputSchema: strictSchema({
|
|
736
|
-
workflowId:
|
|
737
|
-
cursor:
|
|
738
|
-
limit:
|
|
970
|
+
workflowId: z4.string().min(1).describe("The workflow ID"),
|
|
971
|
+
cursor: z4.string().min(1).optional().describe("Opaque nextCursor from the previous page"),
|
|
972
|
+
limit: limitInput({ noun: "History items", defaultLimit: 50, max: 100 })
|
|
739
973
|
}),
|
|
974
|
+
outputSchema: AssistantTimelineOutput,
|
|
740
975
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
741
976
|
}, withErrorHandling("get_workflow_assistant_timeline", async (args) => {
|
|
742
977
|
const timeline = await ctx.client.assistant.getTimeline(args.workflowId, {
|
|
@@ -749,8 +984,12 @@ function registerAssistantTools(server, ctx, helpers) {
|
|
|
749
984
|
return jsonResult({
|
|
750
985
|
workflowId: timeline.workflowId,
|
|
751
986
|
sessionId: timeline.sessionId,
|
|
752
|
-
items: timeline.items,
|
|
753
|
-
pagination:
|
|
987
|
+
items: timeline.items ?? [],
|
|
988
|
+
pagination: cursorPage({
|
|
989
|
+
args,
|
|
990
|
+
nextCursor: timeline.pagination?.nextCursor,
|
|
991
|
+
hasMore: timeline.pagination?.hasMore === true
|
|
992
|
+
}),
|
|
754
993
|
dashboardUrl: workflowDashboardUrl(timeline.workflowId)
|
|
755
994
|
});
|
|
756
995
|
}));
|
|
@@ -758,11 +997,12 @@ function registerAssistantTools(server, ctx, helpers) {
|
|
|
758
997
|
title: "Answer Assistant Question",
|
|
759
998
|
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
999
|
inputSchema: strictSchema({
|
|
761
|
-
workflowId:
|
|
762
|
-
questionId:
|
|
763
|
-
answer:
|
|
764
|
-
answers:
|
|
1000
|
+
workflowId: z4.string().min(1).describe("The workflow ID"),
|
|
1001
|
+
questionId: z4.string().min(1).describe("The pending question ID returned by get_workflow_assistant"),
|
|
1002
|
+
answer: z4.string().min(1).optional().describe("Freeform answer"),
|
|
1003
|
+
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
1004
|
}),
|
|
1005
|
+
outputSchema: AssistantAnswerOutput,
|
|
766
1006
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
767
1007
|
}, withErrorHandling("answer_workflow_assistant_question", async (args) => {
|
|
768
1008
|
const hasFreeform = args.answer != null;
|
|
@@ -804,8 +1044,9 @@ function registerAssistantTools(server, ctx, helpers) {
|
|
|
804
1044
|
title: "Interrupt Workflow Assistant",
|
|
805
1045
|
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
1046
|
inputSchema: strictSchema({
|
|
807
|
-
workflowId:
|
|
1047
|
+
workflowId: z4.string().min(1).describe("Workflow whose Assistant session should be interrupted")
|
|
808
1048
|
}),
|
|
1049
|
+
outputSchema: AssistantControlOutput,
|
|
809
1050
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
810
1051
|
}, withErrorHandling("interrupt_workflow_assistant", async (args) => {
|
|
811
1052
|
const workflow = await ctx.client.workflow.get(args.workflowId);
|
|
@@ -838,8 +1079,9 @@ function registerAssistantTools(server, ctx, helpers) {
|
|
|
838
1079
|
title: "Resume Workflow Assistant",
|
|
839
1080
|
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
1081
|
inputSchema: strictSchema({
|
|
841
|
-
workflowId:
|
|
1082
|
+
workflowId: z4.string().min(1).describe("Workflow whose Assistant session should resume")
|
|
842
1083
|
}),
|
|
1084
|
+
outputSchema: AssistantControlOutput,
|
|
843
1085
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
844
1086
|
}, withErrorHandling("resume_workflow_assistant", async (args) => {
|
|
845
1087
|
const workflow = await ctx.client.workflow.get(args.workflowId);
|
|
@@ -872,8 +1114,9 @@ function registerAssistantTools(server, ctx, helpers) {
|
|
|
872
1114
|
title: "Stop Workflow Assistant",
|
|
873
1115
|
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
1116
|
inputSchema: strictSchema({
|
|
875
|
-
workflowId:
|
|
1117
|
+
workflowId: z4.string().min(1).describe("Workflow whose active Assistant session should stop")
|
|
876
1118
|
}),
|
|
1119
|
+
outputSchema: AssistantControlOutput,
|
|
877
1120
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }
|
|
878
1121
|
}, withErrorHandling("stop_workflow_assistant", async (args) => {
|
|
879
1122
|
const workflow = await ctx.client.workflow.get(args.workflowId);
|
|
@@ -905,8 +1148,9 @@ function registerAssistantTools(server, ctx, helpers) {
|
|
|
905
1148
|
title: "Get Workflow Strategy",
|
|
906
1149
|
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
1150
|
inputSchema: strictSchema({
|
|
908
|
-
workflowId:
|
|
1151
|
+
workflowId: z4.string().min(1).describe("The workflow ID")
|
|
909
1152
|
}),
|
|
1153
|
+
outputSchema: WorkflowStrategyOutput,
|
|
910
1154
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
911
1155
|
}, withErrorHandling("get_workflow_strategy", async (args) => {
|
|
912
1156
|
const workflow = await ctx.client.workflow.get(args.workflowId);
|
|
@@ -937,6 +1181,8 @@ function registerAssistantTools(server, ctx, helpers) {
|
|
|
937
1181
|
}
|
|
938
1182
|
var RESUMABLE_ASSISTANT_STATUSES, ACTIVE_ASSISTANT_STATUSES, IDLE_ASSISTANT_STATUSES, CLOSED_ASSISTANT_STATUSES;
|
|
939
1183
|
var init_assistant = __esm(() => {
|
|
1184
|
+
init_output_schemas();
|
|
1185
|
+
init_pagination();
|
|
940
1186
|
init_schemas();
|
|
941
1187
|
RESUMABLE_ASSISTANT_STATUSES = new Set(["pending", "created", "active", "idle", "inactive", "interrupted"]);
|
|
942
1188
|
ACTIVE_ASSISTANT_STATUSES = new Set(["pending", "created", "active"]);
|
|
@@ -945,6 +1191,7 @@ var init_assistant = __esm(() => {
|
|
|
945
1191
|
});
|
|
946
1192
|
|
|
947
1193
|
// src/notification-channels.ts
|
|
1194
|
+
import { createHash } from "node:crypto";
|
|
948
1195
|
function recipientKey(recipients) {
|
|
949
1196
|
return [...new Set(recipients.map((r) => r.trim().toLowerCase()))].sort().join(",");
|
|
950
1197
|
}
|
|
@@ -968,6 +1215,14 @@ function destinationOf(channelType, rawConfig) {
|
|
|
968
1215
|
return `url:${webhookUrl}`;
|
|
969
1216
|
return null;
|
|
970
1217
|
}
|
|
1218
|
+
function mcpChannelName(channelType, config) {
|
|
1219
|
+
const name = `mcp-${channelType.toLowerCase()}`;
|
|
1220
|
+
const slackName = config.slackChannelName;
|
|
1221
|
+
const destination = destinationOf(channelType, config) ?? (channelType === "SLACK" && typeof slackName === "string" && slackName.trim() ? `slack-name:${slackName.trim().toLowerCase()}` : null);
|
|
1222
|
+
if (destination === null)
|
|
1223
|
+
return name;
|
|
1224
|
+
return `${name}-${createHash("sha256").update(destination).digest("hex").slice(0, 8)}`;
|
|
1225
|
+
}
|
|
971
1226
|
function findReusableChannel(channels, channelType, config) {
|
|
972
1227
|
const wanted = destinationOf(channelType, config);
|
|
973
1228
|
if (wanted === null)
|
|
@@ -1012,16 +1267,23 @@ function redactChannelConfig(config) {
|
|
|
1012
1267
|
...auth !== undefined && { auth: redactAuth(auth) }
|
|
1013
1268
|
};
|
|
1014
1269
|
}
|
|
1270
|
+
function redactChannelRefs(channels) {
|
|
1271
|
+
return channels?.map((channel) => {
|
|
1272
|
+
const { config } = channel;
|
|
1273
|
+
return config === undefined ? channel : { ...channel, config: redactChannelConfig(config) };
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
var init_notification_channels = () => {};
|
|
1015
1277
|
|
|
1016
1278
|
// src/tools/notifications.ts
|
|
1017
|
-
import { z as
|
|
1279
|
+
import { z as z5 } from "zod";
|
|
1018
1280
|
function registerNotificationChannelTools(server, ctx, helpers) {
|
|
1019
1281
|
const { withErrorHandling } = helpers;
|
|
1020
1282
|
server.registerTool("list_notification_channels", {
|
|
1021
1283
|
title: "List Notification Channels",
|
|
1022
1284
|
description: "List notification channels configured for a workflow or across the workspace. Channels are destinations (email, Slack, webhook, websocket) where notifications are sent.",
|
|
1023
1285
|
inputSchema: strictSchema({
|
|
1024
|
-
workflowId:
|
|
1286
|
+
workflowId: z5.string().optional().describe("Filter channels linked to a specific workflow. Omit to list all workspace channels.")
|
|
1025
1287
|
}),
|
|
1026
1288
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
1027
1289
|
}, withErrorHandling("list_notification_channels", async (args) => {
|
|
@@ -1036,27 +1298,28 @@ function registerNotificationChannelTools(server, ctx, helpers) {
|
|
|
1036
1298
|
createdAt: ch.createdAt,
|
|
1037
1299
|
dashboardUrl: notificationSettingsDashboardUrl()
|
|
1038
1300
|
})),
|
|
1039
|
-
total: channels.length
|
|
1301
|
+
total: channels.length,
|
|
1302
|
+
pagination: completeList(channels.length)
|
|
1040
1303
|
});
|
|
1041
1304
|
}));
|
|
1042
1305
|
server.registerTool("create_notification_channel", {
|
|
1043
1306
|
title: "Create Notification Channel",
|
|
1044
1307
|
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
1308
|
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:
|
|
1309
|
+
channelType: z5.enum(["EMAIL", "WEBHOOK", "SLACK", "WEBSOCKET"]).describe("Type of notification channel"),
|
|
1310
|
+
name: z5.string().optional().describe("Name for the channel (defaults to 'default')"),
|
|
1311
|
+
config: z5.object({
|
|
1312
|
+
recipients: z5.array(z5.string()).optional().describe("Email recipients (required for EMAIL channels)"),
|
|
1313
|
+
webhookUrl: z5.string().optional().describe("Webhook/Slack endpoint URL (required for WEBHOOK, optional for SLACK)"),
|
|
1314
|
+
httpMethod: z5.enum(["POST", "GET", "PUT", "PATCH"]).optional().describe("HTTP method for WEBHOOK channels (defaults to POST)"),
|
|
1315
|
+
slackChannelId: z5.string().optional().describe("Slack channel ID from OAuth integration (for SLACK channels, e.g. C01ABCDEF)"),
|
|
1316
|
+
slackChannelName: z5.string().optional().describe("Slack channel name (for SLACK channels, e.g. #alerts)"),
|
|
1317
|
+
auth: z5.object({
|
|
1318
|
+
type: z5.enum(["bearer", "basic", "header"]).describe("Authentication type"),
|
|
1319
|
+
token: z5.string().optional().describe("Bearer token (for 'bearer' type)"),
|
|
1320
|
+
username: z5.string().optional().describe("Username (for 'basic' type)"),
|
|
1321
|
+
password: z5.string().optional().describe("Password (for 'basic' type)"),
|
|
1322
|
+
headers: z5.object({}).passthrough().optional().describe("Custom headers as key-value pairs (for 'header' type)")
|
|
1060
1323
|
}).optional().describe("Authentication for WEBHOOK channels")
|
|
1061
1324
|
}).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
1325
|
}),
|
|
@@ -1103,8 +1366,8 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
|
|
|
1103
1366
|
title: "Delete Notification Channel",
|
|
1104
1367
|
description: "Delete a notification channel by ID. " + "Call first without confirmed=true to preview, then with confirmed=true after user agrees.",
|
|
1105
1368
|
inputSchema: strictSchema({
|
|
1106
|
-
channelId:
|
|
1107
|
-
confirmed:
|
|
1369
|
+
channelId: z5.string().min(1).describe("The channel ID to delete"),
|
|
1370
|
+
confirmed: z5.preprocess(coerceBoolean(), z5.boolean()).optional().describe("Set to true only after the user has explicitly confirmed deletion.")
|
|
1108
1371
|
}),
|
|
1109
1372
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
1110
1373
|
}, withErrorHandling("delete_notification_channel", async (args) => {
|
|
@@ -1125,7 +1388,7 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
|
|
|
1125
1388
|
title: "List Notification Settings",
|
|
1126
1389
|
description: "List notification settings (event-to-channel mappings) for a workflow or workspace. Shows which events trigger notifications and through which channels.",
|
|
1127
1390
|
inputSchema: strictSchema({
|
|
1128
|
-
workflowId:
|
|
1391
|
+
workflowId: z5.string().optional().describe("Filter settings for a specific workflow. Omit to list workspace-level settings."),
|
|
1129
1392
|
eventType: NotificationEventTypeSchema.optional().describe("Filter by event type")
|
|
1130
1393
|
}),
|
|
1131
1394
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
@@ -1142,33 +1405,34 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
|
|
|
1142
1405
|
workflowId: s.workflowId,
|
|
1143
1406
|
eventType: s.eventType,
|
|
1144
1407
|
enabled: s.enabled,
|
|
1145
|
-
channels: s.channels,
|
|
1408
|
+
channels: redactChannelRefs(s.channels),
|
|
1146
1409
|
createdAt: s.createdAt
|
|
1147
1410
|
})),
|
|
1148
|
-
total: settings.length
|
|
1411
|
+
total: settings.length,
|
|
1412
|
+
pagination: completeList(settings.length)
|
|
1149
1413
|
});
|
|
1150
1414
|
}));
|
|
1151
1415
|
server.registerTool("configure_notifications", {
|
|
1152
1416
|
title: "Configure Notifications",
|
|
1153
1417
|
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
1418
|
inputSchema: strictSchema({
|
|
1155
|
-
workflowId:
|
|
1156
|
-
events:
|
|
1157
|
-
channels:
|
|
1158
|
-
email:
|
|
1159
|
-
recipients:
|
|
1419
|
+
workflowId: z5.string().optional().describe("Workflow ID to configure notifications for. Omit for workspace-level notifications."),
|
|
1420
|
+
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.'),
|
|
1421
|
+
channels: z5.object({
|
|
1422
|
+
email: z5.object({
|
|
1423
|
+
recipients: z5.array(z5.string().email()).optional().describe("Email addresses to notify. Omit to use the account default email.")
|
|
1160
1424
|
}).optional().describe("Send email notifications. Pass {} for default email or {recipients: [...]} for custom addresses."),
|
|
1161
|
-
webhook:
|
|
1162
|
-
url:
|
|
1163
|
-
httpMethod:
|
|
1425
|
+
webhook: z5.object({
|
|
1426
|
+
url: z5.string().url().describe("Webhook endpoint URL"),
|
|
1427
|
+
httpMethod: z5.enum(["POST", "GET", "PUT", "PATCH"]).optional().describe("HTTP method (defaults to POST)"),
|
|
1164
1428
|
auth: webhookAuthShape
|
|
1165
1429
|
}).optional().describe("Send via HTTP webhook"),
|
|
1166
|
-
slack:
|
|
1167
|
-
webhookUrl:
|
|
1168
|
-
slackChannelId:
|
|
1169
|
-
slackChannelName:
|
|
1430
|
+
slack: z5.object({
|
|
1431
|
+
webhookUrl: z5.string().url().optional().describe("Slack incoming webhook URL (legacy)"),
|
|
1432
|
+
slackChannelId: z5.string().optional().describe("Slack channel ID from OAuth integration (e.g. C01ABCDEF)"),
|
|
1433
|
+
slackChannelName: z5.string().optional().describe("Slack channel name (e.g. #alerts)")
|
|
1170
1434
|
}).optional().describe("Send to Slack channel. Use slackChannelId/slackChannelName for OAuth integration, or webhookUrl for legacy incoming webhooks."),
|
|
1171
|
-
websocket:
|
|
1435
|
+
websocket: z5.boolean().optional().describe("Enable WebSocket notifications")
|
|
1172
1436
|
}).describe("At least one channel must be specified")
|
|
1173
1437
|
}),
|
|
1174
1438
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
@@ -1178,7 +1442,7 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
|
|
|
1178
1442
|
const ch = args.channels;
|
|
1179
1443
|
if (ch.email) {
|
|
1180
1444
|
if (ch.email.recipients?.length) {
|
|
1181
|
-
channelSetup.EMAIL = { name: "
|
|
1445
|
+
channelSetup.EMAIL = { name: mcpChannelName("EMAIL", ch.email), recipients: ch.email.recipients };
|
|
1182
1446
|
} else {
|
|
1183
1447
|
channelSetup.EMAIL = true;
|
|
1184
1448
|
}
|
|
@@ -1187,7 +1451,7 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
|
|
|
1187
1451
|
channelSetup.WEBSOCKET = true;
|
|
1188
1452
|
if (ch.webhook) {
|
|
1189
1453
|
const webhookChannel = {
|
|
1190
|
-
name: "
|
|
1454
|
+
name: mcpChannelName("WEBHOOK", { webhookUrl: ch.webhook.url }),
|
|
1191
1455
|
webhookUrl: ch.webhook.url,
|
|
1192
1456
|
httpMethod: ch.webhook.httpMethod || "POST"
|
|
1193
1457
|
};
|
|
@@ -1196,7 +1460,7 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
|
|
|
1196
1460
|
channelSetup.WEBHOOK = webhookChannel;
|
|
1197
1461
|
}
|
|
1198
1462
|
if (ch.slack) {
|
|
1199
|
-
const slackChannel = { name: "
|
|
1463
|
+
const slackChannel = { name: mcpChannelName("SLACK", ch.slack) };
|
|
1200
1464
|
if (ch.slack.webhookUrl)
|
|
1201
1465
|
slackChannel.webhookUrl = ch.slack.webhookUrl;
|
|
1202
1466
|
if (ch.slack.slackChannelId)
|
|
@@ -1222,7 +1486,7 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
|
|
|
1222
1486
|
id: s.id,
|
|
1223
1487
|
eventType: s.eventType,
|
|
1224
1488
|
enabled: s.enabled,
|
|
1225
|
-
channels: s.channels
|
|
1489
|
+
channels: redactChannelRefs(s.channels)
|
|
1226
1490
|
}))
|
|
1227
1491
|
});
|
|
1228
1492
|
}));
|
|
@@ -1230,8 +1494,8 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
|
|
|
1230
1494
|
title: "Delete Notification Setting",
|
|
1231
1495
|
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
1496
|
inputSchema: strictSchema({
|
|
1233
|
-
settingId:
|
|
1234
|
-
confirmed:
|
|
1497
|
+
settingId: z5.string().min(1).describe("The notification setting ID to delete (the id from list_notification_settings)"),
|
|
1498
|
+
confirmed: z5.preprocess(coerceBoolean(), z5.boolean()).optional().describe("Set to true only after the user has explicitly confirmed deletion.")
|
|
1235
1499
|
}),
|
|
1236
1500
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
1237
1501
|
}, withErrorHandling("delete_notification_setting", async (args) => {
|
|
@@ -1250,17 +1514,14 @@ function registerNotificationSettingsTools(server, ctx, helpers) {
|
|
|
1250
1514
|
}));
|
|
1251
1515
|
}
|
|
1252
1516
|
var init_notifications = __esm(() => {
|
|
1517
|
+
init_notification_channels();
|
|
1518
|
+
init_pagination();
|
|
1253
1519
|
init_schemas();
|
|
1254
1520
|
});
|
|
1255
1521
|
|
|
1256
1522
|
// src/tools/observability.ts
|
|
1257
1523
|
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
|
-
}
|
|
1524
|
+
import { z as z6 } from "zod";
|
|
1264
1525
|
function mapActivityEvent(event, includeDetails) {
|
|
1265
1526
|
const details = event.details ?? {};
|
|
1266
1527
|
const summary = details._changeSummary;
|
|
@@ -1333,10 +1594,10 @@ function registerObservabilityTools(server, ctx, helpers) {
|
|
|
1333
1594
|
title: "Get Observability Metrics",
|
|
1334
1595
|
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
1596
|
inputSchema: strictSchema({
|
|
1336
|
-
days:
|
|
1337
|
-
includeWorkflows:
|
|
1338
|
-
includeDailySeries:
|
|
1339
|
-
limit:
|
|
1597
|
+
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."),
|
|
1598
|
+
includeWorkflows: z6.preprocess(coerceBoolean(), z6.boolean()).optional().describe("Also return every workflow ranked by severity, not only the down and degraded ones (default false)."),
|
|
1599
|
+
includeDailySeries: z6.preprocess(coerceBoolean(), z6.boolean()).optional().describe("Also return the team-level daily series of successful runs, failed runs, and uptime (default false)."),
|
|
1600
|
+
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
1601
|
}),
|
|
1341
1602
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
1342
1603
|
}, withErrorHandling("get_observability", async (args) => {
|
|
@@ -1392,22 +1653,23 @@ function registerObservabilityTools(server, ctx, helpers) {
|
|
|
1392
1653
|
title: "List Activity",
|
|
1393
1654
|
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
1655
|
inputSchema: strictSchema({
|
|
1395
|
-
limit:
|
|
1396
|
-
offset:
|
|
1397
|
-
workflowId:
|
|
1398
|
-
userId:
|
|
1399
|
-
startDate:
|
|
1400
|
-
endDate:
|
|
1401
|
-
relativeTime:
|
|
1402
|
-
eventTypes:
|
|
1403
|
-
interfaces:
|
|
1404
|
-
resourceTypes:
|
|
1405
|
-
includeDetails:
|
|
1656
|
+
limit: limitInput({ noun: "Events", defaultLimit: ACTIVITY_DEFAULT_LIMIT, max: ACTIVITY_MAX_LIMIT }),
|
|
1657
|
+
offset: offsetInput("offset", "events"),
|
|
1658
|
+
workflowId: z6.string().optional().describe("Only events about this workflow (runs, config changes, data changes, notifications)"),
|
|
1659
|
+
userId: z6.string().optional().describe("Only events by this user id. Resolve an email to an id from a previous list_activity result."),
|
|
1660
|
+
startDate: z6.string().optional().describe("ISO 8601 lower bound. Ignored when relativeTime is set."),
|
|
1661
|
+
endDate: z6.string().optional().describe("ISO 8601 upper bound. Ignored when relativeTime is set."),
|
|
1662
|
+
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."),
|
|
1663
|
+
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."),
|
|
1664
|
+
interfaces: z6.preprocess(coerceList({ upperCase: true }), z6.array(z6.enum(ACTIVITY_INTERFACES))).optional().describe("Interfaces the action came from"),
|
|
1665
|
+
resourceTypes: z6.preprocess(coerceList({ upperCase: true }), z6.array(z6.enum(ACTIVITY_RESOURCE_TYPES))).optional().describe("Resource types to keep"),
|
|
1666
|
+
includeDetails: z6.preprocess(coerceBoolean(), z6.boolean()).optional().describe("Include sanitized previousValue/newValue snapshots per event (default false; large)")
|
|
1406
1667
|
}),
|
|
1407
1668
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
1408
1669
|
}, withErrorHandling("list_activity", async (args) => {
|
|
1670
|
+
const limit = args.limit ?? ACTIVITY_DEFAULT_LIMIT;
|
|
1409
1671
|
const result = await ctx.client.activity.list({
|
|
1410
|
-
limit
|
|
1672
|
+
limit,
|
|
1411
1673
|
offset: args.offset,
|
|
1412
1674
|
workflowId: args.workflowId,
|
|
1413
1675
|
userId: args.userId,
|
|
@@ -1421,7 +1683,15 @@ function registerObservabilityTools(server, ctx, helpers) {
|
|
|
1421
1683
|
const events = result.events.map((event) => mapActivityEvent(event, args.includeDetails === true));
|
|
1422
1684
|
return jsonResult({
|
|
1423
1685
|
events,
|
|
1424
|
-
pagination:
|
|
1686
|
+
pagination: offsetPage({
|
|
1687
|
+
args,
|
|
1688
|
+
param: "offset",
|
|
1689
|
+
offset: args.offset ?? 0,
|
|
1690
|
+
limit,
|
|
1691
|
+
returned: events.length,
|
|
1692
|
+
totalCount: result.pagination?.total,
|
|
1693
|
+
hasMore: result.pagination?.hasMore
|
|
1694
|
+
}),
|
|
1425
1695
|
...events.length === 0 ? {
|
|
1426
1696
|
hint: "No activity matched these filters. Widen relativeTime or drop a filter before concluding nothing happened."
|
|
1427
1697
|
} : {}
|
|
@@ -1430,6 +1700,7 @@ function registerObservabilityTools(server, ctx, helpers) {
|
|
|
1430
1700
|
}
|
|
1431
1701
|
var ACTIVITY_DEFAULT_LIMIT = 25, ACTIVITY_MAX_LIMIT = 200, ACTIVITY_EVENT_TYPES, ACTIVITY_INTERFACES, ACTIVITY_RESOURCE_TYPES, HEALTH_SEVERITY;
|
|
1432
1702
|
var init_observability = __esm(() => {
|
|
1703
|
+
init_pagination();
|
|
1433
1704
|
init_schemas();
|
|
1434
1705
|
ACTIVITY_EVENT_TYPES = [
|
|
1435
1706
|
"CREATED",
|
|
@@ -1470,30 +1741,174 @@ var init_observability = __esm(() => {
|
|
|
1470
1741
|
HEALTH_SEVERITY = { down: 0, degraded: 1, idle: 2, healthy: 3 };
|
|
1471
1742
|
});
|
|
1472
1743
|
|
|
1744
|
+
// src/tools/shared/budget.ts
|
|
1745
|
+
function jsonBytes(value) {
|
|
1746
|
+
const text = JSON.stringify(value);
|
|
1747
|
+
return text === undefined ? 0 : Buffer.byteLength(text, "utf8");
|
|
1748
|
+
}
|
|
1749
|
+
function truncateUtf8(text, maxBytes) {
|
|
1750
|
+
if (Buffer.byteLength(text, "utf8") <= maxBytes)
|
|
1751
|
+
return text;
|
|
1752
|
+
const { read } = new TextEncoder().encodeInto(text, new Uint8Array(Math.max(0, maxBytes)));
|
|
1753
|
+
return text.slice(0, read);
|
|
1754
|
+
}
|
|
1755
|
+
function previewValue(value, maxBytes = VALUE_PREVIEW_BYTES) {
|
|
1756
|
+
if (value === undefined || value === null)
|
|
1757
|
+
return null;
|
|
1758
|
+
const bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
1759
|
+
if (bytes <= maxBytes)
|
|
1760
|
+
return value;
|
|
1761
|
+
if (typeof value === "string")
|
|
1762
|
+
return `${truncateUtf8(value, maxBytes)}… [cut, ${bytes} bytes]`;
|
|
1763
|
+
if (Array.isArray(value))
|
|
1764
|
+
return `[array of ${value.length} items, ${bytes} bytes]`;
|
|
1765
|
+
return `[${typeof value}, ${bytes} bytes]`;
|
|
1766
|
+
}
|
|
1767
|
+
function fitItems(options) {
|
|
1768
|
+
const budget = options.budgetBytes ?? RESULT_BUDGET_BYTES;
|
|
1769
|
+
const all = [...options.items];
|
|
1770
|
+
if (jsonBytes(options.build(all)) <= budget)
|
|
1771
|
+
return { kept: all, omitted: 0, truncated: false };
|
|
1772
|
+
const limit = budget - TRUNCATION_RESERVE_BYTES - (options.reserveBytes ?? 0);
|
|
1773
|
+
let lo = 0;
|
|
1774
|
+
let hi = all.length - 1;
|
|
1775
|
+
while (lo < hi) {
|
|
1776
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
1777
|
+
if (jsonBytes(options.build(all.slice(0, mid))) <= limit)
|
|
1778
|
+
lo = mid;
|
|
1779
|
+
else
|
|
1780
|
+
hi = mid - 1;
|
|
1781
|
+
}
|
|
1782
|
+
return { kept: all.slice(0, lo), omitted: all.length - lo, truncated: true };
|
|
1783
|
+
}
|
|
1784
|
+
function truncationNotice(options) {
|
|
1785
|
+
const budgetBytes = options.budgetBytes ?? RESULT_BUDGET_BYTES;
|
|
1786
|
+
const total = options.kept + options.omitted;
|
|
1787
|
+
return {
|
|
1788
|
+
truncated: true,
|
|
1789
|
+
truncation: { kept: options.kept, omitted: options.omitted, budgetBytes },
|
|
1790
|
+
hint: `Result cut to ${options.kept} of ${total} ${options.noun} to stay under ${Math.round(budgetBytes / 1000)} KB. ${options.hint}`
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
function highlyDivisibleFloor(n) {
|
|
1794
|
+
if (n <= 0)
|
|
1795
|
+
return 0;
|
|
1796
|
+
if (n >= 10)
|
|
1797
|
+
return Math.floor(n / 10) * 10;
|
|
1798
|
+
let p = 1;
|
|
1799
|
+
while (p * 2 <= n)
|
|
1800
|
+
p *= 2;
|
|
1801
|
+
return p;
|
|
1802
|
+
}
|
|
1803
|
+
function alignedKeep(offset, fit) {
|
|
1804
|
+
if (offset === 0)
|
|
1805
|
+
return highlyDivisibleFloor(fit);
|
|
1806
|
+
for (let keep = fit;keep >= 1; keep--) {
|
|
1807
|
+
if (offset % keep === 0 && offset / keep + 2 <= MAX_PAGE)
|
|
1808
|
+
return keep;
|
|
1809
|
+
}
|
|
1810
|
+
return 0;
|
|
1811
|
+
}
|
|
1812
|
+
function resumeNumberedPage(meta, options) {
|
|
1813
|
+
const { next: _replaced, totalPages: _droppedTotalPages, ...rest } = meta;
|
|
1814
|
+
if (options.kept === 0)
|
|
1815
|
+
return { ...rest, hasMore: false };
|
|
1816
|
+
const offset = (options.page - 1) * options.limit;
|
|
1817
|
+
return {
|
|
1818
|
+
...rest,
|
|
1819
|
+
hasMore: true,
|
|
1820
|
+
next: nextArgs(options.args, { page: offset / options.kept + 2, limit: options.kept })
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
function budgetNumberedPage(options) {
|
|
1824
|
+
const offset = (options.page - 1) * options.limit;
|
|
1825
|
+
const fit = fitItems({
|
|
1826
|
+
items: options.items,
|
|
1827
|
+
build: (kept) => options.build(kept, options.pagination),
|
|
1828
|
+
reserveBytes: jsonBytes({ pagination: { next: options.args } })
|
|
1829
|
+
});
|
|
1830
|
+
if (!fit.truncated)
|
|
1831
|
+
return options.build([...options.items], options.pagination);
|
|
1832
|
+
const buildCut = (fitCount) => {
|
|
1833
|
+
const keep = alignedKeep(offset, fitCount);
|
|
1834
|
+
const alignmentImpossible = keep === 0 && fitCount > 0;
|
|
1835
|
+
const pagination = resumeNumberedPage(options.pagination, {
|
|
1836
|
+
args: options.args,
|
|
1837
|
+
page: options.page,
|
|
1838
|
+
limit: options.limit,
|
|
1839
|
+
kept: keep
|
|
1840
|
+
});
|
|
1841
|
+
const hint = keep > 0 ? options.resumeHint : alignmentImpossible ? options.alignmentImpossibleHint ?? DEFAULT_ALIGNMENT_IMPOSSIBLE_HINT : options.emptyHint;
|
|
1842
|
+
const result = {
|
|
1843
|
+
...options.build(options.items.slice(0, keep), pagination),
|
|
1844
|
+
...truncationNotice({ kept: keep, omitted: options.items.length - keep, noun: options.noun, hint })
|
|
1845
|
+
};
|
|
1846
|
+
return { result, keep };
|
|
1847
|
+
};
|
|
1848
|
+
let fitCount = fit.kept.length;
|
|
1849
|
+
let { result, keep } = buildCut(fitCount);
|
|
1850
|
+
while (jsonBytes(result) > RESULT_BUDGET_BYTES && keep > 0) {
|
|
1851
|
+
fitCount = keep - 1;
|
|
1852
|
+
({ result, keep } = buildCut(fitCount));
|
|
1853
|
+
}
|
|
1854
|
+
return result;
|
|
1855
|
+
}
|
|
1856
|
+
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.";
|
|
1857
|
+
var init_budget = __esm(() => {
|
|
1858
|
+
init_pagination();
|
|
1859
|
+
});
|
|
1860
|
+
|
|
1473
1861
|
// src/tools/scrape.ts
|
|
1474
|
-
import { z as
|
|
1862
|
+
import { z as z7 } from "zod";
|
|
1863
|
+
function scrapeResult(response, format) {
|
|
1864
|
+
const { content, ...metadata } = response;
|
|
1865
|
+
if (typeof content !== "string" || content.length === 0)
|
|
1866
|
+
return textOnlyResult(metadata);
|
|
1867
|
+
const contentBytes = Buffer.byteLength(content, "utf8");
|
|
1868
|
+
if (response.isBase64) {
|
|
1869
|
+
return textOnlyResult({
|
|
1870
|
+
...metadata,
|
|
1871
|
+
contentBytes,
|
|
1872
|
+
contentOmitted: "binary",
|
|
1873
|
+
hint: `The URL returned binary content (${response.contentType ?? "unknown type"}), which scrape does not return inline.`
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
const text = truncateUtf8(content, SCRAPE_CONTENT_BUDGET_BYTES);
|
|
1877
|
+
const keptBytes = Buffer.byteLength(text, "utf8");
|
|
1878
|
+
const cut = keptBytes < contentBytes;
|
|
1879
|
+
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.";
|
|
1880
|
+
const meta = {
|
|
1881
|
+
...metadata,
|
|
1882
|
+
contentBytes,
|
|
1883
|
+
...cut && {
|
|
1884
|
+
truncated: true,
|
|
1885
|
+
truncation: { keptBytes, omittedBytes: contentBytes - keptBytes, budgetBytes: SCRAPE_CONTENT_BUDGET_BYTES },
|
|
1886
|
+
hint
|
|
1887
|
+
}
|
|
1888
|
+
};
|
|
1889
|
+
return withExtraContent(textOnlyResult(meta), { type: "text", text });
|
|
1890
|
+
}
|
|
1475
1891
|
function registerScrapeTools(server, ctx, helpers) {
|
|
1476
1892
|
const { withErrorHandling } = helpers;
|
|
1477
1893
|
server.registerTool("scrape", {
|
|
1478
1894
|
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.",
|
|
1895
|
+
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. " + "The result is a JSON metadata block followed by the page text as its own block, capped at 70 KB.",
|
|
1480
1896
|
inputSchema: strictSchema({
|
|
1481
|
-
url:
|
|
1897
|
+
url: z7.url().refine((value) => {
|
|
1482
1898
|
const protocol = new URL(value).protocol;
|
|
1483
1899
|
return protocol === "http:" || protocol === "https:";
|
|
1484
1900
|
}, "URL must use HTTP or HTTPS").describe("HTTP or HTTPS URL to fetch"),
|
|
1485
|
-
format:
|
|
1901
|
+
format: z7.enum(["html", "markdown"]).optional().describe("Output format. Defaults to markdown for compact LLM consumption; use html for raw source.")
|
|
1486
1902
|
}),
|
|
1487
1903
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1488
1904
|
}, withErrorHandling("scrape", async (args) => {
|
|
1489
|
-
const
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
});
|
|
1493
|
-
return jsonResult(response);
|
|
1905
|
+
const format = args.format ?? "markdown";
|
|
1906
|
+
const response = await ctx.client.scrape.fetch({ url: args.url, format });
|
|
1907
|
+
return scrapeResult(response, format);
|
|
1494
1908
|
}));
|
|
1495
1909
|
}
|
|
1496
1910
|
var init_scrape = __esm(() => {
|
|
1911
|
+
init_budget();
|
|
1497
1912
|
init_schemas();
|
|
1498
1913
|
});
|
|
1499
1914
|
|
|
@@ -1649,7 +2064,7 @@ var init_errors = () => {};
|
|
|
1649
2064
|
|
|
1650
2065
|
// src/tools/support.ts
|
|
1651
2066
|
import { KadoaHttpException as KadoaHttpException2 } from "@kadoa/node-sdk";
|
|
1652
|
-
import { z as
|
|
2067
|
+
import { z as z8 } from "zod";
|
|
1653
2068
|
function supportTicketSkipMessage(reason) {
|
|
1654
2069
|
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
2070
|
}
|
|
@@ -1673,11 +2088,11 @@ function registerSupportTools(server, ctx, helpers) {
|
|
|
1673
2088
|
title: "Create Support Ticket",
|
|
1674
2089
|
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
2090
|
inputSchema: strictSchema({
|
|
1676
|
-
workflowId:
|
|
1677
|
-
title:
|
|
1678
|
-
description:
|
|
1679
|
-
category:
|
|
1680
|
-
jobId:
|
|
2091
|
+
workflowId: z8.string().min(1).describe("The workflow the problem is about"),
|
|
2092
|
+
title: z8.string().describe("One-line summary of the problem"),
|
|
2093
|
+
description: z8.string().describe("What is wrong, what was observed, and what was already tried"),
|
|
2094
|
+
category: z8.enum(["bug", "feature", "workflow_issue", "template_issue", "integration", "billing", "data_diff"]).optional().describe("Support category; defaults to a workflow issue"),
|
|
2095
|
+
jobId: z8.string().optional().describe("The failing run's id (from list_workflow_runs). Required when filing from the Kadoa Assistant.")
|
|
1681
2096
|
}),
|
|
1682
2097
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
1683
2098
|
}, withErrorHandling("create_support_ticket", async (args) => {
|
|
@@ -1741,7 +2156,7 @@ var init_support = __esm(() => {
|
|
|
1741
2156
|
});
|
|
1742
2157
|
|
|
1743
2158
|
// src/tools/team.ts
|
|
1744
|
-
import { z as
|
|
2159
|
+
import { z as z9 } from "zod";
|
|
1745
2160
|
function registerWhoamiTool(server, ctx, helpers) {
|
|
1746
2161
|
const { withErrorHandling } = helpers;
|
|
1747
2162
|
server.registerTool("whoami", {
|
|
@@ -1803,7 +2218,7 @@ function registerTeamTools(server, ctx, helpers) {
|
|
|
1803
2218
|
title: "Switch Team",
|
|
1804
2219
|
description: "Switch the active team by name or ID. After switching, all subsequent API calls will use the new team context.",
|
|
1805
2220
|
inputSchema: strictSchema({
|
|
1806
|
-
teamIdentifier:
|
|
2221
|
+
teamIdentifier: z9.string().min(1).describe("Team name or team ID to switch to")
|
|
1807
2222
|
}),
|
|
1808
2223
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
1809
2224
|
}, withErrorHandling("team_switch", async () => errorResult("team_switch is not available for this credential. Reconnect and choose the team at sign-in.")));
|
|
@@ -1814,7 +2229,7 @@ var init_team = __esm(() => {
|
|
|
1814
2229
|
});
|
|
1815
2230
|
|
|
1816
2231
|
// src/tools/templates.ts
|
|
1817
|
-
import { z as
|
|
2232
|
+
import { z as z10 } from "zod";
|
|
1818
2233
|
function normalizeTemplateSchemaField(val) {
|
|
1819
2234
|
if (val === null || typeof val !== "object" || Array.isArray(val))
|
|
1820
2235
|
return val;
|
|
@@ -1844,6 +2259,7 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
1844
2259
|
return jsonResult({
|
|
1845
2260
|
templates: templates.map(withTemplateDashboardUrl),
|
|
1846
2261
|
count: templates.length,
|
|
2262
|
+
pagination: completeList(templates.length),
|
|
1847
2263
|
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
2264
|
});
|
|
1849
2265
|
}));
|
|
@@ -1851,7 +2267,7 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
1851
2267
|
title: "Get Template",
|
|
1852
2268
|
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
2269
|
inputSchema: strictSchema({
|
|
1854
|
-
templateId:
|
|
2270
|
+
templateId: z10.string().min(1).describe("The template ID")
|
|
1855
2271
|
}),
|
|
1856
2272
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
1857
2273
|
}, withErrorHandling("get_template", async (args) => {
|
|
@@ -1865,8 +2281,8 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
1865
2281
|
title: "Create Template",
|
|
1866
2282
|
description: "Create a new template. After creation, use create_template_version to publish a version with prompt, schema, and notifications.",
|
|
1867
2283
|
inputSchema: strictSchema({
|
|
1868
|
-
name:
|
|
1869
|
-
description:
|
|
2284
|
+
name: z10.string().describe("Template name (1-255 chars)"),
|
|
2285
|
+
description: z10.string().optional().describe("Template description (max 2000 chars)")
|
|
1870
2286
|
}),
|
|
1871
2287
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
1872
2288
|
}, withErrorHandling("create_template", async (args) => {
|
|
@@ -1886,9 +2302,9 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
1886
2302
|
|
|
1887
2303
|
` + "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
2304
|
inputSchema: strictSchema({
|
|
1889
|
-
templateId:
|
|
1890
|
-
name:
|
|
1891
|
-
description:
|
|
2305
|
+
templateId: z10.string().min(1).describe("The template ID to update"),
|
|
2306
|
+
name: z10.preprocess(coerceNull(), z10.string().optional()).optional().describe("New template name"),
|
|
2307
|
+
description: z10.preprocess(coerceNull(), z10.string().optional()).optional().describe("New template description")
|
|
1892
2308
|
}),
|
|
1893
2309
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
1894
2310
|
}, withErrorHandling("update_template", async (args) => {
|
|
@@ -1904,8 +2320,8 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
1904
2320
|
title: "Delete Template",
|
|
1905
2321
|
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
2322
|
inputSchema: strictSchema({
|
|
1907
|
-
templateId:
|
|
1908
|
-
confirmed:
|
|
2323
|
+
templateId: z10.string().min(1).describe("The template ID to delete"),
|
|
2324
|
+
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
2325
|
}),
|
|
1910
2326
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
1911
2327
|
}, withErrorHandling("delete_template", async (args) => {
|
|
@@ -1929,15 +2345,15 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
1929
2345
|
});
|
|
1930
2346
|
}));
|
|
1931
2347
|
const TemplateSchemaFieldShape = {
|
|
1932
|
-
name:
|
|
1933
|
-
description:
|
|
1934
|
-
fieldType:
|
|
1935
|
-
example:
|
|
2348
|
+
name: z10.string().describe("Field name"),
|
|
2349
|
+
description: z10.string().optional().describe("Field description"),
|
|
2350
|
+
fieldType: z10.enum(["SCHEMA", "CLASSIFICATION"]).optional().describe("Field role: SCHEMA (default) for extracted data, CLASSIFICATION for a field with predefined categories"),
|
|
2351
|
+
example: z10.string().optional().describe("Example value"),
|
|
1936
2352
|
dataType: SchemaFieldDataTypeSchema.optional().describe("Data type for SCHEMA fields"),
|
|
1937
|
-
isKey:
|
|
1938
|
-
categories:
|
|
1939
|
-
title:
|
|
1940
|
-
definition:
|
|
2353
|
+
isKey: z10.preprocess(coerceBoolean(), z10.boolean()).optional().describe("Whether the field is a key field"),
|
|
2354
|
+
categories: z10.preprocess(coerceArray(), z10.array(z10.object({
|
|
2355
|
+
title: z10.string().describe("Short title/label of the category"),
|
|
2356
|
+
definition: z10.string().describe("Full definition/description of the category")
|
|
1941
2357
|
}))).optional().describe("Predefined categories for a CLASSIFICATION field ({title, definition}[]). Required for fieldType=CLASSIFICATION; omitted otherwise.")
|
|
1942
2358
|
};
|
|
1943
2359
|
async function latestTemplateVersionRules(templateId) {
|
|
@@ -1962,25 +2378,25 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
1962
2378
|
|
|
1963
2379
|
` + "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
2380
|
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:
|
|
2381
|
+
templateId: z10.string().min(1).describe("The template ID to publish a version for"),
|
|
2382
|
+
prompt: z10.string().optional().describe("User prompt to copy into workflow config"),
|
|
2383
|
+
schemaId: z10.string().optional().describe("Existing schema ID to reference (mutually exclusive with schemaFields)"),
|
|
2384
|
+
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)"),
|
|
2385
|
+
schemaEntity: z10.string().optional().describe("Entity name for the inline schema"),
|
|
2386
|
+
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.'),
|
|
2387
|
+
notifications: z10.preprocess(coerceArray(), z10.array(z10.object({
|
|
2388
|
+
eventType: z10.string().describe("Notification event type"),
|
|
2389
|
+
eventConfiguration: z10.preprocess(coerceJson(), z10.record(z10.string(), z10.unknown())).optional(),
|
|
2390
|
+
enabled: z10.preprocess(coerceBoolean(), z10.boolean()).optional(),
|
|
2391
|
+
channelIds: z10.preprocess(coerceArray(), z10.array(z10.string().min(1))).optional(),
|
|
2392
|
+
channels: z10.preprocess(coerceArray(), z10.array(z10.object({ channelId: z10.string() }))).optional()
|
|
1977
2393
|
}))).optional().describe("Notification configuration for this version"),
|
|
1978
|
-
frequency:
|
|
1979
|
-
interval:
|
|
1980
|
-
schedules:
|
|
1981
|
-
type:
|
|
1982
|
-
interval:
|
|
1983
|
-
expressions:
|
|
2394
|
+
frequency: z10.preprocess(coerceJson(), z10.object({
|
|
2395
|
+
interval: z10.enum([...UpdateIntervalSchema.options, "REAL_TIME"]).optional().describe("Scheduling interval the template controls for linked workflows. Use CUSTOM with 'schedules' for cron."),
|
|
2396
|
+
schedules: z10.preprocess(coerceArray(), z10.array(z10.object({
|
|
2397
|
+
type: z10.enum(["repeat", "cron"]).describe("'repeat' (interval token) or 'cron' (cron expressions)"),
|
|
2398
|
+
interval: z10.string().optional().describe("Repeat token (e.g. '1h', '1d') when type='repeat'"),
|
|
2399
|
+
expressions: z10.preprocess(coerceArray(true), z10.array(z10.string())).optional().describe("Cron expressions when type='cron' (e.g. '0 8 * * 2')")
|
|
1984
2400
|
}))).optional().describe("Schedule specs. Required when interval='CUSTOM'.")
|
|
1985
2401
|
})).optional().describe("Template-controlled scheduling for linked workflows ({interval, schedules}). Linked workflows inherit and cannot override this when set.")
|
|
1986
2402
|
})),
|
|
@@ -2010,11 +2426,11 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
2010
2426
|
title: "Save Workflow as Template",
|
|
2011
2427
|
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
2428
|
inputSchema: strictSchema({
|
|
2013
|
-
workflowId:
|
|
2014
|
-
name:
|
|
2015
|
-
description:
|
|
2016
|
-
templateId:
|
|
2017
|
-
includeParts:
|
|
2429
|
+
workflowId: z10.string().min(1).describe("Source workflow ID to extract config from"),
|
|
2430
|
+
name: z10.string().optional().describe("Name for the new template (required if templateId is not set)"),
|
|
2431
|
+
description: z10.string().optional().describe("Description for the new template"),
|
|
2432
|
+
templateId: z10.string().optional().describe("Existing template ID to add a new version to (mutually exclusive with name)"),
|
|
2433
|
+
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
2434
|
}),
|
|
2019
2435
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
2020
2436
|
}, withErrorHandling("save_workflow_as_template", async (args) => {
|
|
@@ -2035,20 +2451,20 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
2035
2451
|
title: "List Template Schemas",
|
|
2036
2452
|
description: "List schemas associated with a template.",
|
|
2037
2453
|
inputSchema: strictSchema({
|
|
2038
|
-
templateId:
|
|
2454
|
+
templateId: z10.string().min(1).describe("The template ID")
|
|
2039
2455
|
}),
|
|
2040
2456
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2041
2457
|
}, withErrorHandling("list_template_schemas", async (args) => {
|
|
2042
2458
|
const schemas = await ctx.client.template.listSchemas(args.templateId);
|
|
2043
|
-
return jsonResult({ schemas, count: schemas.length });
|
|
2459
|
+
return jsonResult({ schemas, count: schemas.length, pagination: completeList(schemas.length) });
|
|
2044
2460
|
}));
|
|
2045
2461
|
server.registerTool("link_template_to_workflows", {
|
|
2046
2462
|
title: "Link Template to Workflows",
|
|
2047
2463
|
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
2464
|
inputSchema: strictSchema({
|
|
2049
|
-
templateId:
|
|
2050
|
-
workflowIds:
|
|
2051
|
-
force:
|
|
2465
|
+
templateId: z10.string().min(1).describe("The template ID to link workflows to"),
|
|
2466
|
+
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."),
|
|
2467
|
+
force: z10.preprocess(coerceBoolean(), z10.boolean()).optional().describe("If true, relink workflows that are already linked to another template. Defaults to false.")
|
|
2052
2468
|
}),
|
|
2053
2469
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
2054
2470
|
}, withErrorHandling("link_template_to_workflows", async (args) => {
|
|
@@ -2075,9 +2491,9 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
2075
2491
|
title: "Apply Template Version",
|
|
2076
2492
|
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
2493
|
inputSchema: strictSchema({
|
|
2078
|
-
templateId:
|
|
2079
|
-
targetVersion:
|
|
2080
|
-
workflowIds:
|
|
2494
|
+
templateId: z10.string().min(1).describe("The template ID whose version to apply"),
|
|
2495
|
+
targetVersion: z10.preprocess(coerceNumber(), z10.number()).describe("The template version number to apply to the workflows"),
|
|
2496
|
+
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
2497
|
}),
|
|
2082
2498
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
2083
2499
|
}, withErrorHandling("apply_template_version", async (args) => {
|
|
@@ -2097,19 +2513,19 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
2097
2513
|
title: "List Template Workflows",
|
|
2098
2514
|
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
2515
|
inputSchema: strictSchema({
|
|
2100
|
-
templateId:
|
|
2516
|
+
templateId: z10.string().min(1).describe("The template ID")
|
|
2101
2517
|
}),
|
|
2102
2518
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2103
2519
|
}, withErrorHandling("list_template_workflows", async (args) => {
|
|
2104
2520
|
const workflows = await ctx.client.template.getLinkedWorkflows(args.templateId);
|
|
2105
|
-
return jsonResult({ workflows, count: workflows.length });
|
|
2521
|
+
return jsonResult({ workflows, count: workflows.length, pagination: completeList(workflows.length) });
|
|
2106
2522
|
}));
|
|
2107
2523
|
server.registerTool("unlink_template_from_workflows", {
|
|
2108
2524
|
title: "Unlink Template from Workflows",
|
|
2109
2525
|
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
2526
|
inputSchema: strictSchema({
|
|
2111
|
-
templateId:
|
|
2112
|
-
workflowIds:
|
|
2527
|
+
templateId: z10.string().min(1).describe("The template ID to unlink workflows from"),
|
|
2528
|
+
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
2529
|
}),
|
|
2114
2530
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2115
2531
|
}, withErrorHandling("unlink_template_from_workflows", async (args) => {
|
|
@@ -2126,21 +2542,22 @@ function registerTemplateTools(server, ctx, helpers) {
|
|
|
2126
2542
|
}
|
|
2127
2543
|
var LEGACY_DATA_TYPE_ALIASES;
|
|
2128
2544
|
var init_templates = __esm(() => {
|
|
2545
|
+
init_pagination();
|
|
2129
2546
|
init_schemas();
|
|
2130
2547
|
LEGACY_DATA_TYPE_ALIASES = { TEXT: "STRING", URL: "LINK" };
|
|
2131
2548
|
});
|
|
2132
2549
|
|
|
2133
2550
|
// src/tools/variables.ts
|
|
2134
|
-
import { z as
|
|
2551
|
+
import { z as z11 } from "zod";
|
|
2135
2552
|
function registerVariableTools(server, ctx, helpers) {
|
|
2136
2553
|
const { withErrorHandling } = helpers;
|
|
2137
2554
|
server.registerTool("create_variable", {
|
|
2138
2555
|
title: "Create Variable",
|
|
2139
2556
|
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
2557
|
inputSchema: strictSchema({
|
|
2141
|
-
key:
|
|
2142
|
-
value:
|
|
2143
|
-
dataType:
|
|
2558
|
+
key: z11.string().describe("Variable key (must be unique within scope). Referenced in prompts as @key."),
|
|
2559
|
+
value: z11.string().describe("Variable value"),
|
|
2560
|
+
dataType: z11.enum(["STRING", "SECRET", "OBJECT", "ARRAY"]).optional().describe("Data type of the value (defaults to STRING)")
|
|
2144
2561
|
}),
|
|
2145
2562
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
2146
2563
|
}, withErrorHandling("create_variable", async (args) => {
|
|
@@ -2164,14 +2581,15 @@ function registerVariableTools(server, ctx, helpers) {
|
|
|
2164
2581
|
const variables = await ctx.client.variable.list();
|
|
2165
2582
|
return jsonResult({
|
|
2166
2583
|
variables: variables.map(withVariableDashboardUrl),
|
|
2167
|
-
count: variables.length
|
|
2584
|
+
count: variables.length,
|
|
2585
|
+
pagination: completeList(variables.length)
|
|
2168
2586
|
});
|
|
2169
2587
|
}));
|
|
2170
2588
|
server.registerTool("get_variable", {
|
|
2171
2589
|
title: "Get Variable",
|
|
2172
2590
|
description: "Get a specific variable by its ID.",
|
|
2173
2591
|
inputSchema: strictSchema({
|
|
2174
|
-
variableId:
|
|
2592
|
+
variableId: z11.string().min(1).describe("The variable ID")
|
|
2175
2593
|
}),
|
|
2176
2594
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2177
2595
|
}, withErrorHandling("get_variable", async (args) => {
|
|
@@ -2182,10 +2600,10 @@ function registerVariableTools(server, ctx, helpers) {
|
|
|
2182
2600
|
title: "Update Variable",
|
|
2183
2601
|
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
2602
|
inputSchema: strictSchema({
|
|
2185
|
-
variableId:
|
|
2186
|
-
key:
|
|
2187
|
-
value:
|
|
2188
|
-
dataType:
|
|
2603
|
+
variableId: z11.string().min(1).describe("The variable ID to update"),
|
|
2604
|
+
key: z11.string().optional().describe("New variable key"),
|
|
2605
|
+
value: z11.string().optional().describe("New variable value"),
|
|
2606
|
+
dataType: z11.enum(["STRING", "SECRET", "OBJECT", "ARRAY"]).optional().describe("New data type")
|
|
2189
2607
|
}),
|
|
2190
2608
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
2191
2609
|
}, withErrorHandling("update_variable", async (args) => {
|
|
@@ -2201,8 +2619,8 @@ function registerVariableTools(server, ctx, helpers) {
|
|
|
2201
2619
|
title: "Delete Variable",
|
|
2202
2620
|
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
2621
|
inputSchema: strictSchema({
|
|
2204
|
-
variableId:
|
|
2205
|
-
confirmed:
|
|
2622
|
+
variableId: z11.string().min(1).describe("The variable ID to delete"),
|
|
2623
|
+
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
2624
|
}),
|
|
2207
2625
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
2208
2626
|
}, withErrorHandling("delete_variable", async (args) => {
|
|
@@ -2227,12 +2645,13 @@ function registerVariableTools(server, ctx, helpers) {
|
|
|
2227
2645
|
}));
|
|
2228
2646
|
}
|
|
2229
2647
|
var init_variables = __esm(() => {
|
|
2648
|
+
init_pagination();
|
|
2230
2649
|
init_schemas();
|
|
2231
2650
|
});
|
|
2232
2651
|
|
|
2233
2652
|
// src/tools/workflows.ts
|
|
2234
2653
|
import { WorkflowStatusFilter } from "@kadoa/node-sdk";
|
|
2235
|
-
import { z as
|
|
2654
|
+
import { z as z12 } from "zod";
|
|
2236
2655
|
function isRealTimeInterval(interval) {
|
|
2237
2656
|
return interval?.toUpperCase().replace("-", "_") === "REAL_TIME";
|
|
2238
2657
|
}
|
|
@@ -2289,6 +2708,49 @@ function mapRunStatus(state) {
|
|
|
2289
2708
|
return state ?? "unknown";
|
|
2290
2709
|
}
|
|
2291
2710
|
}
|
|
2711
|
+
function changeResult(change) {
|
|
2712
|
+
const build = (data, differences) => ({
|
|
2713
|
+
id: change.id,
|
|
2714
|
+
workflowId: change.workflowId,
|
|
2715
|
+
url: change.url,
|
|
2716
|
+
summary: change.summary,
|
|
2717
|
+
screenshotUrl: change.screenshotUrl,
|
|
2718
|
+
data,
|
|
2719
|
+
differences,
|
|
2720
|
+
createdAt: change.createdAt
|
|
2721
|
+
});
|
|
2722
|
+
const full = build(change.data, change.differences);
|
|
2723
|
+
if (jsonBytes(full) <= RESULT_BUDGET_BYTES)
|
|
2724
|
+
return full;
|
|
2725
|
+
const rows = change.data ?? [];
|
|
2726
|
+
const exportHint = `For the full current dataset use export_data with workflowId ${change.workflowId}.`;
|
|
2727
|
+
if (jsonBytes(build([], change.differences)) <= RESULT_BUDGET_BYTES - TRUNCATION_RESERVE_BYTES) {
|
|
2728
|
+
const fit = fitItems({ items: rows, build: (kept) => build(kept, change.differences) });
|
|
2729
|
+
if (!fit.truncated)
|
|
2730
|
+
return build(fit.kept, change.differences);
|
|
2731
|
+
return {
|
|
2732
|
+
...build(fit.kept, change.differences),
|
|
2733
|
+
...truncationNotice({
|
|
2734
|
+
kept: fit.kept.length,
|
|
2735
|
+
omitted: fit.omitted,
|
|
2736
|
+
noun: "data rows",
|
|
2737
|
+
hint: `differences is complete. ${exportHint}`
|
|
2738
|
+
})
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2741
|
+
const fit = fitItems({ items: change.differences ?? [], build: (kept) => build([], kept) });
|
|
2742
|
+
if (!fit.truncated)
|
|
2743
|
+
return build([], fit.kept);
|
|
2744
|
+
return {
|
|
2745
|
+
...build([], fit.kept),
|
|
2746
|
+
...truncationNotice({
|
|
2747
|
+
kept: fit.kept.length,
|
|
2748
|
+
omitted: fit.omitted,
|
|
2749
|
+
noun: "differences",
|
|
2750
|
+
hint: `All ${rows.length} data rows omitted. ${exportHint}`
|
|
2751
|
+
})
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2292
2754
|
function jsonEq(a, b) {
|
|
2293
2755
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
2294
2756
|
}
|
|
@@ -2302,12 +2764,31 @@ function diffWorkflowAuditEntry(entry) {
|
|
|
2302
2764
|
if (!jsonEq(prev[key], next[key]))
|
|
2303
2765
|
changed.push(key);
|
|
2304
2766
|
}
|
|
2305
|
-
|
|
2306
|
-
const nextPrompt = next["additionalData"]?.["userPrompt"];
|
|
2307
|
-
if (!jsonEq(prevPrompt, nextPrompt))
|
|
2767
|
+
if (!jsonEq(auditFieldValue(prev, "additionalData.userPrompt"), auditFieldValue(next, "additionalData.userPrompt"))) {
|
|
2308
2768
|
changed.push("additionalData.userPrompt");
|
|
2769
|
+
}
|
|
2309
2770
|
return changed;
|
|
2310
2771
|
}
|
|
2772
|
+
function auditFieldValue(snapshot, field) {
|
|
2773
|
+
if (field === "additionalData.userPrompt") {
|
|
2774
|
+
return snapshot["additionalData"]?.["userPrompt"];
|
|
2775
|
+
}
|
|
2776
|
+
return snapshot[field];
|
|
2777
|
+
}
|
|
2778
|
+
function summarizeWorkflowAuditEntry(entry) {
|
|
2779
|
+
const changedFields = diffWorkflowAuditEntry(entry);
|
|
2780
|
+
const prev = entry.previousValue ?? {};
|
|
2781
|
+
const next = entry.newValue ?? {};
|
|
2782
|
+
const changes = changedFields.map((field) => ({
|
|
2783
|
+
field,
|
|
2784
|
+
before: previewValue(auditFieldValue(prev, field)),
|
|
2785
|
+
after: previewValue(auditFieldValue(next, field))
|
|
2786
|
+
}));
|
|
2787
|
+
return { changedFields, changes };
|
|
2788
|
+
}
|
|
2789
|
+
function historyEmptyHint(includeSnapshots) {
|
|
2790
|
+
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.";
|
|
2791
|
+
}
|
|
2311
2792
|
function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
2312
2793
|
const { withErrorHandling } = helpers;
|
|
2313
2794
|
function resolveUrls(args) {
|
|
@@ -2359,7 +2840,7 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2359
2840
|
const channels = {};
|
|
2360
2841
|
if (n.email) {
|
|
2361
2842
|
if (typeof n.email === "object" && n.email.recipients?.length) {
|
|
2362
|
-
channels.EMAIL = { name: "
|
|
2843
|
+
channels.EMAIL = { name: mcpChannelName("EMAIL", n.email), recipients: n.email.recipients };
|
|
2363
2844
|
} else {
|
|
2364
2845
|
channels.EMAIL = true;
|
|
2365
2846
|
}
|
|
@@ -2368,7 +2849,7 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2368
2849
|
channels.WEBSOCKET = true;
|
|
2369
2850
|
if (n.webhook) {
|
|
2370
2851
|
const webhookChannel = {
|
|
2371
|
-
name: "
|
|
2852
|
+
name: mcpChannelName("WEBHOOK", { webhookUrl: n.webhook.url }),
|
|
2372
2853
|
webhookUrl: n.webhook.url,
|
|
2373
2854
|
httpMethod: n.webhook.httpMethod || "POST"
|
|
2374
2855
|
};
|
|
@@ -2377,7 +2858,7 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2377
2858
|
channels.WEBHOOK = webhookChannel;
|
|
2378
2859
|
}
|
|
2379
2860
|
if (n.slack) {
|
|
2380
|
-
const slackChannel = { name: "
|
|
2861
|
+
const slackChannel = { name: mcpChannelName("SLACK", n.slack) };
|
|
2381
2862
|
if (n.slack.webhookUrl)
|
|
2382
2863
|
slackChannel.webhookUrl = n.slack.webhookUrl;
|
|
2383
2864
|
if (n.slack.slackChannelId)
|
|
@@ -2421,13 +2902,13 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2421
2902
|
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
2903
|
...extractionInputShape,
|
|
2423
2904
|
...urlInputShape,
|
|
2424
|
-
templateId:
|
|
2425
|
-
templateVersion:
|
|
2426
|
-
description:
|
|
2427
|
-
tags:
|
|
2428
|
-
limit:
|
|
2905
|
+
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."),
|
|
2906
|
+
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."),
|
|
2907
|
+
description: z12.string().max(500).optional().describe("Description of what this workflow does (max 500 characters)"),
|
|
2908
|
+
tags: z12.preprocess(coerceArray(true), z12.array(z12.string())).optional().describe("Tags for organizing workflows"),
|
|
2909
|
+
limit: z12.preprocess(coerceNumber(), z12.number()).optional().describe("Maximum number of records to extract per run. Useful for limiting scope or cost control."),
|
|
2429
2910
|
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:
|
|
2911
|
+
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
2912
|
...notificationsInputShape
|
|
2432
2913
|
}),
|
|
2433
2914
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
@@ -2441,13 +2922,25 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2441
2922
|
}
|
|
2442
2923
|
const n = args.notifications;
|
|
2443
2924
|
const hasNotifications = !!(n && (n.email || n.webhook || n.slack || n.websocket));
|
|
2925
|
+
async function createOrUnknown(create) {
|
|
2926
|
+
try {
|
|
2927
|
+
return await create();
|
|
2928
|
+
} catch (error) {
|
|
2929
|
+
if (!isAmbiguousFailure(error))
|
|
2930
|
+
throw error;
|
|
2931
|
+
console.error("[Tool Error] create_workflow:", safeErrorSummary(error));
|
|
2932
|
+
return {
|
|
2933
|
+
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.`)
|
|
2934
|
+
};
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2444
2937
|
let workflowId;
|
|
2445
2938
|
const warnings = [];
|
|
2446
2939
|
if (args.templateId) {
|
|
2447
2940
|
if (args.entity || args.schema) {
|
|
2448
2941
|
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
2942
|
}
|
|
2450
|
-
const
|
|
2943
|
+
const created = await createOrUnknown(() => ctx.client.workflow.create({
|
|
2451
2944
|
urls,
|
|
2452
2945
|
name: args.name || "Untitled Workflow",
|
|
2453
2946
|
description: args.description,
|
|
@@ -2458,8 +2951,10 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2458
2951
|
templateId: args.templateId,
|
|
2459
2952
|
templateVersion: args.templateVersion,
|
|
2460
2953
|
userPrompt: args.prompt
|
|
2461
|
-
});
|
|
2462
|
-
|
|
2954
|
+
}));
|
|
2955
|
+
if ("outcomeUnknown" in created)
|
|
2956
|
+
return created.outcomeUnknown;
|
|
2957
|
+
workflowId = created.id;
|
|
2463
2958
|
} else {
|
|
2464
2959
|
if (!args.prompt) {
|
|
2465
2960
|
return errorResult("'prompt' is required unless 'templateId' is provided.");
|
|
@@ -2473,7 +2968,9 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2473
2968
|
description: args.description,
|
|
2474
2969
|
schedules: args.schedules
|
|
2475
2970
|
});
|
|
2476
|
-
const workflow = await builder.create();
|
|
2971
|
+
const workflow = await createOrUnknown(() => builder.create());
|
|
2972
|
+
if ("outcomeUnknown" in workflow)
|
|
2973
|
+
return workflow.outcomeUnknown;
|
|
2477
2974
|
workflowId = workflow.workflowId;
|
|
2478
2975
|
const needsUpdate = args.limit !== undefined || args.tags && args.tags.length > 0;
|
|
2479
2976
|
if (needsUpdate) {
|
|
@@ -2546,19 +3043,10 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2546
3043
|
} else {
|
|
2547
3044
|
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
3045
|
}
|
|
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
|
-
};
|
|
3046
|
+
return errorResult(text, {
|
|
3047
|
+
notificationChannelIds,
|
|
3048
|
+
...workflowId ? { workflowId, dashboardUrl: workflowDashboardUrl(workflowId) } : {}
|
|
3049
|
+
});
|
|
2562
3050
|
}
|
|
2563
3051
|
server.registerTool("create_realtime_monitor", {
|
|
2564
3052
|
title: "Create Realtime Monitor",
|
|
@@ -2569,8 +3057,8 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2569
3057
|
prompt: extractionPromptInput.describe("Optional natural language instructions describing what to monitor and which changes matter. If omitted, the Assistant determines what to watch."),
|
|
2570
3058
|
...extractionInputShape,
|
|
2571
3059
|
...urlInputShape,
|
|
2572
|
-
description:
|
|
2573
|
-
tags:
|
|
3060
|
+
description: z12.string().max(500).optional().describe("Description of what this monitor watches (max 500 characters)"),
|
|
3061
|
+
tags: z12.preprocess(coerceArray(true), z12.array(z12.string())).optional().describe("Tags for organizing monitors"),
|
|
2574
3062
|
...requiredNotificationsInputShape
|
|
2575
3063
|
}),
|
|
2576
3064
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
@@ -2621,20 +3109,27 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2621
3109
|
title: "List Workflows",
|
|
2622
3110
|
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
3111
|
inputSchema: strictSchema({
|
|
2624
|
-
limit:
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
3112
|
+
limit: limitInput({
|
|
3113
|
+
noun: "Workflows",
|
|
3114
|
+
defaultLimit: LIST_WORKFLOWS_DEFAULT_LIMIT,
|
|
3115
|
+
max: LIST_WORKFLOWS_MAX_LIMIT
|
|
3116
|
+
}),
|
|
3117
|
+
skip: offsetInput("skip", "workflows"),
|
|
3118
|
+
state: z12.enum(["ACTIVE", "FAILED", "PAUSED", "PREVIEW"]).optional().describe("Filter by state (ACTIVE, FAILED, PAUSED, PREVIEW)"),
|
|
3119
|
+
search: z12.string().optional().describe("Free-text filter matching workflow name, URL, or workflow ID"),
|
|
3120
|
+
tags: z12.array(z12.string()).optional().describe("Filter to workflows having any of these tags"),
|
|
3121
|
+
monitoring: z12.preprocess(coerceBoolean(), z12.boolean()).optional().describe("Filter by monitoring/real-time status"),
|
|
3122
|
+
updateInterval: z12.enum(["HOURLY", "DAILY", "WEEKLY", "MONTHLY", "REAL_TIME"]).optional().describe("Filter by scheduled update frequency or realtime workflows"),
|
|
3123
|
+
templateId: z12.string().optional().describe("Filter to workflows instantiated from this template ID"),
|
|
3124
|
+
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
3125
|
}),
|
|
3126
|
+
outputSchema: ListWorkflowsOutput,
|
|
2634
3127
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2635
3128
|
}, withErrorHandling("list_workflows", async (args) => {
|
|
2636
|
-
const
|
|
2637
|
-
|
|
3129
|
+
const limit = args.limit ?? LIST_WORKFLOWS_DEFAULT_LIMIT;
|
|
3130
|
+
const skip = args.skip ?? 0;
|
|
3131
|
+
const rows = await ctx.client.workflow.list({
|
|
3132
|
+
limit: limit + 1,
|
|
2638
3133
|
state: args.state,
|
|
2639
3134
|
...args.skip !== undefined && { skip: args.skip },
|
|
2640
3135
|
...args.search !== undefined && { search: args.search },
|
|
@@ -2644,6 +3139,7 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2644
3139
|
...args.templateId !== undefined && { templateId: args.templateId },
|
|
2645
3140
|
...args.statusFilters !== undefined && { statusFilters: args.statusFilters }
|
|
2646
3141
|
});
|
|
3142
|
+
const workflows = rows.slice(0, limit);
|
|
2647
3143
|
return jsonResult({
|
|
2648
3144
|
workflows: workflows.map((w) => ({
|
|
2649
3145
|
id: w.id,
|
|
@@ -2680,15 +3176,23 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2680
3176
|
},
|
|
2681
3177
|
awaitingUserInput: w.awaitingUserInput ?? null
|
|
2682
3178
|
})),
|
|
2683
|
-
|
|
3179
|
+
pagination: offsetPage({
|
|
3180
|
+
args,
|
|
3181
|
+
param: "skip",
|
|
3182
|
+
offset: skip,
|
|
3183
|
+
limit,
|
|
3184
|
+
returned: workflows.length,
|
|
3185
|
+
hasMore: rows.length > limit
|
|
3186
|
+
})
|
|
2684
3187
|
});
|
|
2685
3188
|
}));
|
|
2686
3189
|
server.registerTool("get_workflow", {
|
|
2687
3190
|
title: "Get Workflow",
|
|
2688
3191
|
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
3192
|
inputSchema: strictSchema({
|
|
2690
|
-
workflowId:
|
|
3193
|
+
workflowId: z12.string().min(1).describe("The workflow ID")
|
|
2691
3194
|
}),
|
|
3195
|
+
outputSchema: GetWorkflowOutput,
|
|
2692
3196
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2693
3197
|
}, withErrorHandling("get_workflow", async (args) => {
|
|
2694
3198
|
const workflow = await ctx.client.workflow.get(args.workflowId);
|
|
@@ -2705,9 +3209,9 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2705
3209
|
const hasTemplate = workflow.templateId != null || workflow.templateVersion != null;
|
|
2706
3210
|
const isRealtime = workflow.isRealTime === true || isRealTimeInterval(workflow.updateInterval);
|
|
2707
3211
|
return jsonResult({
|
|
2708
|
-
id: workflow.id,
|
|
3212
|
+
id: workflow.id ?? args.workflowId,
|
|
2709
3213
|
name: workflow.name,
|
|
2710
|
-
dashboardUrl: workflowDashboardUrl(workflow.id),
|
|
3214
|
+
dashboardUrl: workflowDashboardUrl(workflow.id ?? args.workflowId),
|
|
2711
3215
|
description: workflow.description,
|
|
2712
3216
|
status: deriveStatusLabel(workflow),
|
|
2713
3217
|
state: workflow.state ?? null,
|
|
@@ -2775,9 +3279,9 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2775
3279
|
title: "Request Workflow Update",
|
|
2776
3280
|
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
3281
|
inputSchema: strictSchema({
|
|
2778
|
-
workflowId:
|
|
2779
|
-
instructions:
|
|
2780
|
-
threadId:
|
|
3282
|
+
workflowId: z12.string().min(1).describe("The existing workflow ID; it will not change"),
|
|
3283
|
+
instructions: z12.string().min(1).describe("Natural-language instructions for the workflow Assistant"),
|
|
3284
|
+
threadId: z12.string().optional().describe("Optional Assistant thread ID to continue")
|
|
2781
3285
|
}),
|
|
2782
3286
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
|
|
2783
3287
|
}, withErrorHandling("request_workflow_update", async (args) => {
|
|
@@ -2805,69 +3309,102 @@ function registerWorkflowHistoryTools(server, ctx, helpers) {
|
|
|
2805
3309
|
const { withErrorHandling } = helpers;
|
|
2806
3310
|
server.registerTool("get_workflow_history", {
|
|
2807
3311
|
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),
|
|
3312
|
+
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
3313
|
inputSchema: strictSchema({
|
|
2810
|
-
workflowId:
|
|
2811
|
-
page:
|
|
2812
|
-
limit:
|
|
3314
|
+
workflowId: z12.string().min(1).describe("The workflow ID"),
|
|
3315
|
+
page: pageInput(),
|
|
3316
|
+
limit: limitInput({
|
|
3317
|
+
noun: "Entries",
|
|
3318
|
+
defaultLimit: WORKFLOW_HISTORY_DEFAULT_LIMIT,
|
|
3319
|
+
max: WORKFLOW_HISTORY_MAX_LIMIT
|
|
3320
|
+
}),
|
|
3321
|
+
includeSnapshots: z12.preprocess(coerceBoolean(), z12.boolean()).optional().describe("Include full previousValue/newValue workflow snapshots per entry (default false; large)")
|
|
2813
3322
|
}),
|
|
2814
3323
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2815
3324
|
}, 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({
|
|
3325
|
+
const page = args.page ?? 1;
|
|
3326
|
+
const limit = args.limit ?? WORKFLOW_HISTORY_DEFAULT_LIMIT;
|
|
3327
|
+
const response = await ctx.client.workflow.getAuditLog(args.workflowId, { page, limit });
|
|
3328
|
+
const includeSnapshots = args.includeSnapshots === true;
|
|
3329
|
+
const entries = (response.logEntries ?? []).map((e) => {
|
|
3330
|
+
const summary = summarizeWorkflowAuditEntry({
|
|
2829
3331
|
previousValue: e.previousValue,
|
|
2830
3332
|
newValue: e.newValue
|
|
2831
|
-
})
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
3333
|
+
});
|
|
3334
|
+
return {
|
|
3335
|
+
id: e.id,
|
|
3336
|
+
operationType: e.operationType,
|
|
3337
|
+
createdAt: e.createdAt,
|
|
3338
|
+
userEmail: e.userEmail,
|
|
3339
|
+
userId: e.userId,
|
|
3340
|
+
requestSource: e.requestSource,
|
|
3341
|
+
authMethod: e.authMethod,
|
|
3342
|
+
changedFields: summary.changedFields,
|
|
3343
|
+
changes: summary.changes,
|
|
3344
|
+
...includeSnapshots && { previousValue: e.previousValue ?? null, newValue: e.newValue ?? null }
|
|
3345
|
+
};
|
|
3346
|
+
});
|
|
3347
|
+
const totalCount = response.pagination?.totalCount ?? response.logEntriesCount;
|
|
3348
|
+
const totalPages = response.pagination?.totalPages;
|
|
3349
|
+
const result = budgetNumberedPage({
|
|
3350
|
+
items: entries,
|
|
3351
|
+
pagination: numberedPage({ args, page, limit, returned: entries.length, totalCount, totalPages }),
|
|
3352
|
+
args,
|
|
3353
|
+
page,
|
|
3354
|
+
limit,
|
|
3355
|
+
noun: "entries",
|
|
3356
|
+
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.",
|
|
3357
|
+
emptyHint: historyEmptyHint(includeSnapshots),
|
|
3358
|
+
build: (kept, meta) => ({
|
|
3359
|
+
workflowId: response.id,
|
|
3360
|
+
...totalCount !== undefined && { totalCount },
|
|
3361
|
+
page,
|
|
3362
|
+
...totalPages !== undefined && kept.length === entries.length && { totalPages },
|
|
3363
|
+
entries: kept,
|
|
3364
|
+
pagination: meta
|
|
3365
|
+
})
|
|
2841
3366
|
});
|
|
3367
|
+
return jsonResult(result);
|
|
2842
3368
|
}));
|
|
2843
3369
|
server.registerTool("list_workflow_runs", {
|
|
2844
3370
|
title: "List Workflow Runs",
|
|
2845
3371
|
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
3372
|
inputSchema: strictSchema({
|
|
2847
|
-
workflowId:
|
|
2848
|
-
status:
|
|
2849
|
-
page:
|
|
2850
|
-
limit:
|
|
3373
|
+
workflowId: z12.string().min(1).describe("The workflow ID"),
|
|
3374
|
+
status: z12.enum(["success", "failed", "in_progress"]).optional().describe("Filter runs by outcome"),
|
|
3375
|
+
page: pageInput(),
|
|
3376
|
+
limit: limitInput({ noun: "Runs", defaultLimit: WORKFLOW_RUNS_DEFAULT_LIMIT, max: WORKFLOW_RUNS_MAX_LIMIT })
|
|
2851
3377
|
}),
|
|
2852
3378
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2853
3379
|
}, withErrorHandling("list_workflow_runs", async (args) => {
|
|
3380
|
+
const page = args.page ?? 1;
|
|
3381
|
+
const limit = args.limit ?? WORKFLOW_RUNS_DEFAULT_LIMIT;
|
|
2854
3382
|
const response = await ctx.client.workflow.listWorkflowRuns(args.workflowId, {
|
|
2855
|
-
page
|
|
2856
|
-
limit
|
|
3383
|
+
page,
|
|
3384
|
+
limit,
|
|
2857
3385
|
status: args.status
|
|
2858
3386
|
});
|
|
3387
|
+
const runs = (response.workflowRuns ?? []).map((r) => ({
|
|
3388
|
+
id: r.id,
|
|
3389
|
+
status: mapRunStatus(r.state),
|
|
3390
|
+
startedAt: r.startedAt,
|
|
3391
|
+
finishedAt: r.finishedAt,
|
|
3392
|
+
records: r.records,
|
|
3393
|
+
errors: r.errors
|
|
3394
|
+
}));
|
|
3395
|
+
const totalCount = response.totalCount ?? response.pagination?.totalCount;
|
|
2859
3396
|
return jsonResult({
|
|
2860
3397
|
workflowId: response.workflowId,
|
|
2861
|
-
runs
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
3398
|
+
runs,
|
|
3399
|
+
...totalCount !== undefined && { totalCount },
|
|
3400
|
+
pagination: numberedPage({
|
|
3401
|
+
args,
|
|
3402
|
+
page,
|
|
3403
|
+
limit,
|
|
3404
|
+
returned: runs.length,
|
|
3405
|
+
totalCount,
|
|
3406
|
+
totalPages: response.pagination?.totalPages
|
|
3407
|
+
})
|
|
2871
3408
|
});
|
|
2872
3409
|
}));
|
|
2873
3410
|
}
|
|
@@ -2877,8 +3414,8 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
2877
3414
|
title: "Run Workflow",
|
|
2878
3415
|
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
3416
|
inputSchema: strictSchema({
|
|
2880
|
-
workflowId:
|
|
2881
|
-
limit:
|
|
3417
|
+
workflowId: z12.string().min(1).describe("The workflow ID to run"),
|
|
3418
|
+
limit: z12.preprocess(coerceNumber(), z12.number()).optional().describe("Maximum number of records to extract (default: 1000)")
|
|
2882
3419
|
}),
|
|
2883
3420
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
2884
3421
|
}, withErrorHandling("run_workflow", async (args) => {
|
|
@@ -2901,49 +3438,72 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
2901
3438
|
title: "Fetch Workflow Data",
|
|
2902
3439
|
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
3440
|
inputSchema: strictSchema({
|
|
2904
|
-
workflowId:
|
|
2905
|
-
limit:
|
|
2906
|
-
|
|
3441
|
+
workflowId: z12.string().min(1).describe("The workflow ID"),
|
|
3442
|
+
limit: limitInput({
|
|
3443
|
+
noun: "Rows",
|
|
3444
|
+
defaultLimit: FETCH_DATA_DEFAULT_LIMIT,
|
|
3445
|
+
max: FETCH_DATA_MAX_LIMIT,
|
|
3446
|
+
overMaxHint: "Use export_data for full datasets."
|
|
3447
|
+
}),
|
|
3448
|
+
page: pageInput(),
|
|
2907
3449
|
filters: dataFiltersInput,
|
|
2908
|
-
sortBy:
|
|
2909
|
-
order:
|
|
3450
|
+
sortBy: z12.string().optional().describe("Field name to sort by"),
|
|
3451
|
+
order: z12.enum(["asc", "desc"]).optional().describe("Sort order: asc or desc")
|
|
2910
3452
|
}),
|
|
3453
|
+
outputSchema: FetchDataOutput,
|
|
2911
3454
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2912
3455
|
}, 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
|
-
}
|
|
3456
|
+
const limit = args.limit ?? FETCH_DATA_DEFAULT_LIMIT;
|
|
3457
|
+
const page = args.page ?? 1;
|
|
2917
3458
|
const data = await ctx.client.extraction.fetchData({
|
|
2918
3459
|
workflowId: args.workflowId,
|
|
2919
|
-
limit
|
|
2920
|
-
page
|
|
3460
|
+
limit,
|
|
3461
|
+
page,
|
|
2921
3462
|
filters: args.filters === undefined ? undefined : JSON.stringify(args.filters),
|
|
2922
3463
|
sortBy: args.sortBy,
|
|
2923
3464
|
order: args.order
|
|
2924
3465
|
});
|
|
2925
|
-
const totalCount = data.pagination?.totalCount
|
|
2926
|
-
const
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
}
|
|
3466
|
+
const totalCount = data.pagination?.totalCount;
|
|
3467
|
+
const pagination = numberedPage({
|
|
3468
|
+
args,
|
|
3469
|
+
page,
|
|
3470
|
+
limit,
|
|
3471
|
+
returned: data.data.length,
|
|
3472
|
+
totalCount,
|
|
3473
|
+
totalPages: data.pagination?.totalPages
|
|
3474
|
+
});
|
|
3475
|
+
const result = budgetNumberedPage({
|
|
3476
|
+
items: data.data,
|
|
3477
|
+
pagination,
|
|
3478
|
+
args,
|
|
3479
|
+
page,
|
|
3480
|
+
limit,
|
|
3481
|
+
noun: "rows",
|
|
3482
|
+
resumeHint: "Call fetch_data again with pagination.next for the following rows, or use export_data for the full dataset.",
|
|
3483
|
+
emptyHint: "Rows are too large to return inline. Use export_data for this data.",
|
|
3484
|
+
alignmentImpossibleHint: "Page offset too deep to resume inline; use export_data instead.",
|
|
3485
|
+
build: (rows, meta) => ({
|
|
3486
|
+
data: rows,
|
|
3487
|
+
pagination: meta,
|
|
3488
|
+
...totalCount !== undefined && { totalCount },
|
|
3489
|
+
...totalCount !== undefined && rows.length < totalCount && {
|
|
3490
|
+
hint: `Showing ${rows.length} of ${totalCount} rows. Use export_data for the full dataset.`
|
|
3491
|
+
}
|
|
3492
|
+
})
|
|
3493
|
+
});
|
|
2934
3494
|
return jsonResult(result);
|
|
2935
3495
|
}));
|
|
2936
3496
|
server.registerTool("export_data", {
|
|
2937
3497
|
title: "Export Workflow Data",
|
|
2938
3498
|
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
3499
|
inputSchema: strictSchema({
|
|
2940
|
-
workflowId:
|
|
2941
|
-
format:
|
|
2942
|
-
runId:
|
|
3500
|
+
workflowId: z12.string().min(1).describe("The workflow ID"),
|
|
3501
|
+
format: z12.enum(["csv", "json"]).optional().describe("Export format. Default 'csv'."),
|
|
3502
|
+
runId: z12.string().optional().describe("Specific run ID to export. Defaults to the latest completed run."),
|
|
2943
3503
|
filters: dataFiltersInput,
|
|
2944
|
-
sortBy:
|
|
2945
|
-
order:
|
|
2946
|
-
rowIds:
|
|
3504
|
+
sortBy: z12.string().optional().describe("Field name to sort by"),
|
|
3505
|
+
order: z12.enum(["asc", "desc"]).optional().describe("Sort order: asc or desc"),
|
|
3506
|
+
rowIds: z12.preprocess(coerceList(), z12.array(z12.string().trim().min(1)).max(1000)).optional().describe("Row ids to include (max 1000)")
|
|
2947
3507
|
}),
|
|
2948
3508
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2949
3509
|
}, withErrorHandling("export_data", async (args) => {
|
|
@@ -2956,7 +3516,7 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
2956
3516
|
order: args.order,
|
|
2957
3517
|
rowIds: joinList(args.rowIds)
|
|
2958
3518
|
});
|
|
2959
|
-
return jsonResult({
|
|
3519
|
+
return withExtraContent(jsonResult({
|
|
2960
3520
|
url: result.url,
|
|
2961
3521
|
rowCount: result.rowCount,
|
|
2962
3522
|
format: result.format,
|
|
@@ -2964,26 +3524,34 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
2964
3524
|
workflowId: result.workflowId,
|
|
2965
3525
|
runId: result.runId,
|
|
2966
3526
|
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}.`
|
|
3527
|
+
}), {
|
|
3528
|
+
type: "resource_link",
|
|
3529
|
+
uri: result.url,
|
|
3530
|
+
name: `${result.workflowId}-${result.runId}.${result.format}`,
|
|
3531
|
+
title: `Workflow export (${result.rowCount} rows)`,
|
|
3532
|
+
mimeType: result.format === "json" ? "application/json" : "text/csv",
|
|
3533
|
+
description: `Signed download URL, no auth needed. Expires ${result.expiresAt}.`
|
|
2967
3534
|
});
|
|
2968
3535
|
}));
|
|
2969
3536
|
server.registerTool("list_changes", {
|
|
2970
3537
|
title: "List Detected Changes",
|
|
2971
3538
|
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
3539
|
inputSchema: strictSchema({
|
|
2973
|
-
workflowIds:
|
|
2974
|
-
startDate:
|
|
2975
|
-
endDate:
|
|
2976
|
-
skip:
|
|
2977
|
-
limit:
|
|
3540
|
+
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."),
|
|
3541
|
+
startDate: z12.string().optional().describe("Start date filter (ISO format, e.g. 2025-01-01)"),
|
|
3542
|
+
endDate: z12.string().optional().describe("End date filter (ISO format, e.g. 2025-12-31)"),
|
|
3543
|
+
skip: offsetInput("skip", "changes"),
|
|
3544
|
+
limit: limitInput({ noun: "Changes", defaultLimit: LIST_CHANGES_DEFAULT_LIMIT, max: LIST_CHANGES_MAX_LIMIT })
|
|
2978
3545
|
}),
|
|
2979
3546
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2980
3547
|
}, withErrorHandling("list_changes", async (args) => {
|
|
3548
|
+
const limit = args.limit ?? LIST_CHANGES_DEFAULT_LIMIT;
|
|
2981
3549
|
const result = await ctx.client.changes.list({
|
|
2982
3550
|
workflowIds: args.workflowIds?.join(","),
|
|
2983
3551
|
startDate: args.startDate,
|
|
2984
3552
|
endDate: args.endDate,
|
|
2985
3553
|
skip: args.skip,
|
|
2986
|
-
limit
|
|
3554
|
+
limit
|
|
2987
3555
|
});
|
|
2988
3556
|
return jsonResult({
|
|
2989
3557
|
changes: result.changes.map((c) => ({
|
|
@@ -2994,36 +3562,34 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
2994
3562
|
differences: c.differences,
|
|
2995
3563
|
createdAt: c.createdAt
|
|
2996
3564
|
})),
|
|
2997
|
-
pagination:
|
|
3565
|
+
pagination: offsetPage({
|
|
3566
|
+
args,
|
|
3567
|
+
param: "skip",
|
|
3568
|
+
offset: args.skip ?? 0,
|
|
3569
|
+
limit,
|
|
3570
|
+
returned: result.changes.length,
|
|
3571
|
+
totalCount: result.pagination?.totalCount
|
|
3572
|
+
}),
|
|
2998
3573
|
changesCount: result.changesCount
|
|
2999
3574
|
});
|
|
3000
3575
|
}));
|
|
3001
3576
|
server.registerTool("get_change", {
|
|
3002
3577
|
title: "Get Detected Change",
|
|
3003
|
-
description: "Get detailed information about a specific detected change, including the
|
|
3578
|
+
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
3579
|
inputSchema: strictSchema({
|
|
3005
|
-
changeId:
|
|
3580
|
+
changeId: z12.string().min(1).describe("The change ID")
|
|
3006
3581
|
}),
|
|
3007
3582
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
3008
3583
|
}, withErrorHandling("get_change", async (args) => {
|
|
3009
3584
|
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
|
-
});
|
|
3585
|
+
return jsonResult(changeResult(change));
|
|
3020
3586
|
}));
|
|
3021
3587
|
server.registerTool("delete_workflow", {
|
|
3022
3588
|
title: "Delete Workflow",
|
|
3023
3589
|
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
3590
|
inputSchema: strictSchema({
|
|
3025
|
-
workflowId:
|
|
3026
|
-
confirmed:
|
|
3591
|
+
workflowId: z12.string().min(1).describe("The workflow ID to delete"),
|
|
3592
|
+
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
3593
|
}),
|
|
3028
3594
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
3029
3595
|
}, withErrorHandling("delete_workflow", async (args) => {
|
|
@@ -3053,7 +3619,7 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
3053
3619
|
title: "Approve Workflow",
|
|
3054
3620
|
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
3621
|
inputSchema: strictSchema({
|
|
3056
|
-
workflowId:
|
|
3622
|
+
workflowId: z12.string().min(1).describe("The workflow ID to approve/activate")
|
|
3057
3623
|
}),
|
|
3058
3624
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
3059
3625
|
}, withErrorHandling("approve_workflow", async (args) => {
|
|
@@ -3068,7 +3634,7 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
3068
3634
|
title: "Pause Workflow",
|
|
3069
3635
|
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
3636
|
inputSchema: strictSchema({
|
|
3071
|
-
workflowId:
|
|
3637
|
+
workflowId: z12.string().min(1).describe("The workflow ID to pause")
|
|
3072
3638
|
}),
|
|
3073
3639
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
3074
3640
|
}, withErrorHandling("pause_workflow", async (args) => {
|
|
@@ -3087,20 +3653,20 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
3087
3653
|
|
|
3088
3654
|
` + "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
3655
|
inputSchema: strictSchema({
|
|
3090
|
-
workflowId:
|
|
3091
|
-
name:
|
|
3092
|
-
urls:
|
|
3093
|
-
entity:
|
|
3094
|
-
schema:
|
|
3095
|
-
description:
|
|
3096
|
-
tags:
|
|
3097
|
-
userPrompt:
|
|
3656
|
+
workflowId: z12.string().min(1).describe("The workflow ID to update"),
|
|
3657
|
+
name: z12.string().optional().describe("New name for the workflow"),
|
|
3658
|
+
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."),
|
|
3659
|
+
entity: z12.string().optional().describe("Entity name for extraction (e.g., 'Product', 'Job Posting')"),
|
|
3660
|
+
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."),
|
|
3661
|
+
description: z12.string().max(500).optional().describe("Workflow description (max 500 characters)"),
|
|
3662
|
+
tags: z12.preprocess(coerceArray(true), z12.array(z12.string())).optional().describe("Tags for organizing workflows"),
|
|
3663
|
+
userPrompt: z12.string().optional().describe("Navigation prompt for agentic-navigation mode (10-5000 characters)"),
|
|
3098
3664
|
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:
|
|
3665
|
+
schedules: z12.preprocess(coerceArray(true), z12.array(z12.string())).optional().describe("Cron expressions for CUSTOM update interval"),
|
|
3666
|
+
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."),
|
|
3667
|
+
location: z12.preprocess(coerceJson(), LocationSchema).optional().describe("Scraping location: {type:'auto'} or {type:'manual', isoCode:'US'}"),
|
|
3668
|
+
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."),
|
|
3669
|
+
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
3670
|
}),
|
|
3105
3671
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
3106
3672
|
}, withErrorHandling("update_workflow", async (args) => {
|
|
@@ -3136,9 +3702,13 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
3136
3702
|
});
|
|
3137
3703
|
}));
|
|
3138
3704
|
}
|
|
3139
|
-
var WORKFLOW_AUDIT_WATCHED_KEYS;
|
|
3705
|
+
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
3706
|
var init_workflows = __esm(() => {
|
|
3707
|
+
init_notification_channels();
|
|
3708
|
+
init_budget();
|
|
3141
3709
|
init_errors();
|
|
3710
|
+
init_output_schemas();
|
|
3711
|
+
init_pagination();
|
|
3142
3712
|
init_schemas();
|
|
3143
3713
|
WORKFLOW_AUDIT_WATCHED_KEYS = [
|
|
3144
3714
|
"name",
|
|
@@ -3863,6 +4433,8 @@ async function createServer3(auth, options) {
|
|
|
3863
4433
|
"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.",
|
|
3864
4434
|
"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
4435
|
"Use list_changes and get_change to retrieve detected diffs from realtime monitoring workflows.",
|
|
4436
|
+
"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.",
|
|
4437
|
+
"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
4438
|
"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
4439
|
"",
|
|
3868
4440
|
"Schema tips: Use descriptive field names and examples. Group related data under one entity.",
|
|
@@ -4322,6 +4894,8 @@ async function createServer(auth, options) {
|
|
|
4322
4894
|
"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.",
|
|
4323
4895
|
"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
4896
|
"Use list_changes and get_change to retrieve detected diffs from realtime monitoring workflows.",
|
|
4897
|
+
"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.",
|
|
4898
|
+
"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
4899
|
"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
4900
|
"",
|
|
4327
4901
|
"Schema tips: Use descriptive field names and examples. Group related data under one entity.",
|