@webhooks-cc/mcp 1.21.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 +269 -12
- package/dist/{chunk-BAGCOSXG.mjs → chunk-TMX4P6ZZ.mjs} +268 -11
- package/dist/index.js +269 -12
- package/dist/index.mjs +2 -2
- package/dist/tools.js +268 -11
- package/dist/tools.mjs +1 -1
- package/package.json +2 -2
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.
|
|
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
|
|
@@ -705,6 +705,38 @@ function keyText(key) {
|
|
|
705
705
|
key.expiresAt ? `It works until ${key.expiresAt}.` : ""
|
|
706
706
|
].filter(Boolean).join("\n");
|
|
707
707
|
}
|
|
708
|
+
function approvalResultText(result) {
|
|
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
|
+
}
|
|
721
|
+
if (typeof result.url === "string") {
|
|
722
|
+
return `Approved. The share link is ${result.url} (anyone with it can read the copy until ${String(result.expiresAt)}).`;
|
|
723
|
+
}
|
|
724
|
+
if (typeof result.removed === "string") {
|
|
725
|
+
return `Approved. The domain ${result.removed} was removed; mail to it is refused now.`;
|
|
726
|
+
}
|
|
727
|
+
if ("name" in result) {
|
|
728
|
+
return result.name === null ? "Approved. The account's display name was cleared." : `Approved. The account's display name is now "${String(result.name)}".`;
|
|
729
|
+
}
|
|
730
|
+
return `Approved and done: ${JSON.stringify(result)}`;
|
|
731
|
+
}
|
|
732
|
+
function approvalAskText(pending) {
|
|
733
|
+
return [
|
|
734
|
+
`Asked for approval: ${pending.summary}`,
|
|
735
|
+
`Show the person this link: ${pending.url}`,
|
|
736
|
+
`The page shows the code ${pending.code}; ask them to approve only if it matches.`,
|
|
737
|
+
`Then call wait_for_approval with id ${pending.id}. The request expires at ${pending.expiresAt}.`
|
|
738
|
+
].join("\n");
|
|
739
|
+
}
|
|
708
740
|
function textContent(text) {
|
|
709
741
|
return { content: [{ type: "text", text }] };
|
|
710
742
|
}
|
|
@@ -2293,19 +2325,12 @@ function registerTools(target, client, options = {}) {
|
|
|
2293
2325
|
const key = await pending.wait();
|
|
2294
2326
|
return textContent(keyText(key));
|
|
2295
2327
|
}
|
|
2296
|
-
return textContent(
|
|
2297
|
-
[
|
|
2298
|
-
`Asked for approval: ${pending.summary}`,
|
|
2299
|
-
`Show the person this link: ${pending.url}`,
|
|
2300
|
-
`The page shows the code ${pending.code}; ask them to approve only if it matches.`,
|
|
2301
|
-
`Then call wait_for_approval with id ${pending.id}. The request expires at ${pending.expiresAt}.`
|
|
2302
|
-
].join("\n")
|
|
2303
|
-
);
|
|
2328
|
+
return textContent(approvalAskText(pending));
|
|
2304
2329
|
})
|
|
2305
2330
|
);
|
|
2306
2331
|
server.tool(
|
|
2307
2332
|
"wait_for_approval",
|
|
2308
|
-
"Wait for the account's owner to answer an approval (from create_api_key) and collect what it grants, such as the new API key. 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.",
|
|
2309
2334
|
{
|
|
2310
2335
|
id: import_zod2.z.string().describe("The approval id"),
|
|
2311
2336
|
timeout: durationOrTimestampSchema.default("45s").describe('How long to wait for the person, for example "45s"')
|
|
@@ -2313,8 +2338,10 @@ function registerTools(target, client, options = {}) {
|
|
|
2313
2338
|
withErrorHandling(async ({ id, timeout }) => {
|
|
2314
2339
|
const ms = durationMs(timeout) ?? 45e3;
|
|
2315
2340
|
try {
|
|
2316
|
-
const result = await client.approvals.wait(id, {
|
|
2317
|
-
|
|
2341
|
+
const result = await client.approvals.wait(id, {
|
|
2342
|
+
timeout: ms
|
|
2343
|
+
});
|
|
2344
|
+
return textContent(approvalResultText(result));
|
|
2318
2345
|
} catch (error) {
|
|
2319
2346
|
if (error instanceof import_sdk.ApprovalDeniedError) {
|
|
2320
2347
|
return textContent(
|
|
@@ -2344,6 +2371,236 @@ function registerTools(target, client, options = {}) {
|
|
|
2344
2371
|
return textContent(`Revoked the API key ${id}, if the account had it.`);
|
|
2345
2372
|
})
|
|
2346
2373
|
);
|
|
2374
|
+
server.tool(
|
|
2375
|
+
"rename_account",
|
|
2376
|
+
"Set the account's display name, or clear it with null. The account's owner must approve it: this returns a link and a code to show them, then call wait_for_approval.",
|
|
2377
|
+
{
|
|
2378
|
+
name: import_zod2.z.string().min(1).max(100).nullable().describe("The new display name, or null to clear it")
|
|
2379
|
+
},
|
|
2380
|
+
withErrorHandling(async ({ name }) => {
|
|
2381
|
+
const pending = await client.account.rename(name);
|
|
2382
|
+
if (!pending.id) return textContent(approvalResultText(await pending.wait()));
|
|
2383
|
+
return textContent(approvalAskText(pending));
|
|
2384
|
+
})
|
|
2385
|
+
);
|
|
2386
|
+
server.tool(
|
|
2387
|
+
"list_connected_agents",
|
|
2388
|
+
"List the agents connected to the account (claimed agents and OAuth sign-ins such as hosted MCP clients): id, client name, when each connected and was last used.",
|
|
2389
|
+
{},
|
|
2390
|
+
withErrorHandling(async () => jsonContent(await client.agents.list()))
|
|
2391
|
+
);
|
|
2392
|
+
server.tool(
|
|
2393
|
+
"disconnect_agent",
|
|
2394
|
+
"Disconnect an agent by its id (from list_connected_agents): its tokens stop working at once. The endpoints it made stay in the account. Disconnecting the agent you are running as ends your own access.",
|
|
2395
|
+
{
|
|
2396
|
+
id: import_zod2.z.string().describe("The agent's id, from list_connected_agents")
|
|
2397
|
+
},
|
|
2398
|
+
withErrorHandling(async ({ id }) => {
|
|
2399
|
+
await client.agents.disconnect(id);
|
|
2400
|
+
return textContent(`Disconnected the agent ${id}.`);
|
|
2401
|
+
})
|
|
2402
|
+
);
|
|
2403
|
+
server.tool(
|
|
2404
|
+
"list_share_links",
|
|
2405
|
+
"List live public links to frozen copies of requests (/s/<token>), the account's or one request's. Not the same as list_shares, which lists port previews and live tunnels.",
|
|
2406
|
+
{
|
|
2407
|
+
requestId: import_zod2.z.string().optional().describe("Only the links to this request")
|
|
2408
|
+
},
|
|
2409
|
+
withErrorHandling(async ({ requestId }) => {
|
|
2410
|
+
const links = requestId ? await client.shareLinks.forRequest(requestId) : await client.shareLinks.list();
|
|
2411
|
+
return jsonContent(links);
|
|
2412
|
+
})
|
|
2413
|
+
);
|
|
2414
|
+
server.tool(
|
|
2415
|
+
"create_share_link",
|
|
2416
|
+
"Make a public link to a read-only copy of one HTTP request (not an email), for a teammate or a bug report. Anyone with the link can read it, so the account's owner must approve: this returns a link and a code to show them, then call wait_for_approval for the share link.",
|
|
2417
|
+
{
|
|
2418
|
+
requestId: import_zod2.z.string().describe("The request's id"),
|
|
2419
|
+
expiresInDays: import_zod2.z.union([import_zod2.z.literal(1), import_zod2.z.literal(7), import_zod2.z.literal(30)]).optional().describe("How long the link works: 1, 7 or 30 days (default 7)"),
|
|
2420
|
+
hideSensitive: import_zod2.z.boolean().optional().describe(
|
|
2421
|
+
"Also leave out signature and token headers (default true). Credentials are always left out."
|
|
2422
|
+
)
|
|
2423
|
+
},
|
|
2424
|
+
withErrorHandling(async ({ requestId, expiresInDays, hideSensitive }) => {
|
|
2425
|
+
const pending = await client.shareLinks.create(requestId, { expiresInDays, hideSensitive });
|
|
2426
|
+
if (!pending.id) return textContent(approvalResultText({ ...await pending.wait() }));
|
|
2427
|
+
return textContent(approvalAskText(pending));
|
|
2428
|
+
})
|
|
2429
|
+
);
|
|
2430
|
+
server.tool(
|
|
2431
|
+
"revoke_share_link",
|
|
2432
|
+
"Revoke a share link by its id (from list_share_links): its page answers 404 from then on.",
|
|
2433
|
+
{
|
|
2434
|
+
id: import_zod2.z.string().describe("The link's id, from list_share_links")
|
|
2435
|
+
},
|
|
2436
|
+
withErrorHandling(async ({ id }) => {
|
|
2437
|
+
await client.shareLinks.revoke(id);
|
|
2438
|
+
return textContent(`Revoked the share link ${id}.`);
|
|
2439
|
+
})
|
|
2440
|
+
);
|
|
2441
|
+
const teamIdSchema = import_zod2.z.string().optional().describe("A Team Business team's id (its owner only); leave out for your own account");
|
|
2442
|
+
server.tool(
|
|
2443
|
+
"list_domains",
|
|
2444
|
+
"List custom inbound domains (Business): each one's status, DNS records to publish, routes and catch-all endpoint.",
|
|
2445
|
+
{ teamId: teamIdSchema },
|
|
2446
|
+
withErrorHandling(async ({ teamId }) => jsonContent(await client.domains.list({ teamId })))
|
|
2447
|
+
);
|
|
2448
|
+
server.tool(
|
|
2449
|
+
"add_domain",
|
|
2450
|
+
"Add a custom inbound domain (Business) so mail to it is captured. Returns the TXT and MX records to publish; then call verify_domain.",
|
|
2451
|
+
{
|
|
2452
|
+
domain: import_zod2.z.string().min(1).max(253).describe("The domain, such as mail.example.com"),
|
|
2453
|
+
teamId: teamIdSchema
|
|
2454
|
+
},
|
|
2455
|
+
withErrorHandling(
|
|
2456
|
+
async ({ domain, teamId }) => jsonContent(await client.domains.add(domain, { teamId }))
|
|
2457
|
+
)
|
|
2458
|
+
);
|
|
2459
|
+
server.tool(
|
|
2460
|
+
"verify_domain",
|
|
2461
|
+
"Look a custom domain's TXT and MX records up now. It receives mail once both are found.",
|
|
2462
|
+
{
|
|
2463
|
+
id: import_zod2.z.string().describe("The domain's id, from list_domains"),
|
|
2464
|
+
teamId: teamIdSchema
|
|
2465
|
+
},
|
|
2466
|
+
withErrorHandling(
|
|
2467
|
+
async ({ id, teamId }) => jsonContent(await client.domains.verify(id, { teamId }))
|
|
2468
|
+
)
|
|
2469
|
+
);
|
|
2470
|
+
server.tool(
|
|
2471
|
+
"route_domain",
|
|
2472
|
+
"Set where a custom domain's mail goes: routes by local part (the part before @) to endpoints, everything else to a catch-all endpoint, or refused when the catch-all is null. Replaces the domain's routing.",
|
|
2473
|
+
{
|
|
2474
|
+
id: import_zod2.z.string().describe("The domain's id, from list_domains"),
|
|
2475
|
+
catchAllEndpointId: import_zod2.z.string().nullable().describe("The endpoint id for mail no route matches, or null to refuse it"),
|
|
2476
|
+
routes: import_zod2.z.array(
|
|
2477
|
+
import_zod2.z.object({
|
|
2478
|
+
localPart: import_zod2.z.string().max(64).describe("The part before @"),
|
|
2479
|
+
endpointId: import_zod2.z.string().describe("The endpoint's id")
|
|
2480
|
+
})
|
|
2481
|
+
).max(100).default([]).describe("Routes by local part"),
|
|
2482
|
+
teamId: teamIdSchema
|
|
2483
|
+
},
|
|
2484
|
+
withErrorHandling(
|
|
2485
|
+
async ({ id, catchAllEndpointId, routes, teamId }) => jsonContent(await client.domains.route(id, { catchAllEndpointId, routes }, { teamId }))
|
|
2486
|
+
)
|
|
2487
|
+
);
|
|
2488
|
+
server.tool(
|
|
2489
|
+
"remove_domain",
|
|
2490
|
+
"Remove a custom inbound domain: mail to it is refused from then on. The account's owner must approve it: this returns a link and a code to show them, then call wait_for_approval.",
|
|
2491
|
+
{
|
|
2492
|
+
id: import_zod2.z.string().describe("The domain's id, from list_domains"),
|
|
2493
|
+
teamId: teamIdSchema
|
|
2494
|
+
},
|
|
2495
|
+
withErrorHandling(async ({ id, teamId }) => {
|
|
2496
|
+
const pending = await client.domains.remove(id, { teamId });
|
|
2497
|
+
if (!pending.id) return textContent(`Removed the domain ${id}.`);
|
|
2498
|
+
return textContent(approvalAskText(pending));
|
|
2499
|
+
})
|
|
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
|
+
);
|
|
2347
2604
|
server.tool(
|
|
2348
2605
|
"stop_share",
|
|
2349
2606
|
"Stop a public preview or live tunnel by its id (from list_shares). The CLI running it is told why and exits.",
|
|
@@ -485,6 +485,38 @@ function keyText(key) {
|
|
|
485
485
|
key.expiresAt ? `It works until ${key.expiresAt}.` : ""
|
|
486
486
|
].filter(Boolean).join("\n");
|
|
487
487
|
}
|
|
488
|
+
function approvalResultText(result) {
|
|
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
|
+
}
|
|
501
|
+
if (typeof result.url === "string") {
|
|
502
|
+
return `Approved. The share link is ${result.url} (anyone with it can read the copy until ${String(result.expiresAt)}).`;
|
|
503
|
+
}
|
|
504
|
+
if (typeof result.removed === "string") {
|
|
505
|
+
return `Approved. The domain ${result.removed} was removed; mail to it is refused now.`;
|
|
506
|
+
}
|
|
507
|
+
if ("name" in result) {
|
|
508
|
+
return result.name === null ? "Approved. The account's display name was cleared." : `Approved. The account's display name is now "${String(result.name)}".`;
|
|
509
|
+
}
|
|
510
|
+
return `Approved and done: ${JSON.stringify(result)}`;
|
|
511
|
+
}
|
|
512
|
+
function approvalAskText(pending) {
|
|
513
|
+
return [
|
|
514
|
+
`Asked for approval: ${pending.summary}`,
|
|
515
|
+
`Show the person this link: ${pending.url}`,
|
|
516
|
+
`The page shows the code ${pending.code}; ask them to approve only if it matches.`,
|
|
517
|
+
`Then call wait_for_approval with id ${pending.id}. The request expires at ${pending.expiresAt}.`
|
|
518
|
+
].join("\n");
|
|
519
|
+
}
|
|
488
520
|
function textContent(text) {
|
|
489
521
|
return { content: [{ type: "text", text }] };
|
|
490
522
|
}
|
|
@@ -2073,19 +2105,12 @@ function registerTools(target, client, options = {}) {
|
|
|
2073
2105
|
const key = await pending.wait();
|
|
2074
2106
|
return textContent(keyText(key));
|
|
2075
2107
|
}
|
|
2076
|
-
return textContent(
|
|
2077
|
-
[
|
|
2078
|
-
`Asked for approval: ${pending.summary}`,
|
|
2079
|
-
`Show the person this link: ${pending.url}`,
|
|
2080
|
-
`The page shows the code ${pending.code}; ask them to approve only if it matches.`,
|
|
2081
|
-
`Then call wait_for_approval with id ${pending.id}. The request expires at ${pending.expiresAt}.`
|
|
2082
|
-
].join("\n")
|
|
2083
|
-
);
|
|
2108
|
+
return textContent(approvalAskText(pending));
|
|
2084
2109
|
})
|
|
2085
2110
|
);
|
|
2086
2111
|
server.tool(
|
|
2087
2112
|
"wait_for_approval",
|
|
2088
|
-
"Wait for the account's owner to answer an approval (from create_api_key) and collect what it grants, such as the new API key. 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.",
|
|
2089
2114
|
{
|
|
2090
2115
|
id: z.string().describe("The approval id"),
|
|
2091
2116
|
timeout: durationOrTimestampSchema.default("45s").describe('How long to wait for the person, for example "45s"')
|
|
@@ -2093,8 +2118,10 @@ function registerTools(target, client, options = {}) {
|
|
|
2093
2118
|
withErrorHandling(async ({ id, timeout }) => {
|
|
2094
2119
|
const ms = durationMs(timeout) ?? 45e3;
|
|
2095
2120
|
try {
|
|
2096
|
-
const result = await client.approvals.wait(id, {
|
|
2097
|
-
|
|
2121
|
+
const result = await client.approvals.wait(id, {
|
|
2122
|
+
timeout: ms
|
|
2123
|
+
});
|
|
2124
|
+
return textContent(approvalResultText(result));
|
|
2098
2125
|
} catch (error) {
|
|
2099
2126
|
if (error instanceof ApprovalDeniedError) {
|
|
2100
2127
|
return textContent(
|
|
@@ -2124,6 +2151,236 @@ function registerTools(target, client, options = {}) {
|
|
|
2124
2151
|
return textContent(`Revoked the API key ${id}, if the account had it.`);
|
|
2125
2152
|
})
|
|
2126
2153
|
);
|
|
2154
|
+
server.tool(
|
|
2155
|
+
"rename_account",
|
|
2156
|
+
"Set the account's display name, or clear it with null. The account's owner must approve it: this returns a link and a code to show them, then call wait_for_approval.",
|
|
2157
|
+
{
|
|
2158
|
+
name: z.string().min(1).max(100).nullable().describe("The new display name, or null to clear it")
|
|
2159
|
+
},
|
|
2160
|
+
withErrorHandling(async ({ name }) => {
|
|
2161
|
+
const pending = await client.account.rename(name);
|
|
2162
|
+
if (!pending.id) return textContent(approvalResultText(await pending.wait()));
|
|
2163
|
+
return textContent(approvalAskText(pending));
|
|
2164
|
+
})
|
|
2165
|
+
);
|
|
2166
|
+
server.tool(
|
|
2167
|
+
"list_connected_agents",
|
|
2168
|
+
"List the agents connected to the account (claimed agents and OAuth sign-ins such as hosted MCP clients): id, client name, when each connected and was last used.",
|
|
2169
|
+
{},
|
|
2170
|
+
withErrorHandling(async () => jsonContent(await client.agents.list()))
|
|
2171
|
+
);
|
|
2172
|
+
server.tool(
|
|
2173
|
+
"disconnect_agent",
|
|
2174
|
+
"Disconnect an agent by its id (from list_connected_agents): its tokens stop working at once. The endpoints it made stay in the account. Disconnecting the agent you are running as ends your own access.",
|
|
2175
|
+
{
|
|
2176
|
+
id: z.string().describe("The agent's id, from list_connected_agents")
|
|
2177
|
+
},
|
|
2178
|
+
withErrorHandling(async ({ id }) => {
|
|
2179
|
+
await client.agents.disconnect(id);
|
|
2180
|
+
return textContent(`Disconnected the agent ${id}.`);
|
|
2181
|
+
})
|
|
2182
|
+
);
|
|
2183
|
+
server.tool(
|
|
2184
|
+
"list_share_links",
|
|
2185
|
+
"List live public links to frozen copies of requests (/s/<token>), the account's or one request's. Not the same as list_shares, which lists port previews and live tunnels.",
|
|
2186
|
+
{
|
|
2187
|
+
requestId: z.string().optional().describe("Only the links to this request")
|
|
2188
|
+
},
|
|
2189
|
+
withErrorHandling(async ({ requestId }) => {
|
|
2190
|
+
const links = requestId ? await client.shareLinks.forRequest(requestId) : await client.shareLinks.list();
|
|
2191
|
+
return jsonContent(links);
|
|
2192
|
+
})
|
|
2193
|
+
);
|
|
2194
|
+
server.tool(
|
|
2195
|
+
"create_share_link",
|
|
2196
|
+
"Make a public link to a read-only copy of one HTTP request (not an email), for a teammate or a bug report. Anyone with the link can read it, so the account's owner must approve: this returns a link and a code to show them, then call wait_for_approval for the share link.",
|
|
2197
|
+
{
|
|
2198
|
+
requestId: z.string().describe("The request's id"),
|
|
2199
|
+
expiresInDays: z.union([z.literal(1), z.literal(7), z.literal(30)]).optional().describe("How long the link works: 1, 7 or 30 days (default 7)"),
|
|
2200
|
+
hideSensitive: z.boolean().optional().describe(
|
|
2201
|
+
"Also leave out signature and token headers (default true). Credentials are always left out."
|
|
2202
|
+
)
|
|
2203
|
+
},
|
|
2204
|
+
withErrorHandling(async ({ requestId, expiresInDays, hideSensitive }) => {
|
|
2205
|
+
const pending = await client.shareLinks.create(requestId, { expiresInDays, hideSensitive });
|
|
2206
|
+
if (!pending.id) return textContent(approvalResultText({ ...await pending.wait() }));
|
|
2207
|
+
return textContent(approvalAskText(pending));
|
|
2208
|
+
})
|
|
2209
|
+
);
|
|
2210
|
+
server.tool(
|
|
2211
|
+
"revoke_share_link",
|
|
2212
|
+
"Revoke a share link by its id (from list_share_links): its page answers 404 from then on.",
|
|
2213
|
+
{
|
|
2214
|
+
id: z.string().describe("The link's id, from list_share_links")
|
|
2215
|
+
},
|
|
2216
|
+
withErrorHandling(async ({ id }) => {
|
|
2217
|
+
await client.shareLinks.revoke(id);
|
|
2218
|
+
return textContent(`Revoked the share link ${id}.`);
|
|
2219
|
+
})
|
|
2220
|
+
);
|
|
2221
|
+
const teamIdSchema = z.string().optional().describe("A Team Business team's id (its owner only); leave out for your own account");
|
|
2222
|
+
server.tool(
|
|
2223
|
+
"list_domains",
|
|
2224
|
+
"List custom inbound domains (Business): each one's status, DNS records to publish, routes and catch-all endpoint.",
|
|
2225
|
+
{ teamId: teamIdSchema },
|
|
2226
|
+
withErrorHandling(async ({ teamId }) => jsonContent(await client.domains.list({ teamId })))
|
|
2227
|
+
);
|
|
2228
|
+
server.tool(
|
|
2229
|
+
"add_domain",
|
|
2230
|
+
"Add a custom inbound domain (Business) so mail to it is captured. Returns the TXT and MX records to publish; then call verify_domain.",
|
|
2231
|
+
{
|
|
2232
|
+
domain: z.string().min(1).max(253).describe("The domain, such as mail.example.com"),
|
|
2233
|
+
teamId: teamIdSchema
|
|
2234
|
+
},
|
|
2235
|
+
withErrorHandling(
|
|
2236
|
+
async ({ domain, teamId }) => jsonContent(await client.domains.add(domain, { teamId }))
|
|
2237
|
+
)
|
|
2238
|
+
);
|
|
2239
|
+
server.tool(
|
|
2240
|
+
"verify_domain",
|
|
2241
|
+
"Look a custom domain's TXT and MX records up now. It receives mail once both are found.",
|
|
2242
|
+
{
|
|
2243
|
+
id: z.string().describe("The domain's id, from list_domains"),
|
|
2244
|
+
teamId: teamIdSchema
|
|
2245
|
+
},
|
|
2246
|
+
withErrorHandling(
|
|
2247
|
+
async ({ id, teamId }) => jsonContent(await client.domains.verify(id, { teamId }))
|
|
2248
|
+
)
|
|
2249
|
+
);
|
|
2250
|
+
server.tool(
|
|
2251
|
+
"route_domain",
|
|
2252
|
+
"Set where a custom domain's mail goes: routes by local part (the part before @) to endpoints, everything else to a catch-all endpoint, or refused when the catch-all is null. Replaces the domain's routing.",
|
|
2253
|
+
{
|
|
2254
|
+
id: z.string().describe("The domain's id, from list_domains"),
|
|
2255
|
+
catchAllEndpointId: z.string().nullable().describe("The endpoint id for mail no route matches, or null to refuse it"),
|
|
2256
|
+
routes: z.array(
|
|
2257
|
+
z.object({
|
|
2258
|
+
localPart: z.string().max(64).describe("The part before @"),
|
|
2259
|
+
endpointId: z.string().describe("The endpoint's id")
|
|
2260
|
+
})
|
|
2261
|
+
).max(100).default([]).describe("Routes by local part"),
|
|
2262
|
+
teamId: teamIdSchema
|
|
2263
|
+
},
|
|
2264
|
+
withErrorHandling(
|
|
2265
|
+
async ({ id, catchAllEndpointId, routes, teamId }) => jsonContent(await client.domains.route(id, { catchAllEndpointId, routes }, { teamId }))
|
|
2266
|
+
)
|
|
2267
|
+
);
|
|
2268
|
+
server.tool(
|
|
2269
|
+
"remove_domain",
|
|
2270
|
+
"Remove a custom inbound domain: mail to it is refused from then on. The account's owner must approve it: this returns a link and a code to show them, then call wait_for_approval.",
|
|
2271
|
+
{
|
|
2272
|
+
id: z.string().describe("The domain's id, from list_domains"),
|
|
2273
|
+
teamId: teamIdSchema
|
|
2274
|
+
},
|
|
2275
|
+
withErrorHandling(async ({ id, teamId }) => {
|
|
2276
|
+
const pending = await client.domains.remove(id, { teamId });
|
|
2277
|
+
if (!pending.id) return textContent(`Removed the domain ${id}.`);
|
|
2278
|
+
return textContent(approvalAskText(pending));
|
|
2279
|
+
})
|
|
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
|
+
);
|
|
2127
2384
|
server.tool(
|
|
2128
2385
|
"stop_share",
|
|
2129
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.
|
|
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
|
|
@@ -729,6 +729,38 @@ function keyText(key) {
|
|
|
729
729
|
key.expiresAt ? `It works until ${key.expiresAt}.` : ""
|
|
730
730
|
].filter(Boolean).join("\n");
|
|
731
731
|
}
|
|
732
|
+
function approvalResultText(result) {
|
|
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
|
+
}
|
|
745
|
+
if (typeof result.url === "string") {
|
|
746
|
+
return `Approved. The share link is ${result.url} (anyone with it can read the copy until ${String(result.expiresAt)}).`;
|
|
747
|
+
}
|
|
748
|
+
if (typeof result.removed === "string") {
|
|
749
|
+
return `Approved. The domain ${result.removed} was removed; mail to it is refused now.`;
|
|
750
|
+
}
|
|
751
|
+
if ("name" in result) {
|
|
752
|
+
return result.name === null ? "Approved. The account's display name was cleared." : `Approved. The account's display name is now "${String(result.name)}".`;
|
|
753
|
+
}
|
|
754
|
+
return `Approved and done: ${JSON.stringify(result)}`;
|
|
755
|
+
}
|
|
756
|
+
function approvalAskText(pending) {
|
|
757
|
+
return [
|
|
758
|
+
`Asked for approval: ${pending.summary}`,
|
|
759
|
+
`Show the person this link: ${pending.url}`,
|
|
760
|
+
`The page shows the code ${pending.code}; ask them to approve only if it matches.`,
|
|
761
|
+
`Then call wait_for_approval with id ${pending.id}. The request expires at ${pending.expiresAt}.`
|
|
762
|
+
].join("\n");
|
|
763
|
+
}
|
|
732
764
|
function textContent(text) {
|
|
733
765
|
return { content: [{ type: "text", text }] };
|
|
734
766
|
}
|
|
@@ -2317,19 +2349,12 @@ function registerTools(target, client, options = {}) {
|
|
|
2317
2349
|
const key = await pending.wait();
|
|
2318
2350
|
return textContent(keyText(key));
|
|
2319
2351
|
}
|
|
2320
|
-
return textContent(
|
|
2321
|
-
[
|
|
2322
|
-
`Asked for approval: ${pending.summary}`,
|
|
2323
|
-
`Show the person this link: ${pending.url}`,
|
|
2324
|
-
`The page shows the code ${pending.code}; ask them to approve only if it matches.`,
|
|
2325
|
-
`Then call wait_for_approval with id ${pending.id}. The request expires at ${pending.expiresAt}.`
|
|
2326
|
-
].join("\n")
|
|
2327
|
-
);
|
|
2352
|
+
return textContent(approvalAskText(pending));
|
|
2328
2353
|
})
|
|
2329
2354
|
);
|
|
2330
2355
|
server.tool(
|
|
2331
2356
|
"wait_for_approval",
|
|
2332
|
-
"Wait for the account's owner to answer an approval (from create_api_key) and collect what it grants, such as the new API key. 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.",
|
|
2333
2358
|
{
|
|
2334
2359
|
id: import_zod2.z.string().describe("The approval id"),
|
|
2335
2360
|
timeout: durationOrTimestampSchema.default("45s").describe('How long to wait for the person, for example "45s"')
|
|
@@ -2337,8 +2362,10 @@ function registerTools(target, client, options = {}) {
|
|
|
2337
2362
|
withErrorHandling(async ({ id, timeout }) => {
|
|
2338
2363
|
const ms = durationMs(timeout) ?? 45e3;
|
|
2339
2364
|
try {
|
|
2340
|
-
const result = await client.approvals.wait(id, {
|
|
2341
|
-
|
|
2365
|
+
const result = await client.approvals.wait(id, {
|
|
2366
|
+
timeout: ms
|
|
2367
|
+
});
|
|
2368
|
+
return textContent(approvalResultText(result));
|
|
2342
2369
|
} catch (error) {
|
|
2343
2370
|
if (error instanceof import_sdk.ApprovalDeniedError) {
|
|
2344
2371
|
return textContent(
|
|
@@ -2368,6 +2395,236 @@ function registerTools(target, client, options = {}) {
|
|
|
2368
2395
|
return textContent(`Revoked the API key ${id}, if the account had it.`);
|
|
2369
2396
|
})
|
|
2370
2397
|
);
|
|
2398
|
+
server.tool(
|
|
2399
|
+
"rename_account",
|
|
2400
|
+
"Set the account's display name, or clear it with null. The account's owner must approve it: this returns a link and a code to show them, then call wait_for_approval.",
|
|
2401
|
+
{
|
|
2402
|
+
name: import_zod2.z.string().min(1).max(100).nullable().describe("The new display name, or null to clear it")
|
|
2403
|
+
},
|
|
2404
|
+
withErrorHandling(async ({ name }) => {
|
|
2405
|
+
const pending = await client.account.rename(name);
|
|
2406
|
+
if (!pending.id) return textContent(approvalResultText(await pending.wait()));
|
|
2407
|
+
return textContent(approvalAskText(pending));
|
|
2408
|
+
})
|
|
2409
|
+
);
|
|
2410
|
+
server.tool(
|
|
2411
|
+
"list_connected_agents",
|
|
2412
|
+
"List the agents connected to the account (claimed agents and OAuth sign-ins such as hosted MCP clients): id, client name, when each connected and was last used.",
|
|
2413
|
+
{},
|
|
2414
|
+
withErrorHandling(async () => jsonContent(await client.agents.list()))
|
|
2415
|
+
);
|
|
2416
|
+
server.tool(
|
|
2417
|
+
"disconnect_agent",
|
|
2418
|
+
"Disconnect an agent by its id (from list_connected_agents): its tokens stop working at once. The endpoints it made stay in the account. Disconnecting the agent you are running as ends your own access.",
|
|
2419
|
+
{
|
|
2420
|
+
id: import_zod2.z.string().describe("The agent's id, from list_connected_agents")
|
|
2421
|
+
},
|
|
2422
|
+
withErrorHandling(async ({ id }) => {
|
|
2423
|
+
await client.agents.disconnect(id);
|
|
2424
|
+
return textContent(`Disconnected the agent ${id}.`);
|
|
2425
|
+
})
|
|
2426
|
+
);
|
|
2427
|
+
server.tool(
|
|
2428
|
+
"list_share_links",
|
|
2429
|
+
"List live public links to frozen copies of requests (/s/<token>), the account's or one request's. Not the same as list_shares, which lists port previews and live tunnels.",
|
|
2430
|
+
{
|
|
2431
|
+
requestId: import_zod2.z.string().optional().describe("Only the links to this request")
|
|
2432
|
+
},
|
|
2433
|
+
withErrorHandling(async ({ requestId }) => {
|
|
2434
|
+
const links = requestId ? await client.shareLinks.forRequest(requestId) : await client.shareLinks.list();
|
|
2435
|
+
return jsonContent(links);
|
|
2436
|
+
})
|
|
2437
|
+
);
|
|
2438
|
+
server.tool(
|
|
2439
|
+
"create_share_link",
|
|
2440
|
+
"Make a public link to a read-only copy of one HTTP request (not an email), for a teammate or a bug report. Anyone with the link can read it, so the account's owner must approve: this returns a link and a code to show them, then call wait_for_approval for the share link.",
|
|
2441
|
+
{
|
|
2442
|
+
requestId: import_zod2.z.string().describe("The request's id"),
|
|
2443
|
+
expiresInDays: import_zod2.z.union([import_zod2.z.literal(1), import_zod2.z.literal(7), import_zod2.z.literal(30)]).optional().describe("How long the link works: 1, 7 or 30 days (default 7)"),
|
|
2444
|
+
hideSensitive: import_zod2.z.boolean().optional().describe(
|
|
2445
|
+
"Also leave out signature and token headers (default true). Credentials are always left out."
|
|
2446
|
+
)
|
|
2447
|
+
},
|
|
2448
|
+
withErrorHandling(async ({ requestId, expiresInDays, hideSensitive }) => {
|
|
2449
|
+
const pending = await client.shareLinks.create(requestId, { expiresInDays, hideSensitive });
|
|
2450
|
+
if (!pending.id) return textContent(approvalResultText({ ...await pending.wait() }));
|
|
2451
|
+
return textContent(approvalAskText(pending));
|
|
2452
|
+
})
|
|
2453
|
+
);
|
|
2454
|
+
server.tool(
|
|
2455
|
+
"revoke_share_link",
|
|
2456
|
+
"Revoke a share link by its id (from list_share_links): its page answers 404 from then on.",
|
|
2457
|
+
{
|
|
2458
|
+
id: import_zod2.z.string().describe("The link's id, from list_share_links")
|
|
2459
|
+
},
|
|
2460
|
+
withErrorHandling(async ({ id }) => {
|
|
2461
|
+
await client.shareLinks.revoke(id);
|
|
2462
|
+
return textContent(`Revoked the share link ${id}.`);
|
|
2463
|
+
})
|
|
2464
|
+
);
|
|
2465
|
+
const teamIdSchema = import_zod2.z.string().optional().describe("A Team Business team's id (its owner only); leave out for your own account");
|
|
2466
|
+
server.tool(
|
|
2467
|
+
"list_domains",
|
|
2468
|
+
"List custom inbound domains (Business): each one's status, DNS records to publish, routes and catch-all endpoint.",
|
|
2469
|
+
{ teamId: teamIdSchema },
|
|
2470
|
+
withErrorHandling(async ({ teamId }) => jsonContent(await client.domains.list({ teamId })))
|
|
2471
|
+
);
|
|
2472
|
+
server.tool(
|
|
2473
|
+
"add_domain",
|
|
2474
|
+
"Add a custom inbound domain (Business) so mail to it is captured. Returns the TXT and MX records to publish; then call verify_domain.",
|
|
2475
|
+
{
|
|
2476
|
+
domain: import_zod2.z.string().min(1).max(253).describe("The domain, such as mail.example.com"),
|
|
2477
|
+
teamId: teamIdSchema
|
|
2478
|
+
},
|
|
2479
|
+
withErrorHandling(
|
|
2480
|
+
async ({ domain, teamId }) => jsonContent(await client.domains.add(domain, { teamId }))
|
|
2481
|
+
)
|
|
2482
|
+
);
|
|
2483
|
+
server.tool(
|
|
2484
|
+
"verify_domain",
|
|
2485
|
+
"Look a custom domain's TXT and MX records up now. It receives mail once both are found.",
|
|
2486
|
+
{
|
|
2487
|
+
id: import_zod2.z.string().describe("The domain's id, from list_domains"),
|
|
2488
|
+
teamId: teamIdSchema
|
|
2489
|
+
},
|
|
2490
|
+
withErrorHandling(
|
|
2491
|
+
async ({ id, teamId }) => jsonContent(await client.domains.verify(id, { teamId }))
|
|
2492
|
+
)
|
|
2493
|
+
);
|
|
2494
|
+
server.tool(
|
|
2495
|
+
"route_domain",
|
|
2496
|
+
"Set where a custom domain's mail goes: routes by local part (the part before @) to endpoints, everything else to a catch-all endpoint, or refused when the catch-all is null. Replaces the domain's routing.",
|
|
2497
|
+
{
|
|
2498
|
+
id: import_zod2.z.string().describe("The domain's id, from list_domains"),
|
|
2499
|
+
catchAllEndpointId: import_zod2.z.string().nullable().describe("The endpoint id for mail no route matches, or null to refuse it"),
|
|
2500
|
+
routes: import_zod2.z.array(
|
|
2501
|
+
import_zod2.z.object({
|
|
2502
|
+
localPart: import_zod2.z.string().max(64).describe("The part before @"),
|
|
2503
|
+
endpointId: import_zod2.z.string().describe("The endpoint's id")
|
|
2504
|
+
})
|
|
2505
|
+
).max(100).default([]).describe("Routes by local part"),
|
|
2506
|
+
teamId: teamIdSchema
|
|
2507
|
+
},
|
|
2508
|
+
withErrorHandling(
|
|
2509
|
+
async ({ id, catchAllEndpointId, routes, teamId }) => jsonContent(await client.domains.route(id, { catchAllEndpointId, routes }, { teamId }))
|
|
2510
|
+
)
|
|
2511
|
+
);
|
|
2512
|
+
server.tool(
|
|
2513
|
+
"remove_domain",
|
|
2514
|
+
"Remove a custom inbound domain: mail to it is refused from then on. The account's owner must approve it: this returns a link and a code to show them, then call wait_for_approval.",
|
|
2515
|
+
{
|
|
2516
|
+
id: import_zod2.z.string().describe("The domain's id, from list_domains"),
|
|
2517
|
+
teamId: teamIdSchema
|
|
2518
|
+
},
|
|
2519
|
+
withErrorHandling(async ({ id, teamId }) => {
|
|
2520
|
+
const pending = await client.domains.remove(id, { teamId });
|
|
2521
|
+
if (!pending.id) return textContent(`Removed the domain ${id}.`);
|
|
2522
|
+
return textContent(approvalAskText(pending));
|
|
2523
|
+
})
|
|
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
|
+
);
|
|
2371
2628
|
server.tool(
|
|
2372
2629
|
"stop_share",
|
|
2373
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-
|
|
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.
|
|
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
|
@@ -496,6 +496,38 @@ function keyText(key) {
|
|
|
496
496
|
key.expiresAt ? `It works until ${key.expiresAt}.` : ""
|
|
497
497
|
].filter(Boolean).join("\n");
|
|
498
498
|
}
|
|
499
|
+
function approvalResultText(result) {
|
|
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
|
+
}
|
|
512
|
+
if (typeof result.url === "string") {
|
|
513
|
+
return `Approved. The share link is ${result.url} (anyone with it can read the copy until ${String(result.expiresAt)}).`;
|
|
514
|
+
}
|
|
515
|
+
if (typeof result.removed === "string") {
|
|
516
|
+
return `Approved. The domain ${result.removed} was removed; mail to it is refused now.`;
|
|
517
|
+
}
|
|
518
|
+
if ("name" in result) {
|
|
519
|
+
return result.name === null ? "Approved. The account's display name was cleared." : `Approved. The account's display name is now "${String(result.name)}".`;
|
|
520
|
+
}
|
|
521
|
+
return `Approved and done: ${JSON.stringify(result)}`;
|
|
522
|
+
}
|
|
523
|
+
function approvalAskText(pending) {
|
|
524
|
+
return [
|
|
525
|
+
`Asked for approval: ${pending.summary}`,
|
|
526
|
+
`Show the person this link: ${pending.url}`,
|
|
527
|
+
`The page shows the code ${pending.code}; ask them to approve only if it matches.`,
|
|
528
|
+
`Then call wait_for_approval with id ${pending.id}. The request expires at ${pending.expiresAt}.`
|
|
529
|
+
].join("\n");
|
|
530
|
+
}
|
|
499
531
|
function textContent(text) {
|
|
500
532
|
return { content: [{ type: "text", text }] };
|
|
501
533
|
}
|
|
@@ -2084,19 +2116,12 @@ function registerTools(target, client, options = {}) {
|
|
|
2084
2116
|
const key = await pending.wait();
|
|
2085
2117
|
return textContent(keyText(key));
|
|
2086
2118
|
}
|
|
2087
|
-
return textContent(
|
|
2088
|
-
[
|
|
2089
|
-
`Asked for approval: ${pending.summary}`,
|
|
2090
|
-
`Show the person this link: ${pending.url}`,
|
|
2091
|
-
`The page shows the code ${pending.code}; ask them to approve only if it matches.`,
|
|
2092
|
-
`Then call wait_for_approval with id ${pending.id}. The request expires at ${pending.expiresAt}.`
|
|
2093
|
-
].join("\n")
|
|
2094
|
-
);
|
|
2119
|
+
return textContent(approvalAskText(pending));
|
|
2095
2120
|
})
|
|
2096
2121
|
);
|
|
2097
2122
|
server.tool(
|
|
2098
2123
|
"wait_for_approval",
|
|
2099
|
-
"Wait for the account's owner to answer an approval (from create_api_key) and collect what it grants, such as the new API key. 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.",
|
|
2100
2125
|
{
|
|
2101
2126
|
id: import_zod.z.string().describe("The approval id"),
|
|
2102
2127
|
timeout: durationOrTimestampSchema.default("45s").describe('How long to wait for the person, for example "45s"')
|
|
@@ -2104,8 +2129,10 @@ function registerTools(target, client, options = {}) {
|
|
|
2104
2129
|
withErrorHandling(async ({ id, timeout }) => {
|
|
2105
2130
|
const ms = durationMs(timeout) ?? 45e3;
|
|
2106
2131
|
try {
|
|
2107
|
-
const result = await client.approvals.wait(id, {
|
|
2108
|
-
|
|
2132
|
+
const result = await client.approvals.wait(id, {
|
|
2133
|
+
timeout: ms
|
|
2134
|
+
});
|
|
2135
|
+
return textContent(approvalResultText(result));
|
|
2109
2136
|
} catch (error) {
|
|
2110
2137
|
if (error instanceof import_sdk.ApprovalDeniedError) {
|
|
2111
2138
|
return textContent(
|
|
@@ -2135,6 +2162,236 @@ function registerTools(target, client, options = {}) {
|
|
|
2135
2162
|
return textContent(`Revoked the API key ${id}, if the account had it.`);
|
|
2136
2163
|
})
|
|
2137
2164
|
);
|
|
2165
|
+
server.tool(
|
|
2166
|
+
"rename_account",
|
|
2167
|
+
"Set the account's display name, or clear it with null. The account's owner must approve it: this returns a link and a code to show them, then call wait_for_approval.",
|
|
2168
|
+
{
|
|
2169
|
+
name: import_zod.z.string().min(1).max(100).nullable().describe("The new display name, or null to clear it")
|
|
2170
|
+
},
|
|
2171
|
+
withErrorHandling(async ({ name }) => {
|
|
2172
|
+
const pending = await client.account.rename(name);
|
|
2173
|
+
if (!pending.id) return textContent(approvalResultText(await pending.wait()));
|
|
2174
|
+
return textContent(approvalAskText(pending));
|
|
2175
|
+
})
|
|
2176
|
+
);
|
|
2177
|
+
server.tool(
|
|
2178
|
+
"list_connected_agents",
|
|
2179
|
+
"List the agents connected to the account (claimed agents and OAuth sign-ins such as hosted MCP clients): id, client name, when each connected and was last used.",
|
|
2180
|
+
{},
|
|
2181
|
+
withErrorHandling(async () => jsonContent(await client.agents.list()))
|
|
2182
|
+
);
|
|
2183
|
+
server.tool(
|
|
2184
|
+
"disconnect_agent",
|
|
2185
|
+
"Disconnect an agent by its id (from list_connected_agents): its tokens stop working at once. The endpoints it made stay in the account. Disconnecting the agent you are running as ends your own access.",
|
|
2186
|
+
{
|
|
2187
|
+
id: import_zod.z.string().describe("The agent's id, from list_connected_agents")
|
|
2188
|
+
},
|
|
2189
|
+
withErrorHandling(async ({ id }) => {
|
|
2190
|
+
await client.agents.disconnect(id);
|
|
2191
|
+
return textContent(`Disconnected the agent ${id}.`);
|
|
2192
|
+
})
|
|
2193
|
+
);
|
|
2194
|
+
server.tool(
|
|
2195
|
+
"list_share_links",
|
|
2196
|
+
"List live public links to frozen copies of requests (/s/<token>), the account's or one request's. Not the same as list_shares, which lists port previews and live tunnels.",
|
|
2197
|
+
{
|
|
2198
|
+
requestId: import_zod.z.string().optional().describe("Only the links to this request")
|
|
2199
|
+
},
|
|
2200
|
+
withErrorHandling(async ({ requestId }) => {
|
|
2201
|
+
const links = requestId ? await client.shareLinks.forRequest(requestId) : await client.shareLinks.list();
|
|
2202
|
+
return jsonContent(links);
|
|
2203
|
+
})
|
|
2204
|
+
);
|
|
2205
|
+
server.tool(
|
|
2206
|
+
"create_share_link",
|
|
2207
|
+
"Make a public link to a read-only copy of one HTTP request (not an email), for a teammate or a bug report. Anyone with the link can read it, so the account's owner must approve: this returns a link and a code to show them, then call wait_for_approval for the share link.",
|
|
2208
|
+
{
|
|
2209
|
+
requestId: import_zod.z.string().describe("The request's id"),
|
|
2210
|
+
expiresInDays: import_zod.z.union([import_zod.z.literal(1), import_zod.z.literal(7), import_zod.z.literal(30)]).optional().describe("How long the link works: 1, 7 or 30 days (default 7)"),
|
|
2211
|
+
hideSensitive: import_zod.z.boolean().optional().describe(
|
|
2212
|
+
"Also leave out signature and token headers (default true). Credentials are always left out."
|
|
2213
|
+
)
|
|
2214
|
+
},
|
|
2215
|
+
withErrorHandling(async ({ requestId, expiresInDays, hideSensitive }) => {
|
|
2216
|
+
const pending = await client.shareLinks.create(requestId, { expiresInDays, hideSensitive });
|
|
2217
|
+
if (!pending.id) return textContent(approvalResultText({ ...await pending.wait() }));
|
|
2218
|
+
return textContent(approvalAskText(pending));
|
|
2219
|
+
})
|
|
2220
|
+
);
|
|
2221
|
+
server.tool(
|
|
2222
|
+
"revoke_share_link",
|
|
2223
|
+
"Revoke a share link by its id (from list_share_links): its page answers 404 from then on.",
|
|
2224
|
+
{
|
|
2225
|
+
id: import_zod.z.string().describe("The link's id, from list_share_links")
|
|
2226
|
+
},
|
|
2227
|
+
withErrorHandling(async ({ id }) => {
|
|
2228
|
+
await client.shareLinks.revoke(id);
|
|
2229
|
+
return textContent(`Revoked the share link ${id}.`);
|
|
2230
|
+
})
|
|
2231
|
+
);
|
|
2232
|
+
const teamIdSchema = import_zod.z.string().optional().describe("A Team Business team's id (its owner only); leave out for your own account");
|
|
2233
|
+
server.tool(
|
|
2234
|
+
"list_domains",
|
|
2235
|
+
"List custom inbound domains (Business): each one's status, DNS records to publish, routes and catch-all endpoint.",
|
|
2236
|
+
{ teamId: teamIdSchema },
|
|
2237
|
+
withErrorHandling(async ({ teamId }) => jsonContent(await client.domains.list({ teamId })))
|
|
2238
|
+
);
|
|
2239
|
+
server.tool(
|
|
2240
|
+
"add_domain",
|
|
2241
|
+
"Add a custom inbound domain (Business) so mail to it is captured. Returns the TXT and MX records to publish; then call verify_domain.",
|
|
2242
|
+
{
|
|
2243
|
+
domain: import_zod.z.string().min(1).max(253).describe("The domain, such as mail.example.com"),
|
|
2244
|
+
teamId: teamIdSchema
|
|
2245
|
+
},
|
|
2246
|
+
withErrorHandling(
|
|
2247
|
+
async ({ domain, teamId }) => jsonContent(await client.domains.add(domain, { teamId }))
|
|
2248
|
+
)
|
|
2249
|
+
);
|
|
2250
|
+
server.tool(
|
|
2251
|
+
"verify_domain",
|
|
2252
|
+
"Look a custom domain's TXT and MX records up now. It receives mail once both are found.",
|
|
2253
|
+
{
|
|
2254
|
+
id: import_zod.z.string().describe("The domain's id, from list_domains"),
|
|
2255
|
+
teamId: teamIdSchema
|
|
2256
|
+
},
|
|
2257
|
+
withErrorHandling(
|
|
2258
|
+
async ({ id, teamId }) => jsonContent(await client.domains.verify(id, { teamId }))
|
|
2259
|
+
)
|
|
2260
|
+
);
|
|
2261
|
+
server.tool(
|
|
2262
|
+
"route_domain",
|
|
2263
|
+
"Set where a custom domain's mail goes: routes by local part (the part before @) to endpoints, everything else to a catch-all endpoint, or refused when the catch-all is null. Replaces the domain's routing.",
|
|
2264
|
+
{
|
|
2265
|
+
id: import_zod.z.string().describe("The domain's id, from list_domains"),
|
|
2266
|
+
catchAllEndpointId: import_zod.z.string().nullable().describe("The endpoint id for mail no route matches, or null to refuse it"),
|
|
2267
|
+
routes: import_zod.z.array(
|
|
2268
|
+
import_zod.z.object({
|
|
2269
|
+
localPart: import_zod.z.string().max(64).describe("The part before @"),
|
|
2270
|
+
endpointId: import_zod.z.string().describe("The endpoint's id")
|
|
2271
|
+
})
|
|
2272
|
+
).max(100).default([]).describe("Routes by local part"),
|
|
2273
|
+
teamId: teamIdSchema
|
|
2274
|
+
},
|
|
2275
|
+
withErrorHandling(
|
|
2276
|
+
async ({ id, catchAllEndpointId, routes, teamId }) => jsonContent(await client.domains.route(id, { catchAllEndpointId, routes }, { teamId }))
|
|
2277
|
+
)
|
|
2278
|
+
);
|
|
2279
|
+
server.tool(
|
|
2280
|
+
"remove_domain",
|
|
2281
|
+
"Remove a custom inbound domain: mail to it is refused from then on. The account's owner must approve it: this returns a link and a code to show them, then call wait_for_approval.",
|
|
2282
|
+
{
|
|
2283
|
+
id: import_zod.z.string().describe("The domain's id, from list_domains"),
|
|
2284
|
+
teamId: teamIdSchema
|
|
2285
|
+
},
|
|
2286
|
+
withErrorHandling(async ({ id, teamId }) => {
|
|
2287
|
+
const pending = await client.domains.remove(id, { teamId });
|
|
2288
|
+
if (!pending.id) return textContent(`Removed the domain ${id}.`);
|
|
2289
|
+
return textContent(approvalAskText(pending));
|
|
2290
|
+
})
|
|
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
|
+
);
|
|
2138
2395
|
server.tool(
|
|
2139
2396
|
"stop_share",
|
|
2140
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webhooks-cc/mcp",
|
|
3
|
-
"version": "1.
|
|
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.
|
|
47
|
+
"@webhooks-cc/sdk": "1.27.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/node": "^26.6.6",
|