@webhooks-cc/mcp 1.16.0 → 1.18.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 +14 -2
- package/dist/bin/mcp.js +240 -6
- package/dist/{chunk-4LEPLCW5.mjs → chunk-GTEE7L2Y.mjs} +233 -2
- package/dist/index.js +240 -6
- package/dist/index.mjs +13 -5
- package/dist/tools.d.mts +22 -2
- package/dist/tools.d.ts +22 -2
- package/dist/tools.js +238 -4
- package/dist/tools.mjs +9 -3
- package/package.json +5 -5
package/dist/tools.js
CHANGED
|
@@ -32,9 +32,12 @@ __export(tools_exports, {
|
|
|
32
32
|
payloadSchemaResult: () => payloadSchemaResult,
|
|
33
33
|
payloadSchemaShape: () => payloadSchemaShape,
|
|
34
34
|
registerTools: () => registerTools,
|
|
35
|
+
shareHints: () => shareHints,
|
|
35
36
|
textContent: () => textContent,
|
|
37
|
+
toolWords: () => toolWords,
|
|
36
38
|
waitForRequestShape: () => waitForRequestShape,
|
|
37
|
-
withErrorHandling: () => withErrorHandling
|
|
39
|
+
withErrorHandling: () => withErrorHandling,
|
|
40
|
+
withShareHints: () => withShareHints
|
|
38
41
|
});
|
|
39
42
|
module.exports = __toCommonJS(tools_exports);
|
|
40
43
|
var import_zod = require("zod");
|
|
@@ -76,6 +79,15 @@ function omitHeaders(holder) {
|
|
|
76
79
|
holder.headers = {};
|
|
77
80
|
holder.headersOmitted = `${count} headers left out to fit the output`;
|
|
78
81
|
}
|
|
82
|
+
function trimAuth(holder) {
|
|
83
|
+
const auth = holder.auth;
|
|
84
|
+
if (auth && typeof auth === "object" && !Array.isArray(auth)) {
|
|
85
|
+
const verdicts = { ...auth };
|
|
86
|
+
delete verdicts.setup;
|
|
87
|
+
delete verdicts.authenticationResults;
|
|
88
|
+
holder.auth = verdicts;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
79
91
|
function trimSpam(holder) {
|
|
80
92
|
const spam = holder.spam;
|
|
81
93
|
if (spam && typeof spam === "object" && !Array.isArray(spam)) {
|
|
@@ -223,6 +235,7 @@ function compactRequest(request) {
|
|
|
223
235
|
shrinkToFit(root, budget, [
|
|
224
236
|
() => trimParsed(compact),
|
|
225
237
|
() => omitHeaders(compact),
|
|
238
|
+
() => trimAuth(email),
|
|
226
239
|
() => trimSpam(email),
|
|
227
240
|
() => capLists(email),
|
|
228
241
|
() => cutStringToFit(root, email, "text", "textTruncated", budget, TEXT_FLOOR),
|
|
@@ -787,7 +800,7 @@ function emailDetail(client, email, view, includeHtml) {
|
|
|
787
800
|
]);
|
|
788
801
|
return detail;
|
|
789
802
|
}
|
|
790
|
-
var LOCAL_ONLY_TOOLS = /* @__PURE__ */ new Set(["send_to", "replay_request"]);
|
|
803
|
+
var LOCAL_ONLY_TOOLS = /* @__PURE__ */ new Set(["send_to", "replay_request", "share_port"]);
|
|
791
804
|
var TARGET_URL_TOOLS = /* @__PURE__ */ new Set(["render_email", "test_webhook_flow"]);
|
|
792
805
|
var REMOTE_MAX_WAIT_MS = 5e4;
|
|
793
806
|
var REMOTE_MIN_POLL_MS = 1e3;
|
|
@@ -845,9 +858,78 @@ function toolTarget(target, remote, handles) {
|
|
|
845
858
|
}
|
|
846
859
|
};
|
|
847
860
|
}
|
|
861
|
+
function shareSummary(live) {
|
|
862
|
+
const notes = [];
|
|
863
|
+
if (live.notice) {
|
|
864
|
+
notes.push(
|
|
865
|
+
"The first time each browser opens the link, it sees a short notice from webhooks.cc and clicks Open the preview (Free, and paid accounts in their first 7 days)."
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
if (live.password) notes.push("Visitors need the password.");
|
|
869
|
+
if (live.expiresAt) {
|
|
870
|
+
notes.push(
|
|
871
|
+
`The plan stops this share at ${live.expiresAt}; reconnecting does not extend it, and starting it again gives a new link.`
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
notes.push("It runs until stop_share or the end of this MCP server.");
|
|
875
|
+
return {
|
|
876
|
+
url: live.url,
|
|
877
|
+
name: live.name,
|
|
878
|
+
port: live.port,
|
|
879
|
+
expiresAt: live.expiresAt,
|
|
880
|
+
notice: live.notice,
|
|
881
|
+
password: live.password,
|
|
882
|
+
notes: notes.join(" ")
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
var REMOTE_SHARE = "`whk share <port>` on the person's computer (or the local MCP server, npx -y @webhooks-cc/mcp, which has share_port)";
|
|
886
|
+
function shareHints(list, remote = false) {
|
|
887
|
+
const start = remote ? REMOTE_SHARE : "share_port";
|
|
888
|
+
const hints = [];
|
|
889
|
+
const limits = list.limits;
|
|
890
|
+
const usage = list.usage;
|
|
891
|
+
const previews = list.shares.filter((s) => s.kind !== "live" && !s.endedAt && s.mine !== false);
|
|
892
|
+
if (usage?.over) {
|
|
893
|
+
hints.push(
|
|
894
|
+
`This month's shared data is used up, so previews are paused until ${usage.resetsAt}. A larger plan (Pro 25 GB, Business 150 GB) raises it; the person upgrades on webhooks.cc.`
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
if (limits && previews.length >= limits.shares) {
|
|
898
|
+
hints.push(
|
|
899
|
+
`All ${limits.shares} of the plan's preview slots are in use: stop one with stop_share (its id is in shares) before ${start}.`
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
if (limits && limits.sessionMinutes) {
|
|
903
|
+
hints.push(
|
|
904
|
+
`Previews on this plan stop after ${limits.sessionMinutes} minutes; ${start} again gives a new link.`
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
if (limits && !limits.passwords) {
|
|
908
|
+
hints.push("Passwords and reserved names come with Pro and Business.");
|
|
909
|
+
}
|
|
910
|
+
return hints;
|
|
911
|
+
}
|
|
912
|
+
function toolWords(hint, remote = false) {
|
|
913
|
+
const words = hint.replace(/whk shares stop(?: <[^>]*>)?/g, "stop_share").replace(/whk shares (?:ls|list)/g, "list_shares").replace(/whk shares log(?: <[^>]*>)?/g, "share_requests").replace(/whk names reserve(?: <[^>]*>)?(?: --team <[^>]*>)?/g, "reserve_name").replace(/whk names release(?: <[^>]*>)?/g, "release_name").replace(/whk names password(?: <[^>]*>)? --clear/g, "clear_name_password").replace(/whk names password(?: <[^>]*>)?/g, "set_name_password").replace(/whk names ls/g, "list_names");
|
|
914
|
+
if (remote) return words;
|
|
915
|
+
return words.replace(/whk share <port> --password/g, "share_port with a password").replace(/whk share <port>/g, "share_port");
|
|
916
|
+
}
|
|
917
|
+
function withShareHints(list, remote = false) {
|
|
918
|
+
const server = (list.hints ?? []).map((h) => toolWords(h, remote));
|
|
919
|
+
const ours = shareHints(list, remote);
|
|
920
|
+
if (list.suspended && !server.some((h) => /suspended/i.test(h))) {
|
|
921
|
+
ours.unshift(
|
|
922
|
+
`Sharing is suspended on this account: ${remote ? "new previews are" : "share_port is"} refused. The person can write to support@webhooks.cc.`
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
const hints = [.../* @__PURE__ */ new Set([...server, ...ours])];
|
|
926
|
+
return hints.length > 0 ? { ...list, hints } : { ...list };
|
|
927
|
+
}
|
|
848
928
|
function registerTools(target, client, options = {}) {
|
|
849
929
|
const handles = [];
|
|
850
|
-
const
|
|
930
|
+
const remote = options.remote === true;
|
|
931
|
+
const server = toolTarget(target, remote, handles);
|
|
932
|
+
const shareWith = remote ? REMOTE_SHARE : "share_port";
|
|
851
933
|
server.tool(
|
|
852
934
|
"create_endpoint",
|
|
853
935
|
"Create a webhook endpoint. Returns the endpoint slug, URL, and metadata.",
|
|
@@ -968,6 +1050,18 @@ function registerTools(target, client, options = {}) {
|
|
|
968
1050
|
return jsonContent(endpoint);
|
|
969
1051
|
})
|
|
970
1052
|
);
|
|
1053
|
+
server.tool(
|
|
1054
|
+
"favorite_endpoint",
|
|
1055
|
+
"Mark an endpoint as one of your favourites, or unmark it with favorite: false. A favourite is your own choice, so endpoints shared with you through teams can be marked too; list_endpoints and get_endpoint show it as favorite. Idempotent.",
|
|
1056
|
+
{
|
|
1057
|
+
slug: import_zod.z.string().describe("The endpoint slug"),
|
|
1058
|
+
favorite: import_zod.z.boolean().optional().describe("true marks it as a favourite (the default), false unmarks it")
|
|
1059
|
+
},
|
|
1060
|
+
withErrorHandling(async ({ slug, favorite }) => {
|
|
1061
|
+
const result = favorite === false ? await client.endpoints.unfavorite(slug) : await client.endpoints.favorite(slug);
|
|
1062
|
+
return jsonContent({ slug, favorite: result.favorite });
|
|
1063
|
+
})
|
|
1064
|
+
);
|
|
971
1065
|
server.tool(
|
|
972
1066
|
"update_endpoint",
|
|
973
1067
|
"Update an endpoint name, mock response, conditional response rules, signing configuration, whether codes and links are picked out of its emails, or the named fields picked out of them.",
|
|
@@ -1896,6 +1990,143 @@ function registerTools(target, client, options = {}) {
|
|
|
1896
1990
|
);
|
|
1897
1991
|
})
|
|
1898
1992
|
);
|
|
1993
|
+
const started = /* @__PURE__ */ new Map();
|
|
1994
|
+
server.tool(
|
|
1995
|
+
"share_port",
|
|
1996
|
+
"Share a port on this computer at a public link (https://<name>.sharehooks.cc) by running the webhooks.cc CLI (whk share), so a person can open the site you are building. Returns the link. It keeps running until stop_share, the plan's session limit, or the end of this MCP server. Asking again for the same port without a name or password returns the running link; with a name or password, the running share on that port stops and a new one starts. Free accounts get a random name ending in -free, a 2-hour limit and a notice page before the site; Pro and Business can use a reserved name and a password. Needs whk installed on this computer. For a webhook provider that must get your local app's real answer, use `whk tunnel <port> --live` instead.",
|
|
1997
|
+
{
|
|
1998
|
+
port: import_zod.z.number().int().min(1).max(65535).describe("The local port the site runs on"),
|
|
1999
|
+
name: import_zod.z.string().optional().describe("A name you reserved (Pro and Business); leave out for a random one"),
|
|
2000
|
+
password: import_zod.z.string().min(8).max(200).optional().describe("What visitors must type (Pro and Business), 8 to 200 characters")
|
|
2001
|
+
},
|
|
2002
|
+
withErrorHandling(async ({ port, name, password }) => {
|
|
2003
|
+
const running = started.get(port);
|
|
2004
|
+
if (running && !name && !password) {
|
|
2005
|
+
return jsonContent({ ...shareSummary(running), alreadyRunning: true });
|
|
2006
|
+
}
|
|
2007
|
+
if (running) {
|
|
2008
|
+
started.delete(port);
|
|
2009
|
+
await running.stop();
|
|
2010
|
+
}
|
|
2011
|
+
let live;
|
|
2012
|
+
try {
|
|
2013
|
+
live = await client.shares.start({
|
|
2014
|
+
port,
|
|
2015
|
+
...name ? { name } : {},
|
|
2016
|
+
...password ? { password } : {},
|
|
2017
|
+
...process.env.WHK_PATH ? { whkPath: process.env.WHK_PATH } : {}
|
|
2018
|
+
});
|
|
2019
|
+
} catch (error) {
|
|
2020
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2021
|
+
throw new Error(
|
|
2022
|
+
`${message} With these tools: list_shares shows what runs and the plan's limits, stop_share frees a slot, list_names shows the names you can use.`
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
2025
|
+
started.set(port, live);
|
|
2026
|
+
void live.closed.then(() => {
|
|
2027
|
+
if (started.get(port) === live) started.delete(port);
|
|
2028
|
+
});
|
|
2029
|
+
const summary = shareSummary(live);
|
|
2030
|
+
try {
|
|
2031
|
+
const list = await client.shares.list({ state: "live" });
|
|
2032
|
+
summary.limits = list.limits;
|
|
2033
|
+
summary.usage = list.usage;
|
|
2034
|
+
const hints = shareHints(list);
|
|
2035
|
+
if (hints.length > 0) summary.hints = hints;
|
|
2036
|
+
} catch {
|
|
2037
|
+
}
|
|
2038
|
+
return jsonContent(summary);
|
|
2039
|
+
})
|
|
2040
|
+
);
|
|
2041
|
+
server.tool(
|
|
2042
|
+
"list_shares",
|
|
2043
|
+
`List your public previews of local ports (whk share${remote ? "" : ", share_port"}) and live tunnels (whk tunnel --live): live ones first, then the latest that ended, with this month's shared data and your plan's sharing limits. Not the same as share_endpoint, which shares an endpoint with a team.`,
|
|
2044
|
+
{
|
|
2045
|
+
state: import_zod.z.enum(["live", "ended"]).optional().describe("Only the running ones, or only the ended ones")
|
|
2046
|
+
},
|
|
2047
|
+
withErrorHandling(async ({ state }) => {
|
|
2048
|
+
return jsonContent(withShareHints(await client.shares.list(state ? { state } : {}), remote));
|
|
2049
|
+
})
|
|
2050
|
+
);
|
|
2051
|
+
server.tool(
|
|
2052
|
+
"stop_share",
|
|
2053
|
+
"Stop a public preview or live tunnel by its id (from list_shares). The CLI running it is told why and exits.",
|
|
2054
|
+
{
|
|
2055
|
+
id: import_zod.z.string().describe("The share's id, from list_shares")
|
|
2056
|
+
},
|
|
2057
|
+
withErrorHandling(async ({ id }) => {
|
|
2058
|
+
const result = await client.shares.stop(id);
|
|
2059
|
+
return jsonContent({
|
|
2060
|
+
id,
|
|
2061
|
+
stopped: result.stopped,
|
|
2062
|
+
edgeReached: result.edgeReached,
|
|
2063
|
+
note: result.stopped ? result.edgeReached ? "Stopped; its CLI was told and exits." : "Stopped; the edge could not be told at once, so it ends within half a minute." : "It had already ended."
|
|
2064
|
+
});
|
|
2065
|
+
})
|
|
2066
|
+
);
|
|
2067
|
+
server.tool(
|
|
2068
|
+
"share_requests",
|
|
2069
|
+
"The last requests visitors made to a running preview (from list_shares), newest first: method, path, status, time, size, country, device and a number per visitor, never an address. Use it to see whether the person opened the link and what failed for them. `available` is false when the log could not be read just now.",
|
|
2070
|
+
{
|
|
2071
|
+
id: import_zod.z.string().describe("The share's id, from list_shares")
|
|
2072
|
+
},
|
|
2073
|
+
withErrorHandling(async ({ id }) => {
|
|
2074
|
+
return jsonContent(await client.shares.requests(id));
|
|
2075
|
+
})
|
|
2076
|
+
);
|
|
2077
|
+
server.tool(
|
|
2078
|
+
"list_names",
|
|
2079
|
+
`List the reserved preview names you can share under (Pro and Business): your own and your teams', each with its link, whether it has a password, whether a preview runs under it now, and whether your plan still covers it (usable). Includes how many your plan allows and how many you hold. Share under one with ${remote ? "`whk share <port> --name <name>` on the person's computer (or the local MCP server's share_port)" : "share_port and its name"}.`,
|
|
2080
|
+
{},
|
|
2081
|
+
withErrorHandling(async () => {
|
|
2082
|
+
return jsonContent(await client.names.list());
|
|
2083
|
+
})
|
|
2084
|
+
);
|
|
2085
|
+
server.tool(
|
|
2086
|
+
"reserve_name",
|
|
2087
|
+
`Reserve a preview name, so ${shareWith} can use the link https://<name>.sharehooks.cc (Pro and Business). Lowercase letters, digits and dashes; names that look like random ones or end in -free are refused. With team, the name belongs to that team and every member may share under it.`,
|
|
2088
|
+
{
|
|
2089
|
+
name: import_zod.z.string().min(1).max(63).describe("The name: lowercase letters, digits and dashes"),
|
|
2090
|
+
team: import_zod.z.string().optional().describe("A team's id or name (from list_teams), to keep the name inside that team")
|
|
2091
|
+
},
|
|
2092
|
+
withErrorHandling(async ({ name, team }) => {
|
|
2093
|
+
return jsonContent(await client.names.reserve(name, team ? { team } : {}));
|
|
2094
|
+
})
|
|
2095
|
+
);
|
|
2096
|
+
server.tool(
|
|
2097
|
+
"release_name",
|
|
2098
|
+
"Release a reserved preview name you kept (or, for a team name, as the team's owner). A preview running under it stops, and the link stops working.",
|
|
2099
|
+
{
|
|
2100
|
+
name: import_zod.z.string().min(1).max(63).describe("The name, from list_names")
|
|
2101
|
+
},
|
|
2102
|
+
withErrorHandling(async ({ name }) => {
|
|
2103
|
+
await client.names.release(name);
|
|
2104
|
+
return jsonContent({ released: name });
|
|
2105
|
+
})
|
|
2106
|
+
);
|
|
2107
|
+
server.tool(
|
|
2108
|
+
"set_name_password",
|
|
2109
|
+
"Put a password on every preview under a reserved name (Pro and Business): visitors must type it before the page opens. The password is not shown back; give it to the person yourself.",
|
|
2110
|
+
{
|
|
2111
|
+
name: import_zod.z.string().min(1).max(63).describe("The name, from list_names"),
|
|
2112
|
+
password: import_zod.z.string().min(8).max(200).describe("What visitors must type, 8 to 200 characters")
|
|
2113
|
+
},
|
|
2114
|
+
withErrorHandling(async ({ name, password }) => {
|
|
2115
|
+
await client.names.setPassword(name, password);
|
|
2116
|
+
return jsonContent({ name, password: true });
|
|
2117
|
+
})
|
|
2118
|
+
);
|
|
2119
|
+
server.tool(
|
|
2120
|
+
"clear_name_password",
|
|
2121
|
+
"Take the password off a reserved name, so its previews open without one.",
|
|
2122
|
+
{
|
|
2123
|
+
name: import_zod.z.string().min(1).max(63).describe("The name, from list_names")
|
|
2124
|
+
},
|
|
2125
|
+
withErrorHandling(async ({ name }) => {
|
|
2126
|
+
await client.names.clearPassword(name);
|
|
2127
|
+
return jsonContent({ name, password: false });
|
|
2128
|
+
})
|
|
2129
|
+
);
|
|
1899
2130
|
server.tool(
|
|
1900
2131
|
"describe",
|
|
1901
2132
|
"Describe all available SDK operations, parameters, and types.",
|
|
@@ -1921,7 +2152,10 @@ function registerTools(target, client, options = {}) {
|
|
|
1921
2152
|
payloadSchemaResult,
|
|
1922
2153
|
payloadSchemaShape,
|
|
1923
2154
|
registerTools,
|
|
2155
|
+
shareHints,
|
|
1924
2156
|
textContent,
|
|
2157
|
+
toolWords,
|
|
1925
2158
|
waitForRequestShape,
|
|
1926
|
-
withErrorHandling
|
|
2159
|
+
withErrorHandling,
|
|
2160
|
+
withShareHints
|
|
1927
2161
|
});
|
package/dist/tools.mjs
CHANGED
|
@@ -11,10 +11,13 @@ import {
|
|
|
11
11
|
payloadSchemaResult,
|
|
12
12
|
payloadSchemaShape,
|
|
13
13
|
registerTools,
|
|
14
|
+
shareHints,
|
|
14
15
|
textContent,
|
|
16
|
+
toolWords,
|
|
15
17
|
waitForRequestShape,
|
|
16
|
-
withErrorHandling
|
|
17
|
-
|
|
18
|
+
withErrorHandling,
|
|
19
|
+
withShareHints
|
|
20
|
+
} from "./chunk-GTEE7L2Y.mjs";
|
|
18
21
|
export {
|
|
19
22
|
REMOTE_MAX_WAIT_MS,
|
|
20
23
|
REMOTE_MIN_POLL_MS,
|
|
@@ -28,7 +31,10 @@ export {
|
|
|
28
31
|
payloadSchemaResult,
|
|
29
32
|
payloadSchemaShape,
|
|
30
33
|
registerTools,
|
|
34
|
+
shareHints,
|
|
31
35
|
textContent,
|
|
36
|
+
toolWords,
|
|
32
37
|
waitForRequestShape,
|
|
33
|
-
withErrorHandling
|
|
38
|
+
withErrorHandling,
|
|
39
|
+
withShareHints
|
|
34
40
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webhooks-cc/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"mcpName": "io.github.kroqdotdev/webhooks-cc",
|
|
5
5
|
"description": "MCP server for webhooks.cc — AI agent integration for webhook testing",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -42,15 +42,15 @@
|
|
|
42
42
|
},
|
|
43
43
|
"homepage": "https://webhooks.cc",
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@modelcontextprotocol/sdk": "^1.32.
|
|
45
|
+
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
46
46
|
"zod": "^4.6.7",
|
|
47
|
-
"@webhooks-cc/sdk": "1.
|
|
47
|
+
"@webhooks-cc/sdk": "1.22.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@types/node": "^26.6.
|
|
50
|
+
"@types/node": "^26.6.6",
|
|
51
51
|
"tsup": "^9.0.0",
|
|
52
52
|
"typescript": "^6.0.3",
|
|
53
|
-
"vitest": "^5.0
|
|
53
|
+
"vitest": "^5.1.0"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"build": "tsup",
|