@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/index.js CHANGED
@@ -20,131 +20,240 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
+ ENABLE_TOOLS: () => ENABLE_TOOLS,
24
+ LocalToolGroups: () => LocalToolGroups,
23
25
  REMOTE_MAX_WAIT_MS: () => REMOTE_MAX_WAIT_MS,
26
+ TOOL_CATALOG: () => TOOL_CATALOG,
27
+ TOOL_GROUPS: () => TOOL_GROUPS,
28
+ TOOL_GROUP_NAMES: () => TOOL_GROUP_NAMES,
29
+ asToolServer: () => asToolServer,
30
+ clientLoadsEverything: () => clientLoadsEverything,
24
31
  createServer: () => createServer,
32
+ defaultToolGroups: () => defaultToolGroups,
33
+ localToolGroups: () => localToolGroups,
34
+ parseToolGroups: () => parseToolGroups,
25
35
  registerAgentRegistrationTools: () => registerAgentRegistrationTools,
26
36
  registerPrompts: () => registerPrompts,
27
37
  registerResources: () => registerResources,
28
38
  registerSandboxTools: () => registerSandboxTools,
29
- registerTools: () => registerTools
39
+ registerTools: () => registerTools,
40
+ toolAnnotations: () => toolAnnotations,
41
+ toolSelection: () => toolSelection
30
42
  });
31
43
  module.exports = __toCommonJS(src_exports);
32
- var import_mcp2 = require("@modelcontextprotocol/sdk/server/mcp.js");
44
+ var import_server3 = require("@modelcontextprotocol/server");
33
45
  var import_sdk3 = require("@webhooks-cc/sdk");
34
46
 
35
- // src/prompts.ts
36
- var import_zod = require("zod");
37
- function registerPrompts(server) {
38
- server.registerPrompt(
39
- "debug_webhook_delivery",
40
- {
41
- title: "Debug Webhook Delivery",
42
- description: "Guide an agent through diagnosing why webhook delivery is failing or missing.",
43
- argsSchema: {
44
- provider: import_zod.z.string().optional().describe("Webhook provider, if known"),
45
- endpointSlug: import_zod.z.string().optional().describe("Hosted endpoint slug, if known"),
46
- targetUrl: import_zod.z.string().optional().describe("Your app's receiving URL, if known")
47
- }
48
- },
49
- async ({ provider, endpointSlug, targetUrl }) => {
50
- const scope = [
51
- provider ? `Provider: ${provider}.` : null,
52
- endpointSlug ? `Endpoint slug: ${endpointSlug}.` : null,
53
- targetUrl ? `Target URL: ${targetUrl}.` : null
54
- ].filter(Boolean).join(" ");
55
- return {
56
- description: "Diagnose a missing or broken webhook delivery.",
57
- messages: [
58
- {
59
- role: "user",
60
- content: {
61
- type: "text",
62
- text: [
63
- scope,
64
- "Diagnose webhook delivery step by step.",
65
- "Use list_endpoints or get_endpoint to confirm the endpoint and URL.",
66
- "Use list_requests, wait_for_request, or wait_for_requests to check whether anything arrived.",
67
- "If the provider is known, use preview_webhook or send_webhook to reproduce the webhook with realistic signing.",
68
- "Use verify_signature when a secret is available.",
69
- "Use compare_requests to compare retries or changed payloads.",
70
- "Conclude with the most likely cause and the next concrete fix."
71
- ].filter(Boolean).join(" ")
72
- }
73
- }
74
- ]
75
- };
76
- }
77
- );
78
- server.registerPrompt(
79
- "setup_provider_testing",
80
- {
81
- title: "Setup Provider Testing",
82
- description: "Guide an agent through setting up webhook testing for a provider.",
83
- argsSchema: {
84
- provider: import_zod.z.string().describe("Webhook provider to test"),
85
- targetUrl: import_zod.z.string().optional().describe("Optional local or remote target URL")
86
- }
87
- },
88
- async ({ provider, targetUrl }) => {
89
- return {
90
- description: `Set up webhook testing for ${provider}.`,
91
- messages: [
92
- {
93
- role: "user",
94
- content: {
95
- type: "text",
96
- text: [
97
- `Set up webhook testing for ${provider}.`,
98
- "Use list_provider_templates to inspect supported templates and signing details first.",
99
- "Create an endpoint with create_endpoint, preferably ephemeral.",
100
- "If a target URL is provided, use preview_webhook before send_to so the request shape is visible.",
101
- targetUrl ? `Target URL: ${targetUrl}.` : null,
102
- "Send a realistic provider webhook, wait for capture, and verify the signature if a secret is available.",
103
- "Once real deliveries are captured, use generate_payload_schema to write the payload types for the handler.",
104
- "Return the endpoint URL, the exact tools used, and the next step for the developer."
105
- ].filter(Boolean).join(" ")
106
- }
107
- }
108
- ]
109
- };
110
- }
47
+ // src/local-tools.ts
48
+ var import_server = require("@modelcontextprotocol/server");
49
+ var import_zod2 = require("zod");
50
+
51
+ // src/catalog.ts
52
+ var TOOL_GROUPS = {
53
+ core: "Endpoints, captured requests and emails, sending test webhooks, signatures, forwarding destinations, deliveries, previews of your local app and their logs, usage and approvals",
54
+ forwarding: "The rest of forwarding: preview what a destination would send, the endpoint's forwarding settings and signing secret, test deliveries",
55
+ layouts: "Email layouts and named fields: try layouts and fields on a captured email, add ready-made layouts, and AI setup",
56
+ extras: "Bulk endpoint changes, favourites, request counts and payload schemas",
57
+ teams: "Teams: members, invites, sharing endpoints with a team, and pricing team changes",
58
+ account: "The account: billing, API keys, connected agents, public share links to requests, and the display name",
59
+ domains: "Custom inbound email domains: add, verify, route and remove them",
60
+ sheets: "Google Sheets destinations: Google accounts, spreadsheets and their tabs",
61
+ names: "Preview names: reserve, release and password-protect them"
62
+ };
63
+ var TOOL_GROUP_NAMES = Object.keys(TOOL_GROUPS);
64
+ var read = { readOnly: true };
65
+ var write = {};
66
+ var update = { idempotent: true };
67
+ var remove = { destructive: true, idempotent: true };
68
+ var replace = { destructive: true, idempotent: true };
69
+ var outward = { openWorld: true };
70
+ function group(name, tools) {
71
+ return Object.fromEntries(
72
+ Object.entries(tools).map(([tool, behaviour]) => [tool, { group: name, ...behaviour }])
111
73
  );
112
- server.registerPrompt(
113
- "compare_webhook_attempts",
114
- {
115
- title: "Compare Webhook Attempts",
116
- description: "Guide an agent through comparing two webhook deliveries or retries.",
117
- argsSchema: {
118
- endpointSlug: import_zod.z.string().optional().describe("Endpoint slug to inspect for recent attempts"),
119
- leftRequestId: import_zod.z.string().optional().describe("First request ID, if already known"),
120
- rightRequestId: import_zod.z.string().optional().describe("Second request ID, if already known")
121
- }
122
- },
123
- async ({ endpointSlug, leftRequestId, rightRequestId }) => {
124
- return {
125
- description: "Compare two webhook deliveries and explain the difference.",
126
- messages: [
127
- {
128
- role: "user",
129
- content: {
130
- type: "text",
131
- text: [
132
- endpointSlug ? `Endpoint slug: ${endpointSlug}.` : null,
133
- leftRequestId && rightRequestId ? `Compare request ${leftRequestId} against ${rightRequestId}.` : "Find the most relevant two webhook attempts first.",
134
- "Use compare_requests for the structured diff.",
135
- "If request IDs are not provided, use list_requests or the endpoint recent resource to find the last two attempts.",
136
- "Explain what changed in the body, headers, or timing, and whether the difference looks expected, retried, or broken."
137
- ].filter(Boolean).join(" ")
138
- }
139
- }
140
- ]
141
- };
74
+ }
75
+ var TOOL_CATALOG = {
76
+ ...group("core", {
77
+ create_endpoint: write,
78
+ list_endpoints: read,
79
+ get_endpoint: read,
80
+ // Replaces what it is given (a mock response, the signing secret).
81
+ update_endpoint: replace,
82
+ delete_endpoint: remove,
83
+ list_requests: read,
84
+ search_requests: read,
85
+ get_request: read,
86
+ wait_for_request: read,
87
+ wait_for_requests: read,
88
+ replay_request: outward,
89
+ compare_requests: read,
90
+ extract_from_request: read,
91
+ clear_requests: remove,
92
+ send_webhook: write,
93
+ send_to: outward,
94
+ test_webhook_flow: outward,
95
+ preview_webhook: read,
96
+ preview_response: read,
97
+ list_provider_templates: read,
98
+ verify_signature: read,
99
+ list_emails: read,
100
+ get_email: read,
101
+ wait_for_email: read,
102
+ send_test_email: write,
103
+ render_email: outward,
104
+ list_destinations: read,
105
+ add_destination: write,
106
+ update_destination: replace,
107
+ remove_destination: remove,
108
+ list_deliveries: read,
109
+ redeliver: outward,
110
+ share_port: outward,
111
+ list_shares: read,
112
+ share_requests: read,
113
+ stop_share: remove,
114
+ get_usage: read,
115
+ // Collecting an approved key hands it over once: not a pure read.
116
+ wait_for_approval: write,
117
+ describe: read
118
+ }),
119
+ ...group("forwarding", {
120
+ preview_destination: read,
121
+ configure_forwarding: replace,
122
+ // rotate: true replaces the secret, and receivers holding the old one fail.
123
+ get_forwarding_secret: { destructive: true },
124
+ test_forwarding: outward
125
+ }),
126
+ ...group("layouts", {
127
+ preview_layouts: read,
128
+ list_layout_templates: read,
129
+ add_layout_template: write,
130
+ preview_email_fields: read,
131
+ ai_setup_status: read,
132
+ // Sends the chosen emails to Anthropic once the owner approves.
133
+ ai_setup: outward
134
+ }),
135
+ ...group("extras", {
136
+ create_endpoints: write,
137
+ delete_endpoints: remove,
138
+ favorite_endpoint: update,
139
+ count_requests: read,
140
+ generate_payload_schema: read
141
+ }),
142
+ ...group("teams", {
143
+ list_teams: read,
144
+ list_team_members: read,
145
+ share_endpoint: update,
146
+ unshare_endpoint: remove,
147
+ create_team: write,
148
+ rename_team: update,
149
+ delete_team: remove,
150
+ remove_team_member: remove,
151
+ leave_team: remove,
152
+ invite_to_team: outward,
153
+ list_invites: read,
154
+ answer_invite: write,
155
+ withdraw_invite: remove,
156
+ quote_team_change: read
157
+ }),
158
+ ...group("account", {
159
+ get_billing: read,
160
+ list_api_keys: read,
161
+ create_api_key: write,
162
+ revoke_api_key: remove,
163
+ rename_account: update,
164
+ list_connected_agents: read,
165
+ disconnect_agent: remove,
166
+ list_share_links: read,
167
+ create_share_link: write,
168
+ revoke_share_link: remove
169
+ }),
170
+ ...group("domains", {
171
+ list_domains: read,
172
+ add_domain: write,
173
+ // A failed check counts toward lapsing a verified domain: not safe to repeat.
174
+ verify_domain: { destructive: true },
175
+ // Replaces the routing; a missing catch-all refuses mail.
176
+ route_domain: replace,
177
+ remove_domain: remove
178
+ }),
179
+ ...group("sheets", {
180
+ list_google_connections: read,
181
+ connect_google: outward,
182
+ pick_spreadsheet: outward,
183
+ create_spreadsheet: outward,
184
+ list_sheet_tabs: { readOnly: true, openWorld: true },
185
+ disconnect_google: remove
186
+ }),
187
+ ...group("names", {
188
+ // The log of what visitors asked a running preview for.
189
+ list_names: read,
190
+ reserve_name: write,
191
+ release_name: remove,
192
+ set_name_password: update,
193
+ // Opens the name to anyone again.
194
+ clear_name_password: { destructive: true, idempotent: true }
195
+ })
196
+ };
197
+ var ENABLE_TOOLS = "enable_tools";
198
+ function toolAnnotations(name) {
199
+ const entry = TOOL_CATALOG[name];
200
+ const readOnly = entry?.readOnly === true;
201
+ return {
202
+ title: toolTitle(name),
203
+ readOnlyHint: readOnly,
204
+ // MCP reads a missing destructiveHint as true on any tool that writes.
205
+ destructiveHint: !readOnly && entry?.destructive === true,
206
+ idempotentHint: readOnly || entry?.idempotent === true,
207
+ openWorldHint: entry?.openWorld === true
208
+ };
209
+ }
210
+ function toolTitle(name) {
211
+ const words = name.split("_").map((word) => TITLE_WORDS[word] ?? word);
212
+ const first = words[0] ?? "";
213
+ return [first.charAt(0).toUpperCase() + first.slice(1), ...words.slice(1)].join(" ");
214
+ }
215
+ var TITLE_WORDS = {
216
+ api: "API",
217
+ ai: "AI",
218
+ mcp: "MCP"
219
+ };
220
+ function parseToolGroups(setting) {
221
+ if (setting === null || setting === void 0 || setting.trim() === "") return null;
222
+ const groups = /* @__PURE__ */ new Set(["core"]);
223
+ const unknown = [];
224
+ for (const raw of setting.split(/[\s,+]+/)) {
225
+ const name = raw.trim().toLowerCase();
226
+ if (!name) continue;
227
+ if (name === "all") {
228
+ for (const g of TOOL_GROUP_NAMES) groups.add(g);
229
+ } else if (TOOL_GROUP_NAMES.includes(name)) {
230
+ groups.add(name);
231
+ } else {
232
+ unknown.push(raw.trim());
142
233
  }
143
- );
234
+ }
235
+ return { groups, unknown };
236
+ }
237
+ var LOADS_EVERYTHING = [
238
+ /^claude-code$/i,
239
+ /^claude-ai$/i,
240
+ /^claude$/i,
241
+ /codex/i,
242
+ /^openai-mcp$/i,
243
+ /chatgpt/i
244
+ ];
245
+ function clientLoadsEverything(clientName) {
246
+ if (!clientName) return false;
247
+ const name = clientName.trim().toLowerCase().replace(/[\s_]+/g, "-");
248
+ return LOADS_EVERYTHING.some((pattern) => pattern.test(name));
249
+ }
250
+ function defaultToolGroups(clientName) {
251
+ return clientLoadsEverything(clientName) ? new Set(TOOL_GROUP_NAMES) : /* @__PURE__ */ new Set(["core"]);
144
252
  }
145
253
 
146
- // src/resources.ts
147
- var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
254
+ // src/tools.ts
255
+ var import_zod = require("zod");
256
+ var import_sdk = require("@webhooks-cc/sdk");
148
257
 
149
258
  // src/compact.ts
150
259
  var MAX_OUTPUT = 32768;
@@ -354,142 +463,11 @@ function compactRequest(request) {
354
463
  return compact;
355
464
  }
356
465
 
357
- // src/resources.ts
358
- var ENDPOINTS_RESOURCE_URI = "webhooks://endpoints";
359
- var ENDPOINT_RECENT_TEMPLATE_URI = "webhooks://endpoint/{slug}/recent";
360
- var REQUEST_TEMPLATE_URI = "webhooks://request/{id}";
361
- var MAX_ENDPOINT_RESOURCE_ITEMS = 25;
362
- var RECENT_REQUEST_LIMIT = 10;
363
- function jsonResource(uri, value) {
364
- return {
365
- contents: [
366
- {
367
- uri,
368
- mimeType: "application/json",
369
- text: JSON.stringify(value, null, 2)
370
- }
371
- ]
372
- };
373
- }
374
- function summarizeRequest(request) {
375
- return {
376
- id: request.id,
377
- method: request.method,
378
- path: request.path,
379
- receivedAt: request.receivedAt,
380
- contentType: request.contentType ?? null
381
- };
382
- }
383
- function variableToString(value, name) {
384
- if (typeof value === "string" && value.length > 0) {
385
- return value;
386
- }
387
- throw new Error(`Missing resource variable "${name}"`);
388
- }
389
- async function listEndpointSummaries(client) {
390
- const endpoints = (await client.endpoints.list()).slice().sort((left, right) => right.createdAt - left.createdAt);
391
- const truncated = endpoints.length > MAX_ENDPOINT_RESOURCE_ITEMS;
392
- const visible = endpoints.slice(0, MAX_ENDPOINT_RESOURCE_ITEMS);
393
- const summaries = await Promise.all(
394
- visible.map(async (endpoint) => {
395
- const recent = await client.requests.list(endpoint.slug, { limit: 1 });
396
- return {
397
- ...endpoint,
398
- lastRequest: recent[0] ? summarizeRequest(recent[0]) : null
399
- };
400
- })
401
- );
402
- return {
403
- endpoints: summaries,
404
- total: endpoints.length,
405
- truncated
406
- };
407
- }
408
- function registerResources(server, client) {
409
- server.registerResource(
410
- "endpoints-overview",
411
- ENDPOINTS_RESOURCE_URI,
412
- {
413
- title: "Endpoints Overview",
414
- description: "All endpoints with a summary of recent activity.",
415
- mimeType: "application/json"
416
- },
417
- async () => {
418
- return jsonResource(ENDPOINTS_RESOURCE_URI, await listEndpointSummaries(client));
419
- }
420
- );
421
- server.registerResource(
422
- "endpoint-recent-requests",
423
- new import_mcp.ResourceTemplate(ENDPOINT_RECENT_TEMPLATE_URI, {
424
- list: async () => {
425
- const endpoints = await client.endpoints.list();
426
- return {
427
- resources: endpoints.map((endpoint) => ({
428
- uri: `webhooks://endpoint/${endpoint.slug}/recent`,
429
- name: `${endpoint.slug} recent requests`,
430
- description: `Recent requests for endpoint ${endpoint.slug}`,
431
- mimeType: "application/json"
432
- }))
433
- };
434
- },
435
- complete: {
436
- slug: async (value) => {
437
- const endpoints = await client.endpoints.list();
438
- return endpoints.map((endpoint) => endpoint.slug).filter((slug) => slug.startsWith(value)).slice(0, 20);
439
- }
440
- }
441
- }),
442
- {
443
- title: "Endpoint Recent Requests",
444
- description: "Last 10 captured requests for an endpoint.",
445
- mimeType: "application/json"
446
- },
447
- async (uri, variables) => {
448
- const slug = variableToString(variables.slug, "slug");
449
- const [endpoint, requests] = await Promise.all([
450
- client.endpoints.get(slug),
451
- client.requests.list(slug, { limit: RECENT_REQUEST_LIMIT })
452
- ]);
453
- return jsonResource(uri.toString(), {
454
- endpoint,
455
- requests: requests.map(compactRequest)
456
- });
457
- }
458
- );
459
- server.registerResource(
460
- "request-details",
461
- new import_mcp.ResourceTemplate(REQUEST_TEMPLATE_URI, {
462
- list: void 0,
463
- complete: {}
464
- }),
465
- {
466
- title: "Request Details",
467
- description: "Full details for a captured request by ID.",
468
- mimeType: "application/json"
469
- },
470
- async (uri, variables) => {
471
- const id = variableToString(variables.id, "id");
472
- const request = await client.requests.get(id);
473
- return jsonResource(uri.toString(), compactRequest(request));
474
- }
475
- );
476
- }
477
-
478
- // src/sandbox-tools.ts
479
- var import_zod3 = require("zod");
480
- var import_sdk2 = require("@webhooks-cc/sdk");
481
-
482
- // src/version.ts
483
- var VERSION = true ? "1.23.0" : "0.0.0-dev";
484
- var MCP_USER_AGENT = `webhooks-cc-mcp/${VERSION}`;
485
-
486
466
  // src/tools.ts
487
- var import_zod2 = require("zod");
488
- var import_sdk = require("@webhooks-cc/sdk");
489
467
  var MAX_BODY_SIZE = MAX_OUTPUT;
490
468
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
491
469
  var TIME_SEPARATOR = " \u2014 ";
492
- var httpUrlSchema = import_zod2.z.string().url().refine(
470
+ var httpUrlSchema = import_zod.z.string().url().refine(
493
471
  (value) => {
494
472
  try {
495
473
  const protocol = new URL(value).protocol;
@@ -500,24 +478,24 @@ var httpUrlSchema = import_zod2.z.string().url().refine(
500
478
  },
501
479
  { message: "Only http and https URLs are supported" }
502
480
  );
503
- var methodSchema = import_zod2.z.enum(HTTP_METHODS).default("POST").describe("HTTP method (default: POST)");
504
- var kindSchema = import_zod2.z.enum(["http", "email"]).optional().describe('Only HTTP requests ("http") or only captured emails ("email")');
481
+ var methodSchema = import_zod.z.enum(HTTP_METHODS).default("POST").describe("HTTP method (default: POST)");
482
+ var kindSchema = import_zod.z.enum(["http", "email"]).optional().describe('Only HTTP requests ("http") or only captured emails ("email")');
505
483
  var EMAIL_SCAN_LIMIT = 100;
506
- var durationOrTimestampSchema = import_zod2.z.union([import_zod2.z.string(), import_zod2.z.number()]);
484
+ var durationOrTimestampSchema = import_zod.z.union([import_zod.z.string(), import_zod.z.number()]);
507
485
  var CONDITION_FIELD_NAMES = Object.keys(import_sdk.CONDITION_FIELDS);
508
486
  var CONDITION_OP_NAMES = import_sdk.CONDITION_OPS;
509
487
  function conditionSchema(use) {
510
488
  const fields = use === "response" ? CONDITION_FIELD_NAMES.filter((field) => import_sdk.CONDITION_FIELDS[field].response) : CONDITION_FIELD_NAMES;
511
- return import_zod2.z.object({
512
- field: import_zod2.z.enum(fields).describe(
489
+ return import_zod.z.object({
490
+ field: import_zod.z.enum(fields).describe(
513
491
  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)"
514
492
  ),
515
- op: import_zod2.z.enum(CONDITION_OP_NAMES).describe(
493
+ op: import_zod.z.enum(CONDITION_OP_NAMES).describe(
516
494
  "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)"
517
495
  ),
518
- value: import_zod2.z.string().max(import_sdk.MAX_CONDITION_VALUE_LEN).optional().describe(`What to compare with (not for exists and not_exists)`),
519
- 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"),
520
- 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"]')
496
+ value: import_zod.z.string().max(import_sdk.MAX_CONDITION_VALUE_LEN).optional().describe(`What to compare with (not for exists and not_exists)`),
497
+ 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"),
498
+ 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"]')
521
499
  }).superRefine((condition, ctx) => {
522
500
  for (const issue of (0, import_sdk.conditionIssues)(condition, { use })) {
523
501
  ctx.addIssue({ code: "custom", message: issue });
@@ -525,46 +503,46 @@ function conditionSchema(use) {
525
503
  });
526
504
  }
527
505
  var ruleConditionSchema = conditionSchema("response");
528
- var emailFieldSchema = import_zod2.z.object({
529
- name: import_zod2.z.string().describe("Letters, digits and _, starting with a letter: {{ fields.name }}"),
530
- from: import_zod2.z.enum(["subject", "text", "html", "from", "to", "header"]).describe("What it reads; html is the HTML as text"),
531
- header: import_zod2.z.string().optional().describe('The header name, for from "header"'),
532
- pattern: import_zod2.z.string().describe("RE2 regular expression; the first group is the value (no lookarounds)")
506
+ var emailFieldSchema = import_zod.z.object({
507
+ name: import_zod.z.string().describe("Letters, digits and _, starting with a letter: {{ fields.name }}"),
508
+ from: import_zod.z.enum(["subject", "text", "html", "from", "to", "header"]).describe("What it reads; html is the HTML as text"),
509
+ header: import_zod.z.string().optional().describe('The header name, for from "header"'),
510
+ pattern: import_zod.z.string().describe("RE2 regular expression; the first group is the value (no lookarounds)")
533
511
  });
534
- var layoutSchema = import_zod2.z.object({
535
- id: import_zod2.z.string().describe("Stable id: 1 to 40 letters, digits, - or _"),
536
- name: import_zod2.z.string().max(100),
537
- enabled: import_zod2.z.boolean().optional().describe("Off: kept but never matched (default on)"),
538
- match: import_zod2.z.object({
539
- logic: import_zod2.z.enum(["and", "or"]).optional(),
540
- conditions: import_zod2.z.array(conditionSchema("filter")).max(10)
512
+ var layoutSchema = import_zod.z.object({
513
+ id: import_zod.z.string().describe("Stable id: 1 to 40 letters, digits, - or _"),
514
+ name: import_zod.z.string().max(100),
515
+ enabled: import_zod.z.boolean().optional().describe("Off: kept but never matched (default on)"),
516
+ match: import_zod.z.object({
517
+ logic: import_zod.z.enum(["and", "or"]).optional(),
518
+ conditions: import_zod.z.array(conditionSchema("filter")).max(10)
541
519
  }).optional().describe("When it applies, e.g. the sender or subject; no conditions: every email"),
542
- fields: import_zod2.z.array(emailFieldSchema).max(20).optional().describe("Named values, as emailFields"),
543
- lineItems: import_zod2.z.object({
544
- from: import_zod2.z.enum(["text", "html", "attachment"]),
545
- pattern: import_zod2.z.string().optional().describe(
520
+ fields: import_zod.z.array(emailFieldSchema).max(20).optional().describe("Named values, as emailFields"),
521
+ lineItems: import_zod.z.object({
522
+ from: import_zod.z.enum(["text", "html", "attachment"]),
523
+ pattern: import_zod.z.string().optional().describe(
546
524
  "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+)"
547
525
  ),
548
- start: import_zod2.z.string().optional().describe("text/html: search only after this (RE2)"),
549
- end: import_zod2.z.string().optional().describe("text/html: and before this (RE2)"),
550
- attachment: import_zod2.z.string().optional().describe("attachment: file name with * and ? (default: the first spreadsheet or CSV)"),
551
- sheet: import_zod2.z.string().optional().describe("attachment: sheet name (default: the first)"),
552
- 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")
526
+ start: import_zod.z.string().optional().describe("text/html: search only after this (RE2)"),
527
+ end: import_zod.z.string().optional().describe("text/html: and before this (RE2)"),
528
+ attachment: import_zod.z.string().optional().describe("attachment: file name with * and ? (default: the first spreadsheet or CSV)"),
529
+ sheet: import_zod.z.string().optional().describe("attachment: sheet name (default: the first)"),
530
+ 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")
553
531
  }).nullable().optional().describe("Rows such as an order's products"),
554
- parseAttachments: import_zod2.z.boolean().optional().describe("Parse CSV, XLSX, ODS, PDF text and text files into the JSON; uses parsed pages")
532
+ parseAttachments: import_zod.z.boolean().optional().describe("Parse CSV, XLSX, ODS, PDF text and text files into the JSON; uses parsed pages")
555
533
  });
556
534
  var destinationFields = {
557
- name: import_zod2.z.string().max(100).nullable().optional().describe("A name for the list and the log"),
558
- enabled: import_zod2.z.boolean().optional().describe("Send to it (default true)"),
559
- http: import_zod2.z.boolean().optional().describe("Takes HTTP requests (default true)"),
560
- email: import_zod2.z.boolean().optional().describe("Takes emails (default true)"),
561
- filter: import_zod2.z.object({
562
- logic: import_zod2.z.enum(["and", "or"]).optional().describe("and (default): every condition; or: any"),
563
- conditions: import_zod2.z.array(conditionSchema("filter")).max(10)
535
+ name: import_zod.z.string().max(100).nullable().optional().describe("A name for the list and the log"),
536
+ enabled: import_zod.z.boolean().optional().describe("Send to it (default true)"),
537
+ http: import_zod.z.boolean().optional().describe("Takes HTTP requests (default true)"),
538
+ email: import_zod.z.boolean().optional().describe("Takes emails (default true)"),
539
+ filter: import_zod.z.object({
540
+ logic: import_zod.z.enum(["and", "or"]).optional().describe("and (default): every condition; or: any"),
541
+ conditions: import_zod.z.array(conditionSchema("filter")).max(10)
564
542
  }).nullable().optional().describe(
565
543
  "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)"
566
544
  ),
567
- format: import_zod2.z.enum([
545
+ format: import_zod.z.enum([
568
546
  "auto",
569
547
  "as_received",
570
548
  "json",
@@ -577,103 +555,103 @@ var destinationFields = {
577
555
  ]).optional().describe(
578
556
  "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"
579
557
  ),
580
- mailgunSigningKey: import_zod2.z.string().max(200).nullable().optional().describe(
558
+ mailgunSigningKey: import_zod.z.string().max(200).nullable().optional().describe(
581
559
  "For format mailgun: your Mailgun HTTP webhook signing key, so the form's signature verifies; null removes it. Write-only."
582
560
  ),
583
- bodyTemplate: import_zod2.z.unknown().optional().describe(
561
+ bodyTemplate: import_zod.z.unknown().optional().describe(
584
562
  `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)`
585
563
  ),
586
- messageTemplate: import_zod2.z.string().max(4e3).nullable().optional().describe(
564
+ messageTemplate: import_zod.z.string().max(4e3).nullable().optional().describe(
587
565
  "For format chat: the message text with {{ placeholders }}; null sends the standard message"
588
566
  ),
589
- headers: import_zod2.z.array(
590
- import_zod2.z.object({
591
- name: import_zod2.z.string().describe("Header name, e.g. Authorization"),
592
- value: import_zod2.z.string().nullable().describe("Value, or null to keep the stored one")
567
+ headers: import_zod.z.array(
568
+ import_zod.z.object({
569
+ name: import_zod.z.string().describe("Header name, e.g. Authorization"),
570
+ value: import_zod.z.string().nullable().describe("Value, or null to keep the stored one")
593
571
  })
594
572
  ).max(10).optional().describe("Your own headers for every delivery (not chat). Replaces the whole list"),
595
- appendPath: import_zod2.z.boolean().optional().describe("Append the captured path to the URL (as_received)"),
596
- 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"),
597
- keepOrder: import_zod2.z.boolean().optional().describe("One delivery at a time, in capture order"),
598
- sentField: import_zod2.z.string().max(128).nullable().optional().describe("JSON body field with the sender's own timestamp"),
599
- throttleSeconds: import_zod2.z.number().int().min(0).max(3600).optional().describe(
573
+ appendPath: import_zod.z.boolean().optional().describe("Append the captured path to the URL (as_received)"),
574
+ 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"),
575
+ keepOrder: import_zod.z.boolean().optional().describe("One delivery at a time, in capture order"),
576
+ sentField: import_zod.z.string().max(128).nullable().optional().describe("JSON body field with the sender's own timestamp"),
577
+ throttleSeconds: import_zod.z.number().int().min(0).max(3600).optional().describe(
600
578
  "At most one delivery every N seconds; the rest are not sent (1 makes a heads-up channel)"
601
579
  ),
602
- delaySeconds: import_zod2.z.number().int().min(0).max(3600).optional().describe("Wait this long after the capture before sending, like a callback")
580
+ delaySeconds: import_zod.z.number().int().min(0).max(3600).optional().describe("Wait this long after the capture before sending, like a callback")
603
581
  };
604
- var responseRuleSchema = import_zod2.z.object({
605
- 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)`),
606
- enabled: import_zod2.z.boolean().optional().default(true).describe("Whether this rule is active"),
607
- logic: import_zod2.z.enum(["and", "or"]).optional().default("and").describe('How to combine conditions: "and" (all match) or "or" (any match)'),
608
- conditions: import_zod2.z.array(ruleConditionSchema).min(1).max(10).describe("Conditions to evaluate (1-10)"),
609
- response: import_zod2.z.lazy(() => mockResponseSchema).describe("Response when conditions match")
582
+ var responseRuleSchema = import_zod.z.object({
583
+ 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)`),
584
+ enabled: import_zod.z.boolean().optional().default(true).describe("Whether this rule is active"),
585
+ logic: import_zod.z.enum(["and", "or"]).optional().default("and").describe('How to combine conditions: "and" (all match) or "or" (any match)'),
586
+ conditions: import_zod.z.array(ruleConditionSchema).min(1).max(10).describe("Conditions to evaluate (1-10)"),
587
+ response: import_zod.z.lazy(() => mockResponseSchema).describe("Response when conditions match")
610
588
  });
611
- var responseRulesSchema = import_zod2.z.array(responseRuleSchema).max(50).describe("Ordered conditional response rules (first match wins, max 50)");
612
- var mockResponseSchema = import_zod2.z.object({
613
- 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})`),
614
- body: import_zod2.z.string().default("").describe(
589
+ var responseRulesSchema = import_zod.z.array(responseRuleSchema).max(50).describe("Ordered conditional response rules (first match wins, max 50)");
590
+ var mockResponseSchema = import_zod.z.object({
591
+ 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})`),
592
+ body: import_zod.z.string().default("").describe(
615
593
  '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'
616
594
  ),
617
- headers: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()).default({}).describe("Response headers (default: none); values may use {{ placeholders }} too"),
618
- delay: import_zod2.z.number().int().min(import_sdk.MOCK_RESPONSE_DELAY_MIN).max(import_sdk.MOCK_RESPONSE_DELAY_MAX).optional().describe(
595
+ headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).default({}).describe("Response headers (default: none); values may use {{ placeholders }} too"),
596
+ delay: import_zod.z.number().int().min(import_sdk.MOCK_RESPONSE_DELAY_MIN).max(import_sdk.MOCK_RESPONSE_DELAY_MAX).optional().describe(
619
597
  `Response delay in milliseconds (${import_sdk.MOCK_RESPONSE_DELAY_MIN}-${import_sdk.MOCK_RESPONSE_DELAY_MAX}, default: none)`
620
598
  )
621
599
  });
622
- var endpointSiteSchema = import_zod2.z.enum(["webhooks", "mailhooks", "both"]).describe(
600
+ var endpointSiteSchema = import_zod.z.enum(["webhooks", "mailhooks", "both"]).describe(
623
601
  "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."
624
602
  );
625
603
  var createEndpointShape = {
626
- name: import_zod2.z.string().optional().describe("Display name for the endpoint"),
627
- ephemeral: import_zod2.z.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
604
+ name: import_zod.z.string().optional().describe("Display name for the endpoint"),
605
+ ephemeral: import_zod.z.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
628
606
  expiresIn: durationOrTimestampSchema.optional().describe('Auto-expire after this duration, for example "12h"'),
629
607
  mockResponse: mockResponseSchema.optional().describe("Optional mock response to return when the endpoint receives a request"),
630
608
  responseRules: responseRulesSchema.optional().describe(
631
609
  "Conditional response rules. Each rule has conditions and a response. First matching rule wins."
632
610
  ),
633
- notificationUrl: import_zod2.z.string().url().optional().describe(
611
+ notificationUrl: import_zod.z.string().url().optional().describe(
634
612
  "URL to POST a JSON summary to after each captured request (e.g. Slack/Discord webhook)"
635
613
  ),
636
614
  site: endpointSiteSchema.optional(),
637
- signingProvider: import_zod2.z.string().optional().describe(
615
+ signingProvider: import_zod.z.string().optional().describe(
638
616
  "Signing provider for automatic signature verification (e.g. stripe, github, shopify)"
639
617
  ),
640
- signingSecret: import_zod2.z.string().optional().describe(
618
+ signingSecret: import_zod.z.string().optional().describe(
641
619
  "Signing secret (encrypted server-side, never returned). Required when signingProvider is set."
642
620
  ),
643
- signingHeader: import_zod2.z.string().optional().describe("Custom signature header name. Only used with generic-hmac provider.")
621
+ signingHeader: import_zod.z.string().optional().describe("Custom signature header name. Only used with generic-hmac provider.")
644
622
  };
645
623
  var listEndpointsShape = {
646
- team: import_zod2.z.string().min(1).optional().describe(
624
+ team: import_zod.z.string().min(1).optional().describe(
647
625
  "Team id or name (case-insensitive). Keeps endpoints shared with you from that team and endpoints you own that are shared with it."
648
626
  )
649
627
  };
650
- var getEndpointShape = { slug: import_zod2.z.string().describe("The endpoint slug") };
651
- var deleteEndpointShape = { slug: import_zod2.z.string().describe("The endpoint slug to delete") };
628
+ var getEndpointShape = { slug: import_zod.z.string().describe("The endpoint slug") };
629
+ var deleteEndpointShape = { slug: import_zod.z.string().describe("The endpoint slug to delete") };
652
630
  var listRequestsShape = {
653
- endpointSlug: import_zod2.z.string().describe("The endpoint slug"),
654
- limit: import_zod2.z.number().int().min(1).max(100).default(25).describe("Max requests to return"),
655
- since: import_zod2.z.number().optional().describe("Only return requests after this timestamp in ms"),
631
+ endpointSlug: import_zod.z.string().describe("The endpoint slug"),
632
+ limit: import_zod.z.number().int().min(1).max(100).default(25).describe("Max requests to return"),
633
+ since: import_zod.z.number().optional().describe("Only return requests after this timestamp in ms"),
656
634
  kind: kindSchema
657
635
  };
658
- var getRequestShape = { requestId: import_zod2.z.string().describe("The request ID") };
636
+ var getRequestShape = { requestId: import_zod.z.string().describe("The request ID") };
659
637
  var waitForRequestShape = {
660
- endpointSlug: import_zod2.z.string().describe("The endpoint slug to monitor"),
638
+ endpointSlug: import_zod.z.string().describe("The endpoint slug to monitor"),
661
639
  timeout: durationOrTimestampSchema.default("30s").describe('How long to wait, for example "30s"'),
662
640
  pollInterval: durationOrTimestampSchema.optional().describe('Interval between polls, for example "500ms" or "1s"')
663
641
  };
664
642
  var payloadSchemaShape = {
665
- endpointSlug: import_zod2.z.string().describe("The endpoint slug"),
666
- format: import_zod2.z.enum(import_sdk.PAYLOAD_SCHEMA_FORMATS).default("typescript").describe(
643
+ endpointSlug: import_zod.z.string().describe("The endpoint slug"),
644
+ format: import_zod.z.enum(import_sdk.PAYLOAD_SCHEMA_FORMATS).default("typescript").describe(
667
645
  'Output: "typescript" interfaces, "zod" schemas, "pydantic" models, or "json-schema"'
668
646
  ),
669
- name: import_zod2.z.string().max(100).optional().describe("Root type name (default: from the detected event, else WebhookPayload)"),
670
- event: import_zod2.z.string().optional().describe(
647
+ name: import_zod.z.string().max(100).optional().describe("Root type name (default: from the detected event, else WebhookPayload)"),
648
+ event: import_zod.z.string().optional().describe(
671
649
  'Only requests whose detected provider event matches, e.g. "invoice.paid" or "push". One event per schema gives the tightest types.'
672
650
  ),
673
- method: import_zod2.z.string().optional().describe("Only requests with this HTTP method"),
674
- path: import_zod2.z.string().optional().describe("Only requests with this path"),
675
- requestIds: import_zod2.z.array(import_zod2.z.string()).min(1).max(50).optional().describe("Use exactly these requests instead of the latest ones"),
676
- limit: import_zod2.z.number().int().min(1).max(100).default(20).describe("How many of the latest requests to read before filtering")
651
+ method: import_zod.z.string().optional().describe("Only requests with this HTTP method"),
652
+ path: import_zod.z.string().optional().describe("Only requests with this path"),
653
+ requestIds: import_zod.z.array(import_zod.z.string()).min(1).max(50).optional().describe("Use exactly these requests instead of the latest ones"),
654
+ limit: import_zod.z.number().int().min(1).max(100).default(20).describe("How many of the latest requests to read before filtering")
677
655
  };
678
656
  function eventName(event) {
679
657
  const words = event.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);
@@ -745,6 +723,14 @@ ${JSON.stringify(result, null, 2)}`;
745
723
  if (typeof result.url === "string") {
746
724
  return `Approved. The share link is ${result.url} (anyone with it can read the copy until ${String(result.expiresAt)}).`;
747
725
  }
726
+ if (typeof result.invitedEmail === "string") {
727
+ const team = typeof result.teamName === "string" ? ` to "${result.teamName}"` : "";
728
+ 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.";
729
+ return `Approved. Invited ${result.invitedEmail}${team} (invite ${String(result.id)}).${warning}`;
730
+ }
731
+ if (typeof result.deleted === "string" && typeof result.teamId === "string") {
732
+ return `Approved. The team ${result.deleted ? `"${result.deleted}" ` : ""}was deleted; its subscription has ended.`;
733
+ }
748
734
  if (typeof result.removed === "string") {
749
735
  return `Approved. The domain ${result.removed} was removed; mail to it is refused now.`;
750
736
  }
@@ -819,21 +805,90 @@ function fitArrayField(value, key) {
819
805
  }
820
806
  return cut(low);
821
807
  }
822
- function jsonContent(value) {
823
- return textContent(serializeJson(value));
808
+ function isoTime(ms) {
809
+ if (ms === null || !Number.isFinite(ms)) return null;
810
+ const date = new Date(ms);
811
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
824
812
  }
825
- async function readBodyTruncated(response, limit = MAX_BODY_SIZE) {
826
- const text = await response.text();
827
- if (text.length <= limit) return text;
828
- return text.slice(0, limit) + `
829
- ... [truncated, ${text.length} chars total]`;
813
+ function searchStoppedNote(searchedUntil, order, hasCursor) {
814
+ const [direction, rest] = order === "asc" ? ["forward", "newer"] : ["back", "older"];
815
+ 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.`;
816
+ return hasCursor ? `${reached} Call search_requests again with cursor to continue.` : reached;
830
817
  }
831
- function sleep(ms) {
832
- return new Promise((resolve) => setTimeout(resolve, ms));
818
+ 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>).";
819
+ 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}`;
820
+ 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}`;
821
+ function clip(value, max) {
822
+ return typeof value === "string" ? sliceText(value, max) : void 0;
833
823
  }
834
- function splitHint(message) {
835
- const separatorIndex = message.indexOf(TIME_SEPARATOR);
836
- if (separatorIndex === -1) {
824
+ function listedSize(request) {
825
+ return jsonSize([[request]]) - 8;
826
+ }
827
+ function fitSearchRequests(requests, room) {
828
+ const share = Math.floor(room / Math.max(1, requests.length));
829
+ const fitted = requests.map(
830
+ (request) => shortenRequest(request, share, listedSize)
831
+ );
832
+ if (fitted.every((request) => listedSize(request) <= share)) return fitted;
833
+ return requests.map((request) => {
834
+ const { id, receivedAt } = request;
835
+ return { id, receivedAt, shortened: true };
836
+ });
837
+ }
838
+ function shortenRequest(request, share, sizeOf) {
839
+ if (sizeOf(request) <= share) return request;
840
+ if (typeof request.body === "string") {
841
+ const body = request.body;
842
+ let keep = body.length - (sizeOf(request) - share) - 96;
843
+ while (keep > 0) {
844
+ const cut = {
845
+ ...request,
846
+ body: sliceText(body, keep),
847
+ bodyTruncated: true,
848
+ bodySize: body.length
849
+ };
850
+ if (sizeOf(cut) <= share) return cut;
851
+ keep = Math.floor(keep / 2);
852
+ }
853
+ }
854
+ const email = request.email;
855
+ const summary = {
856
+ id: request.id,
857
+ slug: clip(request.slug, 64),
858
+ method: clip(request.method, 16),
859
+ path: clip(request.path, 200),
860
+ contentType: clip(request.contentType, 100),
861
+ size: request.size,
862
+ receivedAt: request.receivedAt,
863
+ kind: request.kind,
864
+ ...email ? { subject: clip(email.subject, 200) } : {},
865
+ shortened: true,
866
+ ...typeof request.note === "string" ? { note: request.note } : {}
867
+ };
868
+ return summary;
869
+ }
870
+ function requestContent(request) {
871
+ const compact = compactRequest(request);
872
+ if (jsonSize(compact) <= MAX_BODY_SIZE) return jsonContent(compact);
873
+ return jsonContent(
874
+ shortenRequest({ ...compact, note: REQUEST_SHORTENED_NOTE }, MAX_BODY_SIZE, jsonSize)
875
+ );
876
+ }
877
+ function jsonContent(value) {
878
+ return textContent(serializeJson(value));
879
+ }
880
+ async function readBodyTruncated(response, limit = MAX_BODY_SIZE) {
881
+ const text = await response.text();
882
+ if (text.length <= limit) return text;
883
+ return text.slice(0, limit) + `
884
+ ... [truncated, ${text.length} chars total]`;
885
+ }
886
+ function sleep(ms) {
887
+ return new Promise((resolve) => setTimeout(resolve, ms));
888
+ }
889
+ function splitHint(message) {
890
+ const separatorIndex = message.indexOf(TIME_SEPARATOR);
891
+ if (separatorIndex === -1) {
837
892
  return { message, hint: null };
838
893
  }
839
894
  return {
@@ -863,6 +918,13 @@ function serializeError(error) {
863
918
  } else if (error instanceof import_sdk.WebhooksCCError) {
864
919
  payload.code = error.statusCode >= 500 ? "server_error" : "validation_error";
865
920
  }
921
+ if (error instanceof import_sdk.WebhooksCCError && payload.hint === null) {
922
+ if (error.code === "invalid_cursor") {
923
+ 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.";
924
+ } else if (error.code === "search_busy") {
925
+ payload.hint = "The account's other searches are still running. Wait retryAfter seconds, then call again with the same inputs.";
926
+ }
927
+ }
866
928
  return JSON.stringify(payload, null, 2);
867
929
  }
868
930
  function withErrorHandling(handler) {
@@ -1087,11 +1149,18 @@ function durationMs(value) {
1087
1149
  return null;
1088
1150
  }
1089
1151
  }
1090
- function toolTarget(target, remote, handles) {
1152
+ function toolTarget(target, options, handles) {
1153
+ const remote = options.remote === true;
1091
1154
  return {
1092
1155
  tool(name, description, shape, handler) {
1093
1156
  if (remote && LOCAL_ONLY_TOOLS.has(name)) return { remove() {
1094
1157
  } };
1158
+ const entry = TOOL_CATALOG[name];
1159
+ if (!entry) throw new Error(`${name} has no entry in catalog.ts`);
1160
+ if (options.groups && !options.groups.has(entry.group)) return { remove() {
1161
+ } };
1162
+ if (options.readOnly && entry.readOnly !== true) return { remove() {
1163
+ } };
1095
1164
  const wrapped = remote ? async (args) => {
1096
1165
  const input = args;
1097
1166
  if (TARGET_URL_TOOLS.has(name) && input.targetUrl !== void 0) {
@@ -1125,7 +1194,7 @@ function toolTarget(target, remote, handles) {
1125
1194
  }
1126
1195
  return handler(bounded);
1127
1196
  } : handler;
1128
- const handle = target.tool(name, description, shape, wrapped);
1197
+ const handle = target.tool(name, description, shape, wrapped, toolAnnotations(name));
1129
1198
  handles.push(handle);
1130
1199
  return handle;
1131
1200
  }
@@ -1201,7 +1270,7 @@ function withShareHints(list, remote = false) {
1201
1270
  function registerTools(target, client, options = {}) {
1202
1271
  const handles = [];
1203
1272
  const remote = options.remote === true;
1204
- const server = toolTarget(target, remote, handles);
1273
+ const server = toolTarget(target, options, handles);
1205
1274
  const shareWith = remote ? REMOTE_SHARE : "share_port";
1206
1275
  server.tool(
1207
1276
  "create_endpoint",
@@ -1284,7 +1353,7 @@ function registerTools(target, client, options = {}) {
1284
1353
  server.tool(
1285
1354
  "list_team_members",
1286
1355
  "List a team's members and pending invites. Any member of the team may call this.",
1287
- { teamId: import_zod2.z.string().min(1).describe("Team id (from list_teams)") },
1356
+ { teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)") },
1288
1357
  withErrorHandling(async ({ teamId }) => {
1289
1358
  const result = await client.teams.members(teamId);
1290
1359
  return jsonContent(result);
@@ -1294,8 +1363,8 @@ function registerTools(target, client, options = {}) {
1294
1363
  "share_endpoint",
1295
1364
  "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.",
1296
1365
  {
1297
- slug: import_zod2.z.string().describe("Slug of an endpoint you own"),
1298
- teamId: import_zod2.z.string().min(1).describe("Team id (from list_teams)")
1366
+ slug: import_zod.z.string().describe("Slug of an endpoint you own"),
1367
+ teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)")
1299
1368
  },
1300
1369
  withErrorHandling(async ({ slug, teamId }) => {
1301
1370
  await client.teams.share(teamId, slug);
@@ -1306,14 +1375,132 @@ function registerTools(target, client, options = {}) {
1306
1375
  "unshare_endpoint",
1307
1376
  "Stop sharing an endpoint you own with a team. Works even when the team is suspended.",
1308
1377
  {
1309
- slug: import_zod2.z.string().describe("Slug of an endpoint you own"),
1310
- teamId: import_zod2.z.string().min(1).describe("Team id (from list_teams)")
1378
+ slug: import_zod.z.string().describe("Slug of an endpoint you own"),
1379
+ teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)")
1311
1380
  },
1312
1381
  withErrorHandling(async ({ slug, teamId }) => {
1313
1382
  await client.teams.unshare(teamId, slug);
1314
1383
  return jsonContent({ shared: false, slug, teamId });
1315
1384
  })
1316
1385
  );
1386
+ server.tool(
1387
+ "create_team",
1388
+ "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.",
1389
+ { name: import_zod.z.string().trim().min(1).max(100).describe("The team's name") },
1390
+ withErrorHandling(async ({ name }) => jsonContent(await client.teams.create(name)))
1391
+ );
1392
+ server.tool(
1393
+ "rename_team",
1394
+ "Rename a team you own.",
1395
+ {
1396
+ teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)"),
1397
+ name: import_zod.z.string().trim().min(1).max(100).describe("The new name")
1398
+ },
1399
+ withErrorHandling(async ({ teamId, name }) => {
1400
+ await client.teams.rename(teamId, name);
1401
+ return jsonContent({ renamed: true, teamId, name });
1402
+ })
1403
+ );
1404
+ server.tool(
1405
+ "delete_team",
1406
+ "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.",
1407
+ { teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)") },
1408
+ withErrorHandling(async ({ teamId }) => {
1409
+ const pending = await client.teams.delete(teamId);
1410
+ if (!pending.id) return textContent(`Deleted the team ${teamId}.`);
1411
+ return textContent(approvalAskText(pending));
1412
+ })
1413
+ );
1414
+ server.tool(
1415
+ "remove_team_member",
1416
+ "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.",
1417
+ {
1418
+ teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)"),
1419
+ userId: import_zod.z.string().min(1).describe("The member's userId (from list_team_members)")
1420
+ },
1421
+ withErrorHandling(async ({ teamId, userId }) => {
1422
+ await client.teams.removeMember(teamId, userId);
1423
+ return jsonContent({ removed: true, teamId, userId });
1424
+ })
1425
+ );
1426
+ server.tool(
1427
+ "leave_team",
1428
+ "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.",
1429
+ { teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)") },
1430
+ withErrorHandling(async ({ teamId }) => {
1431
+ await client.teams.leave(teamId);
1432
+ return jsonContent({ left: true, teamId });
1433
+ })
1434
+ );
1435
+ server.tool(
1436
+ "invite_to_team",
1437
+ "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.",
1438
+ {
1439
+ teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)"),
1440
+ email: import_zod.z.string().trim().email().describe("The address to invite")
1441
+ },
1442
+ withErrorHandling(async ({ teamId, email }) => {
1443
+ const pending = await client.teams.invite(teamId, email);
1444
+ if (!pending.id) return textContent(approvalResultText({ ...await pending.wait() }));
1445
+ return textContent(approvalAskText(pending));
1446
+ })
1447
+ );
1448
+ server.tool(
1449
+ "list_invites",
1450
+ "List the team invites waiting for you to answer: team, who invited you, when, and the invite id for answer_invite.",
1451
+ {},
1452
+ withErrorHandling(async () => jsonContent(await client.teams.invites.list()))
1453
+ );
1454
+ server.tool(
1455
+ "answer_invite",
1456
+ "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.",
1457
+ {
1458
+ inviteId: import_zod.z.string().min(1).describe("The invite's id, from list_invites"),
1459
+ answer: import_zod.z.enum(["accept", "decline"]).describe("accept joins the team; decline turns it down")
1460
+ },
1461
+ withErrorHandling(async ({ inviteId, answer }) => {
1462
+ if (answer === "accept") {
1463
+ await client.teams.invites.accept(inviteId);
1464
+ return jsonContent({ accepted: true, inviteId });
1465
+ }
1466
+ await client.teams.invites.decline(inviteId);
1467
+ return jsonContent({ declined: true, inviteId });
1468
+ })
1469
+ );
1470
+ server.tool(
1471
+ "withdraw_invite",
1472
+ "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.",
1473
+ {
1474
+ teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)"),
1475
+ inviteId: import_zod.z.string().min(1).describe("The invite's id (from list_team_members' pendingInvites)")
1476
+ },
1477
+ withErrorHandling(async ({ teamId, inviteId }) => {
1478
+ await client.teams.withdrawInvite(teamId, inviteId);
1479
+ return jsonContent({ withdrawn: true, teamId, inviteId });
1480
+ })
1481
+ );
1482
+ server.tool(
1483
+ "quote_team_change",
1484
+ "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.",
1485
+ {
1486
+ teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)"),
1487
+ seats: import_zod.z.number().int().min(1).optional().describe("Price this seat count"),
1488
+ plan: import_zod.z.enum(["team", "team_business"]).optional().describe("Price a move to this plan: team (Teams) or team_business (Team Business)"),
1489
+ interval: import_zod.z.enum(["month", "year"]).optional().describe("Billing interval for plan (default month; Teams is monthly only)")
1490
+ },
1491
+ withErrorHandling(async ({ teamId, seats, plan, interval }) => {
1492
+ if (seats === void 0 === (plan === void 0)) {
1493
+ throw new Error("Give seats or plan, not both.");
1494
+ }
1495
+ if (seats !== void 0) {
1496
+ if (interval !== void 0) throw new Error("interval goes with plan, not seats.");
1497
+ return jsonContent(await client.billing.quoteTeamSeats(teamId, seats));
1498
+ }
1499
+ return jsonContent(
1500
+ await client.billing.quoteTeamPlan(teamId, plan, interval)
1501
+ );
1502
+ })
1503
+ );
1317
1504
  server.tool(
1318
1505
  "get_endpoint",
1319
1506
  "Get details for a specific webhook endpoint by slug.",
@@ -1327,8 +1514,8 @@ function registerTools(target, client, options = {}) {
1327
1514
  "favorite_endpoint",
1328
1515
  "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.",
1329
1516
  {
1330
- slug: import_zod2.z.string().describe("The endpoint slug"),
1331
- favorite: import_zod2.z.boolean().optional().describe("true marks it as a favourite (the default), false unmarks it")
1517
+ slug: import_zod.z.string().describe("The endpoint slug"),
1518
+ favorite: import_zod.z.boolean().optional().describe("true marks it as a favourite (the default), false unmarks it")
1332
1519
  },
1333
1520
  withErrorHandling(async ({ slug, favorite }) => {
1334
1521
  const result = favorite === false ? await client.endpoints.unfavorite(slug) : await client.endpoints.favorite(slug);
@@ -1339,24 +1526,24 @@ function registerTools(target, client, options = {}) {
1339
1526
  "update_endpoint",
1340
1527
  "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.",
1341
1528
  {
1342
- slug: import_zod2.z.string().describe("The endpoint slug to update"),
1343
- name: import_zod2.z.string().optional().describe("New display name"),
1529
+ slug: import_zod.z.string().describe("The endpoint slug to update"),
1530
+ name: import_zod.z.string().optional().describe("New display name"),
1344
1531
  mockResponse: mockResponseSchema.nullable().optional().describe("Default mock response (used when no rule matches), or null to clear"),
1345
1532
  responseRules: responseRulesSchema.nullable().optional().describe("Conditional response rules (first match wins), or null to clear"),
1346
- notificationUrl: import_zod2.z.string().url().nullable().optional().describe("Notification webhook URL, or null to clear it"),
1347
- signingProvider: import_zod2.z.string().nullable().optional().describe(
1533
+ notificationUrl: import_zod.z.string().url().nullable().optional().describe("Notification webhook URL, or null to clear it"),
1534
+ signingProvider: import_zod.z.string().nullable().optional().describe(
1348
1535
  "Signing provider for automatic verification (e.g. stripe, github), or null to disable"
1349
1536
  ),
1350
- signingSecret: import_zod2.z.string().nullable().optional().describe("New signing secret (encrypted server-side). Pass null to clear."),
1351
- signingHeader: import_zod2.z.string().nullable().optional().describe(
1537
+ signingSecret: import_zod.z.string().nullable().optional().describe("New signing secret (encrypted server-side). Pass null to clear."),
1538
+ signingHeader: import_zod.z.string().nullable().optional().describe(
1352
1539
  "Custom signature header name (only for generic-hmac provider), or null to clear"
1353
1540
  ),
1354
- showEmailExtracts: import_zod2.z.boolean().optional().describe("Pick codes and links out of emails (dashboard and forwarded JSON). Owner only."),
1541
+ showEmailExtracts: import_zod.z.boolean().optional().describe("Pick codes and links out of emails (dashboard and forwarded JSON). Owner only."),
1355
1542
  site: endpointSiteSchema.optional(),
1356
- emailFields: import_zod2.z.array(emailFieldSchema).max(20).nullable().optional().describe(
1543
+ emailFields: import_zod.z.array(emailFieldSchema).max(20).nullable().optional().describe(
1357
1544
  "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."
1358
1545
  ),
1359
- layouts: import_zod2.z.array(layoutSchema).max(50).nullable().optional().describe(
1546
+ layouts: import_zod.z.array(layoutSchema).max(50).nullable().optional().describe(
1360
1547
  "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."
1361
1548
  )
1362
1549
  },
@@ -1396,9 +1583,9 @@ function registerTools(target, client, options = {}) {
1396
1583
  "preview_layouts",
1397
1584
  "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.",
1398
1585
  {
1399
- slug: import_zod2.z.string().describe("The endpoint slug"),
1400
- requestId: import_zod2.z.string().optional().describe("A captured email on the endpoint (default: the newest)"),
1401
- layouts: import_zod2.z.array(layoutSchema).max(50).optional().describe("Unsaved layouts to try")
1586
+ slug: import_zod.z.string().describe("The endpoint slug"),
1587
+ requestId: import_zod.z.string().optional().describe("A captured email on the endpoint (default: the newest)"),
1588
+ layouts: import_zod.z.array(layoutSchema).max(50).optional().describe("Unsaved layouts to try")
1402
1589
  },
1403
1590
  withErrorHandling(async ({ slug, requestId, layouts }) => {
1404
1591
  const preview = await client.endpoints.previewLayouts(slug, { requestId, layouts });
@@ -1407,12 +1594,41 @@ function registerTools(target, client, options = {}) {
1407
1594
  return jsonContent(holder);
1408
1595
  })
1409
1596
  );
1597
+ server.tool(
1598
+ "list_layout_templates",
1599
+ "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.",
1600
+ { slug: import_zod.z.string().describe("The endpoint slug") },
1601
+ withErrorHandling(async ({ slug }) => jsonContent(await client.endpoints.layoutTemplates(slug)))
1602
+ );
1603
+ server.tool(
1604
+ "add_layout_template",
1605
+ "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.",
1606
+ {
1607
+ slug: import_zod.z.string().describe("The endpoint slug"),
1608
+ template: import_zod.z.string().min(1).describe("The template's id, from list_layout_templates")
1609
+ },
1610
+ withErrorHandling(
1611
+ async ({ slug, template }) => jsonContent(await client.endpoints.addLayoutTemplate(slug, template))
1612
+ )
1613
+ );
1614
+ server.tool(
1615
+ "preview_email_fields",
1616
+ "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.",
1617
+ {
1618
+ slug: import_zod.z.string().describe("The endpoint slug"),
1619
+ requestId: import_zod.z.string().optional().describe("A captured email on the endpoint (default: the newest)"),
1620
+ emailFields: import_zod.z.array(emailFieldSchema).max(20).optional().describe("Unsaved fields to try instead of the saved ones")
1621
+ },
1622
+ withErrorHandling(
1623
+ async ({ slug, requestId, emailFields }) => jsonContent(await client.endpoints.previewEmailFields(slug, { requestId, emailFields }))
1624
+ )
1625
+ );
1410
1626
  server.tool(
1411
1627
  "preview_response",
1412
1628
  "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.",
1413
1629
  {
1414
- slug: import_zod2.z.string().describe("The endpoint slug"),
1415
- requestId: import_zod2.z.string().optional().describe(
1630
+ slug: import_zod.z.string().describe("The endpoint slug"),
1631
+ requestId: import_zod.z.string().optional().describe(
1416
1632
  "A captured request on the endpoint (default: the newest HTTP request, or a sample)"
1417
1633
  ),
1418
1634
  mockResponse: mockResponseSchema.nullable().optional().describe("Try this default response instead of the saved one (null: none)"),
@@ -1440,9 +1656,9 @@ function registerTools(target, client, options = {}) {
1440
1656
  "create_endpoints",
1441
1657
  "Create multiple webhook endpoints in one call.",
1442
1658
  {
1443
- count: import_zod2.z.number().int().min(1).max(20).describe("Number of endpoints to create"),
1444
- namePrefix: import_zod2.z.string().optional().describe("Optional prefix for endpoint names"),
1445
- ephemeral: import_zod2.z.boolean().optional().describe("Create temporary endpoints that auto-expire"),
1659
+ count: import_zod.z.number().int().min(1).max(20).describe("Number of endpoints to create"),
1660
+ namePrefix: import_zod.z.string().optional().describe("Optional prefix for endpoint names"),
1661
+ ephemeral: import_zod.z.boolean().optional().describe("Create temporary endpoints that auto-expire"),
1446
1662
  expiresIn: durationOrTimestampSchema.optional().describe('Auto-expire after this duration, for example "12h"')
1447
1663
  },
1448
1664
  withErrorHandling(async ({ count, namePrefix, ephemeral, expiresIn }) => {
@@ -1463,7 +1679,7 @@ function registerTools(target, client, options = {}) {
1463
1679
  "delete_endpoints",
1464
1680
  "Delete multiple webhook endpoints in one call.",
1465
1681
  {
1466
- slugs: import_zod2.z.array(import_zod2.z.string()).min(1).max(100).describe("Endpoint slugs to delete")
1682
+ slugs: import_zod.z.array(import_zod.z.string()).min(1).max(100).describe("Endpoint slugs to delete")
1467
1683
  },
1468
1684
  withErrorHandling(async ({ slugs }) => {
1469
1685
  const settled = await Promise.allSettled(
@@ -1491,14 +1707,14 @@ function registerTools(target, client, options = {}) {
1491
1707
  "send_webhook",
1492
1708
  "Send a test webhook to a hosted endpoint. Supports provider templates and signing.",
1493
1709
  {
1494
- slug: import_zod2.z.string().describe("The endpoint slug to send to"),
1710
+ slug: import_zod.z.string().describe("The endpoint slug to send to"),
1495
1711
  method: methodSchema,
1496
- headers: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()).optional().describe("HTTP headers to include"),
1497
- body: import_zod2.z.unknown().optional().describe("Request body"),
1498
- provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to send with signed headers"),
1499
- template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
1500
- event: import_zod2.z.string().optional().describe("Provider event or topic name"),
1501
- secret: import_zod2.z.string().optional().describe(
1712
+ headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional().describe("HTTP headers to include"),
1713
+ body: import_zod.z.unknown().optional().describe("Request body"),
1714
+ provider: import_zod.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to send with signed headers"),
1715
+ template: import_zod.z.string().optional().describe("Provider-specific template preset"),
1716
+ event: import_zod.z.string().optional().describe("Provider event or topic name"),
1717
+ secret: import_zod.z.string().optional().describe(
1502
1718
  "Signing secret. Required for signed provider templates; omit for secretless providers (sendgrid, discord, plaid)."
1503
1719
  )
1504
1720
  },
@@ -1542,56 +1758,95 @@ function registerTools(target, client, options = {}) {
1542
1758
  );
1543
1759
  server.tool(
1544
1760
  "search_requests",
1545
- "Search captured webhook requests across endpoints using retained full-text search.",
1761
+ "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.",
1546
1762
  {
1547
- slug: import_zod2.z.string().optional().describe("Filter to a specific endpoint slug"),
1548
- method: import_zod2.z.string().optional().describe("Filter by HTTP method"),
1763
+ slug: import_zod.z.string().optional().describe("Filter to a specific endpoint slug"),
1764
+ method: import_zod.z.string().optional().describe("Filter by HTTP method"),
1549
1765
  kind: kindSchema,
1550
- q: import_zod2.z.string().optional().describe("Free-text search across path, body, and headers"),
1551
- from: durationOrTimestampSchema.optional().describe('Start time as a timestamp or duration like "1h" or "7d"'),
1552
- to: durationOrTimestampSchema.optional().describe('End time as a timestamp or duration like "1h" or "7d"'),
1553
- limit: import_zod2.z.number().int().min(1).max(200).default(50).describe("Max results to return"),
1554
- offset: import_zod2.z.number().int().min(0).max(1e4).default(0).describe("Result offset"),
1555
- order: import_zod2.z.enum(["asc", "desc"]).default("desc").describe("Sort order by received time")
1556
- },
1557
- withErrorHandling(async ({ slug, method, kind, q, from, to, limit, offset, order }) => {
1558
- const results = await client.requests.search({
1559
- slug,
1560
- method,
1561
- kind,
1562
- q,
1563
- from,
1564
- to,
1565
- limit,
1566
- offset,
1567
- order
1568
- });
1569
- return jsonContent(results.map(compactRequest));
1766
+ q: import_zod.z.string().optional().describe(
1767
+ "Substring to find in the path, headers, body and decoded email fields (up to 1,000 characters)"
1768
+ ),
1769
+ from: durationOrTimestampSchema.optional().describe(
1770
+ '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.'
1771
+ ),
1772
+ to: durationOrTimestampSchema.optional().describe(
1773
+ '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.'
1774
+ ),
1775
+ limit: import_zod.z.number().int().min(1).max(200).default(50).describe("Max results to return"),
1776
+ 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)"),
1777
+ order: import_zod.z.enum(["asc", "desc"]).default("desc").describe("Sort order by received time"),
1778
+ cursor: import_zod.z.string().optional().describe(
1779
+ "The cursor from a previous search_requests result, to continue that search. Keep the other inputs the same."
1780
+ )
1781
+ },
1782
+ withErrorHandling(async ({ slug, method, kind, q, from, to, limit, offset, order, cursor }) => {
1783
+ const page = await client.requests.searchPage(
1784
+ { slug, method, kind, q, from, to, limit, offset, order },
1785
+ { cursor }
1786
+ );
1787
+ const searchedUntil = isoTime(page.searchedUntil);
1788
+ const envelope = (requests2, shortened) => {
1789
+ const notes = [
1790
+ ...page.complete ? [] : [searchStoppedNote(searchedUntil, order, Boolean(page.cursor))],
1791
+ ...shortened ? [SEARCH_SHORTENED_NOTE] : []
1792
+ ];
1793
+ return {
1794
+ requests: requests2,
1795
+ complete: page.complete,
1796
+ searchedUntil,
1797
+ cursor: page.cursor,
1798
+ ...notes.length > 0 ? { note: notes.join(" ") } : {}
1799
+ };
1800
+ };
1801
+ const requests = page.requests.map(compactRequest);
1802
+ if (jsonSize(envelope(requests, false)) <= MAX_BODY_SIZE) {
1803
+ return jsonContent(envelope(requests, false));
1804
+ }
1805
+ const room = MAX_BODY_SIZE - jsonSize(envelope([], true)) - 8;
1806
+ return jsonContent(envelope(fitSearchRequests(requests, room), true));
1570
1807
  })
1571
1808
  );
1572
1809
  server.tool(
1573
1810
  "count_requests",
1574
- "Count captured webhook requests that match the given filters.",
1811
+ "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).",
1575
1812
  {
1576
- slug: import_zod2.z.string().optional().describe("Filter to a specific endpoint slug"),
1577
- method: import_zod2.z.string().optional().describe("Filter by HTTP method"),
1813
+ slug: import_zod.z.string().optional().describe("Filter to a specific endpoint slug"),
1814
+ method: import_zod.z.string().optional().describe("Filter by HTTP method"),
1578
1815
  kind: kindSchema,
1579
- q: import_zod2.z.string().optional().describe("Free-text search across path, body, and headers"),
1580
- from: durationOrTimestampSchema.optional().describe('Start time as a timestamp or duration like "1h" or "7d"'),
1581
- to: durationOrTimestampSchema.optional().describe('End time as a timestamp or duration like "1h" or "7d"')
1816
+ q: import_zod.z.string().optional().describe(
1817
+ "Substring to find in the path, headers, body and decoded email fields (up to 1,000 characters)"
1818
+ ),
1819
+ from: durationOrTimestampSchema.optional().describe(
1820
+ '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.'
1821
+ ),
1822
+ to: durationOrTimestampSchema.optional().describe(
1823
+ '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.'
1824
+ ),
1825
+ cursor: import_zod.z.string().optional().describe(
1826
+ "The cursor from a previous count_requests result, to count the next stretch. Keep the other inputs the same."
1827
+ )
1582
1828
  },
1583
- withErrorHandling(async ({ slug, method, kind, q, from, to }) => {
1584
- const count = await client.requests.count({ slug, method, kind, q, from, to });
1585
- return jsonContent({ count });
1829
+ withErrorHandling(async ({ slug, method, kind, q, from, to, cursor }) => {
1830
+ const page = await client.requests.countPage({ slug, method, kind, q, from, to }, { cursor });
1831
+ const searchedUntil = isoTime(page.searchedUntil);
1832
+ return jsonContent({
1833
+ count: page.count,
1834
+ exact: page.exact,
1835
+ searchedUntil,
1836
+ cursor: page.cursor,
1837
+ ...page.exact ? {} : {
1838
+ 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."}`
1839
+ }
1840
+ });
1586
1841
  })
1587
1842
  );
1588
1843
  server.tool(
1589
1844
  "get_request",
1590
- "Get full details for a specific captured request by ID.",
1845
+ "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.",
1591
1846
  getRequestShape,
1592
1847
  withErrorHandling(async ({ requestId }) => {
1593
1848
  const request = await client.requests.get(requestId);
1594
- return jsonContent(compactRequest(request));
1849
+ return requestContent(request);
1595
1850
  })
1596
1851
  );
1597
1852
  server.tool(
@@ -1600,18 +1855,18 @@ function registerTools(target, client, options = {}) {
1600
1855
  waitForRequestShape,
1601
1856
  withErrorHandling(async ({ endpointSlug, timeout, pollInterval }) => {
1602
1857
  const request = await client.requests.waitFor(endpointSlug, { timeout, pollInterval });
1603
- return jsonContent(compactRequest(request));
1858
+ return requestContent(request);
1604
1859
  })
1605
1860
  );
1606
1861
  server.tool(
1607
1862
  "wait_for_requests",
1608
1863
  "Wait for multiple requests to arrive at an endpoint.",
1609
1864
  {
1610
- endpointSlug: import_zod2.z.string().describe("The endpoint slug to monitor"),
1611
- count: import_zod2.z.number().int().min(1).max(20).describe("Number of requests to collect"),
1865
+ endpointSlug: import_zod.z.string().describe("The endpoint slug to monitor"),
1866
+ count: import_zod.z.number().int().min(1).max(20).describe("Number of requests to collect"),
1612
1867
  timeout: durationOrTimestampSchema.default("30s").describe('How long to wait, for example "30s"'),
1613
1868
  pollInterval: durationOrTimestampSchema.optional().describe('Interval between polls, for example "500ms" or "1s"'),
1614
- method: import_zod2.z.string().optional().describe("Only collect requests with this HTTP method")
1869
+ method: import_zod.z.string().optional().describe("Only collect requests with this HTTP method")
1615
1870
  },
1616
1871
  withErrorHandling(async ({ endpointSlug, count, timeout, pollInterval, method }) => {
1617
1872
  const result = await waitForMultipleRequests(client, endpointSlug, {
@@ -1629,7 +1884,7 @@ function registerTools(target, client, options = {}) {
1629
1884
  "replay_request",
1630
1885
  "Replay a previously captured request to a target URL.",
1631
1886
  {
1632
- requestId: import_zod2.z.string().describe("The captured request ID"),
1887
+ requestId: import_zod.z.string().describe("The captured request ID"),
1633
1888
  targetUrl: httpUrlSchema.describe("The URL to replay the request to")
1634
1889
  },
1635
1890
  withErrorHandling(async ({ requestId, targetUrl }) => {
@@ -1646,9 +1901,9 @@ function registerTools(target, client, options = {}) {
1646
1901
  "render_email",
1647
1902
  "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.",
1648
1903
  {
1649
- requestId: import_zod2.z.string().describe("The captured email's request ID"),
1650
- format: import_zod2.z.enum(["postmark", "sendgrid", "mailgun", "cloudmailin", "json"]).describe("Whose shape; json is webhooks.cc's own email.received JSON (unsigned)"),
1651
- mailgunSigningKey: import_zod2.z.string().optional().describe("For mailgun: the signing key your handler verifies with (a test key)"),
1904
+ requestId: import_zod.z.string().describe("The captured email's request ID"),
1905
+ format: import_zod.z.enum(["postmark", "sendgrid", "mailgun", "cloudmailin", "json"]).describe("Whose shape; json is webhooks.cc's own email.received JSON (unsigned)"),
1906
+ mailgunSigningKey: import_zod.z.string().optional().describe("For mailgun: the signing key your handler verifies with (a test key)"),
1652
1907
  targetUrl: httpUrlSchema.optional().describe("Post it here, e.g. http://localhost:3000/api/inbound/postmark")
1653
1908
  },
1654
1909
  withErrorHandling(async ({ requestId, format, mailgunSigningKey, targetUrl }) => {
@@ -1679,9 +1934,9 @@ function registerTools(target, client, options = {}) {
1679
1934
  "compare_requests",
1680
1935
  "Compare two captured requests and show the structured differences.",
1681
1936
  {
1682
- leftRequestId: import_zod2.z.string().describe("The first request ID"),
1683
- rightRequestId: import_zod2.z.string().describe("The second request ID"),
1684
- ignoreHeaders: import_zod2.z.array(import_zod2.z.string()).optional().describe("Headers to ignore during comparison")
1937
+ leftRequestId: import_zod.z.string().describe("The first request ID"),
1938
+ rightRequestId: import_zod.z.string().describe("The second request ID"),
1939
+ ignoreHeaders: import_zod.z.array(import_zod.z.string()).optional().describe("Headers to ignore during comparison")
1685
1940
  },
1686
1941
  withErrorHandling(async ({ leftRequestId, rightRequestId, ignoreHeaders }) => {
1687
1942
  const [leftRequest, rightRequest] = await Promise.all([
@@ -1705,8 +1960,8 @@ function registerTools(target, client, options = {}) {
1705
1960
  "extract_from_request",
1706
1961
  "Extract specific JSON fields from a captured request body.",
1707
1962
  {
1708
- requestId: import_zod2.z.string().describe("The request ID"),
1709
- jsonPaths: import_zod2.z.array(import_zod2.z.string()).min(1).max(50).describe("Dot-notation JSON paths to extract")
1963
+ requestId: import_zod.z.string().describe("The request ID"),
1964
+ jsonPaths: import_zod.z.array(import_zod.z.string()).min(1).max(50).describe("Dot-notation JSON paths to extract")
1710
1965
  },
1711
1966
  withErrorHandling(async ({ requestId, jsonPaths }) => {
1712
1967
  const request = await client.requests.get(requestId);
@@ -1720,14 +1975,14 @@ function registerTools(target, client, options = {}) {
1720
1975
  "verify_signature",
1721
1976
  "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.",
1722
1977
  {
1723
- requestId: import_zod2.z.string().describe("The captured request ID"),
1724
- provider: import_zod2.z.enum(import_sdk.VERIFY_PROVIDERS).optional().describe("Provider whose signature scheme should be verified. Omit to use stored result."),
1725
- secret: import_zod2.z.string().optional().describe(
1978
+ requestId: import_zod.z.string().describe("The captured request ID"),
1979
+ provider: import_zod.z.enum(import_sdk.VERIFY_PROVIDERS).optional().describe("Provider whose signature scheme should be verified. Omit to use stored result."),
1980
+ secret: import_zod.z.string().optional().describe(
1726
1981
  "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."
1727
1982
  ),
1728
- publicKey: import_zod2.z.string().optional().describe("Discord application public key. Required for provider=discord."),
1983
+ publicKey: import_zod.z.string().optional().describe("Discord application public key. Required for provider=discord."),
1729
1984
  url: httpUrlSchema.optional().describe("Original signed URL. Required for Twilio, Square, and HubSpot verification."),
1730
- method: import_zod2.z.string().optional().describe(
1985
+ method: import_zod.z.string().optional().describe(
1731
1986
  "Original HTTP method used when the signature was generated. Used by HubSpot v3 verification; the SDK assumes POST when omitted."
1732
1987
  )
1733
1988
  },
@@ -1776,7 +2031,7 @@ function registerTools(target, client, options = {}) {
1776
2031
  "clear_requests",
1777
2032
  "Delete captured requests for an endpoint without deleting the endpoint itself.",
1778
2033
  {
1779
- slug: import_zod2.z.string().describe("The endpoint slug to clear"),
2034
+ slug: import_zod.z.string().describe("The endpoint slug to clear"),
1780
2035
  before: durationOrTimestampSchema.optional().describe('Only clear requests older than this timestamp or duration like "1h"')
1781
2036
  },
1782
2037
  withErrorHandling(async ({ slug, before }) => {
@@ -1790,12 +2045,12 @@ function registerTools(target, client, options = {}) {
1790
2045
  {
1791
2046
  url: httpUrlSchema.describe("Target URL"),
1792
2047
  method: methodSchema,
1793
- headers: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()).optional().describe("HTTP headers to include"),
1794
- body: import_zod2.z.unknown().optional().describe("Request body"),
1795
- provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
1796
- template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
1797
- event: import_zod2.z.string().optional().describe("Provider event or topic name"),
1798
- secret: import_zod2.z.string().optional().describe(
2048
+ headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional().describe("HTTP headers to include"),
2049
+ body: import_zod.z.unknown().optional().describe("Request body"),
2050
+ provider: import_zod.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
2051
+ template: import_zod.z.string().optional().describe("Provider-specific template preset"),
2052
+ event: import_zod.z.string().optional().describe("Provider event or topic name"),
2053
+ secret: import_zod.z.string().optional().describe(
1799
2054
  "Signing secret. Required for signed provider templates; omit for secretless providers (sendgrid, discord, plaid)."
1800
2055
  )
1801
2056
  },
@@ -1823,12 +2078,12 @@ function registerTools(target, client, options = {}) {
1823
2078
  {
1824
2079
  url: httpUrlSchema.describe("Target URL"),
1825
2080
  method: methodSchema,
1826
- headers: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()).optional().describe("HTTP headers to include"),
1827
- body: import_zod2.z.unknown().optional().describe("Request body"),
1828
- provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
1829
- template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
1830
- event: import_zod2.z.string().optional().describe("Provider event or topic name"),
1831
- secret: import_zod2.z.string().optional().describe(
2081
+ headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional().describe("HTTP headers to include"),
2082
+ body: import_zod.z.unknown().optional().describe("Request body"),
2083
+ provider: import_zod.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
2084
+ template: import_zod.z.string().optional().describe("Provider-specific template preset"),
2085
+ event: import_zod.z.string().optional().describe("Provider event or topic name"),
2086
+ secret: import_zod.z.string().optional().describe(
1832
2087
  "Signing secret. Required for signed provider templates; omit for secretless providers (sendgrid, discord, plaid)."
1833
2088
  )
1834
2089
  },
@@ -1849,7 +2104,7 @@ function registerTools(target, client, options = {}) {
1849
2104
  "list_provider_templates",
1850
2105
  "List supported webhook providers, templates, and signing metadata.",
1851
2106
  {
1852
- provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Filter to a single provider")
2107
+ provider: import_zod.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Filter to a single provider")
1853
2108
  },
1854
2109
  withErrorHandling(async ({ provider }) => {
1855
2110
  if (provider) {
@@ -1903,15 +2158,15 @@ function registerTools(target, client, options = {}) {
1903
2158
  "test_webhook_flow",
1904
2159
  "Run a full webhook test flow: create endpoint, optionally mock, send, wait, verify, replay, and clean up.",
1905
2160
  {
1906
- provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to use when sending the webhook"),
1907
- event: import_zod2.z.string().optional().describe("Optional provider event or topic name"),
1908
- secret: import_zod2.z.string().optional().describe(
2161
+ provider: import_zod.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to use when sending the webhook"),
2162
+ event: import_zod.z.string().optional().describe("Optional provider event or topic name"),
2163
+ secret: import_zod.z.string().optional().describe(
1909
2164
  "Signing secret. Required when provider is set or signature verification is enabled."
1910
2165
  ),
1911
- mockStatus: import_zod2.z.number().int().min(100).max(599).optional().describe("Optional mock response status to configure before sending"),
2166
+ mockStatus: import_zod.z.number().int().min(100).max(599).optional().describe("Optional mock response status to configure before sending"),
1912
2167
  targetUrl: httpUrlSchema.optional().describe("Optional URL to replay the captured request to after capture"),
1913
- verifySignature: import_zod2.z.boolean().default(false).describe("Verify the captured request signature after capture"),
1914
- cleanup: import_zod2.z.boolean().default(true).describe("Delete the created endpoint after the flow completes")
2168
+ verifySignature: import_zod.z.boolean().default(false).describe("Verify the captured request signature after capture"),
2169
+ cleanup: import_zod.z.boolean().default(true).describe("Delete the created endpoint after the flow completes")
1915
2170
  },
1916
2171
  withErrorHandling(
1917
2172
  async ({
@@ -1986,14 +2241,14 @@ function registerTools(target, client, options = {}) {
1986
2241
  "list_emails",
1987
2242
  "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.",
1988
2243
  {
1989
- endpointSlug: import_zod2.z.string().describe("The endpoint slug"),
1990
- limit: import_zod2.z.number().int().min(1).max(100).default(25).describe(
2244
+ endpointSlug: import_zod.z.string().describe("The endpoint slug"),
2245
+ limit: import_zod.z.number().int().min(1).max(100).default(25).describe(
1991
2246
  `Max emails to return. With tag, subject or from, the newest ${EMAIL_SCAN_LIMIT} emails are searched.`
1992
2247
  ),
1993
- since: import_zod2.z.number().optional().describe("Only emails received after this timestamp in ms"),
1994
- tag: import_zod2.z.string().optional().describe("Only emails sent to {slug}+{tag}@mailhooks.cc (exact, case-sensitive)"),
1995
- subject: import_zod2.z.string().optional().describe("Only emails whose subject contains this text"),
1996
- from: import_zod2.z.string().optional().describe("Only emails from this address (case-insensitive)")
2248
+ since: import_zod.z.number().optional().describe("Only emails received after this timestamp in ms"),
2249
+ tag: import_zod.z.string().optional().describe("Only emails sent to {slug}+{tag}@mailhooks.cc (exact, case-sensitive)"),
2250
+ subject: import_zod.z.string().optional().describe("Only emails whose subject contains this text"),
2251
+ from: import_zod.z.string().optional().describe("Only emails from this address (case-insensitive)")
1997
2252
  },
1998
2253
  withErrorHandling(async ({ endpointSlug, limit, since, tag, subject, from }) => {
1999
2254
  const filtered = tag !== void 0 || subject !== void 0 || from !== void 0;
@@ -2011,10 +2266,10 @@ function registerTools(target, client, options = {}) {
2011
2266
  "get_email",
2012
2267
  "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.",
2013
2268
  {
2014
- requestId: import_zod2.z.string().optional().describe("The email's request ID"),
2015
- endpointSlug: import_zod2.z.string().optional().describe("Get the newest email at this endpoint instead (with requestId unset)"),
2016
- tag: import_zod2.z.string().optional().describe("With endpointSlug: only emails sent to this +tag"),
2017
- includeHtml: import_zod2.z.boolean().default(false).describe("Include the HTML part (default: false, only its size)")
2269
+ requestId: import_zod.z.string().optional().describe("The email's request ID"),
2270
+ endpointSlug: import_zod.z.string().optional().describe("Get the newest email at this endpoint instead (with requestId unset)"),
2271
+ tag: import_zod.z.string().optional().describe("With endpointSlug: only emails sent to this +tag"),
2272
+ includeHtml: import_zod.z.boolean().default(false).describe("Include the HTML part (default: false, only its size)")
2018
2273
  },
2019
2274
  withErrorHandling(async ({ requestId, endpointSlug, tag, includeHtml }) => {
2020
2275
  if (requestId === void 0 === (endpointSlug === void 0)) {
@@ -2037,15 +2292,15 @@ function registerTools(target, client, options = {}) {
2037
2292
  "wait_for_email",
2038
2293
  "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.",
2039
2294
  {
2040
- endpointSlug: import_zod2.z.string().describe("The endpoint slug"),
2041
- tag: import_zod2.z.string().optional().describe("Only emails sent to {slug}+{tag}@mailhooks.cc"),
2042
- subject: import_zod2.z.string().optional().describe("Only emails whose subject contains this text"),
2043
- from: import_zod2.z.string().optional().describe("Only emails from this address (case-insensitive)"),
2295
+ endpointSlug: import_zod.z.string().describe("The endpoint slug"),
2296
+ tag: import_zod.z.string().optional().describe("Only emails sent to {slug}+{tag}@mailhooks.cc"),
2297
+ subject: import_zod.z.string().optional().describe("Only emails whose subject contains this text"),
2298
+ from: import_zod.z.string().optional().describe("Only emails from this address (case-insensitive)"),
2044
2299
  timeout: durationOrTimestampSchema.default("30s").describe(
2045
2300
  `How long to wait (default "30s"). Keep it under your MCP client's request timeout, often 60 seconds.`
2046
2301
  ),
2047
- since: import_zod2.z.number().optional().describe("Only emails received after this timestamp in ms (default: five minutes ago)"),
2048
- includeHtml: import_zod2.z.boolean().default(false).describe("Include the HTML part (default: false)")
2302
+ since: import_zod.z.number().optional().describe("Only emails received after this timestamp in ms (default: five minutes ago)"),
2303
+ includeHtml: import_zod.z.boolean().default(false).describe("Include the HTML part (default: false)")
2049
2304
  },
2050
2305
  withErrorHandling(async ({ endpointSlug, tag, subject, from, timeout, since, includeHtml }) => {
2051
2306
  const view = await emailEndpointView(client, endpointSlug);
@@ -2063,8 +2318,8 @@ function registerTools(target, client, options = {}) {
2063
2318
  "send_test_email",
2064
2319
  "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.",
2065
2320
  {
2066
- slug: import_zod2.z.string().describe("The endpoint slug"),
2067
- tag: import_zod2.z.string().optional().describe("Deliver to {slug}+{tag}@mailhooks.cc")
2321
+ slug: import_zod.z.string().describe("The endpoint slug"),
2322
+ tag: import_zod.z.string().optional().describe("Deliver to {slug}+{tag}@mailhooks.cc")
2068
2323
  },
2069
2324
  withErrorHandling(async ({ slug, tag }) => {
2070
2325
  const result = await client.emails.sendTest(slug, { tag });
@@ -2075,8 +2330,8 @@ function registerTools(target, client, options = {}) {
2075
2330
  "list_destinations",
2076
2331
  "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.",
2077
2332
  {
2078
- slug: import_zod2.z.string().describe("The endpoint slug"),
2079
- summary: import_zod2.z.boolean().default(true).describe("Add each destination's last 24 hours")
2333
+ slug: import_zod.z.string().describe("The endpoint slug"),
2334
+ summary: import_zod.z.boolean().default(true).describe("Add each destination's last 24 hours")
2080
2335
  },
2081
2336
  withErrorHandling(async ({ slug, summary }) => {
2082
2337
  return jsonContent(await client.destinations.list(slug, { summary }));
@@ -2086,7 +2341,7 @@ function registerTools(target, client, options = {}) {
2086
2341
  "add_destination",
2087
2342
  "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.",
2088
2343
  {
2089
- slug: import_zod2.z.string().describe("The endpoint slug"),
2344
+ slug: import_zod.z.string().describe("The endpoint slug"),
2090
2345
  url: httpUrlSchema.describe("https URL on a public host name, or a Slack or Discord webhook"),
2091
2346
  ...destinationFields
2092
2347
  },
@@ -2098,8 +2353,8 @@ function registerTools(target, client, options = {}) {
2098
2353
  "update_destination",
2099
2354
  "Change a destination: any of its fields; only the ones you pass change. Owner only.",
2100
2355
  {
2101
- slug: import_zod2.z.string().describe("The endpoint slug"),
2102
- destinationId: import_zod2.z.string().describe("The destination's id (from list_destinations)"),
2356
+ slug: import_zod.z.string().describe("The endpoint slug"),
2357
+ destinationId: import_zod.z.string().describe("The destination's id (from list_destinations)"),
2103
2358
  url: httpUrlSchema.optional().describe("A new URL"),
2104
2359
  ...destinationFields
2105
2360
  },
@@ -2111,8 +2366,8 @@ function registerTools(target, client, options = {}) {
2111
2366
  "remove_destination",
2112
2367
  "Remove a destination. Deliveries still waiting for it are marked failed; its log stays. Owner only.",
2113
2368
  {
2114
- slug: import_zod2.z.string().describe("The endpoint slug"),
2115
- destinationId: import_zod2.z.string().describe("The destination's id")
2369
+ slug: import_zod.z.string().describe("The endpoint slug"),
2370
+ destinationId: import_zod.z.string().describe("The destination's id")
2116
2371
  },
2117
2372
  withErrorHandling(async ({ slug, destinationId }) => {
2118
2373
  await client.destinations.delete(slug, destinationId);
@@ -2123,10 +2378,10 @@ function registerTools(target, client, options = {}) {
2123
2378
  "preview_destination",
2124
2379
  "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.",
2125
2380
  {
2126
- slug: import_zod2.z.string().describe("The endpoint slug"),
2127
- destinationId: import_zod2.z.string().optional().describe("A saved destination"),
2128
- destination: import_zod2.z.object({ url: httpUrlSchema.optional(), ...destinationFields }).optional().describe("Unsaved fields to try"),
2129
- requestId: import_zod2.z.string().optional().describe("A captured request (default: the newest of the kind it takes, or a sample)")
2381
+ slug: import_zod.z.string().describe("The endpoint slug"),
2382
+ destinationId: import_zod.z.string().optional().describe("A saved destination"),
2383
+ destination: import_zod.z.object({ url: httpUrlSchema.optional(), ...destinationFields }).optional().describe("Unsaved fields to try"),
2384
+ requestId: import_zod.z.string().optional().describe("A captured request (default: the newest of the kind it takes, or a sample)")
2130
2385
  },
2131
2386
  withErrorHandling(async ({ slug, destinationId, destination, requestId }) => {
2132
2387
  if (!destinationId && !destination?.url) {
@@ -2145,26 +2400,26 @@ function registerTools(target, client, options = {}) {
2145
2400
  "configure_forwarding",
2146
2401
  "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.",
2147
2402
  {
2148
- slug: import_zod2.z.string().describe("The endpoint slug"),
2403
+ slug: import_zod.z.string().describe("The endpoint slug"),
2149
2404
  url: httpUrlSchema.nullable().optional().describe("https URL on a public host name, or null to remove it (forwarding must be off)"),
2150
- enabled: import_zod2.z.boolean().optional().describe("Turn forwarding on (needs a URL) or off (fails deliveries still waiting)"),
2151
- http: import_zod2.z.boolean().optional().describe("Forward HTTP requests"),
2152
- email: import_zod2.z.boolean().optional().describe("Forward emails"),
2153
- format: import_zod2.z.enum(["auto", "as_received", "json", "chat"]).optional().describe(
2405
+ enabled: import_zod.z.boolean().optional().describe("Turn forwarding on (needs a URL) or off (fails deliveries still waiting)"),
2406
+ http: import_zod.z.boolean().optional().describe("Forward HTTP requests"),
2407
+ email: import_zod.z.boolean().optional().describe("Forward emails"),
2408
+ format: import_zod.z.enum(["auto", "as_received", "json", "chat"]).optional().describe(
2154
2409
  "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)"
2155
2410
  ),
2156
- headers: import_zod2.z.array(
2157
- import_zod2.z.object({
2158
- name: import_zod2.z.string().describe("Header name, e.g. Authorization"),
2159
- value: import_zod2.z.string().nullable().describe("Header value, or null to keep the value stored under this name")
2411
+ headers: import_zod.z.array(
2412
+ import_zod.z.object({
2413
+ name: import_zod.z.string().describe("Header name, e.g. Authorization"),
2414
+ value: import_zod.z.string().nullable().describe("Header value, or null to keep the value stored under this name")
2160
2415
  })
2161
2416
  ).max(10).optional().describe(
2162
2417
  "Your own headers for every delivery (not chat). Replaces the whole list; [] removes them"
2163
2418
  ),
2164
- appendPath: import_zod2.z.boolean().optional().describe("Add the path after the slug to the URL for relayed requests (default true)"),
2165
- 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"),
2166
- keepOrder: import_zod2.z.boolean().optional().describe("One delivery at a time, in capture order (default false)"),
2167
- sentField: import_zod2.z.string().max(128).nullable().optional().describe(
2419
+ appendPath: import_zod.z.boolean().optional().describe("Add the path after the slug to the URL for relayed requests (default true)"),
2420
+ 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"),
2421
+ keepOrder: import_zod.z.boolean().optional().describe("One delivery at a time, in capture order (default false)"),
2422
+ sentField: import_zod.z.string().max(128).nullable().optional().describe(
2168
2423
  "JSON body field with the sender's own timestamp, e.g. publishedAt or data.publishedAt; null clears it"
2169
2424
  )
2170
2425
  },
@@ -2190,9 +2445,9 @@ function registerTools(target, client, options = {}) {
2190
2445
  "get_forwarding_secret",
2191
2446
  "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.",
2192
2447
  {
2193
- slug: import_zod2.z.string().describe("The endpoint slug"),
2194
- destinationId: import_zod2.z.string().optional().describe("A destination (default: the endpoint's primary destination)"),
2195
- rotate: import_zod2.z.boolean().default(false).describe("Replace the secret; every delivery from now on uses the new one")
2448
+ slug: import_zod.z.string().describe("The endpoint slug"),
2449
+ destinationId: import_zod.z.string().optional().describe("A destination (default: the endpoint's primary destination)"),
2450
+ rotate: import_zod.z.boolean().default(false).describe("Replace the secret; every delivery from now on uses the new one")
2196
2451
  },
2197
2452
  withErrorHandling(async ({ slug, destinationId, rotate }) => {
2198
2453
  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);
@@ -2203,10 +2458,10 @@ function registerTools(target, client, options = {}) {
2203
2458
  "test_forwarding",
2204
2459
  "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.",
2205
2460
  {
2206
- slug: import_zod2.z.string().describe("The endpoint slug"),
2207
- destinationId: import_zod2.z.string().optional().describe("A destination (default: the endpoint's primary destination)"),
2208
- kind: import_zod2.z.enum(["http", "email"]).optional().describe("Which kind to send (default: http when the destination takes HTTP requests)"),
2209
- requestId: import_zod2.z.string().optional().describe("With destinationId: a captured request to send")
2461
+ slug: import_zod.z.string().describe("The endpoint slug"),
2462
+ destinationId: import_zod.z.string().optional().describe("A destination (default: the endpoint's primary destination)"),
2463
+ kind: import_zod.z.enum(["http", "email"]).optional().describe("Which kind to send (default: http when the destination takes HTTP requests)"),
2464
+ requestId: import_zod.z.string().optional().describe("With destinationId: a captured request to send")
2210
2465
  },
2211
2466
  withErrorHandling(async ({ slug, destinationId, kind, requestId }) => {
2212
2467
  if (destinationId) {
@@ -2224,14 +2479,14 @@ function registerTools(target, client, options = {}) {
2224
2479
  "list_deliveries",
2225
2480
  "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.",
2226
2481
  {
2227
- endpointSlug: import_zod2.z.string().optional().describe("List the endpoint's delivery log"),
2228
- requestId: import_zod2.z.string().optional().describe("List every delivery and try of this request"),
2229
- limit: import_zod2.z.number().int().min(1).max(100).default(20).describe("With endpointSlug: max rows"),
2230
- status: import_zod2.z.enum(["all", "pending", "failed", "skipped"]).default("all").describe(
2482
+ endpointSlug: import_zod.z.string().optional().describe("List the endpoint's delivery log"),
2483
+ requestId: import_zod.z.string().optional().describe("List every delivery and try of this request"),
2484
+ limit: import_zod.z.number().int().min(1).max(100).default(20).describe("With endpointSlug: max rows"),
2485
+ status: import_zod.z.enum(["all", "pending", "failed", "skipped"]).default("all").describe(
2231
2486
  "With endpointSlug: all, pending (queued or retrying), failed or skipped (filtered out)"
2232
2487
  ),
2233
- destinationId: import_zod2.z.string().optional().describe("With endpointSlug: only this destination"),
2234
- before: import_zod2.z.string().optional().describe("With endpointSlug: a row's cursor, to list older rows")
2488
+ destinationId: import_zod.z.string().optional().describe("With endpointSlug: only this destination"),
2489
+ before: import_zod.z.string().optional().describe("With endpointSlug: a row's cursor, to list older rows")
2235
2490
  },
2236
2491
  withErrorHandling(async ({ endpointSlug, requestId, limit, status, destinationId, before }) => {
2237
2492
  if (requestId === void 0 === (endpointSlug === void 0)) {
@@ -2258,9 +2513,9 @@ function registerTools(target, client, options = {}) {
2258
2513
  "redeliver",
2259
2514
  "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.",
2260
2515
  {
2261
- requestId: import_zod2.z.string().optional().describe("The request or email to send again"),
2262
- endpointSlug: import_zod2.z.string().optional().describe("Send every failed delivery of this endpoint again"),
2263
- destinationId: import_zod2.z.string().optional().describe("Only this destination")
2516
+ requestId: import_zod.z.string().optional().describe("The request or email to send again"),
2517
+ endpointSlug: import_zod.z.string().optional().describe("Send every failed delivery of this endpoint again"),
2518
+ destinationId: import_zod.z.string().optional().describe("Only this destination")
2264
2519
  },
2265
2520
  withErrorHandling(async ({ requestId, endpointSlug, destinationId }) => {
2266
2521
  if (requestId === void 0 === (endpointSlug === void 0)) {
@@ -2277,9 +2532,9 @@ function registerTools(target, client, options = {}) {
2277
2532
  "share_port",
2278
2533
  "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.",
2279
2534
  {
2280
- port: import_zod2.z.number().int().min(1).max(65535).describe("The local port the site runs on"),
2281
- name: import_zod2.z.string().optional().describe("A name you reserved (Pro and Business); leave out for a random one"),
2282
- password: import_zod2.z.string().min(8).max(200).optional().describe("What visitors must type (Pro and Business), 8 to 200 characters")
2535
+ port: import_zod.z.number().int().min(1).max(65535).describe("The local port the site runs on"),
2536
+ name: import_zod.z.string().optional().describe("A name you reserved (Pro and Business); leave out for a random one"),
2537
+ password: import_zod.z.string().min(8).max(200).optional().describe("What visitors must type (Pro and Business), 8 to 200 characters")
2283
2538
  },
2284
2539
  withErrorHandling(async ({ port, name, password }) => {
2285
2540
  const running = started.get(port);
@@ -2324,7 +2579,7 @@ function registerTools(target, client, options = {}) {
2324
2579
  "list_shares",
2325
2580
  `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.`,
2326
2581
  {
2327
- state: import_zod2.z.enum(["live", "ended"]).optional().describe("Only the running ones, or only the ended ones")
2582
+ state: import_zod.z.enum(["live", "ended"]).optional().describe("Only the running ones, or only the ended ones")
2328
2583
  },
2329
2584
  withErrorHandling(async ({ state }) => {
2330
2585
  return jsonContent(withShareHints(await client.shares.list(state ? { state } : {}), remote));
@@ -2340,8 +2595,8 @@ function registerTools(target, client, options = {}) {
2340
2595
  "create_api_key",
2341
2596
  "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).",
2342
2597
  {
2343
- name: import_zod2.z.string().min(1).max(64).describe('A name that says where the key is used, such as "GitHub Actions"'),
2344
- expiresInDays: import_zod2.z.number().int().min(1).max(365).optional().describe("How many days the key works, 1 to 365 (default 90)")
2598
+ name: import_zod.z.string().min(1).max(64).describe('A name that says where the key is used, such as "GitHub Actions"'),
2599
+ expiresInDays: import_zod.z.number().int().min(1).max(365).optional().describe("How many days the key works, 1 to 365 (default 90)")
2345
2600
  },
2346
2601
  withErrorHandling(async ({ name, expiresInDays }) => {
2347
2602
  const pending = await client.apiKeys.create({ name, expiresInDays });
@@ -2354,9 +2609,9 @@ function registerTools(target, client, options = {}) {
2354
2609
  );
2355
2610
  server.tool(
2356
2611
  "wait_for_approval",
2357
- "Wait for the account's owner to answer an approval (from create_api_key, create_share_link, remove_domain, rename_account, connect_google, pick_spreadsheet or ai_setup) and collect what it grants or did, such as the new API key, the share link or AI setup's proposals. If they have not answered yet, call it again.",
2612
+ "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.",
2358
2613
  {
2359
- id: import_zod2.z.string().describe("The approval id"),
2614
+ id: import_zod.z.string().describe("The approval id"),
2360
2615
  timeout: durationOrTimestampSchema.default("45s").describe('How long to wait for the person, for example "45s"')
2361
2616
  },
2362
2617
  withErrorHandling(async ({ id, timeout }) => {
@@ -2388,7 +2643,7 @@ function registerTools(target, client, options = {}) {
2388
2643
  "revoke_api_key",
2389
2644
  "Revoke one of the account's API keys by its id (from list_api_keys). Anything using it stops working at once.",
2390
2645
  {
2391
- id: import_zod2.z.string().describe("The key's id, from list_api_keys")
2646
+ id: import_zod.z.string().describe("The key's id, from list_api_keys")
2392
2647
  },
2393
2648
  withErrorHandling(async ({ id }) => {
2394
2649
  await client.apiKeys.revoke(id);
@@ -2399,7 +2654,7 @@ function registerTools(target, client, options = {}) {
2399
2654
  "rename_account",
2400
2655
  "Set the account's display name, or clear it with null. The account's owner must approve it: this returns a link and a code to show them, then call wait_for_approval.",
2401
2656
  {
2402
- name: import_zod2.z.string().min(1).max(100).nullable().describe("The new display name, or null to clear it")
2657
+ name: import_zod.z.string().min(1).max(100).nullable().describe("The new display name, or null to clear it")
2403
2658
  },
2404
2659
  withErrorHandling(async ({ name }) => {
2405
2660
  const pending = await client.account.rename(name);
@@ -2417,7 +2672,7 @@ function registerTools(target, client, options = {}) {
2417
2672
  "disconnect_agent",
2418
2673
  "Disconnect an agent by its id (from list_connected_agents): its tokens stop working at once. The endpoints it made stay in the account. Disconnecting the agent you are running as ends your own access.",
2419
2674
  {
2420
- id: import_zod2.z.string().describe("The agent's id, from list_connected_agents")
2675
+ id: import_zod.z.string().describe("The agent's id, from list_connected_agents")
2421
2676
  },
2422
2677
  withErrorHandling(async ({ id }) => {
2423
2678
  await client.agents.disconnect(id);
@@ -2428,7 +2683,7 @@ function registerTools(target, client, options = {}) {
2428
2683
  "list_share_links",
2429
2684
  "List live public links to frozen copies of requests (/s/<token>), the account's or one request's. Not the same as list_shares, which lists port previews and live tunnels.",
2430
2685
  {
2431
- requestId: import_zod2.z.string().optional().describe("Only the links to this request")
2686
+ requestId: import_zod.z.string().optional().describe("Only the links to this request")
2432
2687
  },
2433
2688
  withErrorHandling(async ({ requestId }) => {
2434
2689
  const links = requestId ? await client.shareLinks.forRequest(requestId) : await client.shareLinks.list();
@@ -2439,9 +2694,9 @@ function registerTools(target, client, options = {}) {
2439
2694
  "create_share_link",
2440
2695
  "Make a public link to a read-only copy of one HTTP request (not an email), for a teammate or a bug report. Anyone with the link can read it, so the account's owner must approve: this returns a link and a code to show them, then call wait_for_approval for the share link.",
2441
2696
  {
2442
- requestId: import_zod2.z.string().describe("The request's id"),
2443
- expiresInDays: import_zod2.z.union([import_zod2.z.literal(1), import_zod2.z.literal(7), import_zod2.z.literal(30)]).optional().describe("How long the link works: 1, 7 or 30 days (default 7)"),
2444
- hideSensitive: import_zod2.z.boolean().optional().describe(
2697
+ requestId: import_zod.z.string().describe("The request's id"),
2698
+ 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)"),
2699
+ hideSensitive: import_zod.z.boolean().optional().describe(
2445
2700
  "Also leave out signature and token headers (default true). Credentials are always left out."
2446
2701
  )
2447
2702
  },
@@ -2455,14 +2710,14 @@ function registerTools(target, client, options = {}) {
2455
2710
  "revoke_share_link",
2456
2711
  "Revoke a share link by its id (from list_share_links): its page answers 404 from then on.",
2457
2712
  {
2458
- id: import_zod2.z.string().describe("The link's id, from list_share_links")
2713
+ id: import_zod.z.string().describe("The link's id, from list_share_links")
2459
2714
  },
2460
2715
  withErrorHandling(async ({ id }) => {
2461
2716
  await client.shareLinks.revoke(id);
2462
2717
  return textContent(`Revoked the share link ${id}.`);
2463
2718
  })
2464
2719
  );
2465
- const teamIdSchema = import_zod2.z.string().optional().describe("A Team Business team's id (its owner only); leave out for your own account");
2720
+ const teamIdSchema = import_zod.z.string().optional().describe("A Team Business team's id (its owner only); leave out for your own account");
2466
2721
  server.tool(
2467
2722
  "list_domains",
2468
2723
  "List custom inbound domains (Business): each one's status, DNS records to publish, routes and catch-all endpoint.",
@@ -2473,7 +2728,7 @@ function registerTools(target, client, options = {}) {
2473
2728
  "add_domain",
2474
2729
  "Add a custom inbound domain (Business) so mail to it is captured. Returns the TXT and MX records to publish; then call verify_domain.",
2475
2730
  {
2476
- domain: import_zod2.z.string().min(1).max(253).describe("The domain, such as mail.example.com"),
2731
+ domain: import_zod.z.string().min(1).max(253).describe("The domain, such as mail.example.com"),
2477
2732
  teamId: teamIdSchema
2478
2733
  },
2479
2734
  withErrorHandling(
@@ -2484,7 +2739,7 @@ function registerTools(target, client, options = {}) {
2484
2739
  "verify_domain",
2485
2740
  "Look a custom domain's TXT and MX records up now. It receives mail once both are found.",
2486
2741
  {
2487
- id: import_zod2.z.string().describe("The domain's id, from list_domains"),
2742
+ id: import_zod.z.string().describe("The domain's id, from list_domains"),
2488
2743
  teamId: teamIdSchema
2489
2744
  },
2490
2745
  withErrorHandling(
@@ -2495,12 +2750,12 @@ function registerTools(target, client, options = {}) {
2495
2750
  "route_domain",
2496
2751
  "Set where a custom domain's mail goes: routes by local part (the part before @) to endpoints, everything else to a catch-all endpoint, or refused when the catch-all is null. Replaces the domain's routing.",
2497
2752
  {
2498
- id: import_zod2.z.string().describe("The domain's id, from list_domains"),
2499
- catchAllEndpointId: import_zod2.z.string().nullable().describe("The endpoint id for mail no route matches, or null to refuse it"),
2500
- routes: import_zod2.z.array(
2501
- import_zod2.z.object({
2502
- localPart: import_zod2.z.string().max(64).describe("The part before @"),
2503
- endpointId: import_zod2.z.string().describe("The endpoint's id")
2753
+ id: import_zod.z.string().describe("The domain's id, from list_domains"),
2754
+ catchAllEndpointId: import_zod.z.string().nullable().describe("The endpoint id for mail no route matches, or null to refuse it"),
2755
+ routes: import_zod.z.array(
2756
+ import_zod.z.object({
2757
+ localPart: import_zod.z.string().max(64).describe("The part before @"),
2758
+ endpointId: import_zod.z.string().describe("The endpoint's id")
2504
2759
  })
2505
2760
  ).max(100).default([]).describe("Routes by local part"),
2506
2761
  teamId: teamIdSchema
@@ -2513,7 +2768,7 @@ function registerTools(target, client, options = {}) {
2513
2768
  "remove_domain",
2514
2769
  "Remove a custom inbound domain: mail to it is refused from then on. The account's owner must approve it: this returns a link and a code to show them, then call wait_for_approval.",
2515
2770
  {
2516
- id: import_zod2.z.string().describe("The domain's id, from list_domains"),
2771
+ id: import_zod.z.string().describe("The domain's id, from list_domains"),
2517
2772
  teamId: teamIdSchema
2518
2773
  },
2519
2774
  withErrorHandling(async ({ id, teamId }) => {
@@ -2538,7 +2793,7 @@ function registerTools(target, client, options = {}) {
2538
2793
  "pick_spreadsheet",
2539
2794
  "Have the account's owner choose an existing spreadsheet from Google Drive with a connected account (to add rows to it from a Sheets destination). Returns a link and a code to show them; the page opens Google's picker. Then call wait_for_approval for the spreadsheet's id and tabs. To use a new spreadsheet instead, call create_spreadsheet.",
2540
2795
  {
2541
- connectionId: import_zod2.z.string().describe("The Google connection's id, from list_google_connections")
2796
+ connectionId: import_zod.z.string().describe("The Google connection's id, from list_google_connections")
2542
2797
  },
2543
2798
  withErrorHandling(
2544
2799
  async ({ connectionId }) => textContent(approvalAskText(await client.google.pick(connectionId)))
@@ -2548,10 +2803,10 @@ function registerTools(target, client, options = {}) {
2548
2803
  "create_spreadsheet",
2549
2804
  "Create a new spreadsheet with a header row in a connected Google account, for a Sheets destination. Needs no approval: the app reaches only spreadsheets it created or that were chosen for it.",
2550
2805
  {
2551
- connectionId: import_zod2.z.string().describe("The Google connection's id, from list_google_connections"),
2552
- title: import_zod2.z.string().max(200).optional().describe("The spreadsheet's title"),
2553
- sheet: import_zod2.z.string().max(100).optional().describe('The tab rows go to (default "Sheet1")'),
2554
- headers: import_zod2.z.array(import_zod2.z.string().max(200)).max(50).describe("The header row, one per column")
2806
+ connectionId: import_zod.z.string().describe("The Google connection's id, from list_google_connections"),
2807
+ title: import_zod.z.string().max(200).optional().describe("The spreadsheet's title"),
2808
+ sheet: import_zod.z.string().max(100).optional().describe('The tab rows go to (default "Sheet1")'),
2809
+ headers: import_zod.z.array(import_zod.z.string().max(200)).max(50).describe("The header row, one per column")
2555
2810
  },
2556
2811
  withErrorHandling(
2557
2812
  async ({ connectionId, title, sheet, headers }) => jsonContent(await client.google.createSpreadsheet({ connectionId, title, sheet, headers }))
@@ -2561,8 +2816,8 @@ function registerTools(target, client, options = {}) {
2561
2816
  "list_sheet_tabs",
2562
2817
  "List a spreadsheet's tabs (one created by the app or chosen with pick_spreadsheet), to choose the one a Sheets destination writes to.",
2563
2818
  {
2564
- connectionId: import_zod2.z.string().describe("The Google connection's id"),
2565
- spreadsheetId: import_zod2.z.string().describe("The spreadsheet's id")
2819
+ connectionId: import_zod.z.string().describe("The Google connection's id"),
2820
+ spreadsheetId: import_zod.z.string().describe("The spreadsheet's id")
2566
2821
  },
2567
2822
  withErrorHandling(
2568
2823
  async ({ connectionId, spreadsheetId }) => jsonContent({ tabs: await client.google.tabs(connectionId, spreadsheetId) })
@@ -2572,7 +2827,7 @@ function registerTools(target, client, options = {}) {
2572
2827
  "disconnect_google",
2573
2828
  "Disconnect a Google account (id from list_google_connections) and revoke its access at Google. Sheets destinations using it fail until another account is chosen.",
2574
2829
  {
2575
- connectionId: import_zod2.z.string().describe("The Google connection's id")
2830
+ connectionId: import_zod.z.string().describe("The Google connection's id")
2576
2831
  },
2577
2832
  withErrorHandling(async ({ connectionId }) => {
2578
2833
  await client.google.disconnect(connectionId);
@@ -2583,7 +2838,7 @@ function registerTools(target, client, options = {}) {
2583
2838
  "ai_setup_status",
2584
2839
  "Whether AI setup is offered for an endpoint, and how many AI assists the plan has left in a rolling 30 days.",
2585
2840
  {
2586
- slug: import_zod2.z.string().describe("The endpoint's slug")
2841
+ slug: import_zod.z.string().describe("The endpoint's slug")
2587
2842
  },
2588
2843
  withErrorHandling(async ({ slug }) => jsonContent(await client.ai.status(slug)))
2589
2844
  );
@@ -2591,25 +2846,25 @@ function registerTools(target, client, options = {}) {
2591
2846
  "ai_setup",
2592
2847
  "Have AI propose email fields (or a layout, or a repaired field) from 1 to 5 of an endpoint's captured emails. It sends those emails (subject, sender, recipients, text, headers; never attachments) to Anthropic's model, so the account's owner must approve it: this returns a link and a code to show them, and the page lists the emails. Then call wait_for_approval for the proposals, each with the value it found in every email. Nothing is saved: apply the fields with update_endpoint. Uses one AI assist.",
2593
2848
  {
2594
- slug: import_zod2.z.string().describe("The endpoint's slug"),
2595
- requestIds: import_zod2.z.array(import_zod2.z.string()).min(1).max(5).describe("1 to 5 of the endpoint's email request ids, from list_emails"),
2596
- ask: import_zod2.z.union([
2597
- import_zod2.z.object({
2598
- kind: import_zod2.z.literal("field"),
2599
- name: import_zod2.z.string().describe("The field's name: letters, digits and _"),
2600
- description: import_zod2.z.string().describe('What the field is, such as "the order number"')
2849
+ slug: import_zod.z.string().describe("The endpoint's slug"),
2850
+ 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"),
2851
+ ask: import_zod.z.union([
2852
+ import_zod.z.object({
2853
+ kind: import_zod.z.literal("field"),
2854
+ name: import_zod.z.string().describe("The field's name: letters, digits and _"),
2855
+ description: import_zod.z.string().describe('What the field is, such as "the order number"')
2601
2856
  }),
2602
- import_zod2.z.object({
2603
- kind: import_zod2.z.literal("layout"),
2604
- description: import_zod2.z.string().optional().describe("What these emails are")
2857
+ import_zod.z.object({
2858
+ kind: import_zod.z.literal("layout"),
2859
+ description: import_zod.z.string().optional().describe("What these emails are")
2605
2860
  }),
2606
- import_zod2.z.object({
2607
- kind: import_zod2.z.literal("repair"),
2608
- field: import_zod2.z.object({
2609
- name: import_zod2.z.string(),
2610
- from: import_zod2.z.string(),
2611
- header: import_zod2.z.string().optional(),
2612
- pattern: import_zod2.z.string()
2861
+ import_zod.z.object({
2862
+ kind: import_zod.z.literal("repair"),
2863
+ field: import_zod.z.object({
2864
+ name: import_zod.z.string(),
2865
+ from: import_zod.z.string(),
2866
+ header: import_zod.z.string().optional(),
2867
+ pattern: import_zod.z.string()
2613
2868
  }).describe("The field to repair, as the endpoint has it")
2614
2869
  })
2615
2870
  ]).describe("What to propose")
@@ -2629,7 +2884,7 @@ function registerTools(target, client, options = {}) {
2629
2884
  "stop_share",
2630
2885
  "Stop a public preview or live tunnel by its id (from list_shares). The CLI running it is told why and exits.",
2631
2886
  {
2632
- id: import_zod2.z.string().describe("The share's id, from list_shares")
2887
+ id: import_zod.z.string().describe("The share's id, from list_shares")
2633
2888
  },
2634
2889
  withErrorHandling(async ({ id }) => {
2635
2890
  const result = await client.shares.stop(id);
@@ -2645,7 +2900,7 @@ function registerTools(target, client, options = {}) {
2645
2900
  "share_requests",
2646
2901
  "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.",
2647
2902
  {
2648
- id: import_zod2.z.string().describe("The share's id, from list_shares")
2903
+ id: import_zod.z.string().describe("The share's id, from list_shares")
2649
2904
  },
2650
2905
  withErrorHandling(async ({ id }) => {
2651
2906
  return jsonContent(await client.shares.requests(id));
@@ -2663,8 +2918,8 @@ function registerTools(target, client, options = {}) {
2663
2918
  "reserve_name",
2664
2919
  `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.`,
2665
2920
  {
2666
- name: import_zod2.z.string().min(1).max(63).describe("The name: lowercase letters, digits and dashes"),
2667
- team: import_zod2.z.string().optional().describe("A team's id or name (from list_teams), to keep the name inside that team")
2921
+ name: import_zod.z.string().min(1).max(63).describe("The name: lowercase letters, digits and dashes"),
2922
+ team: import_zod.z.string().optional().describe("A team's id or name (from list_teams), to keep the name inside that team")
2668
2923
  },
2669
2924
  withErrorHandling(async ({ name, team }) => {
2670
2925
  return jsonContent(await client.names.reserve(name, team ? { team } : {}));
@@ -2674,7 +2929,7 @@ function registerTools(target, client, options = {}) {
2674
2929
  "release_name",
2675
2930
  "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.",
2676
2931
  {
2677
- name: import_zod2.z.string().min(1).max(63).describe("The name, from list_names")
2932
+ name: import_zod.z.string().min(1).max(63).describe("The name, from list_names")
2678
2933
  },
2679
2934
  withErrorHandling(async ({ name }) => {
2680
2935
  await client.names.release(name);
@@ -2685,8 +2940,8 @@ function registerTools(target, client, options = {}) {
2685
2940
  "set_name_password",
2686
2941
  "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.",
2687
2942
  {
2688
- name: import_zod2.z.string().min(1).max(63).describe("The name, from list_names"),
2689
- password: import_zod2.z.string().min(8).max(200).describe("What visitors must type, 8 to 200 characters")
2943
+ name: import_zod.z.string().min(1).max(63).describe("The name, from list_names"),
2944
+ password: import_zod.z.string().min(8).max(200).describe("What visitors must type, 8 to 200 characters")
2690
2945
  },
2691
2946
  withErrorHandling(async ({ name, password }) => {
2692
2947
  await client.names.setPassword(name, password);
@@ -2697,7 +2952,7 @@ function registerTools(target, client, options = {}) {
2697
2952
  "clear_name_password",
2698
2953
  "Take the password off a reserved name, so its previews open without one.",
2699
2954
  {
2700
- name: import_zod2.z.string().min(1).max(63).describe("The name, from list_names")
2955
+ name: import_zod.z.string().min(1).max(63).describe("The name, from list_names")
2701
2956
  },
2702
2957
  withErrorHandling(async ({ name }) => {
2703
2958
  await client.names.clearPassword(name);
@@ -2716,6 +2971,428 @@ function registerTools(target, client, options = {}) {
2716
2971
  return handles;
2717
2972
  }
2718
2973
 
2974
+ // src/local-tools.ts
2975
+ function asToolServer(server) {
2976
+ return {
2977
+ tool(name, description, shape, handler, annotations) {
2978
+ const registered = server.registerTool(
2979
+ name,
2980
+ {
2981
+ title: annotations?.title,
2982
+ description,
2983
+ inputSchema: import_zod2.z.object(shape),
2984
+ annotations
2985
+ },
2986
+ async (args) => handler(args)
2987
+ );
2988
+ return { remove: () => registered.remove() };
2989
+ }
2990
+ };
2991
+ }
2992
+ var warned = /* @__PURE__ */ new Set();
2993
+ function toolSelection(options = {}) {
2994
+ const fromOption = options.tools !== void 0;
2995
+ const parsed = parseToolGroups(fromOption ? options.tools : process.env.WHK_MCP_TOOLS);
2996
+ const setting = fromOption ? options.tools : process.env.WHK_MCP_TOOLS;
2997
+ if (parsed && parsed.unknown.length > 0 && !warned.has(setting ?? "")) {
2998
+ warned.add(setting ?? "");
2999
+ console.error(
3000
+ `webhooks-cc-mcp: unknown tool groups in ${fromOption ? "tools" : "WHK_MCP_TOOLS"}: ${parsed.unknown.join(", ")} (known: all, ${TOOL_GROUP_NAMES.join(", ")})`
3001
+ );
3002
+ }
3003
+ const readOnly = options.readOnly ?? /^(1|true|yes|on)$/i.test((process.env.WHK_MCP_READ_ONLY ?? "").trim());
3004
+ return { groups: parsed?.groups ?? null, readOnly };
3005
+ }
3006
+ var EXTRA_GROUPS = TOOL_GROUP_NAMES.filter((group2) => group2 !== "core");
3007
+ var ENABLE_TOOLS_ANNOTATIONS = {
3008
+ title: toolTitle(ENABLE_TOOLS),
3009
+ readOnlyHint: false,
3010
+ destructiveHint: false,
3011
+ idempotentHint: true,
3012
+ openWorldHint: false
3013
+ };
3014
+ var LocalToolGroups = class {
3015
+ constructor(server, selection) {
3016
+ this.server = server;
3017
+ this.selection = selection;
3018
+ this.client = null;
3019
+ this.loaded = /* @__PURE__ */ new Set();
3020
+ this.handles = [];
3021
+ this.enableTools = null;
3022
+ this.clientKnown = false;
3023
+ }
3024
+ /** The groups an account's tools start with. */
3025
+ startingGroups() {
3026
+ if (this.selection.groups) return [...this.selection.groups];
3027
+ return this.clientKnown && clientLoadsEverything(this.clientName) ? [...TOOL_GROUP_NAMES] : ["core"];
3028
+ }
3029
+ /** Registers the tools for an account; the handle removes them all again. */
3030
+ show(client) {
3031
+ this.hide();
3032
+ this.client = client;
3033
+ this.load(this.startingGroups());
3034
+ return { remove: () => this.hide() };
3035
+ }
3036
+ /** Removes every account tool, enable_tools included. */
3037
+ hide() {
3038
+ for (const handle of this.handles) handle.remove();
3039
+ this.enableTools?.remove();
3040
+ this.handles = [];
3041
+ this.enableTools = null;
3042
+ this.loaded.clear();
3043
+ this.client = null;
3044
+ }
3045
+ /** The groups loaded now. */
3046
+ loadedGroups() {
3047
+ return TOOL_GROUP_NAMES.filter((group2) => this.loaded.has(group2));
3048
+ }
3049
+ /**
3050
+ * The client named itself (initialize, or a 2026-07-28 request's _meta).
3051
+ * Without a WHK_MCP_TOOLS setting, a client that takes long tool lists
3052
+ * gets every group. Only the first name counts.
3053
+ */
3054
+ clientIdentified(name) {
3055
+ if (this.clientKnown) return;
3056
+ this.clientKnown = true;
3057
+ this.clientName = name;
3058
+ if (this.selection.groups || !clientLoadsEverything(name)) return;
3059
+ if (this.client) this.load(TOOL_GROUP_NAMES);
3060
+ }
3061
+ /** Registers the groups not loaded yet; returns the tools each one added. */
3062
+ load(groups) {
3063
+ const client = this.client;
3064
+ const fresh = groups.filter((group2) => !this.loaded.has(group2));
3065
+ if (!client || fresh.length === 0) return {};
3066
+ const added = {};
3067
+ for (const group2 of fresh) added[group2] = [];
3068
+ const server = this.server;
3069
+ const recorder = {
3070
+ tool(name, description, shape, handler, annotations) {
3071
+ added[TOOL_CATALOG[name].group]?.push(name);
3072
+ return server.tool(name, description, shape, handler, annotations);
3073
+ }
3074
+ };
3075
+ this.handles.push(
3076
+ ...registerTools(recorder, client, {
3077
+ groups: new Set(fresh),
3078
+ readOnly: this.selection.readOnly
3079
+ })
3080
+ );
3081
+ for (const group2 of fresh) this.loaded.add(group2);
3082
+ this.showEnableTools();
3083
+ return added;
3084
+ }
3085
+ /** (Re)registers enable_tools with the current groups, or drops it once all are loaded. */
3086
+ showEnableTools() {
3087
+ this.enableTools?.remove();
3088
+ this.enableTools = null;
3089
+ const left = EXTRA_GROUPS.filter((group2) => !this.loaded.has(group2));
3090
+ if (!this.client || left.length === 0) return;
3091
+ const lines = EXTRA_GROUPS.map(
3092
+ (group2) => `- ${group2}${this.loaded.has(group2) ? " (loaded)" : ""}: ${TOOL_GROUPS[group2]}`
3093
+ );
3094
+ const description = [
3095
+ "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.",
3096
+ "Groups:",
3097
+ ...lines,
3098
+ this.selection.readOnly ? "This server is read-only (WHK_MCP_READ_ONLY): a group brings only its tools that change nothing." : null
3099
+ ].filter(Boolean).join("\n");
3100
+ this.enableTools = this.server.tool(
3101
+ ENABLE_TOOLS,
3102
+ description,
3103
+ {
3104
+ groups: import_zod2.z.array(import_zod2.z.enum([...EXTRA_GROUPS, "all"])).min(1).describe('Groups to load, or ["all"]')
3105
+ },
3106
+ withErrorHandling(async ({ groups }) => {
3107
+ const wanted = groups.includes("all") ? [...TOOL_GROUP_NAMES] : groups;
3108
+ const already = wanted.filter((group2) => group2 !== "core" && this.loaded.has(group2));
3109
+ const added = this.load(wanted);
3110
+ const loadedNow = Object.keys(added).length > 0;
3111
+ return jsonContent({
3112
+ added,
3113
+ ...already.length > 0 ? { alreadyLoaded: [...new Set(already)] } : {},
3114
+ loaded: this.loadedGroups(),
3115
+ ...this.selection.readOnly ? { readOnly: true } : {},
3116
+ 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."
3117
+ });
3118
+ }),
3119
+ ENABLE_TOOLS_ANNOTATIONS
3120
+ );
3121
+ }
3122
+ };
3123
+ function modernClientName(message) {
3124
+ if (typeof message !== "object" || message === null) return null;
3125
+ const params = message.params;
3126
+ const info = params?._meta?.[import_server.CLIENT_INFO_META_KEY];
3127
+ if (typeof info !== "object" || info === null) return null;
3128
+ const name = info.name;
3129
+ return typeof name === "string" ? name : void 0;
3130
+ }
3131
+ function watchClient(server, groups) {
3132
+ const low = server.server;
3133
+ const initialized = low.oninitialized;
3134
+ low.oninitialized = () => {
3135
+ initialized?.();
3136
+ groups.clientIdentified(low.getClientVersion()?.name);
3137
+ };
3138
+ const connect = server.connect.bind(server);
3139
+ server.connect = async (transport) => {
3140
+ const previous = transport.onmessage;
3141
+ transport.onmessage = (message, extra) => {
3142
+ const name = modernClientName(message);
3143
+ if (name !== null) groups.clientIdentified(name);
3144
+ previous?.call(transport, message, extra);
3145
+ };
3146
+ return connect(transport);
3147
+ };
3148
+ }
3149
+ function localToolGroups(server, selection) {
3150
+ const groups = new LocalToolGroups(asToolServer(server), selection);
3151
+ watchClient(server, groups);
3152
+ return groups;
3153
+ }
3154
+
3155
+ // src/prompts.ts
3156
+ var import_zod3 = require("zod");
3157
+ function registerPrompts(server) {
3158
+ server.registerPrompt(
3159
+ "debug_webhook_delivery",
3160
+ {
3161
+ title: "Debug Webhook Delivery",
3162
+ description: "Guide an agent through diagnosing why webhook delivery is failing or missing.",
3163
+ argsSchema: import_zod3.z.object({
3164
+ provider: import_zod3.z.string().optional().describe("Webhook provider, if known"),
3165
+ endpointSlug: import_zod3.z.string().optional().describe("Hosted endpoint slug, if known"),
3166
+ targetUrl: import_zod3.z.string().optional().describe("Your app's receiving URL, if known")
3167
+ })
3168
+ },
3169
+ async ({ provider, endpointSlug, targetUrl }) => {
3170
+ const scope = [
3171
+ provider ? `Provider: ${provider}.` : null,
3172
+ endpointSlug ? `Endpoint slug: ${endpointSlug}.` : null,
3173
+ targetUrl ? `Target URL: ${targetUrl}.` : null
3174
+ ].filter(Boolean).join(" ");
3175
+ return {
3176
+ description: "Diagnose a missing or broken webhook delivery.",
3177
+ messages: [
3178
+ {
3179
+ role: "user",
3180
+ content: {
3181
+ type: "text",
3182
+ text: [
3183
+ scope,
3184
+ "Diagnose webhook delivery step by step.",
3185
+ "Use list_endpoints or get_endpoint to confirm the endpoint and URL.",
3186
+ "Use list_requests, wait_for_request, or wait_for_requests to check whether anything arrived.",
3187
+ "If the provider is known, use preview_webhook or send_webhook to reproduce the webhook with realistic signing.",
3188
+ "Use verify_signature when a secret is available.",
3189
+ "Use compare_requests to compare retries or changed payloads.",
3190
+ "Conclude with the most likely cause and the next concrete fix."
3191
+ ].filter(Boolean).join(" ")
3192
+ }
3193
+ }
3194
+ ]
3195
+ };
3196
+ }
3197
+ );
3198
+ server.registerPrompt(
3199
+ "setup_provider_testing",
3200
+ {
3201
+ title: "Setup Provider Testing",
3202
+ description: "Guide an agent through setting up webhook testing for a provider.",
3203
+ argsSchema: import_zod3.z.object({
3204
+ provider: import_zod3.z.string().describe("Webhook provider to test"),
3205
+ targetUrl: import_zod3.z.string().optional().describe("Optional local or remote target URL")
3206
+ })
3207
+ },
3208
+ async ({ provider, targetUrl }) => {
3209
+ return {
3210
+ description: `Set up webhook testing for ${provider}.`,
3211
+ messages: [
3212
+ {
3213
+ role: "user",
3214
+ content: {
3215
+ type: "text",
3216
+ text: [
3217
+ `Set up webhook testing for ${provider}.`,
3218
+ "Use list_provider_templates to inspect supported templates and signing details first.",
3219
+ "Create an endpoint with create_endpoint, preferably ephemeral.",
3220
+ "If a target URL is provided, use preview_webhook before send_to so the request shape is visible.",
3221
+ targetUrl ? `Target URL: ${targetUrl}.` : null,
3222
+ "Send a realistic provider webhook, wait for capture, and verify the signature if a secret is available.",
3223
+ "Once real deliveries are captured, use generate_payload_schema to write the payload types for the handler.",
3224
+ "Return the endpoint URL, the exact tools used, and the next step for the developer."
3225
+ ].filter(Boolean).join(" ")
3226
+ }
3227
+ }
3228
+ ]
3229
+ };
3230
+ }
3231
+ );
3232
+ server.registerPrompt(
3233
+ "compare_webhook_attempts",
3234
+ {
3235
+ title: "Compare Webhook Attempts",
3236
+ description: "Guide an agent through comparing two webhook deliveries or retries.",
3237
+ argsSchema: import_zod3.z.object({
3238
+ endpointSlug: import_zod3.z.string().optional().describe("Endpoint slug to inspect for recent attempts"),
3239
+ leftRequestId: import_zod3.z.string().optional().describe("First request ID, if already known"),
3240
+ rightRequestId: import_zod3.z.string().optional().describe("Second request ID, if already known")
3241
+ })
3242
+ },
3243
+ async ({ endpointSlug, leftRequestId, rightRequestId }) => {
3244
+ return {
3245
+ description: "Compare two webhook deliveries and explain the difference.",
3246
+ messages: [
3247
+ {
3248
+ role: "user",
3249
+ content: {
3250
+ type: "text",
3251
+ text: [
3252
+ endpointSlug ? `Endpoint slug: ${endpointSlug}.` : null,
3253
+ leftRequestId && rightRequestId ? `Compare request ${leftRequestId} against ${rightRequestId}.` : "Find the most relevant two webhook attempts first.",
3254
+ "Use compare_requests for the structured diff.",
3255
+ "If request IDs are not provided, use list_requests or the endpoint recent resource to find the last two attempts.",
3256
+ "Explain what changed in the body, headers, or timing, and whether the difference looks expected, retried, or broken."
3257
+ ].filter(Boolean).join(" ")
3258
+ }
3259
+ }
3260
+ ]
3261
+ };
3262
+ }
3263
+ );
3264
+ }
3265
+
3266
+ // src/resources.ts
3267
+ var import_server2 = require("@modelcontextprotocol/server");
3268
+ var ENDPOINTS_RESOURCE_URI = "webhooks://endpoints";
3269
+ var ENDPOINT_RECENT_TEMPLATE_URI = "webhooks://endpoint/{slug}/recent";
3270
+ var REQUEST_TEMPLATE_URI = "webhooks://request/{id}";
3271
+ var MAX_ENDPOINT_RESOURCE_ITEMS = 25;
3272
+ var RECENT_REQUEST_LIMIT = 10;
3273
+ function jsonResource(uri, value) {
3274
+ return {
3275
+ contents: [
3276
+ {
3277
+ uri,
3278
+ mimeType: "application/json",
3279
+ text: JSON.stringify(value, null, 2)
3280
+ }
3281
+ ]
3282
+ };
3283
+ }
3284
+ function summarizeRequest(request) {
3285
+ return {
3286
+ id: request.id,
3287
+ method: request.method,
3288
+ path: request.path,
3289
+ receivedAt: request.receivedAt,
3290
+ contentType: request.contentType ?? null
3291
+ };
3292
+ }
3293
+ function variableToString(value, name) {
3294
+ if (typeof value === "string" && value.length > 0) {
3295
+ return value;
3296
+ }
3297
+ throw new Error(`Missing resource variable "${name}"`);
3298
+ }
3299
+ async function listEndpointSummaries(client) {
3300
+ const endpoints = (await client.endpoints.list()).slice().sort((left, right) => right.createdAt - left.createdAt);
3301
+ const truncated = endpoints.length > MAX_ENDPOINT_RESOURCE_ITEMS;
3302
+ const visible = endpoints.slice(0, MAX_ENDPOINT_RESOURCE_ITEMS);
3303
+ const summaries = await Promise.all(
3304
+ visible.map(async (endpoint) => {
3305
+ const recent = await client.requests.list(endpoint.slug, { limit: 1 });
3306
+ return {
3307
+ ...endpoint,
3308
+ lastRequest: recent[0] ? summarizeRequest(recent[0]) : null
3309
+ };
3310
+ })
3311
+ );
3312
+ return {
3313
+ endpoints: summaries,
3314
+ total: endpoints.length,
3315
+ truncated
3316
+ };
3317
+ }
3318
+ function registerResources(server, client) {
3319
+ server.registerResource(
3320
+ "endpoints-overview",
3321
+ ENDPOINTS_RESOURCE_URI,
3322
+ {
3323
+ title: "Endpoints Overview",
3324
+ description: "All endpoints with a summary of recent activity.",
3325
+ mimeType: "application/json"
3326
+ },
3327
+ async () => {
3328
+ return jsonResource(ENDPOINTS_RESOURCE_URI, await listEndpointSummaries(client));
3329
+ }
3330
+ );
3331
+ server.registerResource(
3332
+ "endpoint-recent-requests",
3333
+ new import_server2.ResourceTemplate(ENDPOINT_RECENT_TEMPLATE_URI, {
3334
+ list: async () => {
3335
+ const endpoints = await client.endpoints.list();
3336
+ return {
3337
+ resources: endpoints.map((endpoint) => ({
3338
+ uri: `webhooks://endpoint/${endpoint.slug}/recent`,
3339
+ name: `${endpoint.slug} recent requests`,
3340
+ description: `Recent requests for endpoint ${endpoint.slug}`,
3341
+ mimeType: "application/json"
3342
+ }))
3343
+ };
3344
+ },
3345
+ complete: {
3346
+ slug: async (value) => {
3347
+ const endpoints = await client.endpoints.list();
3348
+ return endpoints.map((endpoint) => endpoint.slug).filter((slug) => slug.startsWith(value)).slice(0, 20);
3349
+ }
3350
+ }
3351
+ }),
3352
+ {
3353
+ title: "Endpoint Recent Requests",
3354
+ description: "Last 10 captured requests for an endpoint.",
3355
+ mimeType: "application/json"
3356
+ },
3357
+ async (uri, variables) => {
3358
+ const slug = variableToString(variables.slug, "slug");
3359
+ const [endpoint, requests] = await Promise.all([
3360
+ client.endpoints.get(slug),
3361
+ client.requests.list(slug, { limit: RECENT_REQUEST_LIMIT })
3362
+ ]);
3363
+ return jsonResource(uri.toString(), {
3364
+ endpoint,
3365
+ requests: requests.map(compactRequest)
3366
+ });
3367
+ }
3368
+ );
3369
+ server.registerResource(
3370
+ "request-details",
3371
+ new import_server2.ResourceTemplate(REQUEST_TEMPLATE_URI, {
3372
+ list: void 0,
3373
+ complete: {}
3374
+ }),
3375
+ {
3376
+ title: "Request Details",
3377
+ description: "Full details for a captured request by ID.",
3378
+ mimeType: "application/json"
3379
+ },
3380
+ async (uri, variables) => {
3381
+ const id = variableToString(variables.id, "id");
3382
+ const request = await client.requests.get(id);
3383
+ return jsonResource(uri.toString(), compactRequest(request));
3384
+ }
3385
+ );
3386
+ }
3387
+
3388
+ // src/sandbox-tools.ts
3389
+ var import_zod4 = require("zod");
3390
+ var import_sdk2 = require("@webhooks-cc/sdk");
3391
+
3392
+ // src/version.ts
3393
+ var VERSION = true ? "2.0.0" : "0.0.0-dev";
3394
+ var MCP_USER_AGENT = `webhooks-cc-mcp/${VERSION}`;
3395
+
2719
3396
  // src/sandbox-tools.ts
2720
3397
  var SANDBOX = {
2721
3398
  maxEndpoints: 3,
@@ -2830,6 +3507,8 @@ function registerSandboxTools(server, options = {}) {
2830
3507
  options.clientName ?? "webhooks-cc-mcp",
2831
3508
  options.credentialsFile
2832
3509
  );
3510
+ const target = asToolServer(server);
3511
+ const groups = options.toolGroups ?? localToolGroups(server, toolSelection(options));
2833
3512
  let handles = [];
2834
3513
  let shown = 0;
2835
3514
  const swap = (register) => {
@@ -2837,7 +3516,9 @@ function registerSandboxTools(server, options = {}) {
2837
3516
  for (const handle of handles) handle.remove();
2838
3517
  handles = register(id);
2839
3518
  };
2840
- const showSandbox = () => swap(() => sandboxToolSet(server, session, showAccount));
3519
+ const showSandbox = () => swap(
3520
+ () => sandboxToolSet(sandboxTarget(target, groups.selection.readOnly), session, showAccount, groups)
3521
+ );
2841
3522
  const savedAccount = (id) => options.credentialsFile === false ? null : import_sdk2.WebhooksCC.fromSavedConnection({
2842
3523
  baseUrl,
2843
3524
  webhookUrl,
@@ -2850,18 +3531,54 @@ function registerSandboxTools(server, options = {}) {
2850
3531
  })
2851
3532
  });
2852
3533
  function showAccount(client) {
2853
- swap((id) => registerTools(server, savedAccount(id) ?? client));
3534
+ swap((id) => [groups.show(savedAccount(id) ?? client)]);
2854
3535
  options.onConnected?.(client);
2855
3536
  }
2856
3537
  const saved = savedAccount(1);
2857
3538
  if (saved) {
2858
- swap(() => registerTools(server, saved));
3539
+ swap(() => [groups.show(saved)]);
2859
3540
  options.onConnected?.(saved);
2860
3541
  } else {
2861
3542
  showSandbox();
2862
3543
  }
2863
3544
  }
2864
- function sandboxToolSet(server, session, showAccount) {
3545
+ var SANDBOX_ONLY_TOOLS = {
3546
+ // A new code each time.
3547
+ connect_account: {
3548
+ readOnlyHint: false,
3549
+ destructiveHint: false,
3550
+ idempotentHint: false,
3551
+ openWorldHint: false
3552
+ },
3553
+ // Changes this session's tools once the human has connected.
3554
+ wait_for_connection: {
3555
+ readOnlyHint: false,
3556
+ destructiveHint: false,
3557
+ idempotentHint: true,
3558
+ openWorldHint: false
3559
+ },
3560
+ about_sandbox: {
3561
+ readOnlyHint: true,
3562
+ destructiveHint: false,
3563
+ idempotentHint: true,
3564
+ openWorldHint: false
3565
+ }
3566
+ };
3567
+ var CONNECT_TOOLS = /* @__PURE__ */ new Set(["connect_account", "wait_for_connection"]);
3568
+ function sandboxTarget(target, readOnly) {
3569
+ return {
3570
+ tool(name, description, shape, handler) {
3571
+ const own = SANDBOX_ONLY_TOOLS[name];
3572
+ const annotations = own ? { title: toolTitle(name), ...own } : toolAnnotations(name);
3573
+ if (readOnly && !annotations.readOnlyHint && !CONNECT_TOOLS.has(name)) {
3574
+ return { remove() {
3575
+ } };
3576
+ }
3577
+ return target.tool(name, description, shape, handler, annotations);
3578
+ }
3579
+ };
3580
+ }
3581
+ function sandboxToolSet(server, session, showAccount, groups) {
2865
3582
  const tools = [];
2866
3583
  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`;
2867
3584
  const file = savedTo(session.credentialsFile);
@@ -2976,7 +3693,7 @@ function sandboxToolSet(server, session, showAccount) {
2976
3693
  "connect_account",
2977
3694
  "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.",
2978
3695
  {
2979
- email: import_zod3.z.string().email({ message: "Invalid email address" }).describe(
3696
+ email: import_zod4.z.string().email({ message: "Invalid email address" }).describe(
2980
3697
  "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"
2981
3698
  )
2982
3699
  },
@@ -3029,7 +3746,7 @@ function sandboxToolSet(server, session, showAccount) {
3029
3746
  "wait_for_connection",
3030
3747
  `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.`,
3031
3748
  {
3032
- timeoutSeconds: import_zod3.z.number().int().min(1).max(MAX_WAIT_SECONDS).optional().describe(`How long to wait, in seconds (default ${DEFAULT_WAIT_SECONDS})`)
3749
+ timeoutSeconds: import_zod4.z.number().int().min(1).max(MAX_WAIT_SECONDS).optional().describe(`How long to wait, in seconds (default ${DEFAULT_WAIT_SECONDS})`)
3033
3750
  },
3034
3751
  withErrorHandling(async ({ timeoutSeconds }) => {
3035
3752
  const pending = session.connect;
@@ -3072,7 +3789,7 @@ function sandboxToolSet(server, session, showAccount) {
3072
3789
  return jsonContent({
3073
3790
  connected: true,
3074
3791
  email: pending.email,
3075
- 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."}`
3792
+ 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."}`
3076
3793
  });
3077
3794
  })
3078
3795
  )
@@ -3114,33 +3831,57 @@ function registerAgentRegistrationTools(server) {
3114
3831
  }
3115
3832
 
3116
3833
  // src/index.ts
3834
+ 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).";
3117
3835
  function createServer(options = {}) {
3118
3836
  const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
3119
3837
  const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
3120
3838
  const baseUrl = options.baseUrl ?? process.env.WHK_BASE_URL;
3121
- const server = new import_mcp2.McpServer(
3122
- { name: "webhooks-cc", version: VERSION },
3123
- // Connecting an account swaps every tool at once: one list_changed, not dozens.
3124
- { debouncedNotificationMethods: ["notifications/tools/list_changed"] }
3839
+ const server = new import_server3.McpServer(
3840
+ { name: "webhooks-cc", title: "webhooks.cc", version: VERSION },
3841
+ {
3842
+ capabilities: { tools: { listChanged: true } },
3843
+ instructions: INSTRUCTIONS,
3844
+ // Connecting an account or loading groups changes many tools at once:
3845
+ // one list_changed, not dozens.
3846
+ debouncedNotificationMethods: ["notifications/tools/list_changed"]
3847
+ }
3125
3848
  );
3849
+ const groups = localToolGroups(server, toolSelection(options));
3126
3850
  if (!apiKey) {
3127
- registerSandboxTools(server, { baseUrl, webhookUrl, credentialsFile: options.credentialsFile });
3851
+ registerSandboxTools(server, {
3852
+ baseUrl,
3853
+ webhookUrl,
3854
+ credentialsFile: options.credentialsFile,
3855
+ toolGroups: groups
3856
+ });
3128
3857
  registerPrompts(server);
3129
3858
  return server;
3130
3859
  }
3131
3860
  const client = new import_sdk3.WebhooksCC({ apiKey, webhookUrl, baseUrl, userAgent: MCP_USER_AGENT });
3132
- registerTools(server, client);
3861
+ groups.show(client);
3133
3862
  registerPrompts(server);
3134
3863
  registerResources(server, client);
3135
3864
  return server;
3136
3865
  }
3137
3866
  // Annotate the CommonJS export names for ESM import in node:
3138
3867
  0 && (module.exports = {
3868
+ ENABLE_TOOLS,
3869
+ LocalToolGroups,
3139
3870
  REMOTE_MAX_WAIT_MS,
3871
+ TOOL_CATALOG,
3872
+ TOOL_GROUPS,
3873
+ TOOL_GROUP_NAMES,
3874
+ asToolServer,
3875
+ clientLoadsEverything,
3140
3876
  createServer,
3877
+ defaultToolGroups,
3878
+ localToolGroups,
3879
+ parseToolGroups,
3141
3880
  registerAgentRegistrationTools,
3142
3881
  registerPrompts,
3143
3882
  registerResources,
3144
3883
  registerSandboxTools,
3145
- registerTools
3884
+ registerTools,
3885
+ toolAnnotations,
3886
+ toolSelection
3146
3887
  });