@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/README.md +42 -22
- package/dist/bin/mcp.js +255 -161
- package/dist/chunk-RHQD2GRD.mjs +1819 -0
- package/dist/index.d.mts +21 -9
- package/dist/index.d.ts +21 -9
- package/dist/index.js +219 -135
- package/dist/index.mjs +161 -1861
- package/dist/tools.d.mts +171 -0
- package/dist/tools.d.ts +171 -0
- package/dist/tools.js +1827 -0
- package/dist/tools.mjs +34 -0
- package/package.json +7 -2
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
|
|
22
|
-
__export(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
1746
|
-
|
|
1747
|
-
|
|
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, ...
|
|
1862
|
-
const endpoint = await client.forwarding.configure(slug,
|
|
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
|
|
2026
|
+
const options2 = destinationId ? { destinationId } : {};
|
|
1960
2027
|
return jsonContent(
|
|
1961
|
-
requestId ? await client.forwarding.redeliver(requestId,
|
|
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.
|
|
2002
|
-
|
|
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
|
|
2006
|
-
*
|
|
2007
|
-
*
|
|
2008
|
-
*
|
|
2009
|
-
*
|
|
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,
|
|
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 (
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
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
|
-
|
|
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.
|
|
2045
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
2088
|
-
options.clientName ?? "webhooks-cc-mcp"
|
|
2089
|
-
|
|
2090
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
2310
|
+
let attempt;
|
|
2311
|
+
let movesEndpoints = false;
|
|
2212
2312
|
if (sandbox) {
|
|
2213
|
-
claim = {
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
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
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
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
|
-
|
|
2237
|
-
|
|
2238
|
-
};
|
|
2329
|
+
clientName: session.clientName,
|
|
2330
|
+
credentialsFile: session.credentialsFile
|
|
2331
|
+
}));
|
|
2332
|
+
session.connect = { via: "connect", pending, email: normalized };
|
|
2333
|
+
attempt = pending;
|
|
2239
2334
|
}
|
|
2240
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
-
|
|
2262
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ${
|
|
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
|
|
2302
|
-
|
|
2303
|
-
|
|
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:
|
|
2315
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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,
|