@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/bin/mcp.js
CHANGED
|
@@ -754,7 +754,9 @@ async function waitForMultipleRequests(client, endpointSlug, options) {
|
|
|
754
754
|
};
|
|
755
755
|
}
|
|
756
756
|
}
|
|
757
|
-
await sleep(
|
|
757
|
+
await sleep(
|
|
758
|
+
Math.min(Math.max(10, pollIntervalMs), Math.max(0, timeoutMs - (Date.now() - startedAt)))
|
|
759
|
+
);
|
|
758
760
|
}
|
|
759
761
|
return {
|
|
760
762
|
requests,
|
|
@@ -912,7 +914,67 @@ function emailDetail(client, email, view, includeHtml) {
|
|
|
912
914
|
]);
|
|
913
915
|
return detail;
|
|
914
916
|
}
|
|
915
|
-
|
|
917
|
+
var LOCAL_ONLY_TOOLS = /* @__PURE__ */ new Set(["send_to", "replay_request"]);
|
|
918
|
+
var TARGET_URL_TOOLS = /* @__PURE__ */ new Set(["render_email", "test_webhook_flow"]);
|
|
919
|
+
var REMOTE_MAX_WAIT_MS = 5e4;
|
|
920
|
+
var REMOTE_MIN_POLL_MS = 1e3;
|
|
921
|
+
function durationMs(value) {
|
|
922
|
+
if (typeof value === "number") return Number.isFinite(value) && value >= 0 ? value : null;
|
|
923
|
+
if (typeof value !== "string") return null;
|
|
924
|
+
try {
|
|
925
|
+
const ms = (0, import_sdk.parseDuration)(value);
|
|
926
|
+
return Number.isFinite(ms) && ms >= 0 ? ms : null;
|
|
927
|
+
} catch {
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
function toolTarget(target, remote, handles) {
|
|
932
|
+
return {
|
|
933
|
+
tool(name, description, shape, handler) {
|
|
934
|
+
if (remote && LOCAL_ONLY_TOOLS.has(name)) return { remove() {
|
|
935
|
+
} };
|
|
936
|
+
const wrapped = remote ? async (args2) => {
|
|
937
|
+
const input = args2;
|
|
938
|
+
if (TARGET_URL_TOOLS.has(name) && input.targetUrl !== void 0) {
|
|
939
|
+
return {
|
|
940
|
+
...textContent(
|
|
941
|
+
"targetUrl needs the local MCP server (npx -y @webhooks-cc/mcp): the hosted server cannot reach your machine. Leave it out here."
|
|
942
|
+
),
|
|
943
|
+
isError: true
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
const bounded = { ...input };
|
|
947
|
+
if ("pollInterval" in shape && input.pollInterval === void 0) {
|
|
948
|
+
bounded.pollInterval = REMOTE_MIN_POLL_MS;
|
|
949
|
+
}
|
|
950
|
+
for (const [key, bound] of [
|
|
951
|
+
["timeout", (ms) => Math.min(ms, REMOTE_MAX_WAIT_MS)],
|
|
952
|
+
[
|
|
953
|
+
"pollInterval",
|
|
954
|
+
(ms) => Math.min(Math.max(ms, REMOTE_MIN_POLL_MS), REMOTE_MAX_WAIT_MS)
|
|
955
|
+
]
|
|
956
|
+
]) {
|
|
957
|
+
if (bounded[key] === void 0) continue;
|
|
958
|
+
const ms = durationMs(bounded[key]);
|
|
959
|
+
if (ms === null) {
|
|
960
|
+
return {
|
|
961
|
+
...textContent(`${key} must be a duration such as "30s" or milliseconds.`),
|
|
962
|
+
isError: true
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
bounded[key] = bound(ms);
|
|
966
|
+
}
|
|
967
|
+
return handler(bounded);
|
|
968
|
+
} : handler;
|
|
969
|
+
const handle = target.tool(name, description, shape, wrapped);
|
|
970
|
+
handles.push(handle);
|
|
971
|
+
return handle;
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
function registerTools(target, client, options = {}) {
|
|
976
|
+
const handles = [];
|
|
977
|
+
const server = toolTarget(target, options.remote === true, handles);
|
|
916
978
|
server.tool(
|
|
917
979
|
"create_endpoint",
|
|
918
980
|
"Create a webhook endpoint. Returns the endpoint slug, URL, and metadata.",
|
|
@@ -1594,7 +1656,7 @@ function registerTools(server, client) {
|
|
|
1594
1656
|
verifySignature: shouldVerify,
|
|
1595
1657
|
cleanup
|
|
1596
1658
|
}) => {
|
|
1597
|
-
const flow = client.flow().createEndpoint({ expiresIn: "1h" }).waitForCapture({ timeout: "30s" });
|
|
1659
|
+
const flow = client.flow().createEndpoint({ expiresIn: "1h" }).waitForCapture({ timeout: "30s", pollInterval: "1s" });
|
|
1598
1660
|
if (mockStatus !== void 0) {
|
|
1599
1661
|
flow.setMock({
|
|
1600
1662
|
status: mockStatus,
|
|
@@ -1719,10 +1781,14 @@ function registerTools(server, client) {
|
|
|
1719
1781
|
includeHtml: import_zod2.z.boolean().default(false).describe("Include the HTML part (default: false)")
|
|
1720
1782
|
},
|
|
1721
1783
|
withErrorHandling(async ({ endpointSlug, tag, subject, from, timeout, since, includeHtml }) => {
|
|
1722
|
-
const
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1784
|
+
const view = await emailEndpointView(client, endpointSlug);
|
|
1785
|
+
const email = await client.emails.waitFor(endpointSlug, {
|
|
1786
|
+
tag,
|
|
1787
|
+
subject,
|
|
1788
|
+
from,
|
|
1789
|
+
timeout,
|
|
1790
|
+
since
|
|
1791
|
+
});
|
|
1726
1792
|
return jsonContent(emailDetail(client, email, view, includeHtml));
|
|
1727
1793
|
})
|
|
1728
1794
|
);
|
|
@@ -1835,8 +1901,8 @@ function registerTools(server, client) {
|
|
|
1835
1901
|
"JSON body field with the sender's own timestamp, e.g. publishedAt or data.publishedAt; null clears it"
|
|
1836
1902
|
)
|
|
1837
1903
|
},
|
|
1838
|
-
withErrorHandling(async ({ slug, ...
|
|
1839
|
-
const endpoint = await client.forwarding.configure(slug,
|
|
1904
|
+
withErrorHandling(async ({ slug, ...options2 }) => {
|
|
1905
|
+
const endpoint = await client.forwarding.configure(slug, options2);
|
|
1840
1906
|
return jsonContent({
|
|
1841
1907
|
slug: endpoint.slug,
|
|
1842
1908
|
forwardEnabled: endpoint.forwardEnabled ?? false,
|
|
@@ -1933,9 +1999,9 @@ function registerTools(server, client) {
|
|
|
1933
1999
|
if (requestId === void 0 === (endpointSlug === void 0)) {
|
|
1934
2000
|
throw new Error("Pass exactly one of requestId or endpointSlug");
|
|
1935
2001
|
}
|
|
1936
|
-
const
|
|
2002
|
+
const options2 = destinationId ? { destinationId } : {};
|
|
1937
2003
|
return jsonContent(
|
|
1938
|
-
requestId ? await client.forwarding.redeliver(requestId,
|
|
2004
|
+
requestId ? await client.forwarding.redeliver(requestId, options2) : await client.forwarding.redeliverFailed(endpointSlug, options2)
|
|
1939
2005
|
);
|
|
1940
2006
|
})
|
|
1941
2007
|
);
|
|
@@ -1948,6 +2014,7 @@ function registerTools(server, client) {
|
|
|
1948
2014
|
return jsonContent(description);
|
|
1949
2015
|
})
|
|
1950
2016
|
);
|
|
2017
|
+
return handles;
|
|
1951
2018
|
}
|
|
1952
2019
|
|
|
1953
2020
|
// src/sandbox-tools.ts
|
|
@@ -1959,7 +2026,6 @@ var SANDBOX = {
|
|
|
1959
2026
|
};
|
|
1960
2027
|
var DEFAULT_WAIT_SECONDS = 45;
|
|
1961
2028
|
var MAX_WAIT_SECONDS = 600;
|
|
1962
|
-
var TOKEN_REFRESH_MARGIN_MS = 6e4;
|
|
1963
2029
|
var DOCS_URL = "https://webhooks.cc/docs/agents";
|
|
1964
2030
|
var ACCOUNT_ONLY_OPTIONS = [
|
|
1965
2031
|
"mockResponse",
|
|
@@ -1970,20 +2036,29 @@ var ACCOUNT_ONLY_OPTIONS = [
|
|
|
1970
2036
|
"signingHeader"
|
|
1971
2037
|
];
|
|
1972
2038
|
var AgentSession = class {
|
|
1973
|
-
constructor(baseUrl, webhookUrl, clientName) {
|
|
2039
|
+
constructor(baseUrl, webhookUrl, clientName, credentialsFile) {
|
|
1974
2040
|
this.baseUrl = baseUrl;
|
|
1975
2041
|
this.webhookUrl = webhookUrl;
|
|
1976
2042
|
this.clientName = clientName;
|
|
2043
|
+
this.credentialsFile = credentialsFile;
|
|
1977
2044
|
this.sandbox = null;
|
|
1978
|
-
this.
|
|
1979
|
-
|
|
2045
|
+
this.connect = null;
|
|
2046
|
+
}
|
|
2047
|
+
sandboxOptions() {
|
|
2048
|
+
return {
|
|
2049
|
+
baseUrl: this.baseUrl,
|
|
2050
|
+
webhookUrl: this.webhookUrl,
|
|
2051
|
+
clientName: this.clientName,
|
|
2052
|
+
credentialsFile: this.credentialsFile
|
|
2053
|
+
};
|
|
1980
2054
|
}
|
|
1981
2055
|
/**
|
|
1982
|
-
* The sandbox
|
|
1983
|
-
*
|
|
1984
|
-
*
|
|
1985
|
-
*
|
|
1986
|
-
*
|
|
2056
|
+
* The sandbox: the one saved by an earlier session when it is still live,
|
|
2057
|
+
* else a new registration (and again after it expired). Tool calls can run
|
|
2058
|
+
* in parallel: this.sandbox is read again after every await, and a new
|
|
2059
|
+
* sandbox is assigned with no await in between, so parallel calls share
|
|
2060
|
+
* one registration. Calls waiting on a registration that fails get its
|
|
2061
|
+
* error (a 429 asks them to wait); the next call starts again.
|
|
1987
2062
|
*/
|
|
1988
2063
|
async sandboxClient() {
|
|
1989
2064
|
while (this.sandbox) {
|
|
@@ -1991,35 +2066,36 @@ var AgentSession = class {
|
|
|
1991
2066
|
const sandbox = await pending;
|
|
1992
2067
|
if (!this.expired(pending, sandbox)) return sandbox;
|
|
1993
2068
|
}
|
|
1994
|
-
const created = import_sdk2.WebhooksCC.sandbox(
|
|
1995
|
-
baseUrl: this.baseUrl,
|
|
1996
|
-
webhookUrl: this.webhookUrl,
|
|
1997
|
-
clientName: this.clientName
|
|
1998
|
-
});
|
|
2069
|
+
const created = import_sdk2.WebhooksCC.sandbox(this.sandboxOptions());
|
|
1999
2070
|
this.sandbox = created;
|
|
2000
2071
|
created.catch(() => {
|
|
2001
2072
|
if (this.sandbox === created) this.sandbox = null;
|
|
2002
2073
|
});
|
|
2003
2074
|
return created;
|
|
2004
2075
|
}
|
|
2005
|
-
/** The live sandbox,
|
|
2076
|
+
/** The live sandbox, from this session or the credential file; never registers one. */
|
|
2006
2077
|
async existingSandbox() {
|
|
2007
2078
|
const pending = this.sandbox;
|
|
2008
|
-
if (
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2079
|
+
if (pending) {
|
|
2080
|
+
let sandbox;
|
|
2081
|
+
try {
|
|
2082
|
+
sandbox = await pending;
|
|
2083
|
+
} catch {
|
|
2084
|
+
return null;
|
|
2085
|
+
}
|
|
2086
|
+
return this.expired(pending, sandbox) ? null : sandbox;
|
|
2014
2087
|
}
|
|
2015
|
-
|
|
2088
|
+
const resumed = await import_sdk2.SandboxClient.resume(this.sandboxOptions()).catch(() => null);
|
|
2089
|
+
if (!resumed) return null;
|
|
2090
|
+
if (!this.sandbox) this.sandbox = Promise.resolve(resumed);
|
|
2091
|
+
return this.sandbox;
|
|
2016
2092
|
}
|
|
2017
2093
|
/** True when the sandbox is past its 24 hours; then it is dropped. */
|
|
2018
2094
|
expired(pending, sandbox) {
|
|
2019
2095
|
if (sandbox.expiresAt.getTime() > Date.now()) return false;
|
|
2020
2096
|
if (this.sandbox === pending) this.sandbox = null;
|
|
2021
|
-
if (this.
|
|
2022
|
-
this.
|
|
2097
|
+
if (this.connect?.via === "sandbox" && this.connect.sandbox === sandbox) {
|
|
2098
|
+
this.connect = null;
|
|
2023
2099
|
}
|
|
2024
2100
|
return true;
|
|
2025
2101
|
}
|
|
@@ -2030,26 +2106,11 @@ var AgentSession = class {
|
|
|
2030
2106
|
}
|
|
2031
2107
|
return sandbox;
|
|
2032
2108
|
}
|
|
2033
|
-
|
|
2109
|
+
reset() {
|
|
2034
2110
|
this.sandbox = null;
|
|
2111
|
+
this.connect = null;
|
|
2035
2112
|
}
|
|
2036
2113
|
};
|
|
2037
|
-
function claimedTokens(baseUrl, assertion, first) {
|
|
2038
|
-
let current = first;
|
|
2039
|
-
let pending = null;
|
|
2040
|
-
return async (options) => {
|
|
2041
|
-
if (!options?.forceRefresh && current.expiresAt.getTime() - Date.now() > TOKEN_REFRESH_MARGIN_MS) {
|
|
2042
|
-
return current.accessToken;
|
|
2043
|
-
}
|
|
2044
|
-
if (!pending) {
|
|
2045
|
-
pending = import_sdk2.WebhooksCC.agent.exchange({ baseUrl, assertion }).finally(() => {
|
|
2046
|
-
pending = null;
|
|
2047
|
-
});
|
|
2048
|
-
}
|
|
2049
|
-
current = await pending;
|
|
2050
|
-
return current.accessToken;
|
|
2051
|
-
};
|
|
2052
|
-
}
|
|
2053
2114
|
function minutesLeft(until) {
|
|
2054
2115
|
return Math.max(1, Math.round((until.getTime() - Date.now()) / 6e4));
|
|
2055
2116
|
}
|
|
@@ -2057,16 +2118,53 @@ function onlyKind(requests, kind) {
|
|
|
2057
2118
|
if (!kind) return requests;
|
|
2058
2119
|
return requests.filter((request) => (request.kind ?? "http") === kind);
|
|
2059
2120
|
}
|
|
2121
|
+
function savedTo(credentialsFile) {
|
|
2122
|
+
return import_sdk2.AgentCredentialStore.resolve(credentialsFile)?.path ?? null;
|
|
2123
|
+
}
|
|
2060
2124
|
function registerSandboxTools(server, options = {}) {
|
|
2061
2125
|
const baseUrl = options.baseUrl ?? process.env.WHK_BASE_URL ?? "https://webhooks.cc";
|
|
2126
|
+
const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
|
|
2062
2127
|
const session = new AgentSession(
|
|
2063
2128
|
baseUrl,
|
|
2064
|
-
|
|
2065
|
-
options.clientName ?? "webhooks-cc-mcp"
|
|
2066
|
-
|
|
2067
|
-
|
|
2129
|
+
webhookUrl,
|
|
2130
|
+
options.clientName ?? "webhooks-cc-mcp",
|
|
2131
|
+
options.credentialsFile
|
|
2132
|
+
);
|
|
2133
|
+
let handles = [];
|
|
2134
|
+
let shown = 0;
|
|
2135
|
+
const swap = (register) => {
|
|
2136
|
+
const id = ++shown;
|
|
2137
|
+
for (const handle of handles) handle.remove();
|
|
2138
|
+
handles = register(id);
|
|
2139
|
+
};
|
|
2140
|
+
const showSandbox = () => swap(() => sandboxToolSet(server, session, showAccount));
|
|
2141
|
+
const savedAccount = (id) => options.credentialsFile === false ? null : import_sdk2.WebhooksCC.fromSavedConnection({
|
|
2142
|
+
baseUrl,
|
|
2143
|
+
webhookUrl,
|
|
2144
|
+
credentialsFile: options.credentialsFile,
|
|
2145
|
+
// The human disconnected the agent: back to the sandbox, unless a
|
|
2146
|
+
// newer connection is already on display.
|
|
2147
|
+
onRevoked: () => queueMicrotask(() => {
|
|
2148
|
+
if (shown === id) showSandbox();
|
|
2149
|
+
})
|
|
2150
|
+
});
|
|
2151
|
+
function showAccount(client) {
|
|
2152
|
+
swap((id) => registerTools(server, savedAccount(id) ?? client));
|
|
2153
|
+
options.onConnected?.(client);
|
|
2154
|
+
}
|
|
2155
|
+
const saved = savedAccount(1);
|
|
2156
|
+
if (saved) {
|
|
2157
|
+
swap(() => registerTools(server, saved));
|
|
2158
|
+
options.onConnected?.(saved);
|
|
2159
|
+
} else {
|
|
2160
|
+
showSandbox();
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
function sandboxToolSet(server, session, showAccount) {
|
|
2164
|
+
const tools = [];
|
|
2068
2165
|
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`;
|
|
2069
|
-
|
|
2166
|
+
const file = savedTo(session.credentialsFile);
|
|
2167
|
+
tools.push(
|
|
2070
2168
|
server.tool(
|
|
2071
2169
|
"create_endpoint",
|
|
2072
2170
|
`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.`,
|
|
@@ -2090,7 +2188,7 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2090
2188
|
})
|
|
2091
2189
|
)
|
|
2092
2190
|
);
|
|
2093
|
-
|
|
2191
|
+
tools.push(
|
|
2094
2192
|
server.tool(
|
|
2095
2193
|
"list_endpoints",
|
|
2096
2194
|
"List this agent's sandbox endpoints. Empty until create_endpoint starts the sandbox.",
|
|
@@ -2104,7 +2202,7 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2104
2202
|
})
|
|
2105
2203
|
)
|
|
2106
2204
|
);
|
|
2107
|
-
|
|
2205
|
+
tools.push(
|
|
2108
2206
|
server.tool(
|
|
2109
2207
|
"get_endpoint",
|
|
2110
2208
|
"Get one of this agent's sandbox endpoints by slug, with its sandbox limits and usage.",
|
|
@@ -2114,7 +2212,7 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2114
2212
|
})
|
|
2115
2213
|
)
|
|
2116
2214
|
);
|
|
2117
|
-
|
|
2215
|
+
tools.push(
|
|
2118
2216
|
server.tool(
|
|
2119
2217
|
"delete_endpoint",
|
|
2120
2218
|
"Delete one of this agent's sandbox endpoints and its captured requests. Frees a slot; the sandbox's request budget is not refunded.",
|
|
@@ -2125,7 +2223,7 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2125
2223
|
})
|
|
2126
2224
|
)
|
|
2127
2225
|
);
|
|
2128
|
-
|
|
2226
|
+
tools.push(
|
|
2129
2227
|
server.tool(
|
|
2130
2228
|
"list_requests",
|
|
2131
2229
|
"List recent requests captured by a sandbox endpoint, newest first.",
|
|
@@ -2137,7 +2235,7 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2137
2235
|
})
|
|
2138
2236
|
)
|
|
2139
2237
|
);
|
|
2140
|
-
|
|
2238
|
+
tools.push(
|
|
2141
2239
|
server.tool(
|
|
2142
2240
|
"get_request",
|
|
2143
2241
|
"Get full details for a request captured by a sandbox endpoint.",
|
|
@@ -2148,7 +2246,7 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2148
2246
|
})
|
|
2149
2247
|
)
|
|
2150
2248
|
);
|
|
2151
|
-
|
|
2249
|
+
tools.push(
|
|
2152
2250
|
server.tool(
|
|
2153
2251
|
"wait_for_request",
|
|
2154
2252
|
"Wait for a request to arrive at a sandbox endpoint.",
|
|
@@ -2160,7 +2258,7 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2160
2258
|
})
|
|
2161
2259
|
)
|
|
2162
2260
|
);
|
|
2163
|
-
|
|
2261
|
+
tools.push(
|
|
2164
2262
|
server.tool(
|
|
2165
2263
|
"generate_payload_schema",
|
|
2166
2264
|
"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.",
|
|
@@ -2172,62 +2270,59 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2172
2270
|
})
|
|
2173
2271
|
)
|
|
2174
2272
|
);
|
|
2175
|
-
|
|
2273
|
+
tools.push(
|
|
2176
2274
|
server.tool(
|
|
2177
2275
|
"connect_account",
|
|
2178
2276
|
"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.",
|
|
2179
2277
|
{
|
|
2180
|
-
email: import_zod3.z.string().email({ message: "Invalid email address" }).describe(
|
|
2278
|
+
email: import_zod3.z.string().email({ message: "Invalid email address" }).describe(
|
|
2279
|
+
"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"
|
|
2280
|
+
)
|
|
2181
2281
|
},
|
|
2182
2282
|
withErrorHandling(async ({ email }) => {
|
|
2183
|
-
if (session.connected) {
|
|
2184
|
-
return jsonContent({ connected: true, message: "Already connected to an account." });
|
|
2185
|
-
}
|
|
2186
2283
|
const normalized = email.trim().toLowerCase();
|
|
2284
|
+
const previous = session.connect;
|
|
2187
2285
|
const sandbox = await session.existingSandbox();
|
|
2188
|
-
let
|
|
2286
|
+
let attempt;
|
|
2287
|
+
let movesEndpoints = false;
|
|
2189
2288
|
if (sandbox) {
|
|
2190
|
-
claim = {
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
attempt: await sandbox.claim({ email: normalized }),
|
|
2195
|
-
claimExpires: sandbox.expiresAt
|
|
2196
|
-
};
|
|
2197
|
-
} else if (session.claim?.kind === "service_auth" && session.claim.email === normalized && session.claim.claimExpires.getTime() > Date.now()) {
|
|
2198
|
-
const attempt = await import_sdk2.WebhooksCC.agent.startClaim({
|
|
2199
|
-
baseUrl,
|
|
2200
|
-
claimToken: session.claim.claimToken
|
|
2201
|
-
});
|
|
2202
|
-
claim = { ...session.claim, attempt };
|
|
2289
|
+
const claim = await sandbox.claim({ email: normalized });
|
|
2290
|
+
session.connect = { via: "sandbox", sandbox, email: normalized, attempt: claim };
|
|
2291
|
+
attempt = claim;
|
|
2292
|
+
movesEndpoints = claim.movesEndpoints;
|
|
2203
2293
|
} else {
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2294
|
+
let pending = null;
|
|
2295
|
+
if (previous?.via === "connect" && previous.email === normalized) {
|
|
2296
|
+
pending = await previous.pending.restart().catch((error) => {
|
|
2297
|
+
if (error instanceof import_sdk2.AgentAuthError) return null;
|
|
2298
|
+
throw error;
|
|
2299
|
+
});
|
|
2300
|
+
}
|
|
2301
|
+
pending ?? (pending = await import_sdk2.WebhooksCC.connect({
|
|
2302
|
+
baseUrl: session.baseUrl,
|
|
2303
|
+
webhookUrl: session.webhookUrl,
|
|
2212
2304
|
email: normalized,
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
};
|
|
2305
|
+
clientName: session.clientName,
|
|
2306
|
+
credentialsFile: session.credentialsFile
|
|
2307
|
+
}));
|
|
2308
|
+
session.connect = { via: "connect", pending, email: normalized };
|
|
2309
|
+
attempt = pending;
|
|
2216
2310
|
}
|
|
2217
|
-
|
|
2218
|
-
const { verificationUri, userCode, expiresAt } = claim.attempt;
|
|
2311
|
+
const { verificationUri, userCode, expiresAt } = attempt;
|
|
2219
2312
|
return jsonContent({
|
|
2220
2313
|
verificationUri,
|
|
2221
2314
|
userCode,
|
|
2222
2315
|
expiresAt: expiresAt.toISOString(),
|
|
2223
2316
|
tellTheHuman: `Open ${verificationUri}, sign in or sign up as ${normalized}, and enter the code ${userCode}.`,
|
|
2224
2317
|
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.`,
|
|
2225
|
-
...
|
|
2318
|
+
...sandbox ? {
|
|
2319
|
+
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."
|
|
2320
|
+
} : {}
|
|
2226
2321
|
});
|
|
2227
2322
|
})
|
|
2228
2323
|
)
|
|
2229
2324
|
);
|
|
2230
|
-
|
|
2325
|
+
tools.push(
|
|
2231
2326
|
server.tool(
|
|
2232
2327
|
"wait_for_connection",
|
|
2233
2328
|
`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.`,
|
|
@@ -2235,28 +2330,22 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2235
2330
|
timeoutSeconds: import_zod3.z.number().int().min(1).max(MAX_WAIT_SECONDS).optional().describe(`How long to wait, in seconds (default ${DEFAULT_WAIT_SECONDS})`)
|
|
2236
2331
|
},
|
|
2237
2332
|
withErrorHandling(async ({ timeoutSeconds }) => {
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
}
|
|
2241
|
-
const claim = session.claim;
|
|
2242
|
-
if (!claim) {
|
|
2333
|
+
const pending = session.connect;
|
|
2334
|
+
if (!pending) {
|
|
2243
2335
|
throw new Error("Nothing to wait for: call connect_account first.");
|
|
2244
2336
|
}
|
|
2245
|
-
|
|
2337
|
+
const timeout = (timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3;
|
|
2338
|
+
const expiresAt = pending.via === "sandbox" ? pending.attempt.expiresAt : pending.pending.expiresAt;
|
|
2339
|
+
let client;
|
|
2246
2340
|
try {
|
|
2247
|
-
|
|
2248
|
-
baseUrl,
|
|
2249
|
-
claimToken: claim.claimToken,
|
|
2250
|
-
interval: claim.attempt.interval,
|
|
2251
|
-
timeout: (timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3
|
|
2252
|
-
});
|
|
2341
|
+
client = pending.via === "sandbox" ? await pending.sandbox.waitForClaim({ timeout }) : await pending.pending.wait({ timeout });
|
|
2253
2342
|
} catch (error) {
|
|
2254
2343
|
if (!(error instanceof import_sdk2.AgentAuthError)) throw error;
|
|
2255
2344
|
if (error.code === "timeout") {
|
|
2256
2345
|
return jsonContent({
|
|
2257
2346
|
connected: false,
|
|
2258
2347
|
status: "waiting",
|
|
2259
|
-
message: `Not connected yet. The code works until ${
|
|
2348
|
+
message: `Not connected yet. The code works until ${expiresAt.toISOString()}; call wait_for_connection again.`
|
|
2260
2349
|
});
|
|
2261
2350
|
}
|
|
2262
2351
|
if (error.code === "expired_token") {
|
|
@@ -2275,34 +2364,25 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2275
2364
|
}
|
|
2276
2365
|
throw error;
|
|
2277
2366
|
}
|
|
2278
|
-
const
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
getAccessToken: claimedTokens(baseUrl, claimed.identityAssertion, claimed.token)
|
|
2282
|
-
});
|
|
2283
|
-
session.connected = true;
|
|
2284
|
-
session.claim = null;
|
|
2285
|
-
session.forgetSandbox();
|
|
2286
|
-
for (const tool of registered) tool.remove();
|
|
2287
|
-
registerTools(server, client);
|
|
2288
|
-
options.onConnected?.(client);
|
|
2367
|
+
const movedEndpoints = pending.via === "sandbox" && pending.attempt.movesEndpoints;
|
|
2368
|
+
session.reset();
|
|
2369
|
+
showAccount(client);
|
|
2289
2370
|
return jsonContent({
|
|
2290
2371
|
connected: true,
|
|
2291
|
-
email:
|
|
2292
|
-
|
|
2293
|
-
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.`
|
|
2372
|
+
email: pending.email,
|
|
2373
|
+
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."}`
|
|
2294
2374
|
});
|
|
2295
2375
|
})
|
|
2296
2376
|
)
|
|
2297
2377
|
);
|
|
2298
|
-
|
|
2378
|
+
tools.push(
|
|
2299
2379
|
server.tool(
|
|
2300
2380
|
"about_sandbox",
|
|
2301
2381
|
"Explain what this agent can do without a webhooks.cc account, the sandbox limits, and how to connect an account. No network call.",
|
|
2302
2382
|
{},
|
|
2303
2383
|
withErrorHandling(async () => {
|
|
2304
2384
|
return jsonContent({
|
|
2305
|
-
mode: "sandbox (no WHK_API_KEY)",
|
|
2385
|
+
mode: "sandbox (no WHK_API_KEY, no saved connection)",
|
|
2306
2386
|
sandbox: {
|
|
2307
2387
|
maxEndpoints: SANDBOX.maxEndpoints,
|
|
2308
2388
|
requestsPerEndpoint: SANDBOX.requestsPerEndpoint,
|
|
@@ -2313,21 +2393,23 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2313
2393
|
notAvailable: "Mock responses, response rules, notifications, signature verification, email capture, forwarding, teams and sending webhooks."
|
|
2314
2394
|
},
|
|
2315
2395
|
connectAnAccount: [
|
|
2316
|
-
"Call connect_account with the human
|
|
2396
|
+
"Call connect_account with the email the human signs in to webhooks.cc with, and show them the link and code it returns.",
|
|
2317
2397
|
"Call wait_for_connection until it reports connected; the full tools for their account then replace the sandbox tools.",
|
|
2318
|
-
"Sandbox endpoints move into the account with their captured requests."
|
|
2398
|
+
"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)."
|
|
2319
2399
|
],
|
|
2400
|
+
savedIn: file ? `${file} (shared with the whk CLI and the SDKs): the sandbox and the connection survive restarts.` : "nowhere: this session only.",
|
|
2320
2401
|
withAnApiKey: "Set WHK_API_KEY to a whcc_ key from the dashboard and restart for the full tools from the start.",
|
|
2321
2402
|
docs: DOCS_URL,
|
|
2322
|
-
protocol: `${baseUrl}/auth.md`
|
|
2403
|
+
protocol: `${session.baseUrl}/auth.md`
|
|
2323
2404
|
});
|
|
2324
2405
|
})
|
|
2325
2406
|
)
|
|
2326
2407
|
);
|
|
2408
|
+
return tools;
|
|
2327
2409
|
}
|
|
2328
2410
|
|
|
2329
2411
|
// src/index.ts
|
|
2330
|
-
var VERSION = true ? "1.
|
|
2412
|
+
var VERSION = true ? "1.15.0" : "0.0.0-dev";
|
|
2331
2413
|
function createServer(options = {}) {
|
|
2332
2414
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
2333
2415
|
const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
|
|
@@ -2338,7 +2420,7 @@ function createServer(options = {}) {
|
|
|
2338
2420
|
{ debouncedNotificationMethods: ["notifications/tools/list_changed"] }
|
|
2339
2421
|
);
|
|
2340
2422
|
if (!apiKey) {
|
|
2341
|
-
registerSandboxTools(server, { baseUrl, webhookUrl });
|
|
2423
|
+
registerSandboxTools(server, { baseUrl, webhookUrl, credentialsFile: options.credentialsFile });
|
|
2342
2424
|
registerPrompts(server);
|
|
2343
2425
|
return server;
|
|
2344
2426
|
}
|
|
@@ -2367,13 +2449,16 @@ function mcpServerConfig(apiKey) {
|
|
|
2367
2449
|
return {
|
|
2368
2450
|
command: "npx",
|
|
2369
2451
|
args: ["-y", "@webhooks-cc/mcp"],
|
|
2370
|
-
env: { WHK_API_KEY: apiKey }
|
|
2452
|
+
...apiKey ? { env: { WHK_API_KEY: apiKey } } : {}
|
|
2371
2453
|
};
|
|
2372
2454
|
}
|
|
2455
|
+
function vscodeServerConfig(apiKey) {
|
|
2456
|
+
return { type: "stdio", ...mcpServerConfig(apiKey) };
|
|
2457
|
+
}
|
|
2373
2458
|
function resolveApiKey(flags) {
|
|
2374
2459
|
return flags["api-key"] ?? process.env.WHK_API_KEY ?? null;
|
|
2375
2460
|
}
|
|
2376
|
-
function mergeJsonConfig(filePath, serverName, serverConfig) {
|
|
2461
|
+
function mergeJsonConfig(filePath, serverName, serverConfig, key = "mcpServers") {
|
|
2377
2462
|
let existing = {};
|
|
2378
2463
|
if ((0, import_fs.existsSync)(filePath)) {
|
|
2379
2464
|
try {
|
|
@@ -2384,9 +2469,9 @@ function mergeJsonConfig(filePath, serverName, serverConfig) {
|
|
|
2384
2469
|
(0, import_fs.copyFileSync)(filePath, `${filePath}.bak`);
|
|
2385
2470
|
}
|
|
2386
2471
|
}
|
|
2387
|
-
const
|
|
2388
|
-
|
|
2389
|
-
existing
|
|
2472
|
+
const servers = existing[key] ?? {};
|
|
2473
|
+
servers[serverName] = serverConfig;
|
|
2474
|
+
existing[key] = servers;
|
|
2390
2475
|
const dir = (0, import_path.dirname)(filePath);
|
|
2391
2476
|
if (!(0, import_fs.existsSync)(dir)) (0, import_fs.mkdirSync)(dir, { recursive: true });
|
|
2392
2477
|
(0, import_fs.writeFileSync)(filePath, JSON.stringify(existing, null, 2) + "\n");
|
|
@@ -2423,8 +2508,7 @@ function setupClaudeCode(apiKey) {
|
|
|
2423
2508
|
"--transport",
|
|
2424
2509
|
"stdio",
|
|
2425
2510
|
"webhooks-cc",
|
|
2426
|
-
"-e",
|
|
2427
|
-
`WHK_API_KEY=${apiKey}`,
|
|
2511
|
+
...apiKey ? ["-e", `WHK_API_KEY=${apiKey}`] : [],
|
|
2428
2512
|
"--",
|
|
2429
2513
|
"npx",
|
|
2430
2514
|
"-y",
|
|
@@ -2439,7 +2523,7 @@ function setupClaudeCode(apiKey) {
|
|
|
2439
2523
|
console.error("Install it: npm install -g @anthropic-ai/claude-code");
|
|
2440
2524
|
console.error("\nManual alternative \u2014 run this command:");
|
|
2441
2525
|
console.error(
|
|
2442
|
-
` claude mcp add -s user --transport stdio webhooks-cc -e WHK_API_KEY=<your-key> -- npx -y @webhooks-cc/mcp`
|
|
2526
|
+
` claude mcp add -s user --transport stdio webhooks-cc${apiKey ? " -e WHK_API_KEY=<your-key>" : ""} -- npx -y @webhooks-cc/mcp`
|
|
2443
2527
|
);
|
|
2444
2528
|
process.exit(1);
|
|
2445
2529
|
}
|
|
@@ -2458,7 +2542,7 @@ function setupCursor(apiKey) {
|
|
|
2458
2542
|
}
|
|
2459
2543
|
function setupVSCode(apiKey) {
|
|
2460
2544
|
const configPath = (0, import_path.join)(process.cwd(), ".vscode", "mcp.json");
|
|
2461
|
-
mergeJsonConfig(configPath, "webhooks-cc",
|
|
2545
|
+
mergeJsonConfig(configPath, "webhooks-cc", vscodeServerConfig(apiKey), "servers");
|
|
2462
2546
|
console.log(`Done! Config written to ${configPath}`);
|
|
2463
2547
|
console.log("Reload VS Code window for changes to take effect.");
|
|
2464
2548
|
}
|
|
@@ -2470,8 +2554,7 @@ function setupCodex(apiKey) {
|
|
|
2470
2554
|
"mcp",
|
|
2471
2555
|
"add",
|
|
2472
2556
|
"webhooks-cc",
|
|
2473
|
-
"-e",
|
|
2474
|
-
`WHK_API_KEY=${apiKey}`,
|
|
2557
|
+
...apiKey ? ["-e", `WHK_API_KEY=${apiKey}`] : [],
|
|
2475
2558
|
"--",
|
|
2476
2559
|
"npx",
|
|
2477
2560
|
"-y",
|
|
@@ -2484,13 +2567,13 @@ function setupCodex(apiKey) {
|
|
|
2484
2567
|
console.error("\nFailed to run 'codex mcp add'. Is OpenAI Codex CLI installed?");
|
|
2485
2568
|
console.error("\nManual alternative \u2014 add to ~/.codex/config.toml:");
|
|
2486
2569
|
console.error(`
|
|
2487
|
-
[
|
|
2570
|
+
[mcp_servers.webhooks-cc]
|
|
2488
2571
|
command = "npx"
|
|
2489
2572
|
args = ["-y", "@webhooks-cc/mcp"]
|
|
2490
|
-
|
|
2491
|
-
[
|
|
2573
|
+
${apiKey ? `
|
|
2574
|
+
[mcp_servers.webhooks-cc.env]
|
|
2492
2575
|
WHK_API_KEY = "<your-key>"
|
|
2493
|
-
`);
|
|
2576
|
+
` : ""}`);
|
|
2494
2577
|
process.exit(1);
|
|
2495
2578
|
}
|
|
2496
2579
|
}
|
|
@@ -2507,6 +2590,9 @@ function printUsage() {
|
|
|
2507
2590
|
Usage:
|
|
2508
2591
|
npx @webhooks-cc/mcp setup <tool> [--api-key <key>]
|
|
2509
2592
|
|
|
2593
|
+
Without a key the server starts in the keyless agent sandbox; your agent can
|
|
2594
|
+
connect your account later (connect_account), and the connection is saved.
|
|
2595
|
+
|
|
2510
2596
|
Tools:
|
|
2511
2597
|
claude-code Claude Code CLI (runs 'claude mcp add')
|
|
2512
2598
|
claude-desktop Claude Desktop app
|
|
@@ -2516,9 +2602,10 @@ Tools:
|
|
|
2516
2602
|
windsurf Windsurf editor
|
|
2517
2603
|
|
|
2518
2604
|
Options:
|
|
2519
|
-
--api-key <key> Your webhooks.cc API key (or set WHK_API_KEY env var)
|
|
2605
|
+
--api-key <key> Your webhooks.cc API key (or set WHK_API_KEY env var); optional
|
|
2520
2606
|
|
|
2521
2607
|
Examples:
|
|
2608
|
+
npx @webhooks-cc/mcp setup claude-code
|
|
2522
2609
|
npx @webhooks-cc/mcp setup claude-code --api-key whcc_abc123
|
|
2523
2610
|
WHK_API_KEY=whcc_abc123 npx @webhooks-cc/mcp setup cursor
|
|
2524
2611
|
npx @webhooks-cc/mcp setup vscode --api-key whcc_abc123
|
|
@@ -2546,14 +2633,13 @@ function runSetup(args2) {
|
|
|
2546
2633
|
process.exit(tool ? 1 : 0);
|
|
2547
2634
|
}
|
|
2548
2635
|
const apiKey = resolveApiKey(flags);
|
|
2549
|
-
if (
|
|
2550
|
-
console.error("Error: API key required. Pass --api-key or set WHK_API_KEY env var.");
|
|
2551
|
-
console.error("Get your API key at https://webhooks.cc/account/api-keys\n");
|
|
2552
|
-
process.exit(1);
|
|
2553
|
-
}
|
|
2554
|
-
validateApiKeyFormat(apiKey);
|
|
2636
|
+
if (apiKey) validateApiKeyFormat(apiKey);
|
|
2555
2637
|
console.log(`Setting up webhooks-cc MCP server for ${tool}...
|
|
2556
2638
|
`);
|
|
2639
|
+
if (!apiKey) {
|
|
2640
|
+
console.log("No API key: the server starts in the keyless agent sandbox. Your agent can");
|
|
2641
|
+
console.log("connect your account later with connect_account; the connection is saved.\n");
|
|
2642
|
+
}
|
|
2557
2643
|
switch (tool) {
|
|
2558
2644
|
case "claude-code":
|
|
2559
2645
|
setupClaudeCode(apiKey);
|
|
@@ -2591,17 +2677,25 @@ Usage:
|
|
|
2591
2677
|
npx @webhooks-cc/mcp --help Show this help
|
|
2592
2678
|
|
|
2593
2679
|
Environment:
|
|
2594
|
-
WHK_API_KEY Your webhooks.cc API key (
|
|
2680
|
+
WHK_API_KEY Your webhooks.cc API key (optional; without it the server
|
|
2681
|
+
starts connected to the account your agent was connected
|
|
2682
|
+
to before, or in the keyless agent sandbox)
|
|
2683
|
+
WHK_AGENT_FILE Where the sandbox and the connection are saved (default:
|
|
2684
|
+
~/.config/whk/agent.json, shared with the whk CLI and SDKs)
|
|
2595
2685
|
WHK_WEBHOOK_URL Custom webhook receiver URL (optional)
|
|
2596
2686
|
WHK_BASE_URL Custom API base URL (optional)
|
|
2597
2687
|
|
|
2598
2688
|
Setup:
|
|
2599
|
-
npx @webhooks-cc/mcp setup claude-code
|
|
2600
|
-
npx @webhooks-cc/mcp setup cursor
|
|
2601
|
-
npx @webhooks-cc/mcp setup vscode
|
|
2602
|
-
npx @webhooks-cc/mcp setup codex
|
|
2603
|
-
npx @webhooks-cc/mcp setup windsurf
|
|
2604
|
-
npx @webhooks-cc/mcp setup claude-desktop
|
|
2689
|
+
npx @webhooks-cc/mcp setup claude-code
|
|
2690
|
+
npx @webhooks-cc/mcp setup cursor
|
|
2691
|
+
npx @webhooks-cc/mcp setup vscode
|
|
2692
|
+
npx @webhooks-cc/mcp setup codex
|
|
2693
|
+
npx @webhooks-cc/mcp setup windsurf
|
|
2694
|
+
npx @webhooks-cc/mcp setup claude-desktop
|
|
2695
|
+
(add --api-key whcc_... to start with your account's tools)
|
|
2696
|
+
|
|
2697
|
+
Hosted agents (claude.ai, ChatGPT): add https://webhooks.cc/mcp as a remote
|
|
2698
|
+
MCP server instead; you approve it once in your browser.
|
|
2605
2699
|
|
|
2606
2700
|
Get your API key at https://webhooks.cc/account/api-keys
|
|
2607
2701
|
`);
|