@kadoa/mcp 0.5.5 → 0.5.8-rc.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/README.md +153 -12
- package/dist/index.js +324 -63
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -85,6 +85,17 @@ Claude checks the run status with get_workflow, then calls fetch_data
|
|
|
85
85
|
to retrieve the extracted records and display them as a table.
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
### Create a workflow from a template
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
> You: Use my "Product Scraper" template to scrape https://example-shop.com.
|
|
92
|
+
|
|
93
|
+
Claude calls list_templates to find the matching template, then
|
|
94
|
+
create_workflow with `templateId` and `urls` only — the prompt and
|
|
95
|
+
schema are inherited from the template version. Returns the workflow
|
|
96
|
+
ID for follow-up with get_workflow or fetch_data.
|
|
97
|
+
```
|
|
98
|
+
|
|
88
99
|
### Update a workflow and re-run
|
|
89
100
|
|
|
90
101
|
```
|
|
@@ -126,25 +137,90 @@ delete_workflow for each, confirming before proceeding.
|
|
|
126
137
|
- Restart your MCP client
|
|
127
138
|
- Re-authenticate via OAuth if prompted
|
|
128
139
|
|
|
129
|
-
##
|
|
140
|
+
## Releases
|
|
130
141
|
|
|
131
|
-
|
|
142
|
+
This repo ships two surfaces, and a "release" usually touches both:
|
|
132
143
|
|
|
133
|
-
**
|
|
144
|
+
- **npm package `@kadoa/mcp`** — stdio CLI users install locally (`npx @kadoa/mcp`). Cut by [Release Please](https://github.com/googleapis/release-please) from Conventional Commits on `main`.
|
|
145
|
+
- **Hosted server at `mcp.kadoa.com`** — Docker container on GKE, built and deployed from the [`kadoa-backend`](https://github.com/kadoa-org/kadoa-backend) monorepo, which pins a specific `@kadoa/mcp` version.
|
|
134
146
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
147
|
+
Relevant files in `kadoa-backend`:
|
|
148
|
+
- `infra/docker/mcp/Dockerfile.mcp-server` — image definition
|
|
149
|
+
- `infra/docker/mcp/package.json` — pinned `@kadoa/mcp` version
|
|
150
|
+
- `infra/cdk8s/mcp/charts/Server.ts` — k8s manifest source (prod chart only; the test deployment is provisioned ad hoc — see below)
|
|
151
|
+
|
|
152
|
+
### Production release
|
|
153
|
+
|
|
154
|
+
1. Merge PRs to `main` using Conventional Commits (`feat:`, `fix:`, etc.). Release Please opens/maintains a `chore(main): release mcp x.y.z` PR.
|
|
155
|
+
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).
|
|
156
|
+
3. In `kadoa-backend`, bump `infra/docker/mcp/package.json` `@kadoa/mcp` to the new version, run `bun install` to refresh `bun.lock`, open a PR.
|
|
157
|
+
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).
|
|
158
|
+
5. Trigger the **Deploy to Production** workflow ([`deploy-prod.yml`](https://github.com/kadoa-org/kadoa-backend/actions/workflows/deploy-prod.yml)) with:
|
|
139
159
|
- **Target cluster:** `gcp`
|
|
140
160
|
- **Deployment scope:** `mcp`
|
|
141
|
-
- **Image tag:** the tag from step
|
|
161
|
+
- **Image tag:** the tag from step 4
|
|
142
162
|
- **Method:** `kubectl`
|
|
143
163
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
- `
|
|
147
|
-
|
|
164
|
+
### RC / test release
|
|
165
|
+
|
|
166
|
+
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.
|
|
167
|
+
|
|
168
|
+
There are two ways to consume an RC:
|
|
169
|
+
|
|
170
|
+
- **Local stdio**: `npx @kadoa/mcp@rc` — installs from the `rc` dist-tag on npm. Good for quick sanity checks where the bug doesn't depend on the hosted OAuth flow.
|
|
171
|
+
- **Hosted `mcp-server-test`**: a separate k8s deployment alongside prod in the same GKE cluster. Required when you need to validate the OAuth callback, Redis token store, multi-tenant session behavior, etc.
|
|
172
|
+
|
|
173
|
+
#### Publish an RC to npm
|
|
174
|
+
|
|
175
|
+
Manual — Release Please only cuts stable versions today.
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
# from a branch on kadoa-mcp
|
|
179
|
+
bun install && bun run build
|
|
180
|
+
# bump version to a prerelease, e.g. 0.5.7-rc.1
|
|
181
|
+
npm version 0.5.7-rc.1 --no-git-tag-version
|
|
182
|
+
npm publish --tag rc --access public
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Verify: `npm view @kadoa/mcp dist-tags`. The `rc` tag should now point to your version. Existing tags today: `latest`, `rc`, `next`.
|
|
186
|
+
|
|
187
|
+
Promote later with: `npm dist-tag add @kadoa/mcp@0.5.7 latest` (run **after** the matching stable version has been published the normal way).
|
|
188
|
+
|
|
189
|
+
#### Deploy the RC to `mcp-server-test`
|
|
190
|
+
|
|
191
|
+
1. In `kadoa-backend`, set `infra/docker/mcp/package.json` `@kadoa/mcp` to the RC version, `bun install`, push to a branch and merge to `main`. CI builds the `mcp-server:<IMAGE_TAG>` image as usual.
|
|
192
|
+
2. Update the test deployment to the new image:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
gcloud container clusters get-credentials kadoa-prod \
|
|
196
|
+
--region=europe-west3 --project=oceanic-base-310208
|
|
197
|
+
|
|
198
|
+
kubectl set image deployment/mcp-server-test \
|
|
199
|
+
mcp-server=europe-west3-docker.pkg.dev/oceanic-base-310208/kadoa-artifacts/mcp-server:<IMAGE_TAG>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
3. Port-forward to reach it (the test deployment has **no public ingress** by design):
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
kubectl port-forward svc/mcp-server-test 3000:3000
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
4. Point your MCP client at `http://localhost:3000/mcp` and exercise the change.
|
|
209
|
+
|
|
210
|
+
**Auth gotcha:** `auth.kadoa.com` enforces an `ALLOWED_CALLBACK_ORIGINS` allowlist (`app.kadoa.com`, `mcp.kadoa.com`, `kadoa.com`) plus a loopback bypass for `127.0.0.1` when `NODE_ENV !== production`. `localhost` is **not** treated the same as `127.0.0.1` by that check. The test pod is configured with `MCP_SERVER_URL=http://localhost:3000` so the port-forwarded session resolves correctly. If you change the local port, update the deployment env var too:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
kubectl set env deployment/mcp-server-test MCP_SERVER_URL=http://localhost:<PORT>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
#### Promotion checklist
|
|
217
|
+
|
|
218
|
+
Before flipping `latest` / deploying to prod:
|
|
219
|
+
|
|
220
|
+
- [ ] RC consumed locally (`npx @kadoa/mcp@rc`) — stdio tools work
|
|
221
|
+
- [ ] RC consumed via `mcp-server-test` — OAuth login succeeds end-to-end
|
|
222
|
+
- [ ] No new errors in the test pod's logs (`kubectl logs deployment/mcp-server-test -f`)
|
|
223
|
+
- [ ] Release Please PR open with the stable version
|
|
148
224
|
|
|
149
225
|
## Development
|
|
150
226
|
|
|
@@ -169,6 +245,71 @@ KADOA_PUBLIC_API_URI=http://localhost:12380 bun run dev
|
|
|
169
245
|
|
|
170
246
|
The server starts in HTTP mode. You authenticate via OAuth the same way as with the remote server.
|
|
171
247
|
|
|
248
|
+
## Contributing
|
|
249
|
+
|
|
250
|
+
A typical feature touches three repos — **`kadoa-backend` → `kadoa-sdks` → `kadoa-mcp`**. The same surgery on three layers; same operation, different audience above it.
|
|
251
|
+
|
|
252
|
+
### Architecture
|
|
253
|
+
|
|
254
|
+
| Layer | Repo / path | Role |
|
|
255
|
+
|---|---|---|
|
|
256
|
+
| Backend endpoint | `kadoa-backend/` | The HTTP API. Source of truth; the OpenAPI spec is generated from here. |
|
|
257
|
+
| SDK low-level | `kadoa-sdks/sdks/node/src/generated/api/*` | Auto-generated axios client. Raw HTTP, typed from the OpenAPI spec. **Do not hand-edit.** |
|
|
258
|
+
| SDK domain (high-level) | `kadoa-sdks/sdks/node/src/domains/<thing>/<thing>.service.ts` | Thin domain wrapper over the generated client. `client.<thing>.<action>(...)`. This is what callers use. |
|
|
259
|
+
| CLI | `kadoa-cli/src/commands/<thing>.ts` | Commander action: flag parsing, spinner, table/json output. Calls SDK domain. |
|
|
260
|
+
| MCP (this repo) | `kadoa-mcp/src/tools.ts` | Zod input schema + tool description tuned for LLMs. Calls SDK domain. |
|
|
261
|
+
|
|
262
|
+
Mental model:
|
|
263
|
+
- **SDK** = pure function over HTTP.
|
|
264
|
+
- **CLI** = SDK + human UX (flags, table output).
|
|
265
|
+
- **MCP** = SDK + LLM UX (zod schema, prose description).
|
|
266
|
+
|
|
267
|
+
CLI and MCP both call **identical** SDK domain methods. A new feature added to the SDK is picked up by both clients for free — as long as we keep the layering honest.
|
|
268
|
+
|
|
269
|
+
### Adding a new feature
|
|
270
|
+
|
|
271
|
+
1. **Backend** (`kadoa-backend`)
|
|
272
|
+
- Add or modify the HTTP endpoint.
|
|
273
|
+
- Make sure the OpenAPI spec reflects the change (paths, request/response schemas).
|
|
274
|
+
|
|
275
|
+
2. **SDK** (`kadoa-sdks`) — always do this **before** MCP/CLI work (the SDK-first rule). No bandaid `axios` calls in MCP.
|
|
276
|
+
- Regenerate the low-level client:
|
|
277
|
+
```bash
|
|
278
|
+
bun kadoa-codegen fetch-spec -e https://api.kadoa.com/openapi -f
|
|
279
|
+
bun kadoa-codegen generate -e https://api.kadoa.com/openapi --fetch-latest -f
|
|
280
|
+
```
|
|
281
|
+
(Swap the endpoint for `http://localhost:12380/openapi` when developing against a local backend.)
|
|
282
|
+
- Add or extend the domain service in `sdks/node/src/domains/<thing>/<thing>.service.ts`. Keep it typed, no `any`.
|
|
283
|
+
- Commit `specs/` separately from generated/domain code so the API diff is reviewable.
|
|
284
|
+
- Open a PR. On merge, a new `@kadoa/node-sdk` version is released.
|
|
285
|
+
|
|
286
|
+
3. **MCP** (this repo)
|
|
287
|
+
- Bump `@kadoa/node-sdk` in `package.json` to the new version, `bun install`.
|
|
288
|
+
- Add the tool in `src/tools.ts`:
|
|
289
|
+
- Zod input schema (use coercion helpers in `src/coercion.ts` where needed — LLM clients sometimes send strings for numbers/bools).
|
|
290
|
+
- Description string written **for the LLM**: when to call it, what inputs mean, what the response shape is.
|
|
291
|
+
- Implementation calls `ctx.client.<thing>.<action>(...)` — never raw HTTP.
|
|
292
|
+
- Add a unit test under `tests/unit/`.
|
|
293
|
+
- Use a Conventional Commit message (`feat(mcp): ...`, `fix(mcp): ...`). Release Please will cut the next version on merge to `main`.
|
|
294
|
+
|
|
295
|
+
4. **CLI** (`kadoa-cli`), if the feature is user-facing on the command line: mirror the MCP step in `src/commands/<thing>.ts`. Same SDK call, different UX.
|
|
296
|
+
|
|
297
|
+
### Anti-patterns
|
|
298
|
+
|
|
299
|
+
- ❌ Raw `axios` calls or hand-rolled HTTP in `src/tools.ts`. Always go through `ctx.client.<thing>`.
|
|
300
|
+
- ❌ Putting tool-shaped logic (input validation, response shaping) in the MCP layer when the SDK should expose it. If two clients would need the same logic, it belongs in the SDK.
|
|
301
|
+
- ❌ Editing files under `sdks/node/src/generated/` by hand. They get overwritten by `kadoa-codegen generate`.
|
|
302
|
+
- ❌ Bundling a spec update and an SDK feature in the same commit — keep `specs/` PRs separate from domain-method PRs for clean review.
|
|
303
|
+
|
|
304
|
+
### Where to find things in this repo
|
|
305
|
+
|
|
306
|
+
- `src/tools.ts` — every MCP tool. Single file by design; search for the tool name.
|
|
307
|
+
- `src/auth.ts` — OAuth flow, JWT verification, team selection.
|
|
308
|
+
- `src/client.ts` — wires `@kadoa/node-sdk` per request, injects bearer token.
|
|
309
|
+
- `src/http.ts` — Express server, transport adapter, session handling.
|
|
310
|
+
- `src/redis-store.ts` — token store for the hosted server.
|
|
311
|
+
- `tests/unit/` — bun tests; run with `bun run test`.
|
|
312
|
+
|
|
172
313
|
## License
|
|
173
314
|
|
|
174
315
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -14552,7 +14552,7 @@ function finalize(ctx, schema) {
|
|
|
14552
14552
|
result.$schema = "http://json-schema.org/draft-07/schema#";
|
|
14553
14553
|
} else if (ctx.target === "draft-04") {
|
|
14554
14554
|
result.$schema = "http://json-schema.org/draft-04/schema#";
|
|
14555
|
-
} else if (ctx.target === "openapi-3.0") {}
|
|
14555
|
+
} else if (ctx.target === "openapi-3.0") {}
|
|
14556
14556
|
if (ctx.external?.uri) {
|
|
14557
14557
|
const id = ctx.external.registry.get(schema)?.id;
|
|
14558
14558
|
if (!id)
|
|
@@ -14817,7 +14817,7 @@ var formatMap, stringProcessor = (schema, ctx, _json, _params) => {
|
|
|
14817
14817
|
if (val === undefined) {
|
|
14818
14818
|
if (ctx.unrepresentable === "throw") {
|
|
14819
14819
|
throw new Error("Literal `undefined` cannot be represented in JSON Schema");
|
|
14820
|
-
}
|
|
14820
|
+
}
|
|
14821
14821
|
} else if (typeof val === "bigint") {
|
|
14822
14822
|
if (ctx.unrepresentable === "throw") {
|
|
14823
14823
|
throw new Error("BigInt literals cannot be represented in JSON Schema");
|
|
@@ -44451,6 +44451,9 @@ function isNewerVersion(version1, version2) {
|
|
|
44451
44451
|
}
|
|
44452
44452
|
return false;
|
|
44453
44453
|
}
|
|
44454
|
+
async function resolveBearerToken(provider) {
|
|
44455
|
+
return typeof provider === "function" ? await provider() : provider;
|
|
44456
|
+
}
|
|
44454
44457
|
function createValidationDomain(core2, rules) {
|
|
44455
44458
|
return {
|
|
44456
44459
|
rules,
|
|
@@ -44498,9 +44501,9 @@ function createClientDomains(params) {
|
|
|
44498
44501
|
const dataFetcherService = new DataFetcherService(client.apis.workflows);
|
|
44499
44502
|
const channelsService = new NotificationChannelsService(client.apis.notifications, userService);
|
|
44500
44503
|
const settingsService = new NotificationSettingsService(client.apis.notifications);
|
|
44501
|
-
const workflowsCoreService = new WorkflowsCoreService(client.apis.workflows);
|
|
44502
44504
|
const schemasService = new SchemasService(client);
|
|
44503
44505
|
const templatesService = new TemplatesService(client);
|
|
44506
|
+
const workflowsCoreService = new WorkflowsCoreService(client.apis.workflows, templatesService);
|
|
44504
44507
|
const variablesService = new VariablesService(client);
|
|
44505
44508
|
const channelSetupService = new NotificationSetupService(channelsService, settingsService);
|
|
44506
44509
|
const coreService = new ValidationCoreService(client);
|
|
@@ -48714,7 +48717,7 @@ var import_debug, __require2, ChangeDifferenceType, KadoaErrorCode, _KadoaSdkExc
|
|
|
48714
48717
|
}));
|
|
48715
48718
|
return channels;
|
|
48716
48719
|
}
|
|
48717
|
-
}, PUBLIC_API_URI, WSS_API_URI, REALTIME_API_URI, SDK_VERSION = "0.
|
|
48720
|
+
}, PUBLIC_API_URI, WSS_API_URI, REALTIME_API_URI, SDK_VERSION = "0.34.0", SDK_NAME = "kadoa-node-sdk", SDK_LANGUAGE = "node", debug6, isDrainControlMessage = (message) => message.type === "control.draining", isRealtimeEvent = (message) => message.type !== "heartbeat" && message.type !== "control.draining", _Realtime = class _Realtime2 {
|
|
48718
48721
|
constructor(config2) {
|
|
48719
48722
|
this.drainingSockets = /* @__PURE__ */ new Set;
|
|
48720
48723
|
this.lastHeartbeat = Date.now();
|
|
@@ -49117,6 +49120,54 @@ var import_debug, __require2, ChangeDifferenceType, KadoaErrorCode, _KadoaSdkExc
|
|
|
49117
49120
|
}
|
|
49118
49121
|
return version2;
|
|
49119
49122
|
}
|
|
49123
|
+
async linkWorkflows(templateId, body) {
|
|
49124
|
+
debug7("Linking %d workflow(s) to template: %s", body.workflowIds?.length ?? 0, templateId);
|
|
49125
|
+
try {
|
|
49126
|
+
const response = await this.templatesApi.v4TemplatesTemplateIdLinkPost({
|
|
49127
|
+
templateId,
|
|
49128
|
+
linkWorkflowsBody: body
|
|
49129
|
+
});
|
|
49130
|
+
return response.data;
|
|
49131
|
+
} catch (error48) {
|
|
49132
|
+
if (KadoaHttpException.isInstance(error48)) {
|
|
49133
|
+
const httpError = error48;
|
|
49134
|
+
const conflicts = httpError.httpStatus === 409 ? httpError.responseBody?.conflicts : undefined;
|
|
49135
|
+
if (conflicts) {
|
|
49136
|
+
return {
|
|
49137
|
+
error: true,
|
|
49138
|
+
success: false,
|
|
49139
|
+
linkedCount: 0,
|
|
49140
|
+
workflowIds: [],
|
|
49141
|
+
conflicts
|
|
49142
|
+
};
|
|
49143
|
+
}
|
|
49144
|
+
}
|
|
49145
|
+
throw error48;
|
|
49146
|
+
}
|
|
49147
|
+
}
|
|
49148
|
+
async getLinkedWorkflows(templateId) {
|
|
49149
|
+
debug7("Listing workflows linked to template: %s", templateId);
|
|
49150
|
+
const response = await this.templatesApi.v4TemplatesTemplateIdWorkflowsGet({
|
|
49151
|
+
templateId
|
|
49152
|
+
});
|
|
49153
|
+
return response.data.data ?? [];
|
|
49154
|
+
}
|
|
49155
|
+
async applyVersion(templateId, body) {
|
|
49156
|
+
debug7("Applying template %s version %d to %d workflow(s)", templateId, body.targetVersion, body.workflowIds?.length ?? 0);
|
|
49157
|
+
const response = await this.templatesApi.v4TemplatesTemplateIdApplyPost({
|
|
49158
|
+
templateId,
|
|
49159
|
+
applyTemplateUpdateBody: body
|
|
49160
|
+
});
|
|
49161
|
+
return response.data;
|
|
49162
|
+
}
|
|
49163
|
+
async unlinkWorkflows(templateId, body) {
|
|
49164
|
+
debug7("Unlinking %d workflow(s) from template: %s", body.workflowIds?.length ?? 0, templateId);
|
|
49165
|
+
const response = await this.templatesApi.v4TemplatesTemplateIdUnlinkPost({
|
|
49166
|
+
templateId,
|
|
49167
|
+
unlinkWorkflowsBody: body
|
|
49168
|
+
});
|
|
49169
|
+
return response.data;
|
|
49170
|
+
}
|
|
49120
49171
|
async listSchemas(templateId) {
|
|
49121
49172
|
debug7("Listing schemas for template: %s", templateId);
|
|
49122
49173
|
const response = await this.templatesApi.v4TemplatesTemplateIdSchemasGet({
|
|
@@ -49146,7 +49197,6 @@ var import_debug, __require2, ChangeDifferenceType, KadoaErrorCode, _KadoaSdkExc
|
|
|
49146
49197
|
const response = await this.client.axiosInstance.get("/v5/user", {
|
|
49147
49198
|
baseURL: this.client.baseUrl,
|
|
49148
49199
|
headers: {
|
|
49149
|
-
"x-api-key": this.client.apiKey,
|
|
49150
49200
|
"Content-Type": "application/json"
|
|
49151
49201
|
}
|
|
49152
49202
|
});
|
|
@@ -49443,36 +49493,82 @@ var import_debug, __require2, ChangeDifferenceType, KadoaErrorCode, _KadoaSdkExc
|
|
|
49443
49493
|
});
|
|
49444
49494
|
}
|
|
49445
49495
|
}, JobStateEnum, TERMINAL_JOB_STATES, TERMINAL_RUN_STATES2, debug9, WorkflowsCoreService = class {
|
|
49446
|
-
constructor(workflowsApi) {
|
|
49496
|
+
constructor(workflowsApi, templatesService) {
|
|
49447
49497
|
this.workflowsApi = workflowsApi;
|
|
49498
|
+
this.templatesService = templatesService;
|
|
49448
49499
|
}
|
|
49449
49500
|
async create(input) {
|
|
49450
49501
|
validateAdditionalData(input.additionalData);
|
|
49451
|
-
|
|
49502
|
+
const isFromTemplate = input.templateId != null;
|
|
49503
|
+
if (isFromTemplate) {
|
|
49504
|
+
const conflicting = [];
|
|
49505
|
+
if (input.userPrompt != null)
|
|
49506
|
+
conflicting.push("userPrompt");
|
|
49507
|
+
if (input.entity != null)
|
|
49508
|
+
conflicting.push("entity");
|
|
49509
|
+
if (input.fields != null)
|
|
49510
|
+
conflicting.push("fields");
|
|
49511
|
+
if (input.schemaId != null)
|
|
49512
|
+
conflicting.push("schemaId");
|
|
49513
|
+
if (input.monitoring != null)
|
|
49514
|
+
conflicting.push("monitoring");
|
|
49515
|
+
if (input.navigationMode != null)
|
|
49516
|
+
conflicting.push("navigationMode");
|
|
49517
|
+
if (conflicting.length > 0) {
|
|
49518
|
+
throw new KadoaSdkException(`Fields are defined by the template and cannot be supplied when creating from a template: ${conflicting.join(", ")}`, {
|
|
49519
|
+
code: "VALIDATION_ERROR",
|
|
49520
|
+
details: { conflicting }
|
|
49521
|
+
});
|
|
49522
|
+
}
|
|
49523
|
+
} else if (!input.userPrompt) {
|
|
49452
49524
|
throw new KadoaSdkException("userPrompt is required to create a workflow", {
|
|
49453
49525
|
code: "VALIDATION_ERROR",
|
|
49454
49526
|
details: { urls: input.urls }
|
|
49455
49527
|
});
|
|
49456
49528
|
}
|
|
49457
49529
|
const domainName = new URL(input.urls[0]).hostname;
|
|
49458
|
-
|
|
49459
|
-
|
|
49460
|
-
|
|
49461
|
-
|
|
49462
|
-
|
|
49463
|
-
|
|
49464
|
-
|
|
49465
|
-
|
|
49466
|
-
|
|
49467
|
-
|
|
49468
|
-
|
|
49469
|
-
|
|
49470
|
-
|
|
49471
|
-
|
|
49472
|
-
|
|
49473
|
-
|
|
49474
|
-
|
|
49475
|
-
|
|
49530
|
+
let request;
|
|
49531
|
+
if (isFromTemplate) {
|
|
49532
|
+
const templateId = input.templateId;
|
|
49533
|
+
const templateVersion = input.templateVersion ?? await this.resolveLatestVersion(templateId);
|
|
49534
|
+
request = {
|
|
49535
|
+
urls: input.urls,
|
|
49536
|
+
templateId,
|
|
49537
|
+
templateVersion,
|
|
49538
|
+
...input.name != null && { name: input.name },
|
|
49539
|
+
...input.description != null && { description: input.description },
|
|
49540
|
+
...input.tags != null && { tags: input.tags },
|
|
49541
|
+
...input.interval != null && { interval: input.interval },
|
|
49542
|
+
...input.schedules != null && { schedules: input.schedules },
|
|
49543
|
+
...input.location != null && { location: input.location },
|
|
49544
|
+
...input.bypassPreview != null && {
|
|
49545
|
+
bypassPreview: input.bypassPreview
|
|
49546
|
+
},
|
|
49547
|
+
...input.additionalData != null && {
|
|
49548
|
+
additionalData: input.additionalData
|
|
49549
|
+
},
|
|
49550
|
+
...input.limit != null && { limit: input.limit }
|
|
49551
|
+
};
|
|
49552
|
+
} else {
|
|
49553
|
+
request = {
|
|
49554
|
+
urls: input.urls,
|
|
49555
|
+
name: input.name ?? domainName,
|
|
49556
|
+
description: input.description,
|
|
49557
|
+
userPrompt: input.userPrompt,
|
|
49558
|
+
navigationMode: "agentic-navigation",
|
|
49559
|
+
schemaId: input.schemaId,
|
|
49560
|
+
...input.entity != null && { entity: input.entity },
|
|
49561
|
+
fields: input.fields,
|
|
49562
|
+
bypassPreview: input.bypassPreview ?? true,
|
|
49563
|
+
tags: input.tags,
|
|
49564
|
+
interval: input.interval,
|
|
49565
|
+
monitoring: input.monitoring,
|
|
49566
|
+
location: input.location,
|
|
49567
|
+
schedules: input.schedules,
|
|
49568
|
+
additionalData: input.additionalData,
|
|
49569
|
+
limit: input.limit
|
|
49570
|
+
};
|
|
49571
|
+
}
|
|
49476
49572
|
const response = await this.workflowsApi.v4WorkflowsPost({
|
|
49477
49573
|
publicWorkflowCreateRequest: request
|
|
49478
49574
|
});
|
|
@@ -49487,6 +49583,23 @@ var import_debug, __require2, ChangeDifferenceType, KadoaErrorCode, _KadoaSdkExc
|
|
|
49487
49583
|
}
|
|
49488
49584
|
return { id: workflowId };
|
|
49489
49585
|
}
|
|
49586
|
+
async resolveLatestVersion(templateId) {
|
|
49587
|
+
if (!this.templatesService) {
|
|
49588
|
+
throw new KadoaSdkException("TemplatesService is required to resolve a template's latest version. Pass `templateVersion` explicitly or construct WorkflowsCoreService with a TemplatesService.", {
|
|
49589
|
+
code: "INTERNAL_ERROR",
|
|
49590
|
+
details: { templateId }
|
|
49591
|
+
});
|
|
49592
|
+
}
|
|
49593
|
+
const template = await this.templatesService.get(templateId);
|
|
49594
|
+
const latest = template.latestVersion;
|
|
49595
|
+
if (latest == null) {
|
|
49596
|
+
throw new KadoaSdkException(`Template ${templateId} has no published versions; supply templateVersion explicitly or publish a version first.`, {
|
|
49597
|
+
code: "VALIDATION_ERROR",
|
|
49598
|
+
details: { templateId }
|
|
49599
|
+
});
|
|
49600
|
+
}
|
|
49601
|
+
return latest;
|
|
49602
|
+
}
|
|
49490
49603
|
async get(id) {
|
|
49491
49604
|
const response = await this.workflowsApi.v4WorkflowsWorkflowIdGet({
|
|
49492
49605
|
workflowId: id
|
|
@@ -49645,12 +49758,15 @@ var import_debug, __require2, ChangeDifferenceType, KadoaErrorCode, _KadoaSdkExc
|
|
|
49645
49758
|
const timeout2 = config2.timeout ?? 30000;
|
|
49646
49759
|
const headers = createSdkHeaders();
|
|
49647
49760
|
this._axiosInstance = createAxiosInstance({ timeout: timeout2, headers });
|
|
49648
|
-
this._axiosInstance.interceptors.request.use((reqConfig) => {
|
|
49761
|
+
this._axiosInstance.interceptors.request.use(async (reqConfig) => {
|
|
49649
49762
|
if (this._bearerToken) {
|
|
49763
|
+
const token = await resolveBearerToken(this._bearerToken);
|
|
49650
49764
|
if (!reqConfig.headers["Authorization"]) {
|
|
49651
|
-
reqConfig.headers["Authorization"] = `Bearer ${
|
|
49765
|
+
reqConfig.headers["Authorization"] = `Bearer ${token}`;
|
|
49652
49766
|
}
|
|
49653
49767
|
delete reqConfig.headers["x-api-key"];
|
|
49768
|
+
} else if (this._apiKey && !reqConfig.headers["x-api-key"]) {
|
|
49769
|
+
reqConfig.headers["x-api-key"] = this._apiKey;
|
|
49654
49770
|
}
|
|
49655
49771
|
return reqConfig;
|
|
49656
49772
|
});
|
|
@@ -49714,7 +49830,9 @@ var import_debug, __require2, ChangeDifferenceType, KadoaErrorCode, _KadoaSdkExc
|
|
|
49714
49830
|
};
|
|
49715
49831
|
}
|
|
49716
49832
|
async listTeams(opts) {
|
|
49717
|
-
const headers = opts?.bearerToken ? {
|
|
49833
|
+
const headers = opts?.bearerToken ? {
|
|
49834
|
+
Authorization: `Bearer ${await resolveBearerToken(opts.bearerToken)}`
|
|
49835
|
+
} : undefined;
|
|
49718
49836
|
const response = await this._axiosInstance.get("/v5/user", {
|
|
49719
49837
|
baseURL: this._baseUrl,
|
|
49720
49838
|
...headers && { headers }
|
|
@@ -50848,7 +50966,7 @@ function registerTools(server, ctx) {
|
|
|
50848
50966
|
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.")
|
|
50849
50967
|
};
|
|
50850
50968
|
const extractionInputShape = {
|
|
50851
|
-
prompt: exports_external.string().describe('Natural language description of what to extract (e.g., "Extract product prices and names")'),
|
|
50969
|
+
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.'),
|
|
50852
50970
|
name: exports_external.string().optional().describe("Optional name for the workflow"),
|
|
50853
50971
|
entity: exports_external.string().optional().describe("Entity name for extraction (e.g., 'Product', 'Job Posting')"),
|
|
50854
50972
|
schema: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object(SchemaFieldShape))).optional().describe("Extraction schema fields. If omitted, the AI agent auto-detects the schema.")
|
|
@@ -50953,10 +51071,12 @@ function registerTools(server, ctx) {
|
|
|
50953
51071
|
|
|
50954
51072
|
` + "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.
|
|
50955
51073
|
|
|
50956
|
-
` + "NOTE: This tool is for one-time or scheduled extraction ONLY. " + "For continuous real-time monitoring (watching a page for changes and alerting), use the create_realtime_monitor tool instead.",
|
|
51074
|
+
` + "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 `templateId` is set, only `urls` is required — `prompt`, `entity`, and `schema` must NOT be supplied; they are inherited from the template version.\n\n" + "NOTE: This tool is for one-time or scheduled extraction ONLY. " + "For continuous real-time monitoring (watching a page for changes and alerting), use the create_realtime_monitor tool instead.",
|
|
50957
51075
|
inputSchema: strictSchema({
|
|
50958
51076
|
...extractionInputShape,
|
|
50959
51077
|
...urlInputShape,
|
|
51078
|
+
templateId: exports_external.string().optional().describe("Instantiate this workflow from a published template. When set, only 'urls' is required — prompt/entity/schema must NOT be supplied; they are inherited from the template version, and the workflow's output conforms to the template's declared schema (field names are enforced, not drifted). Discover templates via list_templates."),
|
|
51079
|
+
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."),
|
|
50960
51080
|
description: exports_external.string().max(500).optional().describe("Description of what this workflow does (max 500 characters)"),
|
|
50961
51081
|
tags: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string())).optional().describe("Tags for organizing workflows"),
|
|
50962
51082
|
limit: exports_external.preprocess(coerceNumber(), exports_external.number()).optional().describe("Maximum number of records to extract per run. Useful for limiting scope or cost control."),
|
|
@@ -50991,32 +51111,62 @@ function registerTools(server, ctx) {
|
|
|
50991
51111
|
if (args.updateInterval === "CUSTOM" && (!args.schedules || args.schedules.length === 0)) {
|
|
50992
51112
|
return errorResult("updateInterval='CUSTOM' requires at least one cron expression in the 'schedules' field (e.g. '0 8 * * 2' for Tuesdays at 8am UTC).");
|
|
50993
51113
|
}
|
|
50994
|
-
let builder = ctx.client.extract({
|
|
50995
|
-
urls,
|
|
50996
|
-
name: args.name || "Untitled Workflow",
|
|
50997
|
-
userPrompt: args.prompt,
|
|
50998
|
-
extraction: buildExtraction(args),
|
|
50999
|
-
interval: args.updateInterval,
|
|
51000
|
-
description: args.description,
|
|
51001
|
-
schedules: args.schedules
|
|
51002
|
-
});
|
|
51003
51114
|
const n = args.notifications;
|
|
51004
|
-
const hasNotifications = n && (n.email || n.webhook || n.slack || n.websocket);
|
|
51005
|
-
|
|
51006
|
-
|
|
51007
|
-
|
|
51008
|
-
|
|
51115
|
+
const hasNotifications = !!(n && (n.email || n.webhook || n.slack || n.websocket));
|
|
51116
|
+
let workflowId;
|
|
51117
|
+
if (args.templateId) {
|
|
51118
|
+
if (args.prompt || args.entity || args.schema) {
|
|
51119
|
+
return errorResult("When 'templateId' is set, 'prompt', 'entity', and 'schema' must NOT be supplied — they are inherited from the template version.");
|
|
51120
|
+
}
|
|
51121
|
+
const { id } = await ctx.client.workflow.create({
|
|
51122
|
+
urls,
|
|
51123
|
+
name: args.name || "Untitled Workflow",
|
|
51124
|
+
description: args.description,
|
|
51125
|
+
interval: args.updateInterval,
|
|
51126
|
+
schedules: args.schedules,
|
|
51127
|
+
tags: args.tags && args.tags.length > 0 ? args.tags : undefined,
|
|
51128
|
+
limit: args.limit,
|
|
51129
|
+
templateId: args.templateId,
|
|
51130
|
+
templateVersion: args.templateVersion
|
|
51009
51131
|
});
|
|
51010
|
-
|
|
51011
|
-
|
|
51012
|
-
|
|
51013
|
-
|
|
51014
|
-
|
|
51015
|
-
|
|
51016
|
-
|
|
51017
|
-
|
|
51018
|
-
|
|
51019
|
-
|
|
51132
|
+
workflowId = id;
|
|
51133
|
+
if (hasNotifications) {
|
|
51134
|
+
await ctx.client.notification.configure({
|
|
51135
|
+
workflowId,
|
|
51136
|
+
events: ["workflow_data_change"],
|
|
51137
|
+
channels: buildNotificationChannels(n)
|
|
51138
|
+
});
|
|
51139
|
+
}
|
|
51140
|
+
} else {
|
|
51141
|
+
if (!args.prompt) {
|
|
51142
|
+
return errorResult("'prompt' is required unless 'templateId' is provided.");
|
|
51143
|
+
}
|
|
51144
|
+
let builder = ctx.client.extract({
|
|
51145
|
+
urls,
|
|
51146
|
+
name: args.name || "Untitled Workflow",
|
|
51147
|
+
userPrompt: args.prompt,
|
|
51148
|
+
extraction: buildExtraction(args),
|
|
51149
|
+
interval: args.updateInterval,
|
|
51150
|
+
description: args.description,
|
|
51151
|
+
schedules: args.schedules
|
|
51152
|
+
});
|
|
51153
|
+
if (hasNotifications) {
|
|
51154
|
+
builder = builder.withNotifications({
|
|
51155
|
+
events: ["workflow_data_change"],
|
|
51156
|
+
channels: buildNotificationChannels(n)
|
|
51157
|
+
});
|
|
51158
|
+
}
|
|
51159
|
+
const workflow = await builder.create();
|
|
51160
|
+
workflowId = workflow.workflowId;
|
|
51161
|
+
const needsUpdate = args.limit !== undefined || args.tags && args.tags.length > 0;
|
|
51162
|
+
if (needsUpdate) {
|
|
51163
|
+
const updates = {};
|
|
51164
|
+
if (args.limit !== undefined)
|
|
51165
|
+
updates.limit = args.limit;
|
|
51166
|
+
if (args.tags && args.tags.length > 0)
|
|
51167
|
+
updates.tags = args.tags;
|
|
51168
|
+
await ctx.client.workflow.update(workflowId, updates);
|
|
51169
|
+
}
|
|
51020
51170
|
}
|
|
51021
51171
|
const enabledChannels = await describeNotifications(n);
|
|
51022
51172
|
let message = "Workflow created successfully. The AI agent will start extracting data automatically.";
|
|
@@ -51026,8 +51176,8 @@ function registerTools(server, ctx) {
|
|
|
51026
51176
|
message += " Use fetch_data to retrieve the latest extracted results.";
|
|
51027
51177
|
return jsonResult({
|
|
51028
51178
|
success: true,
|
|
51029
|
-
workflowId
|
|
51030
|
-
dashboardUrl: workflowDashboardUrl(
|
|
51179
|
+
workflowId,
|
|
51180
|
+
dashboardUrl: workflowDashboardUrl(workflowId),
|
|
51031
51181
|
message
|
|
51032
51182
|
});
|
|
51033
51183
|
}));
|
|
@@ -51889,10 +52039,14 @@ function registerTools(server, ctx) {
|
|
|
51889
52039
|
const TemplateSchemaFieldShape = {
|
|
51890
52040
|
name: exports_external.string().describe("Field name"),
|
|
51891
52041
|
description: exports_external.string().optional().describe("Field description"),
|
|
51892
|
-
fieldType: exports_external.string().optional().describe("Field type (e.g. SCHEMA)"),
|
|
52042
|
+
fieldType: exports_external.string().optional().describe("Field type (e.g. SCHEMA, CLASSIFICATION)"),
|
|
51893
52043
|
example: exports_external.string().optional().describe("Example value"),
|
|
51894
52044
|
dataType: exports_external.string().optional().describe("Data type (STRING, NUMBER, DATE, LINK, etc.)"),
|
|
51895
|
-
isKey: exports_external.preprocess(coerceBoolean(), exports_external.boolean()).optional().describe("Whether the field is a key field")
|
|
52045
|
+
isKey: exports_external.preprocess(coerceBoolean(), exports_external.boolean()).optional().describe("Whether the field is a key field"),
|
|
52046
|
+
categories: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object({
|
|
52047
|
+
title: exports_external.string().describe("Short title/label of the category"),
|
|
52048
|
+
definition: exports_external.string().describe("Full definition/description of the category")
|
|
52049
|
+
}))).optional().describe("Predefined categories for a CLASSIFICATION field ({title, definition}[]). Required for fieldType=CLASSIFICATION; omitted otherwise.")
|
|
51896
52050
|
};
|
|
51897
52051
|
server.registerTool("create_template_version", {
|
|
51898
52052
|
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.",
|
|
@@ -51902,13 +52056,41 @@ function registerTools(server, ctx) {
|
|
|
51902
52056
|
schemaId: exports_external.string().optional().describe("Existing schema ID to reference (mutually exclusive with schemaFields)"),
|
|
51903
52057
|
schemaFields: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object(TemplateSchemaFieldShape))).optional().describe("Inline schema fields to create a new schema (mutually exclusive with schemaId)"),
|
|
51904
52058
|
schemaEntity: exports_external.string().optional().describe("Entity name for the inline schema"),
|
|
52059
|
+
schemaValidationRules: exports_external.preprocess(coerceJson(), exports_external.record(exports_external.string(), exports_external.record(exports_external.string(), exports_external.unknown()))).optional().describe("Per-field schema validation rules, keyed by field name. Not inherited from prior versions — omitting this on a new version drops any rules the previous version had."),
|
|
51905
52060
|
notifications: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object({
|
|
51906
52061
|
eventType: exports_external.string().describe("Notification event type"),
|
|
51907
52062
|
eventConfiguration: exports_external.preprocess(coerceJson(), exports_external.record(exports_external.string(), exports_external.unknown())).optional(),
|
|
51908
52063
|
enabled: exports_external.preprocess(coerceBoolean(), exports_external.boolean()).optional(),
|
|
51909
52064
|
channelIds: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.string())).optional(),
|
|
51910
52065
|
channels: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object({ channelId: exports_external.string() }))).optional()
|
|
51911
|
-
}))).optional().describe("Notification configuration for this version")
|
|
52066
|
+
}))).optional().describe("Notification configuration for this version"),
|
|
52067
|
+
frequency: exports_external.preprocess(coerceJson(), exports_external.object({
|
|
52068
|
+
interval: exports_external.enum([
|
|
52069
|
+
"ONLY_ONCE",
|
|
52070
|
+
"EVERY_10_MINUTES",
|
|
52071
|
+
"HALF_HOURLY",
|
|
52072
|
+
"HOURLY",
|
|
52073
|
+
"THREE_HOURLY",
|
|
52074
|
+
"SIX_HOURLY",
|
|
52075
|
+
"TWELVE_HOURLY",
|
|
52076
|
+
"EIGHTEEN_HOURLY",
|
|
52077
|
+
"DAILY",
|
|
52078
|
+
"TWO_DAY",
|
|
52079
|
+
"THREE_DAY",
|
|
52080
|
+
"WEEKLY",
|
|
52081
|
+
"BIWEEKLY",
|
|
52082
|
+
"TRIWEEKLY",
|
|
52083
|
+
"FOUR_WEEKS",
|
|
52084
|
+
"MONTHLY",
|
|
52085
|
+
"CUSTOM",
|
|
52086
|
+
"REAL_TIME"
|
|
52087
|
+
]).optional().describe("Scheduling interval the template controls for linked workflows. Use CUSTOM with 'schedules' for cron."),
|
|
52088
|
+
schedules: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object({
|
|
52089
|
+
type: exports_external.enum(["repeat", "cron"]).describe("'repeat' (interval token) or 'cron' (cron expressions)"),
|
|
52090
|
+
interval: exports_external.string().optional().describe("Repeat token (e.g. '1h', '1d') when type='repeat'"),
|
|
52091
|
+
expressions: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string())).optional().describe("Cron expressions when type='cron' (e.g. '0 8 * * 2')")
|
|
52092
|
+
}))).optional().describe("Schedule specs. Required when interval='CUSTOM'.")
|
|
52093
|
+
})).optional().describe("Template-controlled scheduling for linked workflows ({interval, schedules}). Linked workflows inherit and cannot override this when set.")
|
|
51912
52094
|
}),
|
|
51913
52095
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
|
|
51914
52096
|
}, withErrorHandling("create_template_version", async (args) => {
|
|
@@ -51927,7 +52109,7 @@ function registerTools(server, ctx) {
|
|
|
51927
52109
|
name: exports_external.string().optional().describe("Name for the new template (required if templateId is not set)"),
|
|
51928
52110
|
description: exports_external.string().optional().describe("Description for the new template"),
|
|
51929
52111
|
templateId: exports_external.string().optional().describe("Existing template ID to add a new version to (mutually exclusive with name)"),
|
|
51930
|
-
includeParts: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.enum(["prompt", "schema", "notifications"])).optional()).describe("Which parts of the workflow config to include. Omit to include all. At least 1 required if provided.")
|
|
52112
|
+
includeParts: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.enum(["prompt", "schema", "schemaValidationRules", "notifications", "frequency"])).optional()).describe("Which parts of the workflow config to include: prompt, schema, schemaValidationRules, notifications, frequency. Omit to include all. At least 1 required if provided.")
|
|
51931
52113
|
}),
|
|
51932
52114
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
|
|
51933
52115
|
}, withErrorHandling("save_workflow_as_template", async (args) => {
|
|
@@ -51954,6 +52136,83 @@ function registerTools(server, ctx) {
|
|
|
51954
52136
|
const schemas4 = await ctx.client.template.listSchemas(args.templateId);
|
|
51955
52137
|
return jsonResult({ schemas: schemas4, count: schemas4.length });
|
|
51956
52138
|
}));
|
|
52139
|
+
server.registerTool("link_template_to_workflows", {
|
|
52140
|
+
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.",
|
|
52141
|
+
inputSchema: strictSchema({
|
|
52142
|
+
templateId: exports_external.string().describe("The template ID to link workflows to"),
|
|
52143
|
+
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."),
|
|
52144
|
+
force: exports_external.preprocess(coerceBoolean(), exports_external.boolean()).optional().describe("If true, relink workflows that are already linked to another template. Defaults to false.")
|
|
52145
|
+
}),
|
|
52146
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true }
|
|
52147
|
+
}, withErrorHandling("link_template_to_workflows", async (args) => {
|
|
52148
|
+
const result = await ctx.client.template.linkWorkflows(args.templateId, {
|
|
52149
|
+
workflowIds: args.workflowIds,
|
|
52150
|
+
...args.force != null && { force: args.force }
|
|
52151
|
+
});
|
|
52152
|
+
if (result.conflicts && result.conflicts.length > 0) {
|
|
52153
|
+
const summary = result.conflicts.map((c) => `${c.workflowName || c.workflowId} (currently on "${c.templateName}")`).join(", ");
|
|
52154
|
+
return jsonResult({
|
|
52155
|
+
success: false,
|
|
52156
|
+
conflicts: result.conflicts,
|
|
52157
|
+
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."
|
|
52158
|
+
});
|
|
52159
|
+
}
|
|
52160
|
+
return jsonResult({
|
|
52161
|
+
success: true,
|
|
52162
|
+
linkedCount: result.linkedCount,
|
|
52163
|
+
workflowIds: result.workflowIds,
|
|
52164
|
+
message: `Linked ${result.linkedCount} workflow(s) to template ${args.templateId}. Linked workflows now follow the template's schema.`
|
|
52165
|
+
});
|
|
52166
|
+
}));
|
|
52167
|
+
server.registerTool("apply_template_version", {
|
|
52168
|
+
description: "Push a template version onto its linked workflows. Updates the given workflows to the template's `targetVersion`, applying the template-controlled parts (prompt, schema, validation rules, notifications, frequency). " + "Use this after publishing a new template version to bring linked workflows up to date (see list_template_workflows for which workflows are outdated). " + "Returns how many workflows were updated and which parts the template controls.",
|
|
52169
|
+
inputSchema: strictSchema({
|
|
52170
|
+
templateId: exports_external.string().describe("The template ID whose version to apply"),
|
|
52171
|
+
targetVersion: exports_external.preprocess(coerceNumber(), exports_external.number()).describe("The template version number to apply to the workflows"),
|
|
52172
|
+
workflowIds: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string()).min(1)).describe("Workflow IDs to update to the target version (array of strings). Also accepts a single ID string.")
|
|
52173
|
+
}),
|
|
52174
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true }
|
|
52175
|
+
}, withErrorHandling("apply_template_version", async (args) => {
|
|
52176
|
+
const result = await ctx.client.template.applyVersion(args.templateId, {
|
|
52177
|
+
targetVersion: args.targetVersion,
|
|
52178
|
+
workflowIds: args.workflowIds
|
|
52179
|
+
});
|
|
52180
|
+
return jsonResult({
|
|
52181
|
+
success: true,
|
|
52182
|
+
updatedCount: result.updatedCount,
|
|
52183
|
+
workflowIds: result.workflowIds,
|
|
52184
|
+
controlledParts: result.controlledParts,
|
|
52185
|
+
message: `Applied template ${args.templateId} v${args.targetVersion} to ${result.updatedCount} workflow(s).`
|
|
52186
|
+
});
|
|
52187
|
+
}));
|
|
52188
|
+
server.registerTool("list_template_workflows", {
|
|
52189
|
+
description: "List the workflows linked to a template, with each workflow's pinned template version and an `isOutdated` flag (true when a newer template version exists). " + "Use this to verify which workflows are bound to a template and to find ones that need apply_template_version.",
|
|
52190
|
+
inputSchema: strictSchema({
|
|
52191
|
+
templateId: exports_external.string().describe("The template ID")
|
|
52192
|
+
}),
|
|
52193
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }
|
|
52194
|
+
}, withErrorHandling("list_template_workflows", async (args) => {
|
|
52195
|
+
const workflows = await ctx.client.template.getLinkedWorkflows(args.templateId);
|
|
52196
|
+
return jsonResult({ workflows, count: workflows.length });
|
|
52197
|
+
}));
|
|
52198
|
+
server.registerTool("unlink_template_from_workflows", {
|
|
52199
|
+
description: "Unlink one or more workflows from a template in a single call. " + "Unlinked workflows keep their current configuration but stop tracking the template (future template version changes no longer apply to them).",
|
|
52200
|
+
inputSchema: strictSchema({
|
|
52201
|
+
templateId: exports_external.string().describe("The template ID to unlink workflows from"),
|
|
52202
|
+
workflowIds: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string()).min(1)).describe("Workflow IDs to unlink from the template (array of strings). Also accepts a single ID string.")
|
|
52203
|
+
}),
|
|
52204
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true }
|
|
52205
|
+
}, withErrorHandling("unlink_template_from_workflows", async (args) => {
|
|
52206
|
+
const result = await ctx.client.template.unlinkWorkflows(args.templateId, {
|
|
52207
|
+
workflowIds: args.workflowIds
|
|
52208
|
+
});
|
|
52209
|
+
return jsonResult({
|
|
52210
|
+
success: true,
|
|
52211
|
+
unlinkedCount: result.unlinkedCount,
|
|
52212
|
+
workflowIds: result.workflowIds,
|
|
52213
|
+
message: `Unlinked ${result.unlinkedCount} workflow(s) from template ${args.templateId}.`
|
|
52214
|
+
});
|
|
52215
|
+
}));
|
|
51957
52216
|
}
|
|
51958
52217
|
var SchemaFieldShape, DASHBOARD_BASE_URL = "https://www.kadoa.com", WORKFLOW_AUDIT_WATCHED_KEYS;
|
|
51959
52218
|
var init_tools = __esm(() => {
|
|
@@ -51987,7 +52246,7 @@ var package_default;
|
|
|
51987
52246
|
var init_package = __esm(() => {
|
|
51988
52247
|
package_default = {
|
|
51989
52248
|
name: "@kadoa/mcp",
|
|
51990
|
-
version: "0.5.
|
|
52249
|
+
version: "0.5.8-rc.1",
|
|
51991
52250
|
description: "Kadoa MCP Server — manage workflows from Claude Desktop, Cursor, and other MCP clients",
|
|
51992
52251
|
type: "module",
|
|
51993
52252
|
main: "dist/index.js",
|
|
@@ -52011,7 +52270,7 @@ var init_package = __esm(() => {
|
|
|
52011
52270
|
prepublishOnly: "bun run check-types && bun run test:unit && bun run build"
|
|
52012
52271
|
},
|
|
52013
52272
|
dependencies: {
|
|
52014
|
-
"@kadoa/node-sdk": "^0.
|
|
52273
|
+
"@kadoa/node-sdk": "^0.34.0",
|
|
52015
52274
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
52016
52275
|
express: "^5.2.1",
|
|
52017
52276
|
ioredis: "^5.6.1",
|
|
@@ -57241,7 +57500,9 @@ function createServer(auth) {
|
|
|
57241
57500
|
"Use list_changes and get_change to retrieve detected diffs from real-time monitoring workflows.",
|
|
57242
57501
|
"",
|
|
57243
57502
|
"Schema tips: Use descriptive field names and examples. Group related data under one entity.",
|
|
57244
|
-
"The AI agent uses the schema + prompt to understand what to extract \u2014 a detailed prompt with a comprehensive schema produces better results than multiple simple workflows."
|
|
57503
|
+
"The AI agent uses the schema + prompt to understand what to extract \u2014 a detailed prompt with a comprehensive schema produces better results than multiple simple workflows.",
|
|
57504
|
+
"",
|
|
57505
|
+
"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 \u2014 do NOT assume the extractor will drift field names away from a template's schema."
|
|
57245
57506
|
].join(`
|
|
57246
57507
|
`)
|
|
57247
57508
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kadoa/mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.8-rc.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",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"prepublishOnly": "bun run check-types && bun run test:unit && bun run build"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@kadoa/node-sdk": "^0.
|
|
27
|
+
"@kadoa/node-sdk": "^0.34.0",
|
|
28
28
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
29
29
|
"express": "^5.2.1",
|
|
30
30
|
"ioredis": "^5.6.1",
|