@webhooks-cc/mcp 1.22.0 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/mcp.js CHANGED
@@ -456,7 +456,7 @@ var import_zod3 = require("zod");
456
456
  var import_sdk2 = require("@webhooks-cc/sdk");
457
457
 
458
458
  // src/version.ts
459
- var VERSION = true ? "1.22.0" : "0.0.0-dev";
459
+ var VERSION = true ? "1.23.0" : "0.0.0-dev";
460
460
  var MCP_USER_AGENT = `webhooks-cc-mcp/${VERSION}`;
461
461
 
462
462
  // src/tools.ts
@@ -707,6 +707,17 @@ function keyText(key) {
707
707
  }
708
708
  function approvalResultText(result) {
709
709
  if (typeof result.key === "string") return keyText(result);
710
+ if (typeof result.spreadsheetId === "string") {
711
+ const tabs = Array.isArray(result.tabs) ? result.tabs.join(", ") : "";
712
+ return `Approved. The chosen spreadsheet is ${String(result.spreadsheetId)} (${String(result.url)}), on connection ${String(result.connectionId)}${tabs ? `; its tabs: ${tabs}` : ""}. Use them in a Sheets destination's config.`;
713
+ }
714
+ if (Array.isArray(result.fields)) {
715
+ return `Approved. AI setup proposed (nothing is saved until you apply it):
716
+ ${JSON.stringify(result, null, 2)}`;
717
+ }
718
+ if (typeof result.connectionId === "string") {
719
+ return `Approved. The Google account ${result.email ? `${String(result.email)} ` : ""}is connected as ${result.connectionId}.`;
720
+ }
710
721
  if (typeof result.url === "string") {
711
722
  return `Approved. The share link is ${result.url} (anyone with it can read the copy until ${String(result.expiresAt)}).`;
712
723
  }
@@ -2319,7 +2330,7 @@ function registerTools(target, client, options = {}) {
2319
2330
  );
2320
2331
  server.tool(
2321
2332
  "wait_for_approval",
2322
- "Wait for the account's owner to answer an approval (from create_api_key, create_share_link, remove_domain or rename_account) and collect what it grants or did, such as the new API key or the share link. If they have not answered yet, call it again.",
2333
+ "Wait for the account's owner to answer an approval (from create_api_key, create_share_link, remove_domain, rename_account, connect_google, pick_spreadsheet or ai_setup) and collect what it grants or did, such as the new API key, the share link or AI setup's proposals. If they have not answered yet, call it again.",
2323
2334
  {
2324
2335
  id: import_zod2.z.string().describe("The approval id"),
2325
2336
  timeout: durationOrTimestampSchema.default("45s").describe('How long to wait for the person, for example "45s"')
@@ -2487,6 +2498,109 @@ function registerTools(target, client, options = {}) {
2487
2498
  return textContent(approvalAskText(pending));
2488
2499
  })
2489
2500
  );
2501
+ server.tool(
2502
+ "list_google_connections",
2503
+ "List the Google accounts connected for Google Sheets destinations (id, email, when connected), and whether Google Sheets is set up on this server.",
2504
+ {},
2505
+ withErrorHandling(async () => jsonContent(await client.google.connections()))
2506
+ );
2507
+ server.tool(
2508
+ "connect_google",
2509
+ "Connect a Google account for Google Sheets destinations. The account's owner must approve it: this returns a link and a code to show them, and approving takes them to Google to choose the account. Then call wait_for_approval for the new connection's id.",
2510
+ {},
2511
+ withErrorHandling(async () => textContent(approvalAskText(await client.google.connect())))
2512
+ );
2513
+ server.tool(
2514
+ "pick_spreadsheet",
2515
+ "Have the account's owner choose an existing spreadsheet from Google Drive with a connected account (to add rows to it from a Sheets destination). Returns a link and a code to show them; the page opens Google's picker. Then call wait_for_approval for the spreadsheet's id and tabs. To use a new spreadsheet instead, call create_spreadsheet.",
2516
+ {
2517
+ connectionId: import_zod2.z.string().describe("The Google connection's id, from list_google_connections")
2518
+ },
2519
+ withErrorHandling(
2520
+ async ({ connectionId }) => textContent(approvalAskText(await client.google.pick(connectionId)))
2521
+ )
2522
+ );
2523
+ server.tool(
2524
+ "create_spreadsheet",
2525
+ "Create a new spreadsheet with a header row in a connected Google account, for a Sheets destination. Needs no approval: the app reaches only spreadsheets it created or that were chosen for it.",
2526
+ {
2527
+ connectionId: import_zod2.z.string().describe("The Google connection's id, from list_google_connections"),
2528
+ title: import_zod2.z.string().max(200).optional().describe("The spreadsheet's title"),
2529
+ sheet: import_zod2.z.string().max(100).optional().describe('The tab rows go to (default "Sheet1")'),
2530
+ headers: import_zod2.z.array(import_zod2.z.string().max(200)).max(50).describe("The header row, one per column")
2531
+ },
2532
+ withErrorHandling(
2533
+ async ({ connectionId, title, sheet, headers }) => jsonContent(await client.google.createSpreadsheet({ connectionId, title, sheet, headers }))
2534
+ )
2535
+ );
2536
+ server.tool(
2537
+ "list_sheet_tabs",
2538
+ "List a spreadsheet's tabs (one created by the app or chosen with pick_spreadsheet), to choose the one a Sheets destination writes to.",
2539
+ {
2540
+ connectionId: import_zod2.z.string().describe("The Google connection's id"),
2541
+ spreadsheetId: import_zod2.z.string().describe("The spreadsheet's id")
2542
+ },
2543
+ withErrorHandling(
2544
+ async ({ connectionId, spreadsheetId }) => jsonContent({ tabs: await client.google.tabs(connectionId, spreadsheetId) })
2545
+ )
2546
+ );
2547
+ server.tool(
2548
+ "disconnect_google",
2549
+ "Disconnect a Google account (id from list_google_connections) and revoke its access at Google. Sheets destinations using it fail until another account is chosen.",
2550
+ {
2551
+ connectionId: import_zod2.z.string().describe("The Google connection's id")
2552
+ },
2553
+ withErrorHandling(async ({ connectionId }) => {
2554
+ await client.google.disconnect(connectionId);
2555
+ return textContent(`Disconnected the Google account ${connectionId}.`);
2556
+ })
2557
+ );
2558
+ server.tool(
2559
+ "ai_setup_status",
2560
+ "Whether AI setup is offered for an endpoint, and how many AI assists the plan has left in a rolling 30 days.",
2561
+ {
2562
+ slug: import_zod2.z.string().describe("The endpoint's slug")
2563
+ },
2564
+ withErrorHandling(async ({ slug }) => jsonContent(await client.ai.status(slug)))
2565
+ );
2566
+ server.tool(
2567
+ "ai_setup",
2568
+ "Have AI propose email fields (or a layout, or a repaired field) from 1 to 5 of an endpoint's captured emails. It sends those emails (subject, sender, recipients, text, headers; never attachments) to Anthropic's model, so the account's owner must approve it: this returns a link and a code to show them, and the page lists the emails. Then call wait_for_approval for the proposals, each with the value it found in every email. Nothing is saved: apply the fields with update_endpoint. Uses one AI assist.",
2569
+ {
2570
+ slug: import_zod2.z.string().describe("The endpoint's slug"),
2571
+ requestIds: import_zod2.z.array(import_zod2.z.string()).min(1).max(5).describe("1 to 5 of the endpoint's email request ids, from list_emails"),
2572
+ ask: import_zod2.z.union([
2573
+ import_zod2.z.object({
2574
+ kind: import_zod2.z.literal("field"),
2575
+ name: import_zod2.z.string().describe("The field's name: letters, digits and _"),
2576
+ description: import_zod2.z.string().describe('What the field is, such as "the order number"')
2577
+ }),
2578
+ import_zod2.z.object({
2579
+ kind: import_zod2.z.literal("layout"),
2580
+ description: import_zod2.z.string().optional().describe("What these emails are")
2581
+ }),
2582
+ import_zod2.z.object({
2583
+ kind: import_zod2.z.literal("repair"),
2584
+ field: import_zod2.z.object({
2585
+ name: import_zod2.z.string(),
2586
+ from: import_zod2.z.string(),
2587
+ header: import_zod2.z.string().optional(),
2588
+ pattern: import_zod2.z.string()
2589
+ }).describe("The field to repair, as the endpoint has it")
2590
+ })
2591
+ ]).describe("What to propose")
2592
+ },
2593
+ withErrorHandling(
2594
+ async ({ slug, requestIds, ask }) => textContent(
2595
+ approvalAskText(
2596
+ await client.ai.setup(slug, {
2597
+ requestIds,
2598
+ ask
2599
+ })
2600
+ )
2601
+ )
2602
+ )
2603
+ );
2490
2604
  server.tool(
2491
2605
  "stop_share",
2492
2606
  "Stop a public preview or live tunnel by its id (from list_shares). The CLI running it is told why and exits.",
@@ -487,6 +487,17 @@ function keyText(key) {
487
487
  }
488
488
  function approvalResultText(result) {
489
489
  if (typeof result.key === "string") return keyText(result);
490
+ if (typeof result.spreadsheetId === "string") {
491
+ const tabs = Array.isArray(result.tabs) ? result.tabs.join(", ") : "";
492
+ return `Approved. The chosen spreadsheet is ${String(result.spreadsheetId)} (${String(result.url)}), on connection ${String(result.connectionId)}${tabs ? `; its tabs: ${tabs}` : ""}. Use them in a Sheets destination's config.`;
493
+ }
494
+ if (Array.isArray(result.fields)) {
495
+ return `Approved. AI setup proposed (nothing is saved until you apply it):
496
+ ${JSON.stringify(result, null, 2)}`;
497
+ }
498
+ if (typeof result.connectionId === "string") {
499
+ return `Approved. The Google account ${result.email ? `${String(result.email)} ` : ""}is connected as ${result.connectionId}.`;
500
+ }
490
501
  if (typeof result.url === "string") {
491
502
  return `Approved. The share link is ${result.url} (anyone with it can read the copy until ${String(result.expiresAt)}).`;
492
503
  }
@@ -2099,7 +2110,7 @@ function registerTools(target, client, options = {}) {
2099
2110
  );
2100
2111
  server.tool(
2101
2112
  "wait_for_approval",
2102
- "Wait for the account's owner to answer an approval (from create_api_key, create_share_link, remove_domain or rename_account) and collect what it grants or did, such as the new API key or the share link. If they have not answered yet, call it again.",
2113
+ "Wait for the account's owner to answer an approval (from create_api_key, create_share_link, remove_domain, rename_account, connect_google, pick_spreadsheet or ai_setup) and collect what it grants or did, such as the new API key, the share link or AI setup's proposals. If they have not answered yet, call it again.",
2103
2114
  {
2104
2115
  id: z.string().describe("The approval id"),
2105
2116
  timeout: durationOrTimestampSchema.default("45s").describe('How long to wait for the person, for example "45s"')
@@ -2267,6 +2278,109 @@ function registerTools(target, client, options = {}) {
2267
2278
  return textContent(approvalAskText(pending));
2268
2279
  })
2269
2280
  );
2281
+ server.tool(
2282
+ "list_google_connections",
2283
+ "List the Google accounts connected for Google Sheets destinations (id, email, when connected), and whether Google Sheets is set up on this server.",
2284
+ {},
2285
+ withErrorHandling(async () => jsonContent(await client.google.connections()))
2286
+ );
2287
+ server.tool(
2288
+ "connect_google",
2289
+ "Connect a Google account for Google Sheets destinations. The account's owner must approve it: this returns a link and a code to show them, and approving takes them to Google to choose the account. Then call wait_for_approval for the new connection's id.",
2290
+ {},
2291
+ withErrorHandling(async () => textContent(approvalAskText(await client.google.connect())))
2292
+ );
2293
+ server.tool(
2294
+ "pick_spreadsheet",
2295
+ "Have the account's owner choose an existing spreadsheet from Google Drive with a connected account (to add rows to it from a Sheets destination). Returns a link and a code to show them; the page opens Google's picker. Then call wait_for_approval for the spreadsheet's id and tabs. To use a new spreadsheet instead, call create_spreadsheet.",
2296
+ {
2297
+ connectionId: z.string().describe("The Google connection's id, from list_google_connections")
2298
+ },
2299
+ withErrorHandling(
2300
+ async ({ connectionId }) => textContent(approvalAskText(await client.google.pick(connectionId)))
2301
+ )
2302
+ );
2303
+ server.tool(
2304
+ "create_spreadsheet",
2305
+ "Create a new spreadsheet with a header row in a connected Google account, for a Sheets destination. Needs no approval: the app reaches only spreadsheets it created or that were chosen for it.",
2306
+ {
2307
+ connectionId: z.string().describe("The Google connection's id, from list_google_connections"),
2308
+ title: z.string().max(200).optional().describe("The spreadsheet's title"),
2309
+ sheet: z.string().max(100).optional().describe('The tab rows go to (default "Sheet1")'),
2310
+ headers: z.array(z.string().max(200)).max(50).describe("The header row, one per column")
2311
+ },
2312
+ withErrorHandling(
2313
+ async ({ connectionId, title, sheet, headers }) => jsonContent(await client.google.createSpreadsheet({ connectionId, title, sheet, headers }))
2314
+ )
2315
+ );
2316
+ server.tool(
2317
+ "list_sheet_tabs",
2318
+ "List a spreadsheet's tabs (one created by the app or chosen with pick_spreadsheet), to choose the one a Sheets destination writes to.",
2319
+ {
2320
+ connectionId: z.string().describe("The Google connection's id"),
2321
+ spreadsheetId: z.string().describe("The spreadsheet's id")
2322
+ },
2323
+ withErrorHandling(
2324
+ async ({ connectionId, spreadsheetId }) => jsonContent({ tabs: await client.google.tabs(connectionId, spreadsheetId) })
2325
+ )
2326
+ );
2327
+ server.tool(
2328
+ "disconnect_google",
2329
+ "Disconnect a Google account (id from list_google_connections) and revoke its access at Google. Sheets destinations using it fail until another account is chosen.",
2330
+ {
2331
+ connectionId: z.string().describe("The Google connection's id")
2332
+ },
2333
+ withErrorHandling(async ({ connectionId }) => {
2334
+ await client.google.disconnect(connectionId);
2335
+ return textContent(`Disconnected the Google account ${connectionId}.`);
2336
+ })
2337
+ );
2338
+ server.tool(
2339
+ "ai_setup_status",
2340
+ "Whether AI setup is offered for an endpoint, and how many AI assists the plan has left in a rolling 30 days.",
2341
+ {
2342
+ slug: z.string().describe("The endpoint's slug")
2343
+ },
2344
+ withErrorHandling(async ({ slug }) => jsonContent(await client.ai.status(slug)))
2345
+ );
2346
+ server.tool(
2347
+ "ai_setup",
2348
+ "Have AI propose email fields (or a layout, or a repaired field) from 1 to 5 of an endpoint's captured emails. It sends those emails (subject, sender, recipients, text, headers; never attachments) to Anthropic's model, so the account's owner must approve it: this returns a link and a code to show them, and the page lists the emails. Then call wait_for_approval for the proposals, each with the value it found in every email. Nothing is saved: apply the fields with update_endpoint. Uses one AI assist.",
2349
+ {
2350
+ slug: z.string().describe("The endpoint's slug"),
2351
+ requestIds: z.array(z.string()).min(1).max(5).describe("1 to 5 of the endpoint's email request ids, from list_emails"),
2352
+ ask: z.union([
2353
+ z.object({
2354
+ kind: z.literal("field"),
2355
+ name: z.string().describe("The field's name: letters, digits and _"),
2356
+ description: z.string().describe('What the field is, such as "the order number"')
2357
+ }),
2358
+ z.object({
2359
+ kind: z.literal("layout"),
2360
+ description: z.string().optional().describe("What these emails are")
2361
+ }),
2362
+ z.object({
2363
+ kind: z.literal("repair"),
2364
+ field: z.object({
2365
+ name: z.string(),
2366
+ from: z.string(),
2367
+ header: z.string().optional(),
2368
+ pattern: z.string()
2369
+ }).describe("The field to repair, as the endpoint has it")
2370
+ })
2371
+ ]).describe("What to propose")
2372
+ },
2373
+ withErrorHandling(
2374
+ async ({ slug, requestIds, ask }) => textContent(
2375
+ approvalAskText(
2376
+ await client.ai.setup(slug, {
2377
+ requestIds,
2378
+ ask
2379
+ })
2380
+ )
2381
+ )
2382
+ )
2383
+ );
2270
2384
  server.tool(
2271
2385
  "stop_share",
2272
2386
  "Stop a public preview or live tunnel by its id (from list_shares). The CLI running it is told why and exits.",
package/dist/index.js CHANGED
@@ -480,7 +480,7 @@ var import_zod3 = require("zod");
480
480
  var import_sdk2 = require("@webhooks-cc/sdk");
481
481
 
482
482
  // src/version.ts
483
- var VERSION = true ? "1.22.0" : "0.0.0-dev";
483
+ var VERSION = true ? "1.23.0" : "0.0.0-dev";
484
484
  var MCP_USER_AGENT = `webhooks-cc-mcp/${VERSION}`;
485
485
 
486
486
  // src/tools.ts
@@ -731,6 +731,17 @@ function keyText(key) {
731
731
  }
732
732
  function approvalResultText(result) {
733
733
  if (typeof result.key === "string") return keyText(result);
734
+ if (typeof result.spreadsheetId === "string") {
735
+ const tabs = Array.isArray(result.tabs) ? result.tabs.join(", ") : "";
736
+ return `Approved. The chosen spreadsheet is ${String(result.spreadsheetId)} (${String(result.url)}), on connection ${String(result.connectionId)}${tabs ? `; its tabs: ${tabs}` : ""}. Use them in a Sheets destination's config.`;
737
+ }
738
+ if (Array.isArray(result.fields)) {
739
+ return `Approved. AI setup proposed (nothing is saved until you apply it):
740
+ ${JSON.stringify(result, null, 2)}`;
741
+ }
742
+ if (typeof result.connectionId === "string") {
743
+ return `Approved. The Google account ${result.email ? `${String(result.email)} ` : ""}is connected as ${result.connectionId}.`;
744
+ }
734
745
  if (typeof result.url === "string") {
735
746
  return `Approved. The share link is ${result.url} (anyone with it can read the copy until ${String(result.expiresAt)}).`;
736
747
  }
@@ -2343,7 +2354,7 @@ function registerTools(target, client, options = {}) {
2343
2354
  );
2344
2355
  server.tool(
2345
2356
  "wait_for_approval",
2346
- "Wait for the account's owner to answer an approval (from create_api_key, create_share_link, remove_domain or rename_account) and collect what it grants or did, such as the new API key or the share link. If they have not answered yet, call it again.",
2357
+ "Wait for the account's owner to answer an approval (from create_api_key, create_share_link, remove_domain, rename_account, connect_google, pick_spreadsheet or ai_setup) and collect what it grants or did, such as the new API key, the share link or AI setup's proposals. If they have not answered yet, call it again.",
2347
2358
  {
2348
2359
  id: import_zod2.z.string().describe("The approval id"),
2349
2360
  timeout: durationOrTimestampSchema.default("45s").describe('How long to wait for the person, for example "45s"')
@@ -2511,6 +2522,109 @@ function registerTools(target, client, options = {}) {
2511
2522
  return textContent(approvalAskText(pending));
2512
2523
  })
2513
2524
  );
2525
+ server.tool(
2526
+ "list_google_connections",
2527
+ "List the Google accounts connected for Google Sheets destinations (id, email, when connected), and whether Google Sheets is set up on this server.",
2528
+ {},
2529
+ withErrorHandling(async () => jsonContent(await client.google.connections()))
2530
+ );
2531
+ server.tool(
2532
+ "connect_google",
2533
+ "Connect a Google account for Google Sheets destinations. The account's owner must approve it: this returns a link and a code to show them, and approving takes them to Google to choose the account. Then call wait_for_approval for the new connection's id.",
2534
+ {},
2535
+ withErrorHandling(async () => textContent(approvalAskText(await client.google.connect())))
2536
+ );
2537
+ server.tool(
2538
+ "pick_spreadsheet",
2539
+ "Have the account's owner choose an existing spreadsheet from Google Drive with a connected account (to add rows to it from a Sheets destination). Returns a link and a code to show them; the page opens Google's picker. Then call wait_for_approval for the spreadsheet's id and tabs. To use a new spreadsheet instead, call create_spreadsheet.",
2540
+ {
2541
+ connectionId: import_zod2.z.string().describe("The Google connection's id, from list_google_connections")
2542
+ },
2543
+ withErrorHandling(
2544
+ async ({ connectionId }) => textContent(approvalAskText(await client.google.pick(connectionId)))
2545
+ )
2546
+ );
2547
+ server.tool(
2548
+ "create_spreadsheet",
2549
+ "Create a new spreadsheet with a header row in a connected Google account, for a Sheets destination. Needs no approval: the app reaches only spreadsheets it created or that were chosen for it.",
2550
+ {
2551
+ connectionId: import_zod2.z.string().describe("The Google connection's id, from list_google_connections"),
2552
+ title: import_zod2.z.string().max(200).optional().describe("The spreadsheet's title"),
2553
+ sheet: import_zod2.z.string().max(100).optional().describe('The tab rows go to (default "Sheet1")'),
2554
+ headers: import_zod2.z.array(import_zod2.z.string().max(200)).max(50).describe("The header row, one per column")
2555
+ },
2556
+ withErrorHandling(
2557
+ async ({ connectionId, title, sheet, headers }) => jsonContent(await client.google.createSpreadsheet({ connectionId, title, sheet, headers }))
2558
+ )
2559
+ );
2560
+ server.tool(
2561
+ "list_sheet_tabs",
2562
+ "List a spreadsheet's tabs (one created by the app or chosen with pick_spreadsheet), to choose the one a Sheets destination writes to.",
2563
+ {
2564
+ connectionId: import_zod2.z.string().describe("The Google connection's id"),
2565
+ spreadsheetId: import_zod2.z.string().describe("The spreadsheet's id")
2566
+ },
2567
+ withErrorHandling(
2568
+ async ({ connectionId, spreadsheetId }) => jsonContent({ tabs: await client.google.tabs(connectionId, spreadsheetId) })
2569
+ )
2570
+ );
2571
+ server.tool(
2572
+ "disconnect_google",
2573
+ "Disconnect a Google account (id from list_google_connections) and revoke its access at Google. Sheets destinations using it fail until another account is chosen.",
2574
+ {
2575
+ connectionId: import_zod2.z.string().describe("The Google connection's id")
2576
+ },
2577
+ withErrorHandling(async ({ connectionId }) => {
2578
+ await client.google.disconnect(connectionId);
2579
+ return textContent(`Disconnected the Google account ${connectionId}.`);
2580
+ })
2581
+ );
2582
+ server.tool(
2583
+ "ai_setup_status",
2584
+ "Whether AI setup is offered for an endpoint, and how many AI assists the plan has left in a rolling 30 days.",
2585
+ {
2586
+ slug: import_zod2.z.string().describe("The endpoint's slug")
2587
+ },
2588
+ withErrorHandling(async ({ slug }) => jsonContent(await client.ai.status(slug)))
2589
+ );
2590
+ server.tool(
2591
+ "ai_setup",
2592
+ "Have AI propose email fields (or a layout, or a repaired field) from 1 to 5 of an endpoint's captured emails. It sends those emails (subject, sender, recipients, text, headers; never attachments) to Anthropic's model, so the account's owner must approve it: this returns a link and a code to show them, and the page lists the emails. Then call wait_for_approval for the proposals, each with the value it found in every email. Nothing is saved: apply the fields with update_endpoint. Uses one AI assist.",
2593
+ {
2594
+ slug: import_zod2.z.string().describe("The endpoint's slug"),
2595
+ requestIds: import_zod2.z.array(import_zod2.z.string()).min(1).max(5).describe("1 to 5 of the endpoint's email request ids, from list_emails"),
2596
+ ask: import_zod2.z.union([
2597
+ import_zod2.z.object({
2598
+ kind: import_zod2.z.literal("field"),
2599
+ name: import_zod2.z.string().describe("The field's name: letters, digits and _"),
2600
+ description: import_zod2.z.string().describe('What the field is, such as "the order number"')
2601
+ }),
2602
+ import_zod2.z.object({
2603
+ kind: import_zod2.z.literal("layout"),
2604
+ description: import_zod2.z.string().optional().describe("What these emails are")
2605
+ }),
2606
+ import_zod2.z.object({
2607
+ kind: import_zod2.z.literal("repair"),
2608
+ field: import_zod2.z.object({
2609
+ name: import_zod2.z.string(),
2610
+ from: import_zod2.z.string(),
2611
+ header: import_zod2.z.string().optional(),
2612
+ pattern: import_zod2.z.string()
2613
+ }).describe("The field to repair, as the endpoint has it")
2614
+ })
2615
+ ]).describe("What to propose")
2616
+ },
2617
+ withErrorHandling(
2618
+ async ({ slug, requestIds, ask }) => textContent(
2619
+ approvalAskText(
2620
+ await client.ai.setup(slug, {
2621
+ requestIds,
2622
+ ask
2623
+ })
2624
+ )
2625
+ )
2626
+ )
2627
+ );
2514
2628
  server.tool(
2515
2629
  "stop_share",
2516
2630
  "Stop a public preview or live tunnel by its id (from list_shares). The CLI running it is told why and exits.",
package/dist/index.mjs CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  registerTools,
14
14
  waitForRequestShape,
15
15
  withErrorHandling
16
- } from "./chunk-VO7HGSRN.mjs";
16
+ } from "./chunk-TMX4P6ZZ.mjs";
17
17
 
18
18
  // src/index.ts
19
19
  import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -262,7 +262,7 @@ import {
262
262
  } from "@webhooks-cc/sdk";
263
263
 
264
264
  // src/version.ts
265
- var VERSION = true ? "1.22.0" : "0.0.0-dev";
265
+ var VERSION = true ? "1.23.0" : "0.0.0-dev";
266
266
  var MCP_USER_AGENT = `webhooks-cc-mcp/${VERSION}`;
267
267
 
268
268
  // src/sandbox-tools.ts
package/dist/tools.js CHANGED
@@ -498,6 +498,17 @@ function keyText(key) {
498
498
  }
499
499
  function approvalResultText(result) {
500
500
  if (typeof result.key === "string") return keyText(result);
501
+ if (typeof result.spreadsheetId === "string") {
502
+ const tabs = Array.isArray(result.tabs) ? result.tabs.join(", ") : "";
503
+ return `Approved. The chosen spreadsheet is ${String(result.spreadsheetId)} (${String(result.url)}), on connection ${String(result.connectionId)}${tabs ? `; its tabs: ${tabs}` : ""}. Use them in a Sheets destination's config.`;
504
+ }
505
+ if (Array.isArray(result.fields)) {
506
+ return `Approved. AI setup proposed (nothing is saved until you apply it):
507
+ ${JSON.stringify(result, null, 2)}`;
508
+ }
509
+ if (typeof result.connectionId === "string") {
510
+ return `Approved. The Google account ${result.email ? `${String(result.email)} ` : ""}is connected as ${result.connectionId}.`;
511
+ }
501
512
  if (typeof result.url === "string") {
502
513
  return `Approved. The share link is ${result.url} (anyone with it can read the copy until ${String(result.expiresAt)}).`;
503
514
  }
@@ -2110,7 +2121,7 @@ function registerTools(target, client, options = {}) {
2110
2121
  );
2111
2122
  server.tool(
2112
2123
  "wait_for_approval",
2113
- "Wait for the account's owner to answer an approval (from create_api_key, create_share_link, remove_domain or rename_account) and collect what it grants or did, such as the new API key or the share link. If they have not answered yet, call it again.",
2124
+ "Wait for the account's owner to answer an approval (from create_api_key, create_share_link, remove_domain, rename_account, connect_google, pick_spreadsheet or ai_setup) and collect what it grants or did, such as the new API key, the share link or AI setup's proposals. If they have not answered yet, call it again.",
2114
2125
  {
2115
2126
  id: import_zod.z.string().describe("The approval id"),
2116
2127
  timeout: durationOrTimestampSchema.default("45s").describe('How long to wait for the person, for example "45s"')
@@ -2278,6 +2289,109 @@ function registerTools(target, client, options = {}) {
2278
2289
  return textContent(approvalAskText(pending));
2279
2290
  })
2280
2291
  );
2292
+ server.tool(
2293
+ "list_google_connections",
2294
+ "List the Google accounts connected for Google Sheets destinations (id, email, when connected), and whether Google Sheets is set up on this server.",
2295
+ {},
2296
+ withErrorHandling(async () => jsonContent(await client.google.connections()))
2297
+ );
2298
+ server.tool(
2299
+ "connect_google",
2300
+ "Connect a Google account for Google Sheets destinations. The account's owner must approve it: this returns a link and a code to show them, and approving takes them to Google to choose the account. Then call wait_for_approval for the new connection's id.",
2301
+ {},
2302
+ withErrorHandling(async () => textContent(approvalAskText(await client.google.connect())))
2303
+ );
2304
+ server.tool(
2305
+ "pick_spreadsheet",
2306
+ "Have the account's owner choose an existing spreadsheet from Google Drive with a connected account (to add rows to it from a Sheets destination). Returns a link and a code to show them; the page opens Google's picker. Then call wait_for_approval for the spreadsheet's id and tabs. To use a new spreadsheet instead, call create_spreadsheet.",
2307
+ {
2308
+ connectionId: import_zod.z.string().describe("The Google connection's id, from list_google_connections")
2309
+ },
2310
+ withErrorHandling(
2311
+ async ({ connectionId }) => textContent(approvalAskText(await client.google.pick(connectionId)))
2312
+ )
2313
+ );
2314
+ server.tool(
2315
+ "create_spreadsheet",
2316
+ "Create a new spreadsheet with a header row in a connected Google account, for a Sheets destination. Needs no approval: the app reaches only spreadsheets it created or that were chosen for it.",
2317
+ {
2318
+ connectionId: import_zod.z.string().describe("The Google connection's id, from list_google_connections"),
2319
+ title: import_zod.z.string().max(200).optional().describe("The spreadsheet's title"),
2320
+ sheet: import_zod.z.string().max(100).optional().describe('The tab rows go to (default "Sheet1")'),
2321
+ headers: import_zod.z.array(import_zod.z.string().max(200)).max(50).describe("The header row, one per column")
2322
+ },
2323
+ withErrorHandling(
2324
+ async ({ connectionId, title, sheet, headers }) => jsonContent(await client.google.createSpreadsheet({ connectionId, title, sheet, headers }))
2325
+ )
2326
+ );
2327
+ server.tool(
2328
+ "list_sheet_tabs",
2329
+ "List a spreadsheet's tabs (one created by the app or chosen with pick_spreadsheet), to choose the one a Sheets destination writes to.",
2330
+ {
2331
+ connectionId: import_zod.z.string().describe("The Google connection's id"),
2332
+ spreadsheetId: import_zod.z.string().describe("The spreadsheet's id")
2333
+ },
2334
+ withErrorHandling(
2335
+ async ({ connectionId, spreadsheetId }) => jsonContent({ tabs: await client.google.tabs(connectionId, spreadsheetId) })
2336
+ )
2337
+ );
2338
+ server.tool(
2339
+ "disconnect_google",
2340
+ "Disconnect a Google account (id from list_google_connections) and revoke its access at Google. Sheets destinations using it fail until another account is chosen.",
2341
+ {
2342
+ connectionId: import_zod.z.string().describe("The Google connection's id")
2343
+ },
2344
+ withErrorHandling(async ({ connectionId }) => {
2345
+ await client.google.disconnect(connectionId);
2346
+ return textContent(`Disconnected the Google account ${connectionId}.`);
2347
+ })
2348
+ );
2349
+ server.tool(
2350
+ "ai_setup_status",
2351
+ "Whether AI setup is offered for an endpoint, and how many AI assists the plan has left in a rolling 30 days.",
2352
+ {
2353
+ slug: import_zod.z.string().describe("The endpoint's slug")
2354
+ },
2355
+ withErrorHandling(async ({ slug }) => jsonContent(await client.ai.status(slug)))
2356
+ );
2357
+ server.tool(
2358
+ "ai_setup",
2359
+ "Have AI propose email fields (or a layout, or a repaired field) from 1 to 5 of an endpoint's captured emails. It sends those emails (subject, sender, recipients, text, headers; never attachments) to Anthropic's model, so the account's owner must approve it: this returns a link and a code to show them, and the page lists the emails. Then call wait_for_approval for the proposals, each with the value it found in every email. Nothing is saved: apply the fields with update_endpoint. Uses one AI assist.",
2360
+ {
2361
+ slug: import_zod.z.string().describe("The endpoint's slug"),
2362
+ requestIds: import_zod.z.array(import_zod.z.string()).min(1).max(5).describe("1 to 5 of the endpoint's email request ids, from list_emails"),
2363
+ ask: import_zod.z.union([
2364
+ import_zod.z.object({
2365
+ kind: import_zod.z.literal("field"),
2366
+ name: import_zod.z.string().describe("The field's name: letters, digits and _"),
2367
+ description: import_zod.z.string().describe('What the field is, such as "the order number"')
2368
+ }),
2369
+ import_zod.z.object({
2370
+ kind: import_zod.z.literal("layout"),
2371
+ description: import_zod.z.string().optional().describe("What these emails are")
2372
+ }),
2373
+ import_zod.z.object({
2374
+ kind: import_zod.z.literal("repair"),
2375
+ field: import_zod.z.object({
2376
+ name: import_zod.z.string(),
2377
+ from: import_zod.z.string(),
2378
+ header: import_zod.z.string().optional(),
2379
+ pattern: import_zod.z.string()
2380
+ }).describe("The field to repair, as the endpoint has it")
2381
+ })
2382
+ ]).describe("What to propose")
2383
+ },
2384
+ withErrorHandling(
2385
+ async ({ slug, requestIds, ask }) => textContent(
2386
+ approvalAskText(
2387
+ await client.ai.setup(slug, {
2388
+ requestIds,
2389
+ ask
2390
+ })
2391
+ )
2392
+ )
2393
+ )
2394
+ );
2281
2395
  server.tool(
2282
2396
  "stop_share",
2283
2397
  "Stop a public preview or live tunnel by its id (from list_shares). The CLI running it is told why and exits.",
package/dist/tools.mjs CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  waitForRequestShape,
19
19
  withErrorHandling,
20
20
  withShareHints
21
- } from "./chunk-VO7HGSRN.mjs";
21
+ } from "./chunk-TMX4P6ZZ.mjs";
22
22
  export {
23
23
  LOCAL_ONLY_TOOLS,
24
24
  REMOTE_MAX_WAIT_MS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webhooks-cc/mcp",
3
- "version": "1.22.0",
3
+ "version": "1.23.0",
4
4
  "mcpName": "io.github.kroqdotdev/webhooks-cc",
5
5
  "description": "MCP server for webhooks.cc — AI agent integration for webhook testing",
6
6
  "main": "dist/index.js",
@@ -44,7 +44,7 @@
44
44
  "dependencies": {
45
45
  "@modelcontextprotocol/sdk": "^1.32.2",
46
46
  "zod": "^4.6.7",
47
- "@webhooks-cc/sdk": "1.26.0"
47
+ "@webhooks-cc/sdk": "1.27.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/node": "^26.6.6",