@kadoa/mcp 0.5.20 → 0.5.22

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 +18 -10
  2. package/dist/index.js +360 -36
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -68,6 +68,7 @@ Point your client to `https://mcp.kadoa.com/mcp` with OAuth authentication.
68
68
  | `get_workflow` | Get canonical intent, Assistant/session, template ownership, run health, schedule, location, monitoring, and validation details |
69
69
  | `request_workflow_update` | Ask the workflow Assistant to change extraction intent, navigation, sourcing, pagination, or generated behavior without changing the workflow ID |
70
70
  | `get_workflow_assistant` | Get the workflow Assistant's current activity and any pending clarification question |
71
+ | `get_workflow_assistant_timeline` | Read paginated customer-visible Assistant messages and questions for Lighthouse or Julie |
71
72
  | `answer_workflow_assistant_question` | Answer the workflow Assistant's current clarification question and resume it |
72
73
  | `interrupt_workflow_assistant` | Safely interrupt active Assistant work without pausing the workflow schedule |
73
74
  | `resume_workflow_assistant` | Resume an idle/interrupted Lighthouse or Julie session with its persisted persona |
@@ -127,9 +128,10 @@ to retrieve the extracted records and display them as a table.
127
128
  > You: Use my "Product Scraper" template to scrape https://example-shop.com.
128
129
 
129
130
  Claude calls list_templates to find the matching template, then
130
- create_workflow with `templateId` and `urls` only the prompt and
131
- schema are inherited from the template version. Returns the workflow
132
- ID for follow-up with get_workflow or fetch_data.
131
+ create_workflow with `templateId` and `urls` only - never copy the prompt,
132
+ entity, or schema into standalone creation. Those values are inherited from
133
+ the template version. Returns the workflow ID for follow-up with get_workflow
134
+ or fetch_data.
133
135
  ```
134
136
 
135
137
  ### Update a workflow and re-run
@@ -148,8 +150,10 @@ changes, and shows the updated field list.
148
150
 
149
151
  > You: Run it again with the new schema.
150
152
 
151
- Claude calls run_workflow and waits for completion, then fetches
152
- the latest data with fetch_data so you can verify the changes.
153
+ Claude calls run_workflow and returns the workflow ID while the run proceeds
154
+ asynchronously. Check back later with get_workflow or fetch_data. If a
155
+ one-time or scheduled workflow fails, call run_workflow again with the same
156
+ workflow ID and configuration - never delete and recreate it just to retry.
153
157
  ```
154
158
 
155
159
  ### Update deterministic workflow settings
@@ -188,9 +192,11 @@ Assistant session, thread, and job identifiers.
188
192
 
189
193
  > You: Does Kadoa need anything from me?
190
194
 
191
- Claude calls get_workflow_assistant. If a clarification is pending, Claude
192
- shows the question and calls answer_workflow_assistant_question with your
193
- answer. It does not poll or sleep-wait for the Assistant.
195
+ Claude calls get_workflow_assistant. To review the conversation, Claude calls
196
+ get_workflow_assistant_timeline and follows its opaque nextCursor only when
197
+ older messages are needed. If a clarification is pending, Claude shows the
198
+ question and calls answer_workflow_assistant_question with your answer. It does
199
+ not poll or sleep-wait for the Assistant.
194
200
 
195
201
  > You: How does this workflow extract the data now?
196
202
 
@@ -247,14 +253,16 @@ Relevant files in `kadoa-backend`:
247
253
 
248
254
  1. Merge PRs to `main` using Conventional Commits (`feat:`, `fix:`, etc.). Release Please opens/maintains a `chore(main): release mcp x.y.z` PR.
249
255
  2. Merge the release PR. The [`release-please.yml`](.github/workflows/release-please.yml) workflow tags, drafts a GitHub Release, and publishes to npm (`latest` dist-tag).
250
- 3. 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.
251
- 4. Merge to `main`. CI (`main-build-deploy.yml`) builds and pushes `europe-west3-docker.pkg.dev/oceanic-base-310208/kadoa-artifacts/mcp-server:<IMAGE_TAG>` (tag shown in the build summary).
256
+ 3. After npm publication, `release-please.yml` opens or updates a deterministic PR in `kadoa-backend` that bumps `infra/docker/mcp/package.json` and refreshes `bun.lock`. The PR validates a frozen production install and Docker image build, and remains manually mergeable.
257
+ 4. Merge the backend PR to `main`. CI (`main-build-deploy.yml`) builds and pushes `europe-west3-docker.pkg.dev/oceanic-base-310208/kadoa-artifacts/mcp-server:<IMAGE_TAG>` (tag shown in the build summary).
252
258
  5. Trigger the **Deploy to Production** workflow ([`deploy-prod.yml`](https://github.com/kadoa-org/kadoa-backend/actions/workflows/deploy-prod.yml)) with:
253
259
  - **Target cluster:** `gcp`
254
260
  - **Deployment scope:** `mcp`
255
261
  - **Image tag:** the tag from step 4
256
262
  - **Method:** `kubectl`
257
263
 
264
+ The cross-repository bump requires the `BACKEND_REPO_TOKEN` secret in this repository. The selected authentication approach is a fine-grained PAT scoped only to `kadoa-org/kadoa-backend` with Contents read/write and Pull requests read/write permissions (Metadata read is automatic). If the secret is not provisioned, the release workflow skips the backend bump with a warning rather than failing npm releases; an administrator must provision it before relying on automatic hosted-server updates.
265
+
258
266
  ### RC / test release
259
267
 
260
268
  Use this when you want to validate a change end-to-end against real clients (Claude Desktop, Cursor, ChatGPT) before promoting to `latest` / prod. The flow mirrors the prod one, but every step targets `rc` channels.
package/dist/index.js CHANGED
@@ -45783,6 +45783,14 @@ var import_debug, __require2, FREEFORM_ASSISTANT_ANSWER_KEY = "_freeform", Kadoa
45783
45783
  }
45784
45784
  return data;
45785
45785
  }
45786
+ async getTimeline(workflowId, input = {}) {
45787
+ const response = await this.agentApi.v5AgentWorkflowAssistantTimeline({
45788
+ workflowId,
45789
+ ...input.cursor != null && { cursor: input.cursor },
45790
+ ...input.limit != null && { limit: input.limit }
45791
+ });
45792
+ return response.data.data;
45793
+ }
45786
45794
  async getPauseState(sessionId) {
45787
45795
  const response = await this.agentApi.v5AgentPauseState({ sessionId });
45788
45796
  return response.data.data;
@@ -46050,6 +46058,32 @@ var import_debug, __require2, FREEFORM_ASSISTANT_ANSWER_KEY = "_freeform", Kadoa
46050
46058
  url: toPathString(localVarUrlObj),
46051
46059
  options: localVarRequestOptions
46052
46060
  };
46061
+ },
46062
+ v5AgentWorkflowAssistantTimeline: async (workflowId, cursor, limit, options = {}) => {
46063
+ assertParamExists("v5AgentWorkflowAssistantTimeline", "workflowId", workflowId);
46064
+ const localVarPath = `/v5/agent/workflows/{workflowId}/timeline`.replace(`{${"workflowId"}}`, encodeURIComponent(String(workflowId)));
46065
+ const localVarUrlObj = new URL$1(localVarPath, DUMMY_BASE_URL);
46066
+ let baseOptions;
46067
+ if (configuration) {
46068
+ baseOptions = configuration.baseOptions;
46069
+ }
46070
+ const localVarRequestOptions = { method: "GET", ...baseOptions, ...options };
46071
+ const localVarHeaderParameter = {};
46072
+ const localVarQueryParameter = {};
46073
+ if (cursor !== undefined) {
46074
+ localVarQueryParameter["cursor"] = cursor;
46075
+ }
46076
+ if (limit !== undefined) {
46077
+ localVarQueryParameter["limit"] = limit;
46078
+ }
46079
+ localVarHeaderParameter["Accept"] = "application/json";
46080
+ setSearchParams(localVarUrlObj, localVarQueryParameter);
46081
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
46082
+ localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
46083
+ return {
46084
+ url: toPathString(localVarUrlObj),
46085
+ options: localVarRequestOptions
46086
+ };
46053
46087
  }
46054
46088
  };
46055
46089
  }, AgentApiFp = function(configuration) {
@@ -46102,6 +46136,12 @@ var import_debug, __require2, FREEFORM_ASSISTANT_ANSWER_KEY = "_freeform", Kadoa
46102
46136
  const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
46103
46137
  const localVarOperationServerBasePath = operationServerMap["AgentApi.v5AgentWorkflowAssistantMessage"]?.[localVarOperationServerIndex]?.url;
46104
46138
  return (axios2, basePath) => createRequestFunction(localVarAxiosArgs, axios_default, BASE_PATH, configuration)(axios2, localVarOperationServerBasePath || basePath);
46139
+ },
46140
+ async v5AgentWorkflowAssistantTimeline(workflowId, cursor, limit, options) {
46141
+ const localVarAxiosArgs = await localVarAxiosParamCreator.v5AgentWorkflowAssistantTimeline(workflowId, cursor, limit, options);
46142
+ const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
46143
+ const localVarOperationServerBasePath = operationServerMap["AgentApi.v5AgentWorkflowAssistantTimeline"]?.[localVarOperationServerIndex]?.url;
46144
+ return (axios2, basePath) => createRequestFunction(localVarAxiosArgs, axios_default, BASE_PATH, configuration)(axios2, localVarOperationServerBasePath || basePath);
46105
46145
  }
46106
46146
  };
46107
46147
  }, AgentApi, CrawlerApiAxiosParamCreator = function(configuration) {
@@ -48157,6 +48197,113 @@ var import_debug, __require2, FREEFORM_ASSISTANT_ANSWER_KEY = "_freeform", Kadoa
48157
48197
  };
48158
48198
  }, TemplatesApi, VariablesApiAxiosParamCreator = function(configuration) {
48159
48199
  return {
48200
+ v4VariablesConnectionsConnectionIdDelete: async (connectionId, options = {}) => {
48201
+ assertParamExists("v4VariablesConnectionsConnectionIdDelete", "connectionId", connectionId);
48202
+ const localVarPath = `/v4/variables/connections/{connectionId}`.replace(`{${"connectionId"}}`, encodeURIComponent(String(connectionId)));
48203
+ const localVarUrlObj = new URL$1(localVarPath, DUMMY_BASE_URL);
48204
+ let baseOptions;
48205
+ if (configuration) {
48206
+ baseOptions = configuration.baseOptions;
48207
+ }
48208
+ const localVarRequestOptions = { method: "DELETE", ...baseOptions, ...options };
48209
+ const localVarHeaderParameter = {};
48210
+ const localVarQueryParameter = {};
48211
+ await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration);
48212
+ localVarHeaderParameter["Accept"] = "application/json";
48213
+ setSearchParams(localVarUrlObj, localVarQueryParameter);
48214
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
48215
+ localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
48216
+ return {
48217
+ url: toPathString(localVarUrlObj),
48218
+ options: localVarRequestOptions
48219
+ };
48220
+ },
48221
+ v4VariablesConnectionsConnectionIdPatch: async (connectionId, v4VariablesConnectionsConnectionIdPatchRequest, options = {}) => {
48222
+ assertParamExists("v4VariablesConnectionsConnectionIdPatch", "connectionId", connectionId);
48223
+ const localVarPath = `/v4/variables/connections/{connectionId}`.replace(`{${"connectionId"}}`, encodeURIComponent(String(connectionId)));
48224
+ const localVarUrlObj = new URL$1(localVarPath, DUMMY_BASE_URL);
48225
+ let baseOptions;
48226
+ if (configuration) {
48227
+ baseOptions = configuration.baseOptions;
48228
+ }
48229
+ const localVarRequestOptions = { method: "PATCH", ...baseOptions, ...options };
48230
+ const localVarHeaderParameter = {};
48231
+ const localVarQueryParameter = {};
48232
+ await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration);
48233
+ localVarHeaderParameter["Content-Type"] = "application/json";
48234
+ localVarHeaderParameter["Accept"] = "application/json";
48235
+ setSearchParams(localVarUrlObj, localVarQueryParameter);
48236
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
48237
+ localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
48238
+ localVarRequestOptions.data = serializeDataIfNeeded(v4VariablesConnectionsConnectionIdPatchRequest, localVarRequestOptions, configuration);
48239
+ return {
48240
+ url: toPathString(localVarUrlObj),
48241
+ options: localVarRequestOptions
48242
+ };
48243
+ },
48244
+ v4VariablesConnectionsConnectionIdVerifyPost: async (connectionId, options = {}) => {
48245
+ assertParamExists("v4VariablesConnectionsConnectionIdVerifyPost", "connectionId", connectionId);
48246
+ const localVarPath = `/v4/variables/connections/{connectionId}/verify`.replace(`{${"connectionId"}}`, encodeURIComponent(String(connectionId)));
48247
+ const localVarUrlObj = new URL$1(localVarPath, DUMMY_BASE_URL);
48248
+ let baseOptions;
48249
+ if (configuration) {
48250
+ baseOptions = configuration.baseOptions;
48251
+ }
48252
+ const localVarRequestOptions = { method: "POST", ...baseOptions, ...options };
48253
+ const localVarHeaderParameter = {};
48254
+ const localVarQueryParameter = {};
48255
+ await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration);
48256
+ localVarHeaderParameter["Accept"] = "application/json";
48257
+ setSearchParams(localVarUrlObj, localVarQueryParameter);
48258
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
48259
+ localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
48260
+ return {
48261
+ url: toPathString(localVarUrlObj),
48262
+ options: localVarRequestOptions
48263
+ };
48264
+ },
48265
+ v4VariablesConnectionsGet: async (options = {}) => {
48266
+ const localVarPath = `/v4/variables/connections`;
48267
+ const localVarUrlObj = new URL$1(localVarPath, DUMMY_BASE_URL);
48268
+ let baseOptions;
48269
+ if (configuration) {
48270
+ baseOptions = configuration.baseOptions;
48271
+ }
48272
+ const localVarRequestOptions = { method: "GET", ...baseOptions, ...options };
48273
+ const localVarHeaderParameter = {};
48274
+ const localVarQueryParameter = {};
48275
+ await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration);
48276
+ localVarHeaderParameter["Accept"] = "application/json";
48277
+ setSearchParams(localVarUrlObj, localVarQueryParameter);
48278
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
48279
+ localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
48280
+ return {
48281
+ url: toPathString(localVarUrlObj),
48282
+ options: localVarRequestOptions
48283
+ };
48284
+ },
48285
+ v4VariablesConnectionsPost: async (v4VariablesConnectionsPostRequest, options = {}) => {
48286
+ const localVarPath = `/v4/variables/connections`;
48287
+ const localVarUrlObj = new URL$1(localVarPath, DUMMY_BASE_URL);
48288
+ let baseOptions;
48289
+ if (configuration) {
48290
+ baseOptions = configuration.baseOptions;
48291
+ }
48292
+ const localVarRequestOptions = { method: "POST", ...baseOptions, ...options };
48293
+ const localVarHeaderParameter = {};
48294
+ const localVarQueryParameter = {};
48295
+ await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration);
48296
+ localVarHeaderParameter["Content-Type"] = "application/json";
48297
+ localVarHeaderParameter["Accept"] = "application/json";
48298
+ setSearchParams(localVarUrlObj, localVarQueryParameter);
48299
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
48300
+ localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
48301
+ localVarRequestOptions.data = serializeDataIfNeeded(v4VariablesConnectionsPostRequest, localVarRequestOptions, configuration);
48302
+ return {
48303
+ url: toPathString(localVarUrlObj),
48304
+ options: localVarRequestOptions
48305
+ };
48306
+ },
48160
48307
  v4VariablesGet: async (options = {}) => {
48161
48308
  const localVarPath = `/v4/variables/`;
48162
48309
  const localVarUrlObj = new URL$1(localVarPath, DUMMY_BASE_URL);
@@ -48268,6 +48415,36 @@ var import_debug, __require2, FREEFORM_ASSISTANT_ANSWER_KEY = "_freeform", Kadoa
48268
48415
  }, VariablesApiFp = function(configuration) {
48269
48416
  const localVarAxiosParamCreator = VariablesApiAxiosParamCreator(configuration);
48270
48417
  return {
48418
+ async v4VariablesConnectionsConnectionIdDelete(connectionId, options) {
48419
+ const localVarAxiosArgs = await localVarAxiosParamCreator.v4VariablesConnectionsConnectionIdDelete(connectionId, options);
48420
+ const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
48421
+ const localVarOperationServerBasePath = operationServerMap["VariablesApi.v4VariablesConnectionsConnectionIdDelete"]?.[localVarOperationServerIndex]?.url;
48422
+ return (axios2, basePath) => createRequestFunction(localVarAxiosArgs, axios_default, BASE_PATH, configuration)(axios2, localVarOperationServerBasePath || basePath);
48423
+ },
48424
+ async v4VariablesConnectionsConnectionIdPatch(connectionId, v4VariablesConnectionsConnectionIdPatchRequest, options) {
48425
+ const localVarAxiosArgs = await localVarAxiosParamCreator.v4VariablesConnectionsConnectionIdPatch(connectionId, v4VariablesConnectionsConnectionIdPatchRequest, options);
48426
+ const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
48427
+ const localVarOperationServerBasePath = operationServerMap["VariablesApi.v4VariablesConnectionsConnectionIdPatch"]?.[localVarOperationServerIndex]?.url;
48428
+ return (axios2, basePath) => createRequestFunction(localVarAxiosArgs, axios_default, BASE_PATH, configuration)(axios2, localVarOperationServerBasePath || basePath);
48429
+ },
48430
+ async v4VariablesConnectionsConnectionIdVerifyPost(connectionId, options) {
48431
+ const localVarAxiosArgs = await localVarAxiosParamCreator.v4VariablesConnectionsConnectionIdVerifyPost(connectionId, options);
48432
+ const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
48433
+ const localVarOperationServerBasePath = operationServerMap["VariablesApi.v4VariablesConnectionsConnectionIdVerifyPost"]?.[localVarOperationServerIndex]?.url;
48434
+ return (axios2, basePath) => createRequestFunction(localVarAxiosArgs, axios_default, BASE_PATH, configuration)(axios2, localVarOperationServerBasePath || basePath);
48435
+ },
48436
+ async v4VariablesConnectionsGet(options) {
48437
+ const localVarAxiosArgs = await localVarAxiosParamCreator.v4VariablesConnectionsGet(options);
48438
+ const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
48439
+ const localVarOperationServerBasePath = operationServerMap["VariablesApi.v4VariablesConnectionsGet"]?.[localVarOperationServerIndex]?.url;
48440
+ return (axios2, basePath) => createRequestFunction(localVarAxiosArgs, axios_default, BASE_PATH, configuration)(axios2, localVarOperationServerBasePath || basePath);
48441
+ },
48442
+ async v4VariablesConnectionsPost(v4VariablesConnectionsPostRequest, options) {
48443
+ const localVarAxiosArgs = await localVarAxiosParamCreator.v4VariablesConnectionsPost(v4VariablesConnectionsPostRequest, options);
48444
+ const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
48445
+ const localVarOperationServerBasePath = operationServerMap["VariablesApi.v4VariablesConnectionsPost"]?.[localVarOperationServerIndex]?.url;
48446
+ return (axios2, basePath) => createRequestFunction(localVarAxiosArgs, axios_default, BASE_PATH, configuration)(axios2, localVarOperationServerBasePath || basePath);
48447
+ },
48271
48448
  async v4VariablesGet(options) {
48272
48449
  const localVarAxiosArgs = await localVarAxiosParamCreator.v4VariablesGet(options);
48273
48450
  const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
@@ -50422,7 +50599,7 @@ var import_debug, __require2, FREEFORM_ASSISTANT_ANSWER_KEY = "_freeform", Kadoa
50422
50599
  }));
50423
50600
  return channels;
50424
50601
  }
50425
- }, PUBLIC_API_URI, WSS_API_URI, REALTIME_API_URI, SDK_VERSION = "0.38.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 {
50602
+ }, PUBLIC_API_URI, WSS_API_URI, REALTIME_API_URI, SDK_VERSION = "0.39.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 {
50426
50603
  constructor(config2) {
50427
50604
  this.drainingSockets = /* @__PURE__ */ new Set;
50428
50605
  this.lastHeartbeat = Date.now();
@@ -51864,6 +52041,9 @@ var init_dist2 = __esm(() => {
51864
52041
  v5AgentWorkflowAssistantMessage(requestParameters, options) {
51865
52042
  return AgentApiFp(this.configuration).v5AgentWorkflowAssistantMessage(requestParameters.workflowId, requestParameters.workflowAssistantMessageRequest, options).then((request) => request(this.axios, this.basePath));
51866
52043
  }
52044
+ v5AgentWorkflowAssistantTimeline(requestParameters, options) {
52045
+ return AgentApiFp(this.configuration).v5AgentWorkflowAssistantTimeline(requestParameters.workflowId, requestParameters.cursor, requestParameters.limit, options).then((request) => request(this.axios, this.basePath));
52046
+ }
51867
52047
  };
51868
52048
  CrawlerApi = class extends BaseAPI {
51869
52049
  v4CrawlBucketDataFilenameb64Get(requestParameters, options) {
@@ -52095,6 +52275,21 @@ var init_dist2 = __esm(() => {
52095
52275
  }
52096
52276
  };
52097
52277
  VariablesApi = class extends BaseAPI {
52278
+ v4VariablesConnectionsConnectionIdDelete(requestParameters, options) {
52279
+ return VariablesApiFp(this.configuration).v4VariablesConnectionsConnectionIdDelete(requestParameters.connectionId, options).then((request) => request(this.axios, this.basePath));
52280
+ }
52281
+ v4VariablesConnectionsConnectionIdPatch(requestParameters, options) {
52282
+ return VariablesApiFp(this.configuration).v4VariablesConnectionsConnectionIdPatch(requestParameters.connectionId, requestParameters.v4VariablesConnectionsConnectionIdPatchRequest, options).then((request) => request(this.axios, this.basePath));
52283
+ }
52284
+ v4VariablesConnectionsConnectionIdVerifyPost(requestParameters, options) {
52285
+ return VariablesApiFp(this.configuration).v4VariablesConnectionsConnectionIdVerifyPost(requestParameters.connectionId, options).then((request) => request(this.axios, this.basePath));
52286
+ }
52287
+ v4VariablesConnectionsGet(options) {
52288
+ return VariablesApiFp(this.configuration).v4VariablesConnectionsGet(options).then((request) => request(this.axios, this.basePath));
52289
+ }
52290
+ v4VariablesConnectionsPost(requestParameters = {}, options) {
52291
+ return VariablesApiFp(this.configuration).v4VariablesConnectionsPost(requestParameters.v4VariablesConnectionsPostRequest, options).then((request) => request(this.axios, this.basePath));
52292
+ }
52098
52293
  v4VariablesGet(options) {
52099
52294
  return VariablesApiFp(this.configuration).v4VariablesGet(options).then((request) => request(this.axios, this.basePath));
52100
52295
  }
@@ -52699,7 +52894,7 @@ function extractApiMessage(responseBody) {
52699
52894
  if (body.validationErrors && typeof body.validationErrors === "object" && body.validationErrors !== null) {
52700
52895
  const details = Object.entries(body.validationErrors).map(([field, err]) => `${field}: "${err}"`).join(", ");
52701
52896
  if (details) {
52702
- msg = msg ? `${msg} Details: ${details}` : `Validation failed Details: ${details}`;
52897
+ msg = msg ? `${msg} - Details: ${details}` : `Validation failed - Details: ${details}`;
52703
52898
  }
52704
52899
  }
52705
52900
  if (!msg && Array.isArray(body.issues)) {
@@ -52709,7 +52904,7 @@ function extractApiMessage(responseBody) {
52709
52904
  return path ? `${path}: "${message}"` : `"${message}"`;
52710
52905
  }).join(", ");
52711
52906
  if (details)
52712
- msg = `Validation failed Details: ${details}`;
52907
+ msg = `Validation failed - Details: ${details}`;
52713
52908
  }
52714
52909
  return msg;
52715
52910
  }
@@ -52895,14 +53090,14 @@ function registerTools(server, ctx, capabilities) {
52895
53090
  });
52896
53091
  }));
52897
53092
  const urlInputShape = {
52898
- url: exports_external.string().optional().describe("Single URL prefer using 'urls' instead. If both are provided, 'urls' takes precedence."),
53093
+ url: exports_external.string().optional().describe("Single URL - prefer using 'urls' instead. If both are provided, 'urls' takes precedence."),
52899
53094
  urls: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string()).min(1)).optional().describe("Starting URLs for the workflow (array of strings). Also accepts a single URL string.")
52900
53095
  };
52901
53096
  const extractionInputShape = {
52902
53097
  prompt: exports_external.string().optional().describe('Natural language description of what to extract (e.g., "Extract product prices and names"). Required unless templateId is provided.'),
52903
53098
  name: exports_external.string().optional().describe("Optional name for the workflow"),
52904
53099
  entity: exports_external.string().optional().describe("Entity name for extraction (e.g., 'Product', 'Job Posting')"),
52905
- schema: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object(SchemaFieldShape).strict())).optional().describe("Extraction schema fields. If omitted, the AI agent auto-detects the schema. When you do supply fields, mark the one that identifies a record with isKey change detection needs it.")
53100
+ schema: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object(SchemaFieldShape).strict())).optional().describe("Extraction schema fields. If omitted, the AI agent auto-detects the schema. When you do supply fields, mark the one that identifies a record with isKey - change detection needs it.")
52906
53101
  };
52907
53102
  const webhookAuthShape = exports_external.object({
52908
53103
  type: exports_external.enum(["bearer", "basic", "header"]).describe("Authentication type"),
@@ -53030,15 +53225,15 @@ function registerTools(server, ctx, capabilities) {
53030
53225
  return channels;
53031
53226
  }
53032
53227
  server.registerTool("create_workflow", {
53033
- description: "IMPORTANT: One workflow = one source. A single workflow can extract many different fields, tables, and sections from the same URL(s) using a rich schema. " + `Do NOT create separate workflows for different data points on the same page instead, define one workflow with a multi-field schema covering everything needed.
53228
+ description: "IMPORTANT: One workflow = one source. A single workflow can extract many different fields, tables, and sections from the same URL(s) using a rich schema. " + `Do NOT create separate workflows for different data points on the same page - instead, define one workflow with a multi-field schema covering everything needed.
53034
53229
 
53035
53230
  ` + "Create a data extraction workflow using agentic navigation. Supports one-time or scheduled runs. " + "If entity and schema are provided, they guide the extraction; otherwise the AI agent auto-detects the schema from the page. " + "The workflow runs asynchronously and may take several minutes. Do NOT poll or sleep-wait for completion. " + `Return the workflow ID to the user and let them check back later with get_workflow or fetch_data.
53036
53231
 
53037
- ` + "PREFER TEMPLATES: If the user's request matches an existing template, instantiate it via `templateId` instead of writing a fresh prompt/schema. " + "Use `list_templates` to discover available templates and `get_template` to inspect schemas before deciding. " + "When `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 Julie realtime monitoring (watching a page for data changes and alerting), use the create_realtime_monitor tool instead.",
53232
+ ` + "PREFER TEMPLATES: If the user's request matches an existing template, instantiate it via `templateId` instead of writing a fresh prompt/schema. " + "Use `list_templates` to discover available templates and `get_template` to inspect schemas before deciding. " + "When creating from a template, call this tool with the template's `templateId`, the source `urls`, and optional `templateVersion` only. Never copy the template prompt, entity, or schema into standalone creation, and never silently match or rewrite inline configuration.\n\n" + "NOTE: This tool is for one-time or scheduled extraction ONLY. " + "For continuous Julie realtime monitoring (watching a page for data changes and alerting), use the create_realtime_monitor tool instead.",
53038
53233
  inputSchema: strictSchema({
53039
53234
  ...extractionInputShape,
53040
53235
  ...urlInputShape,
53041
- 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."),
53236
+ templateId: exports_external.string().optional().describe("Instantiate this workflow from a published template. Pass the templateId, source urls, and optional templateVersion. Do not copy prompt/entity/schema from the template into this call - they are inherited from the published template version, and the workflow's output conforms to its declared schema. Discover templates via list_templates and get_template."),
53042
53237
  templateVersion: exports_external.preprocess(coerceNumber(), exports_external.number()).optional().describe("Specific published template version (integer) to instantiate. Defaults to the latest published version when templateId is set."),
53043
53238
  description: exports_external.string().max(500).optional().describe("Description of what this workflow does (max 500 characters)"),
53044
53239
  tags: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string())).optional().describe("Tags for organizing workflows"),
@@ -53079,7 +53274,7 @@ function registerTools(server, ctx, capabilities) {
53079
53274
  let workflowId;
53080
53275
  if (args.templateId) {
53081
53276
  if (args.prompt || args.entity || args.schema) {
53082
- return errorResult("When 'templateId' is set, 'prompt', 'entity', and 'schema' must NOT be supplied they are inherited from the template version.");
53277
+ return errorResult("When 'templateId' is set, 'prompt', 'entity', and 'schema' must NOT be supplied - they are inherited from the template version.");
53083
53278
  }
53084
53279
  const { id } = await ctx.client.workflow.create({
53085
53280
  urls,
@@ -53235,7 +53430,7 @@ function registerTools(server, ctx, capabilities) {
53235
53430
  });
53236
53431
  }));
53237
53432
  server.registerTool("get_workflow", {
53238
- description: "Get canonical workflow details, including Assistant/session linkage, extraction intent, template-controlled parts, stale-data state, job/run state, schedule timezone, location, monitoring, validation, and realtime health. For Assistant-built workflows, extractionSpecnot the legacy promptis authoritative.",
53433
+ description: "Get canonical workflow details, including Assistant/session linkage, extraction intent, template-controlled parts, stale-data state, job/run state, schedule timezone, location, monitoring, validation, and realtime health. For Assistant-built workflows, extractionSpec - not the legacy prompt - is authoritative.",
53239
53434
  inputSchema: {
53240
53435
  workflowId: exports_external.string().describe("The workflow ID")
53241
53436
  },
@@ -53387,6 +53582,30 @@ function registerTools(server, ctx, capabilities) {
53387
53582
  pendingQuestion: pauseState?.pendingQuestion ?? null
53388
53583
  });
53389
53584
  }));
53585
+ server.registerTool("get_workflow_assistant_timeline", {
53586
+ description: "Read the customer-visible conversation history for a workflow Assistant, including user messages, Assistant messages, and clarification questions with answers. " + "Works for both Shelly/Lighthouse and Julie workflows. Use get_workflow_history instead for configuration revisions. Results are chronological within each page; use nextCursor to request older items when hasMore is true.",
53587
+ inputSchema: strictSchema({
53588
+ workflowId: exports_external.string().min(1).describe("The workflow ID"),
53589
+ cursor: exports_external.string().min(1).optional().describe("Opaque nextCursor from the previous page"),
53590
+ limit: exports_external.preprocess(coerceNumber(), exports_external.number().int().min(1).max(100)).optional().describe("Maximum history items to return (default: 50, maximum: 100)")
53591
+ }),
53592
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }
53593
+ }, withErrorHandling("get_workflow_assistant_timeline", async (args) => {
53594
+ const timeline = await ctx.client.assistant.getTimeline(args.workflowId, {
53595
+ cursor: args.cursor,
53596
+ limit: args.limit
53597
+ });
53598
+ if (timeline.workflowId !== args.workflowId) {
53599
+ return errorResult(`The Assistant timeline returned workflow ID '${timeline.workflowId}' instead of '${args.workflowId}'.`);
53600
+ }
53601
+ return jsonResult({
53602
+ workflowId: timeline.workflowId,
53603
+ sessionId: timeline.sessionId,
53604
+ items: timeline.items,
53605
+ pagination: timeline.pagination,
53606
+ dashboardUrl: workflowDashboardUrl(timeline.workflowId)
53607
+ });
53608
+ }));
53390
53609
  server.registerTool("answer_workflow_assistant_question", {
53391
53610
  description: "Answer the workflow Assistant's currently pending clarification question and resume it. " + "Use `answer` for a freeform response, or `answers` for the structured key/value response described by get_workflow_assistant. Provide exactly one. The question ID must still be current.",
53392
53611
  inputSchema: strictSchema({
@@ -53599,7 +53818,7 @@ function registerTools(server, ctx, capabilities) {
53599
53818
  });
53600
53819
  }));
53601
53820
  server.registerTool("list_workflow_runs", {
53602
- description: "List a workflow's execution run history each run's status, start/finish time, " + "record count, and errors. Use to answer 'when did this last succeed?' " + "(status='success', limit=1) or 'recent success/failure pattern?'. Distinct from " + "get_workflow_history, which is the config audit log. `status` is normally " + "success | failed | in_progress; a run whose backend state this server does not " + "recognize reports that raw state verbatim, so treat any other value as unknown " + "rather than as a failure.",
53821
+ description: "List a workflow's execution run history - each run's status, start/finish time, " + "record count, and errors. Use to answer 'when did this last succeed?' " + "(status='success', limit=1) or 'recent success/failure pattern?'. Distinct from " + "get_workflow_history, which is the config audit log. `status` is normally " + "success | failed | in_progress; a run whose backend state this server does not " + "recognize reports that raw state verbatim, so treat any other value as unknown " + "rather than as a failure.",
53603
53822
  inputSchema: {
53604
53823
  workflowId: exports_external.string().describe("The workflow ID"),
53605
53824
  status: exports_external.enum(["success", "failed", "in_progress"]).optional().describe("Filter runs by outcome"),
@@ -53628,7 +53847,7 @@ function registerTools(server, ctx, capabilities) {
53628
53847
  });
53629
53848
  }));
53630
53849
  server.registerTool("run_workflow", {
53631
- description: "Run a workflow to extract fresh data. The run is asynchronous and may take several minutes. Do NOT poll or sleep-wait for completion. Return the workflow ID to the user and let them check status with get_workflow or fetch results later with fetch_data.",
53850
+ description: "Run a workflow to extract fresh data, including retrying a failed one-time or scheduled workflow. Preserve the existing workflow ID and configuration - do not delete and recreate the workflow solely to retry it. The run is asynchronous and may take several minutes. Do NOT poll or sleep-wait for completion. Return the workflow ID to the user and let them check status with get_workflow or fetch results later with fetch_data. Realtime workflows cannot be manually run.",
53632
53851
  inputSchema: {
53633
53852
  workflowId: exports_external.string().describe("The workflow ID to run"),
53634
53853
  limit: exports_external.preprocess(coerceNumber(), exports_external.number()).optional().describe("Maximum number of records to extract (default: 1000)")
@@ -53651,7 +53870,7 @@ function registerTools(server, ctx, capabilities) {
53651
53870
  const FETCH_DATA_DEFAULT_LIMIT = 50;
53652
53871
  const FETCH_DATA_MAX_LIMIT = 500;
53653
53872
  server.registerTool("fetch_data", {
53654
- description: "Get a PAGE of extracted data from a workflow. Use ONLY for previews, sorted/filtered slices, or explicit 'first N rows' / 'top N' queries (capped at 500 rows per call). Do NOT use this to retrieve a full dataset, 'all rows', or anything the user wants to analyze in Excel / pandas / duckdb use export_data for those. Data is only available after the workflow run has completed (status is no longer 'Running' or 'Validating'). Do NOT poll or sleep-wait for completion.",
53873
+ description: "Get a PAGE of extracted data from a workflow. Use ONLY for previews, sorted/filtered slices, or explicit 'first N rows' / 'top N' queries (capped at 500 rows per call). Do NOT use this to retrieve a full dataset, 'all rows', or anything the user wants to analyze in Excel / pandas / duckdb - use export_data for those. Data is only available after the workflow run has completed (status is no longer 'Running' or 'Validating'). Do NOT poll or sleep-wait for completion.",
53655
53874
  inputSchema: {
53656
53875
  workflowId: exports_external.string().describe("The workflow ID"),
53657
53876
  limit: exports_external.preprocess(coerceNumber(), exports_external.number()).optional().describe(`Maximum number of records to return. Default ${FETCH_DATA_DEFAULT_LIMIT}, max ${FETCH_DATA_MAX_LIMIT}.`),
@@ -53686,7 +53905,7 @@ function registerTools(server, ctx, capabilities) {
53686
53905
  return jsonResult(result);
53687
53906
  }));
53688
53907
  server.registerTool("export_data", {
53689
- description: "PREFERRED tool for retrieving a workflow's FULL dataset. Materializes the data to object storage and returns a signed download URL. Use this whenever the user wants 'all rows', 'the full dataset', 'everything', an export, or anything destined for Excel / pandas / duckdb / a CSV file even for small workflows. The URL is self-authenticating (open with `fetch(url)`, no Authorization header). Use fetch_data ONLY when the user explicitly asks for a small preview slice (e.g., 'first 10', 'top N sorted by X').",
53908
+ description: "PREFERRED tool for retrieving a workflow's FULL dataset. Materializes the data to object storage and returns a signed download URL. Use this whenever the user wants 'all rows', 'the full dataset', 'everything', an export, or anything destined for Excel / pandas / duckdb / a CSV file - even for small workflows. The URL is self-authenticating (open with `fetch(url)`, no Authorization header). Use fetch_data ONLY when the user explicitly asks for a small preview slice (e.g., 'first 10', 'top N sorted by X').",
53690
53909
  inputSchema: {
53691
53910
  workflowId: exports_external.string().describe("The workflow ID"),
53692
53911
  format: exports_external.enum(["csv", "json"]).optional().describe("Export format. Default 'csv'."),
@@ -53812,7 +54031,7 @@ function registerTools(server, ctx, capabilities) {
53812
54031
  });
53813
54032
  }));
53814
54033
  server.registerTool("pause_workflow", {
53815
- description: "Pause an ACTIVE workflow so it stops running on its schedule. Requires the workflow to be in ACTIVE state pausing a workflow that is currently running, already paused, or in PREVIEW will return an error. Use approve_workflow to resume a paused workflow.",
54034
+ description: "Pause an ACTIVE workflow so it stops running on its schedule. Requires the workflow to be in ACTIVE state - pausing a workflow that is currently running, already paused, or in PREVIEW will return an error. Use approve_workflow to resume a paused workflow.",
53816
54035
  inputSchema: {
53817
54036
  workflowId: exports_external.string().min(1).describe("The workflow ID to pause")
53818
54037
  },
@@ -53826,17 +54045,17 @@ function registerTools(server, ctx, capabilities) {
53826
54045
  });
53827
54046
  }));
53828
54047
  server.registerTool("update_workflow", {
53829
- description: `Update a workflow's configuration. All fields are optional only provided fields will be updated. Use this to change the name, URLs, extraction schema, entity, prompt, schedule, or other metadata.
54048
+ description: `Update a workflow's configuration. All fields are optional - only provided fields will be updated. Use this to change the name, URLs, extraction schema, entity, prompt, schedule, or other metadata.
53830
54049
 
53831
54050
  ` + `IMPORTANT: You cannot change a workflow's interval to or from REAL_TIME. Julie realtime workflows are architecturally different from scheduled workflows and must be created with create_realtime_monitor from the start. Existing workflows cannot be converted between these modes in place; do not delete and recreate a workflow as an update workaround.
53832
54051
 
53833
- ` + "ASSISTANT-OWNED INTENT: Use request_workflow_updatenot `userPrompt`—for agent-built workflow changes to extraction intent, navigation, pagination, data sourcing, repair, or generated scripts. `userPrompt` may be rejected with `SHELLY_INTENT_REQUIRES_EXTRACTION_SPEC` because those workflows' canonical intent is owned by the Assistant. " + "Call get_workflow first. If its template.controlledParts contains the setting, create and apply a template version instead of overriding the workflow directly. NEVER delete and recreate a workflow to work around an update limitationthat changes workflowId, breaks downstream tables/connectors, and discards history.",
54052
+ ` + "ASSISTANT-OWNED INTENT: Use request_workflow_update - not `userPrompt` - for agent-built workflow changes to extraction intent, navigation, pagination, data sourcing, repair, or generated scripts. `userPrompt` may be rejected with `SHELLY_INTENT_REQUIRES_EXTRACTION_SPEC` because those workflows' canonical intent is owned by the Assistant. " + "Call get_workflow first. If its template.controlledParts contains the setting, create and apply a template version instead of overriding the workflow directly. NEVER delete and recreate a workflow to work around an update limitation - that changes workflowId, breaks downstream tables/connectors, and discards history.",
53834
54053
  inputSchema: strictSchema({
53835
54054
  workflowId: exports_external.string().describe("The workflow ID to update"),
53836
54055
  name: exports_external.string().optional().describe("New name for the workflow"),
53837
54056
  urls: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string()).min(1)).optional().describe("New target URLs for the workflow (array of strings). Also accepts a single URL string."),
53838
54057
  entity: exports_external.string().optional().describe("Entity name for extraction (e.g., 'Product', 'Job Posting')"),
53839
- schema: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object(SchemaFieldShape).strict())).optional().describe("New extraction schema fields. This REPLACES the whole schema list every field you want to keep, each with its isKey flag, or the omitted ones and their flags are lost. Call get_workflow first and edit the schema it returns."),
54058
+ schema: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object(SchemaFieldShape).strict())).optional().describe("New extraction schema fields. This REPLACES the whole schema - list every field you want to keep, each with its isKey flag, or the omitted ones and their flags are lost. Call get_workflow first and edit the schema it returns."),
53840
54059
  description: exports_external.string().max(500).optional().describe("Workflow description (max 500 characters)"),
53841
54060
  tags: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string())).optional().describe("Tags for organizing workflows"),
53842
54061
  userPrompt: exports_external.string().optional().describe("Navigation prompt for agentic-navigation mode (10-5000 characters)"),
@@ -53858,7 +54077,7 @@ function registerTools(server, ctx, capabilities) {
53858
54077
  "FOUR_WEEKS",
53859
54078
  "MONTHLY",
53860
54079
  "CUSTOM"
53861
- ]).optional().describe("How often the workflow should run. CUSTOM requires schedules. Note: REAL_TIME is NOT allowed here realtime workflows must be created via create_realtime_monitor."),
54080
+ ]).optional().describe("How often the workflow should run. CUSTOM requires schedules. Note: REAL_TIME is NOT allowed here - realtime workflows must be created via create_realtime_monitor."),
53862
54081
  schedules: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string())).optional().describe("Cron expressions for CUSTOM update interval"),
53863
54082
  timezone: exports_external.string().min(1).optional().describe("IANA timezone for cron schedules, for example America/New_York. A timezone-only update preserves existing cron expressions."),
53864
54083
  location: exports_external.preprocess(coerceJson(), LocationSchema).optional().describe("Scraping location: {type:'auto'} or {type:'manual', isoCode:'US'}"),
@@ -54277,22 +54496,29 @@ function registerTools(server, ctx, capabilities) {
54277
54496
  });
54278
54497
  }));
54279
54498
  server.registerTool("list_templates", {
54280
- description: "List all templates in the current team. Templates define reusable configurations (prompt, schema, notifications).",
54499
+ description: "List all templates in the current team. Templates define reusable configurations (prompt, schema, validation rules, notifications, and frequency). Use this to find a matching template before creating a workflow. To instantiate a template, call create_workflow with its templateId, source URLs, and optional templateVersion - never copy the template prompt, entity, or schema into standalone creation.",
54281
54500
  inputSchema: strictSchema({}),
54282
54501
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }
54283
54502
  }, withErrorHandling("list_templates", async () => {
54284
54503
  const templates = await ctx.client.template.list();
54285
- return jsonResult({ templates, count: templates.length });
54504
+ return jsonResult({
54505
+ templates,
54506
+ count: templates.length,
54507
+ instructions: "To instantiate a listed template, call create_workflow with the matching templateId, source URLs, and optional templateVersion. Do not copy the template prompt, entity, or schema into standalone creation."
54508
+ });
54286
54509
  }));
54287
54510
  server.registerTool("get_template", {
54288
- description: "Get a template by ID, including all published versions.",
54511
+ description: "Get a template by ID, including all published versions and their schemas. After inspecting it, instantiate the template with create_workflow using templateId, source URLs, and optional templateVersion. Never copy the returned prompt, entity, or schema into standalone creation - those values are inherited when templateId is passed.",
54289
54512
  inputSchema: strictSchema({
54290
54513
  templateId: exports_external.string().describe("The template ID")
54291
54514
  }),
54292
54515
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }
54293
54516
  }, withErrorHandling("get_template", async (args) => {
54294
54517
  const template = await ctx.client.template.get(args.templateId);
54295
- return jsonResult({ template });
54518
+ return jsonResult({
54519
+ template,
54520
+ instructions: `To instantiate this template, call create_workflow with templateId "${args.templateId}", source URLs, and optional templateVersion. Do not copy this template's prompt, entity, or schema into standalone creation.`
54521
+ });
54296
54522
  }));
54297
54523
  server.registerTool("create_template", {
54298
54524
  description: "Create a new template. After creation, use create_template_version to publish a version with prompt, schema, and notifications.",
@@ -54315,7 +54541,7 @@ function registerTools(server, ctx, capabilities) {
54315
54541
  server.registerTool("update_template", {
54316
54542
  description: `Update a template's name or description ONLY. At least one of the two must be provided.
54317
54543
 
54318
- ` + "This tool CANNOT change what linked workflows inherit prompt, schema, validation rules, notifications and frequency all live in template *versions*, which are immutable snapshots. " + "To change any of those, publish a new version with `create_template_version` (the version IS the edit), then roll it out to linked workflows with `apply_template_version`. " + "There is no in-place edit of a published version, and never report a prompt or schema change as impossible versioning is the supported path.",
54544
+ ` + "This tool CANNOT change what linked workflows inherit - prompt, schema, validation rules, notifications and frequency all live in template *versions*, which are immutable snapshots. " + "To change any of those, publish a new version with `create_template_version` (the version IS the edit), then roll it out to linked workflows with `apply_template_version`. " + "There is no in-place edit of a published version, and never report a prompt or schema change as impossible - versioning is the supported path.",
54319
54545
  inputSchema: strictSchema({
54320
54546
  templateId: exports_external.string().describe("The template ID to update"),
54321
54547
  name: exports_external.preprocess(coerceNull(), exports_external.string().optional()).optional().describe("New template name"),
@@ -54371,7 +54597,7 @@ function registerTools(server, ctx, capabilities) {
54371
54597
  }))).optional().describe("Predefined categories for a CLASSIFICATION field ({title, definition}[]). Required for fieldType=CLASSIFICATION; omitted otherwise.")
54372
54598
  };
54373
54599
  server.registerTool("create_template_version", {
54374
- description: `Publish a new version of a template. Versions capture the full workflow config: prompt, schema, and notifications. All fields are optional include only what this version should set.
54600
+ description: `Publish a new version of a template. Versions capture the full workflow config: prompt, schema, and notifications. All fields are optional - include only what this version should set.
54375
54601
 
54376
54602
  ` + "THIS IS HOW YOU EDIT A TEMPLATE'S PROMPT OR SCHEMA. Published versions are immutable, so 'changing the template prompt' means publishing a new version here and then calling `apply_template_version` to push it onto linked workflows. " + "`update_template` only renames a template; it cannot touch prompt or schema.",
54377
54603
  inputSchema: strictSchema({
@@ -54380,7 +54606,7 @@ function registerTools(server, ctx, capabilities) {
54380
54606
  schemaId: exports_external.string().optional().describe("Existing schema ID to reference (mutually exclusive with schemaFields)"),
54381
54607
  schemaFields: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object(TemplateSchemaFieldShape).strict())).optional().describe("Inline schema fields to create a new schema (mutually exclusive with schemaId)"),
54382
54608
  schemaEntity: exports_external.string().optional().describe("Entity name for the inline schema"),
54383
- 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."),
54609
+ schemaValidationRules: exports_external.preprocess(coerceJson(), SchemaValidationRulesSchema).optional().describe('Per-field schema validation rules keyed by field name. Each rule uses kind STRING, NUMBER, OTHER, OBJECT, or ARRAY and nested rules are supported. Rule metadata requires editedBy (default|llm|agent|ops|user) and an ISO-8601 editedAt timestamp. Example: { "price": { "kind": "NUMBER", "minimum": { "value": 0, "editedBy": "user", "editedAt": "2026-01-01T00:00:00.000Z" } } }. Not inherited from prior versions - omitting this on a new version drops any rules the previous version had.'),
54384
54610
  notifications: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object({
54385
54611
  eventType: exports_external.string().describe("Notification event type"),
54386
54612
  eventConfiguration: exports_external.preprocess(coerceJson(), exports_external.record(exports_external.string(), exports_external.unknown())).optional(),
@@ -54461,7 +54687,7 @@ function registerTools(server, ctx, capabilities) {
54461
54687
  return jsonResult({ schemas: schemas4, count: schemas4.length });
54462
54688
  }));
54463
54689
  server.registerTool("link_template_to_workflows", {
54464
- description: "Link one or more EXISTING workflows to a template in a single call. " + "This is the bulk equivalent of creating a workflow with `templateId`: linked workflows adopt the template's configuration (prompt, schema, notifications) and stay in sync with it. " + "The template ENFORCES its schema on linked workflows 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.",
54690
+ description: "Link one or more EXISTING workflows to a template in a single call. " + "This is the bulk equivalent of creating a workflow with `templateId`: linked workflows adopt the template's configuration (prompt, schema, notifications) and stay in sync with it. " + "The template ENFORCES its schema on linked workflows - their extracted output conforms to the template's declared field names, so this is the way to make many workflows produce a consistent, canonical schema. " + "Use `list_templates`/`get_template` to find the template, and `list_workflows` to find the workflow IDs. " + "Set `force: true` to relink workflows already linked to a different template.",
54465
54691
  inputSchema: strictSchema({
54466
54692
  templateId: exports_external.string().describe("The template ID to link workflows to"),
54467
54693
  workflowIds: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string()).min(1)).describe("Workflow IDs to link to the template (array of strings). Also accepts a single ID string."),
@@ -54478,7 +54704,7 @@ function registerTools(server, ctx, capabilities) {
54478
54704
  return jsonResult({
54479
54705
  success: false,
54480
54706
  conflicts: result.conflicts,
54481
- 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."
54707
+ message: `Cannot link - ${result.conflicts.length} workflow(s) are already linked to another template: ${summary}. ` + "Re-run with force: true to move them to this template."
54482
54708
  });
54483
54709
  }
54484
54710
  return jsonResult({
@@ -54538,7 +54764,7 @@ function registerTools(server, ctx, capabilities) {
54538
54764
  });
54539
54765
  }));
54540
54766
  }
54541
- var SchemaFieldShape, LocationSchema, MonitoringValueOperators, MonitoringValuelessOperators, MonitoringConditionOperatorSchema, MonitoringConditionSchema, MonitoringSchema, RESUMABLE_ASSISTANT_STATUSES, ACTIVE_ASSISTANT_STATUSES, IDLE_ASSISTANT_STATUSES, CLOSED_ASSISTANT_STATUSES, DASHBOARD_BASE_URL = "https://www.kadoa.com", WORKFLOW_AUDIT_WATCHED_KEYS;
54767
+ var SchemaFieldShape, SchemaValidationAttributionShape, SchemaValidationPresenceRule, SchemaValidationUniquenessRule, SchemaValidationStringLengthRule, SchemaValidationStringFormatRule, SchemaValidationFieldRulesSchema, SchemaValidationRulesSchema, LocationSchema, MonitoringValueOperators, MonitoringValuelessOperators, MonitoringConditionOperatorSchema, MonitoringConditionSchema, MonitoringSchema, RESUMABLE_ASSISTANT_STATUSES, ACTIVE_ASSISTANT_STATUSES, IDLE_ASSISTANT_STATUSES, CLOSED_ASSISTANT_STATUSES, DASHBOARD_BASE_URL = "https://www.kadoa.com", WORKFLOW_AUDIT_WATCHED_KEYS;
54542
54768
  var init_tools = __esm(() => {
54543
54769
  init_dist2();
54544
54770
  init_zod();
@@ -54548,8 +54774,105 @@ var init_tools = __esm(() => {
54548
54774
  description: exports_external.string().optional().describe("What this field contains"),
54549
54775
  example: exports_external.string().describe("Example value"),
54550
54776
  dataType: exports_external.enum(["STRING", "NUMBER", "BOOLEAN", "DATE", "DATETIME", "MONEY", "IMAGE", "LINK", "OBJECT", "ARRAY"]).optional().describe("Data type for the field"),
54551
- isKey: exports_external.preprocess(coerceBoolean(), exports_external.boolean()).optional().describe("Marks this field as a key field the stable identity used to match records across runs. Change detection diffs records by their key fields, so a monitored or real-time workflow without one cannot tell an updated record from a new one. Set it on whatever uniquely identifies a row (a detail URL, an ID, a ticker).")
54777
+ isKey: exports_external.preprocess(coerceBoolean(), exports_external.boolean()).optional().describe("Marks this field as a key field - the stable identity used to match records across runs. Change detection diffs records by their key fields, so a monitored or real-time workflow without one cannot tell an updated record from a new one. Set it on whatever uniquely identifies a row (a detail URL, an ID, a ticker).")
54552
54778
  };
54779
+ SchemaValidationAttributionShape = {
54780
+ editedBy: exports_external.enum(["default", "llm", "agent", "ops", "user"]).describe("Who last edited this rule: default, llm, agent, ops, or user"),
54781
+ editedByLabel: exports_external.string().optional().describe("Optional editor identifier"),
54782
+ editedAt: exports_external.string().datetime().describe("ISO-8601 timestamp when this rule was last edited")
54783
+ };
54784
+ SchemaValidationPresenceRule = exports_external.object({
54785
+ target: exports_external.number().int().min(0).max(100).step(20).describe("Expected percentage of rows with a value"),
54786
+ ...SchemaValidationAttributionShape
54787
+ }).strict();
54788
+ SchemaValidationUniquenessRule = exports_external.object({
54789
+ target: exports_external.number().int().min(0).max(100).step(20).describe("Expected percentage of rows with a unique value"),
54790
+ ...SchemaValidationAttributionShape
54791
+ }).strict();
54792
+ SchemaValidationStringLengthRule = exports_external.object({
54793
+ value: exports_external.number().int().min(0).describe("String length bound"),
54794
+ ...SchemaValidationAttributionShape
54795
+ }).strict();
54796
+ SchemaValidationStringFormatRule = exports_external.discriminatedUnion("kind", [
54797
+ exports_external.object({
54798
+ kind: exports_external.literal("FREE_TEXT"),
54799
+ charset: exports_external.discriminatedUnion("kind", [
54800
+ exports_external.object({
54801
+ kind: exports_external.literal("PRESET"),
54802
+ preset: exports_external.enum(["natural_language", "alphanumeric", "alpha"])
54803
+ }).strict()
54804
+ ]),
54805
+ ...SchemaValidationAttributionShape
54806
+ }).strict(),
54807
+ exports_external.object({
54808
+ kind: exports_external.literal("FORMAT"),
54809
+ source: exports_external.discriminatedUnion("kind", [
54810
+ exports_external.object({
54811
+ kind: exports_external.literal("PRESET"),
54812
+ preset: exports_external.enum(["url", "email", "phone", "date", "datetime", "time", "uuid", "slug"])
54813
+ }).strict(),
54814
+ exports_external.object({
54815
+ kind: exports_external.literal("CUSTOM"),
54816
+ pattern: exports_external.string().min(1).describe("Regular expression pattern")
54817
+ }).strict()
54818
+ ]),
54819
+ ...SchemaValidationAttributionShape
54820
+ }).strict(),
54821
+ exports_external.object({
54822
+ kind: exports_external.literal("LIST"),
54823
+ source: exports_external.discriminatedUnion("kind", [
54824
+ exports_external.object({
54825
+ kind: exports_external.literal("PRESET"),
54826
+ preset: exports_external.enum(["language2", "country2", "country3", "currency3", "month3", "usState2"])
54827
+ }).strict(),
54828
+ exports_external.object({
54829
+ kind: exports_external.literal("CUSTOM"),
54830
+ values: exports_external.array(exports_external.string().min(1)).min(1).describe("Allowed string values")
54831
+ }).strict()
54832
+ ]),
54833
+ ...SchemaValidationAttributionShape
54834
+ }).strict()
54835
+ ]);
54836
+ SchemaValidationFieldRulesSchema = exports_external.lazy(() => exports_external.discriminatedUnion("kind", [
54837
+ exports_external.object({
54838
+ kind: exports_external.literal("STRING"),
54839
+ presence: SchemaValidationPresenceRule.optional(),
54840
+ uniqueness: SchemaValidationUniquenessRule.optional(),
54841
+ minLength: SchemaValidationStringLengthRule.optional(),
54842
+ maxLength: SchemaValidationStringLengthRule.optional(),
54843
+ minHtmlElements: SchemaValidationStringLengthRule.optional(),
54844
+ maxHtmlElements: SchemaValidationStringLengthRule.optional(),
54845
+ format: SchemaValidationStringFormatRule.optional()
54846
+ }).strict(),
54847
+ exports_external.object({
54848
+ kind: exports_external.literal("NUMBER"),
54849
+ presence: SchemaValidationPresenceRule.optional(),
54850
+ uniqueness: SchemaValidationUniquenessRule.optional(),
54851
+ minimum: exports_external.object({ value: exports_external.number().describe("Minimum numeric value"), ...SchemaValidationAttributionShape }).strict().optional(),
54852
+ maximum: exports_external.object({ value: exports_external.number().describe("Maximum numeric value"), ...SchemaValidationAttributionShape }).strict().optional(),
54853
+ maxDecimalPlaces: exports_external.object({ value: exports_external.number().int().min(0).max(16), ...SchemaValidationAttributionShape }).strict().optional()
54854
+ }).strict(),
54855
+ exports_external.object({
54856
+ kind: exports_external.literal("OTHER"),
54857
+ presence: SchemaValidationPresenceRule.optional(),
54858
+ uniqueness: SchemaValidationUniquenessRule.optional()
54859
+ }).strict(),
54860
+ exports_external.object({
54861
+ kind: exports_external.literal("OBJECT"),
54862
+ presence: SchemaValidationPresenceRule.optional(),
54863
+ uniqueness: SchemaValidationUniquenessRule.optional(),
54864
+ properties: exports_external.record(exports_external.string(), SchemaValidationFieldRulesSchema)
54865
+ }).strict(),
54866
+ exports_external.object({
54867
+ kind: exports_external.literal("ARRAY"),
54868
+ presence: SchemaValidationPresenceRule.optional(),
54869
+ uniqueness: SchemaValidationUniquenessRule.optional(),
54870
+ minItems: exports_external.object({ value: exports_external.number().int().min(0), ...SchemaValidationAttributionShape }).strict().optional(),
54871
+ maxItems: exports_external.object({ value: exports_external.number().int().min(0), ...SchemaValidationAttributionShape }).strict().optional(),
54872
+ items: SchemaValidationFieldRulesSchema.optional()
54873
+ }).strict()
54874
+ ]));
54875
+ SchemaValidationRulesSchema = exports_external.record(exports_external.string(), SchemaValidationFieldRulesSchema);
54553
54876
  LocationSchema = exports_external.object({
54554
54877
  type: exports_external.enum(["auto", "manual"]),
54555
54878
  isoCode: exports_external.string().trim().min(2).optional()
@@ -54638,7 +54961,7 @@ var package_default;
54638
54961
  var init_package = __esm(() => {
54639
54962
  package_default = {
54640
54963
  name: "@kadoa/mcp",
54641
- version: "0.5.20",
54964
+ version: "0.5.22",
54642
54965
  description: "Kadoa MCP Server — manage workflows from Claude Desktop, Cursor, and other MCP clients",
54643
54966
  type: "module",
54644
54967
  main: "dist/index.js",
@@ -54662,7 +54985,7 @@ var init_package = __esm(() => {
54662
54985
  prepublishOnly: "bun run check-types && bun run test:unit && bun run build"
54663
54986
  },
54664
54987
  dependencies: {
54665
- "@kadoa/node-sdk": "^0.38.0",
54988
+ "@kadoa/node-sdk": "^0.39.0",
54666
54989
  "@modelcontextprotocol/sdk": "^1.26.0",
54667
54990
  express: "^5.2.1",
54668
54991
  "express-rate-limit": "^8.2.1",
@@ -60241,21 +60564,22 @@ async function createServer(auth, options) {
60241
60564
  const server = new McpServer({ name: "kadoa", version: package_default.version }, {
60242
60565
  instructions: [
60243
60566
  "IMPORTANT: One workflow = one source. A single workflow can extract many different fields, tables, and sections from the same URL(s) using a rich schema.",
60244
- "Do NOT create multiple workflows for different data points on the same page \u2014 instead, define one workflow with a multi-field schema covering everything needed.",
60567
+ "Do NOT create multiple workflows for different data points on the same page - instead, define one workflow with a multi-field schema covering everything needed.",
60245
60568
  "",
60246
60569
  "Kadoa workflows use agentic navigation: the AI agent can browse pages, click buttons, fill forms, select dropdowns, paginate, open detail pages, and handle multi-step interactions.",
60247
60570
  "Describe the full navigation steps in the prompt (e.g., 'select year 2022 from the dropdown, click Search, extract the table, then repeat for 2023').",
60248
60571
  "",
60249
- "Workflow lifecycle: create_workflow \u2192 get_workflow (check status) \u2192 fetch_data (get results). Workflows run asynchronously \u2014 never poll or sleep-wait.",
60572
+ "Workflow lifecycle: create_workflow \u2192 get_workflow (check status) \u2192 fetch_data (get results). Workflows run asynchronously - never poll or sleep-wait.",
60250
60573
  "",
60251
60574
  "Use create_realtime_monitor only when the user wants continuous Julie change detection with alerts. It first persists notification channels, then asynchronously returns workflow/session/thread/job IDs; use the workflow Assistant tools for follow-up status, questions, and controls.",
60252
60575
  "For one-time or scheduled extraction, use create_workflow. Use scrape for an immediate raw HTML or markdown fetch from one URL. Use create_workflow for structured extraction, recurring runs, monitoring, or navigation-heavy jobs.",
60576
+ "If a one-time or scheduled workflow fails, retry it with run_workflow using the existing workflow ID. Do NOT delete and recreate the workflow just to retry it - preserve its workflow ID and configuration. Realtime workflows cannot be manually run.",
60253
60577
  "Use list_changes and get_change to retrieve detected diffs from realtime monitoring workflows.",
60254
60578
  "",
60255
60579
  "Schema tips: Use descriptive field names and examples. Group related data under one entity.",
60256
- "The AI agent uses the schema + prompt to understand what to extract \u2014 a detailed prompt with a comprehensive schema produces better results than multiple simple workflows.",
60580
+ "The AI agent uses the schema + prompt to understand what to extract - a detailed prompt with a comprehensive schema produces better results than multiple simple workflows.",
60257
60581
  "",
60258
- "Templates enforce their schema: a workflow created from a template (create_workflow with templateId) or linked to one (link_template_to_workflows) produces output whose field names conform to the template's declared schema. Use templates when you need many workflows to return a consistent, canonical set of fields \u2014 do NOT assume the extractor will drift field names away from a template's schema."
60582
+ "Templates enforce their schema: a workflow created from a template (create_workflow with templateId) or linked to one (link_template_to_workflows) produces output whose field names conform to the template's declared schema. Use templates when you need many workflows to return a consistent, canonical set of fields - do NOT assume the extractor will drift field names away from a template's schema."
60259
60583
  ].join(`
60260
60584
  `)
60261
60585
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kadoa/mcp",
3
- "version": "0.5.20",
3
+ "version": "0.5.22",
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.38.0",
27
+ "@kadoa/node-sdk": "^0.39.0",
28
28
  "@modelcontextprotocol/sdk": "^1.26.0",
29
29
  "express": "^5.2.1",
30
30
  "express-rate-limit": "^8.2.1",