@kadoa/mcp 0.7.0-rc.2 → 0.7.1
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/dist/index.js +218 -102
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ var package_default;
|
|
|
17
17
|
var init_package = __esm(() => {
|
|
18
18
|
package_default = {
|
|
19
19
|
name: "@kadoa/mcp",
|
|
20
|
-
version: "0.7.
|
|
20
|
+
version: "0.7.1",
|
|
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",
|
|
@@ -44,7 +44,7 @@ var init_package = __esm(() => {
|
|
|
44
44
|
prepublishOnly: "bun run check-types && bun run test:unit && bun run build"
|
|
45
45
|
},
|
|
46
46
|
dependencies: {
|
|
47
|
-
"@kadoa/node-sdk": "^0.
|
|
47
|
+
"@kadoa/node-sdk": "^0.48.0",
|
|
48
48
|
"@modelcontextprotocol/client": "2.1.0",
|
|
49
49
|
"@modelcontextprotocol/express": "2.0.1",
|
|
50
50
|
"@modelcontextprotocol/node": "2.1.0",
|
|
@@ -104,6 +104,11 @@ function createKadoaClient(auth) {
|
|
|
104
104
|
var MCP_SCRAPE_REQUEST_TIMEOUT_MS = 310000;
|
|
105
105
|
var init_client = () => {};
|
|
106
106
|
|
|
107
|
+
// src/default-product.ts
|
|
108
|
+
function parseDefaultProductType(value) {
|
|
109
|
+
return value === "realtime" || value === "workflow" ? value : undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
107
112
|
// src/error-summary.ts
|
|
108
113
|
function redactCredentialText(value) {
|
|
109
114
|
return value.replace(/Bearer\s+[^\s,;]+/gi, "Bearer [redacted]").replace(/\b(?:kcs_|tk-)[A-Za-z0-9._~-]+\b/g, "[redacted]").replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, "[redacted]");
|
|
@@ -209,7 +214,7 @@ function withVariableDashboardUrl(variable) {
|
|
|
209
214
|
|
|
210
215
|
// src/tools/shared/output-schemas.ts
|
|
211
216
|
import { z } from "zod";
|
|
212
|
-
var apiField, nullableString, PaginationOutput, WorkflowRowOutput, ListWorkflowsOutput, GetWorkflowOutput, FetchDataOutput, ASSISTANT_ACTIVITIES, WorkflowAssistantOutput, AssistantTimelineOutput, AssistantAnswerOutput, AssistantControlOutput, WorkflowStrategyOutput;
|
|
217
|
+
var apiField, nullableString, PaginationOutput, workflowStatusFields, WorkflowRowOutput, ListWorkflowsOutput, GetWorkflowOutput, FetchDataOutput, ASSISTANT_ACTIVITIES, WorkflowAssistantOutput, AssistantTimelineOutput, AssistantAnswerOutput, AssistantControlOutput, WorkflowStrategyOutput;
|
|
213
218
|
var init_output_schemas = __esm(() => {
|
|
214
219
|
apiField = z.unknown().optional();
|
|
215
220
|
nullableString = z.string().nullable();
|
|
@@ -224,11 +229,20 @@ var init_output_schemas = __esm(() => {
|
|
|
224
229
|
nextCursor: nullableString.optional(),
|
|
225
230
|
next: z.record(z.string(), z.unknown()).optional()
|
|
226
231
|
}).loose();
|
|
232
|
+
workflowStatusFields = {
|
|
233
|
+
status: z.string(),
|
|
234
|
+
badges: z.array(z.object({ kind: z.string(), status: z.string(), label: z.string() }).loose()),
|
|
235
|
+
group: nullableString,
|
|
236
|
+
reviewReason: z.object({ code: z.string(), explanation: z.string() }).loose().nullable(),
|
|
237
|
+
execution: apiField,
|
|
238
|
+
assistantState: apiField,
|
|
239
|
+
workspaceActivity: apiField
|
|
240
|
+
};
|
|
227
241
|
WorkflowRowOutput = z.object({
|
|
228
242
|
id: apiField,
|
|
229
243
|
name: apiField,
|
|
230
244
|
dashboardUrl: z.string(),
|
|
231
|
-
|
|
245
|
+
...workflowStatusFields,
|
|
232
246
|
state: nullableString,
|
|
233
247
|
displayState: nullableString,
|
|
234
248
|
runState: nullableString,
|
|
@@ -239,7 +253,6 @@ var init_output_schemas = __esm(() => {
|
|
|
239
253
|
createdAt: apiField,
|
|
240
254
|
assistant: z.object({
|
|
241
255
|
eligible: z.boolean(),
|
|
242
|
-
dataStale: z.boolean(),
|
|
243
256
|
session: z.object({ id: z.string(), threadId: apiField, status: apiField }).loose().nullable()
|
|
244
257
|
}).loose(),
|
|
245
258
|
template: z.object({ id: z.string(), pinnedVersion: apiField, latestVersion: apiField, isOutdated: z.boolean() }).loose().nullable(),
|
|
@@ -252,7 +265,7 @@ var init_output_schemas = __esm(() => {
|
|
|
252
265
|
name: apiField,
|
|
253
266
|
dashboardUrl: z.string(),
|
|
254
267
|
description: apiField,
|
|
255
|
-
|
|
268
|
+
...workflowStatusFields,
|
|
256
269
|
state: nullableString,
|
|
257
270
|
displayState: nullableString,
|
|
258
271
|
runState: nullableString,
|
|
@@ -263,7 +276,6 @@ var init_output_schemas = __esm(() => {
|
|
|
263
276
|
entity: apiField,
|
|
264
277
|
schema: apiField,
|
|
265
278
|
navigationMode: apiField,
|
|
266
|
-
dataStale: apiField,
|
|
267
279
|
assistantEligible: z.boolean(),
|
|
268
280
|
assistantSessionId: apiField,
|
|
269
281
|
assistantThreadId: apiField,
|
|
@@ -292,8 +304,6 @@ var init_output_schemas = __esm(() => {
|
|
|
292
304
|
createdAt: apiField,
|
|
293
305
|
notificationConfig: apiField,
|
|
294
306
|
channelCounts: apiField,
|
|
295
|
-
dataValidation: apiField,
|
|
296
|
-
validationStatistics: apiField,
|
|
297
307
|
realtime: z.object({ lastDataChangedAt: apiField, observerHealth: apiField }).loose().nullable()
|
|
298
308
|
}).loose();
|
|
299
309
|
FetchDataOutput = z.object({
|
|
@@ -1888,11 +1898,11 @@ function scrapeResult(response, format) {
|
|
|
1888
1898
|
};
|
|
1889
1899
|
return withExtraContent(textOnlyResult(meta), { type: "text", text });
|
|
1890
1900
|
}
|
|
1891
|
-
function registerScrapeTools(server, ctx, helpers) {
|
|
1901
|
+
function registerScrapeTools(server, ctx, helpers, defaultProductType) {
|
|
1892
1902
|
const { withErrorHandling } = helpers;
|
|
1893
1903
|
server.registerTool("scrape", {
|
|
1894
1904
|
title: "Scrape URL",
|
|
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.",
|
|
1905
|
+
description: "Immediately fetch one URL as LLM-friendly markdown or raw HTML without creating a workflow. " + "Markdown is the default. Routing is fully automatic: it starts cheap and escalates through stealth tiers on its own when a site blocks bots, and remembers the working method per host. " + (defaultProductType === "realtime" ? "Use create_realtime_monitor for monitoring, and create_workflow for one-time or scheduled structured extraction or navigation-heavy jobs. " : "Use create_workflow instead for structured extraction, recurring runs, monitoring, or navigation-heavy jobs. ") + "The result is a JSON metadata block followed by the page text as its own block, capped at 70 KB.",
|
|
1896
1906
|
inputSchema: strictSchema({
|
|
1897
1907
|
url: z7.url().refine((value) => {
|
|
1898
1908
|
const protocol = new URL(value).protocol;
|
|
@@ -2161,24 +2171,31 @@ function registerWhoamiTool(server, ctx, helpers) {
|
|
|
2161
2171
|
const { withErrorHandling } = helpers;
|
|
2162
2172
|
server.registerTool("whoami", {
|
|
2163
2173
|
title: "Show Current User",
|
|
2164
|
-
description: "Show current user details: email, authentication method, and team
|
|
2174
|
+
description: "Show current user details: email, authentication method, team memberships, and the team's default product",
|
|
2165
2175
|
inputSchema: strictSchema({}),
|
|
2166
2176
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
2167
2177
|
}, withErrorHandling("whoami", async () => {
|
|
2178
|
+
const defaultProductTypePromise = ctx.client.user.getFeatures().then((features) => parseDefaultProductType(features.defaultProductType), () => {
|
|
2179
|
+
return;
|
|
2180
|
+
});
|
|
2168
2181
|
if (ctx.principal.connector) {
|
|
2169
|
-
const teams = await
|
|
2182
|
+
const [teams, defaultProductType] = await Promise.all([
|
|
2183
|
+
ctx.client.listTeams(),
|
|
2184
|
+
defaultProductTypePromise
|
|
2185
|
+
]);
|
|
2170
2186
|
const activeTeam = teams.find((team) => team.id === ctx.principal.teamId);
|
|
2171
2187
|
if (!activeTeam) {
|
|
2172
2188
|
throw new Error("The active team is unavailable for this credential. Reconnect and choose a team.");
|
|
2173
2189
|
}
|
|
2174
2190
|
return jsonResult({
|
|
2175
2191
|
authMethod: "Shared organization service account",
|
|
2176
|
-
team: { name: activeTeam.name }
|
|
2192
|
+
team: { name: activeTeam.name, ...defaultProductType ? { defaultProductType } : {} }
|
|
2177
2193
|
});
|
|
2178
2194
|
}
|
|
2179
|
-
const [user, teams] = await Promise.all([
|
|
2195
|
+
const [user, teams, defaultProductType] = await Promise.all([
|
|
2180
2196
|
ctx.client.user.getCurrentUser(),
|
|
2181
|
-
ctx.client.listTeams()
|
|
2197
|
+
ctx.client.listTeams(),
|
|
2198
|
+
defaultProductTypePromise
|
|
2182
2199
|
]);
|
|
2183
2200
|
const activeTeam = teams.find((team) => team.id === ctx.principal.teamId);
|
|
2184
2201
|
if (!activeTeam) {
|
|
@@ -2189,7 +2206,8 @@ function registerWhoamiTool(server, ctx, helpers) {
|
|
|
2189
2206
|
authMethod: ctx.principal.actor ? `Kadoa Assistant (delegated via ${ctx.principal.actor.sub})` : "Kadoa access token",
|
|
2190
2207
|
team: {
|
|
2191
2208
|
name: activeTeam.name,
|
|
2192
|
-
...activeTeam.memberRole ? { memberRole: activeTeam.memberRole } : {}
|
|
2209
|
+
...activeTeam.memberRole ? { memberRole: activeTeam.memberRole } : {},
|
|
2210
|
+
...defaultProductType ? { defaultProductType } : {}
|
|
2193
2211
|
},
|
|
2194
2212
|
scopes: ctx.principal.scopes
|
|
2195
2213
|
});
|
|
@@ -2649,49 +2667,109 @@ var init_variables = __esm(() => {
|
|
|
2649
2667
|
init_schemas();
|
|
2650
2668
|
});
|
|
2651
2669
|
|
|
2652
|
-
// src/tools/
|
|
2653
|
-
|
|
2654
|
-
|
|
2670
|
+
// src/tools/shared/workflow-status.ts
|
|
2671
|
+
function reviewReasonExplanation(reason) {
|
|
2672
|
+
return reason && REVIEW_REASON_EXPLANATIONS[reason] || UNKNOWN_REVIEW_REASON;
|
|
2673
|
+
}
|
|
2655
2674
|
function isRealTimeInterval(interval) {
|
|
2656
2675
|
return interval?.toUpperCase().replace("-", "_") === "REAL_TIME";
|
|
2657
2676
|
}
|
|
2658
|
-
function
|
|
2659
|
-
const ds =
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
if (!workflow.updateInterval || workflow.updateInterval === "only once")
|
|
2665
|
-
return "Complete";
|
|
2666
|
-
return "Scheduled";
|
|
2667
|
-
case "RUNNING":
|
|
2668
|
-
return "Running";
|
|
2669
|
-
case "VALIDATING":
|
|
2670
|
-
return "Validating";
|
|
2671
|
-
case "FAILED":
|
|
2672
|
-
return "Failed";
|
|
2673
|
-
case "PAUSED":
|
|
2674
|
-
return "Paused";
|
|
2675
|
-
case "SETUP":
|
|
2676
|
-
return "Sample Processing";
|
|
2677
|
-
case "QUEUED":
|
|
2678
|
-
return "Queued";
|
|
2679
|
-
case "PENDING_START":
|
|
2680
|
-
return "Not Started";
|
|
2681
|
-
case "PREVIEW":
|
|
2682
|
-
return "Pending Review";
|
|
2683
|
-
case "DEGRADED":
|
|
2684
|
-
return "Degraded";
|
|
2685
|
-
case "DELETED":
|
|
2686
|
-
return "Deleted";
|
|
2687
|
-
case "COMPLIANCE_REVIEW":
|
|
2688
|
-
return "Compliance Review";
|
|
2689
|
-
case "COMPLIANCE_REJECTED":
|
|
2690
|
-
return "Rejected";
|
|
2691
|
-
default:
|
|
2692
|
-
return ds ?? "Unknown";
|
|
2677
|
+
function fallbackStatusCode(w) {
|
|
2678
|
+
const ds = w.displayState ?? w.state ?? null;
|
|
2679
|
+
if (ds === "ACTIVE") {
|
|
2680
|
+
if (w.isRealTime === true || isRealTimeInterval(w.updateInterval))
|
|
2681
|
+
return "RUNNING";
|
|
2682
|
+
return w.updateInterval && !["ONLY_ONCE", "only once"].includes(w.updateInterval) ? "SCHEDULED" : "COMPLETE";
|
|
2693
2683
|
}
|
|
2684
|
+
if (ds === "VALIDATING")
|
|
2685
|
+
return "AWAITING_REVIEW";
|
|
2686
|
+
return ds;
|
|
2687
|
+
}
|
|
2688
|
+
function labelFor(code, previewReady) {
|
|
2689
|
+
const label = WORKFLOW_STATUS_LABELS[code === "PREVIEW" && previewReady ? "PREVIEW_READY" : code];
|
|
2690
|
+
if (label)
|
|
2691
|
+
return label;
|
|
2692
|
+
console.error(`[workflow-status] unmapped workflow status, passing through verbatim: ${code}`);
|
|
2693
|
+
return code;
|
|
2694
|
+
}
|
|
2695
|
+
function workflowStatusLabel(w) {
|
|
2696
|
+
const code = w.presentation?.status ?? fallbackStatusCode(w);
|
|
2697
|
+
if (!code)
|
|
2698
|
+
return "Unknown";
|
|
2699
|
+
return labelFor(code, w.availableActions?.approve?.allowed === true);
|
|
2700
|
+
}
|
|
2701
|
+
function workflowStatusView(w) {
|
|
2702
|
+
const previewReady = w.availableActions?.approve?.allowed === true;
|
|
2703
|
+
return {
|
|
2704
|
+
status: workflowStatusLabel(w),
|
|
2705
|
+
badges: (w.presentation?.badges ?? []).map((b) => ({
|
|
2706
|
+
kind: b.kind,
|
|
2707
|
+
status: b.status,
|
|
2708
|
+
label: labelFor(b.status, previewReady)
|
|
2709
|
+
})),
|
|
2710
|
+
group: w.presentation?.group ?? null,
|
|
2711
|
+
reviewReason: w.reviewReason ? { code: w.reviewReason, explanation: reviewReasonExplanation(w.reviewReason) } : null
|
|
2712
|
+
};
|
|
2694
2713
|
}
|
|
2714
|
+
function runStatusLabel(state) {
|
|
2715
|
+
if (!state)
|
|
2716
|
+
return "Unknown";
|
|
2717
|
+
return RUN_STATE_LABELS[state] ?? state;
|
|
2718
|
+
}
|
|
2719
|
+
var REVIEW_STATUS_LABEL = "Verifying data", WORKFLOW_STATUS_LABELS, REVIEW_REASON_EXPLANATIONS, UNKNOWN_REVIEW_REASON = "Kadoa's quality or compliance checks selected this run for review.", RUN_STATE_LABELS;
|
|
2720
|
+
var init_workflow_status = __esm(() => {
|
|
2721
|
+
WORKFLOW_STATUS_LABELS = {
|
|
2722
|
+
ASSISTANT_WORKING: "Agent working",
|
|
2723
|
+
AWAITING_REVIEW: REVIEW_STATUS_LABEL,
|
|
2724
|
+
COMPLETE: "Complete",
|
|
2725
|
+
COMPLIANCE_REJECTED: "Rejected",
|
|
2726
|
+
COMPLIANCE_REVIEW: "Compliance review",
|
|
2727
|
+
DEGRADED: "Degraded",
|
|
2728
|
+
DELETED: "Deleted",
|
|
2729
|
+
DRAFT: "Draft",
|
|
2730
|
+
ERROR: "Failed",
|
|
2731
|
+
FAILED: "Failed",
|
|
2732
|
+
IN_SUPPORT: "In support",
|
|
2733
|
+
NOT_SUPPORTED: "Not supported",
|
|
2734
|
+
PAUSED: "Paused",
|
|
2735
|
+
PENDING_START: "Not started",
|
|
2736
|
+
PREVIEW: "Preview",
|
|
2737
|
+
PREVIEW_READY: "Preview ready",
|
|
2738
|
+
QUEUED: "Queued",
|
|
2739
|
+
REPLY_NEEDED: "Reply needed",
|
|
2740
|
+
RUNNING: "Running",
|
|
2741
|
+
SCHEDULED: "Scheduled",
|
|
2742
|
+
SETUP: "Setup",
|
|
2743
|
+
STOPPED: "Stopped",
|
|
2744
|
+
UPDATE_FAILED: "Update failed",
|
|
2745
|
+
UPDATING: "Updating"
|
|
2746
|
+
};
|
|
2747
|
+
REVIEW_REASON_EXPLANATIONS = {
|
|
2748
|
+
COMPLIANCE_REVIEW: "This run triggered a compliance check before delivery.",
|
|
2749
|
+
HARD_FAIL: "An error stopped this run from completing normally.",
|
|
2750
|
+
TRANSFORMATION_REPAIR_REQUIRED: "A data transformation could not be fixed automatically.",
|
|
2751
|
+
MINIMUM_HUMAN_APPROVALS: "Kadoa's quality checks selected this run for review before delivery.",
|
|
2752
|
+
DATA_QUALITY_SCHEMA_ADHERENCE: "The extracted data did not match this workflow's fields and data types.",
|
|
2753
|
+
DATA_QUALITY_SCHEMA_VALIDATION: "Extracted values did not match this workflow's validation rules.",
|
|
2754
|
+
DATA_QUALITY_COMPLETENESS: "The extracted record count did not match the expected source count.",
|
|
2755
|
+
DATA_QUALITY_SEMANTIC_PLAUSIBILITY: "Some extracted values look unusual for their fields.",
|
|
2756
|
+
ROW_COUNT_CHANGED: "This run's row count changed significantly.",
|
|
2757
|
+
WORKFLOW_REVISION_DRIFT: "This run no longer matches the workflow's current setup.",
|
|
2758
|
+
ZERO_RESULTS: "The source returned no data."
|
|
2759
|
+
};
|
|
2760
|
+
RUN_STATE_LABELS = {
|
|
2761
|
+
FINISHED: "Success",
|
|
2762
|
+
IN_PROGRESS: "Running",
|
|
2763
|
+
VALIDATING: REVIEW_STATUS_LABEL,
|
|
2764
|
+
FAILED: "Failed",
|
|
2765
|
+
FAILED_INSUFFICIENT_FUNDS: "Failed",
|
|
2766
|
+
NOT_SUPPORTED: "Failed"
|
|
2767
|
+
};
|
|
2768
|
+
});
|
|
2769
|
+
|
|
2770
|
+
// src/tools/workflows.ts
|
|
2771
|
+
import { WorkflowStatusFilter } from "@kadoa/node-sdk";
|
|
2772
|
+
import { z as z12 } from "zod";
|
|
2695
2773
|
function mapRunStatus(state) {
|
|
2696
2774
|
switch (state) {
|
|
2697
2775
|
case "FINISHED":
|
|
@@ -2789,7 +2867,7 @@ function summarizeWorkflowAuditEntry(entry) {
|
|
|
2789
2867
|
function historyEmptyHint(includeSnapshots) {
|
|
2790
2868
|
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
2869
|
}
|
|
2792
|
-
function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
2870
|
+
function registerWorkflowCoreTools(server, ctx, helpers, defaultProductType) {
|
|
2793
2871
|
const { withErrorHandling } = helpers;
|
|
2794
2872
|
function resolveUrls(args) {
|
|
2795
2873
|
const urls = args.urls ?? (args.url ? [args.url] : []);
|
|
@@ -2897,7 +2975,7 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
2897
2975
|
|
|
2898
2976
|
` + "Create a data extraction workflow using agentic navigation. Supports one-time or scheduled runs. " + "If entity and schema are provided, they guide the extraction; otherwise the AI agent auto-detects the schema from the page. " + "The workflow runs asynchronously and may take several minutes. Do NOT poll or sleep-wait for completion. " + `Return the workflow ID to the user and let them check back later with get_workflow or fetch_data.
|
|
2899
2977
|
|
|
2900
|
-
` + "PREFER TEMPLATES: If the user's request matches an existing template, instantiate it via `templateId` instead of writing a fresh prompt/schema. " + "Use `list_templates` to discover available templates and `get_template` to inspect schemas before deciding. " + "When creating from a template, call this tool with the template's `templateId`, the source `urls`, optional `templateVersion`, and optional `prompt` containing only workflow-specific additional instructions. Never copy the template prompt, entity, or schema into the call, and never silently match or rewrite inline configuration.\n\n" + "NOTE: This tool is for one-time or scheduled extraction ONLY. " + "For continuous realtime monitoring that watches a page for data changes and sends alerts, use the create_realtime_monitor tool instead.",
|
|
2978
|
+
` + "PREFER TEMPLATES: If the user's request matches an existing template, instantiate it via `templateId` instead of writing a fresh prompt/schema. " + "Use `list_templates` to discover available templates and `get_template` to inspect schemas before deciding. " + "When creating from a template, call this tool with the template's `templateId`, the source `urls`, optional `templateVersion`, and optional `prompt` containing only workflow-specific additional instructions. Never copy the template prompt, entity, or schema into the call, and never silently match or rewrite inline configuration.\n\n" + "NOTE: This tool is for one-time or scheduled extraction ONLY. " + (defaultProductType === "realtime" ? `This team's default product is realtime monitoring. Do not call this tool unless the user explicitly asked for a one-time run (e.g. "once", "just the current list") or a schedule (e.g. "daily"). A request to get, extract, or scrape data that names no frequency is not explicit: call create_realtime_monitor instead.` : "For continuous realtime monitoring that watches a page for data changes and sends alerts, use the create_realtime_monitor tool instead."),
|
|
2901
2979
|
inputSchema: strictSchema({
|
|
2902
2980
|
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.'),
|
|
2903
2981
|
...extractionInputShape,
|
|
@@ -3050,7 +3128,7 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
3050
3128
|
}
|
|
3051
3129
|
server.registerTool("create_realtime_monitor", {
|
|
3052
3130
|
title: "Create Realtime Monitor",
|
|
3053
|
-
description: "Create a realtime monitoring workflow that continuously watches a page for data changes and sends alerts. " + "Use this when the user wants to monitor, track, or watch a page in realtime. " + "At least one notification channel (email, webhook, slack, or websocket) is required; notification settings are persisted before Assistant creation is dispatched. " + `Realtime monitors CANNOT be converted to/from one-time or scheduled extraction workflows after creation.
|
|
3131
|
+
description: "Create a realtime monitoring workflow that continuously watches a page for data changes and sends alerts. " + (defaultProductType === "realtime" ? "This team's default product is realtime monitoring: use this tool whenever the user asks to monitor, track, or watch a page, or to get or extract data from it, unless they explicitly ask for a one-time run or a schedule. A request that names no frequency counts as realtime. " : "Use this when the user wants to monitor, track, or watch a page in realtime. ") + "At least one notification channel (email, webhook, slack, or websocket) is required; notification settings are persisted before Assistant creation is dispatched. " + `Realtime monitors CANNOT be converted to/from one-time or scheduled extraction workflows after creation.
|
|
3054
3132
|
|
|
3055
3133
|
` + "If entity and schema are provided, they guide what the Assistant should monitor; otherwise the Assistant determines what to watch. " + "Creation is asynchronous and the response includes workflow, session, thread, and job IDs plus a dashboard URL. " + "Use generic workflow Assistant tools (get_workflow_assistant, answer_workflow_assistant_question, interrupt_workflow_assistant, resume_workflow_assistant, stop_workflow_assistant) for follow-up status, questions, and controls. " + "Do NOT poll or sleep-wait for completion.",
|
|
3056
3134
|
inputSchema: strictSchema({
|
|
@@ -3107,7 +3185,7 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
3107
3185
|
}));
|
|
3108
3186
|
server.registerTool("list_workflows", {
|
|
3109
3187
|
title: "List Workflows",
|
|
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
|
|
3188
|
+
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 status "Preview ready" is preview data ready to approve with approve_workflow; "Preview" means preview data exists but approval isn't available yet. Workflows with an open support ticket are excluded from group:attention and appear under group:support. badges[].label is exactly what the Kadoa dashboard displays (lifecycle, activity, and support badges, e.g. "Setup" + "Agent working"): use these labels when describing the workflow to the user. status is the one-line summary in dashboard wording (e.g. "Verifying data" = the latest run is held for quality review before delivery; see reviewReason for why).`,
|
|
3111
3189
|
inputSchema: strictSchema({
|
|
3112
3190
|
limit: limitInput({
|
|
3113
3191
|
noun: "Workflows",
|
|
@@ -3145,10 +3223,13 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
3145
3223
|
id: w.id,
|
|
3146
3224
|
name: w.name,
|
|
3147
3225
|
dashboardUrl: workflowDashboardUrl(w.id),
|
|
3148
|
-
|
|
3226
|
+
...workflowStatusView(w),
|
|
3149
3227
|
state: w.state ?? null,
|
|
3150
3228
|
displayState: w.displayState ?? null,
|
|
3151
3229
|
runState: w.runState ?? null,
|
|
3230
|
+
execution: w.execution ?? null,
|
|
3231
|
+
assistantState: w.assistantState ?? null,
|
|
3232
|
+
workspaceActivity: w.workspaceActivity ?? null,
|
|
3152
3233
|
urls: w.urls,
|
|
3153
3234
|
totalRecords: w.totalRecords,
|
|
3154
3235
|
startedAt: w.startedAt,
|
|
@@ -3156,7 +3237,6 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
3156
3237
|
createdAt: w.createdAt,
|
|
3157
3238
|
assistant: {
|
|
3158
3239
|
eligible: w.assistantEligible === true,
|
|
3159
|
-
dataStale: w.dataStale === true,
|
|
3160
3240
|
session: w.assistantSessionId ? {
|
|
3161
3241
|
id: w.assistantSessionId,
|
|
3162
3242
|
threadId: w.assistantThreadId ?? null,
|
|
@@ -3188,7 +3268,7 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
3188
3268
|
}));
|
|
3189
3269
|
server.registerTool("get_workflow", {
|
|
3190
3270
|
title: "Get Workflow",
|
|
3191
|
-
description:
|
|
3271
|
+
description: 'Get canonical workflow details, including Assistant/session linkage, extraction intent, template-controlled parts, job/run state, schedule timezone, location, monitoring, and realtime health. For Assistant-built workflows, extractionSpec - not the legacy prompt - is authoritative. badges[].label is exactly what the Kadoa dashboard displays (lifecycle, activity, and support badges, e.g. "Setup" + "Agent working"): use these labels when describing the workflow to the user. status is the one-line summary in dashboard wording (e.g. "Verifying data" = the latest run is held for quality review before delivery; see reviewReason for why).',
|
|
3192
3272
|
inputSchema: strictSchema({
|
|
3193
3273
|
workflowId: z12.string().min(1).describe("The workflow ID")
|
|
3194
3274
|
}),
|
|
@@ -3213,10 +3293,13 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
3213
3293
|
name: workflow.name,
|
|
3214
3294
|
dashboardUrl: workflowDashboardUrl(workflow.id ?? args.workflowId),
|
|
3215
3295
|
description: workflow.description,
|
|
3216
|
-
|
|
3296
|
+
...workflowStatusView(workflow),
|
|
3217
3297
|
state: workflow.state ?? null,
|
|
3218
3298
|
displayState: workflow.displayState ?? null,
|
|
3219
3299
|
runState: workflow.runState ?? null,
|
|
3300
|
+
execution: workflow.execution ?? null,
|
|
3301
|
+
assistantState: workflow.assistantState ?? null,
|
|
3302
|
+
workspaceActivity: workflow.workspaceActivity ?? null,
|
|
3220
3303
|
urls: workflow.urls,
|
|
3221
3304
|
prompt: extractionSpec ? null : legacyPrompt,
|
|
3222
3305
|
intentSource: extractionSpec ? "extraction_spec" : legacyPrompt ? "legacy_prompt" : null,
|
|
@@ -3224,7 +3307,6 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
3224
3307
|
entity: workflow.entity,
|
|
3225
3308
|
schema: workflow.schema,
|
|
3226
3309
|
navigationMode: workflow.navigationMode,
|
|
3227
|
-
dataStale: workflow.dataStale ?? false,
|
|
3228
3310
|
assistantEligible: workflow.assistantEligible === true,
|
|
3229
3311
|
assistantSessionId: workflow.assistantSessionId ?? null,
|
|
3230
3312
|
assistantThreadId: workflow.assistantThreadId ?? null,
|
|
@@ -3267,8 +3349,6 @@ function registerWorkflowCoreTools(server, ctx, helpers) {
|
|
|
3267
3349
|
createdAt: workflow.createdAt,
|
|
3268
3350
|
notificationConfig: workflow.notificationConfig,
|
|
3269
3351
|
channelCounts: workflow.channelCounts ?? {},
|
|
3270
|
-
dataValidation: workflow.dataValidation ?? null,
|
|
3271
|
-
validationStatistics: workflow.validationStatistics ?? null,
|
|
3272
3352
|
realtime: isRealtime ? {
|
|
3273
3353
|
lastDataChangedAt: workflow.lastDataChangedAt ?? null,
|
|
3274
3354
|
observerHealth: workflow.observerHealth ?? null
|
|
@@ -3368,7 +3448,7 @@ function registerWorkflowHistoryTools(server, ctx, helpers) {
|
|
|
3368
3448
|
}));
|
|
3369
3449
|
server.registerTool("list_workflow_runs", {
|
|
3370
3450
|
title: "List Workflow Runs",
|
|
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.",
|
|
3451
|
+
description: "List a workflow's execution run history - each run's status, start/finish time, " + "record count, and errors. Use to answer 'when did this last succeed?' " + "(status='success', limit=1) or 'recent success/failure pattern?'. Distinct from " + "get_workflow_history, which is the config audit log. `status` is normally " + "success | failed | in_progress; a run whose backend state this server does not " + "recognize reports that raw state verbatim, so treat any other value as unknown " + "rather than as a failure. `label` is the dashboard's wording for the run " + "(Success, Failed, Running, or 'Verifying data' = held for quality review before delivery); " + "prefer it when describing a run to the user.",
|
|
3372
3452
|
inputSchema: strictSchema({
|
|
3373
3453
|
workflowId: z12.string().min(1).describe("The workflow ID"),
|
|
3374
3454
|
status: z12.enum(["success", "failed", "in_progress"]).optional().describe("Filter runs by outcome"),
|
|
@@ -3387,6 +3467,7 @@ function registerWorkflowHistoryTools(server, ctx, helpers) {
|
|
|
3387
3467
|
const runs = (response.workflowRuns ?? []).map((r) => ({
|
|
3388
3468
|
id: r.id,
|
|
3389
3469
|
status: mapRunStatus(r.state),
|
|
3470
|
+
label: runStatusLabel(r.state),
|
|
3390
3471
|
startedAt: r.startedAt,
|
|
3391
3472
|
finishedAt: r.finishedAt,
|
|
3392
3473
|
records: r.records,
|
|
@@ -3436,7 +3517,7 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
3436
3517
|
const FETCH_DATA_MAX_LIMIT = 500;
|
|
3437
3518
|
server.registerTool("fetch_data", {
|
|
3438
3519
|
title: "Fetch Workflow Data",
|
|
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 '
|
|
3520
|
+
description: "Get a PAGE of extracted data from a workflow. Use ONLY for previews, sorted/filtered slices, or explicit 'first N rows' / 'top N' queries (capped at 500 rows per call). Do NOT use this to retrieve a full dataset, 'all rows', or anything the user wants to analyze in Excel / pandas / duckdb - use export_data for those. Data is only available after the workflow run has completed (status is no longer 'Running' or 'Verifying data'). Do NOT poll or sleep-wait for completion.",
|
|
3440
3521
|
inputSchema: strictSchema({
|
|
3441
3522
|
workflowId: z12.string().min(1).describe("The workflow ID"),
|
|
3442
3523
|
limit: limitInput({
|
|
@@ -3598,7 +3679,7 @@ function registerWorkflowRunTools(server, ctx, helpers) {
|
|
|
3598
3679
|
try {
|
|
3599
3680
|
const workflow = await ctx.client.workflow.get(args.workflowId);
|
|
3600
3681
|
workflowName = workflow.name || args.workflowId;
|
|
3601
|
-
workflowStatus =
|
|
3682
|
+
workflowStatus = workflowStatusLabel(workflow);
|
|
3602
3683
|
} catch {}
|
|
3603
3684
|
if (!args.confirmed) {
|
|
3604
3685
|
return jsonResult({
|
|
@@ -3710,6 +3791,7 @@ var init_workflows = __esm(() => {
|
|
|
3710
3791
|
init_output_schemas();
|
|
3711
3792
|
init_pagination();
|
|
3712
3793
|
init_schemas();
|
|
3794
|
+
init_workflow_status();
|
|
3713
3795
|
WORKFLOW_AUDIT_WATCHED_KEYS = [
|
|
3714
3796
|
"name",
|
|
3715
3797
|
"tags",
|
|
@@ -3727,9 +3809,9 @@ var init_workflows = __esm(() => {
|
|
|
3727
3809
|
function registerTools(server, ctx, capabilities) {
|
|
3728
3810
|
const helpers = { withErrorHandling: createWithErrorHandling(ctx) };
|
|
3729
3811
|
if (capabilities.scrape)
|
|
3730
|
-
registerScrapeTools(server, ctx, helpers);
|
|
3812
|
+
registerScrapeTools(server, ctx, helpers, capabilities.defaultProductType);
|
|
3731
3813
|
registerWhoamiTool(server, ctx, helpers);
|
|
3732
|
-
registerWorkflowCoreTools(server, ctx, helpers);
|
|
3814
|
+
registerWorkflowCoreTools(server, ctx, helpers, capabilities.defaultProductType);
|
|
3733
3815
|
registerAssistantTools(server, ctx, helpers);
|
|
3734
3816
|
registerWorkflowHistoryTools(server, ctx, helpers);
|
|
3735
3817
|
registerObservabilityTools(server, ctx, helpers);
|
|
@@ -4413,12 +4495,41 @@ var init_request_log = __esm(() => {
|
|
|
4413
4495
|
|
|
4414
4496
|
// src/server.ts
|
|
4415
4497
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/server";
|
|
4498
|
+
function lifecycleInstruction2(defaultProductType) {
|
|
4499
|
+
return defaultProductType === "realtime" ? "For one-time or scheduled workflows, the lifecycle is create_workflow → get_workflow (check status) → fetch_data (get results). Workflows run asynchronously - never poll or sleep-wait." : "Workflow lifecycle: create_workflow → get_workflow (check status) → fetch_data (get results). Workflows run asynchronously - never poll or sleep-wait.";
|
|
4500
|
+
}
|
|
4501
|
+
function routingInstructions2(defaultProductType) {
|
|
4502
|
+
if (defaultProductType === "realtime") {
|
|
4503
|
+
return [
|
|
4504
|
+
`This team's default product is realtime monitoring. Use create_realtime_monitor whenever the user asks to monitor, track, or watch a page, or to get or extract data from it, unless they explicitly ask for a one-time run (e.g. "once") or a schedule (e.g. "daily"). A request that names no frequency is not explicit: default to realtime without asking the user to choose. It first persists notification channels, then asynchronously returns workflow/session/thread/job IDs; use the workflow Assistant tools for follow-up status, questions, and controls.`,
|
|
4505
|
+
"Use create_workflow only when the user explicitly asks for a one-time run or a schedule. Use scrape for an immediate raw HTML or markdown fetch from one URL."
|
|
4506
|
+
];
|
|
4507
|
+
}
|
|
4508
|
+
return [
|
|
4509
|
+
"Use create_realtime_monitor only when the user wants continuous realtime change detection with alerts. It first persists notification channels, then asynchronously returns workflow/session/thread/job IDs; use the workflow Assistant tools for follow-up status, questions, and controls.",
|
|
4510
|
+
"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."
|
|
4511
|
+
];
|
|
4512
|
+
}
|
|
4416
4513
|
async function createServer3(auth, options) {
|
|
4417
4514
|
const ctx = {
|
|
4418
4515
|
client: createKadoaClient(auth),
|
|
4419
4516
|
principal: auth.principal,
|
|
4420
4517
|
teamId: auth.principal.teamId
|
|
4421
4518
|
};
|
|
4519
|
+
let capabilities = options?.capabilities;
|
|
4520
|
+
if (!capabilities) {
|
|
4521
|
+
try {
|
|
4522
|
+
const response = await ctx.client.user.getFeatures();
|
|
4523
|
+
capabilities = {
|
|
4524
|
+
scrape: response.features.scrape,
|
|
4525
|
+
defaultProductType: parseDefaultProductType(response.defaultProductType)
|
|
4526
|
+
};
|
|
4527
|
+
} catch (error) {
|
|
4528
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
4529
|
+
console.error(`[CAPABILITIES] Failed to resolve team capabilities; gated tools will be hidden: ${message}`);
|
|
4530
|
+
capabilities = { scrape: false };
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4422
4533
|
const server = new McpServer2({ name: "kadoa", version: package_default.version }, {
|
|
4423
4534
|
instructions: [
|
|
4424
4535
|
"IMPORTANT: One workflow = one source. A single workflow can extract many different fields, tables, and sections from the same URL(s) using a rich schema.",
|
|
@@ -4427,15 +4538,14 @@ async function createServer3(auth, options) {
|
|
|
4427
4538
|
"Kadoa workflows use agentic navigation: the AI agent can browse pages, click buttons, fill forms, select dropdowns, paginate, open detail pages, and handle multi-step interactions.",
|
|
4428
4539
|
"Describe the full navigation steps in the prompt (e.g., 'select year 2022 from the dropdown, click Search, extract the table, then repeat for 2023').",
|
|
4429
4540
|
"",
|
|
4430
|
-
|
|
4541
|
+
lifecycleInstruction2(capabilities.defaultProductType),
|
|
4431
4542
|
"",
|
|
4432
|
-
|
|
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.",
|
|
4543
|
+
...routingInstructions2(capabilities.defaultProductType),
|
|
4434
4544
|
"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.",
|
|
4435
4545
|
"Use list_changes and get_change to retrieve detected diffs from realtime monitoring workflows.",
|
|
4436
4546
|
"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
4547
|
"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.",
|
|
4438
|
-
|
|
4548
|
+
`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 status "Preview ready" is preview data ready to approve with approve_workflow; "Preview" means preview data exists but approval isn't available yet. Workflows with an open support ticket are excluded from group:attention and appear under group:support.`,
|
|
4439
4549
|
"",
|
|
4440
4550
|
"Schema tips: Use descriptive field names and examples. Group related data under one entity.",
|
|
4441
4551
|
"The AI agent uses the schema + prompt to understand what to extract - a detailed prompt with a comprehensive schema produces better results than multiple simple workflows.",
|
|
@@ -4444,17 +4554,6 @@ async function createServer3(auth, options) {
|
|
|
4444
4554
|
].join(`
|
|
4445
4555
|
`)
|
|
4446
4556
|
});
|
|
4447
|
-
let capabilities = options?.capabilities;
|
|
4448
|
-
if (!capabilities) {
|
|
4449
|
-
try {
|
|
4450
|
-
const response = await ctx.client.user.getFeatures();
|
|
4451
|
-
capabilities = { scrape: response.features.scrape };
|
|
4452
|
-
} catch (error) {
|
|
4453
|
-
const message = error instanceof Error ? error.message : "unknown error";
|
|
4454
|
-
console.error(`[CAPABILITIES] Failed to resolve team capabilities; gated tools will be hidden: ${message}`);
|
|
4455
|
-
capabilities = { scrape: false };
|
|
4456
|
-
}
|
|
4457
|
-
}
|
|
4458
4557
|
registerTools(server, ctx, capabilities);
|
|
4459
4558
|
server.server.onerror = (error) => console.error("[MCP Error]", safeErrorSummary(error));
|
|
4460
4559
|
return server;
|
|
@@ -4534,7 +4633,7 @@ async function requestNeedsCapabilityLookup(request) {
|
|
|
4534
4633
|
return false;
|
|
4535
4634
|
}
|
|
4536
4635
|
}
|
|
4537
|
-
return method === "tools/list" || method === "tools/call" && name === "scrape";
|
|
4636
|
+
return method === "initialize" || method === "server/discover" || method === "tools/list" || method === "tools/call" && name === "scrape";
|
|
4538
4637
|
}
|
|
4539
4638
|
function isClientCausedHandlerError(error) {
|
|
4540
4639
|
if (error instanceof ProtocolError)
|
|
@@ -4874,12 +4973,41 @@ init_package();
|
|
|
4874
4973
|
init_client();
|
|
4875
4974
|
init_tools();
|
|
4876
4975
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
4976
|
+
function lifecycleInstruction(defaultProductType) {
|
|
4977
|
+
return defaultProductType === "realtime" ? "For one-time or scheduled workflows, the lifecycle is create_workflow → get_workflow (check status) → fetch_data (get results). Workflows run asynchronously - never poll or sleep-wait." : "Workflow lifecycle: create_workflow → get_workflow (check status) → fetch_data (get results). Workflows run asynchronously - never poll or sleep-wait.";
|
|
4978
|
+
}
|
|
4979
|
+
function routingInstructions(defaultProductType) {
|
|
4980
|
+
if (defaultProductType === "realtime") {
|
|
4981
|
+
return [
|
|
4982
|
+
`This team's default product is realtime monitoring. Use create_realtime_monitor whenever the user asks to monitor, track, or watch a page, or to get or extract data from it, unless they explicitly ask for a one-time run (e.g. "once") or a schedule (e.g. "daily"). A request that names no frequency is not explicit: default to realtime without asking the user to choose. It first persists notification channels, then asynchronously returns workflow/session/thread/job IDs; use the workflow Assistant tools for follow-up status, questions, and controls.`,
|
|
4983
|
+
"Use create_workflow only when the user explicitly asks for a one-time run or a schedule. Use scrape for an immediate raw HTML or markdown fetch from one URL."
|
|
4984
|
+
];
|
|
4985
|
+
}
|
|
4986
|
+
return [
|
|
4987
|
+
"Use create_realtime_monitor only when the user wants continuous realtime change detection with alerts. It first persists notification channels, then asynchronously returns workflow/session/thread/job IDs; use the workflow Assistant tools for follow-up status, questions, and controls.",
|
|
4988
|
+
"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."
|
|
4989
|
+
];
|
|
4990
|
+
}
|
|
4877
4991
|
async function createServer(auth, options) {
|
|
4878
4992
|
const ctx = {
|
|
4879
4993
|
client: createKadoaClient(auth),
|
|
4880
4994
|
principal: auth.principal,
|
|
4881
4995
|
teamId: auth.principal.teamId
|
|
4882
4996
|
};
|
|
4997
|
+
let capabilities = options?.capabilities;
|
|
4998
|
+
if (!capabilities) {
|
|
4999
|
+
try {
|
|
5000
|
+
const response = await ctx.client.user.getFeatures();
|
|
5001
|
+
capabilities = {
|
|
5002
|
+
scrape: response.features.scrape,
|
|
5003
|
+
defaultProductType: parseDefaultProductType(response.defaultProductType)
|
|
5004
|
+
};
|
|
5005
|
+
} catch (error) {
|
|
5006
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
5007
|
+
console.error(`[CAPABILITIES] Failed to resolve team capabilities; gated tools will be hidden: ${message}`);
|
|
5008
|
+
capabilities = { scrape: false };
|
|
5009
|
+
}
|
|
5010
|
+
}
|
|
4883
5011
|
const server = new McpServer({ name: "kadoa", version: package_default.version }, {
|
|
4884
5012
|
instructions: [
|
|
4885
5013
|
"IMPORTANT: One workflow = one source. A single workflow can extract many different fields, tables, and sections from the same URL(s) using a rich schema.",
|
|
@@ -4888,15 +5016,14 @@ async function createServer(auth, options) {
|
|
|
4888
5016
|
"Kadoa workflows use agentic navigation: the AI agent can browse pages, click buttons, fill forms, select dropdowns, paginate, open detail pages, and handle multi-step interactions.",
|
|
4889
5017
|
"Describe the full navigation steps in the prompt (e.g., 'select year 2022 from the dropdown, click Search, extract the table, then repeat for 2023').",
|
|
4890
5018
|
"",
|
|
4891
|
-
|
|
5019
|
+
lifecycleInstruction(capabilities.defaultProductType),
|
|
4892
5020
|
"",
|
|
4893
|
-
|
|
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.",
|
|
5021
|
+
...routingInstructions(capabilities.defaultProductType),
|
|
4895
5022
|
"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.",
|
|
4896
5023
|
"Use list_changes and get_change to retrieve detected diffs from realtime monitoring workflows.",
|
|
4897
5024
|
"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
5025
|
"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.",
|
|
4899
|
-
|
|
5026
|
+
`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 status "Preview ready" is preview data ready to approve with approve_workflow; "Preview" means preview data exists but approval isn't available yet. Workflows with an open support ticket are excluded from group:attention and appear under group:support.`,
|
|
4900
5027
|
"",
|
|
4901
5028
|
"Schema tips: Use descriptive field names and examples. Group related data under one entity.",
|
|
4902
5029
|
"The AI agent uses the schema + prompt to understand what to extract - a detailed prompt with a comprehensive schema produces better results than multiple simple workflows.",
|
|
@@ -4905,17 +5032,6 @@ async function createServer(auth, options) {
|
|
|
4905
5032
|
].join(`
|
|
4906
5033
|
`)
|
|
4907
5034
|
});
|
|
4908
|
-
let capabilities = options?.capabilities;
|
|
4909
|
-
if (!capabilities) {
|
|
4910
|
-
try {
|
|
4911
|
-
const response = await ctx.client.user.getFeatures();
|
|
4912
|
-
capabilities = { scrape: response.features.scrape };
|
|
4913
|
-
} catch (error) {
|
|
4914
|
-
const message = error instanceof Error ? error.message : "unknown error";
|
|
4915
|
-
console.error(`[CAPABILITIES] Failed to resolve team capabilities; gated tools will be hidden: ${message}`);
|
|
4916
|
-
capabilities = { scrape: false };
|
|
4917
|
-
}
|
|
4918
|
-
}
|
|
4919
5035
|
registerTools(server, ctx, capabilities);
|
|
4920
5036
|
server.server.onerror = (error) => console.error("[MCP Error]", safeErrorSummary(error));
|
|
4921
5037
|
return server;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kadoa/mcp",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Kadoa MCP Server — manage workflows from Claude Desktop, Cursor, and other MCP clients",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"prepublishOnly": "bun run check-types && bun run test:unit && bun run build"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@kadoa/node-sdk": "^0.
|
|
30
|
+
"@kadoa/node-sdk": "^0.48.0",
|
|
31
31
|
"@modelcontextprotocol/client": "2.1.0",
|
|
32
32
|
"@modelcontextprotocol/express": "2.0.1",
|
|
33
33
|
"@modelcontextprotocol/node": "2.1.0",
|