@kadoa/mcp 0.3.5-rc.4 → 0.3.5-rc.6

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 (2) hide show
  1. package/dist/index.js +92 -16
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -49167,6 +49167,9 @@ var init_client2 = __esm(() => {
49167
49167
  });
49168
49168
 
49169
49169
  // src/tools.ts
49170
+ function strictSchema(shape) {
49171
+ return exports_external.object(shape).strict();
49172
+ }
49170
49173
  function coerceArray(wrapPlainString = false) {
49171
49174
  return (val) => {
49172
49175
  if (typeof val === "string") {
@@ -49258,6 +49261,24 @@ function classifyError(error48) {
49258
49261
  return "Unknown error";
49259
49262
  }
49260
49263
  function registerTools(server, ctx) {
49264
+ async function elicit(message, schema, required2) {
49265
+ try {
49266
+ const result = await server.server.elicitInput({
49267
+ message,
49268
+ requestedSchema: {
49269
+ type: "object",
49270
+ properties: schema,
49271
+ ...required2 ? { required: required2 } : {}
49272
+ }
49273
+ });
49274
+ if (result.action === "accept" && result.content) {
49275
+ return result.content;
49276
+ }
49277
+ return null;
49278
+ } catch {
49279
+ return null;
49280
+ }
49281
+ }
49261
49282
  function withErrorHandling(name, handler) {
49262
49283
  return async (...args) => {
49263
49284
  try {
@@ -49298,8 +49319,8 @@ function registerTools(server, ctx) {
49298
49319
  ` + `• REAL_TIME workflows are fundamentally different from regular extraction workflows. They route to a separate Observer service, run continuously, and cannot be triggered manually.
49299
49320
  ` + `• A regular workflow CANNOT be converted to real-time after creation (and vice versa). The interval=REAL_TIME flag MUST be set at creation time.
49300
49321
  ` + `• If the user wants to monitor a page for changes, set interval='REAL_TIME' here. Do NOT create a regular workflow and then try to change its interval to REAL_TIME via update_workflow — this will be rejected.
49301
- ` + "• When unclear whether the user wants monitoring (real-time) or data extraction (regular), ask them to clarify before creating the workflow.",
49302
- inputSchema: {
49322
+ ` + "• When the user says 'monitor', 'track', 'watch', or 'alert me' — ASK if they want real-time continuous monitoring (interval='REAL_TIME') or scheduled extraction. Do NOT assume one or the other.",
49323
+ inputSchema: strictSchema({
49303
49324
  prompt: exports_external.string().describe('Natural language description of what to extract (e.g., "Extract product prices and names")'),
49304
49325
  url: exports_external.string().optional().describe("Single URL — prefer using 'urls' instead. If both are provided, 'urls' takes precedence."),
49305
49326
  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."),
@@ -49336,16 +49357,17 @@ function registerTools(server, ctx) {
49336
49357
  }).optional().describe("Send notifications to a Slack channel"),
49337
49358
  websocket: exports_external.boolean().optional().describe("Enable WebSocket notifications for programmatic real-time consumption")
49338
49359
  }).optional().describe("Notification channels to alert when data changes. At least one channel is required for REAL_TIME workflows.")
49339
- },
49360
+ }),
49340
49361
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
49341
49362
  }, withErrorHandling("create_workflow", async (args) => {
49342
49363
  const urls = args.urls ?? (args.url ? [args.url] : []);
49343
49364
  if (urls.length === 0) {
49344
49365
  return errorResult("At least one URL is required. Provide 'urls' (array) or 'url' (string).");
49345
49366
  }
49367
+ const interval = args.interval;
49346
49368
  const n = args.notifications;
49347
49369
  const hasNotifications = n && (n.email || n.webhook || n.slack || n.websocket);
49348
- if (args.interval === "REAL_TIME" && !hasNotifications) {
49370
+ if (interval === "REAL_TIME" && !hasNotifications) {
49349
49371
  return errorResult("Real-time workflows require at least one notification channel so you get alerted when data changes. " + "Add a notifications object with one or more channels: email, webhook, slack, or websocket.");
49350
49372
  }
49351
49373
  const extraction = args.schema ? (builder2) => {
@@ -49355,14 +49377,14 @@ function registerTools(server, ctx) {
49355
49377
  }
49356
49378
  return entity;
49357
49379
  } : undefined;
49358
- const isRealTime = args.interval === "REAL_TIME";
49380
+ const isRealTime = interval === "REAL_TIME";
49359
49381
  let builder = ctx.client.extract({
49360
49382
  urls,
49361
49383
  name: args.name || "Untitled Workflow",
49362
49384
  ...isRealTime ? {} : { navigationMode: "agentic-navigation" },
49363
49385
  userPrompt: args.prompt,
49364
49386
  extraction,
49365
- interval: args.interval
49387
+ interval
49366
49388
  });
49367
49389
  if (hasNotifications) {
49368
49390
  const channels = {};
@@ -49401,7 +49423,7 @@ function registerTools(server, ctx) {
49401
49423
  if (n?.websocket)
49402
49424
  enabledChannels.push("websocket");
49403
49425
  let message = "Workflow created successfully.";
49404
- if (args.interval === "REAL_TIME") {
49426
+ if (interval === "REAL_TIME") {
49405
49427
  message += " This real-time workflow monitors continuously — no manual runs needed.";
49406
49428
  } else {
49407
49429
  message += " The AI agent will start extracting data automatically.";
@@ -49516,16 +49538,40 @@ function registerTools(server, ctx) {
49516
49538
  });
49517
49539
  }));
49518
49540
  server.registerTool("delete_workflow", {
49519
- description: "Delete a workflow permanently",
49541
+ description: "Delete a workflow permanently. This is irreversible. " + "You MUST first call this tool without confirmed=true to preview what will be deleted, " + "then show the user the workflow name and ask for confirmation, " + "then call again with confirmed=true only after the user explicitly agrees.",
49520
49542
  inputSchema: {
49521
- workflowId: exports_external.string().describe("The workflow ID to delete")
49543
+ workflowId: exports_external.string().describe("The workflow ID to delete"),
49544
+ confirmed: exports_external.boolean().optional().describe("Set to true only after the user has explicitly confirmed deletion. Omit or set to false for the initial preview call.")
49522
49545
  },
49523
49546
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true }
49524
49547
  }, withErrorHandling("delete_workflow", async (args) => {
49548
+ let workflowName = args.workflowId;
49549
+ let workflowStatus = "unknown";
49550
+ try {
49551
+ const workflow = await ctx.client.workflow.get(args.workflowId);
49552
+ workflowName = workflow.name || args.workflowId;
49553
+ workflowStatus = workflow.displayState ?? workflow.state ?? "unknown";
49554
+ } catch {}
49555
+ if (!args.confirmed) {
49556
+ const confirmation = await elicit(`Are you sure you want to permanently delete "${workflowName}" (${workflowStatus})? This cannot be undone.`, { confirm: { type: "boolean", description: "Confirm deletion" } }, ["confirm"]);
49557
+ if (confirmation !== null) {
49558
+ if (!confirmation.confirm) {
49559
+ return jsonResult({ success: false, message: "Deletion cancelled by user." });
49560
+ }
49561
+ } else {
49562
+ return jsonResult({
49563
+ pending: true,
49564
+ workflowId: args.workflowId,
49565
+ workflowName,
49566
+ status: workflowStatus,
49567
+ message: `⚠️ You are about to permanently delete "${workflowName}" (${workflowStatus}). This cannot be undone. Ask the user to confirm, then call delete_workflow again with confirmed=true.`
49568
+ });
49569
+ }
49570
+ }
49525
49571
  await ctx.client.workflow.delete(args.workflowId);
49526
49572
  return jsonResult({
49527
49573
  success: true,
49528
- message: "Workflow deleted successfully"
49574
+ message: `Workflow "${workflowName}" deleted successfully`
49529
49575
  });
49530
49576
  }));
49531
49577
  server.registerTool("approve_workflow", {
@@ -49546,7 +49592,7 @@ function registerTools(server, ctx) {
49546
49592
  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.
49547
49593
 
49548
49594
  ` + "IMPORTANT: You cannot change a workflow's interval to or from REAL_TIME. Real-time workflows are architecturally different (Observer service vs Scraper) and must be created as real-time from the start using create_workflow with interval='REAL_TIME'. To switch a workflow between real-time and regular, delete it and create a new one with the correct interval.",
49549
- inputSchema: {
49595
+ inputSchema: strictSchema({
49550
49596
  workflowId: exports_external.string().describe("The workflow ID to update"),
49551
49597
  name: exports_external.string().optional().describe("New name for the workflow"),
49552
49598
  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."),
@@ -49576,7 +49622,7 @@ function registerTools(server, ctx) {
49576
49622
  ]).optional().describe("How often the workflow should run. CUSTOM requires schedules. Note: REAL_TIME is NOT allowed here — real-time workflows must be created via create_workflow."),
49577
49623
  schedules: exports_external.preprocess(coerceArray(true), exports_external.array(exports_external.string())).optional().describe("Cron expressions for CUSTOM update interval"),
49578
49624
  limit: exports_external.number().optional().describe("Maximum number of records to extract per run")
49579
- },
49625
+ }),
49580
49626
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true }
49581
49627
  }, withErrorHandling("update_workflow", async (args) => {
49582
49628
  const { workflowId, ...updates } = args;
@@ -49649,12 +49695,27 @@ function registerTools(server, ctx) {
49649
49695
  });
49650
49696
  }));
49651
49697
  server.registerTool("delete_notification_channel", {
49652
- description: "Delete a notification channel by ID",
49698
+ description: "Delete a notification channel by ID. " + "Call first without confirmed=true to preview, then with confirmed=true after user agrees.",
49653
49699
  inputSchema: {
49654
- channelId: exports_external.string().describe("The channel ID to delete")
49700
+ channelId: exports_external.string().describe("The channel ID to delete"),
49701
+ confirmed: exports_external.boolean().optional().describe("Set to true only after the user has explicitly confirmed deletion.")
49655
49702
  },
49656
49703
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true }
49657
49704
  }, withErrorHandling("delete_notification_channel", async (args) => {
49705
+ if (!args.confirmed) {
49706
+ const confirmation = await elicit(`Are you sure you want to delete notification channel "${args.channelId}"?`, { confirm: { type: "boolean", description: "Confirm deletion" } }, ["confirm"]);
49707
+ if (confirmation !== null) {
49708
+ if (!confirmation.confirm) {
49709
+ return jsonResult({ success: false, message: "Deletion cancelled by user." });
49710
+ }
49711
+ } else {
49712
+ return jsonResult({
49713
+ pending: true,
49714
+ channelId: args.channelId,
49715
+ message: `⚠️ About to delete notification channel "${args.channelId}". Ask the user to confirm, then call again with confirmed=true.`
49716
+ });
49717
+ }
49718
+ }
49658
49719
  await ctx.client.notification.channels.deleteChannel(args.channelId);
49659
49720
  return jsonResult({
49660
49721
  success: true,
@@ -49765,12 +49826,27 @@ function registerTools(server, ctx) {
49765
49826
  });
49766
49827
  }));
49767
49828
  server.registerTool("delete_notification_setting", {
49768
- description: "Delete a notification setting by ID. This removes the event-to-channel mapping but does not delete the channels themselves.",
49829
+ description: "Delete a notification setting by ID. This removes the event-to-channel mapping but does not delete the channels themselves. " + "Call first without confirmed=true to preview, then with confirmed=true after user agrees.",
49769
49830
  inputSchema: {
49770
- settingsId: exports_external.string().describe("The notification settings ID to delete")
49831
+ settingsId: exports_external.string().describe("The notification settings ID to delete"),
49832
+ confirmed: exports_external.boolean().optional().describe("Set to true only after the user has explicitly confirmed deletion.")
49771
49833
  },
49772
49834
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true }
49773
49835
  }, withErrorHandling("delete_notification_setting", async (args) => {
49836
+ if (!args.confirmed) {
49837
+ const confirmation = await elicit(`Are you sure you want to delete notification setting "${args.settingsId}"?`, { confirm: { type: "boolean", description: "Confirm deletion" } }, ["confirm"]);
49838
+ if (confirmation !== null) {
49839
+ if (!confirmation.confirm) {
49840
+ return jsonResult({ success: false, message: "Deletion cancelled by user." });
49841
+ }
49842
+ } else {
49843
+ return jsonResult({
49844
+ pending: true,
49845
+ settingsId: args.settingsId,
49846
+ message: `⚠️ About to delete notification setting "${args.settingsId}". Ask the user to confirm, then call again with confirmed=true.`
49847
+ });
49848
+ }
49849
+ }
49774
49850
  await ctx.client.notification.settings.deleteSettings(args.settingsId);
49775
49851
  return jsonResult({
49776
49852
  success: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kadoa/mcp",
3
- "version": "0.3.5-rc.4",
3
+ "version": "0.3.5-rc.6",
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",