@webhooks-cc/mcp 1.14.0 → 1.15.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
@@ -18,8 +18,9 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
 
20
20
  // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ REMOTE_MAX_WAIT_MS: () => REMOTE_MAX_WAIT_MS,
23
24
  createServer: () => createServer,
24
25
  registerAgentRegistrationTools: () => registerAgentRegistrationTools,
25
26
  registerPrompts: () => registerPrompts,
@@ -27,7 +28,7 @@ __export(index_exports, {
27
28
  registerSandboxTools: () => registerSandboxTools,
28
29
  registerTools: () => registerTools
29
30
  });
30
- module.exports = __toCommonJS(index_exports);
31
+ module.exports = __toCommonJS(src_exports);
31
32
  var import_mcp2 = require("@modelcontextprotocol/sdk/server/mcp.js");
32
33
  var import_sdk3 = require("@webhooks-cc/sdk");
33
34
 
@@ -777,7 +778,9 @@ async function waitForMultipleRequests(client, endpointSlug, options) {
777
778
  };
778
779
  }
779
780
  }
780
- await sleep(Math.max(10, pollIntervalMs));
781
+ await sleep(
782
+ Math.min(Math.max(10, pollIntervalMs), Math.max(0, timeoutMs - (Date.now() - startedAt)))
783
+ );
781
784
  }
782
785
  return {
783
786
  requests,
@@ -935,7 +938,67 @@ function emailDetail(client, email, view, includeHtml) {
935
938
  ]);
936
939
  return detail;
937
940
  }
938
- function registerTools(server, client) {
941
+ var LOCAL_ONLY_TOOLS = /* @__PURE__ */ new Set(["send_to", "replay_request"]);
942
+ var TARGET_URL_TOOLS = /* @__PURE__ */ new Set(["render_email", "test_webhook_flow"]);
943
+ var REMOTE_MAX_WAIT_MS = 5e4;
944
+ var REMOTE_MIN_POLL_MS = 1e3;
945
+ function durationMs(value) {
946
+ if (typeof value === "number") return Number.isFinite(value) && value >= 0 ? value : null;
947
+ if (typeof value !== "string") return null;
948
+ try {
949
+ const ms = (0, import_sdk.parseDuration)(value);
950
+ return Number.isFinite(ms) && ms >= 0 ? ms : null;
951
+ } catch {
952
+ return null;
953
+ }
954
+ }
955
+ function toolTarget(target, remote, handles) {
956
+ return {
957
+ tool(name, description, shape, handler) {
958
+ if (remote && LOCAL_ONLY_TOOLS.has(name)) return { remove() {
959
+ } };
960
+ const wrapped = remote ? async (args) => {
961
+ const input = args;
962
+ if (TARGET_URL_TOOLS.has(name) && input.targetUrl !== void 0) {
963
+ return {
964
+ ...textContent(
965
+ "targetUrl needs the local MCP server (npx -y @webhooks-cc/mcp): the hosted server cannot reach your machine. Leave it out here."
966
+ ),
967
+ isError: true
968
+ };
969
+ }
970
+ const bounded = { ...input };
971
+ if ("pollInterval" in shape && input.pollInterval === void 0) {
972
+ bounded.pollInterval = REMOTE_MIN_POLL_MS;
973
+ }
974
+ for (const [key, bound] of [
975
+ ["timeout", (ms) => Math.min(ms, REMOTE_MAX_WAIT_MS)],
976
+ [
977
+ "pollInterval",
978
+ (ms) => Math.min(Math.max(ms, REMOTE_MIN_POLL_MS), REMOTE_MAX_WAIT_MS)
979
+ ]
980
+ ]) {
981
+ if (bounded[key] === void 0) continue;
982
+ const ms = durationMs(bounded[key]);
983
+ if (ms === null) {
984
+ return {
985
+ ...textContent(`${key} must be a duration such as "30s" or milliseconds.`),
986
+ isError: true
987
+ };
988
+ }
989
+ bounded[key] = bound(ms);
990
+ }
991
+ return handler(bounded);
992
+ } : handler;
993
+ const handle = target.tool(name, description, shape, wrapped);
994
+ handles.push(handle);
995
+ return handle;
996
+ }
997
+ };
998
+ }
999
+ function registerTools(target, client, options = {}) {
1000
+ const handles = [];
1001
+ const server = toolTarget(target, options.remote === true, handles);
939
1002
  server.tool(
940
1003
  "create_endpoint",
941
1004
  "Create a webhook endpoint. Returns the endpoint slug, URL, and metadata.",
@@ -1617,7 +1680,7 @@ function registerTools(server, client) {
1617
1680
  verifySignature: shouldVerify,
1618
1681
  cleanup
1619
1682
  }) => {
1620
- const flow = client.flow().createEndpoint({ expiresIn: "1h" }).waitForCapture({ timeout: "30s" });
1683
+ const flow = client.flow().createEndpoint({ expiresIn: "1h" }).waitForCapture({ timeout: "30s", pollInterval: "1s" });
1621
1684
  if (mockStatus !== void 0) {
1622
1685
  flow.setMock({
1623
1686
  status: mockStatus,
@@ -1742,10 +1805,14 @@ function registerTools(server, client) {
1742
1805
  includeHtml: import_zod2.z.boolean().default(false).describe("Include the HTML part (default: false)")
1743
1806
  },
1744
1807
  withErrorHandling(async ({ endpointSlug, tag, subject, from, timeout, since, includeHtml }) => {
1745
- const [email, view] = await Promise.all([
1746
- client.emails.waitFor(endpointSlug, { tag, subject, from, timeout, since }),
1747
- emailEndpointView(client, endpointSlug)
1748
- ]);
1808
+ const view = await emailEndpointView(client, endpointSlug);
1809
+ const email = await client.emails.waitFor(endpointSlug, {
1810
+ tag,
1811
+ subject,
1812
+ from,
1813
+ timeout,
1814
+ since
1815
+ });
1749
1816
  return jsonContent(emailDetail(client, email, view, includeHtml));
1750
1817
  })
1751
1818
  );
@@ -1858,8 +1925,8 @@ function registerTools(server, client) {
1858
1925
  "JSON body field with the sender's own timestamp, e.g. publishedAt or data.publishedAt; null clears it"
1859
1926
  )
1860
1927
  },
1861
- withErrorHandling(async ({ slug, ...options }) => {
1862
- const endpoint = await client.forwarding.configure(slug, options);
1928
+ withErrorHandling(async ({ slug, ...options2 }) => {
1929
+ const endpoint = await client.forwarding.configure(slug, options2);
1863
1930
  return jsonContent({
1864
1931
  slug: endpoint.slug,
1865
1932
  forwardEnabled: endpoint.forwardEnabled ?? false,
@@ -1956,9 +2023,9 @@ function registerTools(server, client) {
1956
2023
  if (requestId === void 0 === (endpointSlug === void 0)) {
1957
2024
  throw new Error("Pass exactly one of requestId or endpointSlug");
1958
2025
  }
1959
- const options = destinationId ? { destinationId } : {};
2026
+ const options2 = destinationId ? { destinationId } : {};
1960
2027
  return jsonContent(
1961
- requestId ? await client.forwarding.redeliver(requestId, options) : await client.forwarding.redeliverFailed(endpointSlug, options)
2028
+ requestId ? await client.forwarding.redeliver(requestId, options2) : await client.forwarding.redeliverFailed(endpointSlug, options2)
1962
2029
  );
1963
2030
  })
1964
2031
  );
@@ -1971,6 +2038,7 @@ function registerTools(server, client) {
1971
2038
  return jsonContent(description);
1972
2039
  })
1973
2040
  );
2041
+ return handles;
1974
2042
  }
1975
2043
 
1976
2044
  // src/sandbox-tools.ts
@@ -1982,7 +2050,6 @@ var SANDBOX = {
1982
2050
  };
1983
2051
  var DEFAULT_WAIT_SECONDS = 45;
1984
2052
  var MAX_WAIT_SECONDS = 600;
1985
- var TOKEN_REFRESH_MARGIN_MS = 6e4;
1986
2053
  var DOCS_URL = "https://webhooks.cc/docs/agents";
1987
2054
  var ACCOUNT_ONLY_OPTIONS = [
1988
2055
  "mockResponse",
@@ -1993,20 +2060,29 @@ var ACCOUNT_ONLY_OPTIONS = [
1993
2060
  "signingHeader"
1994
2061
  ];
1995
2062
  var AgentSession = class {
1996
- constructor(baseUrl, webhookUrl, clientName) {
2063
+ constructor(baseUrl, webhookUrl, clientName, credentialsFile) {
1997
2064
  this.baseUrl = baseUrl;
1998
2065
  this.webhookUrl = webhookUrl;
1999
2066
  this.clientName = clientName;
2067
+ this.credentialsFile = credentialsFile;
2000
2068
  this.sandbox = null;
2001
- this.claim = null;
2002
- this.connected = false;
2069
+ this.connect = null;
2070
+ }
2071
+ sandboxOptions() {
2072
+ return {
2073
+ baseUrl: this.baseUrl,
2074
+ webhookUrl: this.webhookUrl,
2075
+ clientName: this.clientName,
2076
+ credentialsFile: this.credentialsFile
2077
+ };
2003
2078
  }
2004
2079
  /**
2005
- * The sandbox, registered on first use and again after it expired. Tool
2006
- * calls can run in parallel: this.sandbox is read again after every await,
2007
- * and a new sandbox is assigned with no await in between, so parallel
2008
- * calls share one registration. Calls waiting on a registration that fails
2009
- * get its error (a 429 asks them to wait); the next call starts again.
2080
+ * The sandbox: the one saved by an earlier session when it is still live,
2081
+ * else a new registration (and again after it expired). Tool calls can run
2082
+ * in parallel: this.sandbox is read again after every await, and a new
2083
+ * sandbox is assigned with no await in between, so parallel calls share
2084
+ * one registration. Calls waiting on a registration that fails get its
2085
+ * error (a 429 asks them to wait); the next call starts again.
2010
2086
  */
2011
2087
  async sandboxClient() {
2012
2088
  while (this.sandbox) {
@@ -2014,35 +2090,36 @@ var AgentSession = class {
2014
2090
  const sandbox = await pending;
2015
2091
  if (!this.expired(pending, sandbox)) return sandbox;
2016
2092
  }
2017
- const created = import_sdk2.WebhooksCC.sandbox({
2018
- baseUrl: this.baseUrl,
2019
- webhookUrl: this.webhookUrl,
2020
- clientName: this.clientName
2021
- });
2093
+ const created = import_sdk2.WebhooksCC.sandbox(this.sandboxOptions());
2022
2094
  this.sandbox = created;
2023
2095
  created.catch(() => {
2024
2096
  if (this.sandbox === created) this.sandbox = null;
2025
2097
  });
2026
2098
  return created;
2027
2099
  }
2028
- /** The live sandbox, if one was registered; never registers one. */
2100
+ /** The live sandbox, from this session or the credential file; never registers one. */
2029
2101
  async existingSandbox() {
2030
2102
  const pending = this.sandbox;
2031
- if (!pending) return null;
2032
- let sandbox;
2033
- try {
2034
- sandbox = await pending;
2035
- } catch {
2036
- return null;
2103
+ if (pending) {
2104
+ let sandbox;
2105
+ try {
2106
+ sandbox = await pending;
2107
+ } catch {
2108
+ return null;
2109
+ }
2110
+ return this.expired(pending, sandbox) ? null : sandbox;
2037
2111
  }
2038
- return this.expired(pending, sandbox) ? null : sandbox;
2112
+ const resumed = await import_sdk2.SandboxClient.resume(this.sandboxOptions()).catch(() => null);
2113
+ if (!resumed) return null;
2114
+ if (!this.sandbox) this.sandbox = Promise.resolve(resumed);
2115
+ return this.sandbox;
2039
2116
  }
2040
2117
  /** True when the sandbox is past its 24 hours; then it is dropped. */
2041
2118
  expired(pending, sandbox) {
2042
2119
  if (sandbox.expiresAt.getTime() > Date.now()) return false;
2043
2120
  if (this.sandbox === pending) this.sandbox = null;
2044
- if (this.claim?.kind === "sandbox" && this.claim.claimToken === sandbox.registration.claimToken) {
2045
- this.claim = null;
2121
+ if (this.connect?.via === "sandbox" && this.connect.sandbox === sandbox) {
2122
+ this.connect = null;
2046
2123
  }
2047
2124
  return true;
2048
2125
  }
@@ -2053,26 +2130,11 @@ var AgentSession = class {
2053
2130
  }
2054
2131
  return sandbox;
2055
2132
  }
2056
- forgetSandbox() {
2133
+ reset() {
2057
2134
  this.sandbox = null;
2135
+ this.connect = null;
2058
2136
  }
2059
2137
  };
2060
- function claimedTokens(baseUrl, assertion, first) {
2061
- let current = first;
2062
- let pending = null;
2063
- return async (options) => {
2064
- if (!options?.forceRefresh && current.expiresAt.getTime() - Date.now() > TOKEN_REFRESH_MARGIN_MS) {
2065
- return current.accessToken;
2066
- }
2067
- if (!pending) {
2068
- pending = import_sdk2.WebhooksCC.agent.exchange({ baseUrl, assertion }).finally(() => {
2069
- pending = null;
2070
- });
2071
- }
2072
- current = await pending;
2073
- return current.accessToken;
2074
- };
2075
- }
2076
2138
  function minutesLeft(until) {
2077
2139
  return Math.max(1, Math.round((until.getTime() - Date.now()) / 6e4));
2078
2140
  }
@@ -2080,16 +2142,53 @@ function onlyKind(requests, kind) {
2080
2142
  if (!kind) return requests;
2081
2143
  return requests.filter((request) => (request.kind ?? "http") === kind);
2082
2144
  }
2145
+ function savedTo(credentialsFile) {
2146
+ return import_sdk2.AgentCredentialStore.resolve(credentialsFile)?.path ?? null;
2147
+ }
2083
2148
  function registerSandboxTools(server, options = {}) {
2084
2149
  const baseUrl = options.baseUrl ?? process.env.WHK_BASE_URL ?? "https://webhooks.cc";
2150
+ const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
2085
2151
  const session = new AgentSession(
2086
2152
  baseUrl,
2087
- options.webhookUrl ?? process.env.WHK_WEBHOOK_URL,
2088
- options.clientName ?? "webhooks-cc-mcp"
2089
- );
2090
- const registered = [];
2153
+ webhookUrl,
2154
+ options.clientName ?? "webhooks-cc-mcp",
2155
+ options.credentialsFile
2156
+ );
2157
+ let handles = [];
2158
+ let shown = 0;
2159
+ const swap = (register) => {
2160
+ const id = ++shown;
2161
+ for (const handle of handles) handle.remove();
2162
+ handles = register(id);
2163
+ };
2164
+ const showSandbox = () => swap(() => sandboxToolSet(server, session, showAccount));
2165
+ const savedAccount = (id) => options.credentialsFile === false ? null : import_sdk2.WebhooksCC.fromSavedConnection({
2166
+ baseUrl,
2167
+ webhookUrl,
2168
+ credentialsFile: options.credentialsFile,
2169
+ // The human disconnected the agent: back to the sandbox, unless a
2170
+ // newer connection is already on display.
2171
+ onRevoked: () => queueMicrotask(() => {
2172
+ if (shown === id) showSandbox();
2173
+ })
2174
+ });
2175
+ function showAccount(client) {
2176
+ swap((id) => registerTools(server, savedAccount(id) ?? client));
2177
+ options.onConnected?.(client);
2178
+ }
2179
+ const saved = savedAccount(1);
2180
+ if (saved) {
2181
+ swap(() => registerTools(server, saved));
2182
+ options.onConnected?.(saved);
2183
+ } else {
2184
+ showSandbox();
2185
+ }
2186
+ }
2187
+ function sandboxToolSet(server, session, showAccount) {
2188
+ const tools = [];
2091
2189
  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`;
2092
- registered.push(
2190
+ const file = savedTo(session.credentialsFile);
2191
+ tools.push(
2093
2192
  server.tool(
2094
2193
  "create_endpoint",
2095
2194
  `Create a temporary webhook capture endpoint in the agent sandbox (no account needed): ${limits}. The first call registers the sandbox, which takes a few seconds of proof of work. Mock responses, notifications and signature verification need an account: see connect_account.`,
@@ -2113,7 +2212,7 @@ function registerSandboxTools(server, options = {}) {
2113
2212
  })
2114
2213
  )
2115
2214
  );
2116
- registered.push(
2215
+ tools.push(
2117
2216
  server.tool(
2118
2217
  "list_endpoints",
2119
2218
  "List this agent's sandbox endpoints. Empty until create_endpoint starts the sandbox.",
@@ -2127,7 +2226,7 @@ function registerSandboxTools(server, options = {}) {
2127
2226
  })
2128
2227
  )
2129
2228
  );
2130
- registered.push(
2229
+ tools.push(
2131
2230
  server.tool(
2132
2231
  "get_endpoint",
2133
2232
  "Get one of this agent's sandbox endpoints by slug, with its sandbox limits and usage.",
@@ -2137,7 +2236,7 @@ function registerSandboxTools(server, options = {}) {
2137
2236
  })
2138
2237
  )
2139
2238
  );
2140
- registered.push(
2239
+ tools.push(
2141
2240
  server.tool(
2142
2241
  "delete_endpoint",
2143
2242
  "Delete one of this agent's sandbox endpoints and its captured requests. Frees a slot; the sandbox's request budget is not refunded.",
@@ -2148,7 +2247,7 @@ function registerSandboxTools(server, options = {}) {
2148
2247
  })
2149
2248
  )
2150
2249
  );
2151
- registered.push(
2250
+ tools.push(
2152
2251
  server.tool(
2153
2252
  "list_requests",
2154
2253
  "List recent requests captured by a sandbox endpoint, newest first.",
@@ -2160,7 +2259,7 @@ function registerSandboxTools(server, options = {}) {
2160
2259
  })
2161
2260
  )
2162
2261
  );
2163
- registered.push(
2262
+ tools.push(
2164
2263
  server.tool(
2165
2264
  "get_request",
2166
2265
  "Get full details for a request captured by a sandbox endpoint.",
@@ -2171,7 +2270,7 @@ function registerSandboxTools(server, options = {}) {
2171
2270
  })
2172
2271
  )
2173
2272
  );
2174
- registered.push(
2273
+ tools.push(
2175
2274
  server.tool(
2176
2275
  "wait_for_request",
2177
2276
  "Wait for a request to arrive at a sandbox endpoint.",
@@ -2183,7 +2282,7 @@ function registerSandboxTools(server, options = {}) {
2183
2282
  })
2184
2283
  )
2185
2284
  );
2186
- registered.push(
2285
+ tools.push(
2187
2286
  server.tool(
2188
2287
  "generate_payload_schema",
2189
2288
  "Generate TypeScript interfaces, Zod schemas, Pydantic models, or JSON Schema from the bodies of requests captured by a sandbox endpoint. Several requests of one event teach which fields are optional or nullable.",
@@ -2195,62 +2294,59 @@ function registerSandboxTools(server, options = {}) {
2195
2294
  })
2196
2295
  )
2197
2296
  );
2198
- registered.push(
2297
+ tools.push(
2199
2298
  server.tool(
2200
2299
  "connect_account",
2201
2300
  "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.",
2202
2301
  {
2203
- email: import_zod3.z.string().email({ message: "Invalid email address" }).describe("The human's email address; only someone signed in as it can connect")
2302
+ email: import_zod3.z.string().email({ message: "Invalid email address" }).describe(
2303
+ "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"
2304
+ )
2204
2305
  },
2205
2306
  withErrorHandling(async ({ email }) => {
2206
- if (session.connected) {
2207
- return jsonContent({ connected: true, message: "Already connected to an account." });
2208
- }
2209
2307
  const normalized = email.trim().toLowerCase();
2308
+ const previous = session.connect;
2210
2309
  const sandbox = await session.existingSandbox();
2211
- let claim;
2310
+ let attempt;
2311
+ let movesEndpoints = false;
2212
2312
  if (sandbox) {
2213
- claim = {
2214
- kind: "sandbox",
2215
- claimToken: sandbox.registration.claimToken,
2216
- email: normalized,
2217
- attempt: await sandbox.claim({ email: normalized }),
2218
- claimExpires: sandbox.expiresAt
2219
- };
2220
- } else if (session.claim?.kind === "service_auth" && session.claim.email === normalized && session.claim.claimExpires.getTime() > Date.now()) {
2221
- const attempt = await import_sdk2.WebhooksCC.agent.startClaim({
2222
- baseUrl,
2223
- claimToken: session.claim.claimToken
2224
- });
2225
- claim = { ...session.claim, attempt };
2313
+ const claim = await sandbox.claim({ email: normalized });
2314
+ session.connect = { via: "sandbox", sandbox, email: normalized, attempt: claim };
2315
+ attempt = claim;
2316
+ movesEndpoints = claim.movesEndpoints;
2226
2317
  } else {
2227
- const registration = await import_sdk2.WebhooksCC.agent.registerServiceAuth({
2228
- baseUrl,
2229
- email: normalized,
2230
- clientName: session.clientName
2231
- });
2232
- claim = {
2233
- kind: "service_auth",
2234
- claimToken: registration.claimToken,
2318
+ let pending = null;
2319
+ if (previous?.via === "connect" && previous.email === normalized) {
2320
+ pending = await previous.pending.restart().catch((error) => {
2321
+ if (error instanceof import_sdk2.AgentAuthError) return null;
2322
+ throw error;
2323
+ });
2324
+ }
2325
+ pending ?? (pending = await import_sdk2.WebhooksCC.connect({
2326
+ baseUrl: session.baseUrl,
2327
+ webhookUrl: session.webhookUrl,
2235
2328
  email: normalized,
2236
- attempt: registration.claim,
2237
- claimExpires: registration.claimTokenExpires
2238
- };
2329
+ clientName: session.clientName,
2330
+ credentialsFile: session.credentialsFile
2331
+ }));
2332
+ session.connect = { via: "connect", pending, email: normalized };
2333
+ attempt = pending;
2239
2334
  }
2240
- session.claim = claim;
2241
- const { verificationUri, userCode, expiresAt } = claim.attempt;
2335
+ const { verificationUri, userCode, expiresAt } = attempt;
2242
2336
  return jsonContent({
2243
2337
  verificationUri,
2244
2338
  userCode,
2245
2339
  expiresAt: expiresAt.toISOString(),
2246
2340
  tellTheHuman: `Open ${verificationUri}, sign in or sign up as ${normalized}, and enter the code ${userCode}.`,
2247
2341
  next: `Show the human that line, then call wait_for_connection. The code works for about ${minutesLeft(expiresAt)} minutes; connect_account gives a new one.`,
2248
- ...claim.kind === "sandbox" ? { endpoints: "The sandbox endpoints move into that account with what they captured." } : {}
2342
+ ...sandbox ? {
2343
+ endpoints: movesEndpoints ? "The sandbox endpoints move into that account with what they captured." : "The sandbox endpoints were made in an earlier session, so they stay in the sandbox until it expires; the account starts without them."
2344
+ } : {}
2249
2345
  });
2250
2346
  })
2251
2347
  )
2252
2348
  );
2253
- registered.push(
2349
+ tools.push(
2254
2350
  server.tool(
2255
2351
  "wait_for_connection",
2256
2352
  `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.`,
@@ -2258,28 +2354,22 @@ function registerSandboxTools(server, options = {}) {
2258
2354
  timeoutSeconds: import_zod3.z.number().int().min(1).max(MAX_WAIT_SECONDS).optional().describe(`How long to wait, in seconds (default ${DEFAULT_WAIT_SECONDS})`)
2259
2355
  },
2260
2356
  withErrorHandling(async ({ timeoutSeconds }) => {
2261
- if (session.connected) {
2262
- return jsonContent({ connected: true, message: "Already connected to an account." });
2263
- }
2264
- const claim = session.claim;
2265
- if (!claim) {
2357
+ const pending = session.connect;
2358
+ if (!pending) {
2266
2359
  throw new Error("Nothing to wait for: call connect_account first.");
2267
2360
  }
2268
- let claimed;
2361
+ const timeout = (timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3;
2362
+ const expiresAt = pending.via === "sandbox" ? pending.attempt.expiresAt : pending.pending.expiresAt;
2363
+ let client;
2269
2364
  try {
2270
- claimed = await import_sdk2.WebhooksCC.agent.waitForClaim({
2271
- baseUrl,
2272
- claimToken: claim.claimToken,
2273
- interval: claim.attempt.interval,
2274
- timeout: (timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3
2275
- });
2365
+ client = pending.via === "sandbox" ? await pending.sandbox.waitForClaim({ timeout }) : await pending.pending.wait({ timeout });
2276
2366
  } catch (error) {
2277
2367
  if (!(error instanceof import_sdk2.AgentAuthError)) throw error;
2278
2368
  if (error.code === "timeout") {
2279
2369
  return jsonContent({
2280
2370
  connected: false,
2281
2371
  status: "waiting",
2282
- message: `Not connected yet. The code works until ${claim.attempt.expiresAt.toISOString()}; call wait_for_connection again.`
2372
+ message: `Not connected yet. The code works until ${expiresAt.toISOString()}; call wait_for_connection again.`
2283
2373
  });
2284
2374
  }
2285
2375
  if (error.code === "expired_token") {
@@ -2298,34 +2388,25 @@ function registerSandboxTools(server, options = {}) {
2298
2388
  }
2299
2389
  throw error;
2300
2390
  }
2301
- const client = new import_sdk2.WebhooksCC({
2302
- baseUrl,
2303
- webhookUrl: session.webhookUrl,
2304
- getAccessToken: claimedTokens(baseUrl, claimed.identityAssertion, claimed.token)
2305
- });
2306
- session.connected = true;
2307
- session.claim = null;
2308
- session.forgetSandbox();
2309
- for (const tool of registered) tool.remove();
2310
- registerTools(server, client);
2311
- options.onConnected?.(client);
2391
+ const movedEndpoints = pending.via === "sandbox" && pending.attempt.movesEndpoints;
2392
+ session.reset();
2393
+ showAccount(client);
2312
2394
  return jsonContent({
2313
2395
  connected: true,
2314
- email: claim.email,
2315
- scope: claimed.token.scope,
2316
- message: `Connected to the webhooks.cc account of ${claim.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).${claim.kind === "sandbox" ? " The sandbox endpoints are now that account's endpoints, with what they captured." : ""} 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.`
2396
+ email: pending.email,
2397
+ 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."}`
2317
2398
  });
2318
2399
  })
2319
2400
  )
2320
2401
  );
2321
- registered.push(
2402
+ tools.push(
2322
2403
  server.tool(
2323
2404
  "about_sandbox",
2324
2405
  "Explain what this agent can do without a webhooks.cc account, the sandbox limits, and how to connect an account. No network call.",
2325
2406
  {},
2326
2407
  withErrorHandling(async () => {
2327
2408
  return jsonContent({
2328
- mode: "sandbox (no WHK_API_KEY)",
2409
+ mode: "sandbox (no WHK_API_KEY, no saved connection)",
2329
2410
  sandbox: {
2330
2411
  maxEndpoints: SANDBOX.maxEndpoints,
2331
2412
  requestsPerEndpoint: SANDBOX.requestsPerEndpoint,
@@ -2336,24 +2417,26 @@ function registerSandboxTools(server, options = {}) {
2336
2417
  notAvailable: "Mock responses, response rules, notifications, signature verification, email capture, forwarding, teams and sending webhooks."
2337
2418
  },
2338
2419
  connectAnAccount: [
2339
- "Call connect_account with the human's email and show them the link and code it returns.",
2420
+ "Call connect_account with the email the human signs in to webhooks.cc with, and show them the link and code it returns.",
2340
2421
  "Call wait_for_connection until it reports connected; the full tools for their account then replace the sandbox tools.",
2341
- "Sandbox endpoints move into the account with their captured requests."
2422
+ "Sandbox endpoints registered by this server process move into the account with their captured requests; a sandbox resumed from an earlier session stays in the sandbox (connect_account says which)."
2342
2423
  ],
2424
+ savedIn: file ? `${file} (shared with the whk CLI and the SDKs): the sandbox and the connection survive restarts.` : "nowhere: this session only.",
2343
2425
  withAnApiKey: "Set WHK_API_KEY to a whcc_ key from the dashboard and restart for the full tools from the start.",
2344
2426
  docs: DOCS_URL,
2345
- protocol: `${baseUrl}/auth.md`
2427
+ protocol: `${session.baseUrl}/auth.md`
2346
2428
  });
2347
2429
  })
2348
2430
  )
2349
2431
  );
2432
+ return tools;
2350
2433
  }
2351
2434
  function registerAgentRegistrationTools(server) {
2352
2435
  registerSandboxTools(server);
2353
2436
  }
2354
2437
 
2355
2438
  // src/index.ts
2356
- var VERSION = true ? "1.14.0" : "0.0.0-dev";
2439
+ var VERSION = true ? "1.15.0" : "0.0.0-dev";
2357
2440
  function createServer(options = {}) {
2358
2441
  const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
2359
2442
  const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
@@ -2364,7 +2447,7 @@ function createServer(options = {}) {
2364
2447
  { debouncedNotificationMethods: ["notifications/tools/list_changed"] }
2365
2448
  );
2366
2449
  if (!apiKey) {
2367
- registerSandboxTools(server, { baseUrl, webhookUrl });
2450
+ registerSandboxTools(server, { baseUrl, webhookUrl, credentialsFile: options.credentialsFile });
2368
2451
  registerPrompts(server);
2369
2452
  return server;
2370
2453
  }
@@ -2376,6 +2459,7 @@ function createServer(options = {}) {
2376
2459
  }
2377
2460
  // Annotate the CommonJS export names for ESM import in node:
2378
2461
  0 && (module.exports = {
2462
+ REMOTE_MAX_WAIT_MS,
2379
2463
  createServer,
2380
2464
  registerAgentRegistrationTools,
2381
2465
  registerPrompts,