@webhooks-cc/mcp 1.17.0 → 1.19.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 +13 -2
- package/dist/bin/mcp.js +220 -4
- package/dist/{chunk-ZAPNMGCE.mjs → chunk-GREHE3PB.mjs} +222 -3
- package/dist/index.js +220 -4
- package/dist/index.mjs +2 -2
- package/dist/tools.d.mts +22 -2
- package/dist/tools.d.ts +22 -2
- package/dist/tools.js +227 -5
- package/dist/tools.mjs +9 -3
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ For other agents, `npx skills add kroqdotdev/webhooks-cc` installs the same skil
|
|
|
34
34
|
- **claude.ai and Claude Desktop**: Settings, Connectors, add a custom connector with the URL `https://webhooks.cc/mcp`
|
|
35
35
|
- **ChatGPT**: add it as a custom connector (developer mode)
|
|
36
36
|
|
|
37
|
-
A client that can send headers may use an API key instead: `Authorization: Bearer whcc_...`. The remote server has
|
|
37
|
+
A client that can send headers may use an API key instead: `Authorization: Bearer whcc_...`. The remote server has 54 tools: `send_to`, `replay_request` and `share_port` are left out and `targetUrl` is refused, because a hosted server cannot reach your machine, and waits are capped at 50 seconds. It needs an account (no sandbox), and serves tools only, without prompts or resources.
|
|
38
38
|
|
|
39
39
|
### One-click
|
|
40
40
|
|
|
@@ -83,7 +83,7 @@ Leave out `env` to start without a key. Get your API key at [webhooks.cc/account
|
|
|
83
83
|
|
|
84
84
|
## What it exposes
|
|
85
85
|
|
|
86
|
-
The server exposes `
|
|
86
|
+
The server exposes `57` tools, `3` prompts, and `3` resource surfaces with an API key, the same `57` tools and the prompts with a saved connection, and `11` sandbox tools otherwise. The remote server exposes `54` tools.
|
|
87
87
|
|
|
88
88
|
### Endpoint tools
|
|
89
89
|
|
|
@@ -155,6 +155,17 @@ retried and logged (owner only):
|
|
|
155
155
|
|
|
156
156
|
Endpoints shared with you already appear in `list_endpoints` (with `fromTeam`) and work with every request tool except `clear_requests`, which stays with the owner. Invites are not exposed: accepting one claims a paid seat.
|
|
157
157
|
|
|
158
|
+
### Sharing tools
|
|
159
|
+
|
|
160
|
+
- `share_port`: shares a local port at a `https://<name>.sharehooks.cc` link by running `whk share` (local server only; needs [whk](https://webhooks.cc/installation) installed, or `WHK_PATH` naming it). The share runs until `stop_share`, the plan's session limit, or the end of the server. Free shares get a random name ending in `-free`, two hours, and a notice page before the site; Pro and Business can pass a reserved `name` and a `password`.
|
|
161
|
+
- `list_shares`: running and recent previews and live tunnels, this month's shared data, the plan's sharing limits, and `hints` with the next step (the server's own in tool names, plus slots or data running out).
|
|
162
|
+
- `stop_share`: stops a preview or live tunnel, and says whether it had already ended.
|
|
163
|
+
- `share_requests`: the last requests visitors made to a running preview (no addresses).
|
|
164
|
+
- `list_names`, `reserve_name` (optionally inside a team), `release_name`: reserved preview names (Pro and Business).
|
|
165
|
+
- `set_name_password`, `clear_name_password`: the password visitors need for every preview under a name. The password is never echoed back.
|
|
166
|
+
|
|
167
|
+
Everything the dashboard's Shares page does is here, so an agent never needs it; only `share_port` needs the local server.
|
|
168
|
+
|
|
158
169
|
### Discovery and account tools
|
|
159
170
|
|
|
160
171
|
- `list_provider_templates`
|
package/dist/bin/mcp.js
CHANGED
|
@@ -158,6 +158,15 @@ function omitHeaders(holder) {
|
|
|
158
158
|
holder.headers = {};
|
|
159
159
|
holder.headersOmitted = `${count} headers left out to fit the output`;
|
|
160
160
|
}
|
|
161
|
+
function trimAuth(holder) {
|
|
162
|
+
const auth = holder.auth;
|
|
163
|
+
if (auth && typeof auth === "object" && !Array.isArray(auth)) {
|
|
164
|
+
const verdicts = { ...auth };
|
|
165
|
+
delete verdicts.setup;
|
|
166
|
+
delete verdicts.authenticationResults;
|
|
167
|
+
holder.auth = verdicts;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
161
170
|
function trimSpam(holder) {
|
|
162
171
|
const spam = holder.spam;
|
|
163
172
|
if (spam && typeof spam === "object" && !Array.isArray(spam)) {
|
|
@@ -305,6 +314,7 @@ function compactRequest(request) {
|
|
|
305
314
|
shrinkToFit(root, budget, [
|
|
306
315
|
() => trimParsed(compact),
|
|
307
316
|
() => omitHeaders(compact),
|
|
317
|
+
() => trimAuth(email),
|
|
308
318
|
() => trimSpam(email),
|
|
309
319
|
() => capLists(email),
|
|
310
320
|
() => cutStringToFit(root, email, "text", "textTruncated", budget, TEXT_FLOOR),
|
|
@@ -446,7 +456,7 @@ var import_zod3 = require("zod");
|
|
|
446
456
|
var import_sdk2 = require("@webhooks-cc/sdk");
|
|
447
457
|
|
|
448
458
|
// src/version.ts
|
|
449
|
-
var VERSION = true ? "1.
|
|
459
|
+
var VERSION = true ? "1.19.0" : "0.0.0-dev";
|
|
450
460
|
var MCP_USER_AGENT = `webhooks-cc-mcp/${VERSION}`;
|
|
451
461
|
|
|
452
462
|
// src/tools.ts
|
|
@@ -1000,7 +1010,7 @@ function emailDetail(client, email, view, includeHtml) {
|
|
|
1000
1010
|
]);
|
|
1001
1011
|
return detail;
|
|
1002
1012
|
}
|
|
1003
|
-
var LOCAL_ONLY_TOOLS = /* @__PURE__ */ new Set(["send_to", "replay_request"]);
|
|
1013
|
+
var LOCAL_ONLY_TOOLS = /* @__PURE__ */ new Set(["send_to", "replay_request", "share_port"]);
|
|
1004
1014
|
var TARGET_URL_TOOLS = /* @__PURE__ */ new Set(["render_email", "test_webhook_flow"]);
|
|
1005
1015
|
var REMOTE_MAX_WAIT_MS = 5e4;
|
|
1006
1016
|
var REMOTE_MIN_POLL_MS = 1e3;
|
|
@@ -1058,9 +1068,78 @@ function toolTarget(target, remote, handles) {
|
|
|
1058
1068
|
}
|
|
1059
1069
|
};
|
|
1060
1070
|
}
|
|
1071
|
+
function shareSummary(live) {
|
|
1072
|
+
const notes = [];
|
|
1073
|
+
if (live.notice) {
|
|
1074
|
+
notes.push(
|
|
1075
|
+
"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)."
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
if (live.password) notes.push("Visitors need the password.");
|
|
1079
|
+
if (live.expiresAt) {
|
|
1080
|
+
notes.push(
|
|
1081
|
+
`The plan stops this share at ${live.expiresAt}; reconnecting does not extend it, and starting it again gives a new link.`
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
notes.push("It runs until stop_share or the end of this MCP server.");
|
|
1085
|
+
return {
|
|
1086
|
+
url: live.url,
|
|
1087
|
+
name: live.name,
|
|
1088
|
+
port: live.port,
|
|
1089
|
+
expiresAt: live.expiresAt,
|
|
1090
|
+
notice: live.notice,
|
|
1091
|
+
password: live.password,
|
|
1092
|
+
notes: notes.join(" ")
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
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)";
|
|
1096
|
+
function shareHints(list, remote = false) {
|
|
1097
|
+
const start = remote ? REMOTE_SHARE : "share_port";
|
|
1098
|
+
const hints = [];
|
|
1099
|
+
const limits = list.limits;
|
|
1100
|
+
const usage = list.usage;
|
|
1101
|
+
const previews = list.shares.filter((s) => s.kind !== "live" && !s.endedAt && s.mine !== false);
|
|
1102
|
+
if (usage?.over) {
|
|
1103
|
+
hints.push(
|
|
1104
|
+
`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.`
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
if (limits && previews.length >= limits.shares) {
|
|
1108
|
+
hints.push(
|
|
1109
|
+
`All ${limits.shares} of the plan's preview slots are in use: stop one with stop_share (its id is in shares) before ${start}.`
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
if (limits && limits.sessionMinutes) {
|
|
1113
|
+
hints.push(
|
|
1114
|
+
`Previews on this plan stop after ${limits.sessionMinutes} minutes; ${start} again gives a new link.`
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
if (limits && !limits.passwords) {
|
|
1118
|
+
hints.push("Passwords and reserved names come with Pro and Business.");
|
|
1119
|
+
}
|
|
1120
|
+
return hints;
|
|
1121
|
+
}
|
|
1122
|
+
function toolWords(hint, remote = false) {
|
|
1123
|
+
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");
|
|
1124
|
+
if (remote) return words;
|
|
1125
|
+
return words.replace(/whk share <port> --password/g, "share_port with a password").replace(/whk share <port>/g, "share_port");
|
|
1126
|
+
}
|
|
1127
|
+
function withShareHints(list, remote = false) {
|
|
1128
|
+
const server = (list.hints ?? []).map((h) => toolWords(h, remote));
|
|
1129
|
+
const ours = shareHints(list, remote);
|
|
1130
|
+
if (list.suspended && !server.some((h) => /suspended/i.test(h))) {
|
|
1131
|
+
ours.unshift(
|
|
1132
|
+
`Sharing is suspended on this account: ${remote ? "new previews are" : "share_port is"} refused. The person can write to support@webhooks.cc.`
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
const hints = [.../* @__PURE__ */ new Set([...server, ...ours])];
|
|
1136
|
+
return hints.length > 0 ? { ...list, hints } : { ...list };
|
|
1137
|
+
}
|
|
1061
1138
|
function registerTools(target, client, options = {}) {
|
|
1062
1139
|
const handles = [];
|
|
1063
|
-
const
|
|
1140
|
+
const remote = options.remote === true;
|
|
1141
|
+
const server = toolTarget(target, remote, handles);
|
|
1142
|
+
const shareWith = remote ? REMOTE_SHARE : "share_port";
|
|
1064
1143
|
server.tool(
|
|
1065
1144
|
"create_endpoint",
|
|
1066
1145
|
"Create a webhook endpoint. Returns the endpoint slug, URL, and metadata.",
|
|
@@ -1720,7 +1799,7 @@ function registerTools(target, client, options = {}) {
|
|
|
1720
1799
|
);
|
|
1721
1800
|
server.tool(
|
|
1722
1801
|
"get_usage",
|
|
1723
|
-
"Check current request usage, remaining quota, plan, and period end,
|
|
1802
|
+
"Check current request usage, remaining quota, plan, and period end; parsed pages, preview data (whk share) and AI assists, each with when it resets; and the pooled quota of every subscribed team you belong to.",
|
|
1724
1803
|
{},
|
|
1725
1804
|
withErrorHandling(async () => {
|
|
1726
1805
|
const [usageResult, teamsResult] = await Promise.allSettled([
|
|
@@ -2121,6 +2200,143 @@ function registerTools(target, client, options = {}) {
|
|
|
2121
2200
|
);
|
|
2122
2201
|
})
|
|
2123
2202
|
);
|
|
2203
|
+
const started = /* @__PURE__ */ new Map();
|
|
2204
|
+
server.tool(
|
|
2205
|
+
"share_port",
|
|
2206
|
+
"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.",
|
|
2207
|
+
{
|
|
2208
|
+
port: import_zod2.z.number().int().min(1).max(65535).describe("The local port the site runs on"),
|
|
2209
|
+
name: import_zod2.z.string().optional().describe("A name you reserved (Pro and Business); leave out for a random one"),
|
|
2210
|
+
password: import_zod2.z.string().min(8).max(200).optional().describe("What visitors must type (Pro and Business), 8 to 200 characters")
|
|
2211
|
+
},
|
|
2212
|
+
withErrorHandling(async ({ port, name, password }) => {
|
|
2213
|
+
const running = started.get(port);
|
|
2214
|
+
if (running && !name && !password) {
|
|
2215
|
+
return jsonContent({ ...shareSummary(running), alreadyRunning: true });
|
|
2216
|
+
}
|
|
2217
|
+
if (running) {
|
|
2218
|
+
started.delete(port);
|
|
2219
|
+
await running.stop();
|
|
2220
|
+
}
|
|
2221
|
+
let live;
|
|
2222
|
+
try {
|
|
2223
|
+
live = await client.shares.start({
|
|
2224
|
+
port,
|
|
2225
|
+
...name ? { name } : {},
|
|
2226
|
+
...password ? { password } : {},
|
|
2227
|
+
...process.env.WHK_PATH ? { whkPath: process.env.WHK_PATH } : {}
|
|
2228
|
+
});
|
|
2229
|
+
} catch (error) {
|
|
2230
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2231
|
+
throw new Error(
|
|
2232
|
+
`${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.`
|
|
2233
|
+
);
|
|
2234
|
+
}
|
|
2235
|
+
started.set(port, live);
|
|
2236
|
+
void live.closed.then(() => {
|
|
2237
|
+
if (started.get(port) === live) started.delete(port);
|
|
2238
|
+
});
|
|
2239
|
+
const summary = shareSummary(live);
|
|
2240
|
+
try {
|
|
2241
|
+
const list = await client.shares.list({ state: "live" });
|
|
2242
|
+
summary.limits = list.limits;
|
|
2243
|
+
summary.usage = list.usage;
|
|
2244
|
+
const hints = shareHints(list);
|
|
2245
|
+
if (hints.length > 0) summary.hints = hints;
|
|
2246
|
+
} catch {
|
|
2247
|
+
}
|
|
2248
|
+
return jsonContent(summary);
|
|
2249
|
+
})
|
|
2250
|
+
);
|
|
2251
|
+
server.tool(
|
|
2252
|
+
"list_shares",
|
|
2253
|
+
`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.`,
|
|
2254
|
+
{
|
|
2255
|
+
state: import_zod2.z.enum(["live", "ended"]).optional().describe("Only the running ones, or only the ended ones")
|
|
2256
|
+
},
|
|
2257
|
+
withErrorHandling(async ({ state }) => {
|
|
2258
|
+
return jsonContent(withShareHints(await client.shares.list(state ? { state } : {}), remote));
|
|
2259
|
+
})
|
|
2260
|
+
);
|
|
2261
|
+
server.tool(
|
|
2262
|
+
"stop_share",
|
|
2263
|
+
"Stop a public preview or live tunnel by its id (from list_shares). The CLI running it is told why and exits.",
|
|
2264
|
+
{
|
|
2265
|
+
id: import_zod2.z.string().describe("The share's id, from list_shares")
|
|
2266
|
+
},
|
|
2267
|
+
withErrorHandling(async ({ id }) => {
|
|
2268
|
+
const result = await client.shares.stop(id);
|
|
2269
|
+
return jsonContent({
|
|
2270
|
+
id,
|
|
2271
|
+
stopped: result.stopped,
|
|
2272
|
+
edgeReached: result.edgeReached,
|
|
2273
|
+
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."
|
|
2274
|
+
});
|
|
2275
|
+
})
|
|
2276
|
+
);
|
|
2277
|
+
server.tool(
|
|
2278
|
+
"share_requests",
|
|
2279
|
+
"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.",
|
|
2280
|
+
{
|
|
2281
|
+
id: import_zod2.z.string().describe("The share's id, from list_shares")
|
|
2282
|
+
},
|
|
2283
|
+
withErrorHandling(async ({ id }) => {
|
|
2284
|
+
return jsonContent(await client.shares.requests(id));
|
|
2285
|
+
})
|
|
2286
|
+
);
|
|
2287
|
+
server.tool(
|
|
2288
|
+
"list_names",
|
|
2289
|
+
`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"}.`,
|
|
2290
|
+
{},
|
|
2291
|
+
withErrorHandling(async () => {
|
|
2292
|
+
return jsonContent(await client.names.list());
|
|
2293
|
+
})
|
|
2294
|
+
);
|
|
2295
|
+
server.tool(
|
|
2296
|
+
"reserve_name",
|
|
2297
|
+
`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.`,
|
|
2298
|
+
{
|
|
2299
|
+
name: import_zod2.z.string().min(1).max(63).describe("The name: lowercase letters, digits and dashes"),
|
|
2300
|
+
team: import_zod2.z.string().optional().describe("A team's id or name (from list_teams), to keep the name inside that team")
|
|
2301
|
+
},
|
|
2302
|
+
withErrorHandling(async ({ name, team }) => {
|
|
2303
|
+
return jsonContent(await client.names.reserve(name, team ? { team } : {}));
|
|
2304
|
+
})
|
|
2305
|
+
);
|
|
2306
|
+
server.tool(
|
|
2307
|
+
"release_name",
|
|
2308
|
+
"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.",
|
|
2309
|
+
{
|
|
2310
|
+
name: import_zod2.z.string().min(1).max(63).describe("The name, from list_names")
|
|
2311
|
+
},
|
|
2312
|
+
withErrorHandling(async ({ name }) => {
|
|
2313
|
+
await client.names.release(name);
|
|
2314
|
+
return jsonContent({ released: name });
|
|
2315
|
+
})
|
|
2316
|
+
);
|
|
2317
|
+
server.tool(
|
|
2318
|
+
"set_name_password",
|
|
2319
|
+
"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.",
|
|
2320
|
+
{
|
|
2321
|
+
name: import_zod2.z.string().min(1).max(63).describe("The name, from list_names"),
|
|
2322
|
+
password: import_zod2.z.string().min(8).max(200).describe("What visitors must type, 8 to 200 characters")
|
|
2323
|
+
},
|
|
2324
|
+
withErrorHandling(async ({ name, password }) => {
|
|
2325
|
+
await client.names.setPassword(name, password);
|
|
2326
|
+
return jsonContent({ name, password: true });
|
|
2327
|
+
})
|
|
2328
|
+
);
|
|
2329
|
+
server.tool(
|
|
2330
|
+
"clear_name_password",
|
|
2331
|
+
"Take the password off a reserved name, so its previews open without one.",
|
|
2332
|
+
{
|
|
2333
|
+
name: import_zod2.z.string().min(1).max(63).describe("The name, from list_names")
|
|
2334
|
+
},
|
|
2335
|
+
withErrorHandling(async ({ name }) => {
|
|
2336
|
+
await client.names.clearPassword(name);
|
|
2337
|
+
return jsonContent({ name, password: false });
|
|
2338
|
+
})
|
|
2339
|
+
);
|
|
2124
2340
|
server.tool(
|
|
2125
2341
|
"describe",
|
|
2126
2342
|
"Describe all available SDK operations, parameters, and types.",
|
|
@@ -67,6 +67,15 @@ function omitHeaders(holder) {
|
|
|
67
67
|
holder.headers = {};
|
|
68
68
|
holder.headersOmitted = `${count} headers left out to fit the output`;
|
|
69
69
|
}
|
|
70
|
+
function trimAuth(holder) {
|
|
71
|
+
const auth = holder.auth;
|
|
72
|
+
if (auth && typeof auth === "object" && !Array.isArray(auth)) {
|
|
73
|
+
const verdicts = { ...auth };
|
|
74
|
+
delete verdicts.setup;
|
|
75
|
+
delete verdicts.authenticationResults;
|
|
76
|
+
holder.auth = verdicts;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
70
79
|
function trimSpam(holder) {
|
|
71
80
|
const spam = holder.spam;
|
|
72
81
|
if (spam && typeof spam === "object" && !Array.isArray(spam)) {
|
|
@@ -214,6 +223,7 @@ function compactRequest(request) {
|
|
|
214
223
|
shrinkToFit(root, budget, [
|
|
215
224
|
() => trimParsed(compact),
|
|
216
225
|
() => omitHeaders(compact),
|
|
226
|
+
() => trimAuth(email),
|
|
217
227
|
() => trimSpam(email),
|
|
218
228
|
() => capLists(email),
|
|
219
229
|
() => cutStringToFit(root, email, "text", "textTruncated", budget, TEXT_FLOOR),
|
|
@@ -778,7 +788,7 @@ function emailDetail(client, email, view, includeHtml) {
|
|
|
778
788
|
]);
|
|
779
789
|
return detail;
|
|
780
790
|
}
|
|
781
|
-
var LOCAL_ONLY_TOOLS = /* @__PURE__ */ new Set(["send_to", "replay_request"]);
|
|
791
|
+
var LOCAL_ONLY_TOOLS = /* @__PURE__ */ new Set(["send_to", "replay_request", "share_port"]);
|
|
782
792
|
var TARGET_URL_TOOLS = /* @__PURE__ */ new Set(["render_email", "test_webhook_flow"]);
|
|
783
793
|
var REMOTE_MAX_WAIT_MS = 5e4;
|
|
784
794
|
var REMOTE_MIN_POLL_MS = 1e3;
|
|
@@ -836,9 +846,78 @@ function toolTarget(target, remote, handles) {
|
|
|
836
846
|
}
|
|
837
847
|
};
|
|
838
848
|
}
|
|
849
|
+
function shareSummary(live) {
|
|
850
|
+
const notes = [];
|
|
851
|
+
if (live.notice) {
|
|
852
|
+
notes.push(
|
|
853
|
+
"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)."
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
if (live.password) notes.push("Visitors need the password.");
|
|
857
|
+
if (live.expiresAt) {
|
|
858
|
+
notes.push(
|
|
859
|
+
`The plan stops this share at ${live.expiresAt}; reconnecting does not extend it, and starting it again gives a new link.`
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
notes.push("It runs until stop_share or the end of this MCP server.");
|
|
863
|
+
return {
|
|
864
|
+
url: live.url,
|
|
865
|
+
name: live.name,
|
|
866
|
+
port: live.port,
|
|
867
|
+
expiresAt: live.expiresAt,
|
|
868
|
+
notice: live.notice,
|
|
869
|
+
password: live.password,
|
|
870
|
+
notes: notes.join(" ")
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
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)";
|
|
874
|
+
function shareHints(list, remote = false) {
|
|
875
|
+
const start = remote ? REMOTE_SHARE : "share_port";
|
|
876
|
+
const hints = [];
|
|
877
|
+
const limits = list.limits;
|
|
878
|
+
const usage = list.usage;
|
|
879
|
+
const previews = list.shares.filter((s) => s.kind !== "live" && !s.endedAt && s.mine !== false);
|
|
880
|
+
if (usage?.over) {
|
|
881
|
+
hints.push(
|
|
882
|
+
`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.`
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
if (limits && previews.length >= limits.shares) {
|
|
886
|
+
hints.push(
|
|
887
|
+
`All ${limits.shares} of the plan's preview slots are in use: stop one with stop_share (its id is in shares) before ${start}.`
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
if (limits && limits.sessionMinutes) {
|
|
891
|
+
hints.push(
|
|
892
|
+
`Previews on this plan stop after ${limits.sessionMinutes} minutes; ${start} again gives a new link.`
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
if (limits && !limits.passwords) {
|
|
896
|
+
hints.push("Passwords and reserved names come with Pro and Business.");
|
|
897
|
+
}
|
|
898
|
+
return hints;
|
|
899
|
+
}
|
|
900
|
+
function toolWords(hint, remote = false) {
|
|
901
|
+
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");
|
|
902
|
+
if (remote) return words;
|
|
903
|
+
return words.replace(/whk share <port> --password/g, "share_port with a password").replace(/whk share <port>/g, "share_port");
|
|
904
|
+
}
|
|
905
|
+
function withShareHints(list, remote = false) {
|
|
906
|
+
const server = (list.hints ?? []).map((h) => toolWords(h, remote));
|
|
907
|
+
const ours = shareHints(list, remote);
|
|
908
|
+
if (list.suspended && !server.some((h) => /suspended/i.test(h))) {
|
|
909
|
+
ours.unshift(
|
|
910
|
+
`Sharing is suspended on this account: ${remote ? "new previews are" : "share_port is"} refused. The person can write to support@webhooks.cc.`
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
const hints = [.../* @__PURE__ */ new Set([...server, ...ours])];
|
|
914
|
+
return hints.length > 0 ? { ...list, hints } : { ...list };
|
|
915
|
+
}
|
|
839
916
|
function registerTools(target, client, options = {}) {
|
|
840
917
|
const handles = [];
|
|
841
|
-
const
|
|
918
|
+
const remote = options.remote === true;
|
|
919
|
+
const server = toolTarget(target, remote, handles);
|
|
920
|
+
const shareWith = remote ? REMOTE_SHARE : "share_port";
|
|
842
921
|
server.tool(
|
|
843
922
|
"create_endpoint",
|
|
844
923
|
"Create a webhook endpoint. Returns the endpoint slug, URL, and metadata.",
|
|
@@ -1498,7 +1577,7 @@ function registerTools(target, client, options = {}) {
|
|
|
1498
1577
|
);
|
|
1499
1578
|
server.tool(
|
|
1500
1579
|
"get_usage",
|
|
1501
|
-
"Check current request usage, remaining quota, plan, and period end,
|
|
1580
|
+
"Check current request usage, remaining quota, plan, and period end; parsed pages, preview data (whk share) and AI assists, each with when it resets; and the pooled quota of every subscribed team you belong to.",
|
|
1502
1581
|
{},
|
|
1503
1582
|
withErrorHandling(async () => {
|
|
1504
1583
|
const [usageResult, teamsResult] = await Promise.allSettled([
|
|
@@ -1899,6 +1978,143 @@ function registerTools(target, client, options = {}) {
|
|
|
1899
1978
|
);
|
|
1900
1979
|
})
|
|
1901
1980
|
);
|
|
1981
|
+
const started = /* @__PURE__ */ new Map();
|
|
1982
|
+
server.tool(
|
|
1983
|
+
"share_port",
|
|
1984
|
+
"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.",
|
|
1985
|
+
{
|
|
1986
|
+
port: z.number().int().min(1).max(65535).describe("The local port the site runs on"),
|
|
1987
|
+
name: z.string().optional().describe("A name you reserved (Pro and Business); leave out for a random one"),
|
|
1988
|
+
password: z.string().min(8).max(200).optional().describe("What visitors must type (Pro and Business), 8 to 200 characters")
|
|
1989
|
+
},
|
|
1990
|
+
withErrorHandling(async ({ port, name, password }) => {
|
|
1991
|
+
const running = started.get(port);
|
|
1992
|
+
if (running && !name && !password) {
|
|
1993
|
+
return jsonContent({ ...shareSummary(running), alreadyRunning: true });
|
|
1994
|
+
}
|
|
1995
|
+
if (running) {
|
|
1996
|
+
started.delete(port);
|
|
1997
|
+
await running.stop();
|
|
1998
|
+
}
|
|
1999
|
+
let live;
|
|
2000
|
+
try {
|
|
2001
|
+
live = await client.shares.start({
|
|
2002
|
+
port,
|
|
2003
|
+
...name ? { name } : {},
|
|
2004
|
+
...password ? { password } : {},
|
|
2005
|
+
...process.env.WHK_PATH ? { whkPath: process.env.WHK_PATH } : {}
|
|
2006
|
+
});
|
|
2007
|
+
} catch (error) {
|
|
2008
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2009
|
+
throw new Error(
|
|
2010
|
+
`${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.`
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
2013
|
+
started.set(port, live);
|
|
2014
|
+
void live.closed.then(() => {
|
|
2015
|
+
if (started.get(port) === live) started.delete(port);
|
|
2016
|
+
});
|
|
2017
|
+
const summary = shareSummary(live);
|
|
2018
|
+
try {
|
|
2019
|
+
const list = await client.shares.list({ state: "live" });
|
|
2020
|
+
summary.limits = list.limits;
|
|
2021
|
+
summary.usage = list.usage;
|
|
2022
|
+
const hints = shareHints(list);
|
|
2023
|
+
if (hints.length > 0) summary.hints = hints;
|
|
2024
|
+
} catch {
|
|
2025
|
+
}
|
|
2026
|
+
return jsonContent(summary);
|
|
2027
|
+
})
|
|
2028
|
+
);
|
|
2029
|
+
server.tool(
|
|
2030
|
+
"list_shares",
|
|
2031
|
+
`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.`,
|
|
2032
|
+
{
|
|
2033
|
+
state: z.enum(["live", "ended"]).optional().describe("Only the running ones, or only the ended ones")
|
|
2034
|
+
},
|
|
2035
|
+
withErrorHandling(async ({ state }) => {
|
|
2036
|
+
return jsonContent(withShareHints(await client.shares.list(state ? { state } : {}), remote));
|
|
2037
|
+
})
|
|
2038
|
+
);
|
|
2039
|
+
server.tool(
|
|
2040
|
+
"stop_share",
|
|
2041
|
+
"Stop a public preview or live tunnel by its id (from list_shares). The CLI running it is told why and exits.",
|
|
2042
|
+
{
|
|
2043
|
+
id: z.string().describe("The share's id, from list_shares")
|
|
2044
|
+
},
|
|
2045
|
+
withErrorHandling(async ({ id }) => {
|
|
2046
|
+
const result = await client.shares.stop(id);
|
|
2047
|
+
return jsonContent({
|
|
2048
|
+
id,
|
|
2049
|
+
stopped: result.stopped,
|
|
2050
|
+
edgeReached: result.edgeReached,
|
|
2051
|
+
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."
|
|
2052
|
+
});
|
|
2053
|
+
})
|
|
2054
|
+
);
|
|
2055
|
+
server.tool(
|
|
2056
|
+
"share_requests",
|
|
2057
|
+
"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.",
|
|
2058
|
+
{
|
|
2059
|
+
id: z.string().describe("The share's id, from list_shares")
|
|
2060
|
+
},
|
|
2061
|
+
withErrorHandling(async ({ id }) => {
|
|
2062
|
+
return jsonContent(await client.shares.requests(id));
|
|
2063
|
+
})
|
|
2064
|
+
);
|
|
2065
|
+
server.tool(
|
|
2066
|
+
"list_names",
|
|
2067
|
+
`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"}.`,
|
|
2068
|
+
{},
|
|
2069
|
+
withErrorHandling(async () => {
|
|
2070
|
+
return jsonContent(await client.names.list());
|
|
2071
|
+
})
|
|
2072
|
+
);
|
|
2073
|
+
server.tool(
|
|
2074
|
+
"reserve_name",
|
|
2075
|
+
`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.`,
|
|
2076
|
+
{
|
|
2077
|
+
name: z.string().min(1).max(63).describe("The name: lowercase letters, digits and dashes"),
|
|
2078
|
+
team: z.string().optional().describe("A team's id or name (from list_teams), to keep the name inside that team")
|
|
2079
|
+
},
|
|
2080
|
+
withErrorHandling(async ({ name, team }) => {
|
|
2081
|
+
return jsonContent(await client.names.reserve(name, team ? { team } : {}));
|
|
2082
|
+
})
|
|
2083
|
+
);
|
|
2084
|
+
server.tool(
|
|
2085
|
+
"release_name",
|
|
2086
|
+
"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.",
|
|
2087
|
+
{
|
|
2088
|
+
name: z.string().min(1).max(63).describe("The name, from list_names")
|
|
2089
|
+
},
|
|
2090
|
+
withErrorHandling(async ({ name }) => {
|
|
2091
|
+
await client.names.release(name);
|
|
2092
|
+
return jsonContent({ released: name });
|
|
2093
|
+
})
|
|
2094
|
+
);
|
|
2095
|
+
server.tool(
|
|
2096
|
+
"set_name_password",
|
|
2097
|
+
"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.",
|
|
2098
|
+
{
|
|
2099
|
+
name: z.string().min(1).max(63).describe("The name, from list_names"),
|
|
2100
|
+
password: z.string().min(8).max(200).describe("What visitors must type, 8 to 200 characters")
|
|
2101
|
+
},
|
|
2102
|
+
withErrorHandling(async ({ name, password }) => {
|
|
2103
|
+
await client.names.setPassword(name, password);
|
|
2104
|
+
return jsonContent({ name, password: true });
|
|
2105
|
+
})
|
|
2106
|
+
);
|
|
2107
|
+
server.tool(
|
|
2108
|
+
"clear_name_password",
|
|
2109
|
+
"Take the password off a reserved name, so its previews open without one.",
|
|
2110
|
+
{
|
|
2111
|
+
name: z.string().min(1).max(63).describe("The name, from list_names")
|
|
2112
|
+
},
|
|
2113
|
+
withErrorHandling(async ({ name }) => {
|
|
2114
|
+
await client.names.clearPassword(name);
|
|
2115
|
+
return jsonContent({ name, password: false });
|
|
2116
|
+
})
|
|
2117
|
+
);
|
|
1902
2118
|
server.tool(
|
|
1903
2119
|
"describe",
|
|
1904
2120
|
"Describe all available SDK operations, parameters, and types.",
|
|
@@ -1927,5 +2143,8 @@ export {
|
|
|
1927
2143
|
withErrorHandling,
|
|
1928
2144
|
REMOTE_MAX_WAIT_MS,
|
|
1929
2145
|
REMOTE_MIN_POLL_MS,
|
|
2146
|
+
shareHints,
|
|
2147
|
+
toolWords,
|
|
2148
|
+
withShareHints,
|
|
1930
2149
|
registerTools
|
|
1931
2150
|
};
|
package/dist/index.js
CHANGED
|
@@ -182,6 +182,15 @@ function omitHeaders(holder) {
|
|
|
182
182
|
holder.headers = {};
|
|
183
183
|
holder.headersOmitted = `${count} headers left out to fit the output`;
|
|
184
184
|
}
|
|
185
|
+
function trimAuth(holder) {
|
|
186
|
+
const auth = holder.auth;
|
|
187
|
+
if (auth && typeof auth === "object" && !Array.isArray(auth)) {
|
|
188
|
+
const verdicts = { ...auth };
|
|
189
|
+
delete verdicts.setup;
|
|
190
|
+
delete verdicts.authenticationResults;
|
|
191
|
+
holder.auth = verdicts;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
185
194
|
function trimSpam(holder) {
|
|
186
195
|
const spam = holder.spam;
|
|
187
196
|
if (spam && typeof spam === "object" && !Array.isArray(spam)) {
|
|
@@ -329,6 +338,7 @@ function compactRequest(request) {
|
|
|
329
338
|
shrinkToFit(root, budget, [
|
|
330
339
|
() => trimParsed(compact),
|
|
331
340
|
() => omitHeaders(compact),
|
|
341
|
+
() => trimAuth(email),
|
|
332
342
|
() => trimSpam(email),
|
|
333
343
|
() => capLists(email),
|
|
334
344
|
() => cutStringToFit(root, email, "text", "textTruncated", budget, TEXT_FLOOR),
|
|
@@ -470,7 +480,7 @@ var import_zod3 = require("zod");
|
|
|
470
480
|
var import_sdk2 = require("@webhooks-cc/sdk");
|
|
471
481
|
|
|
472
482
|
// src/version.ts
|
|
473
|
-
var VERSION = true ? "1.
|
|
483
|
+
var VERSION = true ? "1.19.0" : "0.0.0-dev";
|
|
474
484
|
var MCP_USER_AGENT = `webhooks-cc-mcp/${VERSION}`;
|
|
475
485
|
|
|
476
486
|
// src/tools.ts
|
|
@@ -1024,7 +1034,7 @@ function emailDetail(client, email, view, includeHtml) {
|
|
|
1024
1034
|
]);
|
|
1025
1035
|
return detail;
|
|
1026
1036
|
}
|
|
1027
|
-
var LOCAL_ONLY_TOOLS = /* @__PURE__ */ new Set(["send_to", "replay_request"]);
|
|
1037
|
+
var LOCAL_ONLY_TOOLS = /* @__PURE__ */ new Set(["send_to", "replay_request", "share_port"]);
|
|
1028
1038
|
var TARGET_URL_TOOLS = /* @__PURE__ */ new Set(["render_email", "test_webhook_flow"]);
|
|
1029
1039
|
var REMOTE_MAX_WAIT_MS = 5e4;
|
|
1030
1040
|
var REMOTE_MIN_POLL_MS = 1e3;
|
|
@@ -1082,9 +1092,78 @@ function toolTarget(target, remote, handles) {
|
|
|
1082
1092
|
}
|
|
1083
1093
|
};
|
|
1084
1094
|
}
|
|
1095
|
+
function shareSummary(live) {
|
|
1096
|
+
const notes = [];
|
|
1097
|
+
if (live.notice) {
|
|
1098
|
+
notes.push(
|
|
1099
|
+
"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)."
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
if (live.password) notes.push("Visitors need the password.");
|
|
1103
|
+
if (live.expiresAt) {
|
|
1104
|
+
notes.push(
|
|
1105
|
+
`The plan stops this share at ${live.expiresAt}; reconnecting does not extend it, and starting it again gives a new link.`
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
notes.push("It runs until stop_share or the end of this MCP server.");
|
|
1109
|
+
return {
|
|
1110
|
+
url: live.url,
|
|
1111
|
+
name: live.name,
|
|
1112
|
+
port: live.port,
|
|
1113
|
+
expiresAt: live.expiresAt,
|
|
1114
|
+
notice: live.notice,
|
|
1115
|
+
password: live.password,
|
|
1116
|
+
notes: notes.join(" ")
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
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)";
|
|
1120
|
+
function shareHints(list, remote = false) {
|
|
1121
|
+
const start = remote ? REMOTE_SHARE : "share_port";
|
|
1122
|
+
const hints = [];
|
|
1123
|
+
const limits = list.limits;
|
|
1124
|
+
const usage = list.usage;
|
|
1125
|
+
const previews = list.shares.filter((s) => s.kind !== "live" && !s.endedAt && s.mine !== false);
|
|
1126
|
+
if (usage?.over) {
|
|
1127
|
+
hints.push(
|
|
1128
|
+
`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.`
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
if (limits && previews.length >= limits.shares) {
|
|
1132
|
+
hints.push(
|
|
1133
|
+
`All ${limits.shares} of the plan's preview slots are in use: stop one with stop_share (its id is in shares) before ${start}.`
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
if (limits && limits.sessionMinutes) {
|
|
1137
|
+
hints.push(
|
|
1138
|
+
`Previews on this plan stop after ${limits.sessionMinutes} minutes; ${start} again gives a new link.`
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
if (limits && !limits.passwords) {
|
|
1142
|
+
hints.push("Passwords and reserved names come with Pro and Business.");
|
|
1143
|
+
}
|
|
1144
|
+
return hints;
|
|
1145
|
+
}
|
|
1146
|
+
function toolWords(hint, remote = false) {
|
|
1147
|
+
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");
|
|
1148
|
+
if (remote) return words;
|
|
1149
|
+
return words.replace(/whk share <port> --password/g, "share_port with a password").replace(/whk share <port>/g, "share_port");
|
|
1150
|
+
}
|
|
1151
|
+
function withShareHints(list, remote = false) {
|
|
1152
|
+
const server = (list.hints ?? []).map((h) => toolWords(h, remote));
|
|
1153
|
+
const ours = shareHints(list, remote);
|
|
1154
|
+
if (list.suspended && !server.some((h) => /suspended/i.test(h))) {
|
|
1155
|
+
ours.unshift(
|
|
1156
|
+
`Sharing is suspended on this account: ${remote ? "new previews are" : "share_port is"} refused. The person can write to support@webhooks.cc.`
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
const hints = [.../* @__PURE__ */ new Set([...server, ...ours])];
|
|
1160
|
+
return hints.length > 0 ? { ...list, hints } : { ...list };
|
|
1161
|
+
}
|
|
1085
1162
|
function registerTools(target, client, options = {}) {
|
|
1086
1163
|
const handles = [];
|
|
1087
|
-
const
|
|
1164
|
+
const remote = options.remote === true;
|
|
1165
|
+
const server = toolTarget(target, remote, handles);
|
|
1166
|
+
const shareWith = remote ? REMOTE_SHARE : "share_port";
|
|
1088
1167
|
server.tool(
|
|
1089
1168
|
"create_endpoint",
|
|
1090
1169
|
"Create a webhook endpoint. Returns the endpoint slug, URL, and metadata.",
|
|
@@ -1744,7 +1823,7 @@ function registerTools(target, client, options = {}) {
|
|
|
1744
1823
|
);
|
|
1745
1824
|
server.tool(
|
|
1746
1825
|
"get_usage",
|
|
1747
|
-
"Check current request usage, remaining quota, plan, and period end,
|
|
1826
|
+
"Check current request usage, remaining quota, plan, and period end; parsed pages, preview data (whk share) and AI assists, each with when it resets; and the pooled quota of every subscribed team you belong to.",
|
|
1748
1827
|
{},
|
|
1749
1828
|
withErrorHandling(async () => {
|
|
1750
1829
|
const [usageResult, teamsResult] = await Promise.allSettled([
|
|
@@ -2145,6 +2224,143 @@ function registerTools(target, client, options = {}) {
|
|
|
2145
2224
|
);
|
|
2146
2225
|
})
|
|
2147
2226
|
);
|
|
2227
|
+
const started = /* @__PURE__ */ new Map();
|
|
2228
|
+
server.tool(
|
|
2229
|
+
"share_port",
|
|
2230
|
+
"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.",
|
|
2231
|
+
{
|
|
2232
|
+
port: import_zod2.z.number().int().min(1).max(65535).describe("The local port the site runs on"),
|
|
2233
|
+
name: import_zod2.z.string().optional().describe("A name you reserved (Pro and Business); leave out for a random one"),
|
|
2234
|
+
password: import_zod2.z.string().min(8).max(200).optional().describe("What visitors must type (Pro and Business), 8 to 200 characters")
|
|
2235
|
+
},
|
|
2236
|
+
withErrorHandling(async ({ port, name, password }) => {
|
|
2237
|
+
const running = started.get(port);
|
|
2238
|
+
if (running && !name && !password) {
|
|
2239
|
+
return jsonContent({ ...shareSummary(running), alreadyRunning: true });
|
|
2240
|
+
}
|
|
2241
|
+
if (running) {
|
|
2242
|
+
started.delete(port);
|
|
2243
|
+
await running.stop();
|
|
2244
|
+
}
|
|
2245
|
+
let live;
|
|
2246
|
+
try {
|
|
2247
|
+
live = await client.shares.start({
|
|
2248
|
+
port,
|
|
2249
|
+
...name ? { name } : {},
|
|
2250
|
+
...password ? { password } : {},
|
|
2251
|
+
...process.env.WHK_PATH ? { whkPath: process.env.WHK_PATH } : {}
|
|
2252
|
+
});
|
|
2253
|
+
} catch (error) {
|
|
2254
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2255
|
+
throw new Error(
|
|
2256
|
+
`${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.`
|
|
2257
|
+
);
|
|
2258
|
+
}
|
|
2259
|
+
started.set(port, live);
|
|
2260
|
+
void live.closed.then(() => {
|
|
2261
|
+
if (started.get(port) === live) started.delete(port);
|
|
2262
|
+
});
|
|
2263
|
+
const summary = shareSummary(live);
|
|
2264
|
+
try {
|
|
2265
|
+
const list = await client.shares.list({ state: "live" });
|
|
2266
|
+
summary.limits = list.limits;
|
|
2267
|
+
summary.usage = list.usage;
|
|
2268
|
+
const hints = shareHints(list);
|
|
2269
|
+
if (hints.length > 0) summary.hints = hints;
|
|
2270
|
+
} catch {
|
|
2271
|
+
}
|
|
2272
|
+
return jsonContent(summary);
|
|
2273
|
+
})
|
|
2274
|
+
);
|
|
2275
|
+
server.tool(
|
|
2276
|
+
"list_shares",
|
|
2277
|
+
`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.`,
|
|
2278
|
+
{
|
|
2279
|
+
state: import_zod2.z.enum(["live", "ended"]).optional().describe("Only the running ones, or only the ended ones")
|
|
2280
|
+
},
|
|
2281
|
+
withErrorHandling(async ({ state }) => {
|
|
2282
|
+
return jsonContent(withShareHints(await client.shares.list(state ? { state } : {}), remote));
|
|
2283
|
+
})
|
|
2284
|
+
);
|
|
2285
|
+
server.tool(
|
|
2286
|
+
"stop_share",
|
|
2287
|
+
"Stop a public preview or live tunnel by its id (from list_shares). The CLI running it is told why and exits.",
|
|
2288
|
+
{
|
|
2289
|
+
id: import_zod2.z.string().describe("The share's id, from list_shares")
|
|
2290
|
+
},
|
|
2291
|
+
withErrorHandling(async ({ id }) => {
|
|
2292
|
+
const result = await client.shares.stop(id);
|
|
2293
|
+
return jsonContent({
|
|
2294
|
+
id,
|
|
2295
|
+
stopped: result.stopped,
|
|
2296
|
+
edgeReached: result.edgeReached,
|
|
2297
|
+
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."
|
|
2298
|
+
});
|
|
2299
|
+
})
|
|
2300
|
+
);
|
|
2301
|
+
server.tool(
|
|
2302
|
+
"share_requests",
|
|
2303
|
+
"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.",
|
|
2304
|
+
{
|
|
2305
|
+
id: import_zod2.z.string().describe("The share's id, from list_shares")
|
|
2306
|
+
},
|
|
2307
|
+
withErrorHandling(async ({ id }) => {
|
|
2308
|
+
return jsonContent(await client.shares.requests(id));
|
|
2309
|
+
})
|
|
2310
|
+
);
|
|
2311
|
+
server.tool(
|
|
2312
|
+
"list_names",
|
|
2313
|
+
`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"}.`,
|
|
2314
|
+
{},
|
|
2315
|
+
withErrorHandling(async () => {
|
|
2316
|
+
return jsonContent(await client.names.list());
|
|
2317
|
+
})
|
|
2318
|
+
);
|
|
2319
|
+
server.tool(
|
|
2320
|
+
"reserve_name",
|
|
2321
|
+
`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.`,
|
|
2322
|
+
{
|
|
2323
|
+
name: import_zod2.z.string().min(1).max(63).describe("The name: lowercase letters, digits and dashes"),
|
|
2324
|
+
team: import_zod2.z.string().optional().describe("A team's id or name (from list_teams), to keep the name inside that team")
|
|
2325
|
+
},
|
|
2326
|
+
withErrorHandling(async ({ name, team }) => {
|
|
2327
|
+
return jsonContent(await client.names.reserve(name, team ? { team } : {}));
|
|
2328
|
+
})
|
|
2329
|
+
);
|
|
2330
|
+
server.tool(
|
|
2331
|
+
"release_name",
|
|
2332
|
+
"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.",
|
|
2333
|
+
{
|
|
2334
|
+
name: import_zod2.z.string().min(1).max(63).describe("The name, from list_names")
|
|
2335
|
+
},
|
|
2336
|
+
withErrorHandling(async ({ name }) => {
|
|
2337
|
+
await client.names.release(name);
|
|
2338
|
+
return jsonContent({ released: name });
|
|
2339
|
+
})
|
|
2340
|
+
);
|
|
2341
|
+
server.tool(
|
|
2342
|
+
"set_name_password",
|
|
2343
|
+
"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.",
|
|
2344
|
+
{
|
|
2345
|
+
name: import_zod2.z.string().min(1).max(63).describe("The name, from list_names"),
|
|
2346
|
+
password: import_zod2.z.string().min(8).max(200).describe("What visitors must type, 8 to 200 characters")
|
|
2347
|
+
},
|
|
2348
|
+
withErrorHandling(async ({ name, password }) => {
|
|
2349
|
+
await client.names.setPassword(name, password);
|
|
2350
|
+
return jsonContent({ name, password: true });
|
|
2351
|
+
})
|
|
2352
|
+
);
|
|
2353
|
+
server.tool(
|
|
2354
|
+
"clear_name_password",
|
|
2355
|
+
"Take the password off a reserved name, so its previews open without one.",
|
|
2356
|
+
{
|
|
2357
|
+
name: import_zod2.z.string().min(1).max(63).describe("The name, from list_names")
|
|
2358
|
+
},
|
|
2359
|
+
withErrorHandling(async ({ name }) => {
|
|
2360
|
+
await client.names.clearPassword(name);
|
|
2361
|
+
return jsonContent({ name, password: false });
|
|
2362
|
+
})
|
|
2363
|
+
);
|
|
2148
2364
|
server.tool(
|
|
2149
2365
|
"describe",
|
|
2150
2366
|
"Describe all available SDK operations, parameters, and types.",
|
package/dist/index.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
registerTools,
|
|
14
14
|
waitForRequestShape,
|
|
15
15
|
withErrorHandling
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-GREHE3PB.mjs";
|
|
17
17
|
|
|
18
18
|
// src/index.ts
|
|
19
19
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -262,7 +262,7 @@ import {
|
|
|
262
262
|
} from "@webhooks-cc/sdk";
|
|
263
263
|
|
|
264
264
|
// src/version.ts
|
|
265
|
-
var VERSION = true ? "1.
|
|
265
|
+
var VERSION = true ? "1.19.0" : "0.0.0-dev";
|
|
266
266
|
var MCP_USER_AGENT = `webhooks-cc-mcp/${VERSION}`;
|
|
267
267
|
|
|
268
268
|
// src/sandbox-tools.ts
|
package/dist/tools.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { WebhooksCC, Request, PayloadSchemaFormat } from '@webhooks-cc/sdk';
|
|
2
|
+
import { WebhooksCC, Request, PayloadSchemaFormat, TunnelShareList } from '@webhooks-cc/sdk';
|
|
3
3
|
|
|
4
4
|
declare const createEndpointShape: {
|
|
5
5
|
name: z.ZodOptional<z.ZodString>;
|
|
@@ -166,11 +166,31 @@ interface RegisterToolsOptions {
|
|
|
166
166
|
declare const REMOTE_MAX_WAIT_MS = 50000;
|
|
167
167
|
/** Shortest poll interval on the hosted server: each poll is an API request. */
|
|
168
168
|
declare const REMOTE_MIN_POLL_MS = 1000;
|
|
169
|
+
/**
|
|
170
|
+
* What an agent can do about the account's sharing limits, from a share
|
|
171
|
+
* list: the next step when slots or data run out, so it never has to send
|
|
172
|
+
* the person to the dashboard. `remote` for the hosted server, which has no
|
|
173
|
+
* share_port.
|
|
174
|
+
*/
|
|
175
|
+
declare function shareHints(list: TunnelShareList, remote?: boolean): string[];
|
|
176
|
+
/**
|
|
177
|
+
* A server hint in this server's words: the whk commands it names become
|
|
178
|
+
* the tools that do the same, so an agent does not reach for a CLI. On the
|
|
179
|
+
* hosted server (`remote`) `whk share` stays a command for the person's
|
|
180
|
+
* computer, since that server has no share_port.
|
|
181
|
+
*/
|
|
182
|
+
declare function toolWords(hint: string, remote?: boolean): string;
|
|
183
|
+
/**
|
|
184
|
+
* A share list with hints for an agent, for list_shares and share_port: the
|
|
185
|
+
* server's own (in tool words) and this server's, without repeats, so
|
|
186
|
+
* nothing the server says (a suspension, a pause) is lost.
|
|
187
|
+
*/
|
|
188
|
+
declare function withShareHints(list: TunnelShareList, remote?: boolean): Record<string, unknown>;
|
|
169
189
|
/**
|
|
170
190
|
* Registers the webhooks.cc tools for an account client. Returns their
|
|
171
191
|
* handles, so a caller can swap the tool set later.
|
|
172
192
|
*/
|
|
173
193
|
declare function registerTools(target: ToolServer, client: WebhooksCC, options?: RegisterToolsOptions): RegisteredToolHandle[];
|
|
174
194
|
|
|
175
|
-
export { REMOTE_MAX_WAIT_MS, REMOTE_MIN_POLL_MS, createEndpointShape, deleteEndpointShape, getEndpointShape, getRequestShape, jsonContent, listEndpointsShape, listRequestsShape, payloadSchemaResult, payloadSchemaShape, registerTools, textContent, waitForRequestShape, withErrorHandling };
|
|
195
|
+
export { REMOTE_MAX_WAIT_MS, REMOTE_MIN_POLL_MS, createEndpointShape, deleteEndpointShape, getEndpointShape, getRequestShape, jsonContent, listEndpointsShape, listRequestsShape, payloadSchemaResult, payloadSchemaShape, registerTools, shareHints, textContent, toolWords, waitForRequestShape, withErrorHandling, withShareHints };
|
|
176
196
|
export type { RegisterToolsOptions, RegisteredToolHandle, ToolResult, ToolServer };
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { WebhooksCC, Request, PayloadSchemaFormat } from '@webhooks-cc/sdk';
|
|
2
|
+
import { WebhooksCC, Request, PayloadSchemaFormat, TunnelShareList } from '@webhooks-cc/sdk';
|
|
3
3
|
|
|
4
4
|
declare const createEndpointShape: {
|
|
5
5
|
name: z.ZodOptional<z.ZodString>;
|
|
@@ -166,11 +166,31 @@ interface RegisterToolsOptions {
|
|
|
166
166
|
declare const REMOTE_MAX_WAIT_MS = 50000;
|
|
167
167
|
/** Shortest poll interval on the hosted server: each poll is an API request. */
|
|
168
168
|
declare const REMOTE_MIN_POLL_MS = 1000;
|
|
169
|
+
/**
|
|
170
|
+
* What an agent can do about the account's sharing limits, from a share
|
|
171
|
+
* list: the next step when slots or data run out, so it never has to send
|
|
172
|
+
* the person to the dashboard. `remote` for the hosted server, which has no
|
|
173
|
+
* share_port.
|
|
174
|
+
*/
|
|
175
|
+
declare function shareHints(list: TunnelShareList, remote?: boolean): string[];
|
|
176
|
+
/**
|
|
177
|
+
* A server hint in this server's words: the whk commands it names become
|
|
178
|
+
* the tools that do the same, so an agent does not reach for a CLI. On the
|
|
179
|
+
* hosted server (`remote`) `whk share` stays a command for the person's
|
|
180
|
+
* computer, since that server has no share_port.
|
|
181
|
+
*/
|
|
182
|
+
declare function toolWords(hint: string, remote?: boolean): string;
|
|
183
|
+
/**
|
|
184
|
+
* A share list with hints for an agent, for list_shares and share_port: the
|
|
185
|
+
* server's own (in tool words) and this server's, without repeats, so
|
|
186
|
+
* nothing the server says (a suspension, a pause) is lost.
|
|
187
|
+
*/
|
|
188
|
+
declare function withShareHints(list: TunnelShareList, remote?: boolean): Record<string, unknown>;
|
|
169
189
|
/**
|
|
170
190
|
* Registers the webhooks.cc tools for an account client. Returns their
|
|
171
191
|
* handles, so a caller can swap the tool set later.
|
|
172
192
|
*/
|
|
173
193
|
declare function registerTools(target: ToolServer, client: WebhooksCC, options?: RegisterToolsOptions): RegisteredToolHandle[];
|
|
174
194
|
|
|
175
|
-
export { REMOTE_MAX_WAIT_MS, REMOTE_MIN_POLL_MS, createEndpointShape, deleteEndpointShape, getEndpointShape, getRequestShape, jsonContent, listEndpointsShape, listRequestsShape, payloadSchemaResult, payloadSchemaShape, registerTools, textContent, waitForRequestShape, withErrorHandling };
|
|
195
|
+
export { REMOTE_MAX_WAIT_MS, REMOTE_MIN_POLL_MS, createEndpointShape, deleteEndpointShape, getEndpointShape, getRequestShape, jsonContent, listEndpointsShape, listRequestsShape, payloadSchemaResult, payloadSchemaShape, registerTools, shareHints, textContent, toolWords, waitForRequestShape, withErrorHandling, withShareHints };
|
|
176
196
|
export type { RegisterToolsOptions, RegisteredToolHandle, ToolResult, ToolServer };
|
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.",
|
|
@@ -1507,7 +1589,7 @@ function registerTools(target, client, options = {}) {
|
|
|
1507
1589
|
);
|
|
1508
1590
|
server.tool(
|
|
1509
1591
|
"get_usage",
|
|
1510
|
-
"Check current request usage, remaining quota, plan, and period end,
|
|
1592
|
+
"Check current request usage, remaining quota, plan, and period end; parsed pages, preview data (whk share) and AI assists, each with when it resets; and the pooled quota of every subscribed team you belong to.",
|
|
1511
1593
|
{},
|
|
1512
1594
|
withErrorHandling(async () => {
|
|
1513
1595
|
const [usageResult, teamsResult] = await Promise.allSettled([
|
|
@@ -1908,6 +1990,143 @@ function registerTools(target, client, options = {}) {
|
|
|
1908
1990
|
);
|
|
1909
1991
|
})
|
|
1910
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
|
+
);
|
|
1911
2130
|
server.tool(
|
|
1912
2131
|
"describe",
|
|
1913
2132
|
"Describe all available SDK operations, parameters, and types.",
|
|
@@ -1933,7 +2152,10 @@ function registerTools(target, client, options = {}) {
|
|
|
1933
2152
|
payloadSchemaResult,
|
|
1934
2153
|
payloadSchemaShape,
|
|
1935
2154
|
registerTools,
|
|
2155
|
+
shareHints,
|
|
1936
2156
|
textContent,
|
|
2157
|
+
toolWords,
|
|
1937
2158
|
waitForRequestShape,
|
|
1938
|
-
withErrorHandling
|
|
2159
|
+
withErrorHandling,
|
|
2160
|
+
withShareHints
|
|
1939
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-GREHE3PB.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.19.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.23.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",
|