@webhooks-cc/mcp 1.15.1 → 1.17.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 +3 -2
- package/dist/bin/mcp.js +133 -15
- package/dist/{chunk-RHQD2GRD.mjs → chunk-ZAPNMGCE.mjs} +123 -11
- package/dist/index.js +133 -15
- package/dist/index.mjs +13 -5
- package/dist/tools.d.mts +5 -0
- package/dist/tools.d.ts +5 -0
- package/dist/tools.js +123 -11
- package/dist/tools.mjs +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ For other agents, `npx skills add kroqdotdev/webhooks-cc` installs the same skil
|
|
|
34
34
|
- **claude.ai and Claude Desktop**: Settings, Connectors, add a custom connector with the URL `https://webhooks.cc/mcp`
|
|
35
35
|
- **ChatGPT**: add it as a custom connector (developer mode)
|
|
36
36
|
|
|
37
|
-
A client that can send headers may use an API key instead: `Authorization: Bearer whcc_...`. The remote server has
|
|
37
|
+
A client that can send headers may use an API key instead: `Authorization: Bearer whcc_...`. The remote server has 46 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.
|
|
38
38
|
|
|
39
39
|
### One-click
|
|
40
40
|
|
|
@@ -83,13 +83,14 @@ Leave out `env` to start without a key. Get your API key at [webhooks.cc/account
|
|
|
83
83
|
|
|
84
84
|
## What it exposes
|
|
85
85
|
|
|
86
|
-
The server exposes `
|
|
86
|
+
The server exposes `48` tools, `3` prompts, and `3` resource surfaces with an API key, the same `48` tools and the prompts with a saved connection, and `11` sandbox tools otherwise. The remote server exposes `46` tools.
|
|
87
87
|
|
|
88
88
|
### Endpoint tools
|
|
89
89
|
|
|
90
90
|
- `create_endpoint`
|
|
91
91
|
- `list_endpoints`
|
|
92
92
|
- `get_endpoint`
|
|
93
|
+
- `favorite_endpoint` (mark or unmark one of your favourites; `list_endpoints` and `get_endpoint` show `favorite`)
|
|
93
94
|
- `update_endpoint`
|
|
94
95
|
- `preview_response`
|
|
95
96
|
- `delete_endpoint`
|
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),
|
|
@@ -397,6 +445,10 @@ function registerResources(server, client) {
|
|
|
397
445
|
var import_zod3 = require("zod");
|
|
398
446
|
var import_sdk2 = require("@webhooks-cc/sdk");
|
|
399
447
|
|
|
448
|
+
// src/version.ts
|
|
449
|
+
var VERSION = true ? "1.17.0" : "0.0.0-dev";
|
|
450
|
+
var MCP_USER_AGENT = `webhooks-cc-mcp/${VERSION}`;
|
|
451
|
+
|
|
400
452
|
// src/tools.ts
|
|
401
453
|
var import_zod2 = require("zod");
|
|
402
454
|
var import_sdk = require("@webhooks-cc/sdk");
|
|
@@ -439,6 +491,34 @@ function conditionSchema(use) {
|
|
|
439
491
|
});
|
|
440
492
|
}
|
|
441
493
|
var ruleConditionSchema = conditionSchema("response");
|
|
494
|
+
var emailFieldSchema = import_zod2.z.object({
|
|
495
|
+
name: import_zod2.z.string().describe("Letters, digits and _, starting with a letter: {{ fields.name }}"),
|
|
496
|
+
from: import_zod2.z.enum(["subject", "text", "html", "from", "to", "header"]).describe("What it reads; html is the HTML as text"),
|
|
497
|
+
header: import_zod2.z.string().optional().describe('The header name, for from "header"'),
|
|
498
|
+
pattern: import_zod2.z.string().describe("RE2 regular expression; the first group is the value (no lookarounds)")
|
|
499
|
+
});
|
|
500
|
+
var layoutSchema = import_zod2.z.object({
|
|
501
|
+
id: import_zod2.z.string().describe("Stable id: 1 to 40 letters, digits, - or _"),
|
|
502
|
+
name: import_zod2.z.string().max(100),
|
|
503
|
+
enabled: import_zod2.z.boolean().optional().describe("Off: kept but never matched (default on)"),
|
|
504
|
+
match: import_zod2.z.object({
|
|
505
|
+
logic: import_zod2.z.enum(["and", "or"]).optional(),
|
|
506
|
+
conditions: import_zod2.z.array(conditionSchema("filter")).max(10)
|
|
507
|
+
}).optional().describe("When it applies, e.g. the sender or subject; no conditions: every email"),
|
|
508
|
+
fields: import_zod2.z.array(emailFieldSchema).max(20).optional().describe("Named values, as emailFields"),
|
|
509
|
+
lineItems: import_zod2.z.object({
|
|
510
|
+
from: import_zod2.z.enum(["text", "html", "attachment"]),
|
|
511
|
+
pattern: import_zod2.z.string().optional().describe(
|
|
512
|
+
"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+)"
|
|
513
|
+
),
|
|
514
|
+
start: import_zod2.z.string().optional().describe("text/html: search only after this (RE2)"),
|
|
515
|
+
end: import_zod2.z.string().optional().describe("text/html: and before this (RE2)"),
|
|
516
|
+
attachment: import_zod2.z.string().optional().describe("attachment: file name with * and ? (default: the first spreadsheet or CSV)"),
|
|
517
|
+
sheet: import_zod2.z.string().optional().describe("attachment: sheet name (default: the first)"),
|
|
518
|
+
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")
|
|
519
|
+
}).nullable().optional().describe("Rows such as an order's products"),
|
|
520
|
+
parseAttachments: import_zod2.z.boolean().optional().describe("Parse CSV, XLSX, ODS, PDF text and text files into the JSON; uses parsed pages")
|
|
521
|
+
});
|
|
442
522
|
var destinationFields = {
|
|
443
523
|
name: import_zod2.z.string().max(100).nullable().optional().describe("A name for the list and the log"),
|
|
444
524
|
enabled: import_zod2.z.boolean().optional().describe("Send to it (default true)"),
|
|
@@ -505,6 +585,9 @@ var mockResponseSchema = import_zod2.z.object({
|
|
|
505
585
|
`Response delay in milliseconds (${import_sdk.MOCK_RESPONSE_DELAY_MIN}-${import_sdk.MOCK_RESPONSE_DELAY_MAX}, default: none)`
|
|
506
586
|
)
|
|
507
587
|
});
|
|
588
|
+
var endpointSiteSchema = import_zod2.z.enum(["webhooks", "mailhooks", "both"]).describe(
|
|
589
|
+
"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."
|
|
590
|
+
);
|
|
508
591
|
var createEndpointShape = {
|
|
509
592
|
name: import_zod2.z.string().optional().describe("Display name for the endpoint"),
|
|
510
593
|
ephemeral: import_zod2.z.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
|
|
@@ -516,6 +599,7 @@ var createEndpointShape = {
|
|
|
516
599
|
notificationUrl: import_zod2.z.string().url().optional().describe(
|
|
517
600
|
"URL to POST a JSON summary to after each captured request (e.g. Slack/Discord webhook)"
|
|
518
601
|
),
|
|
602
|
+
site: endpointSiteSchema.optional(),
|
|
519
603
|
signingProvider: import_zod2.z.string().optional().describe(
|
|
520
604
|
"Signing provider for automatic signature verification (e.g. stripe, github, shopify)"
|
|
521
605
|
),
|
|
@@ -858,6 +942,7 @@ var ESSENTIAL_DETAIL_FIELDS = [
|
|
|
858
942
|
"subject",
|
|
859
943
|
"from",
|
|
860
944
|
"fields",
|
|
945
|
+
"parsed",
|
|
861
946
|
"replyText",
|
|
862
947
|
"size",
|
|
863
948
|
"test",
|
|
@@ -894,6 +979,7 @@ function emailDetail(client, email, view, includeHtml) {
|
|
|
894
979
|
...includeHtml && html !== null ? { html } : {}
|
|
895
980
|
};
|
|
896
981
|
shrinkToFit(detail, MAX_BODY_SIZE, [
|
|
982
|
+
() => trimParsed(detail),
|
|
897
983
|
() => omitHeaders(detail),
|
|
898
984
|
() => trimSpam(detail),
|
|
899
985
|
() => capExtracts(detail),
|
|
@@ -987,6 +1073,7 @@ function registerTools(target, client, options = {}) {
|
|
|
987
1073
|
mockResponse,
|
|
988
1074
|
responseRules,
|
|
989
1075
|
notificationUrl,
|
|
1076
|
+
site,
|
|
990
1077
|
signingProvider,
|
|
991
1078
|
signingSecret,
|
|
992
1079
|
signingHeader
|
|
@@ -1008,7 +1095,8 @@ function registerTools(target, client, options = {}) {
|
|
|
1008
1095
|
expiresIn,
|
|
1009
1096
|
mockResponse,
|
|
1010
1097
|
responseRules,
|
|
1011
|
-
notificationUrl
|
|
1098
|
+
notificationUrl,
|
|
1099
|
+
site
|
|
1012
1100
|
});
|
|
1013
1101
|
if (signingProvider && signingSecret) {
|
|
1014
1102
|
try {
|
|
@@ -1093,6 +1181,18 @@ function registerTools(target, client, options = {}) {
|
|
|
1093
1181
|
return jsonContent(endpoint);
|
|
1094
1182
|
})
|
|
1095
1183
|
);
|
|
1184
|
+
server.tool(
|
|
1185
|
+
"favorite_endpoint",
|
|
1186
|
+
"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.",
|
|
1187
|
+
{
|
|
1188
|
+
slug: import_zod2.z.string().describe("The endpoint slug"),
|
|
1189
|
+
favorite: import_zod2.z.boolean().optional().describe("true marks it as a favourite (the default), false unmarks it")
|
|
1190
|
+
},
|
|
1191
|
+
withErrorHandling(async ({ slug, favorite }) => {
|
|
1192
|
+
const result = favorite === false ? await client.endpoints.unfavorite(slug) : await client.endpoints.favorite(slug);
|
|
1193
|
+
return jsonContent({ slug, favorite: result.favorite });
|
|
1194
|
+
})
|
|
1195
|
+
);
|
|
1096
1196
|
server.tool(
|
|
1097
1197
|
"update_endpoint",
|
|
1098
1198
|
"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.",
|
|
@@ -1110,15 +1210,12 @@ function registerTools(target, client, options = {}) {
|
|
|
1110
1210
|
"Custom signature header name (only for generic-hmac provider), or null to clear"
|
|
1111
1211
|
),
|
|
1112
1212
|
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(
|
|
1213
|
+
site: endpointSiteSchema.optional(),
|
|
1214
|
+
emailFields: import_zod2.z.array(emailFieldSchema).max(20).nullable().optional().describe(
|
|
1121
1215
|
"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."
|
|
1216
|
+
),
|
|
1217
|
+
layouts: import_zod2.z.array(layoutSchema).max(50).nullable().optional().describe(
|
|
1218
|
+
"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
1219
|
)
|
|
1123
1220
|
},
|
|
1124
1221
|
withErrorHandling(
|
|
@@ -1132,7 +1229,9 @@ function registerTools(target, client, options = {}) {
|
|
|
1132
1229
|
signingSecret,
|
|
1133
1230
|
signingHeader,
|
|
1134
1231
|
showEmailExtracts,
|
|
1135
|
-
|
|
1232
|
+
site,
|
|
1233
|
+
emailFields,
|
|
1234
|
+
layouts
|
|
1136
1235
|
}) => {
|
|
1137
1236
|
const endpoint = await client.endpoints.update(slug, {
|
|
1138
1237
|
name,
|
|
@@ -1143,12 +1242,29 @@ function registerTools(target, client, options = {}) {
|
|
|
1143
1242
|
signingSecret,
|
|
1144
1243
|
signingHeader,
|
|
1145
1244
|
showEmailExtracts,
|
|
1146
|
-
|
|
1245
|
+
site,
|
|
1246
|
+
emailFields,
|
|
1247
|
+
layouts
|
|
1147
1248
|
});
|
|
1148
1249
|
return jsonContent(endpoint);
|
|
1149
1250
|
}
|
|
1150
1251
|
)
|
|
1151
1252
|
);
|
|
1253
|
+
server.tool(
|
|
1254
|
+
"preview_layouts",
|
|
1255
|
+
"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.",
|
|
1256
|
+
{
|
|
1257
|
+
slug: import_zod2.z.string().describe("The endpoint slug"),
|
|
1258
|
+
requestId: import_zod2.z.string().optional().describe("A captured email on the endpoint (default: the newest)"),
|
|
1259
|
+
layouts: import_zod2.z.array(layoutSchema).max(50).optional().describe("Unsaved layouts to try")
|
|
1260
|
+
},
|
|
1261
|
+
withErrorHandling(async ({ slug, requestId, layouts }) => {
|
|
1262
|
+
const preview = await client.endpoints.previewLayouts(slug, { requestId, layouts });
|
|
1263
|
+
const holder = { ...preview };
|
|
1264
|
+
shrinkToFit(holder, MAX_BODY_SIZE, [() => trimParsed(holder)]);
|
|
1265
|
+
return jsonContent(holder);
|
|
1266
|
+
})
|
|
1267
|
+
);
|
|
1152
1268
|
server.tool(
|
|
1153
1269
|
"preview_response",
|
|
1154
1270
|
"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.",
|
|
@@ -2049,7 +2165,8 @@ var AgentSession = class {
|
|
|
2049
2165
|
baseUrl: this.baseUrl,
|
|
2050
2166
|
webhookUrl: this.webhookUrl,
|
|
2051
2167
|
clientName: this.clientName,
|
|
2052
|
-
credentialsFile: this.credentialsFile
|
|
2168
|
+
credentialsFile: this.credentialsFile,
|
|
2169
|
+
userAgent: MCP_USER_AGENT
|
|
2053
2170
|
};
|
|
2054
2171
|
}
|
|
2055
2172
|
/**
|
|
@@ -2142,6 +2259,7 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2142
2259
|
baseUrl,
|
|
2143
2260
|
webhookUrl,
|
|
2144
2261
|
credentialsFile: options.credentialsFile,
|
|
2262
|
+
userAgent: MCP_USER_AGENT,
|
|
2145
2263
|
// The human disconnected the agent: back to the sandbox, unless a
|
|
2146
2264
|
// newer connection is already on display.
|
|
2147
2265
|
onRevoked: () => queueMicrotask(() => {
|
|
@@ -2303,7 +2421,8 @@ function sandboxToolSet(server, session, showAccount) {
|
|
|
2303
2421
|
webhookUrl: session.webhookUrl,
|
|
2304
2422
|
email: normalized,
|
|
2305
2423
|
clientName: session.clientName,
|
|
2306
|
-
credentialsFile: session.credentialsFile
|
|
2424
|
+
credentialsFile: session.credentialsFile,
|
|
2425
|
+
userAgent: MCP_USER_AGENT
|
|
2307
2426
|
}));
|
|
2308
2427
|
session.connect = { via: "connect", pending, email: normalized };
|
|
2309
2428
|
attempt = pending;
|
|
@@ -2409,7 +2528,6 @@ function sandboxToolSet(server, session, showAccount) {
|
|
|
2409
2528
|
}
|
|
2410
2529
|
|
|
2411
2530
|
// src/index.ts
|
|
2412
|
-
var VERSION = true ? "1.15.1" : "0.0.0-dev";
|
|
2413
2531
|
function createServer(options = {}) {
|
|
2414
2532
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
2415
2533
|
const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
|
|
@@ -2424,7 +2542,7 @@ function createServer(options = {}) {
|
|
|
2424
2542
|
registerPrompts(server);
|
|
2425
2543
|
return server;
|
|
2426
2544
|
}
|
|
2427
|
-
const client = new import_sdk3.WebhooksCC({ apiKey, webhookUrl, baseUrl });
|
|
2545
|
+
const client = new import_sdk3.WebhooksCC({ apiKey, webhookUrl, baseUrl, userAgent: MCP_USER_AGENT });
|
|
2428
2546
|
registerTools(server, client);
|
|
2429
2547
|
registerPrompts(server);
|
|
2430
2548
|
registerResources(server, client);
|
|
@@ -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 {
|
|
@@ -875,6 +959,18 @@ function registerTools(target, client, options = {}) {
|
|
|
875
959
|
return jsonContent(endpoint);
|
|
876
960
|
})
|
|
877
961
|
);
|
|
962
|
+
server.tool(
|
|
963
|
+
"favorite_endpoint",
|
|
964
|
+
"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.",
|
|
965
|
+
{
|
|
966
|
+
slug: z.string().describe("The endpoint slug"),
|
|
967
|
+
favorite: z.boolean().optional().describe("true marks it as a favourite (the default), false unmarks it")
|
|
968
|
+
},
|
|
969
|
+
withErrorHandling(async ({ slug, favorite }) => {
|
|
970
|
+
const result = favorite === false ? await client.endpoints.unfavorite(slug) : await client.endpoints.favorite(slug);
|
|
971
|
+
return jsonContent({ slug, favorite: result.favorite });
|
|
972
|
+
})
|
|
973
|
+
);
|
|
878
974
|
server.tool(
|
|
879
975
|
"update_endpoint",
|
|
880
976
|
"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.",
|
|
@@ -892,15 +988,12 @@ function registerTools(target, client, options = {}) {
|
|
|
892
988
|
"Custom signature header name (only for generic-hmac provider), or null to clear"
|
|
893
989
|
),
|
|
894
990
|
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(
|
|
991
|
+
site: endpointSiteSchema.optional(),
|
|
992
|
+
emailFields: z.array(emailFieldSchema).max(20).nullable().optional().describe(
|
|
903
993
|
"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."
|
|
994
|
+
),
|
|
995
|
+
layouts: z.array(layoutSchema).max(50).nullable().optional().describe(
|
|
996
|
+
"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
997
|
)
|
|
905
998
|
},
|
|
906
999
|
withErrorHandling(
|
|
@@ -914,7 +1007,9 @@ function registerTools(target, client, options = {}) {
|
|
|
914
1007
|
signingSecret,
|
|
915
1008
|
signingHeader,
|
|
916
1009
|
showEmailExtracts,
|
|
917
|
-
|
|
1010
|
+
site,
|
|
1011
|
+
emailFields,
|
|
1012
|
+
layouts
|
|
918
1013
|
}) => {
|
|
919
1014
|
const endpoint = await client.endpoints.update(slug, {
|
|
920
1015
|
name,
|
|
@@ -925,12 +1020,29 @@ function registerTools(target, client, options = {}) {
|
|
|
925
1020
|
signingSecret,
|
|
926
1021
|
signingHeader,
|
|
927
1022
|
showEmailExtracts,
|
|
928
|
-
|
|
1023
|
+
site,
|
|
1024
|
+
emailFields,
|
|
1025
|
+
layouts
|
|
929
1026
|
});
|
|
930
1027
|
return jsonContent(endpoint);
|
|
931
1028
|
}
|
|
932
1029
|
)
|
|
933
1030
|
);
|
|
1031
|
+
server.tool(
|
|
1032
|
+
"preview_layouts",
|
|
1033
|
+
"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.",
|
|
1034
|
+
{
|
|
1035
|
+
slug: z.string().describe("The endpoint slug"),
|
|
1036
|
+
requestId: z.string().optional().describe("A captured email on the endpoint (default: the newest)"),
|
|
1037
|
+
layouts: z.array(layoutSchema).max(50).optional().describe("Unsaved layouts to try")
|
|
1038
|
+
},
|
|
1039
|
+
withErrorHandling(async ({ slug, requestId, layouts }) => {
|
|
1040
|
+
const preview = await client.endpoints.previewLayouts(slug, { requestId, layouts });
|
|
1041
|
+
const holder = { ...preview };
|
|
1042
|
+
shrinkToFit(holder, MAX_BODY_SIZE, [() => trimParsed(holder)]);
|
|
1043
|
+
return jsonContent(holder);
|
|
1044
|
+
})
|
|
1045
|
+
);
|
|
934
1046
|
server.tool(
|
|
935
1047
|
"preview_response",
|
|
936
1048
|
"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),
|
|
@@ -421,6 +469,10 @@ function registerResources(server, client) {
|
|
|
421
469
|
var import_zod3 = require("zod");
|
|
422
470
|
var import_sdk2 = require("@webhooks-cc/sdk");
|
|
423
471
|
|
|
472
|
+
// src/version.ts
|
|
473
|
+
var VERSION = true ? "1.17.0" : "0.0.0-dev";
|
|
474
|
+
var MCP_USER_AGENT = `webhooks-cc-mcp/${VERSION}`;
|
|
475
|
+
|
|
424
476
|
// src/tools.ts
|
|
425
477
|
var import_zod2 = require("zod");
|
|
426
478
|
var import_sdk = require("@webhooks-cc/sdk");
|
|
@@ -463,6 +515,34 @@ function conditionSchema(use) {
|
|
|
463
515
|
});
|
|
464
516
|
}
|
|
465
517
|
var ruleConditionSchema = conditionSchema("response");
|
|
518
|
+
var emailFieldSchema = import_zod2.z.object({
|
|
519
|
+
name: import_zod2.z.string().describe("Letters, digits and _, starting with a letter: {{ fields.name }}"),
|
|
520
|
+
from: import_zod2.z.enum(["subject", "text", "html", "from", "to", "header"]).describe("What it reads; html is the HTML as text"),
|
|
521
|
+
header: import_zod2.z.string().optional().describe('The header name, for from "header"'),
|
|
522
|
+
pattern: import_zod2.z.string().describe("RE2 regular expression; the first group is the value (no lookarounds)")
|
|
523
|
+
});
|
|
524
|
+
var layoutSchema = import_zod2.z.object({
|
|
525
|
+
id: import_zod2.z.string().describe("Stable id: 1 to 40 letters, digits, - or _"),
|
|
526
|
+
name: import_zod2.z.string().max(100),
|
|
527
|
+
enabled: import_zod2.z.boolean().optional().describe("Off: kept but never matched (default on)"),
|
|
528
|
+
match: import_zod2.z.object({
|
|
529
|
+
logic: import_zod2.z.enum(["and", "or"]).optional(),
|
|
530
|
+
conditions: import_zod2.z.array(conditionSchema("filter")).max(10)
|
|
531
|
+
}).optional().describe("When it applies, e.g. the sender or subject; no conditions: every email"),
|
|
532
|
+
fields: import_zod2.z.array(emailFieldSchema).max(20).optional().describe("Named values, as emailFields"),
|
|
533
|
+
lineItems: import_zod2.z.object({
|
|
534
|
+
from: import_zod2.z.enum(["text", "html", "attachment"]),
|
|
535
|
+
pattern: import_zod2.z.string().optional().describe(
|
|
536
|
+
"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+)"
|
|
537
|
+
),
|
|
538
|
+
start: import_zod2.z.string().optional().describe("text/html: search only after this (RE2)"),
|
|
539
|
+
end: import_zod2.z.string().optional().describe("text/html: and before this (RE2)"),
|
|
540
|
+
attachment: import_zod2.z.string().optional().describe("attachment: file name with * and ? (default: the first spreadsheet or CSV)"),
|
|
541
|
+
sheet: import_zod2.z.string().optional().describe("attachment: sheet name (default: the first)"),
|
|
542
|
+
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")
|
|
543
|
+
}).nullable().optional().describe("Rows such as an order's products"),
|
|
544
|
+
parseAttachments: import_zod2.z.boolean().optional().describe("Parse CSV, XLSX, ODS, PDF text and text files into the JSON; uses parsed pages")
|
|
545
|
+
});
|
|
466
546
|
var destinationFields = {
|
|
467
547
|
name: import_zod2.z.string().max(100).nullable().optional().describe("A name for the list and the log"),
|
|
468
548
|
enabled: import_zod2.z.boolean().optional().describe("Send to it (default true)"),
|
|
@@ -529,6 +609,9 @@ var mockResponseSchema = import_zod2.z.object({
|
|
|
529
609
|
`Response delay in milliseconds (${import_sdk.MOCK_RESPONSE_DELAY_MIN}-${import_sdk.MOCK_RESPONSE_DELAY_MAX}, default: none)`
|
|
530
610
|
)
|
|
531
611
|
});
|
|
612
|
+
var endpointSiteSchema = import_zod2.z.enum(["webhooks", "mailhooks", "both"]).describe(
|
|
613
|
+
"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."
|
|
614
|
+
);
|
|
532
615
|
var createEndpointShape = {
|
|
533
616
|
name: import_zod2.z.string().optional().describe("Display name for the endpoint"),
|
|
534
617
|
ephemeral: import_zod2.z.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
|
|
@@ -540,6 +623,7 @@ var createEndpointShape = {
|
|
|
540
623
|
notificationUrl: import_zod2.z.string().url().optional().describe(
|
|
541
624
|
"URL to POST a JSON summary to after each captured request (e.g. Slack/Discord webhook)"
|
|
542
625
|
),
|
|
626
|
+
site: endpointSiteSchema.optional(),
|
|
543
627
|
signingProvider: import_zod2.z.string().optional().describe(
|
|
544
628
|
"Signing provider for automatic signature verification (e.g. stripe, github, shopify)"
|
|
545
629
|
),
|
|
@@ -882,6 +966,7 @@ var ESSENTIAL_DETAIL_FIELDS = [
|
|
|
882
966
|
"subject",
|
|
883
967
|
"from",
|
|
884
968
|
"fields",
|
|
969
|
+
"parsed",
|
|
885
970
|
"replyText",
|
|
886
971
|
"size",
|
|
887
972
|
"test",
|
|
@@ -918,6 +1003,7 @@ function emailDetail(client, email, view, includeHtml) {
|
|
|
918
1003
|
...includeHtml && html !== null ? { html } : {}
|
|
919
1004
|
};
|
|
920
1005
|
shrinkToFit(detail, MAX_BODY_SIZE, [
|
|
1006
|
+
() => trimParsed(detail),
|
|
921
1007
|
() => omitHeaders(detail),
|
|
922
1008
|
() => trimSpam(detail),
|
|
923
1009
|
() => capExtracts(detail),
|
|
@@ -1011,6 +1097,7 @@ function registerTools(target, client, options = {}) {
|
|
|
1011
1097
|
mockResponse,
|
|
1012
1098
|
responseRules,
|
|
1013
1099
|
notificationUrl,
|
|
1100
|
+
site,
|
|
1014
1101
|
signingProvider,
|
|
1015
1102
|
signingSecret,
|
|
1016
1103
|
signingHeader
|
|
@@ -1032,7 +1119,8 @@ function registerTools(target, client, options = {}) {
|
|
|
1032
1119
|
expiresIn,
|
|
1033
1120
|
mockResponse,
|
|
1034
1121
|
responseRules,
|
|
1035
|
-
notificationUrl
|
|
1122
|
+
notificationUrl,
|
|
1123
|
+
site
|
|
1036
1124
|
});
|
|
1037
1125
|
if (signingProvider && signingSecret) {
|
|
1038
1126
|
try {
|
|
@@ -1117,6 +1205,18 @@ function registerTools(target, client, options = {}) {
|
|
|
1117
1205
|
return jsonContent(endpoint);
|
|
1118
1206
|
})
|
|
1119
1207
|
);
|
|
1208
|
+
server.tool(
|
|
1209
|
+
"favorite_endpoint",
|
|
1210
|
+
"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.",
|
|
1211
|
+
{
|
|
1212
|
+
slug: import_zod2.z.string().describe("The endpoint slug"),
|
|
1213
|
+
favorite: import_zod2.z.boolean().optional().describe("true marks it as a favourite (the default), false unmarks it")
|
|
1214
|
+
},
|
|
1215
|
+
withErrorHandling(async ({ slug, favorite }) => {
|
|
1216
|
+
const result = favorite === false ? await client.endpoints.unfavorite(slug) : await client.endpoints.favorite(slug);
|
|
1217
|
+
return jsonContent({ slug, favorite: result.favorite });
|
|
1218
|
+
})
|
|
1219
|
+
);
|
|
1120
1220
|
server.tool(
|
|
1121
1221
|
"update_endpoint",
|
|
1122
1222
|
"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.",
|
|
@@ -1134,15 +1234,12 @@ function registerTools(target, client, options = {}) {
|
|
|
1134
1234
|
"Custom signature header name (only for generic-hmac provider), or null to clear"
|
|
1135
1235
|
),
|
|
1136
1236
|
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(
|
|
1237
|
+
site: endpointSiteSchema.optional(),
|
|
1238
|
+
emailFields: import_zod2.z.array(emailFieldSchema).max(20).nullable().optional().describe(
|
|
1145
1239
|
"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."
|
|
1240
|
+
),
|
|
1241
|
+
layouts: import_zod2.z.array(layoutSchema).max(50).nullable().optional().describe(
|
|
1242
|
+
"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
1243
|
)
|
|
1147
1244
|
},
|
|
1148
1245
|
withErrorHandling(
|
|
@@ -1156,7 +1253,9 @@ function registerTools(target, client, options = {}) {
|
|
|
1156
1253
|
signingSecret,
|
|
1157
1254
|
signingHeader,
|
|
1158
1255
|
showEmailExtracts,
|
|
1159
|
-
|
|
1256
|
+
site,
|
|
1257
|
+
emailFields,
|
|
1258
|
+
layouts
|
|
1160
1259
|
}) => {
|
|
1161
1260
|
const endpoint = await client.endpoints.update(slug, {
|
|
1162
1261
|
name,
|
|
@@ -1167,12 +1266,29 @@ function registerTools(target, client, options = {}) {
|
|
|
1167
1266
|
signingSecret,
|
|
1168
1267
|
signingHeader,
|
|
1169
1268
|
showEmailExtracts,
|
|
1170
|
-
|
|
1269
|
+
site,
|
|
1270
|
+
emailFields,
|
|
1271
|
+
layouts
|
|
1171
1272
|
});
|
|
1172
1273
|
return jsonContent(endpoint);
|
|
1173
1274
|
}
|
|
1174
1275
|
)
|
|
1175
1276
|
);
|
|
1277
|
+
server.tool(
|
|
1278
|
+
"preview_layouts",
|
|
1279
|
+
"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.",
|
|
1280
|
+
{
|
|
1281
|
+
slug: import_zod2.z.string().describe("The endpoint slug"),
|
|
1282
|
+
requestId: import_zod2.z.string().optional().describe("A captured email on the endpoint (default: the newest)"),
|
|
1283
|
+
layouts: import_zod2.z.array(layoutSchema).max(50).optional().describe("Unsaved layouts to try")
|
|
1284
|
+
},
|
|
1285
|
+
withErrorHandling(async ({ slug, requestId, layouts }) => {
|
|
1286
|
+
const preview = await client.endpoints.previewLayouts(slug, { requestId, layouts });
|
|
1287
|
+
const holder = { ...preview };
|
|
1288
|
+
shrinkToFit(holder, MAX_BODY_SIZE, [() => trimParsed(holder)]);
|
|
1289
|
+
return jsonContent(holder);
|
|
1290
|
+
})
|
|
1291
|
+
);
|
|
1176
1292
|
server.tool(
|
|
1177
1293
|
"preview_response",
|
|
1178
1294
|
"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.",
|
|
@@ -2073,7 +2189,8 @@ var AgentSession = class {
|
|
|
2073
2189
|
baseUrl: this.baseUrl,
|
|
2074
2190
|
webhookUrl: this.webhookUrl,
|
|
2075
2191
|
clientName: this.clientName,
|
|
2076
|
-
credentialsFile: this.credentialsFile
|
|
2192
|
+
credentialsFile: this.credentialsFile,
|
|
2193
|
+
userAgent: MCP_USER_AGENT
|
|
2077
2194
|
};
|
|
2078
2195
|
}
|
|
2079
2196
|
/**
|
|
@@ -2166,6 +2283,7 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2166
2283
|
baseUrl,
|
|
2167
2284
|
webhookUrl,
|
|
2168
2285
|
credentialsFile: options.credentialsFile,
|
|
2286
|
+
userAgent: MCP_USER_AGENT,
|
|
2169
2287
|
// The human disconnected the agent: back to the sandbox, unless a
|
|
2170
2288
|
// newer connection is already on display.
|
|
2171
2289
|
onRevoked: () => queueMicrotask(() => {
|
|
@@ -2327,7 +2445,8 @@ function sandboxToolSet(server, session, showAccount) {
|
|
|
2327
2445
|
webhookUrl: session.webhookUrl,
|
|
2328
2446
|
email: normalized,
|
|
2329
2447
|
clientName: session.clientName,
|
|
2330
|
-
credentialsFile: session.credentialsFile
|
|
2448
|
+
credentialsFile: session.credentialsFile,
|
|
2449
|
+
userAgent: MCP_USER_AGENT
|
|
2331
2450
|
}));
|
|
2332
2451
|
session.connect = { via: "connect", pending, email: normalized };
|
|
2333
2452
|
attempt = pending;
|
|
@@ -2436,7 +2555,6 @@ function registerAgentRegistrationTools(server) {
|
|
|
2436
2555
|
}
|
|
2437
2556
|
|
|
2438
2557
|
// src/index.ts
|
|
2439
|
-
var VERSION = true ? "1.15.1" : "0.0.0-dev";
|
|
2440
2558
|
function createServer(options = {}) {
|
|
2441
2559
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
2442
2560
|
const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
|
|
@@ -2451,7 +2569,7 @@ function createServer(options = {}) {
|
|
|
2451
2569
|
registerPrompts(server);
|
|
2452
2570
|
return server;
|
|
2453
2571
|
}
|
|
2454
|
-
const client = new import_sdk3.WebhooksCC({ apiKey, webhookUrl, baseUrl });
|
|
2572
|
+
const client = new import_sdk3.WebhooksCC({ apiKey, webhookUrl, baseUrl, userAgent: MCP_USER_AGENT });
|
|
2455
2573
|
registerTools(server, client);
|
|
2456
2574
|
registerPrompts(server);
|
|
2457
2575
|
registerResources(server, client);
|
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-ZAPNMGCE.mjs";
|
|
17
17
|
|
|
18
18
|
// src/index.ts
|
|
19
19
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -260,6 +260,12 @@ import {
|
|
|
260
260
|
SandboxClient,
|
|
261
261
|
WebhooksCC
|
|
262
262
|
} from "@webhooks-cc/sdk";
|
|
263
|
+
|
|
264
|
+
// src/version.ts
|
|
265
|
+
var VERSION = true ? "1.17.0" : "0.0.0-dev";
|
|
266
|
+
var MCP_USER_AGENT = `webhooks-cc-mcp/${VERSION}`;
|
|
267
|
+
|
|
268
|
+
// src/sandbox-tools.ts
|
|
263
269
|
var SANDBOX = {
|
|
264
270
|
maxEndpoints: 3,
|
|
265
271
|
requestsPerEndpoint: 25,
|
|
@@ -291,7 +297,8 @@ var AgentSession = class {
|
|
|
291
297
|
baseUrl: this.baseUrl,
|
|
292
298
|
webhookUrl: this.webhookUrl,
|
|
293
299
|
clientName: this.clientName,
|
|
294
|
-
credentialsFile: this.credentialsFile
|
|
300
|
+
credentialsFile: this.credentialsFile,
|
|
301
|
+
userAgent: MCP_USER_AGENT
|
|
295
302
|
};
|
|
296
303
|
}
|
|
297
304
|
/**
|
|
@@ -384,6 +391,7 @@ function registerSandboxTools(server, options = {}) {
|
|
|
384
391
|
baseUrl,
|
|
385
392
|
webhookUrl,
|
|
386
393
|
credentialsFile: options.credentialsFile,
|
|
394
|
+
userAgent: MCP_USER_AGENT,
|
|
387
395
|
// The human disconnected the agent: back to the sandbox, unless a
|
|
388
396
|
// newer connection is already on display.
|
|
389
397
|
onRevoked: () => queueMicrotask(() => {
|
|
@@ -545,7 +553,8 @@ function sandboxToolSet(server, session, showAccount) {
|
|
|
545
553
|
webhookUrl: session.webhookUrl,
|
|
546
554
|
email: normalized,
|
|
547
555
|
clientName: session.clientName,
|
|
548
|
-
credentialsFile: session.credentialsFile
|
|
556
|
+
credentialsFile: session.credentialsFile,
|
|
557
|
+
userAgent: MCP_USER_AGENT
|
|
549
558
|
}));
|
|
550
559
|
session.connect = { via: "connect", pending, email: normalized };
|
|
551
560
|
attempt = pending;
|
|
@@ -654,7 +663,6 @@ function registerAgentRegistrationTools(server) {
|
|
|
654
663
|
}
|
|
655
664
|
|
|
656
665
|
// src/index.ts
|
|
657
|
-
var VERSION = true ? "1.15.1" : "0.0.0-dev";
|
|
658
666
|
function createServer(options = {}) {
|
|
659
667
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
660
668
|
const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
|
|
@@ -669,7 +677,7 @@ function createServer(options = {}) {
|
|
|
669
677
|
registerPrompts(server);
|
|
670
678
|
return server;
|
|
671
679
|
}
|
|
672
|
-
const client = new WebhooksCC2({ apiKey, webhookUrl, baseUrl });
|
|
680
|
+
const client = new WebhooksCC2({ apiKey, webhookUrl, baseUrl, userAgent: MCP_USER_AGENT });
|
|
673
681
|
registerTools(server, client);
|
|
674
682
|
registerPrompts(server);
|
|
675
683
|
registerResources(server, client);
|
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 {
|
|
@@ -884,6 +968,18 @@ function registerTools(target, client, options = {}) {
|
|
|
884
968
|
return jsonContent(endpoint);
|
|
885
969
|
})
|
|
886
970
|
);
|
|
971
|
+
server.tool(
|
|
972
|
+
"favorite_endpoint",
|
|
973
|
+
"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.",
|
|
974
|
+
{
|
|
975
|
+
slug: import_zod.z.string().describe("The endpoint slug"),
|
|
976
|
+
favorite: import_zod.z.boolean().optional().describe("true marks it as a favourite (the default), false unmarks it")
|
|
977
|
+
},
|
|
978
|
+
withErrorHandling(async ({ slug, favorite }) => {
|
|
979
|
+
const result = favorite === false ? await client.endpoints.unfavorite(slug) : await client.endpoints.favorite(slug);
|
|
980
|
+
return jsonContent({ slug, favorite: result.favorite });
|
|
981
|
+
})
|
|
982
|
+
);
|
|
887
983
|
server.tool(
|
|
888
984
|
"update_endpoint",
|
|
889
985
|
"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.",
|
|
@@ -901,15 +997,12 @@ function registerTools(target, client, options = {}) {
|
|
|
901
997
|
"Custom signature header name (only for generic-hmac provider), or null to clear"
|
|
902
998
|
),
|
|
903
999
|
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(
|
|
1000
|
+
site: endpointSiteSchema.optional(),
|
|
1001
|
+
emailFields: import_zod.z.array(emailFieldSchema).max(20).nullable().optional().describe(
|
|
912
1002
|
"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."
|
|
1003
|
+
),
|
|
1004
|
+
layouts: import_zod.z.array(layoutSchema).max(50).nullable().optional().describe(
|
|
1005
|
+
"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
1006
|
)
|
|
914
1007
|
},
|
|
915
1008
|
withErrorHandling(
|
|
@@ -923,7 +1016,9 @@ function registerTools(target, client, options = {}) {
|
|
|
923
1016
|
signingSecret,
|
|
924
1017
|
signingHeader,
|
|
925
1018
|
showEmailExtracts,
|
|
926
|
-
|
|
1019
|
+
site,
|
|
1020
|
+
emailFields,
|
|
1021
|
+
layouts
|
|
927
1022
|
}) => {
|
|
928
1023
|
const endpoint = await client.endpoints.update(slug, {
|
|
929
1024
|
name,
|
|
@@ -934,12 +1029,29 @@ function registerTools(target, client, options = {}) {
|
|
|
934
1029
|
signingSecret,
|
|
935
1030
|
signingHeader,
|
|
936
1031
|
showEmailExtracts,
|
|
937
|
-
|
|
1032
|
+
site,
|
|
1033
|
+
emailFields,
|
|
1034
|
+
layouts
|
|
938
1035
|
});
|
|
939
1036
|
return jsonContent(endpoint);
|
|
940
1037
|
}
|
|
941
1038
|
)
|
|
942
1039
|
);
|
|
1040
|
+
server.tool(
|
|
1041
|
+
"preview_layouts",
|
|
1042
|
+
"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.",
|
|
1043
|
+
{
|
|
1044
|
+
slug: import_zod.z.string().describe("The endpoint slug"),
|
|
1045
|
+
requestId: import_zod.z.string().optional().describe("A captured email on the endpoint (default: the newest)"),
|
|
1046
|
+
layouts: import_zod.z.array(layoutSchema).max(50).optional().describe("Unsaved layouts to try")
|
|
1047
|
+
},
|
|
1048
|
+
withErrorHandling(async ({ slug, requestId, layouts }) => {
|
|
1049
|
+
const preview = await client.endpoints.previewLayouts(slug, { requestId, layouts });
|
|
1050
|
+
const holder = { ...preview };
|
|
1051
|
+
shrinkToFit(holder, MAX_BODY_SIZE, [() => trimParsed(holder)]);
|
|
1052
|
+
return jsonContent(holder);
|
|
1053
|
+
})
|
|
1054
|
+
);
|
|
943
1055
|
server.tool(
|
|
944
1056
|
"preview_response",
|
|
945
1057
|
"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.17.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.21.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/node": "^26.6.5",
|