@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.mjs CHANGED
@@ -1,37 +1,229 @@
1
1
  import {
2
+ ENABLE_TOOLS,
2
3
  REMOTE_MAX_WAIT_MS,
4
+ TOOL_CATALOG,
5
+ TOOL_GROUPS,
6
+ TOOL_GROUP_NAMES,
7
+ clientLoadsEverything,
3
8
  compactRequest,
4
9
  createEndpointShape,
10
+ defaultToolGroups,
5
11
  deleteEndpointShape,
6
12
  getEndpointShape,
7
13
  getRequestShape,
8
14
  jsonContent,
9
15
  listEndpointsShape,
10
16
  listRequestsShape,
17
+ parseToolGroups,
11
18
  payloadSchemaResult,
12
19
  payloadSchemaShape,
13
20
  registerTools,
21
+ toolAnnotations,
22
+ toolTitle,
14
23
  waitForRequestShape,
15
24
  withErrorHandling
16
- } from "./chunk-TMX4P6ZZ.mjs";
25
+ } from "./chunk-VANGXSK7.mjs";
17
26
 
18
27
  // src/index.ts
19
- import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
28
+ import { McpServer } from "@modelcontextprotocol/server";
20
29
  import { WebhooksCC as WebhooksCC2 } from "@webhooks-cc/sdk";
21
30
 
22
- // src/prompts.ts
31
+ // src/local-tools.ts
32
+ import { CLIENT_INFO_META_KEY } from "@modelcontextprotocol/server";
23
33
  import { z } from "zod";
34
+ function asToolServer(server) {
35
+ return {
36
+ tool(name, description, shape, handler, annotations) {
37
+ const registered = server.registerTool(
38
+ name,
39
+ {
40
+ title: annotations?.title,
41
+ description,
42
+ inputSchema: z.object(shape),
43
+ annotations
44
+ },
45
+ async (args) => handler(args)
46
+ );
47
+ return { remove: () => registered.remove() };
48
+ }
49
+ };
50
+ }
51
+ var warned = /* @__PURE__ */ new Set();
52
+ function toolSelection(options = {}) {
53
+ const fromOption = options.tools !== void 0;
54
+ const parsed = parseToolGroups(fromOption ? options.tools : process.env.WHK_MCP_TOOLS);
55
+ const setting = fromOption ? options.tools : process.env.WHK_MCP_TOOLS;
56
+ if (parsed && parsed.unknown.length > 0 && !warned.has(setting ?? "")) {
57
+ warned.add(setting ?? "");
58
+ console.error(
59
+ `webhooks-cc-mcp: unknown tool groups in ${fromOption ? "tools" : "WHK_MCP_TOOLS"}: ${parsed.unknown.join(", ")} (known: all, ${TOOL_GROUP_NAMES.join(", ")})`
60
+ );
61
+ }
62
+ const readOnly = options.readOnly ?? /^(1|true|yes|on)$/i.test((process.env.WHK_MCP_READ_ONLY ?? "").trim());
63
+ return { groups: parsed?.groups ?? null, readOnly };
64
+ }
65
+ var EXTRA_GROUPS = TOOL_GROUP_NAMES.filter((group) => group !== "core");
66
+ var ENABLE_TOOLS_ANNOTATIONS = {
67
+ title: toolTitle(ENABLE_TOOLS),
68
+ readOnlyHint: false,
69
+ destructiveHint: false,
70
+ idempotentHint: true,
71
+ openWorldHint: false
72
+ };
73
+ var LocalToolGroups = class {
74
+ constructor(server, selection) {
75
+ this.server = server;
76
+ this.selection = selection;
77
+ this.client = null;
78
+ this.loaded = /* @__PURE__ */ new Set();
79
+ this.handles = [];
80
+ this.enableTools = null;
81
+ this.clientKnown = false;
82
+ }
83
+ /** The groups an account's tools start with. */
84
+ startingGroups() {
85
+ if (this.selection.groups) return [...this.selection.groups];
86
+ return this.clientKnown && clientLoadsEverything(this.clientName) ? [...TOOL_GROUP_NAMES] : ["core"];
87
+ }
88
+ /** Registers the tools for an account; the handle removes them all again. */
89
+ show(client) {
90
+ this.hide();
91
+ this.client = client;
92
+ this.load(this.startingGroups());
93
+ return { remove: () => this.hide() };
94
+ }
95
+ /** Removes every account tool, enable_tools included. */
96
+ hide() {
97
+ for (const handle of this.handles) handle.remove();
98
+ this.enableTools?.remove();
99
+ this.handles = [];
100
+ this.enableTools = null;
101
+ this.loaded.clear();
102
+ this.client = null;
103
+ }
104
+ /** The groups loaded now. */
105
+ loadedGroups() {
106
+ return TOOL_GROUP_NAMES.filter((group) => this.loaded.has(group));
107
+ }
108
+ /**
109
+ * The client named itself (initialize, or a 2026-07-28 request's _meta).
110
+ * Without a WHK_MCP_TOOLS setting, a client that takes long tool lists
111
+ * gets every group. Only the first name counts.
112
+ */
113
+ clientIdentified(name) {
114
+ if (this.clientKnown) return;
115
+ this.clientKnown = true;
116
+ this.clientName = name;
117
+ if (this.selection.groups || !clientLoadsEverything(name)) return;
118
+ if (this.client) this.load(TOOL_GROUP_NAMES);
119
+ }
120
+ /** Registers the groups not loaded yet; returns the tools each one added. */
121
+ load(groups) {
122
+ const client = this.client;
123
+ const fresh = groups.filter((group) => !this.loaded.has(group));
124
+ if (!client || fresh.length === 0) return {};
125
+ const added = {};
126
+ for (const group of fresh) added[group] = [];
127
+ const server = this.server;
128
+ const recorder = {
129
+ tool(name, description, shape, handler, annotations) {
130
+ added[TOOL_CATALOG[name].group]?.push(name);
131
+ return server.tool(name, description, shape, handler, annotations);
132
+ }
133
+ };
134
+ this.handles.push(
135
+ ...registerTools(recorder, client, {
136
+ groups: new Set(fresh),
137
+ readOnly: this.selection.readOnly
138
+ })
139
+ );
140
+ for (const group of fresh) this.loaded.add(group);
141
+ this.showEnableTools();
142
+ return added;
143
+ }
144
+ /** (Re)registers enable_tools with the current groups, or drops it once all are loaded. */
145
+ showEnableTools() {
146
+ this.enableTools?.remove();
147
+ this.enableTools = null;
148
+ const left = EXTRA_GROUPS.filter((group) => !this.loaded.has(group));
149
+ if (!this.client || left.length === 0) return;
150
+ const lines = EXTRA_GROUPS.map(
151
+ (group) => `- ${group}${this.loaded.has(group) ? " (loaded)" : ""}: ${TOOL_GROUPS[group]}`
152
+ );
153
+ const description = [
154
+ "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.",
155
+ "Groups:",
156
+ ...lines,
157
+ this.selection.readOnly ? "This server is read-only (WHK_MCP_READ_ONLY): a group brings only its tools that change nothing." : null
158
+ ].filter(Boolean).join("\n");
159
+ this.enableTools = this.server.tool(
160
+ ENABLE_TOOLS,
161
+ description,
162
+ {
163
+ groups: z.array(z.enum([...EXTRA_GROUPS, "all"])).min(1).describe('Groups to load, or ["all"]')
164
+ },
165
+ withErrorHandling(async ({ groups }) => {
166
+ const wanted = groups.includes("all") ? [...TOOL_GROUP_NAMES] : groups;
167
+ const already = wanted.filter((group) => group !== "core" && this.loaded.has(group));
168
+ const added = this.load(wanted);
169
+ const loadedNow = Object.keys(added).length > 0;
170
+ return jsonContent({
171
+ added,
172
+ ...already.length > 0 ? { alreadyLoaded: [...new Set(already)] } : {},
173
+ loaded: this.loadedGroups(),
174
+ ...this.selection.readOnly ? { readOnly: true } : {},
175
+ 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."
176
+ });
177
+ }),
178
+ ENABLE_TOOLS_ANNOTATIONS
179
+ );
180
+ }
181
+ };
182
+ function modernClientName(message) {
183
+ if (typeof message !== "object" || message === null) return null;
184
+ const params = message.params;
185
+ const info = params?._meta?.[CLIENT_INFO_META_KEY];
186
+ if (typeof info !== "object" || info === null) return null;
187
+ const name = info.name;
188
+ return typeof name === "string" ? name : void 0;
189
+ }
190
+ function watchClient(server, groups) {
191
+ const low = server.server;
192
+ const initialized = low.oninitialized;
193
+ low.oninitialized = () => {
194
+ initialized?.();
195
+ groups.clientIdentified(low.getClientVersion()?.name);
196
+ };
197
+ const connect = server.connect.bind(server);
198
+ server.connect = async (transport) => {
199
+ const previous = transport.onmessage;
200
+ transport.onmessage = (message, extra) => {
201
+ const name = modernClientName(message);
202
+ if (name !== null) groups.clientIdentified(name);
203
+ previous?.call(transport, message, extra);
204
+ };
205
+ return connect(transport);
206
+ };
207
+ }
208
+ function localToolGroups(server, selection) {
209
+ const groups = new LocalToolGroups(asToolServer(server), selection);
210
+ watchClient(server, groups);
211
+ return groups;
212
+ }
213
+
214
+ // src/prompts.ts
215
+ import { z as z2 } from "zod";
24
216
  function registerPrompts(server) {
25
217
  server.registerPrompt(
26
218
  "debug_webhook_delivery",
27
219
  {
28
220
  title: "Debug Webhook Delivery",
29
221
  description: "Guide an agent through diagnosing why webhook delivery is failing or missing.",
30
- argsSchema: {
31
- provider: z.string().optional().describe("Webhook provider, if known"),
32
- endpointSlug: z.string().optional().describe("Hosted endpoint slug, if known"),
33
- targetUrl: z.string().optional().describe("Your app's receiving URL, if known")
34
- }
222
+ argsSchema: z2.object({
223
+ provider: z2.string().optional().describe("Webhook provider, if known"),
224
+ endpointSlug: z2.string().optional().describe("Hosted endpoint slug, if known"),
225
+ targetUrl: z2.string().optional().describe("Your app's receiving URL, if known")
226
+ })
35
227
  },
36
228
  async ({ provider, endpointSlug, targetUrl }) => {
37
229
  const scope = [
@@ -67,10 +259,10 @@ function registerPrompts(server) {
67
259
  {
68
260
  title: "Setup Provider Testing",
69
261
  description: "Guide an agent through setting up webhook testing for a provider.",
70
- argsSchema: {
71
- provider: z.string().describe("Webhook provider to test"),
72
- targetUrl: z.string().optional().describe("Optional local or remote target URL")
73
- }
262
+ argsSchema: z2.object({
263
+ provider: z2.string().describe("Webhook provider to test"),
264
+ targetUrl: z2.string().optional().describe("Optional local or remote target URL")
265
+ })
74
266
  },
75
267
  async ({ provider, targetUrl }) => {
76
268
  return {
@@ -101,11 +293,11 @@ function registerPrompts(server) {
101
293
  {
102
294
  title: "Compare Webhook Attempts",
103
295
  description: "Guide an agent through comparing two webhook deliveries or retries.",
104
- argsSchema: {
105
- endpointSlug: z.string().optional().describe("Endpoint slug to inspect for recent attempts"),
106
- leftRequestId: z.string().optional().describe("First request ID, if already known"),
107
- rightRequestId: z.string().optional().describe("Second request ID, if already known")
108
- }
296
+ argsSchema: z2.object({
297
+ endpointSlug: z2.string().optional().describe("Endpoint slug to inspect for recent attempts"),
298
+ leftRequestId: z2.string().optional().describe("First request ID, if already known"),
299
+ rightRequestId: z2.string().optional().describe("Second request ID, if already known")
300
+ })
109
301
  },
110
302
  async ({ endpointSlug, leftRequestId, rightRequestId }) => {
111
303
  return {
@@ -131,7 +323,7 @@ function registerPrompts(server) {
131
323
  }
132
324
 
133
325
  // src/resources.ts
134
- import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
326
+ import { ResourceTemplate } from "@modelcontextprotocol/server";
135
327
  var ENDPOINTS_RESOURCE_URI = "webhooks://endpoints";
136
328
  var ENDPOINT_RECENT_TEMPLATE_URI = "webhooks://endpoint/{slug}/recent";
137
329
  var REQUEST_TEMPLATE_URI = "webhooks://request/{id}";
@@ -253,7 +445,7 @@ function registerResources(server, client) {
253
445
  }
254
446
 
255
447
  // src/sandbox-tools.ts
256
- import { z as z2 } from "zod";
448
+ import { z as z3 } from "zod";
257
449
  import {
258
450
  AgentAuthError,
259
451
  AgentCredentialStore,
@@ -262,7 +454,7 @@ import {
262
454
  } from "@webhooks-cc/sdk";
263
455
 
264
456
  // src/version.ts
265
- var VERSION = true ? "1.23.0" : "0.0.0-dev";
457
+ var VERSION = true ? "2.0.0" : "0.0.0-dev";
266
458
  var MCP_USER_AGENT = `webhooks-cc-mcp/${VERSION}`;
267
459
 
268
460
  // src/sandbox-tools.ts
@@ -379,6 +571,8 @@ function registerSandboxTools(server, options = {}) {
379
571
  options.clientName ?? "webhooks-cc-mcp",
380
572
  options.credentialsFile
381
573
  );
574
+ const target = asToolServer(server);
575
+ const groups = options.toolGroups ?? localToolGroups(server, toolSelection(options));
382
576
  let handles = [];
383
577
  let shown = 0;
384
578
  const swap = (register) => {
@@ -386,7 +580,9 @@ function registerSandboxTools(server, options = {}) {
386
580
  for (const handle of handles) handle.remove();
387
581
  handles = register(id);
388
582
  };
389
- const showSandbox = () => swap(() => sandboxToolSet(server, session, showAccount));
583
+ const showSandbox = () => swap(
584
+ () => sandboxToolSet(sandboxTarget(target, groups.selection.readOnly), session, showAccount, groups)
585
+ );
390
586
  const savedAccount = (id) => options.credentialsFile === false ? null : WebhooksCC.fromSavedConnection({
391
587
  baseUrl,
392
588
  webhookUrl,
@@ -399,18 +595,54 @@ function registerSandboxTools(server, options = {}) {
399
595
  })
400
596
  });
401
597
  function showAccount(client) {
402
- swap((id) => registerTools(server, savedAccount(id) ?? client));
598
+ swap((id) => [groups.show(savedAccount(id) ?? client)]);
403
599
  options.onConnected?.(client);
404
600
  }
405
601
  const saved = savedAccount(1);
406
602
  if (saved) {
407
- swap(() => registerTools(server, saved));
603
+ swap(() => [groups.show(saved)]);
408
604
  options.onConnected?.(saved);
409
605
  } else {
410
606
  showSandbox();
411
607
  }
412
608
  }
413
- function sandboxToolSet(server, session, showAccount) {
609
+ var SANDBOX_ONLY_TOOLS = {
610
+ // A new code each time.
611
+ connect_account: {
612
+ readOnlyHint: false,
613
+ destructiveHint: false,
614
+ idempotentHint: false,
615
+ openWorldHint: false
616
+ },
617
+ // Changes this session's tools once the human has connected.
618
+ wait_for_connection: {
619
+ readOnlyHint: false,
620
+ destructiveHint: false,
621
+ idempotentHint: true,
622
+ openWorldHint: false
623
+ },
624
+ about_sandbox: {
625
+ readOnlyHint: true,
626
+ destructiveHint: false,
627
+ idempotentHint: true,
628
+ openWorldHint: false
629
+ }
630
+ };
631
+ var CONNECT_TOOLS = /* @__PURE__ */ new Set(["connect_account", "wait_for_connection"]);
632
+ function sandboxTarget(target, readOnly) {
633
+ return {
634
+ tool(name, description, shape, handler) {
635
+ const own = SANDBOX_ONLY_TOOLS[name];
636
+ const annotations = own ? { title: toolTitle(name), ...own } : toolAnnotations(name);
637
+ if (readOnly && !annotations.readOnlyHint && !CONNECT_TOOLS.has(name)) {
638
+ return { remove() {
639
+ } };
640
+ }
641
+ return target.tool(name, description, shape, handler, annotations);
642
+ }
643
+ };
644
+ }
645
+ function sandboxToolSet(server, session, showAccount, groups) {
414
646
  const tools = [];
415
647
  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`;
416
648
  const file = savedTo(session.credentialsFile);
@@ -525,7 +757,7 @@ function sandboxToolSet(server, session, showAccount) {
525
757
  "connect_account",
526
758
  "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.",
527
759
  {
528
- email: z2.string().email({ message: "Invalid email address" }).describe(
760
+ email: z3.string().email({ message: "Invalid email address" }).describe(
529
761
  "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"
530
762
  )
531
763
  },
@@ -578,7 +810,7 @@ function sandboxToolSet(server, session, showAccount) {
578
810
  "wait_for_connection",
579
811
  `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.`,
580
812
  {
581
- timeoutSeconds: z2.number().int().min(1).max(MAX_WAIT_SECONDS).optional().describe(`How long to wait, in seconds (default ${DEFAULT_WAIT_SECONDS})`)
813
+ timeoutSeconds: z3.number().int().min(1).max(MAX_WAIT_SECONDS).optional().describe(`How long to wait, in seconds (default ${DEFAULT_WAIT_SECONDS})`)
582
814
  },
583
815
  withErrorHandling(async ({ timeoutSeconds }) => {
584
816
  const pending = session.connect;
@@ -621,7 +853,7 @@ function sandboxToolSet(server, session, showAccount) {
621
853
  return jsonContent({
622
854
  connected: true,
623
855
  email: pending.email,
624
- 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."}`
856
+ 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."}`
625
857
  });
626
858
  })
627
859
  )
@@ -663,32 +895,56 @@ function registerAgentRegistrationTools(server) {
663
895
  }
664
896
 
665
897
  // src/index.ts
898
+ 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).";
666
899
  function createServer(options = {}) {
667
900
  const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
668
901
  const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
669
902
  const baseUrl = options.baseUrl ?? process.env.WHK_BASE_URL;
670
- const server = new McpServer2(
671
- { name: "webhooks-cc", version: VERSION },
672
- // Connecting an account swaps every tool at once: one list_changed, not dozens.
673
- { debouncedNotificationMethods: ["notifications/tools/list_changed"] }
903
+ const server = new McpServer(
904
+ { name: "webhooks-cc", title: "webhooks.cc", version: VERSION },
905
+ {
906
+ capabilities: { tools: { listChanged: true } },
907
+ instructions: INSTRUCTIONS,
908
+ // Connecting an account or loading groups changes many tools at once:
909
+ // one list_changed, not dozens.
910
+ debouncedNotificationMethods: ["notifications/tools/list_changed"]
911
+ }
674
912
  );
913
+ const groups = localToolGroups(server, toolSelection(options));
675
914
  if (!apiKey) {
676
- registerSandboxTools(server, { baseUrl, webhookUrl, credentialsFile: options.credentialsFile });
915
+ registerSandboxTools(server, {
916
+ baseUrl,
917
+ webhookUrl,
918
+ credentialsFile: options.credentialsFile,
919
+ toolGroups: groups
920
+ });
677
921
  registerPrompts(server);
678
922
  return server;
679
923
  }
680
924
  const client = new WebhooksCC2({ apiKey, webhookUrl, baseUrl, userAgent: MCP_USER_AGENT });
681
- registerTools(server, client);
925
+ groups.show(client);
682
926
  registerPrompts(server);
683
927
  registerResources(server, client);
684
928
  return server;
685
929
  }
686
930
  export {
931
+ ENABLE_TOOLS,
932
+ LocalToolGroups,
687
933
  REMOTE_MAX_WAIT_MS,
934
+ TOOL_CATALOG,
935
+ TOOL_GROUPS,
936
+ TOOL_GROUP_NAMES,
937
+ asToolServer,
938
+ clientLoadsEverything,
688
939
  createServer,
940
+ defaultToolGroups,
941
+ localToolGroups,
942
+ parseToolGroups,
689
943
  registerAgentRegistrationTools,
690
944
  registerPrompts,
691
945
  registerResources,
692
946
  registerSandboxTools,
693
- registerTools
947
+ registerTools,
948
+ toolAnnotations,
949
+ toolSelection
694
950
  };