@webhooks-cc/mcp 1.23.0 → 2.0.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 +43 -16
- package/dist/bin/mcp.js +1355 -638
- package/dist/{chunk-TMX4P6ZZ.mjs → chunk-VANGXSK7.mjs} +522 -33
- package/dist/index.d.mts +84 -10
- package/dist/index.d.ts +84 -10
- package/dist/index.js +1322 -581
- package/dist/index.mjs +290 -34
- package/dist/tools-Bg9zP80n.d.mts +276 -0
- package/dist/tools-Bg9zP80n.d.ts +276 -0
- package/dist/tools.d.mts +3 -200
- package/dist/tools.d.ts +3 -200
- package/dist/tools.js +526 -33
- package/dist/tools.mjs +15 -1
- package/package.json +5 -4
package/dist/bin/mcp.js
CHANGED
|
@@ -2,125 +2,219 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
// bin/mcp.ts
|
|
5
|
-
var import_stdio = require("@modelcontextprotocol/
|
|
5
|
+
var import_stdio = require("@modelcontextprotocol/server/stdio");
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
|
8
|
-
var
|
|
8
|
+
var import_server3 = require("@modelcontextprotocol/server");
|
|
9
9
|
var import_sdk3 = require("@webhooks-cc/sdk");
|
|
10
10
|
|
|
11
|
-
// src/
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
type: "text",
|
|
38
|
-
text: [
|
|
39
|
-
scope,
|
|
40
|
-
"Diagnose webhook delivery step by step.",
|
|
41
|
-
"Use list_endpoints or get_endpoint to confirm the endpoint and URL.",
|
|
42
|
-
"Use list_requests, wait_for_request, or wait_for_requests to check whether anything arrived.",
|
|
43
|
-
"If the provider is known, use preview_webhook or send_webhook to reproduce the webhook with realistic signing.",
|
|
44
|
-
"Use verify_signature when a secret is available.",
|
|
45
|
-
"Use compare_requests to compare retries or changed payloads.",
|
|
46
|
-
"Conclude with the most likely cause and the next concrete fix."
|
|
47
|
-
].filter(Boolean).join(" ")
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
]
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
);
|
|
54
|
-
server.registerPrompt(
|
|
55
|
-
"setup_provider_testing",
|
|
56
|
-
{
|
|
57
|
-
title: "Setup Provider Testing",
|
|
58
|
-
description: "Guide an agent through setting up webhook testing for a provider.",
|
|
59
|
-
argsSchema: {
|
|
60
|
-
provider: import_zod.z.string().describe("Webhook provider to test"),
|
|
61
|
-
targetUrl: import_zod.z.string().optional().describe("Optional local or remote target URL")
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
async ({ provider, targetUrl }) => {
|
|
65
|
-
return {
|
|
66
|
-
description: `Set up webhook testing for ${provider}.`,
|
|
67
|
-
messages: [
|
|
68
|
-
{
|
|
69
|
-
role: "user",
|
|
70
|
-
content: {
|
|
71
|
-
type: "text",
|
|
72
|
-
text: [
|
|
73
|
-
`Set up webhook testing for ${provider}.`,
|
|
74
|
-
"Use list_provider_templates to inspect supported templates and signing details first.",
|
|
75
|
-
"Create an endpoint with create_endpoint, preferably ephemeral.",
|
|
76
|
-
"If a target URL is provided, use preview_webhook before send_to so the request shape is visible.",
|
|
77
|
-
targetUrl ? `Target URL: ${targetUrl}.` : null,
|
|
78
|
-
"Send a realistic provider webhook, wait for capture, and verify the signature if a secret is available.",
|
|
79
|
-
"Once real deliveries are captured, use generate_payload_schema to write the payload types for the handler.",
|
|
80
|
-
"Return the endpoint URL, the exact tools used, and the next step for the developer."
|
|
81
|
-
].filter(Boolean).join(" ")
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
]
|
|
85
|
-
};
|
|
86
|
-
}
|
|
11
|
+
// src/local-tools.ts
|
|
12
|
+
var import_server = require("@modelcontextprotocol/server");
|
|
13
|
+
var import_zod2 = require("zod");
|
|
14
|
+
|
|
15
|
+
// src/catalog.ts
|
|
16
|
+
var TOOL_GROUPS = {
|
|
17
|
+
core: "Endpoints, captured requests and emails, sending test webhooks, signatures, forwarding destinations, deliveries, previews of your local app and their logs, usage and approvals",
|
|
18
|
+
forwarding: "The rest of forwarding: preview what a destination would send, the endpoint's forwarding settings and signing secret, test deliveries",
|
|
19
|
+
layouts: "Email layouts and named fields: try layouts and fields on a captured email, add ready-made layouts, and AI setup",
|
|
20
|
+
extras: "Bulk endpoint changes, favourites, request counts and payload schemas",
|
|
21
|
+
teams: "Teams: members, invites, sharing endpoints with a team, and pricing team changes",
|
|
22
|
+
account: "The account: billing, API keys, connected agents, public share links to requests, and the display name",
|
|
23
|
+
domains: "Custom inbound email domains: add, verify, route and remove them",
|
|
24
|
+
sheets: "Google Sheets destinations: Google accounts, spreadsheets and their tabs",
|
|
25
|
+
names: "Preview names: reserve, release and password-protect them"
|
|
26
|
+
};
|
|
27
|
+
var TOOL_GROUP_NAMES = Object.keys(TOOL_GROUPS);
|
|
28
|
+
var read = { readOnly: true };
|
|
29
|
+
var write = {};
|
|
30
|
+
var update = { idempotent: true };
|
|
31
|
+
var remove = { destructive: true, idempotent: true };
|
|
32
|
+
var replace = { destructive: true, idempotent: true };
|
|
33
|
+
var outward = { openWorld: true };
|
|
34
|
+
function group(name, tools) {
|
|
35
|
+
return Object.fromEntries(
|
|
36
|
+
Object.entries(tools).map(([tool, behaviour]) => [tool, { group: name, ...behaviour }])
|
|
87
37
|
);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
38
|
+
}
|
|
39
|
+
var TOOL_CATALOG = {
|
|
40
|
+
...group("core", {
|
|
41
|
+
create_endpoint: write,
|
|
42
|
+
list_endpoints: read,
|
|
43
|
+
get_endpoint: read,
|
|
44
|
+
// Replaces what it is given (a mock response, the signing secret).
|
|
45
|
+
update_endpoint: replace,
|
|
46
|
+
delete_endpoint: remove,
|
|
47
|
+
list_requests: read,
|
|
48
|
+
search_requests: read,
|
|
49
|
+
get_request: read,
|
|
50
|
+
wait_for_request: read,
|
|
51
|
+
wait_for_requests: read,
|
|
52
|
+
replay_request: outward,
|
|
53
|
+
compare_requests: read,
|
|
54
|
+
extract_from_request: read,
|
|
55
|
+
clear_requests: remove,
|
|
56
|
+
send_webhook: write,
|
|
57
|
+
send_to: outward,
|
|
58
|
+
test_webhook_flow: outward,
|
|
59
|
+
preview_webhook: read,
|
|
60
|
+
preview_response: read,
|
|
61
|
+
list_provider_templates: read,
|
|
62
|
+
verify_signature: read,
|
|
63
|
+
list_emails: read,
|
|
64
|
+
get_email: read,
|
|
65
|
+
wait_for_email: read,
|
|
66
|
+
send_test_email: write,
|
|
67
|
+
render_email: outward,
|
|
68
|
+
list_destinations: read,
|
|
69
|
+
add_destination: write,
|
|
70
|
+
update_destination: replace,
|
|
71
|
+
remove_destination: remove,
|
|
72
|
+
list_deliveries: read,
|
|
73
|
+
redeliver: outward,
|
|
74
|
+
share_port: outward,
|
|
75
|
+
list_shares: read,
|
|
76
|
+
share_requests: read,
|
|
77
|
+
stop_share: remove,
|
|
78
|
+
get_usage: read,
|
|
79
|
+
// Collecting an approved key hands it over once: not a pure read.
|
|
80
|
+
wait_for_approval: write,
|
|
81
|
+
describe: read
|
|
82
|
+
}),
|
|
83
|
+
...group("forwarding", {
|
|
84
|
+
preview_destination: read,
|
|
85
|
+
configure_forwarding: replace,
|
|
86
|
+
// rotate: true replaces the secret, and receivers holding the old one fail.
|
|
87
|
+
get_forwarding_secret: { destructive: true },
|
|
88
|
+
test_forwarding: outward
|
|
89
|
+
}),
|
|
90
|
+
...group("layouts", {
|
|
91
|
+
preview_layouts: read,
|
|
92
|
+
list_layout_templates: read,
|
|
93
|
+
add_layout_template: write,
|
|
94
|
+
preview_email_fields: read,
|
|
95
|
+
ai_setup_status: read,
|
|
96
|
+
// Sends the chosen emails to Anthropic once the owner approves.
|
|
97
|
+
ai_setup: outward
|
|
98
|
+
}),
|
|
99
|
+
...group("extras", {
|
|
100
|
+
create_endpoints: write,
|
|
101
|
+
delete_endpoints: remove,
|
|
102
|
+
favorite_endpoint: update,
|
|
103
|
+
count_requests: read,
|
|
104
|
+
generate_payload_schema: read
|
|
105
|
+
}),
|
|
106
|
+
...group("teams", {
|
|
107
|
+
list_teams: read,
|
|
108
|
+
list_team_members: read,
|
|
109
|
+
share_endpoint: update,
|
|
110
|
+
unshare_endpoint: remove,
|
|
111
|
+
create_team: write,
|
|
112
|
+
rename_team: update,
|
|
113
|
+
delete_team: remove,
|
|
114
|
+
remove_team_member: remove,
|
|
115
|
+
leave_team: remove,
|
|
116
|
+
invite_to_team: outward,
|
|
117
|
+
list_invites: read,
|
|
118
|
+
answer_invite: write,
|
|
119
|
+
withdraw_invite: remove,
|
|
120
|
+
quote_team_change: read
|
|
121
|
+
}),
|
|
122
|
+
...group("account", {
|
|
123
|
+
get_billing: read,
|
|
124
|
+
list_api_keys: read,
|
|
125
|
+
create_api_key: write,
|
|
126
|
+
revoke_api_key: remove,
|
|
127
|
+
rename_account: update,
|
|
128
|
+
list_connected_agents: read,
|
|
129
|
+
disconnect_agent: remove,
|
|
130
|
+
list_share_links: read,
|
|
131
|
+
create_share_link: write,
|
|
132
|
+
revoke_share_link: remove
|
|
133
|
+
}),
|
|
134
|
+
...group("domains", {
|
|
135
|
+
list_domains: read,
|
|
136
|
+
add_domain: write,
|
|
137
|
+
// A failed check counts toward lapsing a verified domain: not safe to repeat.
|
|
138
|
+
verify_domain: { destructive: true },
|
|
139
|
+
// Replaces the routing; a missing catch-all refuses mail.
|
|
140
|
+
route_domain: replace,
|
|
141
|
+
remove_domain: remove
|
|
142
|
+
}),
|
|
143
|
+
...group("sheets", {
|
|
144
|
+
list_google_connections: read,
|
|
145
|
+
connect_google: outward,
|
|
146
|
+
pick_spreadsheet: outward,
|
|
147
|
+
create_spreadsheet: outward,
|
|
148
|
+
list_sheet_tabs: { readOnly: true, openWorld: true },
|
|
149
|
+
disconnect_google: remove
|
|
150
|
+
}),
|
|
151
|
+
...group("names", {
|
|
152
|
+
// The log of what visitors asked a running preview for.
|
|
153
|
+
list_names: read,
|
|
154
|
+
reserve_name: write,
|
|
155
|
+
release_name: remove,
|
|
156
|
+
set_name_password: update,
|
|
157
|
+
// Opens the name to anyone again.
|
|
158
|
+
clear_name_password: { destructive: true, idempotent: true }
|
|
159
|
+
})
|
|
160
|
+
};
|
|
161
|
+
var ENABLE_TOOLS = "enable_tools";
|
|
162
|
+
function toolAnnotations(name) {
|
|
163
|
+
const entry = TOOL_CATALOG[name];
|
|
164
|
+
const readOnly = entry?.readOnly === true;
|
|
165
|
+
return {
|
|
166
|
+
title: toolTitle(name),
|
|
167
|
+
readOnlyHint: readOnly,
|
|
168
|
+
// MCP reads a missing destructiveHint as true on any tool that writes.
|
|
169
|
+
destructiveHint: !readOnly && entry?.destructive === true,
|
|
170
|
+
idempotentHint: readOnly || entry?.idempotent === true,
|
|
171
|
+
openWorldHint: entry?.openWorld === true
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function toolTitle(name) {
|
|
175
|
+
const words = name.split("_").map((word) => TITLE_WORDS[word] ?? word);
|
|
176
|
+
const first = words[0] ?? "";
|
|
177
|
+
return [first.charAt(0).toUpperCase() + first.slice(1), ...words.slice(1)].join(" ");
|
|
178
|
+
}
|
|
179
|
+
var TITLE_WORDS = {
|
|
180
|
+
api: "API",
|
|
181
|
+
ai: "AI",
|
|
182
|
+
mcp: "MCP"
|
|
183
|
+
};
|
|
184
|
+
function parseToolGroups(setting) {
|
|
185
|
+
if (setting === null || setting === void 0 || setting.trim() === "") return null;
|
|
186
|
+
const groups = /* @__PURE__ */ new Set(["core"]);
|
|
187
|
+
const unknown = [];
|
|
188
|
+
for (const raw of setting.split(/[\s,+]+/)) {
|
|
189
|
+
const name = raw.trim().toLowerCase();
|
|
190
|
+
if (!name) continue;
|
|
191
|
+
if (name === "all") {
|
|
192
|
+
for (const g of TOOL_GROUP_NAMES) groups.add(g);
|
|
193
|
+
} else if (TOOL_GROUP_NAMES.includes(name)) {
|
|
194
|
+
groups.add(name);
|
|
195
|
+
} else {
|
|
196
|
+
unknown.push(raw.trim());
|
|
118
197
|
}
|
|
119
|
-
|
|
198
|
+
}
|
|
199
|
+
return { groups, unknown };
|
|
200
|
+
}
|
|
201
|
+
var LOADS_EVERYTHING = [
|
|
202
|
+
/^claude-code$/i,
|
|
203
|
+
/^claude-ai$/i,
|
|
204
|
+
/^claude$/i,
|
|
205
|
+
/codex/i,
|
|
206
|
+
/^openai-mcp$/i,
|
|
207
|
+
/chatgpt/i
|
|
208
|
+
];
|
|
209
|
+
function clientLoadsEverything(clientName) {
|
|
210
|
+
if (!clientName) return false;
|
|
211
|
+
const name = clientName.trim().toLowerCase().replace(/[\s_]+/g, "-");
|
|
212
|
+
return LOADS_EVERYTHING.some((pattern) => pattern.test(name));
|
|
120
213
|
}
|
|
121
214
|
|
|
122
|
-
// src/
|
|
123
|
-
var
|
|
215
|
+
// src/tools.ts
|
|
216
|
+
var import_zod = require("zod");
|
|
217
|
+
var import_sdk = require("@webhooks-cc/sdk");
|
|
124
218
|
|
|
125
219
|
// src/compact.ts
|
|
126
220
|
var MAX_OUTPUT = 32768;
|
|
@@ -330,142 +424,11 @@ function compactRequest(request) {
|
|
|
330
424
|
return compact;
|
|
331
425
|
}
|
|
332
426
|
|
|
333
|
-
// src/resources.ts
|
|
334
|
-
var ENDPOINTS_RESOURCE_URI = "webhooks://endpoints";
|
|
335
|
-
var ENDPOINT_RECENT_TEMPLATE_URI = "webhooks://endpoint/{slug}/recent";
|
|
336
|
-
var REQUEST_TEMPLATE_URI = "webhooks://request/{id}";
|
|
337
|
-
var MAX_ENDPOINT_RESOURCE_ITEMS = 25;
|
|
338
|
-
var RECENT_REQUEST_LIMIT = 10;
|
|
339
|
-
function jsonResource(uri, value) {
|
|
340
|
-
return {
|
|
341
|
-
contents: [
|
|
342
|
-
{
|
|
343
|
-
uri,
|
|
344
|
-
mimeType: "application/json",
|
|
345
|
-
text: JSON.stringify(value, null, 2)
|
|
346
|
-
}
|
|
347
|
-
]
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
function summarizeRequest(request) {
|
|
351
|
-
return {
|
|
352
|
-
id: request.id,
|
|
353
|
-
method: request.method,
|
|
354
|
-
path: request.path,
|
|
355
|
-
receivedAt: request.receivedAt,
|
|
356
|
-
contentType: request.contentType ?? null
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
function variableToString(value, name) {
|
|
360
|
-
if (typeof value === "string" && value.length > 0) {
|
|
361
|
-
return value;
|
|
362
|
-
}
|
|
363
|
-
throw new Error(`Missing resource variable "${name}"`);
|
|
364
|
-
}
|
|
365
|
-
async function listEndpointSummaries(client) {
|
|
366
|
-
const endpoints = (await client.endpoints.list()).slice().sort((left, right) => right.createdAt - left.createdAt);
|
|
367
|
-
const truncated = endpoints.length > MAX_ENDPOINT_RESOURCE_ITEMS;
|
|
368
|
-
const visible = endpoints.slice(0, MAX_ENDPOINT_RESOURCE_ITEMS);
|
|
369
|
-
const summaries = await Promise.all(
|
|
370
|
-
visible.map(async (endpoint) => {
|
|
371
|
-
const recent = await client.requests.list(endpoint.slug, { limit: 1 });
|
|
372
|
-
return {
|
|
373
|
-
...endpoint,
|
|
374
|
-
lastRequest: recent[0] ? summarizeRequest(recent[0]) : null
|
|
375
|
-
};
|
|
376
|
-
})
|
|
377
|
-
);
|
|
378
|
-
return {
|
|
379
|
-
endpoints: summaries,
|
|
380
|
-
total: endpoints.length,
|
|
381
|
-
truncated
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
function registerResources(server, client) {
|
|
385
|
-
server.registerResource(
|
|
386
|
-
"endpoints-overview",
|
|
387
|
-
ENDPOINTS_RESOURCE_URI,
|
|
388
|
-
{
|
|
389
|
-
title: "Endpoints Overview",
|
|
390
|
-
description: "All endpoints with a summary of recent activity.",
|
|
391
|
-
mimeType: "application/json"
|
|
392
|
-
},
|
|
393
|
-
async () => {
|
|
394
|
-
return jsonResource(ENDPOINTS_RESOURCE_URI, await listEndpointSummaries(client));
|
|
395
|
-
}
|
|
396
|
-
);
|
|
397
|
-
server.registerResource(
|
|
398
|
-
"endpoint-recent-requests",
|
|
399
|
-
new import_mcp.ResourceTemplate(ENDPOINT_RECENT_TEMPLATE_URI, {
|
|
400
|
-
list: async () => {
|
|
401
|
-
const endpoints = await client.endpoints.list();
|
|
402
|
-
return {
|
|
403
|
-
resources: endpoints.map((endpoint) => ({
|
|
404
|
-
uri: `webhooks://endpoint/${endpoint.slug}/recent`,
|
|
405
|
-
name: `${endpoint.slug} recent requests`,
|
|
406
|
-
description: `Recent requests for endpoint ${endpoint.slug}`,
|
|
407
|
-
mimeType: "application/json"
|
|
408
|
-
}))
|
|
409
|
-
};
|
|
410
|
-
},
|
|
411
|
-
complete: {
|
|
412
|
-
slug: async (value) => {
|
|
413
|
-
const endpoints = await client.endpoints.list();
|
|
414
|
-
return endpoints.map((endpoint) => endpoint.slug).filter((slug) => slug.startsWith(value)).slice(0, 20);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}),
|
|
418
|
-
{
|
|
419
|
-
title: "Endpoint Recent Requests",
|
|
420
|
-
description: "Last 10 captured requests for an endpoint.",
|
|
421
|
-
mimeType: "application/json"
|
|
422
|
-
},
|
|
423
|
-
async (uri, variables) => {
|
|
424
|
-
const slug = variableToString(variables.slug, "slug");
|
|
425
|
-
const [endpoint, requests] = await Promise.all([
|
|
426
|
-
client.endpoints.get(slug),
|
|
427
|
-
client.requests.list(slug, { limit: RECENT_REQUEST_LIMIT })
|
|
428
|
-
]);
|
|
429
|
-
return jsonResource(uri.toString(), {
|
|
430
|
-
endpoint,
|
|
431
|
-
requests: requests.map(compactRequest)
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
);
|
|
435
|
-
server.registerResource(
|
|
436
|
-
"request-details",
|
|
437
|
-
new import_mcp.ResourceTemplate(REQUEST_TEMPLATE_URI, {
|
|
438
|
-
list: void 0,
|
|
439
|
-
complete: {}
|
|
440
|
-
}),
|
|
441
|
-
{
|
|
442
|
-
title: "Request Details",
|
|
443
|
-
description: "Full details for a captured request by ID.",
|
|
444
|
-
mimeType: "application/json"
|
|
445
|
-
},
|
|
446
|
-
async (uri, variables) => {
|
|
447
|
-
const id = variableToString(variables.id, "id");
|
|
448
|
-
const request = await client.requests.get(id);
|
|
449
|
-
return jsonResource(uri.toString(), compactRequest(request));
|
|
450
|
-
}
|
|
451
|
-
);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// src/sandbox-tools.ts
|
|
455
|
-
var import_zod3 = require("zod");
|
|
456
|
-
var import_sdk2 = require("@webhooks-cc/sdk");
|
|
457
|
-
|
|
458
|
-
// src/version.ts
|
|
459
|
-
var VERSION = true ? "1.23.0" : "0.0.0-dev";
|
|
460
|
-
var MCP_USER_AGENT = `webhooks-cc-mcp/${VERSION}`;
|
|
461
|
-
|
|
462
427
|
// src/tools.ts
|
|
463
|
-
var import_zod2 = require("zod");
|
|
464
|
-
var import_sdk = require("@webhooks-cc/sdk");
|
|
465
428
|
var MAX_BODY_SIZE = MAX_OUTPUT;
|
|
466
429
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
467
430
|
var TIME_SEPARATOR = " \u2014 ";
|
|
468
|
-
var httpUrlSchema =
|
|
431
|
+
var httpUrlSchema = import_zod.z.string().url().refine(
|
|
469
432
|
(value) => {
|
|
470
433
|
try {
|
|
471
434
|
const protocol = new URL(value).protocol;
|
|
@@ -476,24 +439,24 @@ var httpUrlSchema = import_zod2.z.string().url().refine(
|
|
|
476
439
|
},
|
|
477
440
|
{ message: "Only http and https URLs are supported" }
|
|
478
441
|
);
|
|
479
|
-
var methodSchema =
|
|
480
|
-
var kindSchema =
|
|
442
|
+
var methodSchema = import_zod.z.enum(HTTP_METHODS).default("POST").describe("HTTP method (default: POST)");
|
|
443
|
+
var kindSchema = import_zod.z.enum(["http", "email"]).optional().describe('Only HTTP requests ("http") or only captured emails ("email")');
|
|
481
444
|
var EMAIL_SCAN_LIMIT = 100;
|
|
482
|
-
var durationOrTimestampSchema =
|
|
445
|
+
var durationOrTimestampSchema = import_zod.z.union([import_zod.z.string(), import_zod.z.number()]);
|
|
483
446
|
var CONDITION_FIELD_NAMES = Object.keys(import_sdk.CONDITION_FIELDS);
|
|
484
447
|
var CONDITION_OP_NAMES = import_sdk.CONDITION_OPS;
|
|
485
448
|
function conditionSchema(use) {
|
|
486
449
|
const fields = use === "response" ? CONDITION_FIELD_NAMES.filter((field) => import_sdk.CONDITION_FIELDS[field].response) : CONDITION_FIELD_NAMES;
|
|
487
|
-
return
|
|
488
|
-
field:
|
|
450
|
+
return import_zod.z.object({
|
|
451
|
+
field: import_zod.z.enum(fields).describe(
|
|
489
452
|
use === "response" ? "What to read: method, path, header (name), query (name), body (text), body_path (path into a JSON or form body), content_type, size" : "What to read: method, path, header (name), query (name), body, body_path (path), content_type, size, kind (http or email), email (name: subject, from, to, text or tag), spam_score, email_auth (name: spf, dkim or dmarc), signature (verified, failed or missing)"
|
|
490
453
|
),
|
|
491
|
-
op:
|
|
454
|
+
op: import_zod.z.enum(CONDITION_OP_NAMES).describe(
|
|
492
455
|
"eq, neq, contains, not_contains, starts_with, ends_with, matches (glob: * one path segment, ** any, ? one character), regex (RE2, searched anywhere; (?i) ignores case), exists, not_exists, gt, gte, lt, lte (numbers)"
|
|
493
456
|
),
|
|
494
|
-
value:
|
|
495
|
-
name:
|
|
496
|
-
path:
|
|
457
|
+
value: import_zod.z.string().max(import_sdk.MAX_CONDITION_VALUE_LEN).optional().describe(`What to compare with (not for exists and not_exists)`),
|
|
458
|
+
name: import_zod.z.string().max(import_sdk.MAX_CONDITION_NAME_LEN).optional().describe("Header or query parameter name; the email part; spf, dkim or dmarc"),
|
|
459
|
+
path: import_zod.z.string().max(import_sdk.MAX_CONDITION_PATH_LEN).optional().describe('Dot path for body_path, e.g. data.object.status or items.0.sku or a["odd.key"]')
|
|
497
460
|
}).superRefine((condition, ctx) => {
|
|
498
461
|
for (const issue of (0, import_sdk.conditionIssues)(condition, { use })) {
|
|
499
462
|
ctx.addIssue({ code: "custom", message: issue });
|
|
@@ -501,46 +464,46 @@ function conditionSchema(use) {
|
|
|
501
464
|
});
|
|
502
465
|
}
|
|
503
466
|
var ruleConditionSchema = conditionSchema("response");
|
|
504
|
-
var emailFieldSchema =
|
|
505
|
-
name:
|
|
506
|
-
from:
|
|
507
|
-
header:
|
|
508
|
-
pattern:
|
|
467
|
+
var emailFieldSchema = import_zod.z.object({
|
|
468
|
+
name: import_zod.z.string().describe("Letters, digits and _, starting with a letter: {{ fields.name }}"),
|
|
469
|
+
from: import_zod.z.enum(["subject", "text", "html", "from", "to", "header"]).describe("What it reads; html is the HTML as text"),
|
|
470
|
+
header: import_zod.z.string().optional().describe('The header name, for from "header"'),
|
|
471
|
+
pattern: import_zod.z.string().describe("RE2 regular expression; the first group is the value (no lookarounds)")
|
|
509
472
|
});
|
|
510
|
-
var layoutSchema =
|
|
511
|
-
id:
|
|
512
|
-
name:
|
|
513
|
-
enabled:
|
|
514
|
-
match:
|
|
515
|
-
logic:
|
|
516
|
-
conditions:
|
|
473
|
+
var layoutSchema = import_zod.z.object({
|
|
474
|
+
id: import_zod.z.string().describe("Stable id: 1 to 40 letters, digits, - or _"),
|
|
475
|
+
name: import_zod.z.string().max(100),
|
|
476
|
+
enabled: import_zod.z.boolean().optional().describe("Off: kept but never matched (default on)"),
|
|
477
|
+
match: import_zod.z.object({
|
|
478
|
+
logic: import_zod.z.enum(["and", "or"]).optional(),
|
|
479
|
+
conditions: import_zod.z.array(conditionSchema("filter")).max(10)
|
|
517
480
|
}).optional().describe("When it applies, e.g. the sender or subject; no conditions: every email"),
|
|
518
|
-
fields:
|
|
519
|
-
lineItems:
|
|
520
|
-
from:
|
|
521
|
-
pattern:
|
|
481
|
+
fields: import_zod.z.array(emailFieldSchema).max(20).optional().describe("Named values, as emailFields"),
|
|
482
|
+
lineItems: import_zod.z.object({
|
|
483
|
+
from: import_zod.z.enum(["text", "html", "attachment"]),
|
|
484
|
+
pattern: import_zod.z.string().optional().describe(
|
|
522
485
|
"text/html: RE2 with named groups found again and again, one row each, e.g. (?P<sku>[A-Z]+-\\d+)\\s+x(?P<qty>\\d+)"
|
|
523
486
|
),
|
|
524
|
-
start:
|
|
525
|
-
end:
|
|
526
|
-
attachment:
|
|
527
|
-
sheet:
|
|
528
|
-
columns:
|
|
487
|
+
start: import_zod.z.string().optional().describe("text/html: search only after this (RE2)"),
|
|
488
|
+
end: import_zod.z.string().optional().describe("text/html: and before this (RE2)"),
|
|
489
|
+
attachment: import_zod.z.string().optional().describe("attachment: file name with * and ? (default: the first spreadsheet or CSV)"),
|
|
490
|
+
sheet: import_zod.z.string().optional().describe("attachment: sheet name (default: the first)"),
|
|
491
|
+
columns: import_zod.z.array(import_zod.z.object({ name: import_zod.z.string(), column: import_zod.z.string() })).max(30).optional().describe("attachment: { name, column: header text or letter }; default: every column")
|
|
529
492
|
}).nullable().optional().describe("Rows such as an order's products"),
|
|
530
|
-
parseAttachments:
|
|
493
|
+
parseAttachments: import_zod.z.boolean().optional().describe("Parse CSV, XLSX, ODS, PDF text and text files into the JSON; uses parsed pages")
|
|
531
494
|
});
|
|
532
495
|
var destinationFields = {
|
|
533
|
-
name:
|
|
534
|
-
enabled:
|
|
535
|
-
http:
|
|
536
|
-
email:
|
|
537
|
-
filter:
|
|
538
|
-
logic:
|
|
539
|
-
conditions:
|
|
496
|
+
name: import_zod.z.string().max(100).nullable().optional().describe("A name for the list and the log"),
|
|
497
|
+
enabled: import_zod.z.boolean().optional().describe("Send to it (default true)"),
|
|
498
|
+
http: import_zod.z.boolean().optional().describe("Takes HTTP requests (default true)"),
|
|
499
|
+
email: import_zod.z.boolean().optional().describe("Takes emails (default true)"),
|
|
500
|
+
filter: import_zod.z.object({
|
|
501
|
+
logic: import_zod.z.enum(["and", "or"]).optional().describe("and (default): every condition; or: any"),
|
|
502
|
+
conditions: import_zod.z.array(conditionSchema("filter")).max(10)
|
|
540
503
|
}).nullable().optional().describe(
|
|
541
504
|
"Send only captures that match (null: everything it takes). Conditions like response rules plus kind, email (name: subject, from, to, text, tag), spam_score, email_auth (name: spf, dkim, dmarc) and signature (verified, failed, missing)"
|
|
542
505
|
),
|
|
543
|
-
format:
|
|
506
|
+
format: import_zod.z.enum([
|
|
544
507
|
"auto",
|
|
545
508
|
"as_received",
|
|
546
509
|
"json",
|
|
@@ -553,103 +516,103 @@ var destinationFields = {
|
|
|
553
516
|
]).optional().describe(
|
|
554
517
|
"auto (pick from the URL), as_received (relay HTTP requests unchanged; emails as signed JSON), json (signed JSON envelope), template (your bodyTemplate, signed like json), chat (Slack or Discord message), or an inbound mail provider's shape for emails (postmark, sendgrid, mailgun, cloudmailin; HTTP requests then go as received), to test the inbound handler you wrote for that provider"
|
|
555
518
|
),
|
|
556
|
-
mailgunSigningKey:
|
|
519
|
+
mailgunSigningKey: import_zod.z.string().max(200).nullable().optional().describe(
|
|
557
520
|
"For format mailgun: your Mailgun HTTP webhook signing key, so the form's signature verifies; null removes it. Write-only."
|
|
558
521
|
),
|
|
559
|
-
bodyTemplate:
|
|
522
|
+
bodyTemplate: import_zod.z.unknown().optional().describe(
|
|
560
523
|
`For format template: any JSON value whose strings hold {{ placeholders }}, e.g. {"event": "{{ json.type }}", "amount": "{{ json.data.amount }}", "code": "{{ codes.0 }}"}. A string that is only a placeholder keeps the value's type; a missing field becomes null. HTTP requests read method, path, query.x, headers.x, body, json.path, id, receivedAt, signature; emails read the email.received fields (subject, from.address, to, text, codes, links, auth, spam, tag)`
|
|
561
524
|
),
|
|
562
|
-
messageTemplate:
|
|
525
|
+
messageTemplate: import_zod.z.string().max(4e3).nullable().optional().describe(
|
|
563
526
|
"For format chat: the message text with {{ placeholders }}; null sends the standard message"
|
|
564
527
|
),
|
|
565
|
-
headers:
|
|
566
|
-
|
|
567
|
-
name:
|
|
568
|
-
value:
|
|
528
|
+
headers: import_zod.z.array(
|
|
529
|
+
import_zod.z.object({
|
|
530
|
+
name: import_zod.z.string().describe("Header name, e.g. Authorization"),
|
|
531
|
+
value: import_zod.z.string().nullable().describe("Value, or null to keep the stored one")
|
|
569
532
|
})
|
|
570
533
|
).max(10).optional().describe("Your own headers for every delivery (not chat). Replaces the whole list"),
|
|
571
|
-
appendPath:
|
|
572
|
-
retrySeconds:
|
|
573
|
-
keepOrder:
|
|
574
|
-
sentField:
|
|
575
|
-
throttleSeconds:
|
|
534
|
+
appendPath: import_zod.z.boolean().optional().describe("Append the captured path to the URL (as_received)"),
|
|
535
|
+
retrySeconds: import_zod.z.union([import_zod.z.literal(0), import_zod.z.literal(3600), import_zod.z.literal(86400)]).optional().describe("Retry failed deliveries for 86400 (default), 3600 or 0 seconds"),
|
|
536
|
+
keepOrder: import_zod.z.boolean().optional().describe("One delivery at a time, in capture order"),
|
|
537
|
+
sentField: import_zod.z.string().max(128).nullable().optional().describe("JSON body field with the sender's own timestamp"),
|
|
538
|
+
throttleSeconds: import_zod.z.number().int().min(0).max(3600).optional().describe(
|
|
576
539
|
"At most one delivery every N seconds; the rest are not sent (1 makes a heads-up channel)"
|
|
577
540
|
),
|
|
578
|
-
delaySeconds:
|
|
541
|
+
delaySeconds: import_zod.z.number().int().min(0).max(3600).optional().describe("Wait this long after the capture before sending, like a callback")
|
|
579
542
|
};
|
|
580
|
-
var responseRuleSchema =
|
|
581
|
-
name:
|
|
582
|
-
enabled:
|
|
583
|
-
logic:
|
|
584
|
-
conditions:
|
|
585
|
-
response:
|
|
543
|
+
var responseRuleSchema = import_zod.z.object({
|
|
544
|
+
name: import_zod.z.string().max(import_sdk.MAX_RULE_NAME_LEN).optional().describe(`Human-readable rule name (max ${import_sdk.MAX_RULE_NAME_LEN} chars)`),
|
|
545
|
+
enabled: import_zod.z.boolean().optional().default(true).describe("Whether this rule is active"),
|
|
546
|
+
logic: import_zod.z.enum(["and", "or"]).optional().default("and").describe('How to combine conditions: "and" (all match) or "or" (any match)'),
|
|
547
|
+
conditions: import_zod.z.array(ruleConditionSchema).min(1).max(10).describe("Conditions to evaluate (1-10)"),
|
|
548
|
+
response: import_zod.z.lazy(() => mockResponseSchema).describe("Response when conditions match")
|
|
586
549
|
});
|
|
587
|
-
var responseRulesSchema =
|
|
588
|
-
var mockResponseSchema =
|
|
589
|
-
status:
|
|
590
|
-
body:
|
|
550
|
+
var responseRulesSchema = import_zod.z.array(responseRuleSchema).max(50).describe("Ordered conditional response rules (first match wins, max 50)");
|
|
551
|
+
var mockResponseSchema = import_zod.z.object({
|
|
552
|
+
status: import_zod.z.number().int().min(import_sdk.MOCK_RESPONSE_STATUS_MIN).max(import_sdk.MOCK_RESPONSE_STATUS_MAX).describe(`HTTP status code (${import_sdk.MOCK_RESPONSE_STATUS_MIN}-${import_sdk.MOCK_RESPONSE_STATUS_MAX})`),
|
|
553
|
+
body: import_zod.z.string().default("").describe(
|
|
591
554
|
'Response body (default: empty). {{ placeholders }} are filled in from the request: {{ json.id }} (a JSON or form body field), {{ body }}, {{ headers.user-agent }}, {{ query.page }}, {{ method }}, {{ path }}, {{ id }}, {{ uuid }}, {{ now }}; filters: | default: "x", | json, | lower, | upper, | truncate: 80'
|
|
592
555
|
),
|
|
593
|
-
headers:
|
|
594
|
-
delay:
|
|
556
|
+
headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).default({}).describe("Response headers (default: none); values may use {{ placeholders }} too"),
|
|
557
|
+
delay: import_zod.z.number().int().min(import_sdk.MOCK_RESPONSE_DELAY_MIN).max(import_sdk.MOCK_RESPONSE_DELAY_MAX).optional().describe(
|
|
595
558
|
`Response delay in milliseconds (${import_sdk.MOCK_RESPONSE_DELAY_MIN}-${import_sdk.MOCK_RESPONSE_DELAY_MAX}, default: none)`
|
|
596
559
|
)
|
|
597
560
|
});
|
|
598
|
-
var endpointSiteSchema =
|
|
561
|
+
var endpointSiteSchema = import_zod.z.enum(["webhooks", "mailhooks", "both"]).describe(
|
|
599
562
|
"Which site's dashboard lists the endpoint: webhooks (webhooks.cc), mailhooks (mailhooks.cc) or both (the default when creating). Only the listing: its HTTP URL and email address work either way. Owner only to change."
|
|
600
563
|
);
|
|
601
564
|
var createEndpointShape = {
|
|
602
|
-
name:
|
|
603
|
-
ephemeral:
|
|
565
|
+
name: import_zod.z.string().optional().describe("Display name for the endpoint"),
|
|
566
|
+
ephemeral: import_zod.z.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
|
|
604
567
|
expiresIn: durationOrTimestampSchema.optional().describe('Auto-expire after this duration, for example "12h"'),
|
|
605
568
|
mockResponse: mockResponseSchema.optional().describe("Optional mock response to return when the endpoint receives a request"),
|
|
606
569
|
responseRules: responseRulesSchema.optional().describe(
|
|
607
570
|
"Conditional response rules. Each rule has conditions and a response. First matching rule wins."
|
|
608
571
|
),
|
|
609
|
-
notificationUrl:
|
|
572
|
+
notificationUrl: import_zod.z.string().url().optional().describe(
|
|
610
573
|
"URL to POST a JSON summary to after each captured request (e.g. Slack/Discord webhook)"
|
|
611
574
|
),
|
|
612
575
|
site: endpointSiteSchema.optional(),
|
|
613
|
-
signingProvider:
|
|
576
|
+
signingProvider: import_zod.z.string().optional().describe(
|
|
614
577
|
"Signing provider for automatic signature verification (e.g. stripe, github, shopify)"
|
|
615
578
|
),
|
|
616
|
-
signingSecret:
|
|
579
|
+
signingSecret: import_zod.z.string().optional().describe(
|
|
617
580
|
"Signing secret (encrypted server-side, never returned). Required when signingProvider is set."
|
|
618
581
|
),
|
|
619
|
-
signingHeader:
|
|
582
|
+
signingHeader: import_zod.z.string().optional().describe("Custom signature header name. Only used with generic-hmac provider.")
|
|
620
583
|
};
|
|
621
584
|
var listEndpointsShape = {
|
|
622
|
-
team:
|
|
585
|
+
team: import_zod.z.string().min(1).optional().describe(
|
|
623
586
|
"Team id or name (case-insensitive). Keeps endpoints shared with you from that team and endpoints you own that are shared with it."
|
|
624
587
|
)
|
|
625
588
|
};
|
|
626
|
-
var getEndpointShape = { slug:
|
|
627
|
-
var deleteEndpointShape = { slug:
|
|
589
|
+
var getEndpointShape = { slug: import_zod.z.string().describe("The endpoint slug") };
|
|
590
|
+
var deleteEndpointShape = { slug: import_zod.z.string().describe("The endpoint slug to delete") };
|
|
628
591
|
var listRequestsShape = {
|
|
629
|
-
endpointSlug:
|
|
630
|
-
limit:
|
|
631
|
-
since:
|
|
592
|
+
endpointSlug: import_zod.z.string().describe("The endpoint slug"),
|
|
593
|
+
limit: import_zod.z.number().int().min(1).max(100).default(25).describe("Max requests to return"),
|
|
594
|
+
since: import_zod.z.number().optional().describe("Only return requests after this timestamp in ms"),
|
|
632
595
|
kind: kindSchema
|
|
633
596
|
};
|
|
634
|
-
var getRequestShape = { requestId:
|
|
597
|
+
var getRequestShape = { requestId: import_zod.z.string().describe("The request ID") };
|
|
635
598
|
var waitForRequestShape = {
|
|
636
|
-
endpointSlug:
|
|
599
|
+
endpointSlug: import_zod.z.string().describe("The endpoint slug to monitor"),
|
|
637
600
|
timeout: durationOrTimestampSchema.default("30s").describe('How long to wait, for example "30s"'),
|
|
638
601
|
pollInterval: durationOrTimestampSchema.optional().describe('Interval between polls, for example "500ms" or "1s"')
|
|
639
602
|
};
|
|
640
603
|
var payloadSchemaShape = {
|
|
641
|
-
endpointSlug:
|
|
642
|
-
format:
|
|
604
|
+
endpointSlug: import_zod.z.string().describe("The endpoint slug"),
|
|
605
|
+
format: import_zod.z.enum(import_sdk.PAYLOAD_SCHEMA_FORMATS).default("typescript").describe(
|
|
643
606
|
'Output: "typescript" interfaces, "zod" schemas, "pydantic" models, or "json-schema"'
|
|
644
607
|
),
|
|
645
|
-
name:
|
|
646
|
-
event:
|
|
608
|
+
name: import_zod.z.string().max(100).optional().describe("Root type name (default: from the detected event, else WebhookPayload)"),
|
|
609
|
+
event: import_zod.z.string().optional().describe(
|
|
647
610
|
'Only requests whose detected provider event matches, e.g. "invoice.paid" or "push". One event per schema gives the tightest types.'
|
|
648
611
|
),
|
|
649
|
-
method:
|
|
650
|
-
path:
|
|
651
|
-
requestIds:
|
|
652
|
-
limit:
|
|
612
|
+
method: import_zod.z.string().optional().describe("Only requests with this HTTP method"),
|
|
613
|
+
path: import_zod.z.string().optional().describe("Only requests with this path"),
|
|
614
|
+
requestIds: import_zod.z.array(import_zod.z.string()).min(1).max(50).optional().describe("Use exactly these requests instead of the latest ones"),
|
|
615
|
+
limit: import_zod.z.number().int().min(1).max(100).default(20).describe("How many of the latest requests to read before filtering")
|
|
653
616
|
};
|
|
654
617
|
function eventName(event) {
|
|
655
618
|
const words = event.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
@@ -721,6 +684,14 @@ ${JSON.stringify(result, null, 2)}`;
|
|
|
721
684
|
if (typeof result.url === "string") {
|
|
722
685
|
return `Approved. The share link is ${result.url} (anyone with it can read the copy until ${String(result.expiresAt)}).`;
|
|
723
686
|
}
|
|
687
|
+
if (typeof result.invitedEmail === "string") {
|
|
688
|
+
const team = typeof result.teamName === "string" ? ` to "${result.teamName}"` : "";
|
|
689
|
+
const warning = typeof result.warning === "string" ? ` ${result.warning} Tell them to sign up with that address and accept from their Teams page.` : " They get an email; accepting claims one of the team's seats.";
|
|
690
|
+
return `Approved. Invited ${result.invitedEmail}${team} (invite ${String(result.id)}).${warning}`;
|
|
691
|
+
}
|
|
692
|
+
if (typeof result.deleted === "string" && typeof result.teamId === "string") {
|
|
693
|
+
return `Approved. The team ${result.deleted ? `"${result.deleted}" ` : ""}was deleted; its subscription has ended.`;
|
|
694
|
+
}
|
|
724
695
|
if (typeof result.removed === "string") {
|
|
725
696
|
return `Approved. The domain ${result.removed} was removed; mail to it is refused now.`;
|
|
726
697
|
}
|
|
@@ -795,6 +766,75 @@ function fitArrayField(value, key) {
|
|
|
795
766
|
}
|
|
796
767
|
return cut(low);
|
|
797
768
|
}
|
|
769
|
+
function isoTime(ms) {
|
|
770
|
+
if (ms === null || !Number.isFinite(ms)) return null;
|
|
771
|
+
const date = new Date(ms);
|
|
772
|
+
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
773
|
+
}
|
|
774
|
+
function searchStoppedNote(searchedUntil, order, hasCursor) {
|
|
775
|
+
const [direction, rest] = order === "asc" ? ["forward", "newer"] : ["back", "older"];
|
|
776
|
+
const reached = searchedUntil ? `Searched ${direction} to ${searchedUntil}; ${rest} requests were not searched yet.` : `The search stopped before it read everything; ${rest} requests were not searched yet.`;
|
|
777
|
+
return hasCursor ? `${reached} Call search_requests again with cursor to continue.` : reached;
|
|
778
|
+
}
|
|
779
|
+
var FULL_REQUEST_HINT = "The full request is available from the API (GET /api/requests/{id}), the SDKs (requests.get(id)) or the CLI (whk requests get <id>).";
|
|
780
|
+
var SEARCH_SHORTENED_NOTE = `Some requests were shortened to fit the output (bodyTruncated with bodySize, or shortened: true for a summary); get_request with the id returns as much of one request as fits. ${FULL_REQUEST_HINT}`;
|
|
781
|
+
var REQUEST_SHORTENED_NOTE = `This request was cut to fit the output: its body (bodyTruncated, with bodySize), or everything but a summary (shortened: true). ${FULL_REQUEST_HINT}`;
|
|
782
|
+
function clip(value, max) {
|
|
783
|
+
return typeof value === "string" ? sliceText(value, max) : void 0;
|
|
784
|
+
}
|
|
785
|
+
function listedSize(request) {
|
|
786
|
+
return jsonSize([[request]]) - 8;
|
|
787
|
+
}
|
|
788
|
+
function fitSearchRequests(requests, room) {
|
|
789
|
+
const share = Math.floor(room / Math.max(1, requests.length));
|
|
790
|
+
const fitted = requests.map(
|
|
791
|
+
(request) => shortenRequest(request, share, listedSize)
|
|
792
|
+
);
|
|
793
|
+
if (fitted.every((request) => listedSize(request) <= share)) return fitted;
|
|
794
|
+
return requests.map((request) => {
|
|
795
|
+
const { id, receivedAt } = request;
|
|
796
|
+
return { id, receivedAt, shortened: true };
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
function shortenRequest(request, share, sizeOf) {
|
|
800
|
+
if (sizeOf(request) <= share) return request;
|
|
801
|
+
if (typeof request.body === "string") {
|
|
802
|
+
const body = request.body;
|
|
803
|
+
let keep = body.length - (sizeOf(request) - share) - 96;
|
|
804
|
+
while (keep > 0) {
|
|
805
|
+
const cut = {
|
|
806
|
+
...request,
|
|
807
|
+
body: sliceText(body, keep),
|
|
808
|
+
bodyTruncated: true,
|
|
809
|
+
bodySize: body.length
|
|
810
|
+
};
|
|
811
|
+
if (sizeOf(cut) <= share) return cut;
|
|
812
|
+
keep = Math.floor(keep / 2);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
const email = request.email;
|
|
816
|
+
const summary = {
|
|
817
|
+
id: request.id,
|
|
818
|
+
slug: clip(request.slug, 64),
|
|
819
|
+
method: clip(request.method, 16),
|
|
820
|
+
path: clip(request.path, 200),
|
|
821
|
+
contentType: clip(request.contentType, 100),
|
|
822
|
+
size: request.size,
|
|
823
|
+
receivedAt: request.receivedAt,
|
|
824
|
+
kind: request.kind,
|
|
825
|
+
...email ? { subject: clip(email.subject, 200) } : {},
|
|
826
|
+
shortened: true,
|
|
827
|
+
...typeof request.note === "string" ? { note: request.note } : {}
|
|
828
|
+
};
|
|
829
|
+
return summary;
|
|
830
|
+
}
|
|
831
|
+
function requestContent(request) {
|
|
832
|
+
const compact = compactRequest(request);
|
|
833
|
+
if (jsonSize(compact) <= MAX_BODY_SIZE) return jsonContent(compact);
|
|
834
|
+
return jsonContent(
|
|
835
|
+
shortenRequest({ ...compact, note: REQUEST_SHORTENED_NOTE }, MAX_BODY_SIZE, jsonSize)
|
|
836
|
+
);
|
|
837
|
+
}
|
|
798
838
|
function jsonContent(value) {
|
|
799
839
|
return textContent(serializeJson(value));
|
|
800
840
|
}
|
|
@@ -839,6 +879,13 @@ function serializeError(error) {
|
|
|
839
879
|
} else if (error instanceof import_sdk.WebhooksCCError) {
|
|
840
880
|
payload.code = error.statusCode >= 500 ? "server_error" : "validation_error";
|
|
841
881
|
}
|
|
882
|
+
if (error instanceof import_sdk.WebhooksCCError && payload.hint === null) {
|
|
883
|
+
if (error.code === "invalid_cursor") {
|
|
884
|
+
payload.hint = "The cursor is not one this search returned, or the order changed since. Pass a cursor exactly as a result gave it, or search again without one.";
|
|
885
|
+
} else if (error.code === "search_busy") {
|
|
886
|
+
payload.hint = "The account's other searches are still running. Wait retryAfter seconds, then call again with the same inputs.";
|
|
887
|
+
}
|
|
888
|
+
}
|
|
842
889
|
return JSON.stringify(payload, null, 2);
|
|
843
890
|
}
|
|
844
891
|
function withErrorHandling(handler) {
|
|
@@ -1063,11 +1110,18 @@ function durationMs(value) {
|
|
|
1063
1110
|
return null;
|
|
1064
1111
|
}
|
|
1065
1112
|
}
|
|
1066
|
-
function toolTarget(target,
|
|
1113
|
+
function toolTarget(target, options, handles) {
|
|
1114
|
+
const remote = options.remote === true;
|
|
1067
1115
|
return {
|
|
1068
1116
|
tool(name, description, shape, handler) {
|
|
1069
1117
|
if (remote && LOCAL_ONLY_TOOLS.has(name)) return { remove() {
|
|
1070
1118
|
} };
|
|
1119
|
+
const entry = TOOL_CATALOG[name];
|
|
1120
|
+
if (!entry) throw new Error(`${name} has no entry in catalog.ts`);
|
|
1121
|
+
if (options.groups && !options.groups.has(entry.group)) return { remove() {
|
|
1122
|
+
} };
|
|
1123
|
+
if (options.readOnly && entry.readOnly !== true) return { remove() {
|
|
1124
|
+
} };
|
|
1071
1125
|
const wrapped = remote ? async (args2) => {
|
|
1072
1126
|
const input = args2;
|
|
1073
1127
|
if (TARGET_URL_TOOLS.has(name) && input.targetUrl !== void 0) {
|
|
@@ -1101,7 +1155,7 @@ function toolTarget(target, remote, handles) {
|
|
|
1101
1155
|
}
|
|
1102
1156
|
return handler(bounded);
|
|
1103
1157
|
} : handler;
|
|
1104
|
-
const handle = target.tool(name, description, shape, wrapped);
|
|
1158
|
+
const handle = target.tool(name, description, shape, wrapped, toolAnnotations(name));
|
|
1105
1159
|
handles.push(handle);
|
|
1106
1160
|
return handle;
|
|
1107
1161
|
}
|
|
@@ -1177,7 +1231,7 @@ function withShareHints(list, remote = false) {
|
|
|
1177
1231
|
function registerTools(target, client, options = {}) {
|
|
1178
1232
|
const handles = [];
|
|
1179
1233
|
const remote = options.remote === true;
|
|
1180
|
-
const server = toolTarget(target,
|
|
1234
|
+
const server = toolTarget(target, options, handles);
|
|
1181
1235
|
const shareWith = remote ? REMOTE_SHARE : "share_port";
|
|
1182
1236
|
server.tool(
|
|
1183
1237
|
"create_endpoint",
|
|
@@ -1260,7 +1314,7 @@ function registerTools(target, client, options = {}) {
|
|
|
1260
1314
|
server.tool(
|
|
1261
1315
|
"list_team_members",
|
|
1262
1316
|
"List a team's members and pending invites. Any member of the team may call this.",
|
|
1263
|
-
{ teamId:
|
|
1317
|
+
{ teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)") },
|
|
1264
1318
|
withErrorHandling(async ({ teamId }) => {
|
|
1265
1319
|
const result = await client.teams.members(teamId);
|
|
1266
1320
|
return jsonContent(result);
|
|
@@ -1270,8 +1324,8 @@ function registerTools(target, client, options = {}) {
|
|
|
1270
1324
|
"share_endpoint",
|
|
1271
1325
|
"Share an endpoint you own with a team so its members can inspect, stream, and edit it. The team needs an active subscription; the endpoint's requests then bill the team's pooled quota.",
|
|
1272
1326
|
{
|
|
1273
|
-
slug:
|
|
1274
|
-
teamId:
|
|
1327
|
+
slug: import_zod.z.string().describe("Slug of an endpoint you own"),
|
|
1328
|
+
teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)")
|
|
1275
1329
|
},
|
|
1276
1330
|
withErrorHandling(async ({ slug, teamId }) => {
|
|
1277
1331
|
await client.teams.share(teamId, slug);
|
|
@@ -1282,14 +1336,132 @@ function registerTools(target, client, options = {}) {
|
|
|
1282
1336
|
"unshare_endpoint",
|
|
1283
1337
|
"Stop sharing an endpoint you own with a team. Works even when the team is suspended.",
|
|
1284
1338
|
{
|
|
1285
|
-
slug:
|
|
1286
|
-
teamId:
|
|
1339
|
+
slug: import_zod.z.string().describe("Slug of an endpoint you own"),
|
|
1340
|
+
teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)")
|
|
1287
1341
|
},
|
|
1288
1342
|
withErrorHandling(async ({ slug, teamId }) => {
|
|
1289
1343
|
await client.teams.unshare(teamId, slug);
|
|
1290
1344
|
return jsonContent({ shared: false, slug, teamId });
|
|
1291
1345
|
})
|
|
1292
1346
|
);
|
|
1347
|
+
server.tool(
|
|
1348
|
+
"create_team",
|
|
1349
|
+
"Create a team you own, to share endpoints with its members under a pooled quota. It does nothing until it has a subscription, which the person starts in the browser (Account > Teams); until then it is suspended.",
|
|
1350
|
+
{ name: import_zod.z.string().trim().min(1).max(100).describe("The team's name") },
|
|
1351
|
+
withErrorHandling(async ({ name }) => jsonContent(await client.teams.create(name)))
|
|
1352
|
+
);
|
|
1353
|
+
server.tool(
|
|
1354
|
+
"rename_team",
|
|
1355
|
+
"Rename a team you own.",
|
|
1356
|
+
{
|
|
1357
|
+
teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)"),
|
|
1358
|
+
name: import_zod.z.string().trim().min(1).max(100).describe("The new name")
|
|
1359
|
+
},
|
|
1360
|
+
withErrorHandling(async ({ teamId, name }) => {
|
|
1361
|
+
await client.teams.rename(teamId, name);
|
|
1362
|
+
return jsonContent({ renamed: true, teamId, name });
|
|
1363
|
+
})
|
|
1364
|
+
);
|
|
1365
|
+
server.tool(
|
|
1366
|
+
"delete_team",
|
|
1367
|
+
"Delete a team you own: its subscription ends at once, every member loses the endpoints shared with it, and it cannot be undone. Only when the person asks for it. The team's owner must approve it: this returns a link and a code to show them, then call wait_for_approval.",
|
|
1368
|
+
{ teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)") },
|
|
1369
|
+
withErrorHandling(async ({ teamId }) => {
|
|
1370
|
+
const pending = await client.teams.delete(teamId);
|
|
1371
|
+
if (!pending.id) return textContent(`Deleted the team ${teamId}.`);
|
|
1372
|
+
return textContent(approvalAskText(pending));
|
|
1373
|
+
})
|
|
1374
|
+
);
|
|
1375
|
+
server.tool(
|
|
1376
|
+
"remove_team_member",
|
|
1377
|
+
"Remove a member from a team you own: the endpoints they shared with the team stop being shared, and their seat is free for someone else. Only when the person asks for it.",
|
|
1378
|
+
{
|
|
1379
|
+
teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)"),
|
|
1380
|
+
userId: import_zod.z.string().min(1).describe("The member's userId (from list_team_members)")
|
|
1381
|
+
},
|
|
1382
|
+
withErrorHandling(async ({ teamId, userId }) => {
|
|
1383
|
+
await client.teams.removeMember(teamId, userId);
|
|
1384
|
+
return jsonContent({ removed: true, teamId, userId });
|
|
1385
|
+
})
|
|
1386
|
+
);
|
|
1387
|
+
server.tool(
|
|
1388
|
+
"leave_team",
|
|
1389
|
+
"Leave a team you belong to: the endpoints you shared with it stop being shared, and you lose access to the ones shared with you through it. An owner cannot leave; they delete the team instead (delete_team). Only when the person asks for it.",
|
|
1390
|
+
{ teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)") },
|
|
1391
|
+
withErrorHandling(async ({ teamId }) => {
|
|
1392
|
+
await client.teams.leave(teamId);
|
|
1393
|
+
return jsonContent({ left: true, teamId });
|
|
1394
|
+
})
|
|
1395
|
+
);
|
|
1396
|
+
server.tool(
|
|
1397
|
+
"invite_to_team",
|
|
1398
|
+
"Invite an email address to a team you own. It emails that address, so the team's owner must approve it: this returns a link and a code to show them, then call wait_for_approval, which returns the invite. Accepting the invite claims one of the team's paid seats. Only when the person asks for it.",
|
|
1399
|
+
{
|
|
1400
|
+
teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)"),
|
|
1401
|
+
email: import_zod.z.string().trim().email().describe("The address to invite")
|
|
1402
|
+
},
|
|
1403
|
+
withErrorHandling(async ({ teamId, email }) => {
|
|
1404
|
+
const pending = await client.teams.invite(teamId, email);
|
|
1405
|
+
if (!pending.id) return textContent(approvalResultText({ ...await pending.wait() }));
|
|
1406
|
+
return textContent(approvalAskText(pending));
|
|
1407
|
+
})
|
|
1408
|
+
);
|
|
1409
|
+
server.tool(
|
|
1410
|
+
"list_invites",
|
|
1411
|
+
"List the team invites waiting for you to answer: team, who invited you, when, and the invite id for answer_invite.",
|
|
1412
|
+
{},
|
|
1413
|
+
withErrorHandling(async () => jsonContent(await client.teams.invites.list()))
|
|
1414
|
+
);
|
|
1415
|
+
server.tool(
|
|
1416
|
+
"answer_invite",
|
|
1417
|
+
"Accept or decline a team invite waiting for you (from list_invites). Accepting claims one of the team's paid seats and you join the team, seeing the endpoints shared with it; it fails when the team is full or suspended. Only when the person says which.",
|
|
1418
|
+
{
|
|
1419
|
+
inviteId: import_zod.z.string().min(1).describe("The invite's id, from list_invites"),
|
|
1420
|
+
answer: import_zod.z.enum(["accept", "decline"]).describe("accept joins the team; decline turns it down")
|
|
1421
|
+
},
|
|
1422
|
+
withErrorHandling(async ({ inviteId, answer }) => {
|
|
1423
|
+
if (answer === "accept") {
|
|
1424
|
+
await client.teams.invites.accept(inviteId);
|
|
1425
|
+
return jsonContent({ accepted: true, inviteId });
|
|
1426
|
+
}
|
|
1427
|
+
await client.teams.invites.decline(inviteId);
|
|
1428
|
+
return jsonContent({ declined: true, inviteId });
|
|
1429
|
+
})
|
|
1430
|
+
);
|
|
1431
|
+
server.tool(
|
|
1432
|
+
"withdraw_invite",
|
|
1433
|
+
"Withdraw an invite to a team you own before it is answered (pending invites and their ids are in list_team_members). The invite link stops working.",
|
|
1434
|
+
{
|
|
1435
|
+
teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)"),
|
|
1436
|
+
inviteId: import_zod.z.string().min(1).describe("The invite's id (from list_team_members' pendingInvites)")
|
|
1437
|
+
},
|
|
1438
|
+
withErrorHandling(async ({ teamId, inviteId }) => {
|
|
1439
|
+
await client.teams.withdrawInvite(teamId, inviteId);
|
|
1440
|
+
return jsonContent({ withdrawn: true, teamId, inviteId });
|
|
1441
|
+
})
|
|
1442
|
+
);
|
|
1443
|
+
server.tool(
|
|
1444
|
+
"quote_team_change",
|
|
1445
|
+
"What changing a team's seat count or plan would cost, read only (owner only): what is due now, any credit, and the price from the renewal. Give seats, or plan (with interval). The change itself is made by the person in the browser.",
|
|
1446
|
+
{
|
|
1447
|
+
teamId: import_zod.z.string().min(1).describe("Team id (from list_teams)"),
|
|
1448
|
+
seats: import_zod.z.number().int().min(1).optional().describe("Price this seat count"),
|
|
1449
|
+
plan: import_zod.z.enum(["team", "team_business"]).optional().describe("Price a move to this plan: team (Teams) or team_business (Team Business)"),
|
|
1450
|
+
interval: import_zod.z.enum(["month", "year"]).optional().describe("Billing interval for plan (default month; Teams is monthly only)")
|
|
1451
|
+
},
|
|
1452
|
+
withErrorHandling(async ({ teamId, seats, plan, interval }) => {
|
|
1453
|
+
if (seats === void 0 === (plan === void 0)) {
|
|
1454
|
+
throw new Error("Give seats or plan, not both.");
|
|
1455
|
+
}
|
|
1456
|
+
if (seats !== void 0) {
|
|
1457
|
+
if (interval !== void 0) throw new Error("interval goes with plan, not seats.");
|
|
1458
|
+
return jsonContent(await client.billing.quoteTeamSeats(teamId, seats));
|
|
1459
|
+
}
|
|
1460
|
+
return jsonContent(
|
|
1461
|
+
await client.billing.quoteTeamPlan(teamId, plan, interval)
|
|
1462
|
+
);
|
|
1463
|
+
})
|
|
1464
|
+
);
|
|
1293
1465
|
server.tool(
|
|
1294
1466
|
"get_endpoint",
|
|
1295
1467
|
"Get details for a specific webhook endpoint by slug.",
|
|
@@ -1303,8 +1475,8 @@ function registerTools(target, client, options = {}) {
|
|
|
1303
1475
|
"favorite_endpoint",
|
|
1304
1476
|
"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.",
|
|
1305
1477
|
{
|
|
1306
|
-
slug:
|
|
1307
|
-
favorite:
|
|
1478
|
+
slug: import_zod.z.string().describe("The endpoint slug"),
|
|
1479
|
+
favorite: import_zod.z.boolean().optional().describe("true marks it as a favourite (the default), false unmarks it")
|
|
1308
1480
|
},
|
|
1309
1481
|
withErrorHandling(async ({ slug, favorite }) => {
|
|
1310
1482
|
const result = favorite === false ? await client.endpoints.unfavorite(slug) : await client.endpoints.favorite(slug);
|
|
@@ -1315,24 +1487,24 @@ function registerTools(target, client, options = {}) {
|
|
|
1315
1487
|
"update_endpoint",
|
|
1316
1488
|
"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.",
|
|
1317
1489
|
{
|
|
1318
|
-
slug:
|
|
1319
|
-
name:
|
|
1490
|
+
slug: import_zod.z.string().describe("The endpoint slug to update"),
|
|
1491
|
+
name: import_zod.z.string().optional().describe("New display name"),
|
|
1320
1492
|
mockResponse: mockResponseSchema.nullable().optional().describe("Default mock response (used when no rule matches), or null to clear"),
|
|
1321
1493
|
responseRules: responseRulesSchema.nullable().optional().describe("Conditional response rules (first match wins), or null to clear"),
|
|
1322
|
-
notificationUrl:
|
|
1323
|
-
signingProvider:
|
|
1494
|
+
notificationUrl: import_zod.z.string().url().nullable().optional().describe("Notification webhook URL, or null to clear it"),
|
|
1495
|
+
signingProvider: import_zod.z.string().nullable().optional().describe(
|
|
1324
1496
|
"Signing provider for automatic verification (e.g. stripe, github), or null to disable"
|
|
1325
1497
|
),
|
|
1326
|
-
signingSecret:
|
|
1327
|
-
signingHeader:
|
|
1498
|
+
signingSecret: import_zod.z.string().nullable().optional().describe("New signing secret (encrypted server-side). Pass null to clear."),
|
|
1499
|
+
signingHeader: import_zod.z.string().nullable().optional().describe(
|
|
1328
1500
|
"Custom signature header name (only for generic-hmac provider), or null to clear"
|
|
1329
1501
|
),
|
|
1330
|
-
showEmailExtracts:
|
|
1502
|
+
showEmailExtracts: import_zod.z.boolean().optional().describe("Pick codes and links out of emails (dashboard and forwarded JSON). Owner only."),
|
|
1331
1503
|
site: endpointSiteSchema.optional(),
|
|
1332
|
-
emailFields:
|
|
1504
|
+
emailFields: import_zod.z.array(emailFieldSchema).max(20).nullable().optional().describe(
|
|
1333
1505
|
"Named fields picked out of every email: shown with it, `fields` in its JSON, read by forwarding templates and filters (email_field). Replaces the list; null or [] removes them. Owner only."
|
|
1334
1506
|
),
|
|
1335
|
-
layouts:
|
|
1507
|
+
layouts: import_zod.z.array(layoutSchema).max(50).nullable().optional().describe(
|
|
1336
1508
|
"How the endpoint reads each sender's emails: the first layout whose match holds applies its fields, line items and attachment parsing; the result is `parsed` in the email's JSON, and deliveries wait for it. The plan uses the first 2 (Business 50). Replaces the list; null or [] removes them. Try them with preview_layouts first. Owner only."
|
|
1337
1509
|
)
|
|
1338
1510
|
},
|
|
@@ -1372,9 +1544,9 @@ function registerTools(target, client, options = {}) {
|
|
|
1372
1544
|
"preview_layouts",
|
|
1373
1545
|
"Dry run of an endpoint's layouts on a captured email: which layout matches, the fields and line items it finds, and what earlier parses of its attachments held. Nothing is saved and no pages are used. Pass layouts to try them before saving them with update_endpoint.",
|
|
1374
1546
|
{
|
|
1375
|
-
slug:
|
|
1376
|
-
requestId:
|
|
1377
|
-
layouts:
|
|
1547
|
+
slug: import_zod.z.string().describe("The endpoint slug"),
|
|
1548
|
+
requestId: import_zod.z.string().optional().describe("A captured email on the endpoint (default: the newest)"),
|
|
1549
|
+
layouts: import_zod.z.array(layoutSchema).max(50).optional().describe("Unsaved layouts to try")
|
|
1378
1550
|
},
|
|
1379
1551
|
withErrorHandling(async ({ slug, requestId, layouts }) => {
|
|
1380
1552
|
const preview = await client.endpoints.previewLayouts(slug, { requestId, layouts });
|
|
@@ -1383,12 +1555,41 @@ function registerTools(target, client, options = {}) {
|
|
|
1383
1555
|
return jsonContent(holder);
|
|
1384
1556
|
})
|
|
1385
1557
|
);
|
|
1558
|
+
server.tool(
|
|
1559
|
+
"list_layout_templates",
|
|
1560
|
+
"List the ready-made email layouts (order emails from Shopify, WooCommerce, Etsy and more, payments, bookings, attachments): each with its id, sender, kind, description, the fields it reads and whether it reads line items. Add one with add_layout_template.",
|
|
1561
|
+
{ slug: import_zod.z.string().describe("The endpoint slug") },
|
|
1562
|
+
withErrorHandling(async ({ slug }) => jsonContent(await client.endpoints.layoutTemplates(slug)))
|
|
1563
|
+
);
|
|
1564
|
+
server.tool(
|
|
1565
|
+
"add_layout_template",
|
|
1566
|
+
"Add a ready-made layout (an id from list_layout_templates) to the end of an endpoint's layouts. Emails it matches are parsed from the next one on; check it on a captured email with preview_layouts. Owner only.",
|
|
1567
|
+
{
|
|
1568
|
+
slug: import_zod.z.string().describe("The endpoint slug"),
|
|
1569
|
+
template: import_zod.z.string().min(1).describe("The template's id, from list_layout_templates")
|
|
1570
|
+
},
|
|
1571
|
+
withErrorHandling(
|
|
1572
|
+
async ({ slug, template }) => jsonContent(await client.endpoints.addLayoutTemplate(slug, template))
|
|
1573
|
+
)
|
|
1574
|
+
);
|
|
1575
|
+
server.tool(
|
|
1576
|
+
"preview_email_fields",
|
|
1577
|
+
"Dry run of an endpoint's named email fields on a captured email: each field's value (null where it finds nothing), and problems with fields you pass. Nothing is saved. Pass emailFields to try fields before saving them with update_endpoint. Owner only.",
|
|
1578
|
+
{
|
|
1579
|
+
slug: import_zod.z.string().describe("The endpoint slug"),
|
|
1580
|
+
requestId: import_zod.z.string().optional().describe("A captured email on the endpoint (default: the newest)"),
|
|
1581
|
+
emailFields: import_zod.z.array(emailFieldSchema).max(20).optional().describe("Unsaved fields to try instead of the saved ones")
|
|
1582
|
+
},
|
|
1583
|
+
withErrorHandling(
|
|
1584
|
+
async ({ slug, requestId, emailFields }) => jsonContent(await client.endpoints.previewEmailFields(slug, { requestId, emailFields }))
|
|
1585
|
+
)
|
|
1586
|
+
);
|
|
1386
1587
|
server.tool(
|
|
1387
1588
|
"preview_response",
|
|
1388
1589
|
"Dry run of an endpoint's responses: which response rule answers a captured request, and the response with its {{ placeholders }} filled in, exactly as the receiver would send it. Nothing is sent. Pass mockResponse or responseRules to try settings before saving them with update_endpoint.",
|
|
1389
1590
|
{
|
|
1390
|
-
slug:
|
|
1391
|
-
requestId:
|
|
1591
|
+
slug: import_zod.z.string().describe("The endpoint slug"),
|
|
1592
|
+
requestId: import_zod.z.string().optional().describe(
|
|
1392
1593
|
"A captured request on the endpoint (default: the newest HTTP request, or a sample)"
|
|
1393
1594
|
),
|
|
1394
1595
|
mockResponse: mockResponseSchema.nullable().optional().describe("Try this default response instead of the saved one (null: none)"),
|
|
@@ -1416,9 +1617,9 @@ function registerTools(target, client, options = {}) {
|
|
|
1416
1617
|
"create_endpoints",
|
|
1417
1618
|
"Create multiple webhook endpoints in one call.",
|
|
1418
1619
|
{
|
|
1419
|
-
count:
|
|
1420
|
-
namePrefix:
|
|
1421
|
-
ephemeral:
|
|
1620
|
+
count: import_zod.z.number().int().min(1).max(20).describe("Number of endpoints to create"),
|
|
1621
|
+
namePrefix: import_zod.z.string().optional().describe("Optional prefix for endpoint names"),
|
|
1622
|
+
ephemeral: import_zod.z.boolean().optional().describe("Create temporary endpoints that auto-expire"),
|
|
1422
1623
|
expiresIn: durationOrTimestampSchema.optional().describe('Auto-expire after this duration, for example "12h"')
|
|
1423
1624
|
},
|
|
1424
1625
|
withErrorHandling(async ({ count, namePrefix, ephemeral, expiresIn }) => {
|
|
@@ -1439,7 +1640,7 @@ function registerTools(target, client, options = {}) {
|
|
|
1439
1640
|
"delete_endpoints",
|
|
1440
1641
|
"Delete multiple webhook endpoints in one call.",
|
|
1441
1642
|
{
|
|
1442
|
-
slugs:
|
|
1643
|
+
slugs: import_zod.z.array(import_zod.z.string()).min(1).max(100).describe("Endpoint slugs to delete")
|
|
1443
1644
|
},
|
|
1444
1645
|
withErrorHandling(async ({ slugs }) => {
|
|
1445
1646
|
const settled = await Promise.allSettled(
|
|
@@ -1467,14 +1668,14 @@ function registerTools(target, client, options = {}) {
|
|
|
1467
1668
|
"send_webhook",
|
|
1468
1669
|
"Send a test webhook to a hosted endpoint. Supports provider templates and signing.",
|
|
1469
1670
|
{
|
|
1470
|
-
slug:
|
|
1671
|
+
slug: import_zod.z.string().describe("The endpoint slug to send to"),
|
|
1471
1672
|
method: methodSchema,
|
|
1472
|
-
headers:
|
|
1473
|
-
body:
|
|
1474
|
-
provider:
|
|
1475
|
-
template:
|
|
1476
|
-
event:
|
|
1477
|
-
secret:
|
|
1673
|
+
headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional().describe("HTTP headers to include"),
|
|
1674
|
+
body: import_zod.z.unknown().optional().describe("Request body"),
|
|
1675
|
+
provider: import_zod.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to send with signed headers"),
|
|
1676
|
+
template: import_zod.z.string().optional().describe("Provider-specific template preset"),
|
|
1677
|
+
event: import_zod.z.string().optional().describe("Provider event or topic name"),
|
|
1678
|
+
secret: import_zod.z.string().optional().describe(
|
|
1478
1679
|
"Signing secret. Required for signed provider templates; omit for secretless providers (sendgrid, discord, plaid)."
|
|
1479
1680
|
)
|
|
1480
1681
|
},
|
|
@@ -1518,56 +1719,95 @@ function registerTools(target, client, options = {}) {
|
|
|
1518
1719
|
);
|
|
1519
1720
|
server.tool(
|
|
1520
1721
|
"search_requests",
|
|
1521
|
-
"Search captured
|
|
1722
|
+
"Search captured requests and emails across endpoints: a substring match on path, headers, body and decoded email fields. One call reads back through history for about 2 seconds and returns { requests, complete, searchedUntil, cursor }. When complete is false, older requests were not searched yet: call again with the cursor and the same filters to continue.",
|
|
1522
1723
|
{
|
|
1523
|
-
slug:
|
|
1524
|
-
method:
|
|
1724
|
+
slug: import_zod.z.string().optional().describe("Filter to a specific endpoint slug"),
|
|
1725
|
+
method: import_zod.z.string().optional().describe("Filter by HTTP method"),
|
|
1525
1726
|
kind: kindSchema,
|
|
1526
|
-
q:
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
from,
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1727
|
+
q: import_zod.z.string().optional().describe(
|
|
1728
|
+
"Substring to find in the path, headers, body and decoded email fields (up to 1,000 characters)"
|
|
1729
|
+
),
|
|
1730
|
+
from: durationOrTimestampSchema.optional().describe(
|
|
1731
|
+
'Start time as a timestamp (Unix ms) or a duration back from now like "1h" or "7d". A duration is counted from each call, so when you continue with a cursor pass a timestamp to keep the same range.'
|
|
1732
|
+
),
|
|
1733
|
+
to: durationOrTimestampSchema.optional().describe(
|
|
1734
|
+
'End time as a timestamp (Unix ms) or a duration back from now like "1h" or "7d". A duration is counted from each call, so when you continue with a cursor pass a timestamp to keep the same range.'
|
|
1735
|
+
),
|
|
1736
|
+
limit: import_zod.z.number().int().min(1).max(200).default(50).describe("Max results to return"),
|
|
1737
|
+
offset: import_zod.z.number().int().min(0).max(1e4).default(0).describe("Matches to skip (first call only; a cursor carries what is left of it)"),
|
|
1738
|
+
order: import_zod.z.enum(["asc", "desc"]).default("desc").describe("Sort order by received time"),
|
|
1739
|
+
cursor: import_zod.z.string().optional().describe(
|
|
1740
|
+
"The cursor from a previous search_requests result, to continue that search. Keep the other inputs the same."
|
|
1741
|
+
)
|
|
1742
|
+
},
|
|
1743
|
+
withErrorHandling(async ({ slug, method, kind, q, from, to, limit, offset, order, cursor }) => {
|
|
1744
|
+
const page = await client.requests.searchPage(
|
|
1745
|
+
{ slug, method, kind, q, from, to, limit, offset, order },
|
|
1746
|
+
{ cursor }
|
|
1747
|
+
);
|
|
1748
|
+
const searchedUntil = isoTime(page.searchedUntil);
|
|
1749
|
+
const envelope = (requests2, shortened) => {
|
|
1750
|
+
const notes = [
|
|
1751
|
+
...page.complete ? [] : [searchStoppedNote(searchedUntil, order, Boolean(page.cursor))],
|
|
1752
|
+
...shortened ? [SEARCH_SHORTENED_NOTE] : []
|
|
1753
|
+
];
|
|
1754
|
+
return {
|
|
1755
|
+
requests: requests2,
|
|
1756
|
+
complete: page.complete,
|
|
1757
|
+
searchedUntil,
|
|
1758
|
+
cursor: page.cursor,
|
|
1759
|
+
...notes.length > 0 ? { note: notes.join(" ") } : {}
|
|
1760
|
+
};
|
|
1761
|
+
};
|
|
1762
|
+
const requests = page.requests.map(compactRequest);
|
|
1763
|
+
if (jsonSize(envelope(requests, false)) <= MAX_BODY_SIZE) {
|
|
1764
|
+
return jsonContent(envelope(requests, false));
|
|
1765
|
+
}
|
|
1766
|
+
const room = MAX_BODY_SIZE - jsonSize(envelope([], true)) - 8;
|
|
1767
|
+
return jsonContent(envelope(fitSearchRequests(requests, room), true));
|
|
1546
1768
|
})
|
|
1547
1769
|
);
|
|
1548
1770
|
server.tool(
|
|
1549
1771
|
"count_requests",
|
|
1550
|
-
"Count captured
|
|
1772
|
+
"Count captured requests and emails that match the given filters (q is a substring match on path, headers, body and decoded email fields). Returns { count, exact, searchedUntil, cursor }. With q or method a call counts back through history for about 2 seconds: when exact is false, count covers history back to searchedUntil only, and calling again with the cursor counts the next stretch (add the counts up).",
|
|
1551
1773
|
{
|
|
1552
|
-
slug:
|
|
1553
|
-
method:
|
|
1774
|
+
slug: import_zod.z.string().optional().describe("Filter to a specific endpoint slug"),
|
|
1775
|
+
method: import_zod.z.string().optional().describe("Filter by HTTP method"),
|
|
1554
1776
|
kind: kindSchema,
|
|
1555
|
-
q:
|
|
1556
|
-
|
|
1557
|
-
|
|
1777
|
+
q: import_zod.z.string().optional().describe(
|
|
1778
|
+
"Substring to find in the path, headers, body and decoded email fields (up to 1,000 characters)"
|
|
1779
|
+
),
|
|
1780
|
+
from: durationOrTimestampSchema.optional().describe(
|
|
1781
|
+
'Start time as a timestamp (Unix ms) or a duration back from now like "1h" or "7d". A duration is counted from each call, so when you continue with a cursor pass a timestamp to keep the same range.'
|
|
1782
|
+
),
|
|
1783
|
+
to: durationOrTimestampSchema.optional().describe(
|
|
1784
|
+
'End time as a timestamp (Unix ms) or a duration back from now like "1h" or "7d". A duration is counted from each call, so when you continue with a cursor pass a timestamp to keep the same range.'
|
|
1785
|
+
),
|
|
1786
|
+
cursor: import_zod.z.string().optional().describe(
|
|
1787
|
+
"The cursor from a previous count_requests result, to count the next stretch. Keep the other inputs the same."
|
|
1788
|
+
)
|
|
1558
1789
|
},
|
|
1559
|
-
withErrorHandling(async ({ slug, method, kind, q, from, to }) => {
|
|
1560
|
-
const
|
|
1561
|
-
|
|
1790
|
+
withErrorHandling(async ({ slug, method, kind, q, from, to, cursor }) => {
|
|
1791
|
+
const page = await client.requests.countPage({ slug, method, kind, q, from, to }, { cursor });
|
|
1792
|
+
const searchedUntil = isoTime(page.searchedUntil);
|
|
1793
|
+
return jsonContent({
|
|
1794
|
+
count: page.count,
|
|
1795
|
+
exact: page.exact,
|
|
1796
|
+
searchedUntil,
|
|
1797
|
+
cursor: page.cursor,
|
|
1798
|
+
...page.exact ? {} : {
|
|
1799
|
+
note: `At least ${page.count}, counted ${searchedUntil ? `back to ${searchedUntil}` : "over part of the history"}; ${page.cursor ? "call count_requests again with cursor to count further (the counts add up)." : "there is no cursor to count further with."}`
|
|
1800
|
+
}
|
|
1801
|
+
});
|
|
1562
1802
|
})
|
|
1563
1803
|
);
|
|
1564
1804
|
server.tool(
|
|
1565
1805
|
"get_request",
|
|
1566
|
-
"Get
|
|
1806
|
+
"Get the details of a captured request by ID: method, path, headers and body. A request larger than the tool output has its body cut (bodyTruncated, bodySize) and a note saying where the whole request is available.",
|
|
1567
1807
|
getRequestShape,
|
|
1568
1808
|
withErrorHandling(async ({ requestId }) => {
|
|
1569
1809
|
const request = await client.requests.get(requestId);
|
|
1570
|
-
return
|
|
1810
|
+
return requestContent(request);
|
|
1571
1811
|
})
|
|
1572
1812
|
);
|
|
1573
1813
|
server.tool(
|
|
@@ -1576,18 +1816,18 @@ function registerTools(target, client, options = {}) {
|
|
|
1576
1816
|
waitForRequestShape,
|
|
1577
1817
|
withErrorHandling(async ({ endpointSlug, timeout, pollInterval }) => {
|
|
1578
1818
|
const request = await client.requests.waitFor(endpointSlug, { timeout, pollInterval });
|
|
1579
|
-
return
|
|
1819
|
+
return requestContent(request);
|
|
1580
1820
|
})
|
|
1581
1821
|
);
|
|
1582
1822
|
server.tool(
|
|
1583
1823
|
"wait_for_requests",
|
|
1584
1824
|
"Wait for multiple requests to arrive at an endpoint.",
|
|
1585
1825
|
{
|
|
1586
|
-
endpointSlug:
|
|
1587
|
-
count:
|
|
1826
|
+
endpointSlug: import_zod.z.string().describe("The endpoint slug to monitor"),
|
|
1827
|
+
count: import_zod.z.number().int().min(1).max(20).describe("Number of requests to collect"),
|
|
1588
1828
|
timeout: durationOrTimestampSchema.default("30s").describe('How long to wait, for example "30s"'),
|
|
1589
1829
|
pollInterval: durationOrTimestampSchema.optional().describe('Interval between polls, for example "500ms" or "1s"'),
|
|
1590
|
-
method:
|
|
1830
|
+
method: import_zod.z.string().optional().describe("Only collect requests with this HTTP method")
|
|
1591
1831
|
},
|
|
1592
1832
|
withErrorHandling(async ({ endpointSlug, count, timeout, pollInterval, method }) => {
|
|
1593
1833
|
const result = await waitForMultipleRequests(client, endpointSlug, {
|
|
@@ -1605,7 +1845,7 @@ function registerTools(target, client, options = {}) {
|
|
|
1605
1845
|
"replay_request",
|
|
1606
1846
|
"Replay a previously captured request to a target URL.",
|
|
1607
1847
|
{
|
|
1608
|
-
requestId:
|
|
1848
|
+
requestId: import_zod.z.string().describe("The captured request ID"),
|
|
1609
1849
|
targetUrl: httpUrlSchema.describe("The URL to replay the request to")
|
|
1610
1850
|
},
|
|
1611
1851
|
withErrorHandling(async ({ requestId, targetUrl }) => {
|
|
@@ -1622,9 +1862,9 @@ function registerTools(target, client, options = {}) {
|
|
|
1622
1862
|
"render_email",
|
|
1623
1863
|
"A captured email in the shape an inbound mail provider posts it (postmark, sendgrid, mailgun, cloudmailin), to test the inbound handler you wrote for that provider against real mail. Returns the method, headers and body; with targetUrl (your local handler), posts it there and returns the answer. A Mailgun form is signed when you pass the signing key your handler checks.",
|
|
1624
1864
|
{
|
|
1625
|
-
requestId:
|
|
1626
|
-
format:
|
|
1627
|
-
mailgunSigningKey:
|
|
1865
|
+
requestId: import_zod.z.string().describe("The captured email's request ID"),
|
|
1866
|
+
format: import_zod.z.enum(["postmark", "sendgrid", "mailgun", "cloudmailin", "json"]).describe("Whose shape; json is webhooks.cc's own email.received JSON (unsigned)"),
|
|
1867
|
+
mailgunSigningKey: import_zod.z.string().optional().describe("For mailgun: the signing key your handler verifies with (a test key)"),
|
|
1628
1868
|
targetUrl: httpUrlSchema.optional().describe("Post it here, e.g. http://localhost:3000/api/inbound/postmark")
|
|
1629
1869
|
},
|
|
1630
1870
|
withErrorHandling(async ({ requestId, format, mailgunSigningKey, targetUrl }) => {
|
|
@@ -1655,9 +1895,9 @@ function registerTools(target, client, options = {}) {
|
|
|
1655
1895
|
"compare_requests",
|
|
1656
1896
|
"Compare two captured requests and show the structured differences.",
|
|
1657
1897
|
{
|
|
1658
|
-
leftRequestId:
|
|
1659
|
-
rightRequestId:
|
|
1660
|
-
ignoreHeaders:
|
|
1898
|
+
leftRequestId: import_zod.z.string().describe("The first request ID"),
|
|
1899
|
+
rightRequestId: import_zod.z.string().describe("The second request ID"),
|
|
1900
|
+
ignoreHeaders: import_zod.z.array(import_zod.z.string()).optional().describe("Headers to ignore during comparison")
|
|
1661
1901
|
},
|
|
1662
1902
|
withErrorHandling(async ({ leftRequestId, rightRequestId, ignoreHeaders }) => {
|
|
1663
1903
|
const [leftRequest, rightRequest] = await Promise.all([
|
|
@@ -1681,8 +1921,8 @@ function registerTools(target, client, options = {}) {
|
|
|
1681
1921
|
"extract_from_request",
|
|
1682
1922
|
"Extract specific JSON fields from a captured request body.",
|
|
1683
1923
|
{
|
|
1684
|
-
requestId:
|
|
1685
|
-
jsonPaths:
|
|
1924
|
+
requestId: import_zod.z.string().describe("The request ID"),
|
|
1925
|
+
jsonPaths: import_zod.z.array(import_zod.z.string()).min(1).max(50).describe("Dot-notation JSON paths to extract")
|
|
1686
1926
|
},
|
|
1687
1927
|
withErrorHandling(async ({ requestId, jsonPaths }) => {
|
|
1688
1928
|
const request = await client.requests.get(requestId);
|
|
@@ -1696,14 +1936,14 @@ function registerTools(target, client, options = {}) {
|
|
|
1696
1936
|
"verify_signature",
|
|
1697
1937
|
"Verify the webhook signature on a captured request. When called without provider/secret, returns the server-side pre-computed result (if signing is configured on the endpoint). When called with provider+secret, performs client-side verification.",
|
|
1698
1938
|
{
|
|
1699
|
-
requestId:
|
|
1700
|
-
provider:
|
|
1701
|
-
secret:
|
|
1939
|
+
requestId: import_zod.z.string().describe("The captured request ID"),
|
|
1940
|
+
provider: import_zod.z.enum(import_sdk.VERIFY_PROVIDERS).optional().describe("Provider whose signature scheme should be verified. Omit to use stored result."),
|
|
1941
|
+
secret: import_zod.z.string().optional().describe(
|
|
1702
1942
|
"Provider credential for client-side verification (for example a shared signing secret, Adyen hex HMAC key, or PayPal webhook ID). Required for non-Discord providers."
|
|
1703
1943
|
),
|
|
1704
|
-
publicKey:
|
|
1944
|
+
publicKey: import_zod.z.string().optional().describe("Discord application public key. Required for provider=discord."),
|
|
1705
1945
|
url: httpUrlSchema.optional().describe("Original signed URL. Required for Twilio, Square, and HubSpot verification."),
|
|
1706
|
-
method:
|
|
1946
|
+
method: import_zod.z.string().optional().describe(
|
|
1707
1947
|
"Original HTTP method used when the signature was generated. Used by HubSpot v3 verification; the SDK assumes POST when omitted."
|
|
1708
1948
|
)
|
|
1709
1949
|
},
|
|
@@ -1752,7 +1992,7 @@ function registerTools(target, client, options = {}) {
|
|
|
1752
1992
|
"clear_requests",
|
|
1753
1993
|
"Delete captured requests for an endpoint without deleting the endpoint itself.",
|
|
1754
1994
|
{
|
|
1755
|
-
slug:
|
|
1995
|
+
slug: import_zod.z.string().describe("The endpoint slug to clear"),
|
|
1756
1996
|
before: durationOrTimestampSchema.optional().describe('Only clear requests older than this timestamp or duration like "1h"')
|
|
1757
1997
|
},
|
|
1758
1998
|
withErrorHandling(async ({ slug, before }) => {
|
|
@@ -1766,12 +2006,12 @@ function registerTools(target, client, options = {}) {
|
|
|
1766
2006
|
{
|
|
1767
2007
|
url: httpUrlSchema.describe("Target URL"),
|
|
1768
2008
|
method: methodSchema,
|
|
1769
|
-
headers:
|
|
1770
|
-
body:
|
|
1771
|
-
provider:
|
|
1772
|
-
template:
|
|
1773
|
-
event:
|
|
1774
|
-
secret:
|
|
2009
|
+
headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional().describe("HTTP headers to include"),
|
|
2010
|
+
body: import_zod.z.unknown().optional().describe("Request body"),
|
|
2011
|
+
provider: import_zod.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
2012
|
+
template: import_zod.z.string().optional().describe("Provider-specific template preset"),
|
|
2013
|
+
event: import_zod.z.string().optional().describe("Provider event or topic name"),
|
|
2014
|
+
secret: import_zod.z.string().optional().describe(
|
|
1775
2015
|
"Signing secret. Required for signed provider templates; omit for secretless providers (sendgrid, discord, plaid)."
|
|
1776
2016
|
)
|
|
1777
2017
|
},
|
|
@@ -1799,12 +2039,12 @@ function registerTools(target, client, options = {}) {
|
|
|
1799
2039
|
{
|
|
1800
2040
|
url: httpUrlSchema.describe("Target URL"),
|
|
1801
2041
|
method: methodSchema,
|
|
1802
|
-
headers:
|
|
1803
|
-
body:
|
|
1804
|
-
provider:
|
|
1805
|
-
template:
|
|
1806
|
-
event:
|
|
1807
|
-
secret:
|
|
2042
|
+
headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional().describe("HTTP headers to include"),
|
|
2043
|
+
body: import_zod.z.unknown().optional().describe("Request body"),
|
|
2044
|
+
provider: import_zod.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
2045
|
+
template: import_zod.z.string().optional().describe("Provider-specific template preset"),
|
|
2046
|
+
event: import_zod.z.string().optional().describe("Provider event or topic name"),
|
|
2047
|
+
secret: import_zod.z.string().optional().describe(
|
|
1808
2048
|
"Signing secret. Required for signed provider templates; omit for secretless providers (sendgrid, discord, plaid)."
|
|
1809
2049
|
)
|
|
1810
2050
|
},
|
|
@@ -1825,7 +2065,7 @@ function registerTools(target, client, options = {}) {
|
|
|
1825
2065
|
"list_provider_templates",
|
|
1826
2066
|
"List supported webhook providers, templates, and signing metadata.",
|
|
1827
2067
|
{
|
|
1828
|
-
provider:
|
|
2068
|
+
provider: import_zod.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Filter to a single provider")
|
|
1829
2069
|
},
|
|
1830
2070
|
withErrorHandling(async ({ provider }) => {
|
|
1831
2071
|
if (provider) {
|
|
@@ -1879,15 +2119,15 @@ function registerTools(target, client, options = {}) {
|
|
|
1879
2119
|
"test_webhook_flow",
|
|
1880
2120
|
"Run a full webhook test flow: create endpoint, optionally mock, send, wait, verify, replay, and clean up.",
|
|
1881
2121
|
{
|
|
1882
|
-
provider:
|
|
1883
|
-
event:
|
|
1884
|
-
secret:
|
|
2122
|
+
provider: import_zod.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to use when sending the webhook"),
|
|
2123
|
+
event: import_zod.z.string().optional().describe("Optional provider event or topic name"),
|
|
2124
|
+
secret: import_zod.z.string().optional().describe(
|
|
1885
2125
|
"Signing secret. Required when provider is set or signature verification is enabled."
|
|
1886
2126
|
),
|
|
1887
|
-
mockStatus:
|
|
2127
|
+
mockStatus: import_zod.z.number().int().min(100).max(599).optional().describe("Optional mock response status to configure before sending"),
|
|
1888
2128
|
targetUrl: httpUrlSchema.optional().describe("Optional URL to replay the captured request to after capture"),
|
|
1889
|
-
verifySignature:
|
|
1890
|
-
cleanup:
|
|
2129
|
+
verifySignature: import_zod.z.boolean().default(false).describe("Verify the captured request signature after capture"),
|
|
2130
|
+
cleanup: import_zod.z.boolean().default(true).describe("Delete the created endpoint after the flow completes")
|
|
1891
2131
|
},
|
|
1892
2132
|
withErrorHandling(
|
|
1893
2133
|
async ({
|
|
@@ -1962,14 +2202,14 @@ function registerTools(target, client, options = {}) {
|
|
|
1962
2202
|
"list_emails",
|
|
1963
2203
|
"List the emails an endpoint received at its mailhooks.cc address, newest first: subject, sender, tag, and the one-time code and main link found in each.",
|
|
1964
2204
|
{
|
|
1965
|
-
endpointSlug:
|
|
1966
|
-
limit:
|
|
2205
|
+
endpointSlug: import_zod.z.string().describe("The endpoint slug"),
|
|
2206
|
+
limit: import_zod.z.number().int().min(1).max(100).default(25).describe(
|
|
1967
2207
|
`Max emails to return. With tag, subject or from, the newest ${EMAIL_SCAN_LIMIT} emails are searched.`
|
|
1968
2208
|
),
|
|
1969
|
-
since:
|
|
1970
|
-
tag:
|
|
1971
|
-
subject:
|
|
1972
|
-
from:
|
|
2209
|
+
since: import_zod.z.number().optional().describe("Only emails received after this timestamp in ms"),
|
|
2210
|
+
tag: import_zod.z.string().optional().describe("Only emails sent to {slug}+{tag}@mailhooks.cc (exact, case-sensitive)"),
|
|
2211
|
+
subject: import_zod.z.string().optional().describe("Only emails whose subject contains this text"),
|
|
2212
|
+
from: import_zod.z.string().optional().describe("Only emails from this address (case-insensitive)")
|
|
1973
2213
|
},
|
|
1974
2214
|
withErrorHandling(async ({ endpointSlug, limit, since, tag, subject, from }) => {
|
|
1975
2215
|
const filtered = tag !== void 0 || subject !== void 0 || from !== void 0;
|
|
@@ -1987,10 +2227,10 @@ function registerTools(target, client, options = {}) {
|
|
|
1987
2227
|
"get_email",
|
|
1988
2228
|
"Get one captured email: by requestId, or the newest at an endpoint (optionally with a tag). Returns the parsed message with its one-time code and main link.",
|
|
1989
2229
|
{
|
|
1990
|
-
requestId:
|
|
1991
|
-
endpointSlug:
|
|
1992
|
-
tag:
|
|
1993
|
-
includeHtml:
|
|
2230
|
+
requestId: import_zod.z.string().optional().describe("The email's request ID"),
|
|
2231
|
+
endpointSlug: import_zod.z.string().optional().describe("Get the newest email at this endpoint instead (with requestId unset)"),
|
|
2232
|
+
tag: import_zod.z.string().optional().describe("With endpointSlug: only emails sent to this +tag"),
|
|
2233
|
+
includeHtml: import_zod.z.boolean().default(false).describe("Include the HTML part (default: false, only its size)")
|
|
1994
2234
|
},
|
|
1995
2235
|
withErrorHandling(async ({ requestId, endpointSlug, tag, includeHtml }) => {
|
|
1996
2236
|
if (requestId === void 0 === (endpointSlug === void 0)) {
|
|
@@ -2013,15 +2253,15 @@ function registerTools(target, client, options = {}) {
|
|
|
2013
2253
|
"wait_for_email",
|
|
2014
2254
|
"Wait for an email to arrive at an endpoint (for example after triggering a signup), then return it with its one-time code and main link. Use a tag per run to find your own email.",
|
|
2015
2255
|
{
|
|
2016
|
-
endpointSlug:
|
|
2017
|
-
tag:
|
|
2018
|
-
subject:
|
|
2019
|
-
from:
|
|
2256
|
+
endpointSlug: import_zod.z.string().describe("The endpoint slug"),
|
|
2257
|
+
tag: import_zod.z.string().optional().describe("Only emails sent to {slug}+{tag}@mailhooks.cc"),
|
|
2258
|
+
subject: import_zod.z.string().optional().describe("Only emails whose subject contains this text"),
|
|
2259
|
+
from: import_zod.z.string().optional().describe("Only emails from this address (case-insensitive)"),
|
|
2020
2260
|
timeout: durationOrTimestampSchema.default("30s").describe(
|
|
2021
2261
|
`How long to wait (default "30s"). Keep it under your MCP client's request timeout, often 60 seconds.`
|
|
2022
2262
|
),
|
|
2023
|
-
since:
|
|
2024
|
-
includeHtml:
|
|
2263
|
+
since: import_zod.z.number().optional().describe("Only emails received after this timestamp in ms (default: five minutes ago)"),
|
|
2264
|
+
includeHtml: import_zod.z.boolean().default(false).describe("Include the HTML part (default: false)")
|
|
2025
2265
|
},
|
|
2026
2266
|
withErrorHandling(async ({ endpointSlug, tag, subject, from, timeout, since, includeHtml }) => {
|
|
2027
2267
|
const view = await emailEndpointView(client, endpointSlug);
|
|
@@ -2039,8 +2279,8 @@ function registerTools(target, client, options = {}) {
|
|
|
2039
2279
|
"send_test_email",
|
|
2040
2280
|
"Deliver a sample email (with a 6-digit code and a link) to an endpoint, optionally to a +tag address. It counts as one request and skips SMTP, so sender checks do not run on it.",
|
|
2041
2281
|
{
|
|
2042
|
-
slug:
|
|
2043
|
-
tag:
|
|
2282
|
+
slug: import_zod.z.string().describe("The endpoint slug"),
|
|
2283
|
+
tag: import_zod.z.string().optional().describe("Deliver to {slug}+{tag}@mailhooks.cc")
|
|
2044
2284
|
},
|
|
2045
2285
|
withErrorHandling(async ({ slug, tag }) => {
|
|
2046
2286
|
const result = await client.emails.sendTest(slug, { tag });
|
|
@@ -2051,8 +2291,8 @@ function registerTools(target, client, options = {}) {
|
|
|
2051
2291
|
"list_destinations",
|
|
2052
2292
|
"List where an endpoint's captures go: its destinations (up to 10) with their URL, kinds, filter, format, templates and timing, and with summary: true their last 24 hours. Owner only.",
|
|
2053
2293
|
{
|
|
2054
|
-
slug:
|
|
2055
|
-
summary:
|
|
2294
|
+
slug: import_zod.z.string().describe("The endpoint slug"),
|
|
2295
|
+
summary: import_zod.z.boolean().default(true).describe("Add each destination's last 24 hours")
|
|
2056
2296
|
},
|
|
2057
2297
|
withErrorHandling(async ({ slug, summary }) => {
|
|
2058
2298
|
return jsonContent(await client.destinations.list(slug, { summary }));
|
|
@@ -2062,7 +2302,7 @@ function registerTools(target, client, options = {}) {
|
|
|
2062
2302
|
"add_destination",
|
|
2063
2303
|
"Add a destination for an endpoint's captures: your server (as received or signed JSON), a Slack or Discord channel (chat, optionally your own messageTemplate, throttleSeconds 1 for a heads-up), or a JSON body you shape with bodyTemplate (for Zapier, n8n or your API). filter sends only matching captures; delaySeconds makes it a callback. Each destination gets its own signing secret. Check it with preview_destination before relying on it. Owner only.",
|
|
2064
2304
|
{
|
|
2065
|
-
slug:
|
|
2305
|
+
slug: import_zod.z.string().describe("The endpoint slug"),
|
|
2066
2306
|
url: httpUrlSchema.describe("https URL on a public host name, or a Slack or Discord webhook"),
|
|
2067
2307
|
...destinationFields
|
|
2068
2308
|
},
|
|
@@ -2074,8 +2314,8 @@ function registerTools(target, client, options = {}) {
|
|
|
2074
2314
|
"update_destination",
|
|
2075
2315
|
"Change a destination: any of its fields; only the ones you pass change. Owner only.",
|
|
2076
2316
|
{
|
|
2077
|
-
slug:
|
|
2078
|
-
destinationId:
|
|
2317
|
+
slug: import_zod.z.string().describe("The endpoint slug"),
|
|
2318
|
+
destinationId: import_zod.z.string().describe("The destination's id (from list_destinations)"),
|
|
2079
2319
|
url: httpUrlSchema.optional().describe("A new URL"),
|
|
2080
2320
|
...destinationFields
|
|
2081
2321
|
},
|
|
@@ -2087,8 +2327,8 @@ function registerTools(target, client, options = {}) {
|
|
|
2087
2327
|
"remove_destination",
|
|
2088
2328
|
"Remove a destination. Deliveries still waiting for it are marked failed; its log stays. Owner only.",
|
|
2089
2329
|
{
|
|
2090
|
-
slug:
|
|
2091
|
-
destinationId:
|
|
2330
|
+
slug: import_zod.z.string().describe("The endpoint slug"),
|
|
2331
|
+
destinationId: import_zod.z.string().describe("The destination's id")
|
|
2092
2332
|
},
|
|
2093
2333
|
withErrorHandling(async ({ slug, destinationId }) => {
|
|
2094
2334
|
await client.destinations.delete(slug, destinationId);
|
|
@@ -2099,10 +2339,10 @@ function registerTools(target, client, options = {}) {
|
|
|
2099
2339
|
"preview_destination",
|
|
2100
2340
|
"Dry run of a destination against a captured request: whether its filter lets the request through and the exact request it would send (method, URL, headers, body with placeholders filled in). Nothing is sent. Use destinationId for a saved destination, destination for unsaved fields (on top of the saved ones, or with a url on their own), or both.",
|
|
2101
2341
|
{
|
|
2102
|
-
slug:
|
|
2103
|
-
destinationId:
|
|
2104
|
-
destination:
|
|
2105
|
-
requestId:
|
|
2342
|
+
slug: import_zod.z.string().describe("The endpoint slug"),
|
|
2343
|
+
destinationId: import_zod.z.string().optional().describe("A saved destination"),
|
|
2344
|
+
destination: import_zod.z.object({ url: httpUrlSchema.optional(), ...destinationFields }).optional().describe("Unsaved fields to try"),
|
|
2345
|
+
requestId: import_zod.z.string().optional().describe("A captured request (default: the newest of the kind it takes, or a sample)")
|
|
2106
2346
|
},
|
|
2107
2347
|
withErrorHandling(async ({ slug, destinationId, destination, requestId }) => {
|
|
2108
2348
|
if (!destinationId && !destination?.url) {
|
|
@@ -2121,26 +2361,26 @@ function registerTools(target, client, options = {}) {
|
|
|
2121
2361
|
"configure_forwarding",
|
|
2122
2362
|
"Change an endpoint's primary forwarding destination (the one the endpoint's forward* fields stand for): the URL, on or off, which kinds, the format, your own headers, and the advanced options. Only the arguments you pass change. Owner only. For several destinations, filters, templates, throttling or delays, use add_destination and update_destination.",
|
|
2123
2363
|
{
|
|
2124
|
-
slug:
|
|
2364
|
+
slug: import_zod.z.string().describe("The endpoint slug"),
|
|
2125
2365
|
url: httpUrlSchema.nullable().optional().describe("https URL on a public host name, or null to remove it (forwarding must be off)"),
|
|
2126
|
-
enabled:
|
|
2127
|
-
http:
|
|
2128
|
-
email:
|
|
2129
|
-
format:
|
|
2366
|
+
enabled: import_zod.z.boolean().optional().describe("Turn forwarding on (needs a URL) or off (fails deliveries still waiting)"),
|
|
2367
|
+
http: import_zod.z.boolean().optional().describe("Forward HTTP requests"),
|
|
2368
|
+
email: import_zod.z.boolean().optional().describe("Forward emails"),
|
|
2369
|
+
format: import_zod.z.enum(["auto", "as_received", "json", "chat"]).optional().describe(
|
|
2130
2370
|
"auto (pick from the URL), as_received (relay HTTP requests unchanged; emails as signed JSON), json (signed JSON envelope for both kinds) or chat (Slack or Discord message)"
|
|
2131
2371
|
),
|
|
2132
|
-
headers:
|
|
2133
|
-
|
|
2134
|
-
name:
|
|
2135
|
-
value:
|
|
2372
|
+
headers: import_zod.z.array(
|
|
2373
|
+
import_zod.z.object({
|
|
2374
|
+
name: import_zod.z.string().describe("Header name, e.g. Authorization"),
|
|
2375
|
+
value: import_zod.z.string().nullable().describe("Header value, or null to keep the value stored under this name")
|
|
2136
2376
|
})
|
|
2137
2377
|
).max(10).optional().describe(
|
|
2138
2378
|
"Your own headers for every delivery (not chat). Replaces the whole list; [] removes them"
|
|
2139
2379
|
),
|
|
2140
|
-
appendPath:
|
|
2141
|
-
retrySeconds:
|
|
2142
|
-
keepOrder:
|
|
2143
|
-
sentField:
|
|
2380
|
+
appendPath: import_zod.z.boolean().optional().describe("Add the path after the slug to the URL for relayed requests (default true)"),
|
|
2381
|
+
retrySeconds: import_zod.z.union([import_zod.z.literal(0), import_zod.z.literal(3600), import_zod.z.literal(86400)]).optional().describe("Retry failed deliveries for 86400 (default), 3600 or 0 seconds"),
|
|
2382
|
+
keepOrder: import_zod.z.boolean().optional().describe("One delivery at a time, in capture order (default false)"),
|
|
2383
|
+
sentField: import_zod.z.string().max(128).nullable().optional().describe(
|
|
2144
2384
|
"JSON body field with the sender's own timestamp, e.g. publishedAt or data.publishedAt; null clears it"
|
|
2145
2385
|
)
|
|
2146
2386
|
},
|
|
@@ -2166,9 +2406,9 @@ function registerTools(target, client, options = {}) {
|
|
|
2166
2406
|
"get_forwarding_secret",
|
|
2167
2407
|
"Read (or with rotate: true, replace) the whsec_ secret forwarded deliveries are signed with. Owner only. Treat the value as a credential: put it in the handler's environment, not in code.",
|
|
2168
2408
|
{
|
|
2169
|
-
slug:
|
|
2170
|
-
destinationId:
|
|
2171
|
-
rotate:
|
|
2409
|
+
slug: import_zod.z.string().describe("The endpoint slug"),
|
|
2410
|
+
destinationId: import_zod.z.string().optional().describe("A destination (default: the endpoint's primary destination)"),
|
|
2411
|
+
rotate: import_zod.z.boolean().default(false).describe("Replace the secret; every delivery from now on uses the new one")
|
|
2172
2412
|
},
|
|
2173
2413
|
withErrorHandling(async ({ slug, destinationId, rotate }) => {
|
|
2174
2414
|
const secret = destinationId ? rotate ? await client.destinations.rotateSecret(slug, destinationId) : await client.destinations.secret(slug, destinationId) : rotate ? await client.forwarding.rotateSecret(slug) : await client.forwarding.secret(slug);
|
|
@@ -2179,10 +2419,10 @@ function registerTools(target, client, options = {}) {
|
|
|
2179
2419
|
"test_forwarding",
|
|
2180
2420
|
"Send the endpoint's newest captured request of a kind (or a sample, or requestId) to a destination once, exactly as a delivery would go out, and report the answer. Works whether it is on or off, and whatever its filter says; nothing is retried or logged.",
|
|
2181
2421
|
{
|
|
2182
|
-
slug:
|
|
2183
|
-
destinationId:
|
|
2184
|
-
kind:
|
|
2185
|
-
requestId:
|
|
2422
|
+
slug: import_zod.z.string().describe("The endpoint slug"),
|
|
2423
|
+
destinationId: import_zod.z.string().optional().describe("A destination (default: the endpoint's primary destination)"),
|
|
2424
|
+
kind: import_zod.z.enum(["http", "email"]).optional().describe("Which kind to send (default: http when the destination takes HTTP requests)"),
|
|
2425
|
+
requestId: import_zod.z.string().optional().describe("With destinationId: a captured request to send")
|
|
2186
2426
|
},
|
|
2187
2427
|
withErrorHandling(async ({ slug, destinationId, kind, requestId }) => {
|
|
2188
2428
|
if (destinationId) {
|
|
@@ -2200,14 +2440,14 @@ function registerTools(target, client, options = {}) {
|
|
|
2200
2440
|
"list_deliveries",
|
|
2201
2441
|
"List forwarding deliveries: an endpoint's delivery log with its counts (endpointSlug), or every delivery and try of one captured request or email (requestId). Rows carry senderAt when the request had the sender's own timestamp, so receivedAt - senderAt is the time to reach webhooks.cc.",
|
|
2202
2442
|
{
|
|
2203
|
-
endpointSlug:
|
|
2204
|
-
requestId:
|
|
2205
|
-
limit:
|
|
2206
|
-
status:
|
|
2443
|
+
endpointSlug: import_zod.z.string().optional().describe("List the endpoint's delivery log"),
|
|
2444
|
+
requestId: import_zod.z.string().optional().describe("List every delivery and try of this request"),
|
|
2445
|
+
limit: import_zod.z.number().int().min(1).max(100).default(20).describe("With endpointSlug: max rows"),
|
|
2446
|
+
status: import_zod.z.enum(["all", "pending", "failed", "skipped"]).default("all").describe(
|
|
2207
2447
|
"With endpointSlug: all, pending (queued or retrying), failed or skipped (filtered out)"
|
|
2208
2448
|
),
|
|
2209
|
-
destinationId:
|
|
2210
|
-
before:
|
|
2449
|
+
destinationId: import_zod.z.string().optional().describe("With endpointSlug: only this destination"),
|
|
2450
|
+
before: import_zod.z.string().optional().describe("With endpointSlug: a row's cursor, to list older rows")
|
|
2211
2451
|
},
|
|
2212
2452
|
withErrorHandling(async ({ endpointSlug, requestId, limit, status, destinationId, before }) => {
|
|
2213
2453
|
if (requestId === void 0 === (endpointSlug === void 0)) {
|
|
@@ -2234,9 +2474,9 @@ function registerTools(target, client, options = {}) {
|
|
|
2234
2474
|
"redeliver",
|
|
2235
2475
|
"Forward again with the current settings: one captured request or email (requestId; to every destination that takes it, or with destinationId to that one whatever its filter says), or every request whose latest delivery failed (endpointSlug, optionally only destinationId, oldest first, up to 1,000 at a time per destination). Owner only.",
|
|
2236
2476
|
{
|
|
2237
|
-
requestId:
|
|
2238
|
-
endpointSlug:
|
|
2239
|
-
destinationId:
|
|
2477
|
+
requestId: import_zod.z.string().optional().describe("The request or email to send again"),
|
|
2478
|
+
endpointSlug: import_zod.z.string().optional().describe("Send every failed delivery of this endpoint again"),
|
|
2479
|
+
destinationId: import_zod.z.string().optional().describe("Only this destination")
|
|
2240
2480
|
},
|
|
2241
2481
|
withErrorHandling(async ({ requestId, endpointSlug, destinationId }) => {
|
|
2242
2482
|
if (requestId === void 0 === (endpointSlug === void 0)) {
|
|
@@ -2253,9 +2493,9 @@ function registerTools(target, client, options = {}) {
|
|
|
2253
2493
|
"share_port",
|
|
2254
2494
|
"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.",
|
|
2255
2495
|
{
|
|
2256
|
-
port:
|
|
2257
|
-
name:
|
|
2258
|
-
password:
|
|
2496
|
+
port: import_zod.z.number().int().min(1).max(65535).describe("The local port the site runs on"),
|
|
2497
|
+
name: import_zod.z.string().optional().describe("A name you reserved (Pro and Business); leave out for a random one"),
|
|
2498
|
+
password: import_zod.z.string().min(8).max(200).optional().describe("What visitors must type (Pro and Business), 8 to 200 characters")
|
|
2259
2499
|
},
|
|
2260
2500
|
withErrorHandling(async ({ port, name, password }) => {
|
|
2261
2501
|
const running = started.get(port);
|
|
@@ -2300,7 +2540,7 @@ function registerTools(target, client, options = {}) {
|
|
|
2300
2540
|
"list_shares",
|
|
2301
2541
|
`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.`,
|
|
2302
2542
|
{
|
|
2303
|
-
state:
|
|
2543
|
+
state: import_zod.z.enum(["live", "ended"]).optional().describe("Only the running ones, or only the ended ones")
|
|
2304
2544
|
},
|
|
2305
2545
|
withErrorHandling(async ({ state }) => {
|
|
2306
2546
|
return jsonContent(withShareHints(await client.shares.list(state ? { state } : {}), remote));
|
|
@@ -2316,8 +2556,8 @@ function registerTools(target, client, options = {}) {
|
|
|
2316
2556
|
"create_api_key",
|
|
2317
2557
|
"Ask for a new API key for the account, for CI or another tool. The account's owner must approve it: this returns at once with a link and a code. Show the person the link and tell them to check that the page shows the same code, then call wait_for_approval with the approval id to get the key (shown once).",
|
|
2318
2558
|
{
|
|
2319
|
-
name:
|
|
2320
|
-
expiresInDays:
|
|
2559
|
+
name: import_zod.z.string().min(1).max(64).describe('A name that says where the key is used, such as "GitHub Actions"'),
|
|
2560
|
+
expiresInDays: import_zod.z.number().int().min(1).max(365).optional().describe("How many days the key works, 1 to 365 (default 90)")
|
|
2321
2561
|
},
|
|
2322
2562
|
withErrorHandling(async ({ name, expiresInDays }) => {
|
|
2323
2563
|
const pending = await client.apiKeys.create({ name, expiresInDays });
|
|
@@ -2330,9 +2570,9 @@ function registerTools(target, client, options = {}) {
|
|
|
2330
2570
|
);
|
|
2331
2571
|
server.tool(
|
|
2332
2572
|
"wait_for_approval",
|
|
2333
|
-
"Wait for the account's owner to answer an approval (from create_api_key, create_share_link, remove_domain, rename_account, connect_google, pick_spreadsheet or ai_setup) and collect what it grants or did, such as the new API key, the share link or AI setup's proposals. If they have not answered yet, call it again.",
|
|
2573
|
+
"Wait for the account's owner to answer an approval (from create_api_key, create_share_link, remove_domain, rename_account, delete_team, invite_to_team, connect_google, pick_spreadsheet or ai_setup) and collect what it grants or did, such as the new API key, the share link or AI setup's proposals. If they have not answered yet, call it again.",
|
|
2334
2574
|
{
|
|
2335
|
-
id:
|
|
2575
|
+
id: import_zod.z.string().describe("The approval id"),
|
|
2336
2576
|
timeout: durationOrTimestampSchema.default("45s").describe('How long to wait for the person, for example "45s"')
|
|
2337
2577
|
},
|
|
2338
2578
|
withErrorHandling(async ({ id, timeout }) => {
|
|
@@ -2364,7 +2604,7 @@ function registerTools(target, client, options = {}) {
|
|
|
2364
2604
|
"revoke_api_key",
|
|
2365
2605
|
"Revoke one of the account's API keys by its id (from list_api_keys). Anything using it stops working at once.",
|
|
2366
2606
|
{
|
|
2367
|
-
id:
|
|
2607
|
+
id: import_zod.z.string().describe("The key's id, from list_api_keys")
|
|
2368
2608
|
},
|
|
2369
2609
|
withErrorHandling(async ({ id }) => {
|
|
2370
2610
|
await client.apiKeys.revoke(id);
|
|
@@ -2375,7 +2615,7 @@ function registerTools(target, client, options = {}) {
|
|
|
2375
2615
|
"rename_account",
|
|
2376
2616
|
"Set the account's display name, or clear it with null. The account's owner must approve it: this returns a link and a code to show them, then call wait_for_approval.",
|
|
2377
2617
|
{
|
|
2378
|
-
name:
|
|
2618
|
+
name: import_zod.z.string().min(1).max(100).nullable().describe("The new display name, or null to clear it")
|
|
2379
2619
|
},
|
|
2380
2620
|
withErrorHandling(async ({ name }) => {
|
|
2381
2621
|
const pending = await client.account.rename(name);
|
|
@@ -2393,7 +2633,7 @@ function registerTools(target, client, options = {}) {
|
|
|
2393
2633
|
"disconnect_agent",
|
|
2394
2634
|
"Disconnect an agent by its id (from list_connected_agents): its tokens stop working at once. The endpoints it made stay in the account. Disconnecting the agent you are running as ends your own access.",
|
|
2395
2635
|
{
|
|
2396
|
-
id:
|
|
2636
|
+
id: import_zod.z.string().describe("The agent's id, from list_connected_agents")
|
|
2397
2637
|
},
|
|
2398
2638
|
withErrorHandling(async ({ id }) => {
|
|
2399
2639
|
await client.agents.disconnect(id);
|
|
@@ -2404,7 +2644,7 @@ function registerTools(target, client, options = {}) {
|
|
|
2404
2644
|
"list_share_links",
|
|
2405
2645
|
"List live public links to frozen copies of requests (/s/<token>), the account's or one request's. Not the same as list_shares, which lists port previews and live tunnels.",
|
|
2406
2646
|
{
|
|
2407
|
-
requestId:
|
|
2647
|
+
requestId: import_zod.z.string().optional().describe("Only the links to this request")
|
|
2408
2648
|
},
|
|
2409
2649
|
withErrorHandling(async ({ requestId }) => {
|
|
2410
2650
|
const links = requestId ? await client.shareLinks.forRequest(requestId) : await client.shareLinks.list();
|
|
@@ -2415,9 +2655,9 @@ function registerTools(target, client, options = {}) {
|
|
|
2415
2655
|
"create_share_link",
|
|
2416
2656
|
"Make a public link to a read-only copy of one HTTP request (not an email), for a teammate or a bug report. Anyone with the link can read it, so the account's owner must approve: this returns a link and a code to show them, then call wait_for_approval for the share link.",
|
|
2417
2657
|
{
|
|
2418
|
-
requestId:
|
|
2419
|
-
expiresInDays:
|
|
2420
|
-
hideSensitive:
|
|
2658
|
+
requestId: import_zod.z.string().describe("The request's id"),
|
|
2659
|
+
expiresInDays: import_zod.z.union([import_zod.z.literal(1), import_zod.z.literal(7), import_zod.z.literal(30)]).optional().describe("How long the link works: 1, 7 or 30 days (default 7)"),
|
|
2660
|
+
hideSensitive: import_zod.z.boolean().optional().describe(
|
|
2421
2661
|
"Also leave out signature and token headers (default true). Credentials are always left out."
|
|
2422
2662
|
)
|
|
2423
2663
|
},
|
|
@@ -2431,14 +2671,14 @@ function registerTools(target, client, options = {}) {
|
|
|
2431
2671
|
"revoke_share_link",
|
|
2432
2672
|
"Revoke a share link by its id (from list_share_links): its page answers 404 from then on.",
|
|
2433
2673
|
{
|
|
2434
|
-
id:
|
|
2674
|
+
id: import_zod.z.string().describe("The link's id, from list_share_links")
|
|
2435
2675
|
},
|
|
2436
2676
|
withErrorHandling(async ({ id }) => {
|
|
2437
2677
|
await client.shareLinks.revoke(id);
|
|
2438
2678
|
return textContent(`Revoked the share link ${id}.`);
|
|
2439
2679
|
})
|
|
2440
2680
|
);
|
|
2441
|
-
const teamIdSchema =
|
|
2681
|
+
const teamIdSchema = import_zod.z.string().optional().describe("A Team Business team's id (its owner only); leave out for your own account");
|
|
2442
2682
|
server.tool(
|
|
2443
2683
|
"list_domains",
|
|
2444
2684
|
"List custom inbound domains (Business): each one's status, DNS records to publish, routes and catch-all endpoint.",
|
|
@@ -2449,7 +2689,7 @@ function registerTools(target, client, options = {}) {
|
|
|
2449
2689
|
"add_domain",
|
|
2450
2690
|
"Add a custom inbound domain (Business) so mail to it is captured. Returns the TXT and MX records to publish; then call verify_domain.",
|
|
2451
2691
|
{
|
|
2452
|
-
domain:
|
|
2692
|
+
domain: import_zod.z.string().min(1).max(253).describe("The domain, such as mail.example.com"),
|
|
2453
2693
|
teamId: teamIdSchema
|
|
2454
2694
|
},
|
|
2455
2695
|
withErrorHandling(
|
|
@@ -2460,7 +2700,7 @@ function registerTools(target, client, options = {}) {
|
|
|
2460
2700
|
"verify_domain",
|
|
2461
2701
|
"Look a custom domain's TXT and MX records up now. It receives mail once both are found.",
|
|
2462
2702
|
{
|
|
2463
|
-
id:
|
|
2703
|
+
id: import_zod.z.string().describe("The domain's id, from list_domains"),
|
|
2464
2704
|
teamId: teamIdSchema
|
|
2465
2705
|
},
|
|
2466
2706
|
withErrorHandling(
|
|
@@ -2471,12 +2711,12 @@ function registerTools(target, client, options = {}) {
|
|
|
2471
2711
|
"route_domain",
|
|
2472
2712
|
"Set where a custom domain's mail goes: routes by local part (the part before @) to endpoints, everything else to a catch-all endpoint, or refused when the catch-all is null. Replaces the domain's routing.",
|
|
2473
2713
|
{
|
|
2474
|
-
id:
|
|
2475
|
-
catchAllEndpointId:
|
|
2476
|
-
routes:
|
|
2477
|
-
|
|
2478
|
-
localPart:
|
|
2479
|
-
endpointId:
|
|
2714
|
+
id: import_zod.z.string().describe("The domain's id, from list_domains"),
|
|
2715
|
+
catchAllEndpointId: import_zod.z.string().nullable().describe("The endpoint id for mail no route matches, or null to refuse it"),
|
|
2716
|
+
routes: import_zod.z.array(
|
|
2717
|
+
import_zod.z.object({
|
|
2718
|
+
localPart: import_zod.z.string().max(64).describe("The part before @"),
|
|
2719
|
+
endpointId: import_zod.z.string().describe("The endpoint's id")
|
|
2480
2720
|
})
|
|
2481
2721
|
).max(100).default([]).describe("Routes by local part"),
|
|
2482
2722
|
teamId: teamIdSchema
|
|
@@ -2489,7 +2729,7 @@ function registerTools(target, client, options = {}) {
|
|
|
2489
2729
|
"remove_domain",
|
|
2490
2730
|
"Remove a custom inbound domain: mail to it is refused from then on. The account's owner must approve it: this returns a link and a code to show them, then call wait_for_approval.",
|
|
2491
2731
|
{
|
|
2492
|
-
id:
|
|
2732
|
+
id: import_zod.z.string().describe("The domain's id, from list_domains"),
|
|
2493
2733
|
teamId: teamIdSchema
|
|
2494
2734
|
},
|
|
2495
2735
|
withErrorHandling(async ({ id, teamId }) => {
|
|
@@ -2514,7 +2754,7 @@ function registerTools(target, client, options = {}) {
|
|
|
2514
2754
|
"pick_spreadsheet",
|
|
2515
2755
|
"Have the account's owner choose an existing spreadsheet from Google Drive with a connected account (to add rows to it from a Sheets destination). Returns a link and a code to show them; the page opens Google's picker. Then call wait_for_approval for the spreadsheet's id and tabs. To use a new spreadsheet instead, call create_spreadsheet.",
|
|
2516
2756
|
{
|
|
2517
|
-
connectionId:
|
|
2757
|
+
connectionId: import_zod.z.string().describe("The Google connection's id, from list_google_connections")
|
|
2518
2758
|
},
|
|
2519
2759
|
withErrorHandling(
|
|
2520
2760
|
async ({ connectionId }) => textContent(approvalAskText(await client.google.pick(connectionId)))
|
|
@@ -2524,10 +2764,10 @@ function registerTools(target, client, options = {}) {
|
|
|
2524
2764
|
"create_spreadsheet",
|
|
2525
2765
|
"Create a new spreadsheet with a header row in a connected Google account, for a Sheets destination. Needs no approval: the app reaches only spreadsheets it created or that were chosen for it.",
|
|
2526
2766
|
{
|
|
2527
|
-
connectionId:
|
|
2528
|
-
title:
|
|
2529
|
-
sheet:
|
|
2530
|
-
headers:
|
|
2767
|
+
connectionId: import_zod.z.string().describe("The Google connection's id, from list_google_connections"),
|
|
2768
|
+
title: import_zod.z.string().max(200).optional().describe("The spreadsheet's title"),
|
|
2769
|
+
sheet: import_zod.z.string().max(100).optional().describe('The tab rows go to (default "Sheet1")'),
|
|
2770
|
+
headers: import_zod.z.array(import_zod.z.string().max(200)).max(50).describe("The header row, one per column")
|
|
2531
2771
|
},
|
|
2532
2772
|
withErrorHandling(
|
|
2533
2773
|
async ({ connectionId, title, sheet, headers }) => jsonContent(await client.google.createSpreadsheet({ connectionId, title, sheet, headers }))
|
|
@@ -2537,8 +2777,8 @@ function registerTools(target, client, options = {}) {
|
|
|
2537
2777
|
"list_sheet_tabs",
|
|
2538
2778
|
"List a spreadsheet's tabs (one created by the app or chosen with pick_spreadsheet), to choose the one a Sheets destination writes to.",
|
|
2539
2779
|
{
|
|
2540
|
-
connectionId:
|
|
2541
|
-
spreadsheetId:
|
|
2780
|
+
connectionId: import_zod.z.string().describe("The Google connection's id"),
|
|
2781
|
+
spreadsheetId: import_zod.z.string().describe("The spreadsheet's id")
|
|
2542
2782
|
},
|
|
2543
2783
|
withErrorHandling(
|
|
2544
2784
|
async ({ connectionId, spreadsheetId }) => jsonContent({ tabs: await client.google.tabs(connectionId, spreadsheetId) })
|
|
@@ -2548,7 +2788,7 @@ function registerTools(target, client, options = {}) {
|
|
|
2548
2788
|
"disconnect_google",
|
|
2549
2789
|
"Disconnect a Google account (id from list_google_connections) and revoke its access at Google. Sheets destinations using it fail until another account is chosen.",
|
|
2550
2790
|
{
|
|
2551
|
-
connectionId:
|
|
2791
|
+
connectionId: import_zod.z.string().describe("The Google connection's id")
|
|
2552
2792
|
},
|
|
2553
2793
|
withErrorHandling(async ({ connectionId }) => {
|
|
2554
2794
|
await client.google.disconnect(connectionId);
|
|
@@ -2559,7 +2799,7 @@ function registerTools(target, client, options = {}) {
|
|
|
2559
2799
|
"ai_setup_status",
|
|
2560
2800
|
"Whether AI setup is offered for an endpoint, and how many AI assists the plan has left in a rolling 30 days.",
|
|
2561
2801
|
{
|
|
2562
|
-
slug:
|
|
2802
|
+
slug: import_zod.z.string().describe("The endpoint's slug")
|
|
2563
2803
|
},
|
|
2564
2804
|
withErrorHandling(async ({ slug }) => jsonContent(await client.ai.status(slug)))
|
|
2565
2805
|
);
|
|
@@ -2567,25 +2807,25 @@ function registerTools(target, client, options = {}) {
|
|
|
2567
2807
|
"ai_setup",
|
|
2568
2808
|
"Have AI propose email fields (or a layout, or a repaired field) from 1 to 5 of an endpoint's captured emails. It sends those emails (subject, sender, recipients, text, headers; never attachments) to Anthropic's model, so the account's owner must approve it: this returns a link and a code to show them, and the page lists the emails. Then call wait_for_approval for the proposals, each with the value it found in every email. Nothing is saved: apply the fields with update_endpoint. Uses one AI assist.",
|
|
2569
2809
|
{
|
|
2570
|
-
slug:
|
|
2571
|
-
requestIds:
|
|
2572
|
-
ask:
|
|
2573
|
-
|
|
2574
|
-
kind:
|
|
2575
|
-
name:
|
|
2576
|
-
description:
|
|
2810
|
+
slug: import_zod.z.string().describe("The endpoint's slug"),
|
|
2811
|
+
requestIds: import_zod.z.array(import_zod.z.string()).min(1).max(5).describe("1 to 5 of the endpoint's email request ids, from list_emails"),
|
|
2812
|
+
ask: import_zod.z.union([
|
|
2813
|
+
import_zod.z.object({
|
|
2814
|
+
kind: import_zod.z.literal("field"),
|
|
2815
|
+
name: import_zod.z.string().describe("The field's name: letters, digits and _"),
|
|
2816
|
+
description: import_zod.z.string().describe('What the field is, such as "the order number"')
|
|
2577
2817
|
}),
|
|
2578
|
-
|
|
2579
|
-
kind:
|
|
2580
|
-
description:
|
|
2818
|
+
import_zod.z.object({
|
|
2819
|
+
kind: import_zod.z.literal("layout"),
|
|
2820
|
+
description: import_zod.z.string().optional().describe("What these emails are")
|
|
2581
2821
|
}),
|
|
2582
|
-
|
|
2583
|
-
kind:
|
|
2584
|
-
field:
|
|
2585
|
-
name:
|
|
2586
|
-
from:
|
|
2587
|
-
header:
|
|
2588
|
-
pattern:
|
|
2822
|
+
import_zod.z.object({
|
|
2823
|
+
kind: import_zod.z.literal("repair"),
|
|
2824
|
+
field: import_zod.z.object({
|
|
2825
|
+
name: import_zod.z.string(),
|
|
2826
|
+
from: import_zod.z.string(),
|
|
2827
|
+
header: import_zod.z.string().optional(),
|
|
2828
|
+
pattern: import_zod.z.string()
|
|
2589
2829
|
}).describe("The field to repair, as the endpoint has it")
|
|
2590
2830
|
})
|
|
2591
2831
|
]).describe("What to propose")
|
|
@@ -2601,97 +2841,519 @@ function registerTools(target, client, options = {}) {
|
|
|
2601
2841
|
)
|
|
2602
2842
|
)
|
|
2603
2843
|
);
|
|
2604
|
-
server.tool(
|
|
2605
|
-
"stop_share",
|
|
2606
|
-
"Stop a public preview or live tunnel by its id (from list_shares). The CLI running it is told why and exits.",
|
|
2844
|
+
server.tool(
|
|
2845
|
+
"stop_share",
|
|
2846
|
+
"Stop a public preview or live tunnel by its id (from list_shares). The CLI running it is told why and exits.",
|
|
2847
|
+
{
|
|
2848
|
+
id: import_zod.z.string().describe("The share's id, from list_shares")
|
|
2849
|
+
},
|
|
2850
|
+
withErrorHandling(async ({ id }) => {
|
|
2851
|
+
const result = await client.shares.stop(id);
|
|
2852
|
+
return jsonContent({
|
|
2853
|
+
id,
|
|
2854
|
+
stopped: result.stopped,
|
|
2855
|
+
edgeReached: result.edgeReached,
|
|
2856
|
+
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."
|
|
2857
|
+
});
|
|
2858
|
+
})
|
|
2859
|
+
);
|
|
2860
|
+
server.tool(
|
|
2861
|
+
"share_requests",
|
|
2862
|
+
"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.",
|
|
2863
|
+
{
|
|
2864
|
+
id: import_zod.z.string().describe("The share's id, from list_shares")
|
|
2865
|
+
},
|
|
2866
|
+
withErrorHandling(async ({ id }) => {
|
|
2867
|
+
return jsonContent(await client.shares.requests(id));
|
|
2868
|
+
})
|
|
2869
|
+
);
|
|
2870
|
+
server.tool(
|
|
2871
|
+
"list_names",
|
|
2872
|
+
`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"}.`,
|
|
2873
|
+
{},
|
|
2874
|
+
withErrorHandling(async () => {
|
|
2875
|
+
return jsonContent(await client.names.list());
|
|
2876
|
+
})
|
|
2877
|
+
);
|
|
2878
|
+
server.tool(
|
|
2879
|
+
"reserve_name",
|
|
2880
|
+
`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.`,
|
|
2881
|
+
{
|
|
2882
|
+
name: import_zod.z.string().min(1).max(63).describe("The name: lowercase letters, digits and dashes"),
|
|
2883
|
+
team: import_zod.z.string().optional().describe("A team's id or name (from list_teams), to keep the name inside that team")
|
|
2884
|
+
},
|
|
2885
|
+
withErrorHandling(async ({ name, team }) => {
|
|
2886
|
+
return jsonContent(await client.names.reserve(name, team ? { team } : {}));
|
|
2887
|
+
})
|
|
2888
|
+
);
|
|
2889
|
+
server.tool(
|
|
2890
|
+
"release_name",
|
|
2891
|
+
"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.",
|
|
2892
|
+
{
|
|
2893
|
+
name: import_zod.z.string().min(1).max(63).describe("The name, from list_names")
|
|
2894
|
+
},
|
|
2895
|
+
withErrorHandling(async ({ name }) => {
|
|
2896
|
+
await client.names.release(name);
|
|
2897
|
+
return jsonContent({ released: name });
|
|
2898
|
+
})
|
|
2899
|
+
);
|
|
2900
|
+
server.tool(
|
|
2901
|
+
"set_name_password",
|
|
2902
|
+
"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.",
|
|
2903
|
+
{
|
|
2904
|
+
name: import_zod.z.string().min(1).max(63).describe("The name, from list_names"),
|
|
2905
|
+
password: import_zod.z.string().min(8).max(200).describe("What visitors must type, 8 to 200 characters")
|
|
2906
|
+
},
|
|
2907
|
+
withErrorHandling(async ({ name, password }) => {
|
|
2908
|
+
await client.names.setPassword(name, password);
|
|
2909
|
+
return jsonContent({ name, password: true });
|
|
2910
|
+
})
|
|
2911
|
+
);
|
|
2912
|
+
server.tool(
|
|
2913
|
+
"clear_name_password",
|
|
2914
|
+
"Take the password off a reserved name, so its previews open without one.",
|
|
2915
|
+
{
|
|
2916
|
+
name: import_zod.z.string().min(1).max(63).describe("The name, from list_names")
|
|
2917
|
+
},
|
|
2918
|
+
withErrorHandling(async ({ name }) => {
|
|
2919
|
+
await client.names.clearPassword(name);
|
|
2920
|
+
return jsonContent({ name, password: false });
|
|
2921
|
+
})
|
|
2922
|
+
);
|
|
2923
|
+
server.tool(
|
|
2924
|
+
"describe",
|
|
2925
|
+
"Describe all available SDK operations, parameters, and types.",
|
|
2926
|
+
{},
|
|
2927
|
+
withErrorHandling(async () => {
|
|
2928
|
+
const description = client.describe();
|
|
2929
|
+
return jsonContent(description);
|
|
2930
|
+
})
|
|
2931
|
+
);
|
|
2932
|
+
return handles;
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
// src/local-tools.ts
|
|
2936
|
+
function asToolServer(server) {
|
|
2937
|
+
return {
|
|
2938
|
+
tool(name, description, shape, handler, annotations) {
|
|
2939
|
+
const registered = server.registerTool(
|
|
2940
|
+
name,
|
|
2941
|
+
{
|
|
2942
|
+
title: annotations?.title,
|
|
2943
|
+
description,
|
|
2944
|
+
inputSchema: import_zod2.z.object(shape),
|
|
2945
|
+
annotations
|
|
2946
|
+
},
|
|
2947
|
+
async (args2) => handler(args2)
|
|
2948
|
+
);
|
|
2949
|
+
return { remove: () => registered.remove() };
|
|
2950
|
+
}
|
|
2951
|
+
};
|
|
2952
|
+
}
|
|
2953
|
+
var warned = /* @__PURE__ */ new Set();
|
|
2954
|
+
function toolSelection(options = {}) {
|
|
2955
|
+
const fromOption = options.tools !== void 0;
|
|
2956
|
+
const parsed = parseToolGroups(fromOption ? options.tools : process.env.WHK_MCP_TOOLS);
|
|
2957
|
+
const setting = fromOption ? options.tools : process.env.WHK_MCP_TOOLS;
|
|
2958
|
+
if (parsed && parsed.unknown.length > 0 && !warned.has(setting ?? "")) {
|
|
2959
|
+
warned.add(setting ?? "");
|
|
2960
|
+
console.error(
|
|
2961
|
+
`webhooks-cc-mcp: unknown tool groups in ${fromOption ? "tools" : "WHK_MCP_TOOLS"}: ${parsed.unknown.join(", ")} (known: all, ${TOOL_GROUP_NAMES.join(", ")})`
|
|
2962
|
+
);
|
|
2963
|
+
}
|
|
2964
|
+
const readOnly = options.readOnly ?? /^(1|true|yes|on)$/i.test((process.env.WHK_MCP_READ_ONLY ?? "").trim());
|
|
2965
|
+
return { groups: parsed?.groups ?? null, readOnly };
|
|
2966
|
+
}
|
|
2967
|
+
var EXTRA_GROUPS = TOOL_GROUP_NAMES.filter((group2) => group2 !== "core");
|
|
2968
|
+
var ENABLE_TOOLS_ANNOTATIONS = {
|
|
2969
|
+
title: toolTitle(ENABLE_TOOLS),
|
|
2970
|
+
readOnlyHint: false,
|
|
2971
|
+
destructiveHint: false,
|
|
2972
|
+
idempotentHint: true,
|
|
2973
|
+
openWorldHint: false
|
|
2974
|
+
};
|
|
2975
|
+
var LocalToolGroups = class {
|
|
2976
|
+
constructor(server, selection) {
|
|
2977
|
+
this.server = server;
|
|
2978
|
+
this.selection = selection;
|
|
2979
|
+
this.client = null;
|
|
2980
|
+
this.loaded = /* @__PURE__ */ new Set();
|
|
2981
|
+
this.handles = [];
|
|
2982
|
+
this.enableTools = null;
|
|
2983
|
+
this.clientKnown = false;
|
|
2984
|
+
}
|
|
2985
|
+
/** The groups an account's tools start with. */
|
|
2986
|
+
startingGroups() {
|
|
2987
|
+
if (this.selection.groups) return [...this.selection.groups];
|
|
2988
|
+
return this.clientKnown && clientLoadsEverything(this.clientName) ? [...TOOL_GROUP_NAMES] : ["core"];
|
|
2989
|
+
}
|
|
2990
|
+
/** Registers the tools for an account; the handle removes them all again. */
|
|
2991
|
+
show(client) {
|
|
2992
|
+
this.hide();
|
|
2993
|
+
this.client = client;
|
|
2994
|
+
this.load(this.startingGroups());
|
|
2995
|
+
return { remove: () => this.hide() };
|
|
2996
|
+
}
|
|
2997
|
+
/** Removes every account tool, enable_tools included. */
|
|
2998
|
+
hide() {
|
|
2999
|
+
for (const handle of this.handles) handle.remove();
|
|
3000
|
+
this.enableTools?.remove();
|
|
3001
|
+
this.handles = [];
|
|
3002
|
+
this.enableTools = null;
|
|
3003
|
+
this.loaded.clear();
|
|
3004
|
+
this.client = null;
|
|
3005
|
+
}
|
|
3006
|
+
/** The groups loaded now. */
|
|
3007
|
+
loadedGroups() {
|
|
3008
|
+
return TOOL_GROUP_NAMES.filter((group2) => this.loaded.has(group2));
|
|
3009
|
+
}
|
|
3010
|
+
/**
|
|
3011
|
+
* The client named itself (initialize, or a 2026-07-28 request's _meta).
|
|
3012
|
+
* Without a WHK_MCP_TOOLS setting, a client that takes long tool lists
|
|
3013
|
+
* gets every group. Only the first name counts.
|
|
3014
|
+
*/
|
|
3015
|
+
clientIdentified(name) {
|
|
3016
|
+
if (this.clientKnown) return;
|
|
3017
|
+
this.clientKnown = true;
|
|
3018
|
+
this.clientName = name;
|
|
3019
|
+
if (this.selection.groups || !clientLoadsEverything(name)) return;
|
|
3020
|
+
if (this.client) this.load(TOOL_GROUP_NAMES);
|
|
3021
|
+
}
|
|
3022
|
+
/** Registers the groups not loaded yet; returns the tools each one added. */
|
|
3023
|
+
load(groups) {
|
|
3024
|
+
const client = this.client;
|
|
3025
|
+
const fresh = groups.filter((group2) => !this.loaded.has(group2));
|
|
3026
|
+
if (!client || fresh.length === 0) return {};
|
|
3027
|
+
const added = {};
|
|
3028
|
+
for (const group2 of fresh) added[group2] = [];
|
|
3029
|
+
const server = this.server;
|
|
3030
|
+
const recorder = {
|
|
3031
|
+
tool(name, description, shape, handler, annotations) {
|
|
3032
|
+
added[TOOL_CATALOG[name].group]?.push(name);
|
|
3033
|
+
return server.tool(name, description, shape, handler, annotations);
|
|
3034
|
+
}
|
|
3035
|
+
};
|
|
3036
|
+
this.handles.push(
|
|
3037
|
+
...registerTools(recorder, client, {
|
|
3038
|
+
groups: new Set(fresh),
|
|
3039
|
+
readOnly: this.selection.readOnly
|
|
3040
|
+
})
|
|
3041
|
+
);
|
|
3042
|
+
for (const group2 of fresh) this.loaded.add(group2);
|
|
3043
|
+
this.showEnableTools();
|
|
3044
|
+
return added;
|
|
3045
|
+
}
|
|
3046
|
+
/** (Re)registers enable_tools with the current groups, or drops it once all are loaded. */
|
|
3047
|
+
showEnableTools() {
|
|
3048
|
+
this.enableTools?.remove();
|
|
3049
|
+
this.enableTools = null;
|
|
3050
|
+
const left = EXTRA_GROUPS.filter((group2) => !this.loaded.has(group2));
|
|
3051
|
+
if (!this.client || left.length === 0) return;
|
|
3052
|
+
const lines = EXTRA_GROUPS.map(
|
|
3053
|
+
(group2) => `- ${group2}${this.loaded.has(group2) ? " (loaded)" : ""}: ${TOOL_GROUPS[group2]}`
|
|
3054
|
+
);
|
|
3055
|
+
const description = [
|
|
3056
|
+
"Load more webhooks.cc tools into this session. This server lists its core tools by default; the rest come in groups. If a tool you need is missing, load its group, then look at the tool list again.",
|
|
3057
|
+
"Groups:",
|
|
3058
|
+
...lines,
|
|
3059
|
+
this.selection.readOnly ? "This server is read-only (WHK_MCP_READ_ONLY): a group brings only its tools that change nothing." : null
|
|
3060
|
+
].filter(Boolean).join("\n");
|
|
3061
|
+
this.enableTools = this.server.tool(
|
|
3062
|
+
ENABLE_TOOLS,
|
|
3063
|
+
description,
|
|
3064
|
+
{
|
|
3065
|
+
groups: import_zod2.z.array(import_zod2.z.enum([...EXTRA_GROUPS, "all"])).min(1).describe('Groups to load, or ["all"]')
|
|
3066
|
+
},
|
|
3067
|
+
withErrorHandling(async ({ groups }) => {
|
|
3068
|
+
const wanted = groups.includes("all") ? [...TOOL_GROUP_NAMES] : groups;
|
|
3069
|
+
const already = wanted.filter((group2) => group2 !== "core" && this.loaded.has(group2));
|
|
3070
|
+
const added = this.load(wanted);
|
|
3071
|
+
const loadedNow = Object.keys(added).length > 0;
|
|
3072
|
+
return jsonContent({
|
|
3073
|
+
added,
|
|
3074
|
+
...already.length > 0 ? { alreadyLoaded: [...new Set(already)] } : {},
|
|
3075
|
+
loaded: this.loadedGroups(),
|
|
3076
|
+
...this.selection.readOnly ? { readOnly: true } : {},
|
|
3077
|
+
note: loadedNow ? "The tool list changed. If your client does not show the new tools, ask the user to set WHK_MCP_TOOLS (for example all) and restart the server." : "Nothing new to load."
|
|
3078
|
+
});
|
|
3079
|
+
}),
|
|
3080
|
+
ENABLE_TOOLS_ANNOTATIONS
|
|
3081
|
+
);
|
|
3082
|
+
}
|
|
3083
|
+
};
|
|
3084
|
+
function modernClientName(message) {
|
|
3085
|
+
if (typeof message !== "object" || message === null) return null;
|
|
3086
|
+
const params = message.params;
|
|
3087
|
+
const info = params?._meta?.[import_server.CLIENT_INFO_META_KEY];
|
|
3088
|
+
if (typeof info !== "object" || info === null) return null;
|
|
3089
|
+
const name = info.name;
|
|
3090
|
+
return typeof name === "string" ? name : void 0;
|
|
3091
|
+
}
|
|
3092
|
+
function watchClient(server, groups) {
|
|
3093
|
+
const low = server.server;
|
|
3094
|
+
const initialized = low.oninitialized;
|
|
3095
|
+
low.oninitialized = () => {
|
|
3096
|
+
initialized?.();
|
|
3097
|
+
groups.clientIdentified(low.getClientVersion()?.name);
|
|
3098
|
+
};
|
|
3099
|
+
const connect = server.connect.bind(server);
|
|
3100
|
+
server.connect = async (transport) => {
|
|
3101
|
+
const previous = transport.onmessage;
|
|
3102
|
+
transport.onmessage = (message, extra) => {
|
|
3103
|
+
const name = modernClientName(message);
|
|
3104
|
+
if (name !== null) groups.clientIdentified(name);
|
|
3105
|
+
previous?.call(transport, message, extra);
|
|
3106
|
+
};
|
|
3107
|
+
return connect(transport);
|
|
3108
|
+
};
|
|
3109
|
+
}
|
|
3110
|
+
function localToolGroups(server, selection) {
|
|
3111
|
+
const groups = new LocalToolGroups(asToolServer(server), selection);
|
|
3112
|
+
watchClient(server, groups);
|
|
3113
|
+
return groups;
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
// src/prompts.ts
|
|
3117
|
+
var import_zod3 = require("zod");
|
|
3118
|
+
function registerPrompts(server) {
|
|
3119
|
+
server.registerPrompt(
|
|
3120
|
+
"debug_webhook_delivery",
|
|
3121
|
+
{
|
|
3122
|
+
title: "Debug Webhook Delivery",
|
|
3123
|
+
description: "Guide an agent through diagnosing why webhook delivery is failing or missing.",
|
|
3124
|
+
argsSchema: import_zod3.z.object({
|
|
3125
|
+
provider: import_zod3.z.string().optional().describe("Webhook provider, if known"),
|
|
3126
|
+
endpointSlug: import_zod3.z.string().optional().describe("Hosted endpoint slug, if known"),
|
|
3127
|
+
targetUrl: import_zod3.z.string().optional().describe("Your app's receiving URL, if known")
|
|
3128
|
+
})
|
|
3129
|
+
},
|
|
3130
|
+
async ({ provider, endpointSlug, targetUrl }) => {
|
|
3131
|
+
const scope = [
|
|
3132
|
+
provider ? `Provider: ${provider}.` : null,
|
|
3133
|
+
endpointSlug ? `Endpoint slug: ${endpointSlug}.` : null,
|
|
3134
|
+
targetUrl ? `Target URL: ${targetUrl}.` : null
|
|
3135
|
+
].filter(Boolean).join(" ");
|
|
3136
|
+
return {
|
|
3137
|
+
description: "Diagnose a missing or broken webhook delivery.",
|
|
3138
|
+
messages: [
|
|
3139
|
+
{
|
|
3140
|
+
role: "user",
|
|
3141
|
+
content: {
|
|
3142
|
+
type: "text",
|
|
3143
|
+
text: [
|
|
3144
|
+
scope,
|
|
3145
|
+
"Diagnose webhook delivery step by step.",
|
|
3146
|
+
"Use list_endpoints or get_endpoint to confirm the endpoint and URL.",
|
|
3147
|
+
"Use list_requests, wait_for_request, or wait_for_requests to check whether anything arrived.",
|
|
3148
|
+
"If the provider is known, use preview_webhook or send_webhook to reproduce the webhook with realistic signing.",
|
|
3149
|
+
"Use verify_signature when a secret is available.",
|
|
3150
|
+
"Use compare_requests to compare retries or changed payloads.",
|
|
3151
|
+
"Conclude with the most likely cause and the next concrete fix."
|
|
3152
|
+
].filter(Boolean).join(" ")
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
]
|
|
3156
|
+
};
|
|
3157
|
+
}
|
|
3158
|
+
);
|
|
3159
|
+
server.registerPrompt(
|
|
3160
|
+
"setup_provider_testing",
|
|
2607
3161
|
{
|
|
2608
|
-
|
|
3162
|
+
title: "Setup Provider Testing",
|
|
3163
|
+
description: "Guide an agent through setting up webhook testing for a provider.",
|
|
3164
|
+
argsSchema: import_zod3.z.object({
|
|
3165
|
+
provider: import_zod3.z.string().describe("Webhook provider to test"),
|
|
3166
|
+
targetUrl: import_zod3.z.string().optional().describe("Optional local or remote target URL")
|
|
3167
|
+
})
|
|
2609
3168
|
},
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
3169
|
+
async ({ provider, targetUrl }) => {
|
|
3170
|
+
return {
|
|
3171
|
+
description: `Set up webhook testing for ${provider}.`,
|
|
3172
|
+
messages: [
|
|
3173
|
+
{
|
|
3174
|
+
role: "user",
|
|
3175
|
+
content: {
|
|
3176
|
+
type: "text",
|
|
3177
|
+
text: [
|
|
3178
|
+
`Set up webhook testing for ${provider}.`,
|
|
3179
|
+
"Use list_provider_templates to inspect supported templates and signing details first.",
|
|
3180
|
+
"Create an endpoint with create_endpoint, preferably ephemeral.",
|
|
3181
|
+
"If a target URL is provided, use preview_webhook before send_to so the request shape is visible.",
|
|
3182
|
+
targetUrl ? `Target URL: ${targetUrl}.` : null,
|
|
3183
|
+
"Send a realistic provider webhook, wait for capture, and verify the signature if a secret is available.",
|
|
3184
|
+
"Once real deliveries are captured, use generate_payload_schema to write the payload types for the handler.",
|
|
3185
|
+
"Return the endpoint URL, the exact tools used, and the next step for the developer."
|
|
3186
|
+
].filter(Boolean).join(" ")
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
]
|
|
3190
|
+
};
|
|
3191
|
+
}
|
|
2619
3192
|
);
|
|
2620
|
-
server.
|
|
2621
|
-
"
|
|
2622
|
-
"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.",
|
|
3193
|
+
server.registerPrompt(
|
|
3194
|
+
"compare_webhook_attempts",
|
|
2623
3195
|
{
|
|
2624
|
-
|
|
3196
|
+
title: "Compare Webhook Attempts",
|
|
3197
|
+
description: "Guide an agent through comparing two webhook deliveries or retries.",
|
|
3198
|
+
argsSchema: import_zod3.z.object({
|
|
3199
|
+
endpointSlug: import_zod3.z.string().optional().describe("Endpoint slug to inspect for recent attempts"),
|
|
3200
|
+
leftRequestId: import_zod3.z.string().optional().describe("First request ID, if already known"),
|
|
3201
|
+
rightRequestId: import_zod3.z.string().optional().describe("Second request ID, if already known")
|
|
3202
|
+
})
|
|
2625
3203
|
},
|
|
2626
|
-
|
|
2627
|
-
return
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
3204
|
+
async ({ endpointSlug, leftRequestId, rightRequestId }) => {
|
|
3205
|
+
return {
|
|
3206
|
+
description: "Compare two webhook deliveries and explain the difference.",
|
|
3207
|
+
messages: [
|
|
3208
|
+
{
|
|
3209
|
+
role: "user",
|
|
3210
|
+
content: {
|
|
3211
|
+
type: "text",
|
|
3212
|
+
text: [
|
|
3213
|
+
endpointSlug ? `Endpoint slug: ${endpointSlug}.` : null,
|
|
3214
|
+
leftRequestId && rightRequestId ? `Compare request ${leftRequestId} against ${rightRequestId}.` : "Find the most relevant two webhook attempts first.",
|
|
3215
|
+
"Use compare_requests for the structured diff.",
|
|
3216
|
+
"If request IDs are not provided, use list_requests or the endpoint recent resource to find the last two attempts.",
|
|
3217
|
+
"Explain what changed in the body, headers, or timing, and whether the difference looks expected, retried, or broken."
|
|
3218
|
+
].filter(Boolean).join(" ")
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
]
|
|
3222
|
+
};
|
|
3223
|
+
}
|
|
2637
3224
|
);
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
// src/resources.ts
|
|
3228
|
+
var import_server2 = require("@modelcontextprotocol/server");
|
|
3229
|
+
var ENDPOINTS_RESOURCE_URI = "webhooks://endpoints";
|
|
3230
|
+
var ENDPOINT_RECENT_TEMPLATE_URI = "webhooks://endpoint/{slug}/recent";
|
|
3231
|
+
var REQUEST_TEMPLATE_URI = "webhooks://request/{id}";
|
|
3232
|
+
var MAX_ENDPOINT_RESOURCE_ITEMS = 25;
|
|
3233
|
+
var RECENT_REQUEST_LIMIT = 10;
|
|
3234
|
+
function jsonResource(uri, value) {
|
|
3235
|
+
return {
|
|
3236
|
+
contents: [
|
|
3237
|
+
{
|
|
3238
|
+
uri,
|
|
3239
|
+
mimeType: "application/json",
|
|
3240
|
+
text: JSON.stringify(value, null, 2)
|
|
3241
|
+
}
|
|
3242
|
+
]
|
|
3243
|
+
};
|
|
3244
|
+
}
|
|
3245
|
+
function summarizeRequest(request) {
|
|
3246
|
+
return {
|
|
3247
|
+
id: request.id,
|
|
3248
|
+
method: request.method,
|
|
3249
|
+
path: request.path,
|
|
3250
|
+
receivedAt: request.receivedAt,
|
|
3251
|
+
contentType: request.contentType ?? null
|
|
3252
|
+
};
|
|
3253
|
+
}
|
|
3254
|
+
function variableToString(value, name) {
|
|
3255
|
+
if (typeof value === "string" && value.length > 0) {
|
|
3256
|
+
return value;
|
|
3257
|
+
}
|
|
3258
|
+
throw new Error(`Missing resource variable "${name}"`);
|
|
3259
|
+
}
|
|
3260
|
+
async function listEndpointSummaries(client) {
|
|
3261
|
+
const endpoints = (await client.endpoints.list()).slice().sort((left, right) => right.createdAt - left.createdAt);
|
|
3262
|
+
const truncated = endpoints.length > MAX_ENDPOINT_RESOURCE_ITEMS;
|
|
3263
|
+
const visible = endpoints.slice(0, MAX_ENDPOINT_RESOURCE_ITEMS);
|
|
3264
|
+
const summaries = await Promise.all(
|
|
3265
|
+
visible.map(async (endpoint) => {
|
|
3266
|
+
const recent = await client.requests.list(endpoint.slug, { limit: 1 });
|
|
3267
|
+
return {
|
|
3268
|
+
...endpoint,
|
|
3269
|
+
lastRequest: recent[0] ? summarizeRequest(recent[0]) : null
|
|
3270
|
+
};
|
|
2647
3271
|
})
|
|
2648
3272
|
);
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
3273
|
+
return {
|
|
3274
|
+
endpoints: summaries,
|
|
3275
|
+
total: endpoints.length,
|
|
3276
|
+
truncated
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3279
|
+
function registerResources(server, client) {
|
|
3280
|
+
server.registerResource(
|
|
3281
|
+
"endpoints-overview",
|
|
3282
|
+
ENDPOINTS_RESOURCE_URI,
|
|
2652
3283
|
{
|
|
2653
|
-
|
|
3284
|
+
title: "Endpoints Overview",
|
|
3285
|
+
description: "All endpoints with a summary of recent activity.",
|
|
3286
|
+
mimeType: "application/json"
|
|
2654
3287
|
},
|
|
2655
|
-
|
|
2656
|
-
await client
|
|
2657
|
-
|
|
2658
|
-
})
|
|
3288
|
+
async () => {
|
|
3289
|
+
return jsonResource(ENDPOINTS_RESOURCE_URI, await listEndpointSummaries(client));
|
|
3290
|
+
}
|
|
2659
3291
|
);
|
|
2660
|
-
server.
|
|
2661
|
-
"
|
|
2662
|
-
|
|
3292
|
+
server.registerResource(
|
|
3293
|
+
"endpoint-recent-requests",
|
|
3294
|
+
new import_server2.ResourceTemplate(ENDPOINT_RECENT_TEMPLATE_URI, {
|
|
3295
|
+
list: async () => {
|
|
3296
|
+
const endpoints = await client.endpoints.list();
|
|
3297
|
+
return {
|
|
3298
|
+
resources: endpoints.map((endpoint) => ({
|
|
3299
|
+
uri: `webhooks://endpoint/${endpoint.slug}/recent`,
|
|
3300
|
+
name: `${endpoint.slug} recent requests`,
|
|
3301
|
+
description: `Recent requests for endpoint ${endpoint.slug}`,
|
|
3302
|
+
mimeType: "application/json"
|
|
3303
|
+
}))
|
|
3304
|
+
};
|
|
3305
|
+
},
|
|
3306
|
+
complete: {
|
|
3307
|
+
slug: async (value) => {
|
|
3308
|
+
const endpoints = await client.endpoints.list();
|
|
3309
|
+
return endpoints.map((endpoint) => endpoint.slug).filter((slug) => slug.startsWith(value)).slice(0, 20);
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
}),
|
|
2663
3313
|
{
|
|
2664
|
-
|
|
2665
|
-
|
|
3314
|
+
title: "Endpoint Recent Requests",
|
|
3315
|
+
description: "Last 10 captured requests for an endpoint.",
|
|
3316
|
+
mimeType: "application/json"
|
|
2666
3317
|
},
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
3318
|
+
async (uri, variables) => {
|
|
3319
|
+
const slug = variableToString(variables.slug, "slug");
|
|
3320
|
+
const [endpoint, requests] = await Promise.all([
|
|
3321
|
+
client.endpoints.get(slug),
|
|
3322
|
+
client.requests.list(slug, { limit: RECENT_REQUEST_LIMIT })
|
|
3323
|
+
]);
|
|
3324
|
+
return jsonResource(uri.toString(), {
|
|
3325
|
+
endpoint,
|
|
3326
|
+
requests: requests.map(compactRequest)
|
|
3327
|
+
});
|
|
3328
|
+
}
|
|
2671
3329
|
);
|
|
2672
|
-
server.
|
|
2673
|
-
"
|
|
2674
|
-
|
|
3330
|
+
server.registerResource(
|
|
3331
|
+
"request-details",
|
|
3332
|
+
new import_server2.ResourceTemplate(REQUEST_TEMPLATE_URI, {
|
|
3333
|
+
list: void 0,
|
|
3334
|
+
complete: {}
|
|
3335
|
+
}),
|
|
2675
3336
|
{
|
|
2676
|
-
|
|
3337
|
+
title: "Request Details",
|
|
3338
|
+
description: "Full details for a captured request by ID.",
|
|
3339
|
+
mimeType: "application/json"
|
|
2677
3340
|
},
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
server.tool(
|
|
2684
|
-
"describe",
|
|
2685
|
-
"Describe all available SDK operations, parameters, and types.",
|
|
2686
|
-
{},
|
|
2687
|
-
withErrorHandling(async () => {
|
|
2688
|
-
const description = client.describe();
|
|
2689
|
-
return jsonContent(description);
|
|
2690
|
-
})
|
|
3341
|
+
async (uri, variables) => {
|
|
3342
|
+
const id = variableToString(variables.id, "id");
|
|
3343
|
+
const request = await client.requests.get(id);
|
|
3344
|
+
return jsonResource(uri.toString(), compactRequest(request));
|
|
3345
|
+
}
|
|
2691
3346
|
);
|
|
2692
|
-
return handles;
|
|
2693
3347
|
}
|
|
2694
3348
|
|
|
3349
|
+
// src/sandbox-tools.ts
|
|
3350
|
+
var import_zod4 = require("zod");
|
|
3351
|
+
var import_sdk2 = require("@webhooks-cc/sdk");
|
|
3352
|
+
|
|
3353
|
+
// src/version.ts
|
|
3354
|
+
var VERSION = true ? "2.0.0" : "0.0.0-dev";
|
|
3355
|
+
var MCP_USER_AGENT = `webhooks-cc-mcp/${VERSION}`;
|
|
3356
|
+
|
|
2695
3357
|
// src/sandbox-tools.ts
|
|
2696
3358
|
var SANDBOX = {
|
|
2697
3359
|
maxEndpoints: 3,
|
|
@@ -2806,6 +3468,8 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2806
3468
|
options.clientName ?? "webhooks-cc-mcp",
|
|
2807
3469
|
options.credentialsFile
|
|
2808
3470
|
);
|
|
3471
|
+
const target = asToolServer(server);
|
|
3472
|
+
const groups = options.toolGroups ?? localToolGroups(server, toolSelection(options));
|
|
2809
3473
|
let handles = [];
|
|
2810
3474
|
let shown = 0;
|
|
2811
3475
|
const swap = (register) => {
|
|
@@ -2813,7 +3477,9 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2813
3477
|
for (const handle of handles) handle.remove();
|
|
2814
3478
|
handles = register(id);
|
|
2815
3479
|
};
|
|
2816
|
-
const showSandbox = () => swap(
|
|
3480
|
+
const showSandbox = () => swap(
|
|
3481
|
+
() => sandboxToolSet(sandboxTarget(target, groups.selection.readOnly), session, showAccount, groups)
|
|
3482
|
+
);
|
|
2817
3483
|
const savedAccount = (id) => options.credentialsFile === false ? null : import_sdk2.WebhooksCC.fromSavedConnection({
|
|
2818
3484
|
baseUrl,
|
|
2819
3485
|
webhookUrl,
|
|
@@ -2826,18 +3492,54 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2826
3492
|
})
|
|
2827
3493
|
});
|
|
2828
3494
|
function showAccount(client) {
|
|
2829
|
-
swap((id) =>
|
|
3495
|
+
swap((id) => [groups.show(savedAccount(id) ?? client)]);
|
|
2830
3496
|
options.onConnected?.(client);
|
|
2831
3497
|
}
|
|
2832
3498
|
const saved = savedAccount(1);
|
|
2833
3499
|
if (saved) {
|
|
2834
|
-
swap(() =>
|
|
3500
|
+
swap(() => [groups.show(saved)]);
|
|
2835
3501
|
options.onConnected?.(saved);
|
|
2836
3502
|
} else {
|
|
2837
3503
|
showSandbox();
|
|
2838
3504
|
}
|
|
2839
3505
|
}
|
|
2840
|
-
|
|
3506
|
+
var SANDBOX_ONLY_TOOLS = {
|
|
3507
|
+
// A new code each time.
|
|
3508
|
+
connect_account: {
|
|
3509
|
+
readOnlyHint: false,
|
|
3510
|
+
destructiveHint: false,
|
|
3511
|
+
idempotentHint: false,
|
|
3512
|
+
openWorldHint: false
|
|
3513
|
+
},
|
|
3514
|
+
// Changes this session's tools once the human has connected.
|
|
3515
|
+
wait_for_connection: {
|
|
3516
|
+
readOnlyHint: false,
|
|
3517
|
+
destructiveHint: false,
|
|
3518
|
+
idempotentHint: true,
|
|
3519
|
+
openWorldHint: false
|
|
3520
|
+
},
|
|
3521
|
+
about_sandbox: {
|
|
3522
|
+
readOnlyHint: true,
|
|
3523
|
+
destructiveHint: false,
|
|
3524
|
+
idempotentHint: true,
|
|
3525
|
+
openWorldHint: false
|
|
3526
|
+
}
|
|
3527
|
+
};
|
|
3528
|
+
var CONNECT_TOOLS = /* @__PURE__ */ new Set(["connect_account", "wait_for_connection"]);
|
|
3529
|
+
function sandboxTarget(target, readOnly) {
|
|
3530
|
+
return {
|
|
3531
|
+
tool(name, description, shape, handler) {
|
|
3532
|
+
const own = SANDBOX_ONLY_TOOLS[name];
|
|
3533
|
+
const annotations = own ? { title: toolTitle(name), ...own } : toolAnnotations(name);
|
|
3534
|
+
if (readOnly && !annotations.readOnlyHint && !CONNECT_TOOLS.has(name)) {
|
|
3535
|
+
return { remove() {
|
|
3536
|
+
} };
|
|
3537
|
+
}
|
|
3538
|
+
return target.tool(name, description, shape, handler, annotations);
|
|
3539
|
+
}
|
|
3540
|
+
};
|
|
3541
|
+
}
|
|
3542
|
+
function sandboxToolSet(server, session, showAccount, groups) {
|
|
2841
3543
|
const tools = [];
|
|
2842
3544
|
const limits = `up to ${SANDBOX.maxEndpoints} endpoints at a time, ${SANDBOX.requestsPerEndpoint} captured requests each and ${SANDBOX.requests} in all, deleted ${SANDBOX.lifetimeHours} hours after the sandbox was created`;
|
|
2843
3545
|
const file = savedTo(session.credentialsFile);
|
|
@@ -2952,7 +3654,7 @@ function sandboxToolSet(server, session, showAccount) {
|
|
|
2952
3654
|
"connect_account",
|
|
2953
3655
|
"Connect this agent to a human's webhooks.cc account, for the full tool set (mock responses, signatures, email, teams) and to keep the sandbox endpoints. Returns a link and a 6-digit code: show both to the human, who signs in or signs up with that email and enters the code. Then call wait_for_connection. Calling again gives a new code.",
|
|
2954
3656
|
{
|
|
2955
|
-
email:
|
|
3657
|
+
email: import_zod4.z.string().email({ message: "Invalid email address" }).describe(
|
|
2956
3658
|
"The email the human signs in to webhooks.cc with (their GitHub or Google primary email if they sign in that way); only someone signed in as it can connect"
|
|
2957
3659
|
)
|
|
2958
3660
|
},
|
|
@@ -3005,7 +3707,7 @@ function sandboxToolSet(server, session, showAccount) {
|
|
|
3005
3707
|
"wait_for_connection",
|
|
3006
3708
|
`Wait for the human to enter the code from connect_account. On success the full webhooks.cc tools for their account replace these sandbox tools. Returns after timeoutSeconds (default ${DEFAULT_WAIT_SECONDS}) if they have not finished yet; call it again to keep waiting.`,
|
|
3007
3709
|
{
|
|
3008
|
-
timeoutSeconds:
|
|
3710
|
+
timeoutSeconds: import_zod4.z.number().int().min(1).max(MAX_WAIT_SECONDS).optional().describe(`How long to wait, in seconds (default ${DEFAULT_WAIT_SECONDS})`)
|
|
3009
3711
|
},
|
|
3010
3712
|
withErrorHandling(async ({ timeoutSeconds }) => {
|
|
3011
3713
|
const pending = session.connect;
|
|
@@ -3048,7 +3750,7 @@ function sandboxToolSet(server, session, showAccount) {
|
|
|
3048
3750
|
return jsonContent({
|
|
3049
3751
|
connected: true,
|
|
3050
3752
|
email: pending.email,
|
|
3051
|
-
message: `Connected to the webhooks.cc account of ${pending.email}. The
|
|
3753
|
+
message: `Connected to the webhooks.cc account of ${pending.email}. The account's tools have replaced the sandbox tools (the tool list changed; if your client does not pick that up, ask the user to restart it).${groups.loadedGroups().length < TOOL_GROUP_NAMES.length ? " More tools load in groups: see enable_tools." : ""}${movedEndpoints ? " The sandbox endpoints are now that account's endpoints, with what they captured." : ""} ${file ? `The connection is saved in ${file}, so later sessions, the whk CLI and the SDKs start connected; it lasts 90 days, and the human can remove it under Account, Connected agents.` : "This connection lasts while this MCP server runs; for a lasting setup, the human can create an API key under Account and set WHK_API_KEY."}`
|
|
3052
3754
|
});
|
|
3053
3755
|
})
|
|
3054
3756
|
)
|
|
@@ -3087,22 +3789,34 @@ function sandboxToolSet(server, session, showAccount) {
|
|
|
3087
3789
|
}
|
|
3088
3790
|
|
|
3089
3791
|
// src/index.ts
|
|
3792
|
+
var INSTRUCTIONS = "webhooks.cc captures webhooks and inbound email for testing. Start with create_endpoint, send to its URL (or point a provider at it), then wait_for_request and get_request to inspect what arrived. send_webhook sends a signed test webhook to an endpoint; verify_signature checks a captured request. With an account the tools come in groups: when a tool you need is missing, enable_tools loads its group (teams, domains, Google Sheets, billing and keys, and more).";
|
|
3090
3793
|
function createServer(options = {}) {
|
|
3091
3794
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
3092
3795
|
const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
|
|
3093
3796
|
const baseUrl = options.baseUrl ?? process.env.WHK_BASE_URL;
|
|
3094
|
-
const server = new
|
|
3095
|
-
{ name: "webhooks-cc", version: VERSION },
|
|
3096
|
-
|
|
3097
|
-
|
|
3797
|
+
const server = new import_server3.McpServer(
|
|
3798
|
+
{ name: "webhooks-cc", title: "webhooks.cc", version: VERSION },
|
|
3799
|
+
{
|
|
3800
|
+
capabilities: { tools: { listChanged: true } },
|
|
3801
|
+
instructions: INSTRUCTIONS,
|
|
3802
|
+
// Connecting an account or loading groups changes many tools at once:
|
|
3803
|
+
// one list_changed, not dozens.
|
|
3804
|
+
debouncedNotificationMethods: ["notifications/tools/list_changed"]
|
|
3805
|
+
}
|
|
3098
3806
|
);
|
|
3807
|
+
const groups = localToolGroups(server, toolSelection(options));
|
|
3099
3808
|
if (!apiKey) {
|
|
3100
|
-
registerSandboxTools(server, {
|
|
3809
|
+
registerSandboxTools(server, {
|
|
3810
|
+
baseUrl,
|
|
3811
|
+
webhookUrl,
|
|
3812
|
+
credentialsFile: options.credentialsFile,
|
|
3813
|
+
toolGroups: groups
|
|
3814
|
+
});
|
|
3101
3815
|
registerPrompts(server);
|
|
3102
3816
|
return server;
|
|
3103
3817
|
}
|
|
3104
3818
|
const client = new import_sdk3.WebhooksCC({ apiKey, webhookUrl, baseUrl, userAgent: MCP_USER_AGENT });
|
|
3105
|
-
|
|
3819
|
+
groups.show(client);
|
|
3106
3820
|
registerPrompts(server);
|
|
3107
3821
|
registerResources(server, client);
|
|
3108
3822
|
return server;
|
|
@@ -3388,7 +4102,7 @@ if (command === "setup") {
|
|
|
3388
4102
|
runSetup(args.slice(1));
|
|
3389
4103
|
} else if (command === "--help" || command === "-h") {
|
|
3390
4104
|
console.log(`
|
|
3391
|
-
@webhooks-cc/mcp
|
|
4105
|
+
@webhooks-cc/mcp: MCP server for webhooks.cc
|
|
3392
4106
|
|
|
3393
4107
|
Usage:
|
|
3394
4108
|
npx @webhooks-cc/mcp Start stdio MCP server
|
|
@@ -3403,6 +4117,11 @@ Environment:
|
|
|
3403
4117
|
~/.config/whk/agent.json, shared with the whk CLI and SDKs)
|
|
3404
4118
|
WHK_WEBHOOK_URL Custom webhook receiver URL (optional)
|
|
3405
4119
|
WHK_BASE_URL Custom API base URL (optional)
|
|
4120
|
+
WHK_MCP_TOOLS Tool groups to list: all, or core plus any of forwarding,
|
|
4121
|
+
layouts, extras, teams, account, domains, sheets, names
|
|
4122
|
+
(default: core, and all for Claude Code, Codex and
|
|
4123
|
+
ChatGPT; enable_tools loads more during a session)
|
|
4124
|
+
WHK_MCP_READ_ONLY 1 to list only the tools that change nothing
|
|
3406
4125
|
|
|
3407
4126
|
Setup:
|
|
3408
4127
|
npx @webhooks-cc/mcp setup claude-code
|
|
@@ -3422,10 +4141,8 @@ Get your API key at https://webhooks.cc/account/api-keys
|
|
|
3422
4141
|
console.error(`Unknown command: "${command}". Run with --help for usage.`);
|
|
3423
4142
|
process.exit(1);
|
|
3424
4143
|
} else {
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
console.error("Fatal:", error instanceof Error ? error.message : error);
|
|
3429
|
-
process.exit(1);
|
|
4144
|
+
(0, import_stdio.serveStdio)(() => createServer(), {
|
|
4145
|
+
// Out-of-band reports only, never what was sent; stdout is the protocol.
|
|
4146
|
+
onerror: (error) => console.error(`webhooks-cc-mcp: ${error.message}`)
|
|
3430
4147
|
});
|
|
3431
4148
|
}
|