@kadoa/mcp 0.3.5-rc.5 → 0.3.5-rc.7

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 +188 -117
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -49312,21 +49312,88 @@ function registerTools(server, ctx) {
49312
49312
  }))
49313
49313
  });
49314
49314
  }));
49315
+ const urlInputShape = {
49316
+ url: exports_external.string().optional().describe("Single URL — prefer using 'urls' instead. If both are provided, 'urls' takes precedence."),
49317
+ 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.")
49318
+ };
49319
+ const extractionInputShape = {
49320
+ prompt: exports_external.string().describe('Natural language description of what to extract (e.g., "Extract product prices and names")'),
49321
+ name: exports_external.string().optional().describe("Optional name for the workflow"),
49322
+ entity: exports_external.string().optional().describe("Entity name for extraction (e.g., 'Product', 'Job Posting')"),
49323
+ schema: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object(SchemaFieldShape))).optional().describe("Extraction schema fields. If omitted, the AI agent auto-detects the schema.")
49324
+ };
49325
+ const notificationsInputShape = {
49326
+ notifications: exports_external.object({
49327
+ email: exports_external.boolean().optional().describe("Send email notifications to the account email when data changes"),
49328
+ webhook: exports_external.object({
49329
+ url: exports_external.string().url().describe("Webhook endpoint URL"),
49330
+ httpMethod: exports_external.enum(["POST", "GET", "PUT", "PATCH"]).optional().describe("HTTP method (defaults to POST)")
49331
+ }).optional().describe("Send notifications via HTTP webhook"),
49332
+ slack: exports_external.object({
49333
+ webhookUrl: exports_external.string().url().describe("Slack incoming webhook URL")
49334
+ }).optional().describe("Send notifications to a Slack channel"),
49335
+ websocket: exports_external.boolean().optional().describe("Enable WebSocket notifications for programmatic real-time consumption")
49336
+ }).optional().describe("Notification channels to alert when data changes.")
49337
+ };
49338
+ function resolveUrls(args) {
49339
+ const urls = args.urls ?? (args.url ? [args.url] : []);
49340
+ return urls.length > 0 ? urls : null;
49341
+ }
49342
+ function buildExtraction(args) {
49343
+ if (!args.schema)
49344
+ return;
49345
+ return (builder) => {
49346
+ const entity = builder.entity(args.entity || "Data");
49347
+ for (const field of args.schema) {
49348
+ entity.field(field.name, field.description || field.name, field.dataType || "STRING", { example: field.example });
49349
+ }
49350
+ return entity;
49351
+ };
49352
+ }
49353
+ function buildNotificationChannels(n) {
49354
+ const channels = {};
49355
+ if (n.email)
49356
+ channels.EMAIL = true;
49357
+ if (n.websocket)
49358
+ channels.WEBSOCKET = true;
49359
+ if (n.webhook) {
49360
+ channels.WEBHOOK = {
49361
+ name: "mcp-webhook",
49362
+ webhookUrl: n.webhook.url,
49363
+ httpMethod: n.webhook.httpMethod || "POST"
49364
+ };
49365
+ }
49366
+ if (n.slack) {
49367
+ channels.SLACK = {
49368
+ name: "mcp-slack",
49369
+ webhookUrl: n.slack.webhookUrl
49370
+ };
49371
+ }
49372
+ return channels;
49373
+ }
49374
+ async function describeNotifications(n) {
49375
+ if (!n)
49376
+ return [];
49377
+ const channels = [];
49378
+ if (n.email) {
49379
+ const user = await ctx.client.user.getCurrentUser();
49380
+ channels.push(`email (${user.email})`);
49381
+ }
49382
+ if (n.slack)
49383
+ channels.push(`slack (${n.slack.webhookUrl})`);
49384
+ if (n.webhook)
49385
+ channels.push(`webhook (${n.webhook.url})`);
49386
+ if (n.websocket)
49387
+ channels.push("websocket");
49388
+ return channels;
49389
+ }
49315
49390
  server.registerTool("create_workflow", {
49316
- description: `Create a new workflow that extracts data using agentic navigation. Supports one-time, scheduled, and real-time (continuous monitoring) modes via the interval parameter. If entity and schema are provided, they guide the extraction. Otherwise, the AI agent auto-detects the schema from the page based on the prompt. The workflow runs asynchronously and may take several minutes. Do NOT poll or sleep-wait for completion. Instead, return the workflow ID to the user and let them check back later with get_workflow or fetch_data.
49391
+ description: "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.
49317
49392
 
49318
- ` + `IMPORTANT REAL_TIME vs regular workflows:
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.
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.
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.
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.",
49393
+ ` + "NOTE: This tool is for one-time or scheduled extraction ONLY. " + "For continuous real-time monitoring (watching a page for changes and alerting), use the create_realtime_monitor tool instead.",
49323
49394
  inputSchema: strictSchema({
49324
- prompt: exports_external.string().describe('Natural language description of what to extract (e.g., "Extract product prices and names")'),
49325
- url: exports_external.string().optional().describe("Single URL — prefer using 'urls' instead. If both are provided, 'urls' takes precedence."),
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."),
49327
- name: exports_external.string().optional().describe("Optional name for the workflow"),
49328
- entity: exports_external.string().optional().describe("Entity name for extraction (e.g., 'Product', 'Job Posting')"),
49329
- schema: exports_external.preprocess(coerceArray(), exports_external.array(exports_external.object(SchemaFieldShape))).optional().describe("Extraction schema fields. If omitted, the AI agent auto-detects the schema."),
49395
+ ...extractionInputShape,
49396
+ ...urlInputShape,
49330
49397
  interval: exports_external.enum([
49331
49398
  "ONLY_ONCE",
49332
49399
  "EVERY_10_MINUTES",
@@ -49343,103 +49410,35 @@ function registerTools(server, ctx) {
49343
49410
  "BIWEEKLY",
49344
49411
  "TRIWEEKLY",
49345
49412
  "FOUR_WEEKS",
49346
- "MONTHLY",
49347
- "REAL_TIME"
49348
- ]).optional().describe("How often the workflow runs. Defaults to ONLY_ONCE. Use REAL_TIME for continuous monitoring."),
49349
- notifications: exports_external.object({
49350
- email: exports_external.boolean().optional().describe("Send email notifications to the account email when data changes"),
49351
- webhook: exports_external.object({
49352
- url: exports_external.string().url().describe("Webhook endpoint URL"),
49353
- httpMethod: exports_external.enum(["POST", "GET", "PUT", "PATCH"]).optional().describe("HTTP method (defaults to POST)")
49354
- }).optional().describe("Send notifications via HTTP webhook"),
49355
- slack: exports_external.object({
49356
- webhookUrl: exports_external.string().url().describe("Slack incoming webhook URL")
49357
- }).optional().describe("Send notifications to a Slack channel"),
49358
- websocket: exports_external.boolean().optional().describe("Enable WebSocket notifications for programmatic real-time consumption")
49359
- }).optional().describe("Notification channels to alert when data changes. At least one channel is required for REAL_TIME workflows.")
49413
+ "MONTHLY"
49414
+ ]).optional().describe("How often the workflow runs. Defaults to ONLY_ONCE."),
49415
+ ...notificationsInputShape
49360
49416
  }),
49361
49417
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
49362
49418
  }, withErrorHandling("create_workflow", async (args) => {
49363
- const urls = args.urls ?? (args.url ? [args.url] : []);
49364
- if (urls.length === 0) {
49419
+ const urls = resolveUrls(args);
49420
+ if (!urls) {
49365
49421
  return errorResult("At least one URL is required. Provide 'urls' (array) or 'url' (string).");
49366
49422
  }
49367
- let interval = args.interval;
49368
- if (!interval && REALTIME_INTENT_KEYWORDS.test(args.prompt || "")) {
49369
- const answer = await elicit("Your request sounds like it could be either real-time monitoring or scheduled extraction. Which do you want?", {
49370
- workflowType: {
49371
- type: "string",
49372
- enum: ["Real-time monitoring (continuous, alerts on changes)", "Scheduled extraction (one-time or recurring data pull)"],
49373
- description: "Choose the workflow type"
49374
- }
49375
- }, ["workflowType"]);
49376
- if (answer === null) {} else if (typeof answer.workflowType === "string" && answer.workflowType.startsWith("Real-time")) {
49377
- interval = "REAL_TIME";
49378
- }
49379
- }
49380
- const n = args.notifications;
49381
- const hasNotifications = n && (n.email || n.webhook || n.slack || n.websocket);
49382
- if (interval === "REAL_TIME" && !hasNotifications) {
49383
- 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.");
49384
- }
49385
- const extraction = args.schema ? (builder2) => {
49386
- const entity = builder2.entity(args.entity || "Data");
49387
- for (const field of args.schema) {
49388
- entity.field(field.name, field.description || field.name, field.dataType || "STRING", { example: field.example });
49389
- }
49390
- return entity;
49391
- } : undefined;
49392
- const isRealTime = interval === "REAL_TIME";
49393
49423
  let builder = ctx.client.extract({
49394
49424
  urls,
49395
49425
  name: args.name || "Untitled Workflow",
49396
- ...isRealTime ? {} : { navigationMode: "agentic-navigation" },
49426
+ navigationMode: "agentic-navigation",
49397
49427
  userPrompt: args.prompt,
49398
- extraction,
49399
- interval
49428
+ extraction: buildExtraction(args),
49429
+ interval: args.interval
49400
49430
  });
49431
+ const n = args.notifications;
49432
+ const hasNotifications = n && (n.email || n.webhook || n.slack || n.websocket);
49401
49433
  if (hasNotifications) {
49402
- const channels = {};
49403
- if (n.email)
49404
- channels.EMAIL = true;
49405
- if (n.websocket)
49406
- channels.WEBSOCKET = true;
49407
- if (n.webhook) {
49408
- channels.WEBHOOK = {
49409
- name: "mcp-webhook",
49410
- webhookUrl: n.webhook.url,
49411
- httpMethod: n.webhook.httpMethod || "POST"
49412
- };
49413
- }
49414
- if (n.slack) {
49415
- channels.SLACK = {
49416
- name: "mcp-slack",
49417
- webhookUrl: n.slack.webhookUrl
49418
- };
49419
- }
49420
49434
  builder = builder.withNotifications({
49421
49435
  events: ["workflow_data_change"],
49422
- channels
49436
+ channels: buildNotificationChannels(n)
49423
49437
  });
49424
49438
  }
49425
49439
  const workflow = await builder.create();
49426
- const enabledChannels = [];
49427
- if (n?.email) {
49428
- const user = await ctx.client.user.getCurrentUser();
49429
- enabledChannels.push(`email (${user.email})`);
49430
- }
49431
- if (n?.slack)
49432
- enabledChannels.push(`slack (${n.slack.webhookUrl})`);
49433
- if (n?.webhook)
49434
- enabledChannels.push(`webhook (${n.webhook.url})`);
49435
- if (n?.websocket)
49436
- enabledChannels.push("websocket");
49437
- let message = "Workflow created successfully.";
49438
- if (interval === "REAL_TIME") {
49439
- message += " This real-time workflow monitors continuously — no manual runs needed.";
49440
- } else {
49441
- message += " The AI agent will start extracting data automatically.";
49442
- }
49440
+ const enabledChannels = await describeNotifications(n);
49441
+ let message = "Workflow created successfully. The AI agent will start extracting data automatically.";
49443
49442
  if (enabledChannels.length > 0) {
49444
49443
  message += ` Notifications on data changes via: ${enabledChannels.join(", ")}.`;
49445
49444
  }
@@ -49450,6 +49449,50 @@ function registerTools(server, ctx) {
49450
49449
  message
49451
49450
  });
49452
49451
  }));
49452
+ server.registerTool("create_realtime_monitor", {
49453
+ description: "Create a real-time monitoring workflow that continuously watches a page for changes and sends alerts. " + "Use this when the user wants to monitor, track, or watch a page in real-time. " + "At least one notification channel (email, webhook, slack, or websocket) is required so the user gets alerted on changes. " + `Real-time monitors route to a separate Observer service and CANNOT be converted to/from regular extraction workflows after creation.
49454
+
49455
+ ` + "If entity and schema are provided, they guide what to monitor; otherwise the AI agent auto-detects what to watch. " + "Do NOT poll or sleep-wait for completion. Return the workflow ID and let the user check back with get_workflow or fetch_data.",
49456
+ inputSchema: strictSchema({
49457
+ ...extractionInputShape,
49458
+ ...urlInputShape,
49459
+ ...notificationsInputShape
49460
+ }),
49461
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
49462
+ }, withErrorHandling("create_realtime_monitor", async (args) => {
49463
+ const urls = resolveUrls(args);
49464
+ if (!urls) {
49465
+ return errorResult("At least one URL is required. Provide 'urls' (array) or 'url' (string).");
49466
+ }
49467
+ const n = args.notifications;
49468
+ const hasNotifications = n && (n.email || n.webhook || n.slack || n.websocket);
49469
+ if (!hasNotifications) {
49470
+ return errorResult("Real-time monitors 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.");
49471
+ }
49472
+ let builder = ctx.client.extract({
49473
+ urls,
49474
+ name: args.name || "Untitled Monitor",
49475
+ userPrompt: args.prompt,
49476
+ extraction: buildExtraction(args),
49477
+ interval: "REAL_TIME"
49478
+ });
49479
+ builder = builder.withNotifications({
49480
+ events: ["workflow_data_change"],
49481
+ channels: buildNotificationChannels(n)
49482
+ });
49483
+ const workflow = await builder.create();
49484
+ const enabledChannels = await describeNotifications(n);
49485
+ let message = "Real-time monitor created successfully. It will continuously watch for changes — no manual runs needed.";
49486
+ if (enabledChannels.length > 0) {
49487
+ message += ` Notifications on data changes via: ${enabledChannels.join(", ")}.`;
49488
+ }
49489
+ message += " Use fetch_data to retrieve the latest data.";
49490
+ return jsonResult({
49491
+ success: true,
49492
+ workflowId: workflow.workflowId,
49493
+ message
49494
+ });
49495
+ }));
49453
49496
  server.registerTool("list_workflows", {
49454
49497
  description: "List all workflows with their current status",
49455
49498
  inputSchema: {
@@ -49550,28 +49593,35 @@ function registerTools(server, ctx) {
49550
49593
  });
49551
49594
  }));
49552
49595
  server.registerTool("delete_workflow", {
49553
- description: "Delete a workflow permanently",
49596
+ 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.",
49554
49597
  inputSchema: {
49555
- workflowId: exports_external.string().describe("The workflow ID to delete")
49598
+ workflowId: exports_external.string().describe("The workflow ID to delete"),
49599
+ 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.")
49556
49600
  },
49557
49601
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true }
49558
49602
  }, withErrorHandling("delete_workflow", async (args) => {
49559
49603
  let workflowName = args.workflowId;
49604
+ let workflowStatus = "unknown";
49560
49605
  try {
49561
49606
  const workflow = await ctx.client.workflow.get(args.workflowId);
49562
49607
  workflowName = workflow.name || args.workflowId;
49608
+ workflowStatus = workflow.displayState ?? workflow.state ?? "unknown";
49563
49609
  } catch {}
49564
- const confirmation = await elicit(`Are you sure you want to permanently delete "${workflowName}"? This cannot be undone.`, {
49565
- confirm: {
49566
- type: "boolean",
49567
- description: "Confirm deletion"
49610
+ if (!args.confirmed) {
49611
+ 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"]);
49612
+ if (confirmation !== null) {
49613
+ if (!confirmation.confirm) {
49614
+ return jsonResult({ success: false, message: "Deletion cancelled by user." });
49615
+ }
49616
+ } else {
49617
+ return jsonResult({
49618
+ pending: true,
49619
+ workflowId: args.workflowId,
49620
+ workflowName,
49621
+ status: workflowStatus,
49622
+ 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.`
49623
+ });
49568
49624
  }
49569
- }, ["confirm"]);
49570
- if (confirmation !== null && !confirmation.confirm) {
49571
- return jsonResult({
49572
- success: false,
49573
- message: "Deletion cancelled by user."
49574
- });
49575
49625
  }
49576
49626
  await ctx.client.workflow.delete(args.workflowId);
49577
49627
  return jsonResult({
@@ -49700,15 +49750,26 @@ function registerTools(server, ctx) {
49700
49750
  });
49701
49751
  }));
49702
49752
  server.registerTool("delete_notification_channel", {
49703
- description: "Delete a notification channel by ID",
49753
+ description: "Delete a notification channel by ID. " + "Call first without confirmed=true to preview, then with confirmed=true after user agrees.",
49704
49754
  inputSchema: {
49705
- channelId: exports_external.string().describe("The channel ID to delete")
49755
+ channelId: exports_external.string().describe("The channel ID to delete"),
49756
+ confirmed: exports_external.boolean().optional().describe("Set to true only after the user has explicitly confirmed deletion.")
49706
49757
  },
49707
49758
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true }
49708
49759
  }, withErrorHandling("delete_notification_channel", async (args) => {
49709
- const confirmation = await elicit(`Are you sure you want to delete notification channel "${args.channelId}"?`, { confirm: { type: "boolean", description: "Confirm deletion" } }, ["confirm"]);
49710
- if (confirmation !== null && !confirmation.confirm) {
49711
- return jsonResult({ success: false, message: "Deletion cancelled by user." });
49760
+ if (!args.confirmed) {
49761
+ const confirmation = await elicit(`Are you sure you want to delete notification channel "${args.channelId}"?`, { confirm: { type: "boolean", description: "Confirm deletion" } }, ["confirm"]);
49762
+ if (confirmation !== null) {
49763
+ if (!confirmation.confirm) {
49764
+ return jsonResult({ success: false, message: "Deletion cancelled by user." });
49765
+ }
49766
+ } else {
49767
+ return jsonResult({
49768
+ pending: true,
49769
+ channelId: args.channelId,
49770
+ message: `⚠️ About to delete notification channel "${args.channelId}". Ask the user to confirm, then call again with confirmed=true.`
49771
+ });
49772
+ }
49712
49773
  }
49713
49774
  await ctx.client.notification.channels.deleteChannel(args.channelId);
49714
49775
  return jsonResult({
@@ -49820,15 +49881,26 @@ function registerTools(server, ctx) {
49820
49881
  });
49821
49882
  }));
49822
49883
  server.registerTool("delete_notification_setting", {
49823
- description: "Delete a notification setting by ID. This removes the event-to-channel mapping but does not delete the channels themselves.",
49884
+ 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.",
49824
49885
  inputSchema: {
49825
- settingsId: exports_external.string().describe("The notification settings ID to delete")
49886
+ settingsId: exports_external.string().describe("The notification settings ID to delete"),
49887
+ confirmed: exports_external.boolean().optional().describe("Set to true only after the user has explicitly confirmed deletion.")
49826
49888
  },
49827
49889
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true }
49828
49890
  }, withErrorHandling("delete_notification_setting", async (args) => {
49829
- const confirmation = await elicit(`Are you sure you want to delete notification setting "${args.settingsId}"?`, { confirm: { type: "boolean", description: "Confirm deletion" } }, ["confirm"]);
49830
- if (confirmation !== null && !confirmation.confirm) {
49831
- return jsonResult({ success: false, message: "Deletion cancelled by user." });
49891
+ if (!args.confirmed) {
49892
+ const confirmation = await elicit(`Are you sure you want to delete notification setting "${args.settingsId}"?`, { confirm: { type: "boolean", description: "Confirm deletion" } }, ["confirm"]);
49893
+ if (confirmation !== null) {
49894
+ if (!confirmation.confirm) {
49895
+ return jsonResult({ success: false, message: "Deletion cancelled by user." });
49896
+ }
49897
+ } else {
49898
+ return jsonResult({
49899
+ pending: true,
49900
+ settingsId: args.settingsId,
49901
+ message: `⚠️ About to delete notification setting "${args.settingsId}". Ask the user to confirm, then call again with confirmed=true.`
49902
+ });
49903
+ }
49832
49904
  }
49833
49905
  await ctx.client.notification.settings.deleteSettings(args.settingsId);
49834
49906
  return jsonResult({
@@ -49890,7 +49962,7 @@ function registerTools(server, ctx) {
49890
49962
  });
49891
49963
  }));
49892
49964
  }
49893
- var SchemaFieldShape, REALTIME_INTENT_KEYWORDS;
49965
+ var SchemaFieldShape;
49894
49966
  var init_tools = __esm(() => {
49895
49967
  init_zod();
49896
49968
  init_dist2();
@@ -49904,7 +49976,6 @@ var init_tools = __esm(() => {
49904
49976
  isRequired: exports_external.boolean().optional(),
49905
49977
  isUnique: exports_external.boolean().optional()
49906
49978
  };
49907
- REALTIME_INTENT_KEYWORDS = /\b(monitor|track|watch|alert|real[- ]?time|live|continuous|notify)\b/i;
49908
49979
  });
49909
49980
 
49910
49981
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/middleware/hostHeaderValidation.js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kadoa/mcp",
3
- "version": "0.3.5-rc.5",
3
+ "version": "0.3.5-rc.7",
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",