@webhooks-cc/mcp 1.11.0 → 1.12.1

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 CHANGED
@@ -97,13 +97,14 @@ The request tools and resources return emails too (filter with `kind`), without
97
97
 
98
98
  ### Forwarding tools
99
99
 
100
- Forward every captured email to a URL as signed JSON (owner only):
100
+ Forward every captured HTTP request and email to a URL or a Slack or Discord channel, as received,
101
+ as signed JSON or as a chat message, retried and logged (owner only):
101
102
 
102
- - `configure_forwarding`
103
+ - `configure_forwarding` (URL, on/off, kinds, format, your headers, path, retries, order, sent time field)
103
104
  - `get_forwarding_secret` (returns the `whsec_` secret; with `rotate: true`, replaces it)
104
105
  - `test_forwarding`
105
- - `list_deliveries`
106
- - `redeliver_email`
106
+ - `list_deliveries` (the delivery log with its counts, or every try of one request)
107
+ - `redeliver` (one request, or every failed delivery of an endpoint)
107
108
 
108
109
  ### Send and test tools
109
110
 
package/dist/bin/mcp.js CHANGED
@@ -157,6 +157,13 @@ function omitHeaders(holder) {
157
157
  holder.headers = {};
158
158
  holder.headersOmitted = `${count} headers left out to fit the output`;
159
159
  }
160
+ function trimSpam(holder) {
161
+ const spam = holder.spam;
162
+ if (spam && typeof spam === "object" && !Array.isArray(spam)) {
163
+ const { score, required, action, error } = spam;
164
+ holder.spam = { score, required, action, error };
165
+ }
166
+ }
160
167
  function capExtracts(holder) {
161
168
  if (Array.isArray(holder.codes) && holder.codes.length > MAX_EXTRACTS) {
162
169
  holder.codes = holder.codes.slice(0, MAX_EXTRACTS);
@@ -221,7 +228,8 @@ var ESSENTIAL_EMAIL_FIELDS = [
221
228
  "date",
222
229
  "text",
223
230
  "textTruncated",
224
- "htmlSize"
231
+ "htmlSize",
232
+ "spam"
225
233
  ];
226
234
  function compactRequest(request) {
227
235
  if (request.kind !== "email") return request;
@@ -248,6 +256,7 @@ function compactRequest(request) {
248
256
  const budget = MAX_OUTPUT - 128;
249
257
  shrinkToFit(root, budget, [
250
258
  () => omitHeaders(compact),
259
+ () => trimSpam(email),
251
260
  () => capLists(email),
252
261
  () => cutStringToFit(root, email, "text", "textTruncated", budget, TEXT_FLOOR),
253
262
  // Down to the essentials there may be room for more of the text again.
@@ -737,6 +746,7 @@ var ESSENTIAL_DETAIL_FIELDS = [
737
746
  "from",
738
747
  "size",
739
748
  "test",
749
+ "spam",
740
750
  "text",
741
751
  "textTruncated",
742
752
  "htmlSize",
@@ -765,6 +775,7 @@ function emailDetail(client, email, view, includeHtml) {
765
775
  };
766
776
  shrinkToFit(detail, MAX_BODY_SIZE, [
767
777
  () => omitHeaders(detail),
778
+ () => trimSpam(detail),
768
779
  () => capExtracts(detail),
769
780
  () => capLists(detail),
770
781
  () => cutStringToFit(detail, detail, "html", "htmlTruncated", MAX_BODY_SIZE),
@@ -1537,25 +1548,52 @@ function registerTools(server, client) {
1537
1548
  );
1538
1549
  server.tool(
1539
1550
  "configure_forwarding",
1540
- "Set the URL captured emails are forwarded to (as signed JSON) and turn forwarding on or off. Owner only. Saving the first URL creates the signing secret.",
1551
+ "Change an endpoint's forwarding: the URL, on or off, which kinds (HTTP requests, emails), the format, your own headers, and the advanced options. Only the arguments you pass change. Owner only. Saving the first URL creates the signing secret. A Slack or Discord webhook URL gets a chat message automatically.",
1541
1552
  {
1542
1553
  slug: import_zod2.z.string().describe("The endpoint slug"),
1543
1554
  url: httpUrlSchema.nullable().optional().describe("https URL on a public host name, or null to remove it (forwarding must be off)"),
1544
- enabled: import_zod2.z.boolean().optional().describe("Turn forwarding on (needs a URL) or off (fails deliveries still waiting)")
1555
+ enabled: import_zod2.z.boolean().optional().describe("Turn forwarding on (needs a URL) or off (fails deliveries still waiting)"),
1556
+ http: import_zod2.z.boolean().optional().describe("Forward HTTP requests"),
1557
+ email: import_zod2.z.boolean().optional().describe("Forward emails"),
1558
+ format: import_zod2.z.enum(["auto", "as_received", "json", "chat"]).optional().describe(
1559
+ "auto (pick from the URL), as_received (relay HTTP requests unchanged; emails as signed JSON), json (signed JSON envelope for both kinds) or chat (Slack or Discord message)"
1560
+ ),
1561
+ headers: import_zod2.z.array(
1562
+ import_zod2.z.object({
1563
+ name: import_zod2.z.string().describe("Header name, e.g. Authorization"),
1564
+ value: import_zod2.z.string().nullable().describe("Header value, or null to keep the value stored under this name")
1565
+ })
1566
+ ).max(10).optional().describe(
1567
+ "Your own headers for every delivery (not chat). Replaces the whole list; [] removes them"
1568
+ ),
1569
+ appendPath: import_zod2.z.boolean().optional().describe("Add the path after the slug to the URL for relayed requests (default true)"),
1570
+ retrySeconds: import_zod2.z.union([import_zod2.z.literal(0), import_zod2.z.literal(3600), import_zod2.z.literal(86400)]).optional().describe("Retry failed deliveries for 86400 (default), 3600 or 0 seconds"),
1571
+ keepOrder: import_zod2.z.boolean().optional().describe("One delivery at a time, in capture order (default false)"),
1572
+ sentField: import_zod2.z.string().max(128).nullable().optional().describe(
1573
+ "JSON body field with the sender's own timestamp, e.g. publishedAt or data.publishedAt; null clears it"
1574
+ )
1545
1575
  },
1546
- withErrorHandling(async ({ slug, url, enabled }) => {
1547
- const endpoint = await client.forwarding.configure(slug, { url, enabled });
1576
+ withErrorHandling(async ({ slug, ...options }) => {
1577
+ const endpoint = await client.forwarding.configure(slug, options);
1548
1578
  return jsonContent({
1549
1579
  slug: endpoint.slug,
1550
1580
  forwardEnabled: endpoint.forwardEnabled ?? false,
1551
1581
  forwardUrl: endpoint.forwardUrl ?? null,
1552
- hasForwardSecret: endpoint.hasForwardSecret ?? false
1582
+ hasForwardSecret: endpoint.hasForwardSecret ?? false,
1583
+ forwardHttp: endpoint.forwardHttp ?? false,
1584
+ forwardEmail: endpoint.forwardEmail ?? false,
1585
+ forwardFormat: endpoint.forwardFormat ?? "auto",
1586
+ forwardHeaders: endpoint.forwardHeaders ?? [],
1587
+ forwardAppendPath: endpoint.forwardAppendPath ?? true,
1588
+ forwardRetrySeconds: endpoint.forwardRetrySeconds ?? 86400,
1589
+ forwardKeepOrder: endpoint.forwardKeepOrder ?? false,
1590
+ forwardSentField: endpoint.forwardSentField ?? null
1553
1591
  });
1554
1592
  })
1555
1593
  );
1556
1594
  server.tool(
1557
1595
  "get_forwarding_secret",
1558
- "Read (or with rotate: true, replace) the whsec_ secret forwarded emails are signed with. Owner only. Treat the value as a credential: put it in the handler's environment, not in code.",
1596
+ "Read (or with rotate: true, replace) the whsec_ secret forwarded deliveries are signed with. Owner only. Treat the value as a credential: put it in the handler's environment, not in code.",
1559
1597
  {
1560
1598
  slug: import_zod2.z.string().describe("The endpoint slug"),
1561
1599
  rotate: import_zod2.z.boolean().default(false).describe("Replace the secret; every delivery from now on uses the new one")
@@ -1567,35 +1605,57 @@ function registerTools(server, client) {
1567
1605
  );
1568
1606
  server.tool(
1569
1607
  "test_forwarding",
1570
- "Post the endpoint's newest email (or a sample) to its forwarding URL once and report what the server answered. Works with forwarding on or off; nothing is retried or logged.",
1571
- { slug: import_zod2.z.string().describe("The endpoint slug") },
1572
- withErrorHandling(async ({ slug }) => {
1573
- return jsonContent(await client.forwarding.test(slug));
1608
+ "Send the endpoint's newest captured request of a kind (or a sample) to its forwarding URL once, exactly as a delivery would go out, and report the answer. Works with forwarding on or off; nothing is retried or logged.",
1609
+ {
1610
+ slug: import_zod2.z.string().describe("The endpoint slug"),
1611
+ kind: import_zod2.z.enum(["http", "email"]).optional().describe(
1612
+ "Which kind to send (default: http when HTTP requests are forwarded, else email)"
1613
+ )
1614
+ },
1615
+ withErrorHandling(async ({ slug, kind }) => {
1616
+ return jsonContent(await client.forwarding.test(slug, kind ? { kind } : {}));
1574
1617
  })
1575
1618
  );
1576
1619
  server.tool(
1577
1620
  "list_deliveries",
1578
- "List forwarding deliveries: an endpoint's latest (endpointSlug), or every try of one email (requestId).",
1621
+ "List forwarding deliveries: an endpoint's delivery log with its counts (endpointSlug), or every delivery and try of one captured request or email (requestId). Rows carry senderAt when the request had the sender's own timestamp, so receivedAt - senderAt is the time to reach webhooks.cc.",
1579
1622
  {
1580
- endpointSlug: import_zod2.z.string().optional().describe("List the endpoint's latest deliveries"),
1581
- requestId: import_zod2.z.string().optional().describe("List every delivery and try of this email"),
1582
- limit: import_zod2.z.number().int().min(1).max(20).default(5).describe("With endpointSlug: max deliveries")
1623
+ endpointSlug: import_zod2.z.string().optional().describe("List the endpoint's delivery log"),
1624
+ requestId: import_zod2.z.string().optional().describe("List every delivery and try of this request"),
1625
+ limit: import_zod2.z.number().int().min(1).max(100).default(20).describe("With endpointSlug: max rows"),
1626
+ status: import_zod2.z.enum(["all", "pending", "failed"]).default("all").describe("With endpointSlug: all, pending (queued or retrying) or failed"),
1627
+ before: import_zod2.z.string().optional().describe("With endpointSlug: a row's cursor, to list older rows")
1583
1628
  },
1584
- withErrorHandling(async ({ endpointSlug, requestId, limit }) => {
1629
+ withErrorHandling(async ({ endpointSlug, requestId, limit, status, before }) => {
1585
1630
  if (requestId === void 0 === (endpointSlug === void 0)) {
1586
1631
  throw new Error("Pass exactly one of endpointSlug or requestId");
1587
1632
  }
1588
- return jsonContent(
1589
- requestId ? await client.forwarding.emailDeliveries(requestId) : await client.forwarding.deliveries(endpointSlug, { limit })
1590
- );
1633
+ if (requestId) return jsonContent(await client.forwarding.requestDeliveries(requestId));
1634
+ const [summary, deliveries] = await Promise.all([
1635
+ client.forwarding.summary(endpointSlug),
1636
+ client.forwarding.deliveries(endpointSlug, { limit, status, before })
1637
+ ]);
1638
+ return jsonContent({
1639
+ summary,
1640
+ deliveries,
1641
+ nextCursor: deliveries.length === limit ? deliveries[deliveries.length - 1]?.cursor ?? null : null
1642
+ });
1591
1643
  })
1592
1644
  );
1593
1645
  server.tool(
1594
- "redeliver_email",
1595
- "Forward a captured email again with the endpoint's current URL and secret. Owner only; fails while forwarding is off.",
1596
- { requestId: import_zod2.z.string().describe("The email's request ID") },
1597
- withErrorHandling(async ({ requestId }) => {
1598
- return jsonContent(await client.forwarding.redeliver(requestId));
1646
+ "redeliver",
1647
+ "Forward again with the endpoint's current settings: one captured request or email (requestId; it also sends one that was never forwarded), or every request whose latest delivery failed (endpointSlug, oldest first, up to 1,000 at a time). Owner only; fails while forwarding is off.",
1648
+ {
1649
+ requestId: import_zod2.z.string().optional().describe("The request or email to send again"),
1650
+ endpointSlug: import_zod2.z.string().optional().describe("Send every failed delivery of this endpoint again")
1651
+ },
1652
+ withErrorHandling(async ({ requestId, endpointSlug }) => {
1653
+ if (requestId === void 0 === (endpointSlug === void 0)) {
1654
+ throw new Error("Pass exactly one of requestId or endpointSlug");
1655
+ }
1656
+ return jsonContent(
1657
+ requestId ? await client.forwarding.redeliver(requestId) : await client.forwarding.redeliverFailed(endpointSlug)
1658
+ );
1599
1659
  })
1600
1660
  );
1601
1661
  server.tool(
@@ -1974,7 +2034,7 @@ function registerSandboxTools(server, options = {}) {
1974
2034
  }
1975
2035
 
1976
2036
  // src/index.ts
1977
- var VERSION = true ? "1.11.0" : "0.0.0-dev";
2037
+ var VERSION = true ? "1.12.1" : "0.0.0-dev";
1978
2038
  function createServer(options = {}) {
1979
2039
  const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
1980
2040
  const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
package/dist/index.js CHANGED
@@ -180,6 +180,13 @@ function omitHeaders(holder) {
180
180
  holder.headers = {};
181
181
  holder.headersOmitted = `${count} headers left out to fit the output`;
182
182
  }
183
+ function trimSpam(holder) {
184
+ const spam = holder.spam;
185
+ if (spam && typeof spam === "object" && !Array.isArray(spam)) {
186
+ const { score, required, action, error } = spam;
187
+ holder.spam = { score, required, action, error };
188
+ }
189
+ }
183
190
  function capExtracts(holder) {
184
191
  if (Array.isArray(holder.codes) && holder.codes.length > MAX_EXTRACTS) {
185
192
  holder.codes = holder.codes.slice(0, MAX_EXTRACTS);
@@ -244,7 +251,8 @@ var ESSENTIAL_EMAIL_FIELDS = [
244
251
  "date",
245
252
  "text",
246
253
  "textTruncated",
247
- "htmlSize"
254
+ "htmlSize",
255
+ "spam"
248
256
  ];
249
257
  function compactRequest(request) {
250
258
  if (request.kind !== "email") return request;
@@ -271,6 +279,7 @@ function compactRequest(request) {
271
279
  const budget = MAX_OUTPUT - 128;
272
280
  shrinkToFit(root, budget, [
273
281
  () => omitHeaders(compact),
282
+ () => trimSpam(email),
274
283
  () => capLists(email),
275
284
  () => cutStringToFit(root, email, "text", "textTruncated", budget, TEXT_FLOOR),
276
285
  // Down to the essentials there may be room for more of the text again.
@@ -760,6 +769,7 @@ var ESSENTIAL_DETAIL_FIELDS = [
760
769
  "from",
761
770
  "size",
762
771
  "test",
772
+ "spam",
763
773
  "text",
764
774
  "textTruncated",
765
775
  "htmlSize",
@@ -788,6 +798,7 @@ function emailDetail(client, email, view, includeHtml) {
788
798
  };
789
799
  shrinkToFit(detail, MAX_BODY_SIZE, [
790
800
  () => omitHeaders(detail),
801
+ () => trimSpam(detail),
791
802
  () => capExtracts(detail),
792
803
  () => capLists(detail),
793
804
  () => cutStringToFit(detail, detail, "html", "htmlTruncated", MAX_BODY_SIZE),
@@ -1560,25 +1571,52 @@ function registerTools(server, client) {
1560
1571
  );
1561
1572
  server.tool(
1562
1573
  "configure_forwarding",
1563
- "Set the URL captured emails are forwarded to (as signed JSON) and turn forwarding on or off. Owner only. Saving the first URL creates the signing secret.",
1574
+ "Change an endpoint's forwarding: the URL, on or off, which kinds (HTTP requests, emails), the format, your own headers, and the advanced options. Only the arguments you pass change. Owner only. Saving the first URL creates the signing secret. A Slack or Discord webhook URL gets a chat message automatically.",
1564
1575
  {
1565
1576
  slug: import_zod2.z.string().describe("The endpoint slug"),
1566
1577
  url: httpUrlSchema.nullable().optional().describe("https URL on a public host name, or null to remove it (forwarding must be off)"),
1567
- enabled: import_zod2.z.boolean().optional().describe("Turn forwarding on (needs a URL) or off (fails deliveries still waiting)")
1578
+ enabled: import_zod2.z.boolean().optional().describe("Turn forwarding on (needs a URL) or off (fails deliveries still waiting)"),
1579
+ http: import_zod2.z.boolean().optional().describe("Forward HTTP requests"),
1580
+ email: import_zod2.z.boolean().optional().describe("Forward emails"),
1581
+ format: import_zod2.z.enum(["auto", "as_received", "json", "chat"]).optional().describe(
1582
+ "auto (pick from the URL), as_received (relay HTTP requests unchanged; emails as signed JSON), json (signed JSON envelope for both kinds) or chat (Slack or Discord message)"
1583
+ ),
1584
+ headers: import_zod2.z.array(
1585
+ import_zod2.z.object({
1586
+ name: import_zod2.z.string().describe("Header name, e.g. Authorization"),
1587
+ value: import_zod2.z.string().nullable().describe("Header value, or null to keep the value stored under this name")
1588
+ })
1589
+ ).max(10).optional().describe(
1590
+ "Your own headers for every delivery (not chat). Replaces the whole list; [] removes them"
1591
+ ),
1592
+ appendPath: import_zod2.z.boolean().optional().describe("Add the path after the slug to the URL for relayed requests (default true)"),
1593
+ retrySeconds: import_zod2.z.union([import_zod2.z.literal(0), import_zod2.z.literal(3600), import_zod2.z.literal(86400)]).optional().describe("Retry failed deliveries for 86400 (default), 3600 or 0 seconds"),
1594
+ keepOrder: import_zod2.z.boolean().optional().describe("One delivery at a time, in capture order (default false)"),
1595
+ sentField: import_zod2.z.string().max(128).nullable().optional().describe(
1596
+ "JSON body field with the sender's own timestamp, e.g. publishedAt or data.publishedAt; null clears it"
1597
+ )
1568
1598
  },
1569
- withErrorHandling(async ({ slug, url, enabled }) => {
1570
- const endpoint = await client.forwarding.configure(slug, { url, enabled });
1599
+ withErrorHandling(async ({ slug, ...options }) => {
1600
+ const endpoint = await client.forwarding.configure(slug, options);
1571
1601
  return jsonContent({
1572
1602
  slug: endpoint.slug,
1573
1603
  forwardEnabled: endpoint.forwardEnabled ?? false,
1574
1604
  forwardUrl: endpoint.forwardUrl ?? null,
1575
- hasForwardSecret: endpoint.hasForwardSecret ?? false
1605
+ hasForwardSecret: endpoint.hasForwardSecret ?? false,
1606
+ forwardHttp: endpoint.forwardHttp ?? false,
1607
+ forwardEmail: endpoint.forwardEmail ?? false,
1608
+ forwardFormat: endpoint.forwardFormat ?? "auto",
1609
+ forwardHeaders: endpoint.forwardHeaders ?? [],
1610
+ forwardAppendPath: endpoint.forwardAppendPath ?? true,
1611
+ forwardRetrySeconds: endpoint.forwardRetrySeconds ?? 86400,
1612
+ forwardKeepOrder: endpoint.forwardKeepOrder ?? false,
1613
+ forwardSentField: endpoint.forwardSentField ?? null
1576
1614
  });
1577
1615
  })
1578
1616
  );
1579
1617
  server.tool(
1580
1618
  "get_forwarding_secret",
1581
- "Read (or with rotate: true, replace) the whsec_ secret forwarded emails are signed with. Owner only. Treat the value as a credential: put it in the handler's environment, not in code.",
1619
+ "Read (or with rotate: true, replace) the whsec_ secret forwarded deliveries are signed with. Owner only. Treat the value as a credential: put it in the handler's environment, not in code.",
1582
1620
  {
1583
1621
  slug: import_zod2.z.string().describe("The endpoint slug"),
1584
1622
  rotate: import_zod2.z.boolean().default(false).describe("Replace the secret; every delivery from now on uses the new one")
@@ -1590,35 +1628,57 @@ function registerTools(server, client) {
1590
1628
  );
1591
1629
  server.tool(
1592
1630
  "test_forwarding",
1593
- "Post the endpoint's newest email (or a sample) to its forwarding URL once and report what the server answered. Works with forwarding on or off; nothing is retried or logged.",
1594
- { slug: import_zod2.z.string().describe("The endpoint slug") },
1595
- withErrorHandling(async ({ slug }) => {
1596
- return jsonContent(await client.forwarding.test(slug));
1631
+ "Send the endpoint's newest captured request of a kind (or a sample) to its forwarding URL once, exactly as a delivery would go out, and report the answer. Works with forwarding on or off; nothing is retried or logged.",
1632
+ {
1633
+ slug: import_zod2.z.string().describe("The endpoint slug"),
1634
+ kind: import_zod2.z.enum(["http", "email"]).optional().describe(
1635
+ "Which kind to send (default: http when HTTP requests are forwarded, else email)"
1636
+ )
1637
+ },
1638
+ withErrorHandling(async ({ slug, kind }) => {
1639
+ return jsonContent(await client.forwarding.test(slug, kind ? { kind } : {}));
1597
1640
  })
1598
1641
  );
1599
1642
  server.tool(
1600
1643
  "list_deliveries",
1601
- "List forwarding deliveries: an endpoint's latest (endpointSlug), or every try of one email (requestId).",
1644
+ "List forwarding deliveries: an endpoint's delivery log with its counts (endpointSlug), or every delivery and try of one captured request or email (requestId). Rows carry senderAt when the request had the sender's own timestamp, so receivedAt - senderAt is the time to reach webhooks.cc.",
1602
1645
  {
1603
- endpointSlug: import_zod2.z.string().optional().describe("List the endpoint's latest deliveries"),
1604
- requestId: import_zod2.z.string().optional().describe("List every delivery and try of this email"),
1605
- limit: import_zod2.z.number().int().min(1).max(20).default(5).describe("With endpointSlug: max deliveries")
1646
+ endpointSlug: import_zod2.z.string().optional().describe("List the endpoint's delivery log"),
1647
+ requestId: import_zod2.z.string().optional().describe("List every delivery and try of this request"),
1648
+ limit: import_zod2.z.number().int().min(1).max(100).default(20).describe("With endpointSlug: max rows"),
1649
+ status: import_zod2.z.enum(["all", "pending", "failed"]).default("all").describe("With endpointSlug: all, pending (queued or retrying) or failed"),
1650
+ before: import_zod2.z.string().optional().describe("With endpointSlug: a row's cursor, to list older rows")
1606
1651
  },
1607
- withErrorHandling(async ({ endpointSlug, requestId, limit }) => {
1652
+ withErrorHandling(async ({ endpointSlug, requestId, limit, status, before }) => {
1608
1653
  if (requestId === void 0 === (endpointSlug === void 0)) {
1609
1654
  throw new Error("Pass exactly one of endpointSlug or requestId");
1610
1655
  }
1611
- return jsonContent(
1612
- requestId ? await client.forwarding.emailDeliveries(requestId) : await client.forwarding.deliveries(endpointSlug, { limit })
1613
- );
1656
+ if (requestId) return jsonContent(await client.forwarding.requestDeliveries(requestId));
1657
+ const [summary, deliveries] = await Promise.all([
1658
+ client.forwarding.summary(endpointSlug),
1659
+ client.forwarding.deliveries(endpointSlug, { limit, status, before })
1660
+ ]);
1661
+ return jsonContent({
1662
+ summary,
1663
+ deliveries,
1664
+ nextCursor: deliveries.length === limit ? deliveries[deliveries.length - 1]?.cursor ?? null : null
1665
+ });
1614
1666
  })
1615
1667
  );
1616
1668
  server.tool(
1617
- "redeliver_email",
1618
- "Forward a captured email again with the endpoint's current URL and secret. Owner only; fails while forwarding is off.",
1619
- { requestId: import_zod2.z.string().describe("The email's request ID") },
1620
- withErrorHandling(async ({ requestId }) => {
1621
- return jsonContent(await client.forwarding.redeliver(requestId));
1669
+ "redeliver",
1670
+ "Forward again with the endpoint's current settings: one captured request or email (requestId; it also sends one that was never forwarded), or every request whose latest delivery failed (endpointSlug, oldest first, up to 1,000 at a time). Owner only; fails while forwarding is off.",
1671
+ {
1672
+ requestId: import_zod2.z.string().optional().describe("The request or email to send again"),
1673
+ endpointSlug: import_zod2.z.string().optional().describe("Send every failed delivery of this endpoint again")
1674
+ },
1675
+ withErrorHandling(async ({ requestId, endpointSlug }) => {
1676
+ if (requestId === void 0 === (endpointSlug === void 0)) {
1677
+ throw new Error("Pass exactly one of requestId or endpointSlug");
1678
+ }
1679
+ return jsonContent(
1680
+ requestId ? await client.forwarding.redeliver(requestId) : await client.forwarding.redeliverFailed(endpointSlug)
1681
+ );
1622
1682
  })
1623
1683
  );
1624
1684
  server.tool(
@@ -2000,7 +2060,7 @@ function registerAgentRegistrationTools(server) {
2000
2060
  }
2001
2061
 
2002
2062
  // src/index.ts
2003
- var VERSION = true ? "1.11.0" : "0.0.0-dev";
2063
+ var VERSION = true ? "1.12.1" : "0.0.0-dev";
2004
2064
  function createServer(options = {}) {
2005
2065
  const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
2006
2066
  const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
package/dist/index.mjs CHANGED
@@ -151,6 +151,13 @@ function omitHeaders(holder) {
151
151
  holder.headers = {};
152
152
  holder.headersOmitted = `${count} headers left out to fit the output`;
153
153
  }
154
+ function trimSpam(holder) {
155
+ const spam = holder.spam;
156
+ if (spam && typeof spam === "object" && !Array.isArray(spam)) {
157
+ const { score, required, action, error } = spam;
158
+ holder.spam = { score, required, action, error };
159
+ }
160
+ }
154
161
  function capExtracts(holder) {
155
162
  if (Array.isArray(holder.codes) && holder.codes.length > MAX_EXTRACTS) {
156
163
  holder.codes = holder.codes.slice(0, MAX_EXTRACTS);
@@ -215,7 +222,8 @@ var ESSENTIAL_EMAIL_FIELDS = [
215
222
  "date",
216
223
  "text",
217
224
  "textTruncated",
218
- "htmlSize"
225
+ "htmlSize",
226
+ "spam"
219
227
  ];
220
228
  function compactRequest(request) {
221
229
  if (request.kind !== "email") return request;
@@ -242,6 +250,7 @@ function compactRequest(request) {
242
250
  const budget = MAX_OUTPUT - 128;
243
251
  shrinkToFit(root, budget, [
244
252
  () => omitHeaders(compact),
253
+ () => trimSpam(email),
245
254
  () => capLists(email),
246
255
  () => cutStringToFit(root, email, "text", "textTruncated", budget, TEXT_FLOOR),
247
256
  // Down to the essentials there may be room for more of the text again.
@@ -757,6 +766,7 @@ var ESSENTIAL_DETAIL_FIELDS = [
757
766
  "from",
758
767
  "size",
759
768
  "test",
769
+ "spam",
760
770
  "text",
761
771
  "textTruncated",
762
772
  "htmlSize",
@@ -785,6 +795,7 @@ function emailDetail(client, email, view, includeHtml) {
785
795
  };
786
796
  shrinkToFit(detail, MAX_BODY_SIZE, [
787
797
  () => omitHeaders(detail),
798
+ () => trimSpam(detail),
788
799
  () => capExtracts(detail),
789
800
  () => capLists(detail),
790
801
  () => cutStringToFit(detail, detail, "html", "htmlTruncated", MAX_BODY_SIZE),
@@ -1557,25 +1568,52 @@ function registerTools(server, client) {
1557
1568
  );
1558
1569
  server.tool(
1559
1570
  "configure_forwarding",
1560
- "Set the URL captured emails are forwarded to (as signed JSON) and turn forwarding on or off. Owner only. Saving the first URL creates the signing secret.",
1571
+ "Change an endpoint's forwarding: the URL, on or off, which kinds (HTTP requests, emails), the format, your own headers, and the advanced options. Only the arguments you pass change. Owner only. Saving the first URL creates the signing secret. A Slack or Discord webhook URL gets a chat message automatically.",
1561
1572
  {
1562
1573
  slug: z2.string().describe("The endpoint slug"),
1563
1574
  url: httpUrlSchema.nullable().optional().describe("https URL on a public host name, or null to remove it (forwarding must be off)"),
1564
- enabled: z2.boolean().optional().describe("Turn forwarding on (needs a URL) or off (fails deliveries still waiting)")
1575
+ enabled: z2.boolean().optional().describe("Turn forwarding on (needs a URL) or off (fails deliveries still waiting)"),
1576
+ http: z2.boolean().optional().describe("Forward HTTP requests"),
1577
+ email: z2.boolean().optional().describe("Forward emails"),
1578
+ format: z2.enum(["auto", "as_received", "json", "chat"]).optional().describe(
1579
+ "auto (pick from the URL), as_received (relay HTTP requests unchanged; emails as signed JSON), json (signed JSON envelope for both kinds) or chat (Slack or Discord message)"
1580
+ ),
1581
+ headers: z2.array(
1582
+ z2.object({
1583
+ name: z2.string().describe("Header name, e.g. Authorization"),
1584
+ value: z2.string().nullable().describe("Header value, or null to keep the value stored under this name")
1585
+ })
1586
+ ).max(10).optional().describe(
1587
+ "Your own headers for every delivery (not chat). Replaces the whole list; [] removes them"
1588
+ ),
1589
+ appendPath: z2.boolean().optional().describe("Add the path after the slug to the URL for relayed requests (default true)"),
1590
+ retrySeconds: z2.union([z2.literal(0), z2.literal(3600), z2.literal(86400)]).optional().describe("Retry failed deliveries for 86400 (default), 3600 or 0 seconds"),
1591
+ keepOrder: z2.boolean().optional().describe("One delivery at a time, in capture order (default false)"),
1592
+ sentField: z2.string().max(128).nullable().optional().describe(
1593
+ "JSON body field with the sender's own timestamp, e.g. publishedAt or data.publishedAt; null clears it"
1594
+ )
1565
1595
  },
1566
- withErrorHandling(async ({ slug, url, enabled }) => {
1567
- const endpoint = await client.forwarding.configure(slug, { url, enabled });
1596
+ withErrorHandling(async ({ slug, ...options }) => {
1597
+ const endpoint = await client.forwarding.configure(slug, options);
1568
1598
  return jsonContent({
1569
1599
  slug: endpoint.slug,
1570
1600
  forwardEnabled: endpoint.forwardEnabled ?? false,
1571
1601
  forwardUrl: endpoint.forwardUrl ?? null,
1572
- hasForwardSecret: endpoint.hasForwardSecret ?? false
1602
+ hasForwardSecret: endpoint.hasForwardSecret ?? false,
1603
+ forwardHttp: endpoint.forwardHttp ?? false,
1604
+ forwardEmail: endpoint.forwardEmail ?? false,
1605
+ forwardFormat: endpoint.forwardFormat ?? "auto",
1606
+ forwardHeaders: endpoint.forwardHeaders ?? [],
1607
+ forwardAppendPath: endpoint.forwardAppendPath ?? true,
1608
+ forwardRetrySeconds: endpoint.forwardRetrySeconds ?? 86400,
1609
+ forwardKeepOrder: endpoint.forwardKeepOrder ?? false,
1610
+ forwardSentField: endpoint.forwardSentField ?? null
1573
1611
  });
1574
1612
  })
1575
1613
  );
1576
1614
  server.tool(
1577
1615
  "get_forwarding_secret",
1578
- "Read (or with rotate: true, replace) the whsec_ secret forwarded emails are signed with. Owner only. Treat the value as a credential: put it in the handler's environment, not in code.",
1616
+ "Read (or with rotate: true, replace) the whsec_ secret forwarded deliveries are signed with. Owner only. Treat the value as a credential: put it in the handler's environment, not in code.",
1579
1617
  {
1580
1618
  slug: z2.string().describe("The endpoint slug"),
1581
1619
  rotate: z2.boolean().default(false).describe("Replace the secret; every delivery from now on uses the new one")
@@ -1587,35 +1625,57 @@ function registerTools(server, client) {
1587
1625
  );
1588
1626
  server.tool(
1589
1627
  "test_forwarding",
1590
- "Post the endpoint's newest email (or a sample) to its forwarding URL once and report what the server answered. Works with forwarding on or off; nothing is retried or logged.",
1591
- { slug: z2.string().describe("The endpoint slug") },
1592
- withErrorHandling(async ({ slug }) => {
1593
- return jsonContent(await client.forwarding.test(slug));
1628
+ "Send the endpoint's newest captured request of a kind (or a sample) to its forwarding URL once, exactly as a delivery would go out, and report the answer. Works with forwarding on or off; nothing is retried or logged.",
1629
+ {
1630
+ slug: z2.string().describe("The endpoint slug"),
1631
+ kind: z2.enum(["http", "email"]).optional().describe(
1632
+ "Which kind to send (default: http when HTTP requests are forwarded, else email)"
1633
+ )
1634
+ },
1635
+ withErrorHandling(async ({ slug, kind }) => {
1636
+ return jsonContent(await client.forwarding.test(slug, kind ? { kind } : {}));
1594
1637
  })
1595
1638
  );
1596
1639
  server.tool(
1597
1640
  "list_deliveries",
1598
- "List forwarding deliveries: an endpoint's latest (endpointSlug), or every try of one email (requestId).",
1641
+ "List forwarding deliveries: an endpoint's delivery log with its counts (endpointSlug), or every delivery and try of one captured request or email (requestId). Rows carry senderAt when the request had the sender's own timestamp, so receivedAt - senderAt is the time to reach webhooks.cc.",
1599
1642
  {
1600
- endpointSlug: z2.string().optional().describe("List the endpoint's latest deliveries"),
1601
- requestId: z2.string().optional().describe("List every delivery and try of this email"),
1602
- limit: z2.number().int().min(1).max(20).default(5).describe("With endpointSlug: max deliveries")
1643
+ endpointSlug: z2.string().optional().describe("List the endpoint's delivery log"),
1644
+ requestId: z2.string().optional().describe("List every delivery and try of this request"),
1645
+ limit: z2.number().int().min(1).max(100).default(20).describe("With endpointSlug: max rows"),
1646
+ status: z2.enum(["all", "pending", "failed"]).default("all").describe("With endpointSlug: all, pending (queued or retrying) or failed"),
1647
+ before: z2.string().optional().describe("With endpointSlug: a row's cursor, to list older rows")
1603
1648
  },
1604
- withErrorHandling(async ({ endpointSlug, requestId, limit }) => {
1649
+ withErrorHandling(async ({ endpointSlug, requestId, limit, status, before }) => {
1605
1650
  if (requestId === void 0 === (endpointSlug === void 0)) {
1606
1651
  throw new Error("Pass exactly one of endpointSlug or requestId");
1607
1652
  }
1608
- return jsonContent(
1609
- requestId ? await client.forwarding.emailDeliveries(requestId) : await client.forwarding.deliveries(endpointSlug, { limit })
1610
- );
1653
+ if (requestId) return jsonContent(await client.forwarding.requestDeliveries(requestId));
1654
+ const [summary, deliveries] = await Promise.all([
1655
+ client.forwarding.summary(endpointSlug),
1656
+ client.forwarding.deliveries(endpointSlug, { limit, status, before })
1657
+ ]);
1658
+ return jsonContent({
1659
+ summary,
1660
+ deliveries,
1661
+ nextCursor: deliveries.length === limit ? deliveries[deliveries.length - 1]?.cursor ?? null : null
1662
+ });
1611
1663
  })
1612
1664
  );
1613
1665
  server.tool(
1614
- "redeliver_email",
1615
- "Forward a captured email again with the endpoint's current URL and secret. Owner only; fails while forwarding is off.",
1616
- { requestId: z2.string().describe("The email's request ID") },
1617
- withErrorHandling(async ({ requestId }) => {
1618
- return jsonContent(await client.forwarding.redeliver(requestId));
1666
+ "redeliver",
1667
+ "Forward again with the endpoint's current settings: one captured request or email (requestId; it also sends one that was never forwarded), or every request whose latest delivery failed (endpointSlug, oldest first, up to 1,000 at a time). Owner only; fails while forwarding is off.",
1668
+ {
1669
+ requestId: z2.string().optional().describe("The request or email to send again"),
1670
+ endpointSlug: z2.string().optional().describe("Send every failed delivery of this endpoint again")
1671
+ },
1672
+ withErrorHandling(async ({ requestId, endpointSlug }) => {
1673
+ if (requestId === void 0 === (endpointSlug === void 0)) {
1674
+ throw new Error("Pass exactly one of requestId or endpointSlug");
1675
+ }
1676
+ return jsonContent(
1677
+ requestId ? await client.forwarding.redeliver(requestId) : await client.forwarding.redeliverFailed(endpointSlug)
1678
+ );
1619
1679
  })
1620
1680
  );
1621
1681
  server.tool(
@@ -1997,7 +2057,7 @@ function registerAgentRegistrationTools(server) {
1997
2057
  }
1998
2058
 
1999
2059
  // src/index.ts
2000
- var VERSION = true ? "1.11.0" : "0.0.0-dev";
2060
+ var VERSION = true ? "1.12.1" : "0.0.0-dev";
2001
2061
  function createServer(options = {}) {
2002
2062
  const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
2003
2063
  const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webhooks-cc/mcp",
3
- "version": "1.11.0",
3
+ "version": "1.12.1",
4
4
  "description": "MCP server for webhooks.cc — AI agent integration for webhook testing",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -38,7 +38,7 @@
38
38
  "dependencies": {
39
39
  "@modelcontextprotocol/sdk": "^1.31.0",
40
40
  "zod": "^4.6.5",
41
- "@webhooks-cc/sdk": "1.13.0"
41
+ "@webhooks-cc/sdk": "1.15.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "^26.6.4",