@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.
Files changed (3) hide show
  1. package/README.md +153 -12
  2. package/dist/index.js +324 -63
  3. 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
- ## Deploying the Remote Server
140
+ ## Releases
130
141
 
131
- The remote MCP server at `mcp.kadoa.com` runs as a Docker container on GKE, deployed from the `kadoa-backend` monorepo.
142
+ This repo ships two surfaces, and a "release" usually touches both:
132
143
 
133
- **To deploy a new version:**
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
- 1. Publish a new `@kadoa/mcp` version to npm (`npm publish`)
136
- 2. In `kadoa-backend`, update `infra/docker/mcp/package.json` to the new version and run `bun install` to regenerate the lockfile
137
- 3. Merge to `main` — the CI pipeline (`main-build-deploy.yml`) builds and pushes the Docker image automatically
138
- 4. Trigger the **Deploy to Production** workflow (`deploy-prod.yml`) with:
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 3 (shown in the build summary)
161
+ - **Image tag:** the tag from step 4
142
162
  - **Method:** `kubectl`
143
163
 
144
- Infrastructure files in `kadoa-backend`:
145
- - `infra/docker/mcp/Dockerfile.mcp-server` — Docker image definition
146
- - `infra/docker/mcp/package.json` pinned `@kadoa/mcp` version
147
- - `infra/cdk8s/mcp/` — Kubernetes manifests (cdk8s)
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") {} else {}
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
- } else {}
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.32.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 {
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
- if (!input.userPrompt) {
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
- const request = {
49459
- urls: input.urls,
49460
- name: input.name ?? domainName,
49461
- description: input.description,
49462
- userPrompt: input.userPrompt,
49463
- navigationMode: "agentic-navigation",
49464
- schemaId: input.schemaId,
49465
- ...input.entity != null && { entity: input.entity },
49466
- fields: input.fields,
49467
- bypassPreview: input.bypassPreview ?? true,
49468
- tags: input.tags,
49469
- interval: input.interval,
49470
- monitoring: input.monitoring,
49471
- location: input.location,
49472
- schedules: input.schedules,
49473
- additionalData: input.additionalData,
49474
- limit: input.limit
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 ${this._bearerToken}`;
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 ? { Authorization: `Bearer ${opts.bearerToken}` } : undefined;
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
- if (hasNotifications) {
51006
- builder = builder.withNotifications({
51007
- events: ["workflow_data_change"],
51008
- channels: buildNotificationChannels(n)
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
- const workflow = await builder.create();
51012
- const needsUpdate = args.limit !== undefined || args.tags && args.tags.length > 0;
51013
- if (needsUpdate) {
51014
- const updates = {};
51015
- if (args.limit !== undefined)
51016
- updates.limit = args.limit;
51017
- if (args.tags && args.tags.length > 0)
51018
- updates.tags = args.tags;
51019
- await ctx.client.workflow.update(workflow.workflowId, updates);
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: workflow.workflowId,
51030
- dashboardUrl: workflowDashboardUrl(workflow.workflowId),
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.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.32.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.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.32.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",