@kadoa/mcp 0.5.7 → 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 +142 -12
- package/dist/index.js +179 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -137,25 +137,90 @@ delete_workflow for each, confirming before proceeding.
|
|
|
137
137
|
- Restart your MCP client
|
|
138
138
|
- Re-authenticate via OAuth if prompted
|
|
139
139
|
|
|
140
|
-
##
|
|
140
|
+
## Releases
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
This repo ships two surfaces, and a "release" usually touches both:
|
|
143
143
|
|
|
144
|
-
**
|
|
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.
|
|
145
146
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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:
|
|
150
159
|
- **Target cluster:** `gcp`
|
|
151
160
|
- **Deployment scope:** `mcp`
|
|
152
|
-
- **Image tag:** the tag from step
|
|
161
|
+
- **Image tag:** the tag from step 4
|
|
153
162
|
- **Method:** `kubectl`
|
|
154
163
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
- `
|
|
158
|
-
|
|
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
|
|
159
224
|
|
|
160
225
|
## Development
|
|
161
226
|
|
|
@@ -180,6 +245,71 @@ KADOA_PUBLIC_API_URI=http://localhost:12380 bun run dev
|
|
|
180
245
|
|
|
181
246
|
The server starts in HTTP mode. You authenticate via OAuth the same way as with the remote server.
|
|
182
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
|
+
|
|
183
313
|
## License
|
|
184
314
|
|
|
185
315
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -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,
|
|
@@ -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
|
});
|
|
@@ -49708,12 +49758,15 @@ var import_debug, __require2, ChangeDifferenceType, KadoaErrorCode, _KadoaSdkExc
|
|
|
49708
49758
|
const timeout2 = config2.timeout ?? 30000;
|
|
49709
49759
|
const headers = createSdkHeaders();
|
|
49710
49760
|
this._axiosInstance = createAxiosInstance({ timeout: timeout2, headers });
|
|
49711
|
-
this._axiosInstance.interceptors.request.use((reqConfig) => {
|
|
49761
|
+
this._axiosInstance.interceptors.request.use(async (reqConfig) => {
|
|
49712
49762
|
if (this._bearerToken) {
|
|
49763
|
+
const token = await resolveBearerToken(this._bearerToken);
|
|
49713
49764
|
if (!reqConfig.headers["Authorization"]) {
|
|
49714
|
-
reqConfig.headers["Authorization"] = `Bearer ${
|
|
49765
|
+
reqConfig.headers["Authorization"] = `Bearer ${token}`;
|
|
49715
49766
|
}
|
|
49716
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;
|
|
49717
49770
|
}
|
|
49718
49771
|
return reqConfig;
|
|
49719
49772
|
});
|
|
@@ -49777,7 +49830,9 @@ var import_debug, __require2, ChangeDifferenceType, KadoaErrorCode, _KadoaSdkExc
|
|
|
49777
49830
|
};
|
|
49778
49831
|
}
|
|
49779
49832
|
async listTeams(opts) {
|
|
49780
|
-
const headers = opts?.bearerToken ? {
|
|
49833
|
+
const headers = opts?.bearerToken ? {
|
|
49834
|
+
Authorization: `Bearer ${await resolveBearerToken(opts.bearerToken)}`
|
|
49835
|
+
} : undefined;
|
|
49781
49836
|
const response = await this._axiosInstance.get("/v5/user", {
|
|
49782
49837
|
baseURL: this._baseUrl,
|
|
49783
49838
|
...headers && { headers }
|
|
@@ -51020,7 +51075,7 @@ function registerTools(server, ctx) {
|
|
|
51020
51075
|
inputSchema: strictSchema({
|
|
51021
51076
|
...extractionInputShape,
|
|
51022
51077
|
...urlInputShape,
|
|
51023
|
-
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. Discover templates via list_templates."),
|
|
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."),
|
|
51024
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."),
|
|
51025
51080
|
description: exports_external.string().max(500).optional().describe("Description of what this workflow does (max 500 characters)"),
|
|
51026
51081
|
tags: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string())).optional().describe("Tags for organizing workflows"),
|
|
@@ -51984,10 +52039,14 @@ function registerTools(server, ctx) {
|
|
|
51984
52039
|
const TemplateSchemaFieldShape = {
|
|
51985
52040
|
name: exports_external.string().describe("Field name"),
|
|
51986
52041
|
description: exports_external.string().optional().describe("Field description"),
|
|
51987
|
-
fieldType: exports_external.string().optional().describe("Field type (e.g. SCHEMA)"),
|
|
52042
|
+
fieldType: exports_external.string().optional().describe("Field type (e.g. SCHEMA, CLASSIFICATION)"),
|
|
51988
52043
|
example: exports_external.string().optional().describe("Example value"),
|
|
51989
52044
|
dataType: exports_external.string().optional().describe("Data type (STRING, NUMBER, DATE, LINK, etc.)"),
|
|
51990
|
-
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.")
|
|
51991
52050
|
};
|
|
51992
52051
|
server.registerTool("create_template_version", {
|
|
51993
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.",
|
|
@@ -51997,13 +52056,41 @@ function registerTools(server, ctx) {
|
|
|
51997
52056
|
schemaId: exports_external.string().optional().describe("Existing schema ID to reference (mutually exclusive with schemaFields)"),
|
|
51998
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)"),
|
|
51999
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."),
|
|
52000
52060
|
notifications: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object({
|
|
52001
52061
|
eventType: exports_external.string().describe("Notification event type"),
|
|
52002
52062
|
eventConfiguration: exports_external.preprocess(coerceJson(), exports_external.record(exports_external.string(), exports_external.unknown())).optional(),
|
|
52003
52063
|
enabled: exports_external.preprocess(coerceBoolean(), exports_external.boolean()).optional(),
|
|
52004
52064
|
channelIds: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.string())).optional(),
|
|
52005
52065
|
channels: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object({ channelId: exports_external.string() }))).optional()
|
|
52006
|
-
}))).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.")
|
|
52007
52094
|
}),
|
|
52008
52095
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
|
|
52009
52096
|
}, withErrorHandling("create_template_version", async (args) => {
|
|
@@ -52022,7 +52109,7 @@ function registerTools(server, ctx) {
|
|
|
52022
52109
|
name: exports_external.string().optional().describe("Name for the new template (required if templateId is not set)"),
|
|
52023
52110
|
description: exports_external.string().optional().describe("Description for the new template"),
|
|
52024
52111
|
templateId: exports_external.string().optional().describe("Existing template ID to add a new version to (mutually exclusive with name)"),
|
|
52025
|
-
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.")
|
|
52026
52113
|
}),
|
|
52027
52114
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
|
|
52028
52115
|
}, withErrorHandling("save_workflow_as_template", async (args) => {
|
|
@@ -52049,6 +52136,83 @@ function registerTools(server, ctx) {
|
|
|
52049
52136
|
const schemas4 = await ctx.client.template.listSchemas(args.templateId);
|
|
52050
52137
|
return jsonResult({ schemas: schemas4, count: schemas4.length });
|
|
52051
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
|
+
}));
|
|
52052
52216
|
}
|
|
52053
52217
|
var SchemaFieldShape, DASHBOARD_BASE_URL = "https://www.kadoa.com", WORKFLOW_AUDIT_WATCHED_KEYS;
|
|
52054
52218
|
var init_tools = __esm(() => {
|
|
@@ -52082,7 +52246,7 @@ var package_default;
|
|
|
52082
52246
|
var init_package = __esm(() => {
|
|
52083
52247
|
package_default = {
|
|
52084
52248
|
name: "@kadoa/mcp",
|
|
52085
|
-
version: "0.5.
|
|
52249
|
+
version: "0.5.8-rc.1",
|
|
52086
52250
|
description: "Kadoa MCP Server — manage workflows from Claude Desktop, Cursor, and other MCP clients",
|
|
52087
52251
|
type: "module",
|
|
52088
52252
|
main: "dist/index.js",
|
|
@@ -52106,7 +52270,7 @@ var init_package = __esm(() => {
|
|
|
52106
52270
|
prepublishOnly: "bun run check-types && bun run test:unit && bun run build"
|
|
52107
52271
|
},
|
|
52108
52272
|
dependencies: {
|
|
52109
|
-
"@kadoa/node-sdk": "^0.
|
|
52273
|
+
"@kadoa/node-sdk": "^0.34.0",
|
|
52110
52274
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
52111
52275
|
express: "^5.2.1",
|
|
52112
52276
|
ioredis: "^5.6.1",
|
|
@@ -57336,7 +57500,9 @@ function createServer(auth) {
|
|
|
57336
57500
|
"Use list_changes and get_change to retrieve detected diffs from real-time monitoring workflows.",
|
|
57337
57501
|
"",
|
|
57338
57502
|
"Schema tips: Use descriptive field names and examples. Group related data under one entity.",
|
|
57339
|
-
"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."
|
|
57340
57506
|
].join(`
|
|
57341
57507
|
`)
|
|
57342
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",
|