@webhooks-cc/mcp 1.15.0 → 1.16.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 +8 -7
- package/dist/bin/mcp.js +163 -21
- package/dist/{chunk-RHQD2GRD.mjs → chunk-4LEPLCW5.mjs} +111 -11
- package/dist/index.js +112 -12
- package/dist/index.mjs +2 -2
- package/dist/tools.d.mts +5 -0
- package/dist/tools.d.ts +5 -0
- package/dist/tools.js +111 -11
- package/dist/tools.mjs +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
MCP server for [webhooks.cc](https://webhooks.cc). It gives coding agents direct access to webhook testing workflows: create endpoints, send signed test webhooks, inspect captures, compare attempts, verify signatures, and read recent webhook context through MCP resources.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The easiest way in is the [remote server](#remote-server) at `https://webhooks.cc/mcp`, for any client that can add a remote MCP server (Claude Code, Cursor, VS Code, Codex, claude.ai, ChatGPT): you approve it once in your browser, with no key and nothing to install. This package is the local server, for sends to a handler on your machine, its prompts and resources, a keyless sandbox, and CI. It works with Claude Code, Cursor, VS Code, Codex, Devin Desktop (formerly Windsurf), and Claude Desktop.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -27,13 +27,14 @@ For other agents, `npx skills add kroqdotdev/webhooks-cc` installs the same skil
|
|
|
27
27
|
|
|
28
28
|
`https://webhooks.cc/mcp` serves the same tools over streamable HTTP, with nothing to install. The client opens webhooks.cc in the browser once; the human signs in and clicks Connect (OAuth 2.1 with PKCE, no code to type), and can disconnect it later under Account, Connected agents.
|
|
29
29
|
|
|
30
|
-
- **
|
|
31
|
-
- **
|
|
32
|
-
- **Claude Code**: `claude mcp add --transport http webhooks-cc https://webhooks.cc/mcp`
|
|
30
|
+
- **Claude Code**: `claude mcp add -s user --transport http webhooks-cc https://webhooks.cc/mcp`, then `/mcp` in Claude Code to sign in
|
|
31
|
+
- **Codex**: `codex mcp add webhooks-cc --url https://webhooks.cc/mcp` (it opens the browser to sign in; if it does not, `codex mcp login webhooks-cc`)
|
|
33
32
|
- **Cursor** (`.cursor/mcp.json`): `{ "mcpServers": { "webhooks-cc": { "url": "https://webhooks.cc/mcp" } } }`
|
|
34
33
|
- **VS Code** (`.vscode/mcp.json`): `{ "servers": { "webhooks-cc": { "type": "http", "url": "https://webhooks.cc/mcp" } } }`
|
|
34
|
+
- **claude.ai and Claude Desktop**: Settings, Connectors, add a custom connector with the URL `https://webhooks.cc/mcp`
|
|
35
|
+
- **ChatGPT**: add it as a custom connector (developer mode)
|
|
35
36
|
|
|
36
|
-
A client that can send headers may use an API key instead: `Authorization: Bearer whcc_...`. The remote server has
|
|
37
|
+
A client that can send headers may use an API key instead: `Authorization: Bearer whcc_...`. The remote server has 45 tools: `send_to` and `replay_request` are left out and `targetUrl` is refused, because a hosted server cannot reach your machine, and waits are capped at 50 seconds. It needs an account (no sandbox), and serves tools only, without prompts or resources.
|
|
37
38
|
|
|
38
39
|
### One-click
|
|
39
40
|
|
|
@@ -56,7 +57,7 @@ npx @webhooks-cc/mcp setup claude-code
|
|
|
56
57
|
npx @webhooks-cc/mcp setup cursor
|
|
57
58
|
npx @webhooks-cc/mcp setup vscode
|
|
58
59
|
npx @webhooks-cc/mcp setup codex
|
|
59
|
-
npx @webhooks-cc/mcp setup windsurf
|
|
60
|
+
npx @webhooks-cc/mcp setup devin # Devin Desktop, formerly Windsurf ("windsurf" works too)
|
|
60
61
|
npx @webhooks-cc/mcp setup claude-desktop
|
|
61
62
|
```
|
|
62
63
|
|
|
@@ -82,7 +83,7 @@ Leave out `env` to start without a key. Get your API key at [webhooks.cc/account
|
|
|
82
83
|
|
|
83
84
|
## What it exposes
|
|
84
85
|
|
|
85
|
-
The server exposes `
|
|
86
|
+
The server exposes `47` tools, `3` prompts, and `3` resource surfaces with an API key, the same `47` tools and the prompts with a saved connection, and `11` sandbox tools otherwise. The remote server exposes `45` tools.
|
|
86
87
|
|
|
87
88
|
### Endpoint tools
|
|
88
89
|
|
package/dist/bin/mcp.js
CHANGED
|
@@ -191,6 +191,53 @@ function capLists(holder) {
|
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
|
+
var MAX_PARSED_ITEMS = 20;
|
|
195
|
+
var MAX_PARSED_VALUE = 200;
|
|
196
|
+
var MAX_PARSED_SHEETS = 10;
|
|
197
|
+
function trimParsed(holder) {
|
|
198
|
+
const parsed = holder.parsed;
|
|
199
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return;
|
|
200
|
+
const next = { ...parsed };
|
|
201
|
+
if (Array.isArray(next.lineItems)) {
|
|
202
|
+
if (next.lineItems.length > MAX_PARSED_ITEMS) {
|
|
203
|
+
next.lineItemsTotal = next.lineItems.length;
|
|
204
|
+
}
|
|
205
|
+
next.lineItems = next.lineItems.slice(0, MAX_PARSED_ITEMS).map(
|
|
206
|
+
(row) => Object.fromEntries(
|
|
207
|
+
Object.entries(row).map(([key, value]) => [
|
|
208
|
+
key,
|
|
209
|
+
typeof value === "string" ? sliceText(value, MAX_PARSED_VALUE) : value
|
|
210
|
+
])
|
|
211
|
+
)
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
if (next.fields && typeof next.fields === "object") {
|
|
215
|
+
next.fields = Object.fromEntries(
|
|
216
|
+
Object.entries(next.fields).map(([key, value]) => [
|
|
217
|
+
key,
|
|
218
|
+
typeof value === "string" ? sliceText(value, MAX_PARSED_VALUE) : value
|
|
219
|
+
])
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
if (Array.isArray(next.attachments)) {
|
|
223
|
+
next.attachments = next.attachments.map((attachment) => {
|
|
224
|
+
const { sheets, text, ...rest } = attachment;
|
|
225
|
+
return {
|
|
226
|
+
...rest,
|
|
227
|
+
...Array.isArray(sheets) ? {
|
|
228
|
+
sheets: sheets.slice(0, MAX_PARSED_SHEETS).map((sheet) => ({
|
|
229
|
+
name: sheet.name,
|
|
230
|
+
rowCount: sheet.rowCount
|
|
231
|
+
})),
|
|
232
|
+
...sheets.length > MAX_PARSED_SHEETS ? { sheetsTotal: sheets.length } : {}
|
|
233
|
+
} : {},
|
|
234
|
+
...typeof text === "string" ? { textLength: text.length } : {}
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
next.trimmed = "Line items, sheet rows and attachment text were cut to fit the output.";
|
|
239
|
+
holder.parsed = next;
|
|
240
|
+
}
|
|
194
241
|
function keepOnly(holder, keep) {
|
|
195
242
|
for (const key of Object.keys(holder)) {
|
|
196
243
|
if (!keep.includes(key)) delete holder[key];
|
|
@@ -256,6 +303,7 @@ function compactRequest(request) {
|
|
|
256
303
|
const root = { items: [compact], truncated: true, total: 1e3, returned: 1e3 };
|
|
257
304
|
const budget = MAX_OUTPUT - 128;
|
|
258
305
|
shrinkToFit(root, budget, [
|
|
306
|
+
() => trimParsed(compact),
|
|
259
307
|
() => omitHeaders(compact),
|
|
260
308
|
() => trimSpam(email),
|
|
261
309
|
() => capLists(email),
|
|
@@ -439,6 +487,34 @@ function conditionSchema(use) {
|
|
|
439
487
|
});
|
|
440
488
|
}
|
|
441
489
|
var ruleConditionSchema = conditionSchema("response");
|
|
490
|
+
var emailFieldSchema = import_zod2.z.object({
|
|
491
|
+
name: import_zod2.z.string().describe("Letters, digits and _, starting with a letter: {{ fields.name }}"),
|
|
492
|
+
from: import_zod2.z.enum(["subject", "text", "html", "from", "to", "header"]).describe("What it reads; html is the HTML as text"),
|
|
493
|
+
header: import_zod2.z.string().optional().describe('The header name, for from "header"'),
|
|
494
|
+
pattern: import_zod2.z.string().describe("RE2 regular expression; the first group is the value (no lookarounds)")
|
|
495
|
+
});
|
|
496
|
+
var layoutSchema = import_zod2.z.object({
|
|
497
|
+
id: import_zod2.z.string().describe("Stable id: 1 to 40 letters, digits, - or _"),
|
|
498
|
+
name: import_zod2.z.string().max(100),
|
|
499
|
+
enabled: import_zod2.z.boolean().optional().describe("Off: kept but never matched (default on)"),
|
|
500
|
+
match: import_zod2.z.object({
|
|
501
|
+
logic: import_zod2.z.enum(["and", "or"]).optional(),
|
|
502
|
+
conditions: import_zod2.z.array(conditionSchema("filter")).max(10)
|
|
503
|
+
}).optional().describe("When it applies, e.g. the sender or subject; no conditions: every email"),
|
|
504
|
+
fields: import_zod2.z.array(emailFieldSchema).max(20).optional().describe("Named values, as emailFields"),
|
|
505
|
+
lineItems: import_zod2.z.object({
|
|
506
|
+
from: import_zod2.z.enum(["text", "html", "attachment"]),
|
|
507
|
+
pattern: import_zod2.z.string().optional().describe(
|
|
508
|
+
"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+)"
|
|
509
|
+
),
|
|
510
|
+
start: import_zod2.z.string().optional().describe("text/html: search only after this (RE2)"),
|
|
511
|
+
end: import_zod2.z.string().optional().describe("text/html: and before this (RE2)"),
|
|
512
|
+
attachment: import_zod2.z.string().optional().describe("attachment: file name with * and ? (default: the first spreadsheet or CSV)"),
|
|
513
|
+
sheet: import_zod2.z.string().optional().describe("attachment: sheet name (default: the first)"),
|
|
514
|
+
columns: import_zod2.z.array(import_zod2.z.object({ name: import_zod2.z.string(), column: import_zod2.z.string() })).max(30).optional().describe("attachment: { name, column: header text or letter }; default: every column")
|
|
515
|
+
}).nullable().optional().describe("Rows such as an order's products"),
|
|
516
|
+
parseAttachments: import_zod2.z.boolean().optional().describe("Parse CSV, XLSX, ODS, PDF text and text files into the JSON; uses parsed pages")
|
|
517
|
+
});
|
|
442
518
|
var destinationFields = {
|
|
443
519
|
name: import_zod2.z.string().max(100).nullable().optional().describe("A name for the list and the log"),
|
|
444
520
|
enabled: import_zod2.z.boolean().optional().describe("Send to it (default true)"),
|
|
@@ -505,6 +581,9 @@ var mockResponseSchema = import_zod2.z.object({
|
|
|
505
581
|
`Response delay in milliseconds (${import_sdk.MOCK_RESPONSE_DELAY_MIN}-${import_sdk.MOCK_RESPONSE_DELAY_MAX}, default: none)`
|
|
506
582
|
)
|
|
507
583
|
});
|
|
584
|
+
var endpointSiteSchema = import_zod2.z.enum(["webhooks", "mailhooks", "both"]).describe(
|
|
585
|
+
"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."
|
|
586
|
+
);
|
|
508
587
|
var createEndpointShape = {
|
|
509
588
|
name: import_zod2.z.string().optional().describe("Display name for the endpoint"),
|
|
510
589
|
ephemeral: import_zod2.z.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
|
|
@@ -516,6 +595,7 @@ var createEndpointShape = {
|
|
|
516
595
|
notificationUrl: import_zod2.z.string().url().optional().describe(
|
|
517
596
|
"URL to POST a JSON summary to after each captured request (e.g. Slack/Discord webhook)"
|
|
518
597
|
),
|
|
598
|
+
site: endpointSiteSchema.optional(),
|
|
519
599
|
signingProvider: import_zod2.z.string().optional().describe(
|
|
520
600
|
"Signing provider for automatic signature verification (e.g. stripe, github, shopify)"
|
|
521
601
|
),
|
|
@@ -858,6 +938,7 @@ var ESSENTIAL_DETAIL_FIELDS = [
|
|
|
858
938
|
"subject",
|
|
859
939
|
"from",
|
|
860
940
|
"fields",
|
|
941
|
+
"parsed",
|
|
861
942
|
"replyText",
|
|
862
943
|
"size",
|
|
863
944
|
"test",
|
|
@@ -894,6 +975,7 @@ function emailDetail(client, email, view, includeHtml) {
|
|
|
894
975
|
...includeHtml && html !== null ? { html } : {}
|
|
895
976
|
};
|
|
896
977
|
shrinkToFit(detail, MAX_BODY_SIZE, [
|
|
978
|
+
() => trimParsed(detail),
|
|
897
979
|
() => omitHeaders(detail),
|
|
898
980
|
() => trimSpam(detail),
|
|
899
981
|
() => capExtracts(detail),
|
|
@@ -987,6 +1069,7 @@ function registerTools(target, client, options = {}) {
|
|
|
987
1069
|
mockResponse,
|
|
988
1070
|
responseRules,
|
|
989
1071
|
notificationUrl,
|
|
1072
|
+
site,
|
|
990
1073
|
signingProvider,
|
|
991
1074
|
signingSecret,
|
|
992
1075
|
signingHeader
|
|
@@ -1008,7 +1091,8 @@ function registerTools(target, client, options = {}) {
|
|
|
1008
1091
|
expiresIn,
|
|
1009
1092
|
mockResponse,
|
|
1010
1093
|
responseRules,
|
|
1011
|
-
notificationUrl
|
|
1094
|
+
notificationUrl,
|
|
1095
|
+
site
|
|
1012
1096
|
});
|
|
1013
1097
|
if (signingProvider && signingSecret) {
|
|
1014
1098
|
try {
|
|
@@ -1110,15 +1194,12 @@ function registerTools(target, client, options = {}) {
|
|
|
1110
1194
|
"Custom signature header name (only for generic-hmac provider), or null to clear"
|
|
1111
1195
|
),
|
|
1112
1196
|
showEmailExtracts: import_zod2.z.boolean().optional().describe("Pick codes and links out of emails (dashboard and forwarded JSON). Owner only."),
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
name: import_zod2.z.string().describe("Letters, digits and _, starting with a letter: {{ fields.name }}"),
|
|
1116
|
-
from: import_zod2.z.enum(["subject", "text", "html", "from", "to", "header"]).describe("What it reads; html is the HTML as text"),
|
|
1117
|
-
header: import_zod2.z.string().optional().describe('The header name, for from "header"'),
|
|
1118
|
-
pattern: import_zod2.z.string().describe("RE2 regular expression; the first group is the value (no lookarounds)")
|
|
1119
|
-
})
|
|
1120
|
-
).max(20).nullable().optional().describe(
|
|
1197
|
+
site: endpointSiteSchema.optional(),
|
|
1198
|
+
emailFields: import_zod2.z.array(emailFieldSchema).max(20).nullable().optional().describe(
|
|
1121
1199
|
"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."
|
|
1200
|
+
),
|
|
1201
|
+
layouts: import_zod2.z.array(layoutSchema).max(50).nullable().optional().describe(
|
|
1202
|
+
"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."
|
|
1122
1203
|
)
|
|
1123
1204
|
},
|
|
1124
1205
|
withErrorHandling(
|
|
@@ -1132,7 +1213,9 @@ function registerTools(target, client, options = {}) {
|
|
|
1132
1213
|
signingSecret,
|
|
1133
1214
|
signingHeader,
|
|
1134
1215
|
showEmailExtracts,
|
|
1135
|
-
|
|
1216
|
+
site,
|
|
1217
|
+
emailFields,
|
|
1218
|
+
layouts
|
|
1136
1219
|
}) => {
|
|
1137
1220
|
const endpoint = await client.endpoints.update(slug, {
|
|
1138
1221
|
name,
|
|
@@ -1143,12 +1226,29 @@ function registerTools(target, client, options = {}) {
|
|
|
1143
1226
|
signingSecret,
|
|
1144
1227
|
signingHeader,
|
|
1145
1228
|
showEmailExtracts,
|
|
1146
|
-
|
|
1229
|
+
site,
|
|
1230
|
+
emailFields,
|
|
1231
|
+
layouts
|
|
1147
1232
|
});
|
|
1148
1233
|
return jsonContent(endpoint);
|
|
1149
1234
|
}
|
|
1150
1235
|
)
|
|
1151
1236
|
);
|
|
1237
|
+
server.tool(
|
|
1238
|
+
"preview_layouts",
|
|
1239
|
+
"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.",
|
|
1240
|
+
{
|
|
1241
|
+
slug: import_zod2.z.string().describe("The endpoint slug"),
|
|
1242
|
+
requestId: import_zod2.z.string().optional().describe("A captured email on the endpoint (default: the newest)"),
|
|
1243
|
+
layouts: import_zod2.z.array(layoutSchema).max(50).optional().describe("Unsaved layouts to try")
|
|
1244
|
+
},
|
|
1245
|
+
withErrorHandling(async ({ slug, requestId, layouts }) => {
|
|
1246
|
+
const preview = await client.endpoints.previewLayouts(slug, { requestId, layouts });
|
|
1247
|
+
const holder = { ...preview };
|
|
1248
|
+
shrinkToFit(holder, MAX_BODY_SIZE, [() => trimParsed(holder)]);
|
|
1249
|
+
return jsonContent(holder);
|
|
1250
|
+
})
|
|
1251
|
+
);
|
|
1152
1252
|
server.tool(
|
|
1153
1253
|
"preview_response",
|
|
1154
1254
|
"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.",
|
|
@@ -2409,7 +2509,7 @@ function sandboxToolSet(server, session, showAccount) {
|
|
|
2409
2509
|
}
|
|
2410
2510
|
|
|
2411
2511
|
// src/index.ts
|
|
2412
|
-
var VERSION = true ? "1.
|
|
2512
|
+
var VERSION = true ? "1.16.0" : "0.0.0-dev";
|
|
2413
2513
|
function createServer(options = {}) {
|
|
2414
2514
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
2415
2515
|
const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
|
|
@@ -2436,7 +2536,15 @@ var import_fs = require("fs");
|
|
|
2436
2536
|
var import_path = require("path");
|
|
2437
2537
|
var import_child_process = require("child_process");
|
|
2438
2538
|
var import_os = require("os");
|
|
2439
|
-
var TOOLS = [
|
|
2539
|
+
var TOOLS = [
|
|
2540
|
+
"claude-code",
|
|
2541
|
+
"claude-desktop",
|
|
2542
|
+
"cursor",
|
|
2543
|
+
"vscode",
|
|
2544
|
+
"codex",
|
|
2545
|
+
"devin",
|
|
2546
|
+
"windsurf"
|
|
2547
|
+
];
|
|
2440
2548
|
var API_KEY_REGEX = /^whcc_[a-zA-Z0-9_-]+$/;
|
|
2441
2549
|
function validateApiKeyFormat(key) {
|
|
2442
2550
|
if (!API_KEY_REGEX.test(key)) {
|
|
@@ -2458,12 +2566,13 @@ function vscodeServerConfig(apiKey) {
|
|
|
2458
2566
|
function resolveApiKey(flags) {
|
|
2459
2567
|
return flags["api-key"] ?? process.env.WHK_API_KEY ?? null;
|
|
2460
2568
|
}
|
|
2461
|
-
function mergeJsonConfig(filePath, serverName, serverConfig, key = "mcpServers") {
|
|
2569
|
+
function mergeJsonConfig(filePath, serverName, serverConfig, key = "mcpServers", onInvalid = "replace") {
|
|
2462
2570
|
let existing = {};
|
|
2463
2571
|
if ((0, import_fs.existsSync)(filePath)) {
|
|
2464
2572
|
try {
|
|
2465
2573
|
existing = JSON.parse((0, import_fs.readFileSync)(filePath, "utf-8"));
|
|
2466
2574
|
} catch {
|
|
2575
|
+
if (onInvalid === "keep") return false;
|
|
2467
2576
|
console.warn(`Warning: ${filePath} contains invalid JSON and will be overwritten.`);
|
|
2468
2577
|
console.warn(`A backup has been saved to ${filePath}.bak`);
|
|
2469
2578
|
(0, import_fs.copyFileSync)(filePath, `${filePath}.bak`);
|
|
@@ -2475,6 +2584,7 @@ function mergeJsonConfig(filePath, serverName, serverConfig, key = "mcpServers")
|
|
|
2475
2584
|
const dir = (0, import_path.dirname)(filePath);
|
|
2476
2585
|
if (!(0, import_fs.existsSync)(dir)) (0, import_fs.mkdirSync)(dir, { recursive: true });
|
|
2477
2586
|
(0, import_fs.writeFileSync)(filePath, JSON.stringify(existing, null, 2) + "\n");
|
|
2587
|
+
return true;
|
|
2478
2588
|
}
|
|
2479
2589
|
function getClaudeDesktopConfigPath() {
|
|
2480
2590
|
const os = (0, import_os.platform)();
|
|
@@ -2577,11 +2687,41 @@ WHK_API_KEY = "<your-key>"
|
|
|
2577
2687
|
process.exit(1);
|
|
2578
2688
|
}
|
|
2579
2689
|
}
|
|
2580
|
-
function
|
|
2581
|
-
|
|
2582
|
-
|
|
2690
|
+
function devinMcpConfigPath(os = (0, import_os.platform)(), home = (0, import_os.homedir)(), env = process.env) {
|
|
2691
|
+
if (os === "win32") {
|
|
2692
|
+
const base2 = env.APPDATA || import_path.win32.join(home, "AppData", "Roaming");
|
|
2693
|
+
return import_path.win32.join(base2, "devin", "mcp_config.json");
|
|
2694
|
+
}
|
|
2695
|
+
const xdg = env.XDG_CONFIG_HOME;
|
|
2696
|
+
const base = xdg && import_path.posix.isAbsolute(xdg) ? xdg : import_path.posix.join(home, ".config");
|
|
2697
|
+
return import_path.posix.join(base, "devin", "mcp_config.json");
|
|
2698
|
+
}
|
|
2699
|
+
function legacyWindsurfConfigPath(home = (0, import_os.homedir)()) {
|
|
2700
|
+
return (0, import_path.join)(home, ".codeium", "windsurf", "mcp_config.json");
|
|
2701
|
+
}
|
|
2702
|
+
function setupDevin(apiKey) {
|
|
2703
|
+
const config = mcpServerConfig(apiKey);
|
|
2704
|
+
const configPath = devinMcpConfigPath();
|
|
2705
|
+
if (!mergeJsonConfig(configPath, "webhooks-cc", config, "mcpServers", "keep")) {
|
|
2706
|
+
console.error(
|
|
2707
|
+
`${configPath} is not plain JSON (it may hold comments), so it was left as it is.`
|
|
2708
|
+
);
|
|
2709
|
+
console.error(`Add this under "mcpServers" yourself:
|
|
2710
|
+
`);
|
|
2711
|
+
const entry = mcpServerConfig(apiKey ? "<your-key>" : null);
|
|
2712
|
+
console.error(`"webhooks-cc": ${JSON.stringify(entry, null, 2)}`);
|
|
2713
|
+
process.exit(1);
|
|
2714
|
+
}
|
|
2583
2715
|
console.log(`Done! Config written to ${configPath}`);
|
|
2584
|
-
|
|
2716
|
+
const legacyPath = legacyWindsurfConfigPath();
|
|
2717
|
+
if ((0, import_fs.existsSync)((0, import_path.dirname)(legacyPath))) {
|
|
2718
|
+
if (mergeJsonConfig(legacyPath, "webhooks-cc", config, "mcpServers", "keep")) {
|
|
2719
|
+
console.log(`Also written to ${legacyPath}, for Windsurf from before the rename.`);
|
|
2720
|
+
} else {
|
|
2721
|
+
console.warn(`Left ${legacyPath} as it is: it is not plain JSON.`);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
console.log("Restart Devin Desktop (or Windsurf) for changes to take effect.");
|
|
2585
2725
|
}
|
|
2586
2726
|
function printUsage() {
|
|
2587
2727
|
console.log(`
|
|
@@ -2599,7 +2739,8 @@ Tools:
|
|
|
2599
2739
|
cursor Cursor editor (writes .cursor/mcp.json)
|
|
2600
2740
|
vscode VS Code Copilot (writes .vscode/mcp.json)
|
|
2601
2741
|
codex OpenAI Codex CLI (runs 'codex mcp add')
|
|
2602
|
-
|
|
2742
|
+
devin Devin Desktop (formerly Windsurf) and the Devin CLI
|
|
2743
|
+
windsurf The same as devin
|
|
2603
2744
|
|
|
2604
2745
|
Options:
|
|
2605
2746
|
--api-key <key> Your webhooks.cc API key (or set WHK_API_KEY env var); optional
|
|
@@ -2656,8 +2797,9 @@ function runSetup(args2) {
|
|
|
2656
2797
|
case "codex":
|
|
2657
2798
|
setupCodex(apiKey);
|
|
2658
2799
|
break;
|
|
2800
|
+
case "devin":
|
|
2659
2801
|
case "windsurf":
|
|
2660
|
-
|
|
2802
|
+
setupDevin(apiKey);
|
|
2661
2803
|
break;
|
|
2662
2804
|
}
|
|
2663
2805
|
}
|
|
@@ -2690,7 +2832,7 @@ Setup:
|
|
|
2690
2832
|
npx @webhooks-cc/mcp setup cursor
|
|
2691
2833
|
npx @webhooks-cc/mcp setup vscode
|
|
2692
2834
|
npx @webhooks-cc/mcp setup codex
|
|
2693
|
-
npx @webhooks-cc/mcp setup
|
|
2835
|
+
npx @webhooks-cc/mcp setup devin
|
|
2694
2836
|
npx @webhooks-cc/mcp setup claude-desktop
|
|
2695
2837
|
(add --api-key whcc_... to start with your account's tools)
|
|
2696
2838
|
|
|
@@ -100,6 +100,53 @@ function capLists(holder) {
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
+
var MAX_PARSED_ITEMS = 20;
|
|
104
|
+
var MAX_PARSED_VALUE = 200;
|
|
105
|
+
var MAX_PARSED_SHEETS = 10;
|
|
106
|
+
function trimParsed(holder) {
|
|
107
|
+
const parsed = holder.parsed;
|
|
108
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return;
|
|
109
|
+
const next = { ...parsed };
|
|
110
|
+
if (Array.isArray(next.lineItems)) {
|
|
111
|
+
if (next.lineItems.length > MAX_PARSED_ITEMS) {
|
|
112
|
+
next.lineItemsTotal = next.lineItems.length;
|
|
113
|
+
}
|
|
114
|
+
next.lineItems = next.lineItems.slice(0, MAX_PARSED_ITEMS).map(
|
|
115
|
+
(row) => Object.fromEntries(
|
|
116
|
+
Object.entries(row).map(([key, value]) => [
|
|
117
|
+
key,
|
|
118
|
+
typeof value === "string" ? sliceText(value, MAX_PARSED_VALUE) : value
|
|
119
|
+
])
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
if (next.fields && typeof next.fields === "object") {
|
|
124
|
+
next.fields = Object.fromEntries(
|
|
125
|
+
Object.entries(next.fields).map(([key, value]) => [
|
|
126
|
+
key,
|
|
127
|
+
typeof value === "string" ? sliceText(value, MAX_PARSED_VALUE) : value
|
|
128
|
+
])
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (Array.isArray(next.attachments)) {
|
|
132
|
+
next.attachments = next.attachments.map((attachment) => {
|
|
133
|
+
const { sheets, text, ...rest } = attachment;
|
|
134
|
+
return {
|
|
135
|
+
...rest,
|
|
136
|
+
...Array.isArray(sheets) ? {
|
|
137
|
+
sheets: sheets.slice(0, MAX_PARSED_SHEETS).map((sheet) => ({
|
|
138
|
+
name: sheet.name,
|
|
139
|
+
rowCount: sheet.rowCount
|
|
140
|
+
})),
|
|
141
|
+
...sheets.length > MAX_PARSED_SHEETS ? { sheetsTotal: sheets.length } : {}
|
|
142
|
+
} : {},
|
|
143
|
+
...typeof text === "string" ? { textLength: text.length } : {}
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
next.trimmed = "Line items, sheet rows and attachment text were cut to fit the output.";
|
|
148
|
+
holder.parsed = next;
|
|
149
|
+
}
|
|
103
150
|
function keepOnly(holder, keep) {
|
|
104
151
|
for (const key of Object.keys(holder)) {
|
|
105
152
|
if (!keep.includes(key)) delete holder[key];
|
|
@@ -165,6 +212,7 @@ function compactRequest(request) {
|
|
|
165
212
|
const root = { items: [compact], truncated: true, total: 1e3, returned: 1e3 };
|
|
166
213
|
const budget = MAX_OUTPUT - 128;
|
|
167
214
|
shrinkToFit(root, budget, [
|
|
215
|
+
() => trimParsed(compact),
|
|
168
216
|
() => omitHeaders(compact),
|
|
169
217
|
() => trimSpam(email),
|
|
170
218
|
() => capLists(email),
|
|
@@ -221,6 +269,34 @@ function conditionSchema(use) {
|
|
|
221
269
|
});
|
|
222
270
|
}
|
|
223
271
|
var ruleConditionSchema = conditionSchema("response");
|
|
272
|
+
var emailFieldSchema = z.object({
|
|
273
|
+
name: z.string().describe("Letters, digits and _, starting with a letter: {{ fields.name }}"),
|
|
274
|
+
from: z.enum(["subject", "text", "html", "from", "to", "header"]).describe("What it reads; html is the HTML as text"),
|
|
275
|
+
header: z.string().optional().describe('The header name, for from "header"'),
|
|
276
|
+
pattern: z.string().describe("RE2 regular expression; the first group is the value (no lookarounds)")
|
|
277
|
+
});
|
|
278
|
+
var layoutSchema = z.object({
|
|
279
|
+
id: z.string().describe("Stable id: 1 to 40 letters, digits, - or _"),
|
|
280
|
+
name: z.string().max(100),
|
|
281
|
+
enabled: z.boolean().optional().describe("Off: kept but never matched (default on)"),
|
|
282
|
+
match: z.object({
|
|
283
|
+
logic: z.enum(["and", "or"]).optional(),
|
|
284
|
+
conditions: z.array(conditionSchema("filter")).max(10)
|
|
285
|
+
}).optional().describe("When it applies, e.g. the sender or subject; no conditions: every email"),
|
|
286
|
+
fields: z.array(emailFieldSchema).max(20).optional().describe("Named values, as emailFields"),
|
|
287
|
+
lineItems: z.object({
|
|
288
|
+
from: z.enum(["text", "html", "attachment"]),
|
|
289
|
+
pattern: z.string().optional().describe(
|
|
290
|
+
"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+)"
|
|
291
|
+
),
|
|
292
|
+
start: z.string().optional().describe("text/html: search only after this (RE2)"),
|
|
293
|
+
end: z.string().optional().describe("text/html: and before this (RE2)"),
|
|
294
|
+
attachment: z.string().optional().describe("attachment: file name with * and ? (default: the first spreadsheet or CSV)"),
|
|
295
|
+
sheet: z.string().optional().describe("attachment: sheet name (default: the first)"),
|
|
296
|
+
columns: z.array(z.object({ name: z.string(), column: z.string() })).max(30).optional().describe("attachment: { name, column: header text or letter }; default: every column")
|
|
297
|
+
}).nullable().optional().describe("Rows such as an order's products"),
|
|
298
|
+
parseAttachments: z.boolean().optional().describe("Parse CSV, XLSX, ODS, PDF text and text files into the JSON; uses parsed pages")
|
|
299
|
+
});
|
|
224
300
|
var destinationFields = {
|
|
225
301
|
name: z.string().max(100).nullable().optional().describe("A name for the list and the log"),
|
|
226
302
|
enabled: z.boolean().optional().describe("Send to it (default true)"),
|
|
@@ -287,6 +363,9 @@ var mockResponseSchema = z.object({
|
|
|
287
363
|
`Response delay in milliseconds (${MOCK_RESPONSE_DELAY_MIN}-${MOCK_RESPONSE_DELAY_MAX}, default: none)`
|
|
288
364
|
)
|
|
289
365
|
});
|
|
366
|
+
var endpointSiteSchema = z.enum(["webhooks", "mailhooks", "both"]).describe(
|
|
367
|
+
"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."
|
|
368
|
+
);
|
|
290
369
|
var createEndpointShape = {
|
|
291
370
|
name: z.string().optional().describe("Display name for the endpoint"),
|
|
292
371
|
ephemeral: z.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
|
|
@@ -298,6 +377,7 @@ var createEndpointShape = {
|
|
|
298
377
|
notificationUrl: z.string().url().optional().describe(
|
|
299
378
|
"URL to POST a JSON summary to after each captured request (e.g. Slack/Discord webhook)"
|
|
300
379
|
),
|
|
380
|
+
site: endpointSiteSchema.optional(),
|
|
301
381
|
signingProvider: z.string().optional().describe(
|
|
302
382
|
"Signing provider for automatic signature verification (e.g. stripe, github, shopify)"
|
|
303
383
|
),
|
|
@@ -640,6 +720,7 @@ var ESSENTIAL_DETAIL_FIELDS = [
|
|
|
640
720
|
"subject",
|
|
641
721
|
"from",
|
|
642
722
|
"fields",
|
|
723
|
+
"parsed",
|
|
643
724
|
"replyText",
|
|
644
725
|
"size",
|
|
645
726
|
"test",
|
|
@@ -676,6 +757,7 @@ function emailDetail(client, email, view, includeHtml) {
|
|
|
676
757
|
...includeHtml && html !== null ? { html } : {}
|
|
677
758
|
};
|
|
678
759
|
shrinkToFit(detail, MAX_BODY_SIZE, [
|
|
760
|
+
() => trimParsed(detail),
|
|
679
761
|
() => omitHeaders(detail),
|
|
680
762
|
() => trimSpam(detail),
|
|
681
763
|
() => capExtracts(detail),
|
|
@@ -769,6 +851,7 @@ function registerTools(target, client, options = {}) {
|
|
|
769
851
|
mockResponse,
|
|
770
852
|
responseRules,
|
|
771
853
|
notificationUrl,
|
|
854
|
+
site,
|
|
772
855
|
signingProvider,
|
|
773
856
|
signingSecret,
|
|
774
857
|
signingHeader
|
|
@@ -790,7 +873,8 @@ function registerTools(target, client, options = {}) {
|
|
|
790
873
|
expiresIn,
|
|
791
874
|
mockResponse,
|
|
792
875
|
responseRules,
|
|
793
|
-
notificationUrl
|
|
876
|
+
notificationUrl,
|
|
877
|
+
site
|
|
794
878
|
});
|
|
795
879
|
if (signingProvider && signingSecret) {
|
|
796
880
|
try {
|
|
@@ -892,15 +976,12 @@ function registerTools(target, client, options = {}) {
|
|
|
892
976
|
"Custom signature header name (only for generic-hmac provider), or null to clear"
|
|
893
977
|
),
|
|
894
978
|
showEmailExtracts: z.boolean().optional().describe("Pick codes and links out of emails (dashboard and forwarded JSON). Owner only."),
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
name: z.string().describe("Letters, digits and _, starting with a letter: {{ fields.name }}"),
|
|
898
|
-
from: z.enum(["subject", "text", "html", "from", "to", "header"]).describe("What it reads; html is the HTML as text"),
|
|
899
|
-
header: z.string().optional().describe('The header name, for from "header"'),
|
|
900
|
-
pattern: z.string().describe("RE2 regular expression; the first group is the value (no lookarounds)")
|
|
901
|
-
})
|
|
902
|
-
).max(20).nullable().optional().describe(
|
|
979
|
+
site: endpointSiteSchema.optional(),
|
|
980
|
+
emailFields: z.array(emailFieldSchema).max(20).nullable().optional().describe(
|
|
903
981
|
"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."
|
|
982
|
+
),
|
|
983
|
+
layouts: z.array(layoutSchema).max(50).nullable().optional().describe(
|
|
984
|
+
"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."
|
|
904
985
|
)
|
|
905
986
|
},
|
|
906
987
|
withErrorHandling(
|
|
@@ -914,7 +995,9 @@ function registerTools(target, client, options = {}) {
|
|
|
914
995
|
signingSecret,
|
|
915
996
|
signingHeader,
|
|
916
997
|
showEmailExtracts,
|
|
917
|
-
|
|
998
|
+
site,
|
|
999
|
+
emailFields,
|
|
1000
|
+
layouts
|
|
918
1001
|
}) => {
|
|
919
1002
|
const endpoint = await client.endpoints.update(slug, {
|
|
920
1003
|
name,
|
|
@@ -925,12 +1008,29 @@ function registerTools(target, client, options = {}) {
|
|
|
925
1008
|
signingSecret,
|
|
926
1009
|
signingHeader,
|
|
927
1010
|
showEmailExtracts,
|
|
928
|
-
|
|
1011
|
+
site,
|
|
1012
|
+
emailFields,
|
|
1013
|
+
layouts
|
|
929
1014
|
});
|
|
930
1015
|
return jsonContent(endpoint);
|
|
931
1016
|
}
|
|
932
1017
|
)
|
|
933
1018
|
);
|
|
1019
|
+
server.tool(
|
|
1020
|
+
"preview_layouts",
|
|
1021
|
+
"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.",
|
|
1022
|
+
{
|
|
1023
|
+
slug: z.string().describe("The endpoint slug"),
|
|
1024
|
+
requestId: z.string().optional().describe("A captured email on the endpoint (default: the newest)"),
|
|
1025
|
+
layouts: z.array(layoutSchema).max(50).optional().describe("Unsaved layouts to try")
|
|
1026
|
+
},
|
|
1027
|
+
withErrorHandling(async ({ slug, requestId, layouts }) => {
|
|
1028
|
+
const preview = await client.endpoints.previewLayouts(slug, { requestId, layouts });
|
|
1029
|
+
const holder = { ...preview };
|
|
1030
|
+
shrinkToFit(holder, MAX_BODY_SIZE, [() => trimParsed(holder)]);
|
|
1031
|
+
return jsonContent(holder);
|
|
1032
|
+
})
|
|
1033
|
+
);
|
|
934
1034
|
server.tool(
|
|
935
1035
|
"preview_response",
|
|
936
1036
|
"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.",
|
package/dist/index.js
CHANGED
|
@@ -215,6 +215,53 @@ function capLists(holder) {
|
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
|
+
var MAX_PARSED_ITEMS = 20;
|
|
219
|
+
var MAX_PARSED_VALUE = 200;
|
|
220
|
+
var MAX_PARSED_SHEETS = 10;
|
|
221
|
+
function trimParsed(holder) {
|
|
222
|
+
const parsed = holder.parsed;
|
|
223
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return;
|
|
224
|
+
const next = { ...parsed };
|
|
225
|
+
if (Array.isArray(next.lineItems)) {
|
|
226
|
+
if (next.lineItems.length > MAX_PARSED_ITEMS) {
|
|
227
|
+
next.lineItemsTotal = next.lineItems.length;
|
|
228
|
+
}
|
|
229
|
+
next.lineItems = next.lineItems.slice(0, MAX_PARSED_ITEMS).map(
|
|
230
|
+
(row) => Object.fromEntries(
|
|
231
|
+
Object.entries(row).map(([key, value]) => [
|
|
232
|
+
key,
|
|
233
|
+
typeof value === "string" ? sliceText(value, MAX_PARSED_VALUE) : value
|
|
234
|
+
])
|
|
235
|
+
)
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
if (next.fields && typeof next.fields === "object") {
|
|
239
|
+
next.fields = Object.fromEntries(
|
|
240
|
+
Object.entries(next.fields).map(([key, value]) => [
|
|
241
|
+
key,
|
|
242
|
+
typeof value === "string" ? sliceText(value, MAX_PARSED_VALUE) : value
|
|
243
|
+
])
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
if (Array.isArray(next.attachments)) {
|
|
247
|
+
next.attachments = next.attachments.map((attachment) => {
|
|
248
|
+
const { sheets, text, ...rest } = attachment;
|
|
249
|
+
return {
|
|
250
|
+
...rest,
|
|
251
|
+
...Array.isArray(sheets) ? {
|
|
252
|
+
sheets: sheets.slice(0, MAX_PARSED_SHEETS).map((sheet) => ({
|
|
253
|
+
name: sheet.name,
|
|
254
|
+
rowCount: sheet.rowCount
|
|
255
|
+
})),
|
|
256
|
+
...sheets.length > MAX_PARSED_SHEETS ? { sheetsTotal: sheets.length } : {}
|
|
257
|
+
} : {},
|
|
258
|
+
...typeof text === "string" ? { textLength: text.length } : {}
|
|
259
|
+
};
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
next.trimmed = "Line items, sheet rows and attachment text were cut to fit the output.";
|
|
263
|
+
holder.parsed = next;
|
|
264
|
+
}
|
|
218
265
|
function keepOnly(holder, keep) {
|
|
219
266
|
for (const key of Object.keys(holder)) {
|
|
220
267
|
if (!keep.includes(key)) delete holder[key];
|
|
@@ -280,6 +327,7 @@ function compactRequest(request) {
|
|
|
280
327
|
const root = { items: [compact], truncated: true, total: 1e3, returned: 1e3 };
|
|
281
328
|
const budget = MAX_OUTPUT - 128;
|
|
282
329
|
shrinkToFit(root, budget, [
|
|
330
|
+
() => trimParsed(compact),
|
|
283
331
|
() => omitHeaders(compact),
|
|
284
332
|
() => trimSpam(email),
|
|
285
333
|
() => capLists(email),
|
|
@@ -463,6 +511,34 @@ function conditionSchema(use) {
|
|
|
463
511
|
});
|
|
464
512
|
}
|
|
465
513
|
var ruleConditionSchema = conditionSchema("response");
|
|
514
|
+
var emailFieldSchema = import_zod2.z.object({
|
|
515
|
+
name: import_zod2.z.string().describe("Letters, digits and _, starting with a letter: {{ fields.name }}"),
|
|
516
|
+
from: import_zod2.z.enum(["subject", "text", "html", "from", "to", "header"]).describe("What it reads; html is the HTML as text"),
|
|
517
|
+
header: import_zod2.z.string().optional().describe('The header name, for from "header"'),
|
|
518
|
+
pattern: import_zod2.z.string().describe("RE2 regular expression; the first group is the value (no lookarounds)")
|
|
519
|
+
});
|
|
520
|
+
var layoutSchema = import_zod2.z.object({
|
|
521
|
+
id: import_zod2.z.string().describe("Stable id: 1 to 40 letters, digits, - or _"),
|
|
522
|
+
name: import_zod2.z.string().max(100),
|
|
523
|
+
enabled: import_zod2.z.boolean().optional().describe("Off: kept but never matched (default on)"),
|
|
524
|
+
match: import_zod2.z.object({
|
|
525
|
+
logic: import_zod2.z.enum(["and", "or"]).optional(),
|
|
526
|
+
conditions: import_zod2.z.array(conditionSchema("filter")).max(10)
|
|
527
|
+
}).optional().describe("When it applies, e.g. the sender or subject; no conditions: every email"),
|
|
528
|
+
fields: import_zod2.z.array(emailFieldSchema).max(20).optional().describe("Named values, as emailFields"),
|
|
529
|
+
lineItems: import_zod2.z.object({
|
|
530
|
+
from: import_zod2.z.enum(["text", "html", "attachment"]),
|
|
531
|
+
pattern: import_zod2.z.string().optional().describe(
|
|
532
|
+
"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+)"
|
|
533
|
+
),
|
|
534
|
+
start: import_zod2.z.string().optional().describe("text/html: search only after this (RE2)"),
|
|
535
|
+
end: import_zod2.z.string().optional().describe("text/html: and before this (RE2)"),
|
|
536
|
+
attachment: import_zod2.z.string().optional().describe("attachment: file name with * and ? (default: the first spreadsheet or CSV)"),
|
|
537
|
+
sheet: import_zod2.z.string().optional().describe("attachment: sheet name (default: the first)"),
|
|
538
|
+
columns: import_zod2.z.array(import_zod2.z.object({ name: import_zod2.z.string(), column: import_zod2.z.string() })).max(30).optional().describe("attachment: { name, column: header text or letter }; default: every column")
|
|
539
|
+
}).nullable().optional().describe("Rows such as an order's products"),
|
|
540
|
+
parseAttachments: import_zod2.z.boolean().optional().describe("Parse CSV, XLSX, ODS, PDF text and text files into the JSON; uses parsed pages")
|
|
541
|
+
});
|
|
466
542
|
var destinationFields = {
|
|
467
543
|
name: import_zod2.z.string().max(100).nullable().optional().describe("A name for the list and the log"),
|
|
468
544
|
enabled: import_zod2.z.boolean().optional().describe("Send to it (default true)"),
|
|
@@ -529,6 +605,9 @@ var mockResponseSchema = import_zod2.z.object({
|
|
|
529
605
|
`Response delay in milliseconds (${import_sdk.MOCK_RESPONSE_DELAY_MIN}-${import_sdk.MOCK_RESPONSE_DELAY_MAX}, default: none)`
|
|
530
606
|
)
|
|
531
607
|
});
|
|
608
|
+
var endpointSiteSchema = import_zod2.z.enum(["webhooks", "mailhooks", "both"]).describe(
|
|
609
|
+
"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."
|
|
610
|
+
);
|
|
532
611
|
var createEndpointShape = {
|
|
533
612
|
name: import_zod2.z.string().optional().describe("Display name for the endpoint"),
|
|
534
613
|
ephemeral: import_zod2.z.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
|
|
@@ -540,6 +619,7 @@ var createEndpointShape = {
|
|
|
540
619
|
notificationUrl: import_zod2.z.string().url().optional().describe(
|
|
541
620
|
"URL to POST a JSON summary to after each captured request (e.g. Slack/Discord webhook)"
|
|
542
621
|
),
|
|
622
|
+
site: endpointSiteSchema.optional(),
|
|
543
623
|
signingProvider: import_zod2.z.string().optional().describe(
|
|
544
624
|
"Signing provider for automatic signature verification (e.g. stripe, github, shopify)"
|
|
545
625
|
),
|
|
@@ -882,6 +962,7 @@ var ESSENTIAL_DETAIL_FIELDS = [
|
|
|
882
962
|
"subject",
|
|
883
963
|
"from",
|
|
884
964
|
"fields",
|
|
965
|
+
"parsed",
|
|
885
966
|
"replyText",
|
|
886
967
|
"size",
|
|
887
968
|
"test",
|
|
@@ -918,6 +999,7 @@ function emailDetail(client, email, view, includeHtml) {
|
|
|
918
999
|
...includeHtml && html !== null ? { html } : {}
|
|
919
1000
|
};
|
|
920
1001
|
shrinkToFit(detail, MAX_BODY_SIZE, [
|
|
1002
|
+
() => trimParsed(detail),
|
|
921
1003
|
() => omitHeaders(detail),
|
|
922
1004
|
() => trimSpam(detail),
|
|
923
1005
|
() => capExtracts(detail),
|
|
@@ -1011,6 +1093,7 @@ function registerTools(target, client, options = {}) {
|
|
|
1011
1093
|
mockResponse,
|
|
1012
1094
|
responseRules,
|
|
1013
1095
|
notificationUrl,
|
|
1096
|
+
site,
|
|
1014
1097
|
signingProvider,
|
|
1015
1098
|
signingSecret,
|
|
1016
1099
|
signingHeader
|
|
@@ -1032,7 +1115,8 @@ function registerTools(target, client, options = {}) {
|
|
|
1032
1115
|
expiresIn,
|
|
1033
1116
|
mockResponse,
|
|
1034
1117
|
responseRules,
|
|
1035
|
-
notificationUrl
|
|
1118
|
+
notificationUrl,
|
|
1119
|
+
site
|
|
1036
1120
|
});
|
|
1037
1121
|
if (signingProvider && signingSecret) {
|
|
1038
1122
|
try {
|
|
@@ -1134,15 +1218,12 @@ function registerTools(target, client, options = {}) {
|
|
|
1134
1218
|
"Custom signature header name (only for generic-hmac provider), or null to clear"
|
|
1135
1219
|
),
|
|
1136
1220
|
showEmailExtracts: import_zod2.z.boolean().optional().describe("Pick codes and links out of emails (dashboard and forwarded JSON). Owner only."),
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
name: import_zod2.z.string().describe("Letters, digits and _, starting with a letter: {{ fields.name }}"),
|
|
1140
|
-
from: import_zod2.z.enum(["subject", "text", "html", "from", "to", "header"]).describe("What it reads; html is the HTML as text"),
|
|
1141
|
-
header: import_zod2.z.string().optional().describe('The header name, for from "header"'),
|
|
1142
|
-
pattern: import_zod2.z.string().describe("RE2 regular expression; the first group is the value (no lookarounds)")
|
|
1143
|
-
})
|
|
1144
|
-
).max(20).nullable().optional().describe(
|
|
1221
|
+
site: endpointSiteSchema.optional(),
|
|
1222
|
+
emailFields: import_zod2.z.array(emailFieldSchema).max(20).nullable().optional().describe(
|
|
1145
1223
|
"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."
|
|
1224
|
+
),
|
|
1225
|
+
layouts: import_zod2.z.array(layoutSchema).max(50).nullable().optional().describe(
|
|
1226
|
+
"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."
|
|
1146
1227
|
)
|
|
1147
1228
|
},
|
|
1148
1229
|
withErrorHandling(
|
|
@@ -1156,7 +1237,9 @@ function registerTools(target, client, options = {}) {
|
|
|
1156
1237
|
signingSecret,
|
|
1157
1238
|
signingHeader,
|
|
1158
1239
|
showEmailExtracts,
|
|
1159
|
-
|
|
1240
|
+
site,
|
|
1241
|
+
emailFields,
|
|
1242
|
+
layouts
|
|
1160
1243
|
}) => {
|
|
1161
1244
|
const endpoint = await client.endpoints.update(slug, {
|
|
1162
1245
|
name,
|
|
@@ -1167,12 +1250,29 @@ function registerTools(target, client, options = {}) {
|
|
|
1167
1250
|
signingSecret,
|
|
1168
1251
|
signingHeader,
|
|
1169
1252
|
showEmailExtracts,
|
|
1170
|
-
|
|
1253
|
+
site,
|
|
1254
|
+
emailFields,
|
|
1255
|
+
layouts
|
|
1171
1256
|
});
|
|
1172
1257
|
return jsonContent(endpoint);
|
|
1173
1258
|
}
|
|
1174
1259
|
)
|
|
1175
1260
|
);
|
|
1261
|
+
server.tool(
|
|
1262
|
+
"preview_layouts",
|
|
1263
|
+
"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.",
|
|
1264
|
+
{
|
|
1265
|
+
slug: import_zod2.z.string().describe("The endpoint slug"),
|
|
1266
|
+
requestId: import_zod2.z.string().optional().describe("A captured email on the endpoint (default: the newest)"),
|
|
1267
|
+
layouts: import_zod2.z.array(layoutSchema).max(50).optional().describe("Unsaved layouts to try")
|
|
1268
|
+
},
|
|
1269
|
+
withErrorHandling(async ({ slug, requestId, layouts }) => {
|
|
1270
|
+
const preview = await client.endpoints.previewLayouts(slug, { requestId, layouts });
|
|
1271
|
+
const holder = { ...preview };
|
|
1272
|
+
shrinkToFit(holder, MAX_BODY_SIZE, [() => trimParsed(holder)]);
|
|
1273
|
+
return jsonContent(holder);
|
|
1274
|
+
})
|
|
1275
|
+
);
|
|
1176
1276
|
server.tool(
|
|
1177
1277
|
"preview_response",
|
|
1178
1278
|
"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.",
|
|
@@ -2436,7 +2536,7 @@ function registerAgentRegistrationTools(server) {
|
|
|
2436
2536
|
}
|
|
2437
2537
|
|
|
2438
2538
|
// src/index.ts
|
|
2439
|
-
var VERSION = true ? "1.
|
|
2539
|
+
var VERSION = true ? "1.16.0" : "0.0.0-dev";
|
|
2440
2540
|
function createServer(options = {}) {
|
|
2441
2541
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
2442
2542
|
const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
|
package/dist/index.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
registerTools,
|
|
14
14
|
waitForRequestShape,
|
|
15
15
|
withErrorHandling
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-4LEPLCW5.mjs";
|
|
17
17
|
|
|
18
18
|
// src/index.ts
|
|
19
19
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -654,7 +654,7 @@ function registerAgentRegistrationTools(server) {
|
|
|
654
654
|
}
|
|
655
655
|
|
|
656
656
|
// src/index.ts
|
|
657
|
-
var VERSION = true ? "1.
|
|
657
|
+
var VERSION = true ? "1.16.0" : "0.0.0-dev";
|
|
658
658
|
function createServer(options = {}) {
|
|
659
659
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
660
660
|
const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
|
package/dist/tools.d.mts
CHANGED
|
@@ -64,6 +64,11 @@ declare const createEndpointShape: {
|
|
|
64
64
|
}, z.core.$strip>>;
|
|
65
65
|
}, z.core.$strip>>>;
|
|
66
66
|
notificationUrl: z.ZodOptional<z.ZodString>;
|
|
67
|
+
site: z.ZodOptional<z.ZodEnum<{
|
|
68
|
+
webhooks: "webhooks";
|
|
69
|
+
mailhooks: "mailhooks";
|
|
70
|
+
both: "both";
|
|
71
|
+
}>>;
|
|
67
72
|
signingProvider: z.ZodOptional<z.ZodString>;
|
|
68
73
|
signingSecret: z.ZodOptional<z.ZodString>;
|
|
69
74
|
signingHeader: z.ZodOptional<z.ZodString>;
|
package/dist/tools.d.ts
CHANGED
|
@@ -64,6 +64,11 @@ declare const createEndpointShape: {
|
|
|
64
64
|
}, z.core.$strip>>;
|
|
65
65
|
}, z.core.$strip>>>;
|
|
66
66
|
notificationUrl: z.ZodOptional<z.ZodString>;
|
|
67
|
+
site: z.ZodOptional<z.ZodEnum<{
|
|
68
|
+
webhooks: "webhooks";
|
|
69
|
+
mailhooks: "mailhooks";
|
|
70
|
+
both: "both";
|
|
71
|
+
}>>;
|
|
67
72
|
signingProvider: z.ZodOptional<z.ZodString>;
|
|
68
73
|
signingSecret: z.ZodOptional<z.ZodString>;
|
|
69
74
|
signingHeader: z.ZodOptional<z.ZodString>;
|
package/dist/tools.js
CHANGED
|
@@ -109,6 +109,53 @@ function capLists(holder) {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
+
var MAX_PARSED_ITEMS = 20;
|
|
113
|
+
var MAX_PARSED_VALUE = 200;
|
|
114
|
+
var MAX_PARSED_SHEETS = 10;
|
|
115
|
+
function trimParsed(holder) {
|
|
116
|
+
const parsed = holder.parsed;
|
|
117
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return;
|
|
118
|
+
const next = { ...parsed };
|
|
119
|
+
if (Array.isArray(next.lineItems)) {
|
|
120
|
+
if (next.lineItems.length > MAX_PARSED_ITEMS) {
|
|
121
|
+
next.lineItemsTotal = next.lineItems.length;
|
|
122
|
+
}
|
|
123
|
+
next.lineItems = next.lineItems.slice(0, MAX_PARSED_ITEMS).map(
|
|
124
|
+
(row) => Object.fromEntries(
|
|
125
|
+
Object.entries(row).map(([key, value]) => [
|
|
126
|
+
key,
|
|
127
|
+
typeof value === "string" ? sliceText(value, MAX_PARSED_VALUE) : value
|
|
128
|
+
])
|
|
129
|
+
)
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
if (next.fields && typeof next.fields === "object") {
|
|
133
|
+
next.fields = Object.fromEntries(
|
|
134
|
+
Object.entries(next.fields).map(([key, value]) => [
|
|
135
|
+
key,
|
|
136
|
+
typeof value === "string" ? sliceText(value, MAX_PARSED_VALUE) : value
|
|
137
|
+
])
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (Array.isArray(next.attachments)) {
|
|
141
|
+
next.attachments = next.attachments.map((attachment) => {
|
|
142
|
+
const { sheets, text, ...rest } = attachment;
|
|
143
|
+
return {
|
|
144
|
+
...rest,
|
|
145
|
+
...Array.isArray(sheets) ? {
|
|
146
|
+
sheets: sheets.slice(0, MAX_PARSED_SHEETS).map((sheet) => ({
|
|
147
|
+
name: sheet.name,
|
|
148
|
+
rowCount: sheet.rowCount
|
|
149
|
+
})),
|
|
150
|
+
...sheets.length > MAX_PARSED_SHEETS ? { sheetsTotal: sheets.length } : {}
|
|
151
|
+
} : {},
|
|
152
|
+
...typeof text === "string" ? { textLength: text.length } : {}
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
next.trimmed = "Line items, sheet rows and attachment text were cut to fit the output.";
|
|
157
|
+
holder.parsed = next;
|
|
158
|
+
}
|
|
112
159
|
function keepOnly(holder, keep) {
|
|
113
160
|
for (const key of Object.keys(holder)) {
|
|
114
161
|
if (!keep.includes(key)) delete holder[key];
|
|
@@ -174,6 +221,7 @@ function compactRequest(request) {
|
|
|
174
221
|
const root = { items: [compact], truncated: true, total: 1e3, returned: 1e3 };
|
|
175
222
|
const budget = MAX_OUTPUT - 128;
|
|
176
223
|
shrinkToFit(root, budget, [
|
|
224
|
+
() => trimParsed(compact),
|
|
177
225
|
() => omitHeaders(compact),
|
|
178
226
|
() => trimSpam(email),
|
|
179
227
|
() => capLists(email),
|
|
@@ -230,6 +278,34 @@ function conditionSchema(use) {
|
|
|
230
278
|
});
|
|
231
279
|
}
|
|
232
280
|
var ruleConditionSchema = conditionSchema("response");
|
|
281
|
+
var emailFieldSchema = import_zod.z.object({
|
|
282
|
+
name: import_zod.z.string().describe("Letters, digits and _, starting with a letter: {{ fields.name }}"),
|
|
283
|
+
from: import_zod.z.enum(["subject", "text", "html", "from", "to", "header"]).describe("What it reads; html is the HTML as text"),
|
|
284
|
+
header: import_zod.z.string().optional().describe('The header name, for from "header"'),
|
|
285
|
+
pattern: import_zod.z.string().describe("RE2 regular expression; the first group is the value (no lookarounds)")
|
|
286
|
+
});
|
|
287
|
+
var layoutSchema = import_zod.z.object({
|
|
288
|
+
id: import_zod.z.string().describe("Stable id: 1 to 40 letters, digits, - or _"),
|
|
289
|
+
name: import_zod.z.string().max(100),
|
|
290
|
+
enabled: import_zod.z.boolean().optional().describe("Off: kept but never matched (default on)"),
|
|
291
|
+
match: import_zod.z.object({
|
|
292
|
+
logic: import_zod.z.enum(["and", "or"]).optional(),
|
|
293
|
+
conditions: import_zod.z.array(conditionSchema("filter")).max(10)
|
|
294
|
+
}).optional().describe("When it applies, e.g. the sender or subject; no conditions: every email"),
|
|
295
|
+
fields: import_zod.z.array(emailFieldSchema).max(20).optional().describe("Named values, as emailFields"),
|
|
296
|
+
lineItems: import_zod.z.object({
|
|
297
|
+
from: import_zod.z.enum(["text", "html", "attachment"]),
|
|
298
|
+
pattern: import_zod.z.string().optional().describe(
|
|
299
|
+
"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+)"
|
|
300
|
+
),
|
|
301
|
+
start: import_zod.z.string().optional().describe("text/html: search only after this (RE2)"),
|
|
302
|
+
end: import_zod.z.string().optional().describe("text/html: and before this (RE2)"),
|
|
303
|
+
attachment: import_zod.z.string().optional().describe("attachment: file name with * and ? (default: the first spreadsheet or CSV)"),
|
|
304
|
+
sheet: import_zod.z.string().optional().describe("attachment: sheet name (default: the first)"),
|
|
305
|
+
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")
|
|
306
|
+
}).nullable().optional().describe("Rows such as an order's products"),
|
|
307
|
+
parseAttachments: import_zod.z.boolean().optional().describe("Parse CSV, XLSX, ODS, PDF text and text files into the JSON; uses parsed pages")
|
|
308
|
+
});
|
|
233
309
|
var destinationFields = {
|
|
234
310
|
name: import_zod.z.string().max(100).nullable().optional().describe("A name for the list and the log"),
|
|
235
311
|
enabled: import_zod.z.boolean().optional().describe("Send to it (default true)"),
|
|
@@ -296,6 +372,9 @@ var mockResponseSchema = import_zod.z.object({
|
|
|
296
372
|
`Response delay in milliseconds (${import_sdk.MOCK_RESPONSE_DELAY_MIN}-${import_sdk.MOCK_RESPONSE_DELAY_MAX}, default: none)`
|
|
297
373
|
)
|
|
298
374
|
});
|
|
375
|
+
var endpointSiteSchema = import_zod.z.enum(["webhooks", "mailhooks", "both"]).describe(
|
|
376
|
+
"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."
|
|
377
|
+
);
|
|
299
378
|
var createEndpointShape = {
|
|
300
379
|
name: import_zod.z.string().optional().describe("Display name for the endpoint"),
|
|
301
380
|
ephemeral: import_zod.z.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
|
|
@@ -307,6 +386,7 @@ var createEndpointShape = {
|
|
|
307
386
|
notificationUrl: import_zod.z.string().url().optional().describe(
|
|
308
387
|
"URL to POST a JSON summary to after each captured request (e.g. Slack/Discord webhook)"
|
|
309
388
|
),
|
|
389
|
+
site: endpointSiteSchema.optional(),
|
|
310
390
|
signingProvider: import_zod.z.string().optional().describe(
|
|
311
391
|
"Signing provider for automatic signature verification (e.g. stripe, github, shopify)"
|
|
312
392
|
),
|
|
@@ -649,6 +729,7 @@ var ESSENTIAL_DETAIL_FIELDS = [
|
|
|
649
729
|
"subject",
|
|
650
730
|
"from",
|
|
651
731
|
"fields",
|
|
732
|
+
"parsed",
|
|
652
733
|
"replyText",
|
|
653
734
|
"size",
|
|
654
735
|
"test",
|
|
@@ -685,6 +766,7 @@ function emailDetail(client, email, view, includeHtml) {
|
|
|
685
766
|
...includeHtml && html !== null ? { html } : {}
|
|
686
767
|
};
|
|
687
768
|
shrinkToFit(detail, MAX_BODY_SIZE, [
|
|
769
|
+
() => trimParsed(detail),
|
|
688
770
|
() => omitHeaders(detail),
|
|
689
771
|
() => trimSpam(detail),
|
|
690
772
|
() => capExtracts(detail),
|
|
@@ -778,6 +860,7 @@ function registerTools(target, client, options = {}) {
|
|
|
778
860
|
mockResponse,
|
|
779
861
|
responseRules,
|
|
780
862
|
notificationUrl,
|
|
863
|
+
site,
|
|
781
864
|
signingProvider,
|
|
782
865
|
signingSecret,
|
|
783
866
|
signingHeader
|
|
@@ -799,7 +882,8 @@ function registerTools(target, client, options = {}) {
|
|
|
799
882
|
expiresIn,
|
|
800
883
|
mockResponse,
|
|
801
884
|
responseRules,
|
|
802
|
-
notificationUrl
|
|
885
|
+
notificationUrl,
|
|
886
|
+
site
|
|
803
887
|
});
|
|
804
888
|
if (signingProvider && signingSecret) {
|
|
805
889
|
try {
|
|
@@ -901,15 +985,12 @@ function registerTools(target, client, options = {}) {
|
|
|
901
985
|
"Custom signature header name (only for generic-hmac provider), or null to clear"
|
|
902
986
|
),
|
|
903
987
|
showEmailExtracts: import_zod.z.boolean().optional().describe("Pick codes and links out of emails (dashboard and forwarded JSON). Owner only."),
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
name: import_zod.z.string().describe("Letters, digits and _, starting with a letter: {{ fields.name }}"),
|
|
907
|
-
from: import_zod.z.enum(["subject", "text", "html", "from", "to", "header"]).describe("What it reads; html is the HTML as text"),
|
|
908
|
-
header: import_zod.z.string().optional().describe('The header name, for from "header"'),
|
|
909
|
-
pattern: import_zod.z.string().describe("RE2 regular expression; the first group is the value (no lookarounds)")
|
|
910
|
-
})
|
|
911
|
-
).max(20).nullable().optional().describe(
|
|
988
|
+
site: endpointSiteSchema.optional(),
|
|
989
|
+
emailFields: import_zod.z.array(emailFieldSchema).max(20).nullable().optional().describe(
|
|
912
990
|
"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."
|
|
991
|
+
),
|
|
992
|
+
layouts: import_zod.z.array(layoutSchema).max(50).nullable().optional().describe(
|
|
993
|
+
"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."
|
|
913
994
|
)
|
|
914
995
|
},
|
|
915
996
|
withErrorHandling(
|
|
@@ -923,7 +1004,9 @@ function registerTools(target, client, options = {}) {
|
|
|
923
1004
|
signingSecret,
|
|
924
1005
|
signingHeader,
|
|
925
1006
|
showEmailExtracts,
|
|
926
|
-
|
|
1007
|
+
site,
|
|
1008
|
+
emailFields,
|
|
1009
|
+
layouts
|
|
927
1010
|
}) => {
|
|
928
1011
|
const endpoint = await client.endpoints.update(slug, {
|
|
929
1012
|
name,
|
|
@@ -934,12 +1017,29 @@ function registerTools(target, client, options = {}) {
|
|
|
934
1017
|
signingSecret,
|
|
935
1018
|
signingHeader,
|
|
936
1019
|
showEmailExtracts,
|
|
937
|
-
|
|
1020
|
+
site,
|
|
1021
|
+
emailFields,
|
|
1022
|
+
layouts
|
|
938
1023
|
});
|
|
939
1024
|
return jsonContent(endpoint);
|
|
940
1025
|
}
|
|
941
1026
|
)
|
|
942
1027
|
);
|
|
1028
|
+
server.tool(
|
|
1029
|
+
"preview_layouts",
|
|
1030
|
+
"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.",
|
|
1031
|
+
{
|
|
1032
|
+
slug: import_zod.z.string().describe("The endpoint slug"),
|
|
1033
|
+
requestId: import_zod.z.string().optional().describe("A captured email on the endpoint (default: the newest)"),
|
|
1034
|
+
layouts: import_zod.z.array(layoutSchema).max(50).optional().describe("Unsaved layouts to try")
|
|
1035
|
+
},
|
|
1036
|
+
withErrorHandling(async ({ slug, requestId, layouts }) => {
|
|
1037
|
+
const preview = await client.endpoints.previewLayouts(slug, { requestId, layouts });
|
|
1038
|
+
const holder = { ...preview };
|
|
1039
|
+
shrinkToFit(holder, MAX_BODY_SIZE, [() => trimParsed(holder)]);
|
|
1040
|
+
return jsonContent(holder);
|
|
1041
|
+
})
|
|
1042
|
+
);
|
|
943
1043
|
server.tool(
|
|
944
1044
|
"preview_response",
|
|
945
1045
|
"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.",
|
package/dist/tools.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webhooks-cc/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"mcpName": "io.github.kroqdotdev/webhooks-cc",
|
|
5
5
|
"description": "MCP server for webhooks.cc — AI agent integration for webhook testing",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
46
46
|
"zod": "^4.6.7",
|
|
47
|
-
"@webhooks-cc/sdk": "1.
|
|
47
|
+
"@webhooks-cc/sdk": "1.20.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/node": "^26.6.5",
|