@kadoa/mcp 0.5.20 → 0.5.21
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 +12 -7
- package/dist/index.js +139 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -127,9 +127,10 @@ to retrieve the extracted records and display them as a table.
|
|
|
127
127
|
> You: Use my "Product Scraper" template to scrape https://example-shop.com.
|
|
128
128
|
|
|
129
129
|
Claude calls list_templates to find the matching template, then
|
|
130
|
-
create_workflow with `templateId` and `urls` only
|
|
131
|
-
schema
|
|
132
|
-
ID for follow-up with get_workflow
|
|
130
|
+
create_workflow with `templateId` and `urls` only - never copy the prompt,
|
|
131
|
+
entity, or schema into standalone creation. Those values are inherited from
|
|
132
|
+
the template version. Returns the workflow ID for follow-up with get_workflow
|
|
133
|
+
or fetch_data.
|
|
133
134
|
```
|
|
134
135
|
|
|
135
136
|
### Update a workflow and re-run
|
|
@@ -148,8 +149,10 @@ changes, and shows the updated field list.
|
|
|
148
149
|
|
|
149
150
|
> You: Run it again with the new schema.
|
|
150
151
|
|
|
151
|
-
Claude calls run_workflow and
|
|
152
|
-
|
|
152
|
+
Claude calls run_workflow and returns the workflow ID while the run proceeds
|
|
153
|
+
asynchronously. Check back later with get_workflow or fetch_data. If a
|
|
154
|
+
one-time or scheduled workflow fails, call run_workflow again with the same
|
|
155
|
+
workflow ID and configuration - never delete and recreate it just to retry.
|
|
153
156
|
```
|
|
154
157
|
|
|
155
158
|
### Update deterministic workflow settings
|
|
@@ -247,14 +250,16 @@ Relevant files in `kadoa-backend`:
|
|
|
247
250
|
|
|
248
251
|
1. Merge PRs to `main` using Conventional Commits (`feat:`, `fix:`, etc.). Release Please opens/maintains a `chore(main): release mcp x.y.z` PR.
|
|
249
252
|
2. Merge the release PR. The [`release-please.yml`](.github/workflows/release-please.yml) workflow tags, drafts a GitHub Release, and publishes to npm (`latest` dist-tag).
|
|
250
|
-
3.
|
|
251
|
-
4. Merge to `main`. CI (`main-build-deploy.yml`) builds and pushes `europe-west3-docker.pkg.dev/oceanic-base-310208/kadoa-artifacts/mcp-server:<IMAGE_TAG>` (tag shown in the build summary).
|
|
253
|
+
3. After npm publication, `release-please.yml` opens or updates a deterministic PR in `kadoa-backend` that bumps `infra/docker/mcp/package.json` and refreshes `bun.lock`. The PR validates a frozen production install and Docker image build, and remains manually mergeable.
|
|
254
|
+
4. Merge the backend PR to `main`. CI (`main-build-deploy.yml`) builds and pushes `europe-west3-docker.pkg.dev/oceanic-base-310208/kadoa-artifacts/mcp-server:<IMAGE_TAG>` (tag shown in the build summary).
|
|
252
255
|
5. Trigger the **Deploy to Production** workflow ([`deploy-prod.yml`](https://github.com/kadoa-org/kadoa-backend/actions/workflows/deploy-prod.yml)) with:
|
|
253
256
|
- **Target cluster:** `gcp`
|
|
254
257
|
- **Deployment scope:** `mcp`
|
|
255
258
|
- **Image tag:** the tag from step 4
|
|
256
259
|
- **Method:** `kubectl`
|
|
257
260
|
|
|
261
|
+
The cross-repository bump requires the `BACKEND_REPO_TOKEN` secret in this repository. The selected authentication approach is a fine-grained PAT scoped only to `kadoa-org/kadoa-backend` with Contents read/write and Pull requests read/write permissions (Metadata read is automatic). If the secret is not provisioned, the release workflow skips the backend bump with a warning rather than failing npm releases; an administrator must provision it before relying on automatic hosted-server updates.
|
|
262
|
+
|
|
258
263
|
### RC / test release
|
|
259
264
|
|
|
260
265
|
Use this when you want to validate a change end-to-end against real clients (Claude Desktop, Cursor, ChatGPT) before promoting to `latest` / prod. The flow mirrors the prod one, but every step targets `rc` channels.
|
package/dist/index.js
CHANGED
|
@@ -52699,7 +52699,7 @@ function extractApiMessage(responseBody) {
|
|
|
52699
52699
|
if (body.validationErrors && typeof body.validationErrors === "object" && body.validationErrors !== null) {
|
|
52700
52700
|
const details = Object.entries(body.validationErrors).map(([field, err]) => `${field}: "${err}"`).join(", ");
|
|
52701
52701
|
if (details) {
|
|
52702
|
-
msg = msg ? `${msg}
|
|
52702
|
+
msg = msg ? `${msg} - Details: ${details}` : `Validation failed - Details: ${details}`;
|
|
52703
52703
|
}
|
|
52704
52704
|
}
|
|
52705
52705
|
if (!msg && Array.isArray(body.issues)) {
|
|
@@ -52709,7 +52709,7 @@ function extractApiMessage(responseBody) {
|
|
|
52709
52709
|
return path ? `${path}: "${message}"` : `"${message}"`;
|
|
52710
52710
|
}).join(", ");
|
|
52711
52711
|
if (details)
|
|
52712
|
-
msg = `Validation failed
|
|
52712
|
+
msg = `Validation failed - Details: ${details}`;
|
|
52713
52713
|
}
|
|
52714
52714
|
return msg;
|
|
52715
52715
|
}
|
|
@@ -52895,14 +52895,14 @@ function registerTools(server, ctx, capabilities) {
|
|
|
52895
52895
|
});
|
|
52896
52896
|
}));
|
|
52897
52897
|
const urlInputShape = {
|
|
52898
|
-
url: exports_external.string().optional().describe("Single URL
|
|
52898
|
+
url: exports_external.string().optional().describe("Single URL - prefer using 'urls' instead. If both are provided, 'urls' takes precedence."),
|
|
52899
52899
|
urls: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string()).min(1)).optional().describe("Starting URLs for the workflow (array of strings). Also accepts a single URL string.")
|
|
52900
52900
|
};
|
|
52901
52901
|
const extractionInputShape = {
|
|
52902
52902
|
prompt: exports_external.string().optional().describe('Natural language description of what to extract (e.g., "Extract product prices and names"). Required unless templateId is provided.'),
|
|
52903
52903
|
name: exports_external.string().optional().describe("Optional name for the workflow"),
|
|
52904
52904
|
entity: exports_external.string().optional().describe("Entity name for extraction (e.g., 'Product', 'Job Posting')"),
|
|
52905
|
-
schema: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.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
|
|
52905
|
+
schema: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.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.")
|
|
52906
52906
|
};
|
|
52907
52907
|
const webhookAuthShape = exports_external.object({
|
|
52908
52908
|
type: exports_external.enum(["bearer", "basic", "header"]).describe("Authentication type"),
|
|
@@ -53030,15 +53030,15 @@ function registerTools(server, ctx, capabilities) {
|
|
|
53030
53030
|
return channels;
|
|
53031
53031
|
}
|
|
53032
53032
|
server.registerTool("create_workflow", {
|
|
53033
|
-
description: "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. " + `Do NOT create separate workflows for different data points on the same page
|
|
53033
|
+
description: "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. " + `Do NOT create separate workflows for different data points on the same page - instead, define one workflow with a multi-field schema covering everything needed.
|
|
53034
53034
|
|
|
53035
53035
|
` + "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.
|
|
53036
53036
|
|
|
53037
|
-
` + "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
|
|
53037
|
+
` + "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`, and optional `templateVersion` only. Never copy the template prompt, entity, or schema into standalone creation, and never silently match or rewrite inline configuration.\n\n" + "NOTE: This tool is for one-time or scheduled extraction ONLY. " + "For continuous Julie realtime monitoring (watching a page for data changes and alerting), use the create_realtime_monitor tool instead.",
|
|
53038
53038
|
inputSchema: strictSchema({
|
|
53039
53039
|
...extractionInputShape,
|
|
53040
53040
|
...urlInputShape,
|
|
53041
|
-
templateId: exports_external.string().optional().describe("Instantiate this workflow from a published template.
|
|
53041
|
+
templateId: exports_external.string().optional().describe("Instantiate this workflow from a published template. Pass the templateId, source urls, and optional templateVersion. Do not copy prompt/entity/schema from the template 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."),
|
|
53042
53042
|
templateVersion: exports_external.preprocess(coerceNumber(), exports_external.number()).optional().describe("Specific published template version (integer) to instantiate. Defaults to the latest published version when templateId is set."),
|
|
53043
53043
|
description: exports_external.string().max(500).optional().describe("Description of what this workflow does (max 500 characters)"),
|
|
53044
53044
|
tags: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string())).optional().describe("Tags for organizing workflows"),
|
|
@@ -53079,7 +53079,7 @@ function registerTools(server, ctx, capabilities) {
|
|
|
53079
53079
|
let workflowId;
|
|
53080
53080
|
if (args.templateId) {
|
|
53081
53081
|
if (args.prompt || args.entity || args.schema) {
|
|
53082
|
-
return errorResult("When 'templateId' is set, 'prompt', 'entity', and 'schema' must NOT be supplied
|
|
53082
|
+
return errorResult("When 'templateId' is set, 'prompt', 'entity', and 'schema' must NOT be supplied - they are inherited from the template version.");
|
|
53083
53083
|
}
|
|
53084
53084
|
const { id } = await ctx.client.workflow.create({
|
|
53085
53085
|
urls,
|
|
@@ -53235,7 +53235,7 @@ function registerTools(server, ctx, capabilities) {
|
|
|
53235
53235
|
});
|
|
53236
53236
|
}));
|
|
53237
53237
|
server.registerTool("get_workflow", {
|
|
53238
|
-
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
|
|
53238
|
+
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.",
|
|
53239
53239
|
inputSchema: {
|
|
53240
53240
|
workflowId: exports_external.string().describe("The workflow ID")
|
|
53241
53241
|
},
|
|
@@ -53599,7 +53599,7 @@ function registerTools(server, ctx, capabilities) {
|
|
|
53599
53599
|
});
|
|
53600
53600
|
}));
|
|
53601
53601
|
server.registerTool("list_workflow_runs", {
|
|
53602
|
-
description: "List a workflow's execution run history
|
|
53602
|
+
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.",
|
|
53603
53603
|
inputSchema: {
|
|
53604
53604
|
workflowId: exports_external.string().describe("The workflow ID"),
|
|
53605
53605
|
status: exports_external.enum(["success", "failed", "in_progress"]).optional().describe("Filter runs by outcome"),
|
|
@@ -53628,7 +53628,7 @@ function registerTools(server, ctx, capabilities) {
|
|
|
53628
53628
|
});
|
|
53629
53629
|
}));
|
|
53630
53630
|
server.registerTool("run_workflow", {
|
|
53631
|
-
description: "Run a workflow to extract fresh data. 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.",
|
|
53631
|
+
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.",
|
|
53632
53632
|
inputSchema: {
|
|
53633
53633
|
workflowId: exports_external.string().describe("The workflow ID to run"),
|
|
53634
53634
|
limit: exports_external.preprocess(coerceNumber(), exports_external.number()).optional().describe("Maximum number of records to extract (default: 1000)")
|
|
@@ -53651,7 +53651,7 @@ function registerTools(server, ctx, capabilities) {
|
|
|
53651
53651
|
const FETCH_DATA_DEFAULT_LIMIT = 50;
|
|
53652
53652
|
const FETCH_DATA_MAX_LIMIT = 500;
|
|
53653
53653
|
server.registerTool("fetch_data", {
|
|
53654
|
-
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
|
|
53654
|
+
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.",
|
|
53655
53655
|
inputSchema: {
|
|
53656
53656
|
workflowId: exports_external.string().describe("The workflow ID"),
|
|
53657
53657
|
limit: exports_external.preprocess(coerceNumber(), exports_external.number()).optional().describe(`Maximum number of records to return. Default ${FETCH_DATA_DEFAULT_LIMIT}, max ${FETCH_DATA_MAX_LIMIT}.`),
|
|
@@ -53686,7 +53686,7 @@ function registerTools(server, ctx, capabilities) {
|
|
|
53686
53686
|
return jsonResult(result);
|
|
53687
53687
|
}));
|
|
53688
53688
|
server.registerTool("export_data", {
|
|
53689
|
-
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
|
|
53689
|
+
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').",
|
|
53690
53690
|
inputSchema: {
|
|
53691
53691
|
workflowId: exports_external.string().describe("The workflow ID"),
|
|
53692
53692
|
format: exports_external.enum(["csv", "json"]).optional().describe("Export format. Default 'csv'."),
|
|
@@ -53812,7 +53812,7 @@ function registerTools(server, ctx, capabilities) {
|
|
|
53812
53812
|
});
|
|
53813
53813
|
}));
|
|
53814
53814
|
server.registerTool("pause_workflow", {
|
|
53815
|
-
description: "Pause an ACTIVE workflow so it stops running on its schedule. Requires the workflow to be in ACTIVE state
|
|
53815
|
+
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.",
|
|
53816
53816
|
inputSchema: {
|
|
53817
53817
|
workflowId: exports_external.string().min(1).describe("The workflow ID to pause")
|
|
53818
53818
|
},
|
|
@@ -53826,17 +53826,17 @@ function registerTools(server, ctx, capabilities) {
|
|
|
53826
53826
|
});
|
|
53827
53827
|
}));
|
|
53828
53828
|
server.registerTool("update_workflow", {
|
|
53829
|
-
description: `Update a workflow's configuration. All fields are optional
|
|
53829
|
+
description: `Update a workflow's configuration. All fields are optional - only provided fields will be updated. Use this to change the name, URLs, extraction schema, entity, prompt, schedule, or other metadata.
|
|
53830
53830
|
|
|
53831
53831
|
` + `IMPORTANT: You cannot change a workflow's interval to or from REAL_TIME. Julie realtime workflows are architecturally different from scheduled workflows and must be created with create_realtime_monitor from the start. Existing workflows cannot be converted between these modes in place; do not delete and recreate a workflow as an update workaround.
|
|
53832
53832
|
|
|
53833
|
-
` + "ASSISTANT-OWNED INTENT: Use request_workflow_update
|
|
53833
|
+
` + "ASSISTANT-OWNED INTENT: Use request_workflow_update - not `userPrompt` - for agent-built workflow changes to extraction intent, navigation, pagination, data sourcing, repair, or generated scripts. `userPrompt` may be rejected with `SHELLY_INTENT_REQUIRES_EXTRACTION_SPEC` because those 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.",
|
|
53834
53834
|
inputSchema: strictSchema({
|
|
53835
53835
|
workflowId: exports_external.string().describe("The workflow ID to update"),
|
|
53836
53836
|
name: exports_external.string().optional().describe("New name for the workflow"),
|
|
53837
53837
|
urls: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string()).min(1)).optional().describe("New target URLs for the workflow (array of strings). Also accepts a single URL string."),
|
|
53838
53838
|
entity: exports_external.string().optional().describe("Entity name for extraction (e.g., 'Product', 'Job Posting')"),
|
|
53839
|
-
schema: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object(SchemaFieldShape).strict())).optional().describe("New extraction schema fields. This REPLACES the whole schema
|
|
53839
|
+
schema: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.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."),
|
|
53840
53840
|
description: exports_external.string().max(500).optional().describe("Workflow description (max 500 characters)"),
|
|
53841
53841
|
tags: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string())).optional().describe("Tags for organizing workflows"),
|
|
53842
53842
|
userPrompt: exports_external.string().optional().describe("Navigation prompt for agentic-navigation mode (10-5000 characters)"),
|
|
@@ -53858,7 +53858,7 @@ function registerTools(server, ctx, capabilities) {
|
|
|
53858
53858
|
"FOUR_WEEKS",
|
|
53859
53859
|
"MONTHLY",
|
|
53860
53860
|
"CUSTOM"
|
|
53861
|
-
]).optional().describe("How often the workflow should run. CUSTOM requires schedules. Note: REAL_TIME is NOT allowed here
|
|
53861
|
+
]).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."),
|
|
53862
53862
|
schedules: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string())).optional().describe("Cron expressions for CUSTOM update interval"),
|
|
53863
53863
|
timezone: exports_external.string().min(1).optional().describe("IANA timezone for cron schedules, for example America/New_York. A timezone-only update preserves existing cron expressions."),
|
|
53864
53864
|
location: exports_external.preprocess(coerceJson(), LocationSchema).optional().describe("Scraping location: {type:'auto'} or {type:'manual', isoCode:'US'}"),
|
|
@@ -54277,22 +54277,29 @@ function registerTools(server, ctx, capabilities) {
|
|
|
54277
54277
|
});
|
|
54278
54278
|
}));
|
|
54279
54279
|
server.registerTool("list_templates", {
|
|
54280
|
-
description: "List all templates in the current team. Templates define reusable configurations (prompt, schema, notifications).",
|
|
54280
|
+
description: "List all templates in the current team. Templates define reusable configurations (prompt, schema, validation rules, notifications, and frequency). Use this to find a matching template before creating a workflow. To instantiate a template, call create_workflow with its templateId, source URLs, and optional templateVersion - never copy the template prompt, entity, or schema into standalone creation.",
|
|
54281
54281
|
inputSchema: strictSchema({}),
|
|
54282
54282
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }
|
|
54283
54283
|
}, withErrorHandling("list_templates", async () => {
|
|
54284
54284
|
const templates = await ctx.client.template.list();
|
|
54285
|
-
return jsonResult({
|
|
54285
|
+
return jsonResult({
|
|
54286
|
+
templates,
|
|
54287
|
+
count: templates.length,
|
|
54288
|
+
instructions: "To instantiate a listed template, call create_workflow with the matching templateId, source URLs, and optional templateVersion. Do not copy the template prompt, entity, or schema into standalone creation."
|
|
54289
|
+
});
|
|
54286
54290
|
}));
|
|
54287
54291
|
server.registerTool("get_template", {
|
|
54288
|
-
description: "Get a template by ID, including all published versions.",
|
|
54292
|
+
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, and optional templateVersion. Never copy the returned prompt, entity, or schema into standalone creation - those values are inherited when templateId is passed.",
|
|
54289
54293
|
inputSchema: strictSchema({
|
|
54290
54294
|
templateId: exports_external.string().describe("The template ID")
|
|
54291
54295
|
}),
|
|
54292
54296
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }
|
|
54293
54297
|
}, withErrorHandling("get_template", async (args) => {
|
|
54294
54298
|
const template = await ctx.client.template.get(args.templateId);
|
|
54295
|
-
return jsonResult({
|
|
54299
|
+
return jsonResult({
|
|
54300
|
+
template,
|
|
54301
|
+
instructions: `To instantiate this template, call create_workflow with templateId "${args.templateId}", source URLs, and optional templateVersion. Do not copy this template's prompt, entity, or schema into standalone creation.`
|
|
54302
|
+
});
|
|
54296
54303
|
}));
|
|
54297
54304
|
server.registerTool("create_template", {
|
|
54298
54305
|
description: "Create a new template. After creation, use create_template_version to publish a version with prompt, schema, and notifications.",
|
|
@@ -54315,7 +54322,7 @@ function registerTools(server, ctx, capabilities) {
|
|
|
54315
54322
|
server.registerTool("update_template", {
|
|
54316
54323
|
description: `Update a template's name or description ONLY. At least one of the two must be provided.
|
|
54317
54324
|
|
|
54318
|
-
` + "This tool CANNOT change what linked workflows inherit
|
|
54325
|
+
` + "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.",
|
|
54319
54326
|
inputSchema: strictSchema({
|
|
54320
54327
|
templateId: exports_external.string().describe("The template ID to update"),
|
|
54321
54328
|
name: exports_external.preprocess(coerceNull(), exports_external.string().optional()).optional().describe("New template name"),
|
|
@@ -54371,7 +54378,7 @@ function registerTools(server, ctx, capabilities) {
|
|
|
54371
54378
|
}))).optional().describe("Predefined categories for a CLASSIFICATION field ({title, definition}[]). Required for fieldType=CLASSIFICATION; omitted otherwise.")
|
|
54372
54379
|
};
|
|
54373
54380
|
server.registerTool("create_template_version", {
|
|
54374
|
-
description: `Publish a new version of a template. Versions capture the full workflow config: prompt, schema, and notifications. All fields are optional
|
|
54381
|
+
description: `Publish a new version of a template. Versions capture the full workflow config: prompt, schema, and notifications. All fields are optional - include only what this version should set.
|
|
54375
54382
|
|
|
54376
54383
|
` + "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.",
|
|
54377
54384
|
inputSchema: strictSchema({
|
|
@@ -54380,7 +54387,7 @@ function registerTools(server, ctx, capabilities) {
|
|
|
54380
54387
|
schemaId: exports_external.string().optional().describe("Existing schema ID to reference (mutually exclusive with schemaFields)"),
|
|
54381
54388
|
schemaFields: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object(TemplateSchemaFieldShape).strict())).optional().describe("Inline schema fields to create a new schema (mutually exclusive with schemaId)"),
|
|
54382
54389
|
schemaEntity: exports_external.string().optional().describe("Entity name for the inline schema"),
|
|
54383
|
-
schemaValidationRules: exports_external.preprocess(coerceJson(),
|
|
54390
|
+
schemaValidationRules: exports_external.preprocess(coerceJson(), 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. Rule metadata requires editedBy (default|llm|agent|ops|user) and an ISO-8601 editedAt timestamp. Example: { "price": { "kind": "NUMBER", "minimum": { "value": 0, "editedBy": "user", "editedAt": "2026-01-01T00:00:00.000Z" } } }. Not inherited from prior versions - omitting this on a new version drops any rules the previous version had.'),
|
|
54384
54391
|
notifications: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object({
|
|
54385
54392
|
eventType: exports_external.string().describe("Notification event type"),
|
|
54386
54393
|
eventConfiguration: exports_external.preprocess(coerceJson(), exports_external.record(exports_external.string(), exports_external.unknown())).optional(),
|
|
@@ -54461,7 +54468,7 @@ function registerTools(server, ctx, capabilities) {
|
|
|
54461
54468
|
return jsonResult({ schemas: schemas4, count: schemas4.length });
|
|
54462
54469
|
}));
|
|
54463
54470
|
server.registerTool("link_template_to_workflows", {
|
|
54464
|
-
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
|
|
54471
|
+
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.",
|
|
54465
54472
|
inputSchema: strictSchema({
|
|
54466
54473
|
templateId: exports_external.string().describe("The template ID to link workflows to"),
|
|
54467
54474
|
workflowIds: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string()).min(1)).describe("Workflow IDs to link to the template (array of strings). Also accepts a single ID string."),
|
|
@@ -54478,7 +54485,7 @@ function registerTools(server, ctx, capabilities) {
|
|
|
54478
54485
|
return jsonResult({
|
|
54479
54486
|
success: false,
|
|
54480
54487
|
conflicts: result.conflicts,
|
|
54481
|
-
message: `Cannot link
|
|
54488
|
+
message: `Cannot link - ${result.conflicts.length} workflow(s) are already linked to another template: ${summary}. ` + "Re-run with force: true to move them to this template."
|
|
54482
54489
|
});
|
|
54483
54490
|
}
|
|
54484
54491
|
return jsonResult({
|
|
@@ -54538,7 +54545,7 @@ function registerTools(server, ctx, capabilities) {
|
|
|
54538
54545
|
});
|
|
54539
54546
|
}));
|
|
54540
54547
|
}
|
|
54541
|
-
var SchemaFieldShape, LocationSchema, MonitoringValueOperators, MonitoringValuelessOperators, MonitoringConditionOperatorSchema, MonitoringConditionSchema, MonitoringSchema, RESUMABLE_ASSISTANT_STATUSES, ACTIVE_ASSISTANT_STATUSES, IDLE_ASSISTANT_STATUSES, CLOSED_ASSISTANT_STATUSES, DASHBOARD_BASE_URL = "https://www.kadoa.com", WORKFLOW_AUDIT_WATCHED_KEYS;
|
|
54548
|
+
var SchemaFieldShape, SchemaValidationAttributionShape, SchemaValidationPresenceRule, SchemaValidationUniquenessRule, SchemaValidationStringLengthRule, SchemaValidationStringFormatRule, SchemaValidationFieldRulesSchema, SchemaValidationRulesSchema, LocationSchema, MonitoringValueOperators, MonitoringValuelessOperators, MonitoringConditionOperatorSchema, MonitoringConditionSchema, MonitoringSchema, RESUMABLE_ASSISTANT_STATUSES, ACTIVE_ASSISTANT_STATUSES, IDLE_ASSISTANT_STATUSES, CLOSED_ASSISTANT_STATUSES, DASHBOARD_BASE_URL = "https://www.kadoa.com", WORKFLOW_AUDIT_WATCHED_KEYS;
|
|
54542
54549
|
var init_tools = __esm(() => {
|
|
54543
54550
|
init_dist2();
|
|
54544
54551
|
init_zod();
|
|
@@ -54548,8 +54555,105 @@ var init_tools = __esm(() => {
|
|
|
54548
54555
|
description: exports_external.string().optional().describe("What this field contains"),
|
|
54549
54556
|
example: exports_external.string().describe("Example value"),
|
|
54550
54557
|
dataType: exports_external.enum(["STRING", "NUMBER", "BOOLEAN", "DATE", "DATETIME", "MONEY", "IMAGE", "LINK", "OBJECT", "ARRAY"]).optional().describe("Data type for the field"),
|
|
54551
|
-
isKey: exports_external.preprocess(coerceBoolean(), exports_external.boolean()).optional().describe("Marks this field as a key field
|
|
54558
|
+
isKey: exports_external.preprocess(coerceBoolean(), exports_external.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).")
|
|
54552
54559
|
};
|
|
54560
|
+
SchemaValidationAttributionShape = {
|
|
54561
|
+
editedBy: exports_external.enum(["default", "llm", "agent", "ops", "user"]).describe("Who last edited this rule: default, llm, agent, ops, or user"),
|
|
54562
|
+
editedByLabel: exports_external.string().optional().describe("Optional editor identifier"),
|
|
54563
|
+
editedAt: exports_external.string().datetime().describe("ISO-8601 timestamp when this rule was last edited")
|
|
54564
|
+
};
|
|
54565
|
+
SchemaValidationPresenceRule = exports_external.object({
|
|
54566
|
+
target: exports_external.number().int().min(0).max(100).step(20).describe("Expected percentage of rows with a value"),
|
|
54567
|
+
...SchemaValidationAttributionShape
|
|
54568
|
+
}).strict();
|
|
54569
|
+
SchemaValidationUniquenessRule = exports_external.object({
|
|
54570
|
+
target: exports_external.number().int().min(0).max(100).step(20).describe("Expected percentage of rows with a unique value"),
|
|
54571
|
+
...SchemaValidationAttributionShape
|
|
54572
|
+
}).strict();
|
|
54573
|
+
SchemaValidationStringLengthRule = exports_external.object({
|
|
54574
|
+
value: exports_external.number().int().min(0).describe("String length bound"),
|
|
54575
|
+
...SchemaValidationAttributionShape
|
|
54576
|
+
}).strict();
|
|
54577
|
+
SchemaValidationStringFormatRule = exports_external.discriminatedUnion("kind", [
|
|
54578
|
+
exports_external.object({
|
|
54579
|
+
kind: exports_external.literal("FREE_TEXT"),
|
|
54580
|
+
charset: exports_external.discriminatedUnion("kind", [
|
|
54581
|
+
exports_external.object({
|
|
54582
|
+
kind: exports_external.literal("PRESET"),
|
|
54583
|
+
preset: exports_external.enum(["natural_language", "alphanumeric", "alpha"])
|
|
54584
|
+
}).strict()
|
|
54585
|
+
]),
|
|
54586
|
+
...SchemaValidationAttributionShape
|
|
54587
|
+
}).strict(),
|
|
54588
|
+
exports_external.object({
|
|
54589
|
+
kind: exports_external.literal("FORMAT"),
|
|
54590
|
+
source: exports_external.discriminatedUnion("kind", [
|
|
54591
|
+
exports_external.object({
|
|
54592
|
+
kind: exports_external.literal("PRESET"),
|
|
54593
|
+
preset: exports_external.enum(["url", "email", "phone", "date", "datetime", "time", "uuid", "slug"])
|
|
54594
|
+
}).strict(),
|
|
54595
|
+
exports_external.object({
|
|
54596
|
+
kind: exports_external.literal("CUSTOM"),
|
|
54597
|
+
pattern: exports_external.string().min(1).describe("Regular expression pattern")
|
|
54598
|
+
}).strict()
|
|
54599
|
+
]),
|
|
54600
|
+
...SchemaValidationAttributionShape
|
|
54601
|
+
}).strict(),
|
|
54602
|
+
exports_external.object({
|
|
54603
|
+
kind: exports_external.literal("LIST"),
|
|
54604
|
+
source: exports_external.discriminatedUnion("kind", [
|
|
54605
|
+
exports_external.object({
|
|
54606
|
+
kind: exports_external.literal("PRESET"),
|
|
54607
|
+
preset: exports_external.enum(["language2", "country2", "country3", "currency3", "month3", "usState2"])
|
|
54608
|
+
}).strict(),
|
|
54609
|
+
exports_external.object({
|
|
54610
|
+
kind: exports_external.literal("CUSTOM"),
|
|
54611
|
+
values: exports_external.array(exports_external.string().min(1)).min(1).describe("Allowed string values")
|
|
54612
|
+
}).strict()
|
|
54613
|
+
]),
|
|
54614
|
+
...SchemaValidationAttributionShape
|
|
54615
|
+
}).strict()
|
|
54616
|
+
]);
|
|
54617
|
+
SchemaValidationFieldRulesSchema = exports_external.lazy(() => exports_external.discriminatedUnion("kind", [
|
|
54618
|
+
exports_external.object({
|
|
54619
|
+
kind: exports_external.literal("STRING"),
|
|
54620
|
+
presence: SchemaValidationPresenceRule.optional(),
|
|
54621
|
+
uniqueness: SchemaValidationUniquenessRule.optional(),
|
|
54622
|
+
minLength: SchemaValidationStringLengthRule.optional(),
|
|
54623
|
+
maxLength: SchemaValidationStringLengthRule.optional(),
|
|
54624
|
+
minHtmlElements: SchemaValidationStringLengthRule.optional(),
|
|
54625
|
+
maxHtmlElements: SchemaValidationStringLengthRule.optional(),
|
|
54626
|
+
format: SchemaValidationStringFormatRule.optional()
|
|
54627
|
+
}).strict(),
|
|
54628
|
+
exports_external.object({
|
|
54629
|
+
kind: exports_external.literal("NUMBER"),
|
|
54630
|
+
presence: SchemaValidationPresenceRule.optional(),
|
|
54631
|
+
uniqueness: SchemaValidationUniquenessRule.optional(),
|
|
54632
|
+
minimum: exports_external.object({ value: exports_external.number().describe("Minimum numeric value"), ...SchemaValidationAttributionShape }).strict().optional(),
|
|
54633
|
+
maximum: exports_external.object({ value: exports_external.number().describe("Maximum numeric value"), ...SchemaValidationAttributionShape }).strict().optional(),
|
|
54634
|
+
maxDecimalPlaces: exports_external.object({ value: exports_external.number().int().min(0).max(16), ...SchemaValidationAttributionShape }).strict().optional()
|
|
54635
|
+
}).strict(),
|
|
54636
|
+
exports_external.object({
|
|
54637
|
+
kind: exports_external.literal("OTHER"),
|
|
54638
|
+
presence: SchemaValidationPresenceRule.optional(),
|
|
54639
|
+
uniqueness: SchemaValidationUniquenessRule.optional()
|
|
54640
|
+
}).strict(),
|
|
54641
|
+
exports_external.object({
|
|
54642
|
+
kind: exports_external.literal("OBJECT"),
|
|
54643
|
+
presence: SchemaValidationPresenceRule.optional(),
|
|
54644
|
+
uniqueness: SchemaValidationUniquenessRule.optional(),
|
|
54645
|
+
properties: exports_external.record(exports_external.string(), SchemaValidationFieldRulesSchema)
|
|
54646
|
+
}).strict(),
|
|
54647
|
+
exports_external.object({
|
|
54648
|
+
kind: exports_external.literal("ARRAY"),
|
|
54649
|
+
presence: SchemaValidationPresenceRule.optional(),
|
|
54650
|
+
uniqueness: SchemaValidationUniquenessRule.optional(),
|
|
54651
|
+
minItems: exports_external.object({ value: exports_external.number().int().min(0), ...SchemaValidationAttributionShape }).strict().optional(),
|
|
54652
|
+
maxItems: exports_external.object({ value: exports_external.number().int().min(0), ...SchemaValidationAttributionShape }).strict().optional(),
|
|
54653
|
+
items: SchemaValidationFieldRulesSchema.optional()
|
|
54654
|
+
}).strict()
|
|
54655
|
+
]));
|
|
54656
|
+
SchemaValidationRulesSchema = exports_external.record(exports_external.string(), SchemaValidationFieldRulesSchema);
|
|
54553
54657
|
LocationSchema = exports_external.object({
|
|
54554
54658
|
type: exports_external.enum(["auto", "manual"]),
|
|
54555
54659
|
isoCode: exports_external.string().trim().min(2).optional()
|
|
@@ -54638,7 +54742,7 @@ var package_default;
|
|
|
54638
54742
|
var init_package = __esm(() => {
|
|
54639
54743
|
package_default = {
|
|
54640
54744
|
name: "@kadoa/mcp",
|
|
54641
|
-
version: "0.5.
|
|
54745
|
+
version: "0.5.21",
|
|
54642
54746
|
description: "Kadoa MCP Server — manage workflows from Claude Desktop, Cursor, and other MCP clients",
|
|
54643
54747
|
type: "module",
|
|
54644
54748
|
main: "dist/index.js",
|
|
@@ -60241,21 +60345,22 @@ async function createServer(auth, options) {
|
|
|
60241
60345
|
const server = new McpServer({ name: "kadoa", version: package_default.version }, {
|
|
60242
60346
|
instructions: [
|
|
60243
60347
|
"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.",
|
|
60244
|
-
"Do NOT create multiple workflows for different data points on the same page
|
|
60348
|
+
"Do NOT create multiple workflows for different data points on the same page - instead, define one workflow with a multi-field schema covering everything needed.",
|
|
60245
60349
|
"",
|
|
60246
60350
|
"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.",
|
|
60247
60351
|
"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').",
|
|
60248
60352
|
"",
|
|
60249
|
-
"Workflow lifecycle: create_workflow \u2192 get_workflow (check status) \u2192 fetch_data (get results). Workflows run asynchronously
|
|
60353
|
+
"Workflow lifecycle: create_workflow \u2192 get_workflow (check status) \u2192 fetch_data (get results). Workflows run asynchronously - never poll or sleep-wait.",
|
|
60250
60354
|
"",
|
|
60251
60355
|
"Use create_realtime_monitor only when the user wants continuous Julie 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.",
|
|
60252
60356
|
"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.",
|
|
60357
|
+
"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.",
|
|
60253
60358
|
"Use list_changes and get_change to retrieve detected diffs from realtime monitoring workflows.",
|
|
60254
60359
|
"",
|
|
60255
60360
|
"Schema tips: Use descriptive field names and examples. Group related data under one entity.",
|
|
60256
|
-
"The AI agent uses the schema + prompt to understand what to extract
|
|
60361
|
+
"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.",
|
|
60257
60362
|
"",
|
|
60258
|
-
"Templates enforce their schema: a workflow created from a template (create_workflow with templateId) or linked to one (link_template_to_workflows) produces output whose field names conform to the template's declared schema. Use templates when you need many workflows to return a consistent, canonical set of fields
|
|
60363
|
+
"Templates enforce their schema: a workflow created from a template (create_workflow with templateId) or linked to one (link_template_to_workflows) produces output whose field names conform to the template's declared schema. Use templates when you need many workflows to return a consistent, canonical set of fields - do NOT assume the extractor will drift field names away from a template's schema."
|
|
60259
60364
|
].join(`
|
|
60260
60365
|
`)
|
|
60261
60366
|
});
|