@webhooks-cc/mcp 1.23.0 → 2.0.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
@@ -2,125 +2,219 @@
2
2
  "use strict";
3
3
 
4
4
  // bin/mcp.ts
5
- var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
5
+ var import_stdio = require("@modelcontextprotocol/server/stdio");
6
6
 
7
7
  // src/index.ts
8
- var import_mcp2 = require("@modelcontextprotocol/sdk/server/mcp.js");
8
+ var import_server3 = require("@modelcontextprotocol/server");
9
9
  var import_sdk3 = require("@webhooks-cc/sdk");
10
10
 
11
- // src/prompts.ts
12
- var import_zod = require("zod");
13
- function registerPrompts(server) {
14
- server.registerPrompt(
15
- "debug_webhook_delivery",
16
- {
17
- title: "Debug Webhook Delivery",
18
- description: "Guide an agent through diagnosing why webhook delivery is failing or missing.",
19
- argsSchema: {
20
- provider: import_zod.z.string().optional().describe("Webhook provider, if known"),
21
- endpointSlug: import_zod.z.string().optional().describe("Hosted endpoint slug, if known"),
22
- targetUrl: import_zod.z.string().optional().describe("Your app's receiving URL, if known")
23
- }
24
- },
25
- async ({ provider, endpointSlug, targetUrl }) => {
26
- const scope = [
27
- provider ? `Provider: ${provider}.` : null,
28
- endpointSlug ? `Endpoint slug: ${endpointSlug}.` : null,
29
- targetUrl ? `Target URL: ${targetUrl}.` : null
30
- ].filter(Boolean).join(" ");
31
- return {
32
- description: "Diagnose a missing or broken webhook delivery.",
33
- messages: [
34
- {
35
- role: "user",
36
- content: {
37
- type: "text",
38
- text: [
39
- scope,
40
- "Diagnose webhook delivery step by step.",
41
- "Use list_endpoints or get_endpoint to confirm the endpoint and URL.",
42
- "Use list_requests, wait_for_request, or wait_for_requests to check whether anything arrived.",
43
- "If the provider is known, use preview_webhook or send_webhook to reproduce the webhook with realistic signing.",
44
- "Use verify_signature when a secret is available.",
45
- "Use compare_requests to compare retries or changed payloads.",
46
- "Conclude with the most likely cause and the next concrete fix."
47
- ].filter(Boolean).join(" ")
48
- }
49
- }
50
- ]
51
- };
52
- }
53
- );
54
- server.registerPrompt(
55
- "setup_provider_testing",
56
- {
57
- title: "Setup Provider Testing",
58
- description: "Guide an agent through setting up webhook testing for a provider.",
59
- argsSchema: {
60
- provider: import_zod.z.string().describe("Webhook provider to test"),
61
- targetUrl: import_zod.z.string().optional().describe("Optional local or remote target URL")
62
- }
63
- },
64
- async ({ provider, targetUrl }) => {
65
- return {
66
- description: `Set up webhook testing for ${provider}.`,
67
- messages: [
68
- {
69
- role: "user",
70
- content: {
71
- type: "text",
72
- text: [
73
- `Set up webhook testing for ${provider}.`,
74
- "Use list_provider_templates to inspect supported templates and signing details first.",
75
- "Create an endpoint with create_endpoint, preferably ephemeral.",
76
- "If a target URL is provided, use preview_webhook before send_to so the request shape is visible.",
77
- targetUrl ? `Target URL: ${targetUrl}.` : null,
78
- "Send a realistic provider webhook, wait for capture, and verify the signature if a secret is available.",
79
- "Once real deliveries are captured, use generate_payload_schema to write the payload types for the handler.",
80
- "Return the endpoint URL, the exact tools used, and the next step for the developer."
81
- ].filter(Boolean).join(" ")
82
- }
83
- }
84
- ]
85
- };
86
- }
11
+ // src/local-tools.ts
12
+ var import_server = require("@modelcontextprotocol/server");
13
+ var import_zod2 = require("zod");
14
+
15
+ // src/catalog.ts
16
+ var TOOL_GROUPS = {
17
+ core: "Endpoints, captured requests and emails, sending test webhooks, signatures, forwarding destinations, deliveries, previews of your local app and their logs, usage and approvals",
18
+ forwarding: "The rest of forwarding: preview what a destination would send, the endpoint's forwarding settings and signing secret, test deliveries",
19
+ layouts: "Email layouts and named fields: try layouts and fields on a captured email, add ready-made layouts, and AI setup",
20
+ extras: "Bulk endpoint changes, favourites, request counts and payload schemas",
21
+ teams: "Teams: members, invites, sharing endpoints with a team, and pricing team changes",
22
+ account: "The account: billing, API keys, connected agents, public share links to requests, and the display name",
23
+ domains: "Custom inbound email domains: add, verify, route and remove them",
24
+ sheets: "Google Sheets destinations: Google accounts, spreadsheets and their tabs",
25
+ names: "Preview names: reserve, release and password-protect them"
26
+ };
27
+ var TOOL_GROUP_NAMES = Object.keys(TOOL_GROUPS);
28
+ var read = { readOnly: true };
29
+ var write = {};
30
+ var update = { idempotent: true };
31
+ var remove = { destructive: true, idempotent: true };
32
+ var replace = { destructive: true, idempotent: true };
33
+ var outward = { openWorld: true };
34
+ function group(name, tools) {
35
+ return Object.fromEntries(
36
+ Object.entries(tools).map(([tool, behaviour]) => [tool, { group: name, ...behaviour }])
87
37
  );
88
- server.registerPrompt(
89
- "compare_webhook_attempts",
90
- {
91
- title: "Compare Webhook Attempts",
92
- description: "Guide an agent through comparing two webhook deliveries or retries.",
93
- argsSchema: {
94
- endpointSlug: import_zod.z.string().optional().describe("Endpoint slug to inspect for recent attempts"),
95
- leftRequestId: import_zod.z.string().optional().describe("First request ID, if already known"),
96
- rightRequestId: import_zod.z.string().optional().describe("Second request ID, if already known")
97
- }
98
- },
99
- async ({ endpointSlug, leftRequestId, rightRequestId }) => {
100
- return {
101
- description: "Compare two webhook deliveries and explain the difference.",
102
- messages: [
103
- {
104
- role: "user",
105
- content: {
106
- type: "text",
107
- text: [
108
- endpointSlug ? `Endpoint slug: ${endpointSlug}.` : null,
109
- leftRequestId && rightRequestId ? `Compare request ${leftRequestId} against ${rightRequestId}.` : "Find the most relevant two webhook attempts first.",
110
- "Use compare_requests for the structured diff.",
111
- "If request IDs are not provided, use list_requests or the endpoint recent resource to find the last two attempts.",
112
- "Explain what changed in the body, headers, or timing, and whether the difference looks expected, retried, or broken."
113
- ].filter(Boolean).join(" ")
114
- }
115
- }
116
- ]
117
- };
38
+ }
39
+ var TOOL_CATALOG = {
40
+ ...group("core", {
41
+ create_endpoint: write,
42
+ list_endpoints: read,
43
+ get_endpoint: read,
44
+ // Replaces what it is given (a mock response, the signing secret).
45
+ update_endpoint: replace,
46
+ delete_endpoint: remove,
47
+ list_requests: read,
48
+ search_requests: read,
49
+ get_request: read,
50
+ wait_for_request: read,
51
+ wait_for_requests: read,
52
+ replay_request: outward,
53
+ compare_requests: read,
54
+ extract_from_request: read,
55
+ clear_requests: remove,
56
+ send_webhook: write,
57
+ send_to: outward,
58
+ test_webhook_flow: outward,
59
+ preview_webhook: read,
60
+ preview_response: read,
61
+ list_provider_templates: read,
62
+ verify_signature: read,
63
+ list_emails: read,
64
+ get_email: read,
65
+ wait_for_email: read,
66
+ send_test_email: write,
67
+ render_email: outward,
68
+ list_destinations: read,
69
+ add_destination: write,
70
+ update_destination: replace,
71
+ remove_destination: remove,
72
+ list_deliveries: read,
73
+ redeliver: outward,
74
+ share_port: outward,
75
+ list_shares: read,
76
+ share_requests: read,
77
+ stop_share: remove,
78
+ get_usage: read,
79
+ // Collecting an approved key hands it over once: not a pure read.
80
+ wait_for_approval: write,
81
+ describe: read
82
+ }),
83
+ ...group("forwarding", {
84
+ preview_destination: read,
85
+ configure_forwarding: replace,
86
+ // rotate: true replaces the secret, and receivers holding the old one fail.
87
+ get_forwarding_secret: { destructive: true },
88
+ test_forwarding: outward
89
+ }),
90
+ ...group("layouts", {
91
+ preview_layouts: read,
92
+ list_layout_templates: read,
93
+ add_layout_template: write,
94
+ preview_email_fields: read,
95
+ ai_setup_status: read,
96
+ // Sends the chosen emails to Anthropic once the owner approves.
97
+ ai_setup: outward
98
+ }),
99
+ ...group("extras", {
100
+ create_endpoints: write,
101
+ delete_endpoints: remove,
102
+ favorite_endpoint: update,
103
+ count_requests: read,
104
+ generate_payload_schema: read
105
+ }),
106
+ ...group("teams", {
107
+ list_teams: read,
108
+ list_team_members: read,
109
+ share_endpoint: update,
110
+ unshare_endpoint: remove,
111
+ create_team: write,
112
+ rename_team: update,
113
+ delete_team: remove,
114
+ remove_team_member: remove,
115
+ leave_team: remove,
116
+ invite_to_team: outward,
117
+ list_invites: read,
118
+ answer_invite: write,
119
+ withdraw_invite: remove,
120
+ quote_team_change: read
121
+ }),
122
+ ...group("account", {
123
+ get_billing: read,
124
+ list_api_keys: read,
125
+ create_api_key: write,
126
+ revoke_api_key: remove,
127
+ rename_account: update,
128
+ list_connected_agents: read,
129
+ disconnect_agent: remove,
130
+ list_share_links: read,
131
+ create_share_link: write,
132
+ revoke_share_link: remove
133
+ }),
134
+ ...group("domains", {
135
+ list_domains: read,
136
+ add_domain: write,
137
+ // A failed check counts toward lapsing a verified domain: not safe to repeat.
138
+ verify_domain: { destructive: true },
139
+ // Replaces the routing; a missing catch-all refuses mail.
140
+ route_domain: replace,
141
+ remove_domain: remove
142
+ }),
143
+ ...group("sheets", {
144
+ list_google_connections: read,
145
+ connect_google: outward,
146
+ pick_spreadsheet: outward,
147
+ create_spreadsheet: outward,
148
+ list_sheet_tabs: { readOnly: true, openWorld: true },
149
+ disconnect_google: remove
150
+ }),
151
+ ...group("names", {
152
+ // The log of what visitors asked a running preview for.
153
+ list_names: read,
154
+ reserve_name: write,
155
+ release_name: remove,
156
+ set_name_password: update,
157
+ // Opens the name to anyone again.
158
+ clear_name_password: { destructive: true, idempotent: true }
159
+ })
160
+ };
161
+ var ENABLE_TOOLS = "enable_tools";
162
+ function toolAnnotations(name) {
163
+ const entry = TOOL_CATALOG[name];
164
+ const readOnly = entry?.readOnly === true;
165
+ return {
166
+ title: toolTitle(name),
167
+ readOnlyHint: readOnly,
168
+ // MCP reads a missing destructiveHint as true on any tool that writes.
169
+ destructiveHint: !readOnly && entry?.destructive === true,
170
+ idempotentHint: readOnly || entry?.idempotent === true,
171
+ openWorldHint: entry?.openWorld === true
172
+ };
173
+ }
174
+ function toolTitle(name) {
175
+ const words = name.split("_").map((word) => TITLE_WORDS[word] ?? word);
176
+ const first = words[0] ?? "";
177
+ return [first.charAt(0).toUpperCase() + first.slice(1), ...words.slice(1)].join(" ");
178
+ }
179
+ var TITLE_WORDS = {
180
+ api: "API",
181
+ ai: "AI",
182
+ mcp: "MCP"
183
+ };
184
+ function parseToolGroups(setting) {
185
+ if (setting === null || setting === void 0 || setting.trim() === "") return null;
186
+ const groups = /* @__PURE__ */ new Set(["core"]);
187
+ const unknown = [];
188
+ for (const raw of setting.split(/[\s,+]+/)) {
189
+ const name = raw.trim().toLowerCase();
190
+ if (!name) continue;
191
+ if (name === "all") {
192
+ for (const g of TOOL_GROUP_NAMES) groups.add(g);
193
+ } else if (TOOL_GROUP_NAMES.includes(name)) {
194
+ groups.add(name);
195
+ } else {
196
+ unknown.push(raw.trim());
118
197
  }
119
- );
198
+ }
199
+ return { groups, unknown };
200
+ }
201
+ var LOADS_EVERYTHING = [
202
+ /^claude-code$/i,
203
+ /^claude-ai$/i,
204
+ /^claude$/i,
205
+ /codex/i,
206
+ /^openai-mcp$/i,
207
+ /chatgpt/i
208
+ ];
209
+ function clientLoadsEverything(clientName) {
210
+ if (!clientName) return false;
211
+ const name = clientName.trim().toLowerCase().replace(/[\s_]+/g, "-");
212
+ return LOADS_EVERYTHING.some((pattern) => pattern.test(name));
120
213
  }
121
214
 
122
- // src/resources.ts
123
- var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
215
+ // src/tools.ts
216
+ var import_zod = require("zod");
217
+ var import_sdk = require("@webhooks-cc/sdk");
124
218
 
125
219
  // src/compact.ts
126
220
  var MAX_OUTPUT = 32768;
@@ -330,142 +424,11 @@ function compactRequest(request) {
330
424
  return compact;
331
425
  }
332
426
 
333
- // src/resources.ts
334
- var ENDPOINTS_RESOURCE_URI = "webhooks://endpoints";
335
- var ENDPOINT_RECENT_TEMPLATE_URI = "webhooks://endpoint/{slug}/recent";
336
- var REQUEST_TEMPLATE_URI = "webhooks://request/{id}";
337
- var MAX_ENDPOINT_RESOURCE_ITEMS = 25;
338
- var RECENT_REQUEST_LIMIT = 10;
339
- function jsonResource(uri, value) {
340
- return {
341
- contents: [
342
- {
343
- uri,
344
- mimeType: "application/json",
345
- text: JSON.stringify(value, null, 2)
346
- }
347
- ]
348
- };
349
- }
350
- function summarizeRequest(request) {
351
- return {
352
- id: request.id,
353
- method: request.method,
354
- path: request.path,
355
- receivedAt: request.receivedAt,
356
- contentType: request.contentType ?? null
357
- };
358
- }
359
- function variableToString(value, name) {
360
- if (typeof value === "string" && value.length > 0) {
361
- return value;
362
- }
363
- throw new Error(`Missing resource variable "${name}"`);
364
- }
365
- async function listEndpointSummaries(client) {
366
- const endpoints = (await client.endpoints.list()).slice().sort((left, right) => right.createdAt - left.createdAt);
367
- const truncated = endpoints.length > MAX_ENDPOINT_RESOURCE_ITEMS;
368
- const visible = endpoints.slice(0, MAX_ENDPOINT_RESOURCE_ITEMS);
369
- const summaries = await Promise.all(
370
- visible.map(async (endpoint) => {
371
- const recent = await client.requests.list(endpoint.slug, { limit: 1 });
372
- return {
373
- ...endpoint,
374
- lastRequest: recent[0] ? summarizeRequest(recent[0]) : null
375
- };
376
- })
377
- );
378
- return {
379
- endpoints: summaries,
380
- total: endpoints.length,
381
- truncated
382
- };
383
- }
384
- function registerResources(server, client) {
385
- server.registerResource(
386
- "endpoints-overview",
387
- ENDPOINTS_RESOURCE_URI,
388
- {
389
- title: "Endpoints Overview",
390
- description: "All endpoints with a summary of recent activity.",
391
- mimeType: "application/json"
392
- },
393
- async () => {
394
- return jsonResource(ENDPOINTS_RESOURCE_URI, await listEndpointSummaries(client));
395
- }
396
- );
397
- server.registerResource(
398
- "endpoint-recent-requests",
399
- new import_mcp.ResourceTemplate(ENDPOINT_RECENT_TEMPLATE_URI, {
400
- list: async () => {
401
- const endpoints = await client.endpoints.list();
402
- return {
403
- resources: endpoints.map((endpoint) => ({
404
- uri: `webhooks://endpoint/${endpoint.slug}/recent`,
405
- name: `${endpoint.slug} recent requests`,
406
- description: `Recent requests for endpoint ${endpoint.slug}`,
407
- mimeType: "application/json"
408
- }))
409
- };
410
- },
411
- complete: {
412
- slug: async (value) => {
413
- const endpoints = await client.endpoints.list();
414
- return endpoints.map((endpoint) => endpoint.slug).filter((slug) => slug.startsWith(value)).slice(0, 20);
415
- }
416
- }
417
- }),
418
- {
419
- title: "Endpoint Recent Requests",
420
- description: "Last 10 captured requests for an endpoint.",
421
- mimeType: "application/json"
422
- },
423
- async (uri, variables) => {
424
- const slug = variableToString(variables.slug, "slug");
425
- const [endpoint, requests] = await Promise.all([
426
- client.endpoints.get(slug),
427
- client.requests.list(slug, { limit: RECENT_REQUEST_LIMIT })
428
- ]);
429
- return jsonResource(uri.toString(), {
430
- endpoint,
431
- requests: requests.map(compactRequest)
432
- });
433
- }
434
- );
435
- server.registerResource(
436
- "request-details",
437
- new import_mcp.ResourceTemplate(REQUEST_TEMPLATE_URI, {
438
- list: void 0,
439
- complete: {}
440
- }),
441
- {
442
- title: "Request Details",
443
- description: "Full details for a captured request by ID.",
444
- mimeType: "application/json"
445
- },
446
- async (uri, variables) => {
447
- const id = variableToString(variables.id, "id");
448
- const request = await client.requests.get(id);
449
- return jsonResource(uri.toString(), compactRequest(request));
450
- }
451
- );
452
- }
453
-
454
- // src/sandbox-tools.ts
455
- var import_zod3 = require("zod");
456
- var import_sdk2 = require("@webhooks-cc/sdk");
457
-
458
- // src/version.ts
459
- var VERSION = true ? "1.23.0" : "0.0.0-dev";
460
- var MCP_USER_AGENT = `webhooks-cc-mcp/${VERSION}`;
461
-
462
427
  // src/tools.ts
463
- var import_zod2 = require("zod");
464
- var import_sdk = require("@webhooks-cc/sdk");
465
428
  var MAX_BODY_SIZE = MAX_OUTPUT;
466
429
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
467
430
  var TIME_SEPARATOR = " \u2014 ";
468
- var httpUrlSchema = import_zod2.z.string().url().refine(
431
+ var httpUrlSchema = import_zod.z.string().url().refine(
469
432
  (value) => {
470
433
  try {
471
434
  const protocol = new URL(value).protocol;
@@ -476,24 +439,24 @@ var httpUrlSchema = import_zod2.z.string().url().refine(
476
439
  },
477
440
  { message: "Only http and https URLs are supported" }
478
441
  );
479
- var methodSchema = import_zod2.z.enum(HTTP_METHODS).default("POST").describe("HTTP method (default: POST)");
480
- var kindSchema = import_zod2.z.enum(["http", "email"]).optional().describe('Only HTTP requests ("http") or only captured emails ("email")');
442
+ var methodSchema = import_zod.z.enum(HTTP_METHODS).default("POST").describe("HTTP method (default: POST)");
443
+ var kindSchema = import_zod.z.enum(["http", "email"]).optional().describe('Only HTTP requests ("http") or only captured emails ("email")');
481
444
  var EMAIL_SCAN_LIMIT = 100;
482
- var durationOrTimestampSchema = import_zod2.z.union([import_zod2.z.string(), import_zod2.z.number()]);
445
+ var durationOrTimestampSchema = import_zod.z.union([import_zod.z.string(), import_zod.z.number()]);
483
446
  var CONDITION_FIELD_NAMES = Object.keys(import_sdk.CONDITION_FIELDS);
484
447
  var CONDITION_OP_NAMES = import_sdk.CONDITION_OPS;
485
448
  function conditionSchema(use) {
486
449
  const fields = use === "response" ? CONDITION_FIELD_NAMES.filter((field) => import_sdk.CONDITION_FIELDS[field].response) : CONDITION_FIELD_NAMES;
487
- return import_zod2.z.object({
488
- field: import_zod2.z.enum(fields).describe(
450
+ return import_zod.z.object({
451
+ field: import_zod.z.enum(fields).describe(
489
452
  use === "response" ? "What to read: method, path, header (name), query (name), body (text), body_path (path into a JSON or form body), content_type, size" : "What to read: method, path, header (name), query (name), body, body_path (path), content_type, size, kind (http or email), email (name: subject, from, to, text or tag), spam_score, email_auth (name: spf, dkim or dmarc), signature (verified, failed or missing)"
490
453
  ),
491
- op: import_zod2.z.enum(CONDITION_OP_NAMES).describe(
454
+ op: import_zod.z.enum(CONDITION_OP_NAMES).describe(
492
455
  "eq, neq, contains, not_contains, starts_with, ends_with, matches (glob: * one path segment, ** any, ? one character), regex (RE2, searched anywhere; (?i) ignores case), exists, not_exists, gt, gte, lt, lte (numbers)"
493
456
  ),
494
- value: import_zod2.z.string().max(import_sdk.MAX_CONDITION_VALUE_LEN).optional().describe(`What to compare with (not for exists and not_exists)`),
495
- name: import_zod2.z.string().max(import_sdk.MAX_CONDITION_NAME_LEN).optional().describe("Header or query parameter name; the email part; spf, dkim or dmarc"),
496
- path: import_zod2.z.string().max(import_sdk.MAX_CONDITION_PATH_LEN).optional().describe('Dot path for body_path, e.g. data.object.status or items.0.sku or a["odd.key"]')
457
+ value: import_zod.z.string().max(import_sdk.MAX_CONDITION_VALUE_LEN).optional().describe(`What to compare with (not for exists and not_exists)`),
458
+ name: import_zod.z.string().max(import_sdk.MAX_CONDITION_NAME_LEN).optional().describe("Header or query parameter name; the email part; spf, dkim or dmarc"),
459
+ path: import_zod.z.string().max(import_sdk.MAX_CONDITION_PATH_LEN).optional().describe('Dot path for body_path, e.g. data.object.status or items.0.sku or a["odd.key"]')
497
460
  }).superRefine((condition, ctx) => {
498
461
  for (const issue of (0, import_sdk.conditionIssues)(condition, { use })) {
499
462
  ctx.addIssue({ code: "custom", message: issue });
@@ -501,46 +464,46 @@ function conditionSchema(use) {
501
464
  });
502
465
  }
503
466
  var ruleConditionSchema = conditionSchema("response");
504
- var emailFieldSchema = import_zod2.z.object({
505
- name: import_zod2.z.string().describe("Letters, digits and _, starting with a letter: {{ fields.name }}"),
506
- from: import_zod2.z.enum(["subject", "text", "html", "from", "to", "header"]).describe("What it reads; html is the HTML as text"),
507
- header: import_zod2.z.string().optional().describe('The header name, for from "header"'),
508
- pattern: import_zod2.z.string().describe("RE2 regular expression; the first group is the value (no lookarounds)")
467
+ var emailFieldSchema = import_zod.z.object({
468
+ name: import_zod.z.string().describe("Letters, digits and _, starting with a letter: {{ fields.name }}"),
469
+ from: import_zod.z.enum(["subject", "text", "html", "from", "to", "header"]).describe("What it reads; html is the HTML as text"),
470
+ header: import_zod.z.string().optional().describe('The header name, for from "header"'),
471
+ pattern: import_zod.z.string().describe("RE2 regular expression; the first group is the value (no lookarounds)")
509
472
  });
510
- var layoutSchema = import_zod2.z.object({
511
- id: import_zod2.z.string().describe("Stable id: 1 to 40 letters, digits, - or _"),
512
- name: import_zod2.z.string().max(100),
513
- enabled: import_zod2.z.boolean().optional().describe("Off: kept but never matched (default on)"),
514
- match: import_zod2.z.object({
515
- logic: import_zod2.z.enum(["and", "or"]).optional(),
516
- conditions: import_zod2.z.array(conditionSchema("filter")).max(10)
473
+ var layoutSchema = import_zod.z.object({
474
+ id: import_zod.z.string().describe("Stable id: 1 to 40 letters, digits, - or _"),
475
+ name: import_zod.z.string().max(100),
476
+ enabled: import_zod.z.boolean().optional().describe("Off: kept but never matched (default on)"),
477
+ match: import_zod.z.object({
478
+ logic: import_zod.z.enum(["and", "or"]).optional(),
479
+ conditions: import_zod.z.array(conditionSchema("filter")).max(10)
517
480
  }).optional().describe("When it applies, e.g. the sender or subject; no conditions: every email"),
518
- fields: import_zod2.z.array(emailFieldSchema).max(20).optional().describe("Named values, as emailFields"),
519
- lineItems: import_zod2.z.object({
520
- from: import_zod2.z.enum(["text", "html", "attachment"]),
521
- pattern: import_zod2.z.string().optional().describe(
481
+ fields: import_zod.z.array(emailFieldSchema).max(20).optional().describe("Named values, as emailFields"),
482
+ lineItems: import_zod.z.object({
483
+ from: import_zod.z.enum(["text", "html", "attachment"]),
484
+ pattern: import_zod.z.string().optional().describe(
522
485
  "text/html: RE2 with named groups found again and again, one row each, e.g. (?P<sku>[A-Z]+-\\d+)\\s+x(?P<qty>\\d+)"
523
486
  ),
524
- start: import_zod2.z.string().optional().describe("text/html: search only after this (RE2)"),
525
- end: import_zod2.z.string().optional().describe("text/html: and before this (RE2)"),
526
- attachment: import_zod2.z.string().optional().describe("attachment: file name with * and ? (default: the first spreadsheet or CSV)"),
527
- sheet: import_zod2.z.string().optional().describe("attachment: sheet name (default: the first)"),
528
- columns: import_zod2.z.array(import_zod2.z.object({ name: import_zod2.z.string(), column: import_zod2.z.string() })).max(30).optional().describe("attachment: { name, column: header text or letter }; default: every column")
487
+ start: import_zod.z.string().optional().describe("text/html: search only after this (RE2)"),
488
+ end: import_zod.z.string().optional().describe("text/html: and before this (RE2)"),
489
+ attachment: import_zod.z.string().optional().describe("attachment: file name with * and ? (default: the first spreadsheet or CSV)"),
490
+ sheet: import_zod.z.string().optional().describe("attachment: sheet name (default: the first)"),
491
+ columns: import_zod.z.array(import_zod.z.object({ name: import_zod.z.string(), column: import_zod.z.string() })).max(30).optional().describe("attachment: { name, column: header text or letter }; default: every column")
529
492
  }).nullable().optional().describe("Rows such as an order's products"),
530
- parseAttachments: import_zod2.z.boolean().optional().describe("Parse CSV, XLSX, ODS, PDF text and text files into the JSON; uses parsed pages")
493
+ parseAttachments: import_zod.z.boolean().optional().describe("Parse CSV, XLSX, ODS, PDF text and text files into the JSON; uses parsed pages")
531
494
  });
532
495
  var destinationFields = {
533
- name: import_zod2.z.string().max(100).nullable().optional().describe("A name for the list and the log"),
534
- enabled: import_zod2.z.boolean().optional().describe("Send to it (default true)"),
535
- http: import_zod2.z.boolean().optional().describe("Takes HTTP requests (default true)"),
536
- email: import_zod2.z.boolean().optional().describe("Takes emails (default true)"),
537
- filter: import_zod2.z.object({
538
- logic: import_zod2.z.enum(["and", "or"]).optional().describe("and (default): every condition; or: any"),
539
- conditions: import_zod2.z.array(conditionSchema("filter")).max(10)
496
+ name: import_zod.z.string().max(100).nullable().optional().describe("A name for the list and the log"),
497
+ enabled: import_zod.z.boolean().optional().describe("Send to it (default true)"),
498
+ http: import_zod.z.boolean().optional().describe("Takes HTTP requests (default true)"),
499
+ email: import_zod.z.boolean().optional().describe("Takes emails (default true)"),
500
+ filter: import_zod.z.object({
501
+ logic: import_zod.z.enum(["and", "or"]).optional().describe("and (default): every condition; or: any"),
502
+ conditions: import_zod.z.array(conditionSchema("filter")).max(10)
540
503
  }).nullable().optional().describe(
541
504
  "Send only captures that match (null: everything it takes). Conditions like response rules plus kind, email (name: subject, from, to, text, tag), spam_score, email_auth (name: spf, dkim, dmarc) and signature (verified, failed, missing)"
542
505
  ),
543
- format: import_zod2.z.enum([
506
+ format: import_zod.z.enum([
544
507
  "auto",
545
508
  "as_received",
546
509
  "json",
@@ -553,103 +516,103 @@ var destinationFields = {
553
516
  ]).optional().describe(
554
517
  "auto (pick from the URL), as_received (relay HTTP requests unchanged; emails as signed JSON), json (signed JSON envelope), template (your bodyTemplate, signed like json), chat (Slack or Discord message), or an inbound mail provider's shape for emails (postmark, sendgrid, mailgun, cloudmailin; HTTP requests then go as received), to test the inbound handler you wrote for that provider"
555
518
  ),
556
- mailgunSigningKey: import_zod2.z.string().max(200).nullable().optional().describe(
519
+ mailgunSigningKey: import_zod.z.string().max(200).nullable().optional().describe(
557
520
  "For format mailgun: your Mailgun HTTP webhook signing key, so the form's signature verifies; null removes it. Write-only."
558
521
  ),
559
- bodyTemplate: import_zod2.z.unknown().optional().describe(
522
+ bodyTemplate: import_zod.z.unknown().optional().describe(
560
523
  `For format template: any JSON value whose strings hold {{ placeholders }}, e.g. {"event": "{{ json.type }}", "amount": "{{ json.data.amount }}", "code": "{{ codes.0 }}"}. A string that is only a placeholder keeps the value's type; a missing field becomes null. HTTP requests read method, path, query.x, headers.x, body, json.path, id, receivedAt, signature; emails read the email.received fields (subject, from.address, to, text, codes, links, auth, spam, tag)`
561
524
  ),
562
- messageTemplate: import_zod2.z.string().max(4e3).nullable().optional().describe(
525
+ messageTemplate: import_zod.z.string().max(4e3).nullable().optional().describe(
563
526
  "For format chat: the message text with {{ placeholders }}; null sends the standard message"
564
527
  ),
565
- headers: import_zod2.z.array(
566
- import_zod2.z.object({
567
- name: import_zod2.z.string().describe("Header name, e.g. Authorization"),
568
- value: import_zod2.z.string().nullable().describe("Value, or null to keep the stored one")
528
+ headers: import_zod.z.array(
529
+ import_zod.z.object({
530
+ name: import_zod.z.string().describe("Header name, e.g. Authorization"),
531
+ value: import_zod.z.string().nullable().describe("Value, or null to keep the stored one")
569
532
  })
570
533
  ).max(10).optional().describe("Your own headers for every delivery (not chat). Replaces the whole list"),
571
- appendPath: import_zod2.z.boolean().optional().describe("Append the captured path to the URL (as_received)"),
572
- retrySeconds: import_zod2.z.union([import_zod2.z.literal(0), import_zod2.z.literal(3600), import_zod2.z.literal(86400)]).optional().describe("Retry failed deliveries for 86400 (default), 3600 or 0 seconds"),
573
- keepOrder: import_zod2.z.boolean().optional().describe("One delivery at a time, in capture order"),
574
- sentField: import_zod2.z.string().max(128).nullable().optional().describe("JSON body field with the sender's own timestamp"),
575
- throttleSeconds: import_zod2.z.number().int().min(0).max(3600).optional().describe(
534
+ appendPath: import_zod.z.boolean().optional().describe("Append the captured path to the URL (as_received)"),
535
+ retrySeconds: import_zod.z.union([import_zod.z.literal(0), import_zod.z.literal(3600), import_zod.z.literal(86400)]).optional().describe("Retry failed deliveries for 86400 (default), 3600 or 0 seconds"),
536
+ keepOrder: import_zod.z.boolean().optional().describe("One delivery at a time, in capture order"),
537
+ sentField: import_zod.z.string().max(128).nullable().optional().describe("JSON body field with the sender's own timestamp"),
538
+ throttleSeconds: import_zod.z.number().int().min(0).max(3600).optional().describe(
576
539
  "At most one delivery every N seconds; the rest are not sent (1 makes a heads-up channel)"
577
540
  ),
578
- delaySeconds: import_zod2.z.number().int().min(0).max(3600).optional().describe("Wait this long after the capture before sending, like a callback")
541
+ delaySeconds: import_zod.z.number().int().min(0).max(3600).optional().describe("Wait this long after the capture before sending, like a callback")
579
542
  };
580
- var responseRuleSchema = import_zod2.z.object({
581
- name: import_zod2.z.string().max(import_sdk.MAX_RULE_NAME_LEN).optional().describe(`Human-readable rule name (max ${import_sdk.MAX_RULE_NAME_LEN} chars)`),
582
- enabled: import_zod2.z.boolean().optional().default(true).describe("Whether this rule is active"),
583
- logic: import_zod2.z.enum(["and", "or"]).optional().default("and").describe('How to combine conditions: "and" (all match) or "or" (any match)'),
584
- conditions: import_zod2.z.array(ruleConditionSchema).min(1).max(10).describe("Conditions to evaluate (1-10)"),
585
- response: import_zod2.z.lazy(() => mockResponseSchema).describe("Response when conditions match")
543
+ var responseRuleSchema = import_zod.z.object({
544
+ name: import_zod.z.string().max(import_sdk.MAX_RULE_NAME_LEN).optional().describe(`Human-readable rule name (max ${import_sdk.MAX_RULE_NAME_LEN} chars)`),
545
+ enabled: import_zod.z.boolean().optional().default(true).describe("Whether this rule is active"),
546
+ logic: import_zod.z.enum(["and", "or"]).optional().default("and").describe('How to combine conditions: "and" (all match) or "or" (any match)'),
547
+ conditions: import_zod.z.array(ruleConditionSchema).min(1).max(10).describe("Conditions to evaluate (1-10)"),
548
+ response: import_zod.z.lazy(() => mockResponseSchema).describe("Response when conditions match")
586
549
  });
587
- var responseRulesSchema = import_zod2.z.array(responseRuleSchema).max(50).describe("Ordered conditional response rules (first match wins, max 50)");
588
- var mockResponseSchema = import_zod2.z.object({
589
- status: import_zod2.z.number().int().min(import_sdk.MOCK_RESPONSE_STATUS_MIN).max(import_sdk.MOCK_RESPONSE_STATUS_MAX).describe(`HTTP status code (${import_sdk.MOCK_RESPONSE_STATUS_MIN}-${import_sdk.MOCK_RESPONSE_STATUS_MAX})`),
590
- body: import_zod2.z.string().default("").describe(
550
+ var responseRulesSchema = import_zod.z.array(responseRuleSchema).max(50).describe("Ordered conditional response rules (first match wins, max 50)");
551
+ var mockResponseSchema = import_zod.z.object({
552
+ status: import_zod.z.number().int().min(import_sdk.MOCK_RESPONSE_STATUS_MIN).max(import_sdk.MOCK_RESPONSE_STATUS_MAX).describe(`HTTP status code (${import_sdk.MOCK_RESPONSE_STATUS_MIN}-${import_sdk.MOCK_RESPONSE_STATUS_MAX})`),
553
+ body: import_zod.z.string().default("").describe(
591
554
  'Response body (default: empty). {{ placeholders }} are filled in from the request: {{ json.id }} (a JSON or form body field), {{ body }}, {{ headers.user-agent }}, {{ query.page }}, {{ method }}, {{ path }}, {{ id }}, {{ uuid }}, {{ now }}; filters: | default: "x", | json, | lower, | upper, | truncate: 80'
592
555
  ),
593
- headers: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()).default({}).describe("Response headers (default: none); values may use {{ placeholders }} too"),
594
- delay: import_zod2.z.number().int().min(import_sdk.MOCK_RESPONSE_DELAY_MIN).max(import_sdk.MOCK_RESPONSE_DELAY_MAX).optional().describe(
556
+ headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).default({}).describe("Response headers (default: none); values may use {{ placeholders }} too"),
557
+ delay: import_zod.z.number().int().min(import_sdk.MOCK_RESPONSE_DELAY_MIN).max(import_sdk.MOCK_RESPONSE_DELAY_MAX).optional().describe(
595
558
  `Response delay in milliseconds (${import_sdk.MOCK_RESPONSE_DELAY_MIN}-${import_sdk.MOCK_RESPONSE_DELAY_MAX}, default: none)`
596
559
  )
597
560
  });
598
- var endpointSiteSchema = import_zod2.z.enum(["webhooks", "mailhooks", "both"]).describe(
561
+ var endpointSiteSchema = import_zod.z.enum(["webhooks", "mailhooks", "both"]).describe(
599
562
  "Which site's dashboard lists the endpoint: webhooks (webhooks.cc), mailhooks (mailhooks.cc) or both (the default when creating). Only the listing: its HTTP URL and email address work either way. Owner only to change."
600
563
  );
601
564
  var createEndpointShape = {
602
- name: import_zod2.z.string().optional().describe("Display name for the endpoint"),
603
- ephemeral: import_zod2.z.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
565
+ name: import_zod.z.string().optional().describe("Display name for the endpoint"),
566
+ ephemeral: import_zod.z.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
604
567
  expiresIn: durationOrTimestampSchema.optional().describe('Auto-expire after this duration, for example "12h"'),
605
568
  mockResponse: mockResponseSchema.optional().describe("Optional mock response to return when the endpoint receives a request"),
606
569
  responseRules: responseRulesSchema.optional().describe(
607
570
  "Conditional response rules. Each rule has conditions and a response. First matching rule wins."
608
571
  ),
609
- notificationUrl: import_zod2.z.string().url().optional().describe(
572
+ notificationUrl: import_zod.z.string().url().optional().describe(
610
573
  "URL to POST a JSON summary to after each captured request (e.g. Slack/Discord webhook)"
611
574
  ),
612
575
  site: endpointSiteSchema.optional(),
613
- signingProvider: import_zod2.z.string().optional().describe(
576
+ signingProvider: import_zod.z.string().optional().describe(
614
577
  "Signing provider for automatic signature verification (e.g. stripe, github, shopify)"
615
578
  ),
616
- signingSecret: import_zod2.z.string().optional().describe(
579
+ signingSecret: import_zod.z.string().optional().describe(
617
580
  "Signing secret (encrypted server-side, never returned). Required when signingProvider is set."
618
581
  ),
619
- signingHeader: import_zod2.z.string().optional().describe("Custom signature header name. Only used with generic-hmac provider.")
582
+ signingHeader: import_zod.z.string().optional().describe("Custom signature header name. Only used with generic-hmac provider.")
620
583
  };
621
584
  var listEndpointsShape = {
622
- team: import_zod2.z.string().min(1).optional().describe(
585
+ team: import_zod.z.string().min(1).optional().describe(
623
586
  "Team id or name (case-insensitive). Keeps endpoints shared with you from that team and endpoints you own that are shared with it."
624
587
  )
625
588
  };
626
- var getEndpointShape = { slug: import_zod2.z.string().describe("The endpoint slug") };
627
- var deleteEndpointShape = { slug: import_zod2.z.string().describe("The endpoint slug to delete") };
589
+ var getEndpointShape = { slug: import_zod.z.string().describe("The endpoint slug") };
590
+ var deleteEndpointShape = { slug: import_zod.z.string().describe("The endpoint slug to delete") };
628
591
  var listRequestsShape = {
629
- endpointSlug: import_zod2.z.string().describe("The endpoint slug"),
630
- limit: import_zod2.z.number().int().min(1).max(100).default(25).describe("Max requests to return"),
631
- since: import_zod2.z.number().optional().describe("Only return requests after this timestamp in ms"),
592
+ endpointSlug: import_zod.z.string().describe("The endpoint slug"),
593
+ limit: import_zod.z.number().int().min(1).max(100).default(25).describe("Max requests to return"),
594
+ since: import_zod.z.number().optional().describe("Only return requests after this timestamp in ms"),
632
595
  kind: kindSchema
633
596
  };
634
- var getRequestShape = { requestId: import_zod2.z.string().describe("The request ID") };
597
+ var getRequestShape = { requestId: import_zod.z.string().describe("The request ID") };
635
598
  var waitForRequestShape = {
636
- endpointSlug: import_zod2.z.string().describe("The endpoint slug to monitor"),
599
+ endpointSlug: import_zod.z.string().describe("The endpoint slug to monitor"),
637
600
  timeout: durationOrTimestampSchema.default("30s").describe('How long to wait, for example "30s"'),
638
601
  pollInterval: durationOrTimestampSchema.optional().describe('Interval between polls, for example "500ms" or "1s"')
639
602
  };
640
603
  var payloadSchemaShape = {
641
- endpointSlug: import_zod2.z.string().describe("The endpoint slug"),
642
- format: import_zod2.z.enum(import_sdk.PAYLOAD_SCHEMA_FORMATS).default("typescript").describe(
604
+ endpointSlug: import_zod.z.string().describe("The endpoint slug"),
605
+ format: import_zod.z.enum(import_sdk.PAYLOAD_SCHEMA_FORMATS).default("typescript").describe(
643
606
  'Output: "typescript" interfaces, "zod" schemas, "pydantic" models, or "json-schema"'
644
607
  ),
645
- name: import_zod2.z.string().max(100).optional().describe("Root type name (default: from the detected event, else WebhookPayload)"),
646
- event: import_zod2.z.string().optional().describe(
608
+ name: import_zod.z.string().max(100).optional().describe("Root type name (default: from the detected event, else WebhookPayload)"),
609
+ event: import_zod.z.string().optional().describe(
647
610
  'Only requests whose detected provider event matches, e.g. "invoice.paid" or "push". One event per schema gives the tightest types.'
648
611
  ),
649
- method: import_zod2.z.string().optional().describe("Only requests with this HTTP method"),
650
- path: import_zod2.z.string().optional().describe("Only requests with this path"),
651
- requestIds: import_zod2.z.array(import_zod2.z.string()).min(1).max(50).optional().describe("Use exactly these requests instead of the latest ones"),
652
- limit: import_zod2.z.number().int().min(1).max(100).default(20).describe("How many of the latest requests to read before filtering")
612
+ method: import_zod.z.string().optional().describe("Only requests with this HTTP method"),
613
+ path: import_zod.z.string().optional().describe("Only requests with this path"),
614
+ requestIds: import_zod.z.array(import_zod.z.string()).min(1).max(50).optional().describe("Use exactly these requests instead of the latest ones"),
615
+ limit: import_zod.z.number().int().min(1).max(100).default(20).describe("How many of the latest requests to read before filtering")
653
616
  };
654
617
  function eventName(event) {
655
618
  const words = event.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);
@@ -721,6 +684,14 @@ ${JSON.stringify(result, null, 2)}`;
721
684
  if (typeof result.url === "string") {
722
685
  return `Approved. The share link is ${result.url} (anyone with it can read the copy until ${String(result.expiresAt)}).`;
723
686
  }
687
+ if (typeof result.invitedEmail === "string") {
688
+ const team = typeof result.teamName === "string" ? ` to "${result.teamName}"` : "";
689
+ const warning = typeof result.warning === "string" ? ` ${result.warning} Tell them to sign up with that address and accept from their Teams page.` : " They get an email; accepting claims one of the team's seats.";
690
+ return `Approved. Invited ${result.invitedEmail}${team} (invite ${String(result.id)}).${warning}`;
691
+ }
692
+ if (typeof result.deleted === "string" && typeof result.teamId === "string") {
693
+ return `Approved. The team ${result.deleted ? `"${result.deleted}" ` : ""}was deleted; its subscription has ended.`;
694
+ }
724
695
  if (typeof result.removed === "string") {
725
696
  return `Approved. The domain ${result.removed} was removed; mail to it is refused now.`;
726
697
  }
@@ -795,6 +766,75 @@ function fitArrayField(value, key) {
795
766
  }
796
767
  return cut(low);
797
768
  }
769
+ function isoTime(ms) {
770
+ if (ms === null || !Number.isFinite(ms)) return null;
771
+ const date = new Date(ms);
772
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
773
+ }
774
+ function searchStoppedNote(searchedUntil, order, hasCursor) {
775
+ const [direction, rest] = order === "asc" ? ["forward", "newer"] : ["back", "older"];
776
+ const reached = searchedUntil ? `Searched ${direction} to ${searchedUntil}; ${rest} requests were not searched yet.` : `The search stopped before it read everything; ${rest} requests were not searched yet.`;
777
+ return hasCursor ? `${reached} Call search_requests again with cursor to continue.` : reached;
778
+ }
779
+ var FULL_REQUEST_HINT = "The full request is available from the API (GET /api/requests/{id}), the SDKs (requests.get(id)) or the CLI (whk requests get <id>).";
780
+ var SEARCH_SHORTENED_NOTE = `Some requests were shortened to fit the output (bodyTruncated with bodySize, or shortened: true for a summary); get_request with the id returns as much of one request as fits. ${FULL_REQUEST_HINT}`;
781
+ var REQUEST_SHORTENED_NOTE = `This request was cut to fit the output: its body (bodyTruncated, with bodySize), or everything but a summary (shortened: true). ${FULL_REQUEST_HINT}`;
782
+ function clip(value, max) {
783
+ return typeof value === "string" ? sliceText(value, max) : void 0;
784
+ }
785
+ function listedSize(request) {
786
+ return jsonSize([[request]]) - 8;
787
+ }
788
+ function fitSearchRequests(requests, room) {
789
+ const share = Math.floor(room / Math.max(1, requests.length));
790
+ const fitted = requests.map(
791
+ (request) => shortenRequest(request, share, listedSize)
792
+ );
793
+ if (fitted.every((request) => listedSize(request) <= share)) return fitted;
794
+ return requests.map((request) => {
795
+ const { id, receivedAt } = request;
796
+ return { id, receivedAt, shortened: true };
797
+ });
798
+ }
799
+ function shortenRequest(request, share, sizeOf) {
800
+ if (sizeOf(request) <= share) return request;
801
+ if (typeof request.body === "string") {
802
+ const body = request.body;
803
+ let keep = body.length - (sizeOf(request) - share) - 96;
804
+ while (keep > 0) {
805
+ const cut = {
806
+ ...request,
807
+ body: sliceText(body, keep),
808
+ bodyTruncated: true,
809
+ bodySize: body.length
810
+ };
811
+ if (sizeOf(cut) <= share) return cut;
812
+ keep = Math.floor(keep / 2);
813
+ }
814
+ }
815
+ const email = request.email;
816
+ const summary = {
817
+ id: request.id,
818
+ slug: clip(request.slug, 64),
819
+ method: clip(request.method, 16),
820
+ path: clip(request.path, 200),
821
+ contentType: clip(request.contentType, 100),
822
+ size: request.size,
823
+ receivedAt: request.receivedAt,
824
+ kind: request.kind,
825
+ ...email ? { subject: clip(email.subject, 200) } : {},
826
+ shortened: true,
827
+ ...typeof request.note === "string" ? { note: request.note } : {}
828
+ };
829
+ return summary;
830
+ }
831
+ function requestContent(request) {
832
+ const compact = compactRequest(request);
833
+ if (jsonSize(compact) <= MAX_BODY_SIZE) return jsonContent(compact);
834
+ return jsonContent(
835
+ shortenRequest({ ...compact, note: REQUEST_SHORTENED_NOTE }, MAX_BODY_SIZE, jsonSize)
836
+ );
837
+ }
798
838
  function jsonContent(value) {
799
839
  return textContent(serializeJson(value));
800
840
  }
@@ -839,6 +879,13 @@ function serializeError(error) {
839
879
  } else if (error instanceof import_sdk.WebhooksCCError) {
840
880
  payload.code = error.statusCode >= 500 ? "server_error" : "validation_error";
841
881
  }
882
+ if (error instanceof import_sdk.WebhooksCCError && payload.hint === null) {
883
+ if (error.code === "invalid_cursor") {
884
+ payload.hint = "The cursor is not one this search returned, or the order changed since. Pass a cursor exactly as a result gave it, or search again without one.";
885
+ } else if (error.code === "search_busy") {
886
+ payload.hint = "The account's other searches are still running. Wait retryAfter seconds, then call again with the same inputs.";
887
+ }
888
+ }
842
889
  return JSON.stringify(payload, null, 2);
843
890
  }
844
891
  function withErrorHandling(handler) {
@@ -1063,11 +1110,18 @@ function durationMs(value) {
1063
1110
  return null;
1064
1111
  }
1065
1112
  }
1066
- function toolTarget(target, remote, handles) {
1113
+ function toolTarget(target, options, handles) {
1114
+ const remote = options.remote === true;
1067
1115
  return {
1068
1116
  tool(name, description, shape, handler) {
1069
1117
  if (remote && LOCAL_ONLY_TOOLS.has(name)) return { remove() {
1070
1118
  } };
1119
+ const entry = TOOL_CATALOG[name];
1120
+ if (!entry) throw new Error(`${name} has no entry in catalog.ts`);
1121
+ if (options.groups && !options.groups.has(entry.group)) return { remove() {
1122
+ } };
1123
+ if (options.readOnly && entry.readOnly !== true) return { remove() {
1124
+ } };
1071
1125
  const wrapped = remote ? async (args2) => {
1072
1126
  const input = args2;
1073
1127
  if (TARGET_URL_TOOLS.has(name) && input.targetUrl !== void 0) {
@@ -1101,7 +1155,7 @@ function toolTarget(target, remote, handles) {
1101
1155
  }
1102
1156
  return handler(bounded);
1103
1157
  } : handler;
1104
- const handle = target.tool(name, description, shape, wrapped);
1158
+ const handle = target.tool(name, description, shape, wrapped, toolAnnotations(name));
1105
1159
  handles.push(handle);
1106
1160
  return handle;
1107
1161
  }
@@ -1177,7 +1231,7 @@ function withShareHints(list, remote = false) {
1177
1231
  function registerTools(target, client, options = {}) {
1178
1232
  const handles = [];
1179
1233
  const remote = options.remote === true;
1180
- const server = toolTarget(target, remote, handles);
1234
+ const server = toolTarget(target, options, handles);
1181
1235
  const shareWith = remote ? REMOTE_SHARE : "share_port";
1182
1236
  server.tool(
1183
1237
  "create_endpoint",
@@ -1260,7 +1314,7 @@ function registerTools(target, client, options = {}) {
1260
1314
  server.tool(
1261
1315
  "list_team_members",
1262
1316
  "List a team's members and pending invites. Any member of the team may call this.",
1263
- { teamId: import_zod2.z.string().min(1).describe("Team id (from list_teams)") },
1317
+ { teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)") },
1264
1318
  withErrorHandling(async ({ teamId }) => {
1265
1319
  const result = await client.teams.members(teamId);
1266
1320
  return jsonContent(result);
@@ -1270,8 +1324,8 @@ function registerTools(target, client, options = {}) {
1270
1324
  "share_endpoint",
1271
1325
  "Share an endpoint you own with a team so its members can inspect, stream, and edit it. The team needs an active subscription; the endpoint's requests then bill the team's pooled quota.",
1272
1326
  {
1273
- slug: import_zod2.z.string().describe("Slug of an endpoint you own"),
1274
- teamId: import_zod2.z.string().min(1).describe("Team id (from list_teams)")
1327
+ slug: import_zod.z.string().describe("Slug of an endpoint you own"),
1328
+ teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)")
1275
1329
  },
1276
1330
  withErrorHandling(async ({ slug, teamId }) => {
1277
1331
  await client.teams.share(teamId, slug);
@@ -1282,14 +1336,132 @@ function registerTools(target, client, options = {}) {
1282
1336
  "unshare_endpoint",
1283
1337
  "Stop sharing an endpoint you own with a team. Works even when the team is suspended.",
1284
1338
  {
1285
- slug: import_zod2.z.string().describe("Slug of an endpoint you own"),
1286
- teamId: import_zod2.z.string().min(1).describe("Team id (from list_teams)")
1339
+ slug: import_zod.z.string().describe("Slug of an endpoint you own"),
1340
+ teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)")
1287
1341
  },
1288
1342
  withErrorHandling(async ({ slug, teamId }) => {
1289
1343
  await client.teams.unshare(teamId, slug);
1290
1344
  return jsonContent({ shared: false, slug, teamId });
1291
1345
  })
1292
1346
  );
1347
+ server.tool(
1348
+ "create_team",
1349
+ "Create a team you own, to share endpoints with its members under a pooled quota. It does nothing until it has a subscription, which the person starts in the browser (Account > Teams); until then it is suspended.",
1350
+ { name: import_zod.z.string().trim().min(1).max(100).describe("The team's name") },
1351
+ withErrorHandling(async ({ name }) => jsonContent(await client.teams.create(name)))
1352
+ );
1353
+ server.tool(
1354
+ "rename_team",
1355
+ "Rename a team you own.",
1356
+ {
1357
+ teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)"),
1358
+ name: import_zod.z.string().trim().min(1).max(100).describe("The new name")
1359
+ },
1360
+ withErrorHandling(async ({ teamId, name }) => {
1361
+ await client.teams.rename(teamId, name);
1362
+ return jsonContent({ renamed: true, teamId, name });
1363
+ })
1364
+ );
1365
+ server.tool(
1366
+ "delete_team",
1367
+ "Delete a team you own: its subscription ends at once, every member loses the endpoints shared with it, and it cannot be undone. Only when the person asks for it. The team's owner must approve it: this returns a link and a code to show them, then call wait_for_approval.",
1368
+ { teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)") },
1369
+ withErrorHandling(async ({ teamId }) => {
1370
+ const pending = await client.teams.delete(teamId);
1371
+ if (!pending.id) return textContent(`Deleted the team ${teamId}.`);
1372
+ return textContent(approvalAskText(pending));
1373
+ })
1374
+ );
1375
+ server.tool(
1376
+ "remove_team_member",
1377
+ "Remove a member from a team you own: the endpoints they shared with the team stop being shared, and their seat is free for someone else. Only when the person asks for it.",
1378
+ {
1379
+ teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)"),
1380
+ userId: import_zod.z.string().min(1).describe("The member's userId (from list_team_members)")
1381
+ },
1382
+ withErrorHandling(async ({ teamId, userId }) => {
1383
+ await client.teams.removeMember(teamId, userId);
1384
+ return jsonContent({ removed: true, teamId, userId });
1385
+ })
1386
+ );
1387
+ server.tool(
1388
+ "leave_team",
1389
+ "Leave a team you belong to: the endpoints you shared with it stop being shared, and you lose access to the ones shared with you through it. An owner cannot leave; they delete the team instead (delete_team). Only when the person asks for it.",
1390
+ { teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)") },
1391
+ withErrorHandling(async ({ teamId }) => {
1392
+ await client.teams.leave(teamId);
1393
+ return jsonContent({ left: true, teamId });
1394
+ })
1395
+ );
1396
+ server.tool(
1397
+ "invite_to_team",
1398
+ "Invite an email address to a team you own. It emails that address, so the team's owner must approve it: this returns a link and a code to show them, then call wait_for_approval, which returns the invite. Accepting the invite claims one of the team's paid seats. Only when the person asks for it.",
1399
+ {
1400
+ teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)"),
1401
+ email: import_zod.z.string().trim().email().describe("The address to invite")
1402
+ },
1403
+ withErrorHandling(async ({ teamId, email }) => {
1404
+ const pending = await client.teams.invite(teamId, email);
1405
+ if (!pending.id) return textContent(approvalResultText({ ...await pending.wait() }));
1406
+ return textContent(approvalAskText(pending));
1407
+ })
1408
+ );
1409
+ server.tool(
1410
+ "list_invites",
1411
+ "List the team invites waiting for you to answer: team, who invited you, when, and the invite id for answer_invite.",
1412
+ {},
1413
+ withErrorHandling(async () => jsonContent(await client.teams.invites.list()))
1414
+ );
1415
+ server.tool(
1416
+ "answer_invite",
1417
+ "Accept or decline a team invite waiting for you (from list_invites). Accepting claims one of the team's paid seats and you join the team, seeing the endpoints shared with it; it fails when the team is full or suspended. Only when the person says which.",
1418
+ {
1419
+ inviteId: import_zod.z.string().min(1).describe("The invite's id, from list_invites"),
1420
+ answer: import_zod.z.enum(["accept", "decline"]).describe("accept joins the team; decline turns it down")
1421
+ },
1422
+ withErrorHandling(async ({ inviteId, answer }) => {
1423
+ if (answer === "accept") {
1424
+ await client.teams.invites.accept(inviteId);
1425
+ return jsonContent({ accepted: true, inviteId });
1426
+ }
1427
+ await client.teams.invites.decline(inviteId);
1428
+ return jsonContent({ declined: true, inviteId });
1429
+ })
1430
+ );
1431
+ server.tool(
1432
+ "withdraw_invite",
1433
+ "Withdraw an invite to a team you own before it is answered (pending invites and their ids are in list_team_members). The invite link stops working.",
1434
+ {
1435
+ teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)"),
1436
+ inviteId: import_zod.z.string().min(1).describe("The invite's id (from list_team_members' pendingInvites)")
1437
+ },
1438
+ withErrorHandling(async ({ teamId, inviteId }) => {
1439
+ await client.teams.withdrawInvite(teamId, inviteId);
1440
+ return jsonContent({ withdrawn: true, teamId, inviteId });
1441
+ })
1442
+ );
1443
+ server.tool(
1444
+ "quote_team_change",
1445
+ "What changing a team's seat count or plan would cost, read only (owner only): what is due now, any credit, and the price from the renewal. Give seats, or plan (with interval). The change itself is made by the person in the browser.",
1446
+ {
1447
+ teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)"),
1448
+ seats: import_zod.z.number().int().min(1).optional().describe("Price this seat count"),
1449
+ plan: import_zod.z.enum(["team", "team_business"]).optional().describe("Price a move to this plan: team (Teams) or team_business (Team Business)"),
1450
+ interval: import_zod.z.enum(["month", "year"]).optional().describe("Billing interval for plan (default month; Teams is monthly only)")
1451
+ },
1452
+ withErrorHandling(async ({ teamId, seats, plan, interval }) => {
1453
+ if (seats === void 0 === (plan === void 0)) {
1454
+ throw new Error("Give seats or plan, not both.");
1455
+ }
1456
+ if (seats !== void 0) {
1457
+ if (interval !== void 0) throw new Error("interval goes with plan, not seats.");
1458
+ return jsonContent(await client.billing.quoteTeamSeats(teamId, seats));
1459
+ }
1460
+ return jsonContent(
1461
+ await client.billing.quoteTeamPlan(teamId, plan, interval)
1462
+ );
1463
+ })
1464
+ );
1293
1465
  server.tool(
1294
1466
  "get_endpoint",
1295
1467
  "Get details for a specific webhook endpoint by slug.",
@@ -1303,8 +1475,8 @@ function registerTools(target, client, options = {}) {
1303
1475
  "favorite_endpoint",
1304
1476
  "Mark an endpoint as one of your favourites, or unmark it with favorite: false. A favourite is your own choice, so endpoints shared with you through teams can be marked too; list_endpoints and get_endpoint show it as favorite. Idempotent.",
1305
1477
  {
1306
- slug: import_zod2.z.string().describe("The endpoint slug"),
1307
- favorite: import_zod2.z.boolean().optional().describe("true marks it as a favourite (the default), false unmarks it")
1478
+ slug: import_zod.z.string().describe("The endpoint slug"),
1479
+ favorite: import_zod.z.boolean().optional().describe("true marks it as a favourite (the default), false unmarks it")
1308
1480
  },
1309
1481
  withErrorHandling(async ({ slug, favorite }) => {
1310
1482
  const result = favorite === false ? await client.endpoints.unfavorite(slug) : await client.endpoints.favorite(slug);
@@ -1315,24 +1487,24 @@ function registerTools(target, client, options = {}) {
1315
1487
  "update_endpoint",
1316
1488
  "Update an endpoint name, mock response, conditional response rules, signing configuration, whether codes and links are picked out of its emails, or the named fields picked out of them.",
1317
1489
  {
1318
- slug: import_zod2.z.string().describe("The endpoint slug to update"),
1319
- name: import_zod2.z.string().optional().describe("New display name"),
1490
+ slug: import_zod.z.string().describe("The endpoint slug to update"),
1491
+ name: import_zod.z.string().optional().describe("New display name"),
1320
1492
  mockResponse: mockResponseSchema.nullable().optional().describe("Default mock response (used when no rule matches), or null to clear"),
1321
1493
  responseRules: responseRulesSchema.nullable().optional().describe("Conditional response rules (first match wins), or null to clear"),
1322
- notificationUrl: import_zod2.z.string().url().nullable().optional().describe("Notification webhook URL, or null to clear it"),
1323
- signingProvider: import_zod2.z.string().nullable().optional().describe(
1494
+ notificationUrl: import_zod.z.string().url().nullable().optional().describe("Notification webhook URL, or null to clear it"),
1495
+ signingProvider: import_zod.z.string().nullable().optional().describe(
1324
1496
  "Signing provider for automatic verification (e.g. stripe, github), or null to disable"
1325
1497
  ),
1326
- signingSecret: import_zod2.z.string().nullable().optional().describe("New signing secret (encrypted server-side). Pass null to clear."),
1327
- signingHeader: import_zod2.z.string().nullable().optional().describe(
1498
+ signingSecret: import_zod.z.string().nullable().optional().describe("New signing secret (encrypted server-side). Pass null to clear."),
1499
+ signingHeader: import_zod.z.string().nullable().optional().describe(
1328
1500
  "Custom signature header name (only for generic-hmac provider), or null to clear"
1329
1501
  ),
1330
- showEmailExtracts: import_zod2.z.boolean().optional().describe("Pick codes and links out of emails (dashboard and forwarded JSON). Owner only."),
1502
+ showEmailExtracts: import_zod.z.boolean().optional().describe("Pick codes and links out of emails (dashboard and forwarded JSON). Owner only."),
1331
1503
  site: endpointSiteSchema.optional(),
1332
- emailFields: import_zod2.z.array(emailFieldSchema).max(20).nullable().optional().describe(
1504
+ emailFields: import_zod.z.array(emailFieldSchema).max(20).nullable().optional().describe(
1333
1505
  "Named fields picked out of every email: shown with it, `fields` in its JSON, read by forwarding templates and filters (email_field). Replaces the list; null or [] removes them. Owner only."
1334
1506
  ),
1335
- layouts: import_zod2.z.array(layoutSchema).max(50).nullable().optional().describe(
1507
+ layouts: import_zod.z.array(layoutSchema).max(50).nullable().optional().describe(
1336
1508
  "How the endpoint reads each sender's emails: the first layout whose match holds applies its fields, line items and attachment parsing; the result is `parsed` in the email's JSON, and deliveries wait for it. The plan uses the first 2 (Business 50). Replaces the list; null or [] removes them. Try them with preview_layouts first. Owner only."
1337
1509
  )
1338
1510
  },
@@ -1372,9 +1544,9 @@ function registerTools(target, client, options = {}) {
1372
1544
  "preview_layouts",
1373
1545
  "Dry run of an endpoint's layouts on a captured email: which layout matches, the fields and line items it finds, and what earlier parses of its attachments held. Nothing is saved and no pages are used. Pass layouts to try them before saving them with update_endpoint.",
1374
1546
  {
1375
- slug: import_zod2.z.string().describe("The endpoint slug"),
1376
- requestId: import_zod2.z.string().optional().describe("A captured email on the endpoint (default: the newest)"),
1377
- layouts: import_zod2.z.array(layoutSchema).max(50).optional().describe("Unsaved layouts to try")
1547
+ slug: import_zod.z.string().describe("The endpoint slug"),
1548
+ requestId: import_zod.z.string().optional().describe("A captured email on the endpoint (default: the newest)"),
1549
+ layouts: import_zod.z.array(layoutSchema).max(50).optional().describe("Unsaved layouts to try")
1378
1550
  },
1379
1551
  withErrorHandling(async ({ slug, requestId, layouts }) => {
1380
1552
  const preview = await client.endpoints.previewLayouts(slug, { requestId, layouts });
@@ -1383,12 +1555,41 @@ function registerTools(target, client, options = {}) {
1383
1555
  return jsonContent(holder);
1384
1556
  })
1385
1557
  );
1558
+ server.tool(
1559
+ "list_layout_templates",
1560
+ "List the ready-made email layouts (order emails from Shopify, WooCommerce, Etsy and more, payments, bookings, attachments): each with its id, sender, kind, description, the fields it reads and whether it reads line items. Add one with add_layout_template.",
1561
+ { slug: import_zod.z.string().describe("The endpoint slug") },
1562
+ withErrorHandling(async ({ slug }) => jsonContent(await client.endpoints.layoutTemplates(slug)))
1563
+ );
1564
+ server.tool(
1565
+ "add_layout_template",
1566
+ "Add a ready-made layout (an id from list_layout_templates) to the end of an endpoint's layouts. Emails it matches are parsed from the next one on; check it on a captured email with preview_layouts. Owner only.",
1567
+ {
1568
+ slug: import_zod.z.string().describe("The endpoint slug"),
1569
+ template: import_zod.z.string().min(1).describe("The template's id, from list_layout_templates")
1570
+ },
1571
+ withErrorHandling(
1572
+ async ({ slug, template }) => jsonContent(await client.endpoints.addLayoutTemplate(slug, template))
1573
+ )
1574
+ );
1575
+ server.tool(
1576
+ "preview_email_fields",
1577
+ "Dry run of an endpoint's named email fields on a captured email: each field's value (null where it finds nothing), and problems with fields you pass. Nothing is saved. Pass emailFields to try fields before saving them with update_endpoint. Owner only.",
1578
+ {
1579
+ slug: import_zod.z.string().describe("The endpoint slug"),
1580
+ requestId: import_zod.z.string().optional().describe("A captured email on the endpoint (default: the newest)"),
1581
+ emailFields: import_zod.z.array(emailFieldSchema).max(20).optional().describe("Unsaved fields to try instead of the saved ones")
1582
+ },
1583
+ withErrorHandling(
1584
+ async ({ slug, requestId, emailFields }) => jsonContent(await client.endpoints.previewEmailFields(slug, { requestId, emailFields }))
1585
+ )
1586
+ );
1386
1587
  server.tool(
1387
1588
  "preview_response",
1388
1589
  "Dry run of an endpoint's responses: which response rule answers a captured request, and the response with its {{ placeholders }} filled in, exactly as the receiver would send it. Nothing is sent. Pass mockResponse or responseRules to try settings before saving them with update_endpoint.",
1389
1590
  {
1390
- slug: import_zod2.z.string().describe("The endpoint slug"),
1391
- requestId: import_zod2.z.string().optional().describe(
1591
+ slug: import_zod.z.string().describe("The endpoint slug"),
1592
+ requestId: import_zod.z.string().optional().describe(
1392
1593
  "A captured request on the endpoint (default: the newest HTTP request, or a sample)"
1393
1594
  ),
1394
1595
  mockResponse: mockResponseSchema.nullable().optional().describe("Try this default response instead of the saved one (null: none)"),
@@ -1416,9 +1617,9 @@ function registerTools(target, client, options = {}) {
1416
1617
  "create_endpoints",
1417
1618
  "Create multiple webhook endpoints in one call.",
1418
1619
  {
1419
- count: import_zod2.z.number().int().min(1).max(20).describe("Number of endpoints to create"),
1420
- namePrefix: import_zod2.z.string().optional().describe("Optional prefix for endpoint names"),
1421
- ephemeral: import_zod2.z.boolean().optional().describe("Create temporary endpoints that auto-expire"),
1620
+ count: import_zod.z.number().int().min(1).max(20).describe("Number of endpoints to create"),
1621
+ namePrefix: import_zod.z.string().optional().describe("Optional prefix for endpoint names"),
1622
+ ephemeral: import_zod.z.boolean().optional().describe("Create temporary endpoints that auto-expire"),
1422
1623
  expiresIn: durationOrTimestampSchema.optional().describe('Auto-expire after this duration, for example "12h"')
1423
1624
  },
1424
1625
  withErrorHandling(async ({ count, namePrefix, ephemeral, expiresIn }) => {
@@ -1439,7 +1640,7 @@ function registerTools(target, client, options = {}) {
1439
1640
  "delete_endpoints",
1440
1641
  "Delete multiple webhook endpoints in one call.",
1441
1642
  {
1442
- slugs: import_zod2.z.array(import_zod2.z.string()).min(1).max(100).describe("Endpoint slugs to delete")
1643
+ slugs: import_zod.z.array(import_zod.z.string()).min(1).max(100).describe("Endpoint slugs to delete")
1443
1644
  },
1444
1645
  withErrorHandling(async ({ slugs }) => {
1445
1646
  const settled = await Promise.allSettled(
@@ -1467,14 +1668,14 @@ function registerTools(target, client, options = {}) {
1467
1668
  "send_webhook",
1468
1669
  "Send a test webhook to a hosted endpoint. Supports provider templates and signing.",
1469
1670
  {
1470
- slug: import_zod2.z.string().describe("The endpoint slug to send to"),
1671
+ slug: import_zod.z.string().describe("The endpoint slug to send to"),
1471
1672
  method: methodSchema,
1472
- headers: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()).optional().describe("HTTP headers to include"),
1473
- body: import_zod2.z.unknown().optional().describe("Request body"),
1474
- provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to send with signed headers"),
1475
- template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
1476
- event: import_zod2.z.string().optional().describe("Provider event or topic name"),
1477
- secret: import_zod2.z.string().optional().describe(
1673
+ headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional().describe("HTTP headers to include"),
1674
+ body: import_zod.z.unknown().optional().describe("Request body"),
1675
+ provider: import_zod.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to send with signed headers"),
1676
+ template: import_zod.z.string().optional().describe("Provider-specific template preset"),
1677
+ event: import_zod.z.string().optional().describe("Provider event or topic name"),
1678
+ secret: import_zod.z.string().optional().describe(
1478
1679
  "Signing secret. Required for signed provider templates; omit for secretless providers (sendgrid, discord, plaid)."
1479
1680
  )
1480
1681
  },
@@ -1518,56 +1719,95 @@ function registerTools(target, client, options = {}) {
1518
1719
  );
1519
1720
  server.tool(
1520
1721
  "search_requests",
1521
- "Search captured webhook requests across endpoints using retained full-text search.",
1722
+ "Search captured requests and emails across endpoints: a substring match on path, headers, body and decoded email fields. One call reads back through history for about 2 seconds and returns { requests, complete, searchedUntil, cursor }. When complete is false, older requests were not searched yet: call again with the cursor and the same filters to continue.",
1522
1723
  {
1523
- slug: import_zod2.z.string().optional().describe("Filter to a specific endpoint slug"),
1524
- method: import_zod2.z.string().optional().describe("Filter by HTTP method"),
1724
+ slug: import_zod.z.string().optional().describe("Filter to a specific endpoint slug"),
1725
+ method: import_zod.z.string().optional().describe("Filter by HTTP method"),
1525
1726
  kind: kindSchema,
1526
- q: import_zod2.z.string().optional().describe("Free-text search across path, body, and headers"),
1527
- from: durationOrTimestampSchema.optional().describe('Start time as a timestamp or duration like "1h" or "7d"'),
1528
- to: durationOrTimestampSchema.optional().describe('End time as a timestamp or duration like "1h" or "7d"'),
1529
- limit: import_zod2.z.number().int().min(1).max(200).default(50).describe("Max results to return"),
1530
- offset: import_zod2.z.number().int().min(0).max(1e4).default(0).describe("Result offset"),
1531
- order: import_zod2.z.enum(["asc", "desc"]).default("desc").describe("Sort order by received time")
1532
- },
1533
- withErrorHandling(async ({ slug, method, kind, q, from, to, limit, offset, order }) => {
1534
- const results = await client.requests.search({
1535
- slug,
1536
- method,
1537
- kind,
1538
- q,
1539
- from,
1540
- to,
1541
- limit,
1542
- offset,
1543
- order
1544
- });
1545
- return jsonContent(results.map(compactRequest));
1727
+ q: import_zod.z.string().optional().describe(
1728
+ "Substring to find in the path, headers, body and decoded email fields (up to 1,000 characters)"
1729
+ ),
1730
+ from: durationOrTimestampSchema.optional().describe(
1731
+ 'Start time as a timestamp (Unix ms) or a duration back from now like "1h" or "7d". A duration is counted from each call, so when you continue with a cursor pass a timestamp to keep the same range.'
1732
+ ),
1733
+ to: durationOrTimestampSchema.optional().describe(
1734
+ 'End time as a timestamp (Unix ms) or a duration back from now like "1h" or "7d". A duration is counted from each call, so when you continue with a cursor pass a timestamp to keep the same range.'
1735
+ ),
1736
+ limit: import_zod.z.number().int().min(1).max(200).default(50).describe("Max results to return"),
1737
+ offset: import_zod.z.number().int().min(0).max(1e4).default(0).describe("Matches to skip (first call only; a cursor carries what is left of it)"),
1738
+ order: import_zod.z.enum(["asc", "desc"]).default("desc").describe("Sort order by received time"),
1739
+ cursor: import_zod.z.string().optional().describe(
1740
+ "The cursor from a previous search_requests result, to continue that search. Keep the other inputs the same."
1741
+ )
1742
+ },
1743
+ withErrorHandling(async ({ slug, method, kind, q, from, to, limit, offset, order, cursor }) => {
1744
+ const page = await client.requests.searchPage(
1745
+ { slug, method, kind, q, from, to, limit, offset, order },
1746
+ { cursor }
1747
+ );
1748
+ const searchedUntil = isoTime(page.searchedUntil);
1749
+ const envelope = (requests2, shortened) => {
1750
+ const notes = [
1751
+ ...page.complete ? [] : [searchStoppedNote(searchedUntil, order, Boolean(page.cursor))],
1752
+ ...shortened ? [SEARCH_SHORTENED_NOTE] : []
1753
+ ];
1754
+ return {
1755
+ requests: requests2,
1756
+ complete: page.complete,
1757
+ searchedUntil,
1758
+ cursor: page.cursor,
1759
+ ...notes.length > 0 ? { note: notes.join(" ") } : {}
1760
+ };
1761
+ };
1762
+ const requests = page.requests.map(compactRequest);
1763
+ if (jsonSize(envelope(requests, false)) <= MAX_BODY_SIZE) {
1764
+ return jsonContent(envelope(requests, false));
1765
+ }
1766
+ const room = MAX_BODY_SIZE - jsonSize(envelope([], true)) - 8;
1767
+ return jsonContent(envelope(fitSearchRequests(requests, room), true));
1546
1768
  })
1547
1769
  );
1548
1770
  server.tool(
1549
1771
  "count_requests",
1550
- "Count captured webhook requests that match the given filters.",
1772
+ "Count captured requests and emails that match the given filters (q is a substring match on path, headers, body and decoded email fields). Returns { count, exact, searchedUntil, cursor }. With q or method a call counts back through history for about 2 seconds: when exact is false, count covers history back to searchedUntil only, and calling again with the cursor counts the next stretch (add the counts up).",
1551
1773
  {
1552
- slug: import_zod2.z.string().optional().describe("Filter to a specific endpoint slug"),
1553
- method: import_zod2.z.string().optional().describe("Filter by HTTP method"),
1774
+ slug: import_zod.z.string().optional().describe("Filter to a specific endpoint slug"),
1775
+ method: import_zod.z.string().optional().describe("Filter by HTTP method"),
1554
1776
  kind: kindSchema,
1555
- q: import_zod2.z.string().optional().describe("Free-text search across path, body, and headers"),
1556
- from: durationOrTimestampSchema.optional().describe('Start time as a timestamp or duration like "1h" or "7d"'),
1557
- to: durationOrTimestampSchema.optional().describe('End time as a timestamp or duration like "1h" or "7d"')
1777
+ q: import_zod.z.string().optional().describe(
1778
+ "Substring to find in the path, headers, body and decoded email fields (up to 1,000 characters)"
1779
+ ),
1780
+ from: durationOrTimestampSchema.optional().describe(
1781
+ 'Start time as a timestamp (Unix ms) or a duration back from now like "1h" or "7d". A duration is counted from each call, so when you continue with a cursor pass a timestamp to keep the same range.'
1782
+ ),
1783
+ to: durationOrTimestampSchema.optional().describe(
1784
+ 'End time as a timestamp (Unix ms) or a duration back from now like "1h" or "7d". A duration is counted from each call, so when you continue with a cursor pass a timestamp to keep the same range.'
1785
+ ),
1786
+ cursor: import_zod.z.string().optional().describe(
1787
+ "The cursor from a previous count_requests result, to count the next stretch. Keep the other inputs the same."
1788
+ )
1558
1789
  },
1559
- withErrorHandling(async ({ slug, method, kind, q, from, to }) => {
1560
- const count = await client.requests.count({ slug, method, kind, q, from, to });
1561
- return jsonContent({ count });
1790
+ withErrorHandling(async ({ slug, method, kind, q, from, to, cursor }) => {
1791
+ const page = await client.requests.countPage({ slug, method, kind, q, from, to }, { cursor });
1792
+ const searchedUntil = isoTime(page.searchedUntil);
1793
+ return jsonContent({
1794
+ count: page.count,
1795
+ exact: page.exact,
1796
+ searchedUntil,
1797
+ cursor: page.cursor,
1798
+ ...page.exact ? {} : {
1799
+ note: `At least ${page.count}, counted ${searchedUntil ? `back to ${searchedUntil}` : "over part of the history"}; ${page.cursor ? "call count_requests again with cursor to count further (the counts add up)." : "there is no cursor to count further with."}`
1800
+ }
1801
+ });
1562
1802
  })
1563
1803
  );
1564
1804
  server.tool(
1565
1805
  "get_request",
1566
- "Get full details for a specific captured request by ID.",
1806
+ "Get the details of a captured request by ID: method, path, headers and body. A request larger than the tool output has its body cut (bodyTruncated, bodySize) and a note saying where the whole request is available.",
1567
1807
  getRequestShape,
1568
1808
  withErrorHandling(async ({ requestId }) => {
1569
1809
  const request = await client.requests.get(requestId);
1570
- return jsonContent(compactRequest(request));
1810
+ return requestContent(request);
1571
1811
  })
1572
1812
  );
1573
1813
  server.tool(
@@ -1576,18 +1816,18 @@ function registerTools(target, client, options = {}) {
1576
1816
  waitForRequestShape,
1577
1817
  withErrorHandling(async ({ endpointSlug, timeout, pollInterval }) => {
1578
1818
  const request = await client.requests.waitFor(endpointSlug, { timeout, pollInterval });
1579
- return jsonContent(compactRequest(request));
1819
+ return requestContent(request);
1580
1820
  })
1581
1821
  );
1582
1822
  server.tool(
1583
1823
  "wait_for_requests",
1584
1824
  "Wait for multiple requests to arrive at an endpoint.",
1585
1825
  {
1586
- endpointSlug: import_zod2.z.string().describe("The endpoint slug to monitor"),
1587
- count: import_zod2.z.number().int().min(1).max(20).describe("Number of requests to collect"),
1826
+ endpointSlug: import_zod.z.string().describe("The endpoint slug to monitor"),
1827
+ count: import_zod.z.number().int().min(1).max(20).describe("Number of requests to collect"),
1588
1828
  timeout: durationOrTimestampSchema.default("30s").describe('How long to wait, for example "30s"'),
1589
1829
  pollInterval: durationOrTimestampSchema.optional().describe('Interval between polls, for example "500ms" or "1s"'),
1590
- method: import_zod2.z.string().optional().describe("Only collect requests with this HTTP method")
1830
+ method: import_zod.z.string().optional().describe("Only collect requests with this HTTP method")
1591
1831
  },
1592
1832
  withErrorHandling(async ({ endpointSlug, count, timeout, pollInterval, method }) => {
1593
1833
  const result = await waitForMultipleRequests(client, endpointSlug, {
@@ -1605,7 +1845,7 @@ function registerTools(target, client, options = {}) {
1605
1845
  "replay_request",
1606
1846
  "Replay a previously captured request to a target URL.",
1607
1847
  {
1608
- requestId: import_zod2.z.string().describe("The captured request ID"),
1848
+ requestId: import_zod.z.string().describe("The captured request ID"),
1609
1849
  targetUrl: httpUrlSchema.describe("The URL to replay the request to")
1610
1850
  },
1611
1851
  withErrorHandling(async ({ requestId, targetUrl }) => {
@@ -1622,9 +1862,9 @@ function registerTools(target, client, options = {}) {
1622
1862
  "render_email",
1623
1863
  "A captured email in the shape an inbound mail provider posts it (postmark, sendgrid, mailgun, cloudmailin), to test the inbound handler you wrote for that provider against real mail. Returns the method, headers and body; with targetUrl (your local handler), posts it there and returns the answer. A Mailgun form is signed when you pass the signing key your handler checks.",
1624
1864
  {
1625
- requestId: import_zod2.z.string().describe("The captured email's request ID"),
1626
- format: import_zod2.z.enum(["postmark", "sendgrid", "mailgun", "cloudmailin", "json"]).describe("Whose shape; json is webhooks.cc's own email.received JSON (unsigned)"),
1627
- mailgunSigningKey: import_zod2.z.string().optional().describe("For mailgun: the signing key your handler verifies with (a test key)"),
1865
+ requestId: import_zod.z.string().describe("The captured email's request ID"),
1866
+ format: import_zod.z.enum(["postmark", "sendgrid", "mailgun", "cloudmailin", "json"]).describe("Whose shape; json is webhooks.cc's own email.received JSON (unsigned)"),
1867
+ mailgunSigningKey: import_zod.z.string().optional().describe("For mailgun: the signing key your handler verifies with (a test key)"),
1628
1868
  targetUrl: httpUrlSchema.optional().describe("Post it here, e.g. http://localhost:3000/api/inbound/postmark")
1629
1869
  },
1630
1870
  withErrorHandling(async ({ requestId, format, mailgunSigningKey, targetUrl }) => {
@@ -1655,9 +1895,9 @@ function registerTools(target, client, options = {}) {
1655
1895
  "compare_requests",
1656
1896
  "Compare two captured requests and show the structured differences.",
1657
1897
  {
1658
- leftRequestId: import_zod2.z.string().describe("The first request ID"),
1659
- rightRequestId: import_zod2.z.string().describe("The second request ID"),
1660
- ignoreHeaders: import_zod2.z.array(import_zod2.z.string()).optional().describe("Headers to ignore during comparison")
1898
+ leftRequestId: import_zod.z.string().describe("The first request ID"),
1899
+ rightRequestId: import_zod.z.string().describe("The second request ID"),
1900
+ ignoreHeaders: import_zod.z.array(import_zod.z.string()).optional().describe("Headers to ignore during comparison")
1661
1901
  },
1662
1902
  withErrorHandling(async ({ leftRequestId, rightRequestId, ignoreHeaders }) => {
1663
1903
  const [leftRequest, rightRequest] = await Promise.all([
@@ -1681,8 +1921,8 @@ function registerTools(target, client, options = {}) {
1681
1921
  "extract_from_request",
1682
1922
  "Extract specific JSON fields from a captured request body.",
1683
1923
  {
1684
- requestId: import_zod2.z.string().describe("The request ID"),
1685
- jsonPaths: import_zod2.z.array(import_zod2.z.string()).min(1).max(50).describe("Dot-notation JSON paths to extract")
1924
+ requestId: import_zod.z.string().describe("The request ID"),
1925
+ jsonPaths: import_zod.z.array(import_zod.z.string()).min(1).max(50).describe("Dot-notation JSON paths to extract")
1686
1926
  },
1687
1927
  withErrorHandling(async ({ requestId, jsonPaths }) => {
1688
1928
  const request = await client.requests.get(requestId);
@@ -1696,14 +1936,14 @@ function registerTools(target, client, options = {}) {
1696
1936
  "verify_signature",
1697
1937
  "Verify the webhook signature on a captured request. When called without provider/secret, returns the server-side pre-computed result (if signing is configured on the endpoint). When called with provider+secret, performs client-side verification.",
1698
1938
  {
1699
- requestId: import_zod2.z.string().describe("The captured request ID"),
1700
- provider: import_zod2.z.enum(import_sdk.VERIFY_PROVIDERS).optional().describe("Provider whose signature scheme should be verified. Omit to use stored result."),
1701
- secret: import_zod2.z.string().optional().describe(
1939
+ requestId: import_zod.z.string().describe("The captured request ID"),
1940
+ provider: import_zod.z.enum(import_sdk.VERIFY_PROVIDERS).optional().describe("Provider whose signature scheme should be verified. Omit to use stored result."),
1941
+ secret: import_zod.z.string().optional().describe(
1702
1942
  "Provider credential for client-side verification (for example a shared signing secret, Adyen hex HMAC key, or PayPal webhook ID). Required for non-Discord providers."
1703
1943
  ),
1704
- publicKey: import_zod2.z.string().optional().describe("Discord application public key. Required for provider=discord."),
1944
+ publicKey: import_zod.z.string().optional().describe("Discord application public key. Required for provider=discord."),
1705
1945
  url: httpUrlSchema.optional().describe("Original signed URL. Required for Twilio, Square, and HubSpot verification."),
1706
- method: import_zod2.z.string().optional().describe(
1946
+ method: import_zod.z.string().optional().describe(
1707
1947
  "Original HTTP method used when the signature was generated. Used by HubSpot v3 verification; the SDK assumes POST when omitted."
1708
1948
  )
1709
1949
  },
@@ -1752,7 +1992,7 @@ function registerTools(target, client, options = {}) {
1752
1992
  "clear_requests",
1753
1993
  "Delete captured requests for an endpoint without deleting the endpoint itself.",
1754
1994
  {
1755
- slug: import_zod2.z.string().describe("The endpoint slug to clear"),
1995
+ slug: import_zod.z.string().describe("The endpoint slug to clear"),
1756
1996
  before: durationOrTimestampSchema.optional().describe('Only clear requests older than this timestamp or duration like "1h"')
1757
1997
  },
1758
1998
  withErrorHandling(async ({ slug, before }) => {
@@ -1766,12 +2006,12 @@ function registerTools(target, client, options = {}) {
1766
2006
  {
1767
2007
  url: httpUrlSchema.describe("Target URL"),
1768
2008
  method: methodSchema,
1769
- headers: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()).optional().describe("HTTP headers to include"),
1770
- body: import_zod2.z.unknown().optional().describe("Request body"),
1771
- provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
1772
- template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
1773
- event: import_zod2.z.string().optional().describe("Provider event or topic name"),
1774
- secret: import_zod2.z.string().optional().describe(
2009
+ headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional().describe("HTTP headers to include"),
2010
+ body: import_zod.z.unknown().optional().describe("Request body"),
2011
+ provider: import_zod.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
2012
+ template: import_zod.z.string().optional().describe("Provider-specific template preset"),
2013
+ event: import_zod.z.string().optional().describe("Provider event or topic name"),
2014
+ secret: import_zod.z.string().optional().describe(
1775
2015
  "Signing secret. Required for signed provider templates; omit for secretless providers (sendgrid, discord, plaid)."
1776
2016
  )
1777
2017
  },
@@ -1799,12 +2039,12 @@ function registerTools(target, client, options = {}) {
1799
2039
  {
1800
2040
  url: httpUrlSchema.describe("Target URL"),
1801
2041
  method: methodSchema,
1802
- headers: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()).optional().describe("HTTP headers to include"),
1803
- body: import_zod2.z.unknown().optional().describe("Request body"),
1804
- provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
1805
- template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
1806
- event: import_zod2.z.string().optional().describe("Provider event or topic name"),
1807
- secret: import_zod2.z.string().optional().describe(
2042
+ headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional().describe("HTTP headers to include"),
2043
+ body: import_zod.z.unknown().optional().describe("Request body"),
2044
+ provider: import_zod.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
2045
+ template: import_zod.z.string().optional().describe("Provider-specific template preset"),
2046
+ event: import_zod.z.string().optional().describe("Provider event or topic name"),
2047
+ secret: import_zod.z.string().optional().describe(
1808
2048
  "Signing secret. Required for signed provider templates; omit for secretless providers (sendgrid, discord, plaid)."
1809
2049
  )
1810
2050
  },
@@ -1825,7 +2065,7 @@ function registerTools(target, client, options = {}) {
1825
2065
  "list_provider_templates",
1826
2066
  "List supported webhook providers, templates, and signing metadata.",
1827
2067
  {
1828
- provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Filter to a single provider")
2068
+ provider: import_zod.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Filter to a single provider")
1829
2069
  },
1830
2070
  withErrorHandling(async ({ provider }) => {
1831
2071
  if (provider) {
@@ -1879,15 +2119,15 @@ function registerTools(target, client, options = {}) {
1879
2119
  "test_webhook_flow",
1880
2120
  "Run a full webhook test flow: create endpoint, optionally mock, send, wait, verify, replay, and clean up.",
1881
2121
  {
1882
- provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to use when sending the webhook"),
1883
- event: import_zod2.z.string().optional().describe("Optional provider event or topic name"),
1884
- secret: import_zod2.z.string().optional().describe(
2122
+ provider: import_zod.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to use when sending the webhook"),
2123
+ event: import_zod.z.string().optional().describe("Optional provider event or topic name"),
2124
+ secret: import_zod.z.string().optional().describe(
1885
2125
  "Signing secret. Required when provider is set or signature verification is enabled."
1886
2126
  ),
1887
- mockStatus: import_zod2.z.number().int().min(100).max(599).optional().describe("Optional mock response status to configure before sending"),
2127
+ mockStatus: import_zod.z.number().int().min(100).max(599).optional().describe("Optional mock response status to configure before sending"),
1888
2128
  targetUrl: httpUrlSchema.optional().describe("Optional URL to replay the captured request to after capture"),
1889
- verifySignature: import_zod2.z.boolean().default(false).describe("Verify the captured request signature after capture"),
1890
- cleanup: import_zod2.z.boolean().default(true).describe("Delete the created endpoint after the flow completes")
2129
+ verifySignature: import_zod.z.boolean().default(false).describe("Verify the captured request signature after capture"),
2130
+ cleanup: import_zod.z.boolean().default(true).describe("Delete the created endpoint after the flow completes")
1891
2131
  },
1892
2132
  withErrorHandling(
1893
2133
  async ({
@@ -1962,14 +2202,14 @@ function registerTools(target, client, options = {}) {
1962
2202
  "list_emails",
1963
2203
  "List the emails an endpoint received at its mailhooks.cc address, newest first: subject, sender, tag, and the one-time code and main link found in each.",
1964
2204
  {
1965
- endpointSlug: import_zod2.z.string().describe("The endpoint slug"),
1966
- limit: import_zod2.z.number().int().min(1).max(100).default(25).describe(
2205
+ endpointSlug: import_zod.z.string().describe("The endpoint slug"),
2206
+ limit: import_zod.z.number().int().min(1).max(100).default(25).describe(
1967
2207
  `Max emails to return. With tag, subject or from, the newest ${EMAIL_SCAN_LIMIT} emails are searched.`
1968
2208
  ),
1969
- since: import_zod2.z.number().optional().describe("Only emails received after this timestamp in ms"),
1970
- tag: import_zod2.z.string().optional().describe("Only emails sent to {slug}+{tag}@mailhooks.cc (exact, case-sensitive)"),
1971
- subject: import_zod2.z.string().optional().describe("Only emails whose subject contains this text"),
1972
- from: import_zod2.z.string().optional().describe("Only emails from this address (case-insensitive)")
2209
+ since: import_zod.z.number().optional().describe("Only emails received after this timestamp in ms"),
2210
+ tag: import_zod.z.string().optional().describe("Only emails sent to {slug}+{tag}@mailhooks.cc (exact, case-sensitive)"),
2211
+ subject: import_zod.z.string().optional().describe("Only emails whose subject contains this text"),
2212
+ from: import_zod.z.string().optional().describe("Only emails from this address (case-insensitive)")
1973
2213
  },
1974
2214
  withErrorHandling(async ({ endpointSlug, limit, since, tag, subject, from }) => {
1975
2215
  const filtered = tag !== void 0 || subject !== void 0 || from !== void 0;
@@ -1987,10 +2227,10 @@ function registerTools(target, client, options = {}) {
1987
2227
  "get_email",
1988
2228
  "Get one captured email: by requestId, or the newest at an endpoint (optionally with a tag). Returns the parsed message with its one-time code and main link.",
1989
2229
  {
1990
- requestId: import_zod2.z.string().optional().describe("The email's request ID"),
1991
- endpointSlug: import_zod2.z.string().optional().describe("Get the newest email at this endpoint instead (with requestId unset)"),
1992
- tag: import_zod2.z.string().optional().describe("With endpointSlug: only emails sent to this +tag"),
1993
- includeHtml: import_zod2.z.boolean().default(false).describe("Include the HTML part (default: false, only its size)")
2230
+ requestId: import_zod.z.string().optional().describe("The email's request ID"),
2231
+ endpointSlug: import_zod.z.string().optional().describe("Get the newest email at this endpoint instead (with requestId unset)"),
2232
+ tag: import_zod.z.string().optional().describe("With endpointSlug: only emails sent to this +tag"),
2233
+ includeHtml: import_zod.z.boolean().default(false).describe("Include the HTML part (default: false, only its size)")
1994
2234
  },
1995
2235
  withErrorHandling(async ({ requestId, endpointSlug, tag, includeHtml }) => {
1996
2236
  if (requestId === void 0 === (endpointSlug === void 0)) {
@@ -2013,15 +2253,15 @@ function registerTools(target, client, options = {}) {
2013
2253
  "wait_for_email",
2014
2254
  "Wait for an email to arrive at an endpoint (for example after triggering a signup), then return it with its one-time code and main link. Use a tag per run to find your own email.",
2015
2255
  {
2016
- endpointSlug: import_zod2.z.string().describe("The endpoint slug"),
2017
- tag: import_zod2.z.string().optional().describe("Only emails sent to {slug}+{tag}@mailhooks.cc"),
2018
- subject: import_zod2.z.string().optional().describe("Only emails whose subject contains this text"),
2019
- from: import_zod2.z.string().optional().describe("Only emails from this address (case-insensitive)"),
2256
+ endpointSlug: import_zod.z.string().describe("The endpoint slug"),
2257
+ tag: import_zod.z.string().optional().describe("Only emails sent to {slug}+{tag}@mailhooks.cc"),
2258
+ subject: import_zod.z.string().optional().describe("Only emails whose subject contains this text"),
2259
+ from: import_zod.z.string().optional().describe("Only emails from this address (case-insensitive)"),
2020
2260
  timeout: durationOrTimestampSchema.default("30s").describe(
2021
2261
  `How long to wait (default "30s"). Keep it under your MCP client's request timeout, often 60 seconds.`
2022
2262
  ),
2023
- since: import_zod2.z.number().optional().describe("Only emails received after this timestamp in ms (default: five minutes ago)"),
2024
- includeHtml: import_zod2.z.boolean().default(false).describe("Include the HTML part (default: false)")
2263
+ since: import_zod.z.number().optional().describe("Only emails received after this timestamp in ms (default: five minutes ago)"),
2264
+ includeHtml: import_zod.z.boolean().default(false).describe("Include the HTML part (default: false)")
2025
2265
  },
2026
2266
  withErrorHandling(async ({ endpointSlug, tag, subject, from, timeout, since, includeHtml }) => {
2027
2267
  const view = await emailEndpointView(client, endpointSlug);
@@ -2039,8 +2279,8 @@ function registerTools(target, client, options = {}) {
2039
2279
  "send_test_email",
2040
2280
  "Deliver a sample email (with a 6-digit code and a link) to an endpoint, optionally to a +tag address. It counts as one request and skips SMTP, so sender checks do not run on it.",
2041
2281
  {
2042
- slug: import_zod2.z.string().describe("The endpoint slug"),
2043
- tag: import_zod2.z.string().optional().describe("Deliver to {slug}+{tag}@mailhooks.cc")
2282
+ slug: import_zod.z.string().describe("The endpoint slug"),
2283
+ tag: import_zod.z.string().optional().describe("Deliver to {slug}+{tag}@mailhooks.cc")
2044
2284
  },
2045
2285
  withErrorHandling(async ({ slug, tag }) => {
2046
2286
  const result = await client.emails.sendTest(slug, { tag });
@@ -2051,8 +2291,8 @@ function registerTools(target, client, options = {}) {
2051
2291
  "list_destinations",
2052
2292
  "List where an endpoint's captures go: its destinations (up to 10) with their URL, kinds, filter, format, templates and timing, and with summary: true their last 24 hours. Owner only.",
2053
2293
  {
2054
- slug: import_zod2.z.string().describe("The endpoint slug"),
2055
- summary: import_zod2.z.boolean().default(true).describe("Add each destination's last 24 hours")
2294
+ slug: import_zod.z.string().describe("The endpoint slug"),
2295
+ summary: import_zod.z.boolean().default(true).describe("Add each destination's last 24 hours")
2056
2296
  },
2057
2297
  withErrorHandling(async ({ slug, summary }) => {
2058
2298
  return jsonContent(await client.destinations.list(slug, { summary }));
@@ -2062,7 +2302,7 @@ function registerTools(target, client, options = {}) {
2062
2302
  "add_destination",
2063
2303
  "Add a destination for an endpoint's captures: your server (as received or signed JSON), a Slack or Discord channel (chat, optionally your own messageTemplate, throttleSeconds 1 for a heads-up), or a JSON body you shape with bodyTemplate (for Zapier, n8n or your API). filter sends only matching captures; delaySeconds makes it a callback. Each destination gets its own signing secret. Check it with preview_destination before relying on it. Owner only.",
2064
2304
  {
2065
- slug: import_zod2.z.string().describe("The endpoint slug"),
2305
+ slug: import_zod.z.string().describe("The endpoint slug"),
2066
2306
  url: httpUrlSchema.describe("https URL on a public host name, or a Slack or Discord webhook"),
2067
2307
  ...destinationFields
2068
2308
  },
@@ -2074,8 +2314,8 @@ function registerTools(target, client, options = {}) {
2074
2314
  "update_destination",
2075
2315
  "Change a destination: any of its fields; only the ones you pass change. Owner only.",
2076
2316
  {
2077
- slug: import_zod2.z.string().describe("The endpoint slug"),
2078
- destinationId: import_zod2.z.string().describe("The destination's id (from list_destinations)"),
2317
+ slug: import_zod.z.string().describe("The endpoint slug"),
2318
+ destinationId: import_zod.z.string().describe("The destination's id (from list_destinations)"),
2079
2319
  url: httpUrlSchema.optional().describe("A new URL"),
2080
2320
  ...destinationFields
2081
2321
  },
@@ -2087,8 +2327,8 @@ function registerTools(target, client, options = {}) {
2087
2327
  "remove_destination",
2088
2328
  "Remove a destination. Deliveries still waiting for it are marked failed; its log stays. Owner only.",
2089
2329
  {
2090
- slug: import_zod2.z.string().describe("The endpoint slug"),
2091
- destinationId: import_zod2.z.string().describe("The destination's id")
2330
+ slug: import_zod.z.string().describe("The endpoint slug"),
2331
+ destinationId: import_zod.z.string().describe("The destination's id")
2092
2332
  },
2093
2333
  withErrorHandling(async ({ slug, destinationId }) => {
2094
2334
  await client.destinations.delete(slug, destinationId);
@@ -2099,10 +2339,10 @@ function registerTools(target, client, options = {}) {
2099
2339
  "preview_destination",
2100
2340
  "Dry run of a destination against a captured request: whether its filter lets the request through and the exact request it would send (method, URL, headers, body with placeholders filled in). Nothing is sent. Use destinationId for a saved destination, destination for unsaved fields (on top of the saved ones, or with a url on their own), or both.",
2101
2341
  {
2102
- slug: import_zod2.z.string().describe("The endpoint slug"),
2103
- destinationId: import_zod2.z.string().optional().describe("A saved destination"),
2104
- destination: import_zod2.z.object({ url: httpUrlSchema.optional(), ...destinationFields }).optional().describe("Unsaved fields to try"),
2105
- requestId: import_zod2.z.string().optional().describe("A captured request (default: the newest of the kind it takes, or a sample)")
2342
+ slug: import_zod.z.string().describe("The endpoint slug"),
2343
+ destinationId: import_zod.z.string().optional().describe("A saved destination"),
2344
+ destination: import_zod.z.object({ url: httpUrlSchema.optional(), ...destinationFields }).optional().describe("Unsaved fields to try"),
2345
+ requestId: import_zod.z.string().optional().describe("A captured request (default: the newest of the kind it takes, or a sample)")
2106
2346
  },
2107
2347
  withErrorHandling(async ({ slug, destinationId, destination, requestId }) => {
2108
2348
  if (!destinationId && !destination?.url) {
@@ -2121,26 +2361,26 @@ function registerTools(target, client, options = {}) {
2121
2361
  "configure_forwarding",
2122
2362
  "Change an endpoint's primary forwarding destination (the one the endpoint's forward* fields stand for): the URL, on or off, which kinds, the format, your own headers, and the advanced options. Only the arguments you pass change. Owner only. For several destinations, filters, templates, throttling or delays, use add_destination and update_destination.",
2123
2363
  {
2124
- slug: import_zod2.z.string().describe("The endpoint slug"),
2364
+ slug: import_zod.z.string().describe("The endpoint slug"),
2125
2365
  url: httpUrlSchema.nullable().optional().describe("https URL on a public host name, or null to remove it (forwarding must be off)"),
2126
- enabled: import_zod2.z.boolean().optional().describe("Turn forwarding on (needs a URL) or off (fails deliveries still waiting)"),
2127
- http: import_zod2.z.boolean().optional().describe("Forward HTTP requests"),
2128
- email: import_zod2.z.boolean().optional().describe("Forward emails"),
2129
- format: import_zod2.z.enum(["auto", "as_received", "json", "chat"]).optional().describe(
2366
+ enabled: import_zod.z.boolean().optional().describe("Turn forwarding on (needs a URL) or off (fails deliveries still waiting)"),
2367
+ http: import_zod.z.boolean().optional().describe("Forward HTTP requests"),
2368
+ email: import_zod.z.boolean().optional().describe("Forward emails"),
2369
+ format: import_zod.z.enum(["auto", "as_received", "json", "chat"]).optional().describe(
2130
2370
  "auto (pick from the URL), as_received (relay HTTP requests unchanged; emails as signed JSON), json (signed JSON envelope for both kinds) or chat (Slack or Discord message)"
2131
2371
  ),
2132
- headers: import_zod2.z.array(
2133
- import_zod2.z.object({
2134
- name: import_zod2.z.string().describe("Header name, e.g. Authorization"),
2135
- value: import_zod2.z.string().nullable().describe("Header value, or null to keep the value stored under this name")
2372
+ headers: import_zod.z.array(
2373
+ import_zod.z.object({
2374
+ name: import_zod.z.string().describe("Header name, e.g. Authorization"),
2375
+ value: import_zod.z.string().nullable().describe("Header value, or null to keep the value stored under this name")
2136
2376
  })
2137
2377
  ).max(10).optional().describe(
2138
2378
  "Your own headers for every delivery (not chat). Replaces the whole list; [] removes them"
2139
2379
  ),
2140
- appendPath: import_zod2.z.boolean().optional().describe("Add the path after the slug to the URL for relayed requests (default true)"),
2141
- retrySeconds: import_zod2.z.union([import_zod2.z.literal(0), import_zod2.z.literal(3600), import_zod2.z.literal(86400)]).optional().describe("Retry failed deliveries for 86400 (default), 3600 or 0 seconds"),
2142
- keepOrder: import_zod2.z.boolean().optional().describe("One delivery at a time, in capture order (default false)"),
2143
- sentField: import_zod2.z.string().max(128).nullable().optional().describe(
2380
+ appendPath: import_zod.z.boolean().optional().describe("Add the path after the slug to the URL for relayed requests (default true)"),
2381
+ retrySeconds: import_zod.z.union([import_zod.z.literal(0), import_zod.z.literal(3600), import_zod.z.literal(86400)]).optional().describe("Retry failed deliveries for 86400 (default), 3600 or 0 seconds"),
2382
+ keepOrder: import_zod.z.boolean().optional().describe("One delivery at a time, in capture order (default false)"),
2383
+ sentField: import_zod.z.string().max(128).nullable().optional().describe(
2144
2384
  "JSON body field with the sender's own timestamp, e.g. publishedAt or data.publishedAt; null clears it"
2145
2385
  )
2146
2386
  },
@@ -2166,9 +2406,9 @@ function registerTools(target, client, options = {}) {
2166
2406
  "get_forwarding_secret",
2167
2407
  "Read (or with rotate: true, replace) the whsec_ secret forwarded deliveries are signed with. Owner only. Treat the value as a credential: put it in the handler's environment, not in code.",
2168
2408
  {
2169
- slug: import_zod2.z.string().describe("The endpoint slug"),
2170
- destinationId: import_zod2.z.string().optional().describe("A destination (default: the endpoint's primary destination)"),
2171
- rotate: import_zod2.z.boolean().default(false).describe("Replace the secret; every delivery from now on uses the new one")
2409
+ slug: import_zod.z.string().describe("The endpoint slug"),
2410
+ destinationId: import_zod.z.string().optional().describe("A destination (default: the endpoint's primary destination)"),
2411
+ rotate: import_zod.z.boolean().default(false).describe("Replace the secret; every delivery from now on uses the new one")
2172
2412
  },
2173
2413
  withErrorHandling(async ({ slug, destinationId, rotate }) => {
2174
2414
  const secret = destinationId ? rotate ? await client.destinations.rotateSecret(slug, destinationId) : await client.destinations.secret(slug, destinationId) : rotate ? await client.forwarding.rotateSecret(slug) : await client.forwarding.secret(slug);
@@ -2179,10 +2419,10 @@ function registerTools(target, client, options = {}) {
2179
2419
  "test_forwarding",
2180
2420
  "Send the endpoint's newest captured request of a kind (or a sample, or requestId) to a destination once, exactly as a delivery would go out, and report the answer. Works whether it is on or off, and whatever its filter says; nothing is retried or logged.",
2181
2421
  {
2182
- slug: import_zod2.z.string().describe("The endpoint slug"),
2183
- destinationId: import_zod2.z.string().optional().describe("A destination (default: the endpoint's primary destination)"),
2184
- kind: import_zod2.z.enum(["http", "email"]).optional().describe("Which kind to send (default: http when the destination takes HTTP requests)"),
2185
- requestId: import_zod2.z.string().optional().describe("With destinationId: a captured request to send")
2422
+ slug: import_zod.z.string().describe("The endpoint slug"),
2423
+ destinationId: import_zod.z.string().optional().describe("A destination (default: the endpoint's primary destination)"),
2424
+ kind: import_zod.z.enum(["http", "email"]).optional().describe("Which kind to send (default: http when the destination takes HTTP requests)"),
2425
+ requestId: import_zod.z.string().optional().describe("With destinationId: a captured request to send")
2186
2426
  },
2187
2427
  withErrorHandling(async ({ slug, destinationId, kind, requestId }) => {
2188
2428
  if (destinationId) {
@@ -2200,14 +2440,14 @@ function registerTools(target, client, options = {}) {
2200
2440
  "list_deliveries",
2201
2441
  "List forwarding deliveries: an endpoint's delivery log with its counts (endpointSlug), or every delivery and try of one captured request or email (requestId). Rows carry senderAt when the request had the sender's own timestamp, so receivedAt - senderAt is the time to reach webhooks.cc.",
2202
2442
  {
2203
- endpointSlug: import_zod2.z.string().optional().describe("List the endpoint's delivery log"),
2204
- requestId: import_zod2.z.string().optional().describe("List every delivery and try of this request"),
2205
- limit: import_zod2.z.number().int().min(1).max(100).default(20).describe("With endpointSlug: max rows"),
2206
- status: import_zod2.z.enum(["all", "pending", "failed", "skipped"]).default("all").describe(
2443
+ endpointSlug: import_zod.z.string().optional().describe("List the endpoint's delivery log"),
2444
+ requestId: import_zod.z.string().optional().describe("List every delivery and try of this request"),
2445
+ limit: import_zod.z.number().int().min(1).max(100).default(20).describe("With endpointSlug: max rows"),
2446
+ status: import_zod.z.enum(["all", "pending", "failed", "skipped"]).default("all").describe(
2207
2447
  "With endpointSlug: all, pending (queued or retrying), failed or skipped (filtered out)"
2208
2448
  ),
2209
- destinationId: import_zod2.z.string().optional().describe("With endpointSlug: only this destination"),
2210
- before: import_zod2.z.string().optional().describe("With endpointSlug: a row's cursor, to list older rows")
2449
+ destinationId: import_zod.z.string().optional().describe("With endpointSlug: only this destination"),
2450
+ before: import_zod.z.string().optional().describe("With endpointSlug: a row's cursor, to list older rows")
2211
2451
  },
2212
2452
  withErrorHandling(async ({ endpointSlug, requestId, limit, status, destinationId, before }) => {
2213
2453
  if (requestId === void 0 === (endpointSlug === void 0)) {
@@ -2234,9 +2474,9 @@ function registerTools(target, client, options = {}) {
2234
2474
  "redeliver",
2235
2475
  "Forward again with the current settings: one captured request or email (requestId; to every destination that takes it, or with destinationId to that one whatever its filter says), or every request whose latest delivery failed (endpointSlug, optionally only destinationId, oldest first, up to 1,000 at a time per destination). Owner only.",
2236
2476
  {
2237
- requestId: import_zod2.z.string().optional().describe("The request or email to send again"),
2238
- endpointSlug: import_zod2.z.string().optional().describe("Send every failed delivery of this endpoint again"),
2239
- destinationId: import_zod2.z.string().optional().describe("Only this destination")
2477
+ requestId: import_zod.z.string().optional().describe("The request or email to send again"),
2478
+ endpointSlug: import_zod.z.string().optional().describe("Send every failed delivery of this endpoint again"),
2479
+ destinationId: import_zod.z.string().optional().describe("Only this destination")
2240
2480
  },
2241
2481
  withErrorHandling(async ({ requestId, endpointSlug, destinationId }) => {
2242
2482
  if (requestId === void 0 === (endpointSlug === void 0)) {
@@ -2253,9 +2493,9 @@ function registerTools(target, client, options = {}) {
2253
2493
  "share_port",
2254
2494
  "Share a port on this computer at a public link (https://<name>.sharehooks.cc) by running the webhooks.cc CLI (whk share), so a person can open the site you are building. Returns the link. It keeps running until stop_share, the plan's session limit, or the end of this MCP server. Asking again for the same port without a name or password returns the running link; with a name or password, the running share on that port stops and a new one starts. Free accounts get a random name ending in -free, a 2-hour limit and a notice page before the site; Pro and Business can use a reserved name and a password. Needs whk installed on this computer. For a webhook provider that must get your local app's real answer, use `whk tunnel <port> --live` instead.",
2255
2495
  {
2256
- port: import_zod2.z.number().int().min(1).max(65535).describe("The local port the site runs on"),
2257
- name: import_zod2.z.string().optional().describe("A name you reserved (Pro and Business); leave out for a random one"),
2258
- password: import_zod2.z.string().min(8).max(200).optional().describe("What visitors must type (Pro and Business), 8 to 200 characters")
2496
+ port: import_zod.z.number().int().min(1).max(65535).describe("The local port the site runs on"),
2497
+ name: import_zod.z.string().optional().describe("A name you reserved (Pro and Business); leave out for a random one"),
2498
+ password: import_zod.z.string().min(8).max(200).optional().describe("What visitors must type (Pro and Business), 8 to 200 characters")
2259
2499
  },
2260
2500
  withErrorHandling(async ({ port, name, password }) => {
2261
2501
  const running = started.get(port);
@@ -2300,7 +2540,7 @@ function registerTools(target, client, options = {}) {
2300
2540
  "list_shares",
2301
2541
  `List your public previews of local ports (whk share${remote ? "" : ", share_port"}) and live tunnels (whk tunnel --live): live ones first, then the latest that ended, with this month's shared data and your plan's sharing limits. Not the same as share_endpoint, which shares an endpoint with a team.`,
2302
2542
  {
2303
- state: import_zod2.z.enum(["live", "ended"]).optional().describe("Only the running ones, or only the ended ones")
2543
+ state: import_zod.z.enum(["live", "ended"]).optional().describe("Only the running ones, or only the ended ones")
2304
2544
  },
2305
2545
  withErrorHandling(async ({ state }) => {
2306
2546
  return jsonContent(withShareHints(await client.shares.list(state ? { state } : {}), remote));
@@ -2316,8 +2556,8 @@ function registerTools(target, client, options = {}) {
2316
2556
  "create_api_key",
2317
2557
  "Ask for a new API key for the account, for CI or another tool. The account's owner must approve it: this returns at once with a link and a code. Show the person the link and tell them to check that the page shows the same code, then call wait_for_approval with the approval id to get the key (shown once).",
2318
2558
  {
2319
- name: import_zod2.z.string().min(1).max(64).describe('A name that says where the key is used, such as "GitHub Actions"'),
2320
- expiresInDays: import_zod2.z.number().int().min(1).max(365).optional().describe("How many days the key works, 1 to 365 (default 90)")
2559
+ name: import_zod.z.string().min(1).max(64).describe('A name that says where the key is used, such as "GitHub Actions"'),
2560
+ expiresInDays: import_zod.z.number().int().min(1).max(365).optional().describe("How many days the key works, 1 to 365 (default 90)")
2321
2561
  },
2322
2562
  withErrorHandling(async ({ name, expiresInDays }) => {
2323
2563
  const pending = await client.apiKeys.create({ name, expiresInDays });
@@ -2330,9 +2570,9 @@ function registerTools(target, client, options = {}) {
2330
2570
  );
2331
2571
  server.tool(
2332
2572
  "wait_for_approval",
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.",
2573
+ "Wait for the account's owner to answer an approval (from create_api_key, create_share_link, remove_domain, rename_account, delete_team, invite_to_team, 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.",
2334
2574
  {
2335
- id: import_zod2.z.string().describe("The approval id"),
2575
+ id: import_zod.z.string().describe("The approval id"),
2336
2576
  timeout: durationOrTimestampSchema.default("45s").describe('How long to wait for the person, for example "45s"')
2337
2577
  },
2338
2578
  withErrorHandling(async ({ id, timeout }) => {
@@ -2364,7 +2604,7 @@ function registerTools(target, client, options = {}) {
2364
2604
  "revoke_api_key",
2365
2605
  "Revoke one of the account's API keys by its id (from list_api_keys). Anything using it stops working at once.",
2366
2606
  {
2367
- id: import_zod2.z.string().describe("The key's id, from list_api_keys")
2607
+ id: import_zod.z.string().describe("The key's id, from list_api_keys")
2368
2608
  },
2369
2609
  withErrorHandling(async ({ id }) => {
2370
2610
  await client.apiKeys.revoke(id);
@@ -2375,7 +2615,7 @@ function registerTools(target, client, options = {}) {
2375
2615
  "rename_account",
2376
2616
  "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
2617
  {
2378
- name: import_zod2.z.string().min(1).max(100).nullable().describe("The new display name, or null to clear it")
2618
+ name: import_zod.z.string().min(1).max(100).nullable().describe("The new display name, or null to clear it")
2379
2619
  },
2380
2620
  withErrorHandling(async ({ name }) => {
2381
2621
  const pending = await client.account.rename(name);
@@ -2393,7 +2633,7 @@ function registerTools(target, client, options = {}) {
2393
2633
  "disconnect_agent",
2394
2634
  "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
2635
  {
2396
- id: import_zod2.z.string().describe("The agent's id, from list_connected_agents")
2636
+ id: import_zod.z.string().describe("The agent's id, from list_connected_agents")
2397
2637
  },
2398
2638
  withErrorHandling(async ({ id }) => {
2399
2639
  await client.agents.disconnect(id);
@@ -2404,7 +2644,7 @@ function registerTools(target, client, options = {}) {
2404
2644
  "list_share_links",
2405
2645
  "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
2646
  {
2407
- requestId: import_zod2.z.string().optional().describe("Only the links to this request")
2647
+ requestId: import_zod.z.string().optional().describe("Only the links to this request")
2408
2648
  },
2409
2649
  withErrorHandling(async ({ requestId }) => {
2410
2650
  const links = requestId ? await client.shareLinks.forRequest(requestId) : await client.shareLinks.list();
@@ -2415,9 +2655,9 @@ function registerTools(target, client, options = {}) {
2415
2655
  "create_share_link",
2416
2656
  "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
2657
  {
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(
2658
+ requestId: import_zod.z.string().describe("The request's id"),
2659
+ 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)"),
2660
+ hideSensitive: import_zod.z.boolean().optional().describe(
2421
2661
  "Also leave out signature and token headers (default true). Credentials are always left out."
2422
2662
  )
2423
2663
  },
@@ -2431,14 +2671,14 @@ function registerTools(target, client, options = {}) {
2431
2671
  "revoke_share_link",
2432
2672
  "Revoke a share link by its id (from list_share_links): its page answers 404 from then on.",
2433
2673
  {
2434
- id: import_zod2.z.string().describe("The link's id, from list_share_links")
2674
+ id: import_zod.z.string().describe("The link's id, from list_share_links")
2435
2675
  },
2436
2676
  withErrorHandling(async ({ id }) => {
2437
2677
  await client.shareLinks.revoke(id);
2438
2678
  return textContent(`Revoked the share link ${id}.`);
2439
2679
  })
2440
2680
  );
2441
- const teamIdSchema = import_zod2.z.string().optional().describe("A Team Business team's id (its owner only); leave out for your own account");
2681
+ const teamIdSchema = import_zod.z.string().optional().describe("A Team Business team's id (its owner only); leave out for your own account");
2442
2682
  server.tool(
2443
2683
  "list_domains",
2444
2684
  "List custom inbound domains (Business): each one's status, DNS records to publish, routes and catch-all endpoint.",
@@ -2449,7 +2689,7 @@ function registerTools(target, client, options = {}) {
2449
2689
  "add_domain",
2450
2690
  "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
2691
  {
2452
- domain: import_zod2.z.string().min(1).max(253).describe("The domain, such as mail.example.com"),
2692
+ domain: import_zod.z.string().min(1).max(253).describe("The domain, such as mail.example.com"),
2453
2693
  teamId: teamIdSchema
2454
2694
  },
2455
2695
  withErrorHandling(
@@ -2460,7 +2700,7 @@ function registerTools(target, client, options = {}) {
2460
2700
  "verify_domain",
2461
2701
  "Look a custom domain's TXT and MX records up now. It receives mail once both are found.",
2462
2702
  {
2463
- id: import_zod2.z.string().describe("The domain's id, from list_domains"),
2703
+ id: import_zod.z.string().describe("The domain's id, from list_domains"),
2464
2704
  teamId: teamIdSchema
2465
2705
  },
2466
2706
  withErrorHandling(
@@ -2471,12 +2711,12 @@ function registerTools(target, client, options = {}) {
2471
2711
  "route_domain",
2472
2712
  "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
2713
  {
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")
2714
+ id: import_zod.z.string().describe("The domain's id, from list_domains"),
2715
+ catchAllEndpointId: import_zod.z.string().nullable().describe("The endpoint id for mail no route matches, or null to refuse it"),
2716
+ routes: import_zod.z.array(
2717
+ import_zod.z.object({
2718
+ localPart: import_zod.z.string().max(64).describe("The part before @"),
2719
+ endpointId: import_zod.z.string().describe("The endpoint's id")
2480
2720
  })
2481
2721
  ).max(100).default([]).describe("Routes by local part"),
2482
2722
  teamId: teamIdSchema
@@ -2489,7 +2729,7 @@ function registerTools(target, client, options = {}) {
2489
2729
  "remove_domain",
2490
2730
  "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
2731
  {
2492
- id: import_zod2.z.string().describe("The domain's id, from list_domains"),
2732
+ id: import_zod.z.string().describe("The domain's id, from list_domains"),
2493
2733
  teamId: teamIdSchema
2494
2734
  },
2495
2735
  withErrorHandling(async ({ id, teamId }) => {
@@ -2514,7 +2754,7 @@ function registerTools(target, client, options = {}) {
2514
2754
  "pick_spreadsheet",
2515
2755
  "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
2756
  {
2517
- connectionId: import_zod2.z.string().describe("The Google connection's id, from list_google_connections")
2757
+ connectionId: import_zod.z.string().describe("The Google connection's id, from list_google_connections")
2518
2758
  },
2519
2759
  withErrorHandling(
2520
2760
  async ({ connectionId }) => textContent(approvalAskText(await client.google.pick(connectionId)))
@@ -2524,10 +2764,10 @@ function registerTools(target, client, options = {}) {
2524
2764
  "create_spreadsheet",
2525
2765
  "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
2766
  {
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")
2767
+ connectionId: import_zod.z.string().describe("The Google connection's id, from list_google_connections"),
2768
+ title: import_zod.z.string().max(200).optional().describe("The spreadsheet's title"),
2769
+ sheet: import_zod.z.string().max(100).optional().describe('The tab rows go to (default "Sheet1")'),
2770
+ headers: import_zod.z.array(import_zod.z.string().max(200)).max(50).describe("The header row, one per column")
2531
2771
  },
2532
2772
  withErrorHandling(
2533
2773
  async ({ connectionId, title, sheet, headers }) => jsonContent(await client.google.createSpreadsheet({ connectionId, title, sheet, headers }))
@@ -2537,8 +2777,8 @@ function registerTools(target, client, options = {}) {
2537
2777
  "list_sheet_tabs",
2538
2778
  "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
2779
  {
2540
- connectionId: import_zod2.z.string().describe("The Google connection's id"),
2541
- spreadsheetId: import_zod2.z.string().describe("The spreadsheet's id")
2780
+ connectionId: import_zod.z.string().describe("The Google connection's id"),
2781
+ spreadsheetId: import_zod.z.string().describe("The spreadsheet's id")
2542
2782
  },
2543
2783
  withErrorHandling(
2544
2784
  async ({ connectionId, spreadsheetId }) => jsonContent({ tabs: await client.google.tabs(connectionId, spreadsheetId) })
@@ -2548,7 +2788,7 @@ function registerTools(target, client, options = {}) {
2548
2788
  "disconnect_google",
2549
2789
  "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
2790
  {
2551
- connectionId: import_zod2.z.string().describe("The Google connection's id")
2791
+ connectionId: import_zod.z.string().describe("The Google connection's id")
2552
2792
  },
2553
2793
  withErrorHandling(async ({ connectionId }) => {
2554
2794
  await client.google.disconnect(connectionId);
@@ -2559,7 +2799,7 @@ function registerTools(target, client, options = {}) {
2559
2799
  "ai_setup_status",
2560
2800
  "Whether AI setup is offered for an endpoint, and how many AI assists the plan has left in a rolling 30 days.",
2561
2801
  {
2562
- slug: import_zod2.z.string().describe("The endpoint's slug")
2802
+ slug: import_zod.z.string().describe("The endpoint's slug")
2563
2803
  },
2564
2804
  withErrorHandling(async ({ slug }) => jsonContent(await client.ai.status(slug)))
2565
2805
  );
@@ -2567,25 +2807,25 @@ function registerTools(target, client, options = {}) {
2567
2807
  "ai_setup",
2568
2808
  "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
2809
  {
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"')
2810
+ slug: import_zod.z.string().describe("The endpoint's slug"),
2811
+ 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"),
2812
+ ask: import_zod.z.union([
2813
+ import_zod.z.object({
2814
+ kind: import_zod.z.literal("field"),
2815
+ name: import_zod.z.string().describe("The field's name: letters, digits and _"),
2816
+ description: import_zod.z.string().describe('What the field is, such as "the order number"')
2577
2817
  }),
2578
- import_zod2.z.object({
2579
- kind: import_zod2.z.literal("layout"),
2580
- description: import_zod2.z.string().optional().describe("What these emails are")
2818
+ import_zod.z.object({
2819
+ kind: import_zod.z.literal("layout"),
2820
+ description: import_zod.z.string().optional().describe("What these emails are")
2581
2821
  }),
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()
2822
+ import_zod.z.object({
2823
+ kind: import_zod.z.literal("repair"),
2824
+ field: import_zod.z.object({
2825
+ name: import_zod.z.string(),
2826
+ from: import_zod.z.string(),
2827
+ header: import_zod.z.string().optional(),
2828
+ pattern: import_zod.z.string()
2589
2829
  }).describe("The field to repair, as the endpoint has it")
2590
2830
  })
2591
2831
  ]).describe("What to propose")
@@ -2601,97 +2841,519 @@ function registerTools(target, client, options = {}) {
2601
2841
  )
2602
2842
  )
2603
2843
  );
2604
- server.tool(
2605
- "stop_share",
2606
- "Stop a public preview or live tunnel by its id (from list_shares). The CLI running it is told why and exits.",
2844
+ server.tool(
2845
+ "stop_share",
2846
+ "Stop a public preview or live tunnel by its id (from list_shares). The CLI running it is told why and exits.",
2847
+ {
2848
+ id: import_zod.z.string().describe("The share's id, from list_shares")
2849
+ },
2850
+ withErrorHandling(async ({ id }) => {
2851
+ const result = await client.shares.stop(id);
2852
+ return jsonContent({
2853
+ id,
2854
+ stopped: result.stopped,
2855
+ edgeReached: result.edgeReached,
2856
+ note: result.stopped ? result.edgeReached ? "Stopped; its CLI was told and exits." : "Stopped; the edge could not be told at once, so it ends within half a minute." : "It had already ended."
2857
+ });
2858
+ })
2859
+ );
2860
+ server.tool(
2861
+ "share_requests",
2862
+ "The last requests visitors made to a running preview (from list_shares), newest first: method, path, status, time, size, country, device and a number per visitor, never an address. Use it to see whether the person opened the link and what failed for them. `available` is false when the log could not be read just now.",
2863
+ {
2864
+ id: import_zod.z.string().describe("The share's id, from list_shares")
2865
+ },
2866
+ withErrorHandling(async ({ id }) => {
2867
+ return jsonContent(await client.shares.requests(id));
2868
+ })
2869
+ );
2870
+ server.tool(
2871
+ "list_names",
2872
+ `List the reserved preview names you can share under (Pro and Business): your own and your teams', each with its link, whether it has a password, whether a preview runs under it now, and whether your plan still covers it (usable). Includes how many your plan allows and how many you hold. Share under one with ${remote ? "`whk share <port> --name <name>` on the person's computer (or the local MCP server's share_port)" : "share_port and its name"}.`,
2873
+ {},
2874
+ withErrorHandling(async () => {
2875
+ return jsonContent(await client.names.list());
2876
+ })
2877
+ );
2878
+ server.tool(
2879
+ "reserve_name",
2880
+ `Reserve a preview name, so ${shareWith} can use the link https://<name>.sharehooks.cc (Pro and Business). Lowercase letters, digits and dashes; names that look like random ones or end in -free are refused. With team, the name belongs to that team and every member may share under it.`,
2881
+ {
2882
+ name: import_zod.z.string().min(1).max(63).describe("The name: lowercase letters, digits and dashes"),
2883
+ team: import_zod.z.string().optional().describe("A team's id or name (from list_teams), to keep the name inside that team")
2884
+ },
2885
+ withErrorHandling(async ({ name, team }) => {
2886
+ return jsonContent(await client.names.reserve(name, team ? { team } : {}));
2887
+ })
2888
+ );
2889
+ server.tool(
2890
+ "release_name",
2891
+ "Release a reserved preview name you kept (or, for a team name, as the team's owner). A preview running under it stops, and the link stops working.",
2892
+ {
2893
+ name: import_zod.z.string().min(1).max(63).describe("The name, from list_names")
2894
+ },
2895
+ withErrorHandling(async ({ name }) => {
2896
+ await client.names.release(name);
2897
+ return jsonContent({ released: name });
2898
+ })
2899
+ );
2900
+ server.tool(
2901
+ "set_name_password",
2902
+ "Put a password on every preview under a reserved name (Pro and Business): visitors must type it before the page opens. The password is not shown back; give it to the person yourself.",
2903
+ {
2904
+ name: import_zod.z.string().min(1).max(63).describe("The name, from list_names"),
2905
+ password: import_zod.z.string().min(8).max(200).describe("What visitors must type, 8 to 200 characters")
2906
+ },
2907
+ withErrorHandling(async ({ name, password }) => {
2908
+ await client.names.setPassword(name, password);
2909
+ return jsonContent({ name, password: true });
2910
+ })
2911
+ );
2912
+ server.tool(
2913
+ "clear_name_password",
2914
+ "Take the password off a reserved name, so its previews open without one.",
2915
+ {
2916
+ name: import_zod.z.string().min(1).max(63).describe("The name, from list_names")
2917
+ },
2918
+ withErrorHandling(async ({ name }) => {
2919
+ await client.names.clearPassword(name);
2920
+ return jsonContent({ name, password: false });
2921
+ })
2922
+ );
2923
+ server.tool(
2924
+ "describe",
2925
+ "Describe all available SDK operations, parameters, and types.",
2926
+ {},
2927
+ withErrorHandling(async () => {
2928
+ const description = client.describe();
2929
+ return jsonContent(description);
2930
+ })
2931
+ );
2932
+ return handles;
2933
+ }
2934
+
2935
+ // src/local-tools.ts
2936
+ function asToolServer(server) {
2937
+ return {
2938
+ tool(name, description, shape, handler, annotations) {
2939
+ const registered = server.registerTool(
2940
+ name,
2941
+ {
2942
+ title: annotations?.title,
2943
+ description,
2944
+ inputSchema: import_zod2.z.object(shape),
2945
+ annotations
2946
+ },
2947
+ async (args2) => handler(args2)
2948
+ );
2949
+ return { remove: () => registered.remove() };
2950
+ }
2951
+ };
2952
+ }
2953
+ var warned = /* @__PURE__ */ new Set();
2954
+ function toolSelection(options = {}) {
2955
+ const fromOption = options.tools !== void 0;
2956
+ const parsed = parseToolGroups(fromOption ? options.tools : process.env.WHK_MCP_TOOLS);
2957
+ const setting = fromOption ? options.tools : process.env.WHK_MCP_TOOLS;
2958
+ if (parsed && parsed.unknown.length > 0 && !warned.has(setting ?? "")) {
2959
+ warned.add(setting ?? "");
2960
+ console.error(
2961
+ `webhooks-cc-mcp: unknown tool groups in ${fromOption ? "tools" : "WHK_MCP_TOOLS"}: ${parsed.unknown.join(", ")} (known: all, ${TOOL_GROUP_NAMES.join(", ")})`
2962
+ );
2963
+ }
2964
+ const readOnly = options.readOnly ?? /^(1|true|yes|on)$/i.test((process.env.WHK_MCP_READ_ONLY ?? "").trim());
2965
+ return { groups: parsed?.groups ?? null, readOnly };
2966
+ }
2967
+ var EXTRA_GROUPS = TOOL_GROUP_NAMES.filter((group2) => group2 !== "core");
2968
+ var ENABLE_TOOLS_ANNOTATIONS = {
2969
+ title: toolTitle(ENABLE_TOOLS),
2970
+ readOnlyHint: false,
2971
+ destructiveHint: false,
2972
+ idempotentHint: true,
2973
+ openWorldHint: false
2974
+ };
2975
+ var LocalToolGroups = class {
2976
+ constructor(server, selection) {
2977
+ this.server = server;
2978
+ this.selection = selection;
2979
+ this.client = null;
2980
+ this.loaded = /* @__PURE__ */ new Set();
2981
+ this.handles = [];
2982
+ this.enableTools = null;
2983
+ this.clientKnown = false;
2984
+ }
2985
+ /** The groups an account's tools start with. */
2986
+ startingGroups() {
2987
+ if (this.selection.groups) return [...this.selection.groups];
2988
+ return this.clientKnown && clientLoadsEverything(this.clientName) ? [...TOOL_GROUP_NAMES] : ["core"];
2989
+ }
2990
+ /** Registers the tools for an account; the handle removes them all again. */
2991
+ show(client) {
2992
+ this.hide();
2993
+ this.client = client;
2994
+ this.load(this.startingGroups());
2995
+ return { remove: () => this.hide() };
2996
+ }
2997
+ /** Removes every account tool, enable_tools included. */
2998
+ hide() {
2999
+ for (const handle of this.handles) handle.remove();
3000
+ this.enableTools?.remove();
3001
+ this.handles = [];
3002
+ this.enableTools = null;
3003
+ this.loaded.clear();
3004
+ this.client = null;
3005
+ }
3006
+ /** The groups loaded now. */
3007
+ loadedGroups() {
3008
+ return TOOL_GROUP_NAMES.filter((group2) => this.loaded.has(group2));
3009
+ }
3010
+ /**
3011
+ * The client named itself (initialize, or a 2026-07-28 request's _meta).
3012
+ * Without a WHK_MCP_TOOLS setting, a client that takes long tool lists
3013
+ * gets every group. Only the first name counts.
3014
+ */
3015
+ clientIdentified(name) {
3016
+ if (this.clientKnown) return;
3017
+ this.clientKnown = true;
3018
+ this.clientName = name;
3019
+ if (this.selection.groups || !clientLoadsEverything(name)) return;
3020
+ if (this.client) this.load(TOOL_GROUP_NAMES);
3021
+ }
3022
+ /** Registers the groups not loaded yet; returns the tools each one added. */
3023
+ load(groups) {
3024
+ const client = this.client;
3025
+ const fresh = groups.filter((group2) => !this.loaded.has(group2));
3026
+ if (!client || fresh.length === 0) return {};
3027
+ const added = {};
3028
+ for (const group2 of fresh) added[group2] = [];
3029
+ const server = this.server;
3030
+ const recorder = {
3031
+ tool(name, description, shape, handler, annotations) {
3032
+ added[TOOL_CATALOG[name].group]?.push(name);
3033
+ return server.tool(name, description, shape, handler, annotations);
3034
+ }
3035
+ };
3036
+ this.handles.push(
3037
+ ...registerTools(recorder, client, {
3038
+ groups: new Set(fresh),
3039
+ readOnly: this.selection.readOnly
3040
+ })
3041
+ );
3042
+ for (const group2 of fresh) this.loaded.add(group2);
3043
+ this.showEnableTools();
3044
+ return added;
3045
+ }
3046
+ /** (Re)registers enable_tools with the current groups, or drops it once all are loaded. */
3047
+ showEnableTools() {
3048
+ this.enableTools?.remove();
3049
+ this.enableTools = null;
3050
+ const left = EXTRA_GROUPS.filter((group2) => !this.loaded.has(group2));
3051
+ if (!this.client || left.length === 0) return;
3052
+ const lines = EXTRA_GROUPS.map(
3053
+ (group2) => `- ${group2}${this.loaded.has(group2) ? " (loaded)" : ""}: ${TOOL_GROUPS[group2]}`
3054
+ );
3055
+ const description = [
3056
+ "Load more webhooks.cc tools into this session. This server lists its core tools by default; the rest come in groups. If a tool you need is missing, load its group, then look at the tool list again.",
3057
+ "Groups:",
3058
+ ...lines,
3059
+ this.selection.readOnly ? "This server is read-only (WHK_MCP_READ_ONLY): a group brings only its tools that change nothing." : null
3060
+ ].filter(Boolean).join("\n");
3061
+ this.enableTools = this.server.tool(
3062
+ ENABLE_TOOLS,
3063
+ description,
3064
+ {
3065
+ groups: import_zod2.z.array(import_zod2.z.enum([...EXTRA_GROUPS, "all"])).min(1).describe('Groups to load, or ["all"]')
3066
+ },
3067
+ withErrorHandling(async ({ groups }) => {
3068
+ const wanted = groups.includes("all") ? [...TOOL_GROUP_NAMES] : groups;
3069
+ const already = wanted.filter((group2) => group2 !== "core" && this.loaded.has(group2));
3070
+ const added = this.load(wanted);
3071
+ const loadedNow = Object.keys(added).length > 0;
3072
+ return jsonContent({
3073
+ added,
3074
+ ...already.length > 0 ? { alreadyLoaded: [...new Set(already)] } : {},
3075
+ loaded: this.loadedGroups(),
3076
+ ...this.selection.readOnly ? { readOnly: true } : {},
3077
+ note: loadedNow ? "The tool list changed. If your client does not show the new tools, ask the user to set WHK_MCP_TOOLS (for example all) and restart the server." : "Nothing new to load."
3078
+ });
3079
+ }),
3080
+ ENABLE_TOOLS_ANNOTATIONS
3081
+ );
3082
+ }
3083
+ };
3084
+ function modernClientName(message) {
3085
+ if (typeof message !== "object" || message === null) return null;
3086
+ const params = message.params;
3087
+ const info = params?._meta?.[import_server.CLIENT_INFO_META_KEY];
3088
+ if (typeof info !== "object" || info === null) return null;
3089
+ const name = info.name;
3090
+ return typeof name === "string" ? name : void 0;
3091
+ }
3092
+ function watchClient(server, groups) {
3093
+ const low = server.server;
3094
+ const initialized = low.oninitialized;
3095
+ low.oninitialized = () => {
3096
+ initialized?.();
3097
+ groups.clientIdentified(low.getClientVersion()?.name);
3098
+ };
3099
+ const connect = server.connect.bind(server);
3100
+ server.connect = async (transport) => {
3101
+ const previous = transport.onmessage;
3102
+ transport.onmessage = (message, extra) => {
3103
+ const name = modernClientName(message);
3104
+ if (name !== null) groups.clientIdentified(name);
3105
+ previous?.call(transport, message, extra);
3106
+ };
3107
+ return connect(transport);
3108
+ };
3109
+ }
3110
+ function localToolGroups(server, selection) {
3111
+ const groups = new LocalToolGroups(asToolServer(server), selection);
3112
+ watchClient(server, groups);
3113
+ return groups;
3114
+ }
3115
+
3116
+ // src/prompts.ts
3117
+ var import_zod3 = require("zod");
3118
+ function registerPrompts(server) {
3119
+ server.registerPrompt(
3120
+ "debug_webhook_delivery",
3121
+ {
3122
+ title: "Debug Webhook Delivery",
3123
+ description: "Guide an agent through diagnosing why webhook delivery is failing or missing.",
3124
+ argsSchema: import_zod3.z.object({
3125
+ provider: import_zod3.z.string().optional().describe("Webhook provider, if known"),
3126
+ endpointSlug: import_zod3.z.string().optional().describe("Hosted endpoint slug, if known"),
3127
+ targetUrl: import_zod3.z.string().optional().describe("Your app's receiving URL, if known")
3128
+ })
3129
+ },
3130
+ async ({ provider, endpointSlug, targetUrl }) => {
3131
+ const scope = [
3132
+ provider ? `Provider: ${provider}.` : null,
3133
+ endpointSlug ? `Endpoint slug: ${endpointSlug}.` : null,
3134
+ targetUrl ? `Target URL: ${targetUrl}.` : null
3135
+ ].filter(Boolean).join(" ");
3136
+ return {
3137
+ description: "Diagnose a missing or broken webhook delivery.",
3138
+ messages: [
3139
+ {
3140
+ role: "user",
3141
+ content: {
3142
+ type: "text",
3143
+ text: [
3144
+ scope,
3145
+ "Diagnose webhook delivery step by step.",
3146
+ "Use list_endpoints or get_endpoint to confirm the endpoint and URL.",
3147
+ "Use list_requests, wait_for_request, or wait_for_requests to check whether anything arrived.",
3148
+ "If the provider is known, use preview_webhook or send_webhook to reproduce the webhook with realistic signing.",
3149
+ "Use verify_signature when a secret is available.",
3150
+ "Use compare_requests to compare retries or changed payloads.",
3151
+ "Conclude with the most likely cause and the next concrete fix."
3152
+ ].filter(Boolean).join(" ")
3153
+ }
3154
+ }
3155
+ ]
3156
+ };
3157
+ }
3158
+ );
3159
+ server.registerPrompt(
3160
+ "setup_provider_testing",
2607
3161
  {
2608
- id: import_zod2.z.string().describe("The share's id, from list_shares")
3162
+ title: "Setup Provider Testing",
3163
+ description: "Guide an agent through setting up webhook testing for a provider.",
3164
+ argsSchema: import_zod3.z.object({
3165
+ provider: import_zod3.z.string().describe("Webhook provider to test"),
3166
+ targetUrl: import_zod3.z.string().optional().describe("Optional local or remote target URL")
3167
+ })
2609
3168
  },
2610
- withErrorHandling(async ({ id }) => {
2611
- const result = await client.shares.stop(id);
2612
- return jsonContent({
2613
- id,
2614
- stopped: result.stopped,
2615
- edgeReached: result.edgeReached,
2616
- note: result.stopped ? result.edgeReached ? "Stopped; its CLI was told and exits." : "Stopped; the edge could not be told at once, so it ends within half a minute." : "It had already ended."
2617
- });
2618
- })
3169
+ async ({ provider, targetUrl }) => {
3170
+ return {
3171
+ description: `Set up webhook testing for ${provider}.`,
3172
+ messages: [
3173
+ {
3174
+ role: "user",
3175
+ content: {
3176
+ type: "text",
3177
+ text: [
3178
+ `Set up webhook testing for ${provider}.`,
3179
+ "Use list_provider_templates to inspect supported templates and signing details first.",
3180
+ "Create an endpoint with create_endpoint, preferably ephemeral.",
3181
+ "If a target URL is provided, use preview_webhook before send_to so the request shape is visible.",
3182
+ targetUrl ? `Target URL: ${targetUrl}.` : null,
3183
+ "Send a realistic provider webhook, wait for capture, and verify the signature if a secret is available.",
3184
+ "Once real deliveries are captured, use generate_payload_schema to write the payload types for the handler.",
3185
+ "Return the endpoint URL, the exact tools used, and the next step for the developer."
3186
+ ].filter(Boolean).join(" ")
3187
+ }
3188
+ }
3189
+ ]
3190
+ };
3191
+ }
2619
3192
  );
2620
- server.tool(
2621
- "share_requests",
2622
- "The last requests visitors made to a running preview (from list_shares), newest first: method, path, status, time, size, country, device and a number per visitor, never an address. Use it to see whether the person opened the link and what failed for them. `available` is false when the log could not be read just now.",
3193
+ server.registerPrompt(
3194
+ "compare_webhook_attempts",
2623
3195
  {
2624
- id: import_zod2.z.string().describe("The share's id, from list_shares")
3196
+ title: "Compare Webhook Attempts",
3197
+ description: "Guide an agent through comparing two webhook deliveries or retries.",
3198
+ argsSchema: import_zod3.z.object({
3199
+ endpointSlug: import_zod3.z.string().optional().describe("Endpoint slug to inspect for recent attempts"),
3200
+ leftRequestId: import_zod3.z.string().optional().describe("First request ID, if already known"),
3201
+ rightRequestId: import_zod3.z.string().optional().describe("Second request ID, if already known")
3202
+ })
2625
3203
  },
2626
- withErrorHandling(async ({ id }) => {
2627
- return jsonContent(await client.shares.requests(id));
2628
- })
2629
- );
2630
- server.tool(
2631
- "list_names",
2632
- `List the reserved preview names you can share under (Pro and Business): your own and your teams', each with its link, whether it has a password, whether a preview runs under it now, and whether your plan still covers it (usable). Includes how many your plan allows and how many you hold. Share under one with ${remote ? "`whk share <port> --name <name>` on the person's computer (or the local MCP server's share_port)" : "share_port and its name"}.`,
2633
- {},
2634
- withErrorHandling(async () => {
2635
- return jsonContent(await client.names.list());
2636
- })
3204
+ async ({ endpointSlug, leftRequestId, rightRequestId }) => {
3205
+ return {
3206
+ description: "Compare two webhook deliveries and explain the difference.",
3207
+ messages: [
3208
+ {
3209
+ role: "user",
3210
+ content: {
3211
+ type: "text",
3212
+ text: [
3213
+ endpointSlug ? `Endpoint slug: ${endpointSlug}.` : null,
3214
+ leftRequestId && rightRequestId ? `Compare request ${leftRequestId} against ${rightRequestId}.` : "Find the most relevant two webhook attempts first.",
3215
+ "Use compare_requests for the structured diff.",
3216
+ "If request IDs are not provided, use list_requests or the endpoint recent resource to find the last two attempts.",
3217
+ "Explain what changed in the body, headers, or timing, and whether the difference looks expected, retried, or broken."
3218
+ ].filter(Boolean).join(" ")
3219
+ }
3220
+ }
3221
+ ]
3222
+ };
3223
+ }
2637
3224
  );
2638
- server.tool(
2639
- "reserve_name",
2640
- `Reserve a preview name, so ${shareWith} can use the link https://<name>.sharehooks.cc (Pro and Business). Lowercase letters, digits and dashes; names that look like random ones or end in -free are refused. With team, the name belongs to that team and every member may share under it.`,
2641
- {
2642
- name: import_zod2.z.string().min(1).max(63).describe("The name: lowercase letters, digits and dashes"),
2643
- team: import_zod2.z.string().optional().describe("A team's id or name (from list_teams), to keep the name inside that team")
2644
- },
2645
- withErrorHandling(async ({ name, team }) => {
2646
- return jsonContent(await client.names.reserve(name, team ? { team } : {}));
3225
+ }
3226
+
3227
+ // src/resources.ts
3228
+ var import_server2 = require("@modelcontextprotocol/server");
3229
+ var ENDPOINTS_RESOURCE_URI = "webhooks://endpoints";
3230
+ var ENDPOINT_RECENT_TEMPLATE_URI = "webhooks://endpoint/{slug}/recent";
3231
+ var REQUEST_TEMPLATE_URI = "webhooks://request/{id}";
3232
+ var MAX_ENDPOINT_RESOURCE_ITEMS = 25;
3233
+ var RECENT_REQUEST_LIMIT = 10;
3234
+ function jsonResource(uri, value) {
3235
+ return {
3236
+ contents: [
3237
+ {
3238
+ uri,
3239
+ mimeType: "application/json",
3240
+ text: JSON.stringify(value, null, 2)
3241
+ }
3242
+ ]
3243
+ };
3244
+ }
3245
+ function summarizeRequest(request) {
3246
+ return {
3247
+ id: request.id,
3248
+ method: request.method,
3249
+ path: request.path,
3250
+ receivedAt: request.receivedAt,
3251
+ contentType: request.contentType ?? null
3252
+ };
3253
+ }
3254
+ function variableToString(value, name) {
3255
+ if (typeof value === "string" && value.length > 0) {
3256
+ return value;
3257
+ }
3258
+ throw new Error(`Missing resource variable "${name}"`);
3259
+ }
3260
+ async function listEndpointSummaries(client) {
3261
+ const endpoints = (await client.endpoints.list()).slice().sort((left, right) => right.createdAt - left.createdAt);
3262
+ const truncated = endpoints.length > MAX_ENDPOINT_RESOURCE_ITEMS;
3263
+ const visible = endpoints.slice(0, MAX_ENDPOINT_RESOURCE_ITEMS);
3264
+ const summaries = await Promise.all(
3265
+ visible.map(async (endpoint) => {
3266
+ const recent = await client.requests.list(endpoint.slug, { limit: 1 });
3267
+ return {
3268
+ ...endpoint,
3269
+ lastRequest: recent[0] ? summarizeRequest(recent[0]) : null
3270
+ };
2647
3271
  })
2648
3272
  );
2649
- server.tool(
2650
- "release_name",
2651
- "Release a reserved preview name you kept (or, for a team name, as the team's owner). A preview running under it stops, and the link stops working.",
3273
+ return {
3274
+ endpoints: summaries,
3275
+ total: endpoints.length,
3276
+ truncated
3277
+ };
3278
+ }
3279
+ function registerResources(server, client) {
3280
+ server.registerResource(
3281
+ "endpoints-overview",
3282
+ ENDPOINTS_RESOURCE_URI,
2652
3283
  {
2653
- name: import_zod2.z.string().min(1).max(63).describe("The name, from list_names")
3284
+ title: "Endpoints Overview",
3285
+ description: "All endpoints with a summary of recent activity.",
3286
+ mimeType: "application/json"
2654
3287
  },
2655
- withErrorHandling(async ({ name }) => {
2656
- await client.names.release(name);
2657
- return jsonContent({ released: name });
2658
- })
3288
+ async () => {
3289
+ return jsonResource(ENDPOINTS_RESOURCE_URI, await listEndpointSummaries(client));
3290
+ }
2659
3291
  );
2660
- server.tool(
2661
- "set_name_password",
2662
- "Put a password on every preview under a reserved name (Pro and Business): visitors must type it before the page opens. The password is not shown back; give it to the person yourself.",
3292
+ server.registerResource(
3293
+ "endpoint-recent-requests",
3294
+ new import_server2.ResourceTemplate(ENDPOINT_RECENT_TEMPLATE_URI, {
3295
+ list: async () => {
3296
+ const endpoints = await client.endpoints.list();
3297
+ return {
3298
+ resources: endpoints.map((endpoint) => ({
3299
+ uri: `webhooks://endpoint/${endpoint.slug}/recent`,
3300
+ name: `${endpoint.slug} recent requests`,
3301
+ description: `Recent requests for endpoint ${endpoint.slug}`,
3302
+ mimeType: "application/json"
3303
+ }))
3304
+ };
3305
+ },
3306
+ complete: {
3307
+ slug: async (value) => {
3308
+ const endpoints = await client.endpoints.list();
3309
+ return endpoints.map((endpoint) => endpoint.slug).filter((slug) => slug.startsWith(value)).slice(0, 20);
3310
+ }
3311
+ }
3312
+ }),
2663
3313
  {
2664
- name: import_zod2.z.string().min(1).max(63).describe("The name, from list_names"),
2665
- password: import_zod2.z.string().min(8).max(200).describe("What visitors must type, 8 to 200 characters")
3314
+ title: "Endpoint Recent Requests",
3315
+ description: "Last 10 captured requests for an endpoint.",
3316
+ mimeType: "application/json"
2666
3317
  },
2667
- withErrorHandling(async ({ name, password }) => {
2668
- await client.names.setPassword(name, password);
2669
- return jsonContent({ name, password: true });
2670
- })
3318
+ async (uri, variables) => {
3319
+ const slug = variableToString(variables.slug, "slug");
3320
+ const [endpoint, requests] = await Promise.all([
3321
+ client.endpoints.get(slug),
3322
+ client.requests.list(slug, { limit: RECENT_REQUEST_LIMIT })
3323
+ ]);
3324
+ return jsonResource(uri.toString(), {
3325
+ endpoint,
3326
+ requests: requests.map(compactRequest)
3327
+ });
3328
+ }
2671
3329
  );
2672
- server.tool(
2673
- "clear_name_password",
2674
- "Take the password off a reserved name, so its previews open without one.",
3330
+ server.registerResource(
3331
+ "request-details",
3332
+ new import_server2.ResourceTemplate(REQUEST_TEMPLATE_URI, {
3333
+ list: void 0,
3334
+ complete: {}
3335
+ }),
2675
3336
  {
2676
- name: import_zod2.z.string().min(1).max(63).describe("The name, from list_names")
3337
+ title: "Request Details",
3338
+ description: "Full details for a captured request by ID.",
3339
+ mimeType: "application/json"
2677
3340
  },
2678
- withErrorHandling(async ({ name }) => {
2679
- await client.names.clearPassword(name);
2680
- return jsonContent({ name, password: false });
2681
- })
2682
- );
2683
- server.tool(
2684
- "describe",
2685
- "Describe all available SDK operations, parameters, and types.",
2686
- {},
2687
- withErrorHandling(async () => {
2688
- const description = client.describe();
2689
- return jsonContent(description);
2690
- })
3341
+ async (uri, variables) => {
3342
+ const id = variableToString(variables.id, "id");
3343
+ const request = await client.requests.get(id);
3344
+ return jsonResource(uri.toString(), compactRequest(request));
3345
+ }
2691
3346
  );
2692
- return handles;
2693
3347
  }
2694
3348
 
3349
+ // src/sandbox-tools.ts
3350
+ var import_zod4 = require("zod");
3351
+ var import_sdk2 = require("@webhooks-cc/sdk");
3352
+
3353
+ // src/version.ts
3354
+ var VERSION = true ? "2.0.0" : "0.0.0-dev";
3355
+ var MCP_USER_AGENT = `webhooks-cc-mcp/${VERSION}`;
3356
+
2695
3357
  // src/sandbox-tools.ts
2696
3358
  var SANDBOX = {
2697
3359
  maxEndpoints: 3,
@@ -2806,6 +3468,8 @@ function registerSandboxTools(server, options = {}) {
2806
3468
  options.clientName ?? "webhooks-cc-mcp",
2807
3469
  options.credentialsFile
2808
3470
  );
3471
+ const target = asToolServer(server);
3472
+ const groups = options.toolGroups ?? localToolGroups(server, toolSelection(options));
2809
3473
  let handles = [];
2810
3474
  let shown = 0;
2811
3475
  const swap = (register) => {
@@ -2813,7 +3477,9 @@ function registerSandboxTools(server, options = {}) {
2813
3477
  for (const handle of handles) handle.remove();
2814
3478
  handles = register(id);
2815
3479
  };
2816
- const showSandbox = () => swap(() => sandboxToolSet(server, session, showAccount));
3480
+ const showSandbox = () => swap(
3481
+ () => sandboxToolSet(sandboxTarget(target, groups.selection.readOnly), session, showAccount, groups)
3482
+ );
2817
3483
  const savedAccount = (id) => options.credentialsFile === false ? null : import_sdk2.WebhooksCC.fromSavedConnection({
2818
3484
  baseUrl,
2819
3485
  webhookUrl,
@@ -2826,18 +3492,54 @@ function registerSandboxTools(server, options = {}) {
2826
3492
  })
2827
3493
  });
2828
3494
  function showAccount(client) {
2829
- swap((id) => registerTools(server, savedAccount(id) ?? client));
3495
+ swap((id) => [groups.show(savedAccount(id) ?? client)]);
2830
3496
  options.onConnected?.(client);
2831
3497
  }
2832
3498
  const saved = savedAccount(1);
2833
3499
  if (saved) {
2834
- swap(() => registerTools(server, saved));
3500
+ swap(() => [groups.show(saved)]);
2835
3501
  options.onConnected?.(saved);
2836
3502
  } else {
2837
3503
  showSandbox();
2838
3504
  }
2839
3505
  }
2840
- function sandboxToolSet(server, session, showAccount) {
3506
+ var SANDBOX_ONLY_TOOLS = {
3507
+ // A new code each time.
3508
+ connect_account: {
3509
+ readOnlyHint: false,
3510
+ destructiveHint: false,
3511
+ idempotentHint: false,
3512
+ openWorldHint: false
3513
+ },
3514
+ // Changes this session's tools once the human has connected.
3515
+ wait_for_connection: {
3516
+ readOnlyHint: false,
3517
+ destructiveHint: false,
3518
+ idempotentHint: true,
3519
+ openWorldHint: false
3520
+ },
3521
+ about_sandbox: {
3522
+ readOnlyHint: true,
3523
+ destructiveHint: false,
3524
+ idempotentHint: true,
3525
+ openWorldHint: false
3526
+ }
3527
+ };
3528
+ var CONNECT_TOOLS = /* @__PURE__ */ new Set(["connect_account", "wait_for_connection"]);
3529
+ function sandboxTarget(target, readOnly) {
3530
+ return {
3531
+ tool(name, description, shape, handler) {
3532
+ const own = SANDBOX_ONLY_TOOLS[name];
3533
+ const annotations = own ? { title: toolTitle(name), ...own } : toolAnnotations(name);
3534
+ if (readOnly && !annotations.readOnlyHint && !CONNECT_TOOLS.has(name)) {
3535
+ return { remove() {
3536
+ } };
3537
+ }
3538
+ return target.tool(name, description, shape, handler, annotations);
3539
+ }
3540
+ };
3541
+ }
3542
+ function sandboxToolSet(server, session, showAccount, groups) {
2841
3543
  const tools = [];
2842
3544
  const limits = `up to ${SANDBOX.maxEndpoints} endpoints at a time, ${SANDBOX.requestsPerEndpoint} captured requests each and ${SANDBOX.requests} in all, deleted ${SANDBOX.lifetimeHours} hours after the sandbox was created`;
2843
3545
  const file = savedTo(session.credentialsFile);
@@ -2952,7 +3654,7 @@ function sandboxToolSet(server, session, showAccount) {
2952
3654
  "connect_account",
2953
3655
  "Connect this agent to a human's webhooks.cc account, for the full tool set (mock responses, signatures, email, teams) and to keep the sandbox endpoints. Returns a link and a 6-digit code: show both to the human, who signs in or signs up with that email and enters the code. Then call wait_for_connection. Calling again gives a new code.",
2954
3656
  {
2955
- email: import_zod3.z.string().email({ message: "Invalid email address" }).describe(
3657
+ email: import_zod4.z.string().email({ message: "Invalid email address" }).describe(
2956
3658
  "The email the human signs in to webhooks.cc with (their GitHub or Google primary email if they sign in that way); only someone signed in as it can connect"
2957
3659
  )
2958
3660
  },
@@ -3005,7 +3707,7 @@ function sandboxToolSet(server, session, showAccount) {
3005
3707
  "wait_for_connection",
3006
3708
  `Wait for the human to enter the code from connect_account. On success the full webhooks.cc tools for their account replace these sandbox tools. Returns after timeoutSeconds (default ${DEFAULT_WAIT_SECONDS}) if they have not finished yet; call it again to keep waiting.`,
3007
3709
  {
3008
- timeoutSeconds: import_zod3.z.number().int().min(1).max(MAX_WAIT_SECONDS).optional().describe(`How long to wait, in seconds (default ${DEFAULT_WAIT_SECONDS})`)
3710
+ timeoutSeconds: import_zod4.z.number().int().min(1).max(MAX_WAIT_SECONDS).optional().describe(`How long to wait, in seconds (default ${DEFAULT_WAIT_SECONDS})`)
3009
3711
  },
3010
3712
  withErrorHandling(async ({ timeoutSeconds }) => {
3011
3713
  const pending = session.connect;
@@ -3048,7 +3750,7 @@ function sandboxToolSet(server, session, showAccount) {
3048
3750
  return jsonContent({
3049
3751
  connected: true,
3050
3752
  email: pending.email,
3051
- message: `Connected to the webhooks.cc account of ${pending.email}. The full tool set has replaced the sandbox tools (the tool list changed; if your client does not pick that up, ask the user to restart it).${movedEndpoints ? " The sandbox endpoints are now that account's endpoints, with what they captured." : ""} ${file ? `The connection is saved in ${file}, so later sessions, the whk CLI and the SDKs start connected; it lasts 90 days, and the human can remove it under Account, Connected agents.` : "This connection lasts while this MCP server runs; for a lasting setup, the human can create an API key under Account and set WHK_API_KEY."}`
3753
+ message: `Connected to the webhooks.cc account of ${pending.email}. The account's tools have replaced the sandbox tools (the tool list changed; if your client does not pick that up, ask the user to restart it).${groups.loadedGroups().length < TOOL_GROUP_NAMES.length ? " More tools load in groups: see enable_tools." : ""}${movedEndpoints ? " The sandbox endpoints are now that account's endpoints, with what they captured." : ""} ${file ? `The connection is saved in ${file}, so later sessions, the whk CLI and the SDKs start connected; it lasts 90 days, and the human can remove it under Account, Connected agents.` : "This connection lasts while this MCP server runs; for a lasting setup, the human can create an API key under Account and set WHK_API_KEY."}`
3052
3754
  });
3053
3755
  })
3054
3756
  )
@@ -3087,22 +3789,34 @@ function sandboxToolSet(server, session, showAccount) {
3087
3789
  }
3088
3790
 
3089
3791
  // src/index.ts
3792
+ var INSTRUCTIONS = "webhooks.cc captures webhooks and inbound email for testing. Start with create_endpoint, send to its URL (or point a provider at it), then wait_for_request and get_request to inspect what arrived. send_webhook sends a signed test webhook to an endpoint; verify_signature checks a captured request. With an account the tools come in groups: when a tool you need is missing, enable_tools loads its group (teams, domains, Google Sheets, billing and keys, and more).";
3090
3793
  function createServer(options = {}) {
3091
3794
  const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
3092
3795
  const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
3093
3796
  const baseUrl = options.baseUrl ?? process.env.WHK_BASE_URL;
3094
- const server = new import_mcp2.McpServer(
3095
- { name: "webhooks-cc", version: VERSION },
3096
- // Connecting an account swaps every tool at once: one list_changed, not dozens.
3097
- { debouncedNotificationMethods: ["notifications/tools/list_changed"] }
3797
+ const server = new import_server3.McpServer(
3798
+ { name: "webhooks-cc", title: "webhooks.cc", version: VERSION },
3799
+ {
3800
+ capabilities: { tools: { listChanged: true } },
3801
+ instructions: INSTRUCTIONS,
3802
+ // Connecting an account or loading groups changes many tools at once:
3803
+ // one list_changed, not dozens.
3804
+ debouncedNotificationMethods: ["notifications/tools/list_changed"]
3805
+ }
3098
3806
  );
3807
+ const groups = localToolGroups(server, toolSelection(options));
3099
3808
  if (!apiKey) {
3100
- registerSandboxTools(server, { baseUrl, webhookUrl, credentialsFile: options.credentialsFile });
3809
+ registerSandboxTools(server, {
3810
+ baseUrl,
3811
+ webhookUrl,
3812
+ credentialsFile: options.credentialsFile,
3813
+ toolGroups: groups
3814
+ });
3101
3815
  registerPrompts(server);
3102
3816
  return server;
3103
3817
  }
3104
3818
  const client = new import_sdk3.WebhooksCC({ apiKey, webhookUrl, baseUrl, userAgent: MCP_USER_AGENT });
3105
- registerTools(server, client);
3819
+ groups.show(client);
3106
3820
  registerPrompts(server);
3107
3821
  registerResources(server, client);
3108
3822
  return server;
@@ -3388,7 +4102,7 @@ if (command === "setup") {
3388
4102
  runSetup(args.slice(1));
3389
4103
  } else if (command === "--help" || command === "-h") {
3390
4104
  console.log(`
3391
- @webhooks-cc/mcp \u2014 MCP server for webhooks.cc
4105
+ @webhooks-cc/mcp: MCP server for webhooks.cc
3392
4106
 
3393
4107
  Usage:
3394
4108
  npx @webhooks-cc/mcp Start stdio MCP server
@@ -3403,6 +4117,11 @@ Environment:
3403
4117
  ~/.config/whk/agent.json, shared with the whk CLI and SDKs)
3404
4118
  WHK_WEBHOOK_URL Custom webhook receiver URL (optional)
3405
4119
  WHK_BASE_URL Custom API base URL (optional)
4120
+ WHK_MCP_TOOLS Tool groups to list: all, or core plus any of forwarding,
4121
+ layouts, extras, teams, account, domains, sheets, names
4122
+ (default: core, and all for Claude Code, Codex and
4123
+ ChatGPT; enable_tools loads more during a session)
4124
+ WHK_MCP_READ_ONLY 1 to list only the tools that change nothing
3406
4125
 
3407
4126
  Setup:
3408
4127
  npx @webhooks-cc/mcp setup claude-code
@@ -3422,10 +4141,8 @@ Get your API key at https://webhooks.cc/account/api-keys
3422
4141
  console.error(`Unknown command: "${command}". Run with --help for usage.`);
3423
4142
  process.exit(1);
3424
4143
  } else {
3425
- const server = createServer();
3426
- const transport = new import_stdio.StdioServerTransport();
3427
- server.connect(transport).catch((error) => {
3428
- console.error("Fatal:", error instanceof Error ? error.message : error);
3429
- process.exit(1);
4144
+ (0, import_stdio.serveStdio)(() => createServer(), {
4145
+ // Out-of-band reports only, never what was sent; stdout is the protocol.
4146
+ onerror: (error) => console.error(`webhooks-cc-mcp: ${error.message}`)
3430
4147
  });
3431
4148
  }