@webhooks-cc/mcp 1.1.0 → 1.3.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/dist/bin/mcp.js +166 -51
- package/dist/index.js +166 -51
- package/dist/index.mjs +177 -51
- package/package.json +6 -5
package/dist/bin/mcp.js
CHANGED
|
@@ -245,35 +245,6 @@ var import_zod2 = require("zod");
|
|
|
245
245
|
var import_sdk = require("@webhooks-cc/sdk");
|
|
246
246
|
var MAX_BODY_SIZE = 32768;
|
|
247
247
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
248
|
-
var TEMPLATE_PROVIDER_VALUES = [
|
|
249
|
-
"stripe",
|
|
250
|
-
"github",
|
|
251
|
-
"shopify",
|
|
252
|
-
"twilio",
|
|
253
|
-
"slack",
|
|
254
|
-
"paddle",
|
|
255
|
-
"linear",
|
|
256
|
-
"sendgrid",
|
|
257
|
-
"clerk",
|
|
258
|
-
"discord",
|
|
259
|
-
"vercel",
|
|
260
|
-
"gitlab",
|
|
261
|
-
"standard-webhooks"
|
|
262
|
-
];
|
|
263
|
-
var VERIFY_PROVIDER_VALUES = [
|
|
264
|
-
"stripe",
|
|
265
|
-
"github",
|
|
266
|
-
"shopify",
|
|
267
|
-
"twilio",
|
|
268
|
-
"slack",
|
|
269
|
-
"paddle",
|
|
270
|
-
"linear",
|
|
271
|
-
"clerk",
|
|
272
|
-
"discord",
|
|
273
|
-
"vercel",
|
|
274
|
-
"gitlab",
|
|
275
|
-
"standard-webhooks"
|
|
276
|
-
];
|
|
277
248
|
var TIME_SEPARATOR = " \u2014 ";
|
|
278
249
|
var httpUrlSchema = import_zod2.z.string().url().refine(
|
|
279
250
|
(value) => {
|
|
@@ -288,11 +259,43 @@ var httpUrlSchema = import_zod2.z.string().url().refine(
|
|
|
288
259
|
);
|
|
289
260
|
var methodSchema = import_zod2.z.enum(HTTP_METHODS).default("POST").describe("HTTP method (default: POST)");
|
|
290
261
|
var durationOrTimestampSchema = import_zod2.z.union([import_zod2.z.string(), import_zod2.z.number()]);
|
|
262
|
+
var ruleConditionSchema = import_zod2.z.object({
|
|
263
|
+
field: import_zod2.z.enum(["method", "path", "header", "body_contains", "body_path", "query"]).describe("Request field to match against"),
|
|
264
|
+
op: import_zod2.z.enum(["eq", "contains", "starts_with", "matches", "exists"]).describe("Comparison operator"),
|
|
265
|
+
value: import_zod2.z.string().max(import_sdk.MAX_CONDITION_VALUE_LEN).optional().describe(
|
|
266
|
+
`Value to compare against (required unless op is "exists", max ${import_sdk.MAX_CONDITION_VALUE_LEN} chars)`
|
|
267
|
+
),
|
|
268
|
+
name: import_zod2.z.string().max(import_sdk.MAX_CONDITION_NAME_LEN).optional().describe(
|
|
269
|
+
`Header name or query param name (required for header/query, max ${import_sdk.MAX_CONDITION_NAME_LEN} chars)`
|
|
270
|
+
),
|
|
271
|
+
path: import_zod2.z.string().max(import_sdk.MAX_CONDITION_PATH_LEN).optional().describe(
|
|
272
|
+
`JSON dot-notation path (required for body_path, max ${import_sdk.MAX_CONDITION_PATH_LEN} chars)`
|
|
273
|
+
)
|
|
274
|
+
}).refine((c) => c.op === "exists" || c.value !== void 0 && c.value.length > 0, {
|
|
275
|
+
message: 'value is required when op is not "exists"'
|
|
276
|
+
}).refine(
|
|
277
|
+
(c) => !(c.field === "header" || c.field === "query") || c.name !== void 0 && c.name.length > 0,
|
|
278
|
+
{ message: "name is required for header and query conditions" }
|
|
279
|
+
).refine((c) => c.field !== "body_path" || c.path !== void 0 && c.path.length > 0, {
|
|
280
|
+
message: "path is required for body_path conditions"
|
|
281
|
+
}).refine((c) => c.op !== "matches" || !c.value || c.value.length <= import_sdk.MAX_GLOB_PATTERN_LEN, {
|
|
282
|
+
message: `matches pattern must be ${import_sdk.MAX_GLOB_PATTERN_LEN} chars or less`
|
|
283
|
+
});
|
|
284
|
+
var responseRuleSchema = import_zod2.z.object({
|
|
285
|
+
name: import_zod2.z.string().max(import_sdk.MAX_RULE_NAME_LEN).optional().describe(`Human-readable rule name (max ${import_sdk.MAX_RULE_NAME_LEN} chars)`),
|
|
286
|
+
enabled: import_zod2.z.boolean().optional().default(true).describe("Whether this rule is active"),
|
|
287
|
+
logic: import_zod2.z.enum(["and", "or"]).optional().default("and").describe('How to combine conditions: "and" (all match) or "or" (any match)'),
|
|
288
|
+
conditions: import_zod2.z.array(ruleConditionSchema).min(1).max(10).describe("Conditions to evaluate (1-10)"),
|
|
289
|
+
response: import_zod2.z.lazy(() => mockResponseSchema).describe("Response when conditions match")
|
|
290
|
+
});
|
|
291
|
+
var responseRulesSchema = import_zod2.z.array(responseRuleSchema).max(50).describe("Ordered conditional response rules (first match wins, max 50)");
|
|
291
292
|
var mockResponseSchema = import_zod2.z.object({
|
|
292
|
-
status: import_zod2.z.number().int().min(
|
|
293
|
+
status: import_zod2.z.number().int().min(import_sdk.MOCK_RESPONSE_STATUS_MIN).max(import_sdk.MOCK_RESPONSE_STATUS_MAX).describe(`HTTP status code (${import_sdk.MOCK_RESPONSE_STATUS_MIN}-${import_sdk.MOCK_RESPONSE_STATUS_MAX})`),
|
|
293
294
|
body: import_zod2.z.string().default("").describe("Response body string (default: empty)"),
|
|
294
295
|
headers: import_zod2.z.record(import_zod2.z.string()).default({}).describe("Response headers (default: none)"),
|
|
295
|
-
delay: import_zod2.z.number().int().min(
|
|
296
|
+
delay: import_zod2.z.number().int().min(import_sdk.MOCK_RESPONSE_DELAY_MIN).max(import_sdk.MOCK_RESPONSE_DELAY_MAX).optional().describe(
|
|
297
|
+
`Response delay in milliseconds (${import_sdk.MOCK_RESPONSE_DELAY_MIN}-${import_sdk.MOCK_RESPONSE_DELAY_MAX}, default: none)`
|
|
298
|
+
)
|
|
296
299
|
});
|
|
297
300
|
function textContent(text) {
|
|
298
301
|
return { content: [{ type: "text", text }] };
|
|
@@ -489,12 +492,69 @@ function registerTools(server, client) {
|
|
|
489
492
|
name: import_zod2.z.string().optional().describe("Display name for the endpoint"),
|
|
490
493
|
ephemeral: import_zod2.z.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
|
|
491
494
|
expiresIn: durationOrTimestampSchema.optional().describe('Auto-expire after this duration, for example "12h"'),
|
|
492
|
-
mockResponse: mockResponseSchema.optional().describe("Optional mock response to return when the endpoint receives a request")
|
|
495
|
+
mockResponse: mockResponseSchema.optional().describe("Optional mock response to return when the endpoint receives a request"),
|
|
496
|
+
responseRules: responseRulesSchema.optional().describe(
|
|
497
|
+
"Conditional response rules. Each rule has conditions and a response. First matching rule wins."
|
|
498
|
+
),
|
|
499
|
+
notificationUrl: import_zod2.z.string().url().optional().describe(
|
|
500
|
+
"URL to POST a JSON summary to after each captured request (e.g. Slack/Discord webhook)"
|
|
501
|
+
),
|
|
502
|
+
signingProvider: import_zod2.z.string().optional().describe(
|
|
503
|
+
"Signing provider for automatic signature verification (e.g. stripe, github, shopify)"
|
|
504
|
+
),
|
|
505
|
+
signingSecret: import_zod2.z.string().optional().describe(
|
|
506
|
+
"Signing secret (encrypted server-side, never returned). Required when signingProvider is set."
|
|
507
|
+
),
|
|
508
|
+
signingHeader: import_zod2.z.string().optional().describe("Custom signature header name. Only used with generic-hmac provider.")
|
|
493
509
|
},
|
|
494
|
-
withErrorHandling(
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
510
|
+
withErrorHandling(
|
|
511
|
+
async ({
|
|
512
|
+
name,
|
|
513
|
+
ephemeral,
|
|
514
|
+
expiresIn,
|
|
515
|
+
mockResponse,
|
|
516
|
+
responseRules,
|
|
517
|
+
notificationUrl,
|
|
518
|
+
signingProvider,
|
|
519
|
+
signingSecret,
|
|
520
|
+
signingHeader
|
|
521
|
+
}) => {
|
|
522
|
+
if (signingProvider) {
|
|
523
|
+
if (signingProvider !== "discord" && !signingSecret) {
|
|
524
|
+
throw new Error(`signingSecret is required for provider "${signingProvider}"`);
|
|
525
|
+
}
|
|
526
|
+
if (signingProvider === "generic-hmac" && !signingHeader) {
|
|
527
|
+
throw new Error("signingHeader is required for generic-hmac provider");
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (signingSecret && !signingProvider) {
|
|
531
|
+
throw new Error("signingProvider is required when signingSecret is set");
|
|
532
|
+
}
|
|
533
|
+
const endpoint = await client.endpoints.create({
|
|
534
|
+
name,
|
|
535
|
+
ephemeral,
|
|
536
|
+
expiresIn,
|
|
537
|
+
mockResponse,
|
|
538
|
+
responseRules,
|
|
539
|
+
notificationUrl
|
|
540
|
+
});
|
|
541
|
+
if (signingProvider && signingSecret) {
|
|
542
|
+
try {
|
|
543
|
+
await client.endpoints.update(endpoint.slug, {
|
|
544
|
+
signingProvider,
|
|
545
|
+
signingSecret,
|
|
546
|
+
signingHeader
|
|
547
|
+
});
|
|
548
|
+
} catch (err) {
|
|
549
|
+
return jsonContent({
|
|
550
|
+
...endpoint,
|
|
551
|
+
warning: `Endpoint created but signing configuration failed: ${err instanceof Error ? err.message : String(err)}. Update endpoint "${endpoint.slug}" to add signing.`
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return jsonContent(endpoint);
|
|
556
|
+
}
|
|
557
|
+
)
|
|
498
558
|
);
|
|
499
559
|
server.tool(
|
|
500
560
|
"list_endpoints",
|
|
@@ -516,16 +576,44 @@ function registerTools(server, client) {
|
|
|
516
576
|
);
|
|
517
577
|
server.tool(
|
|
518
578
|
"update_endpoint",
|
|
519
|
-
"Update an endpoint name
|
|
579
|
+
"Update an endpoint name, mock response, conditional response rules, or signing configuration.",
|
|
520
580
|
{
|
|
521
581
|
slug: import_zod2.z.string().describe("The endpoint slug to update"),
|
|
522
582
|
name: import_zod2.z.string().optional().describe("New display name"),
|
|
523
|
-
mockResponse: mockResponseSchema.nullable().optional().describe("
|
|
583
|
+
mockResponse: mockResponseSchema.nullable().optional().describe("Default mock response (used when no rule matches), or null to clear"),
|
|
584
|
+
responseRules: responseRulesSchema.nullable().optional().describe("Conditional response rules (first match wins), or null to clear"),
|
|
585
|
+
notificationUrl: import_zod2.z.string().url().nullable().optional().describe("Notification webhook URL, or null to clear it"),
|
|
586
|
+
signingProvider: import_zod2.z.string().nullable().optional().describe(
|
|
587
|
+
"Signing provider for automatic verification (e.g. stripe, github), or null to disable"
|
|
588
|
+
),
|
|
589
|
+
signingSecret: import_zod2.z.string().nullable().optional().describe("New signing secret (encrypted server-side). Pass null to clear."),
|
|
590
|
+
signingHeader: import_zod2.z.string().nullable().optional().describe(
|
|
591
|
+
"Custom signature header name (only for generic-hmac provider), or null to clear"
|
|
592
|
+
)
|
|
524
593
|
},
|
|
525
|
-
withErrorHandling(
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
594
|
+
withErrorHandling(
|
|
595
|
+
async ({
|
|
596
|
+
slug,
|
|
597
|
+
name,
|
|
598
|
+
mockResponse,
|
|
599
|
+
responseRules,
|
|
600
|
+
notificationUrl,
|
|
601
|
+
signingProvider,
|
|
602
|
+
signingSecret,
|
|
603
|
+
signingHeader
|
|
604
|
+
}) => {
|
|
605
|
+
const endpoint = await client.endpoints.update(slug, {
|
|
606
|
+
name,
|
|
607
|
+
mockResponse,
|
|
608
|
+
responseRules,
|
|
609
|
+
notificationUrl,
|
|
610
|
+
signingProvider,
|
|
611
|
+
signingSecret,
|
|
612
|
+
signingHeader
|
|
613
|
+
});
|
|
614
|
+
return jsonContent(endpoint);
|
|
615
|
+
}
|
|
616
|
+
)
|
|
529
617
|
);
|
|
530
618
|
server.tool(
|
|
531
619
|
"delete_endpoint",
|
|
@@ -595,7 +683,7 @@ function registerTools(server, client) {
|
|
|
595
683
|
method: methodSchema,
|
|
596
684
|
headers: import_zod2.z.record(import_zod2.z.string()).optional().describe("HTTP headers to include"),
|
|
597
685
|
body: import_zod2.z.unknown().optional().describe("Request body"),
|
|
598
|
-
provider: import_zod2.z.enum(
|
|
686
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to send with signed headers"),
|
|
599
687
|
template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
|
|
600
688
|
event: import_zod2.z.string().optional().describe("Provider event or topic name"),
|
|
601
689
|
secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -777,16 +865,43 @@ function registerTools(server, client) {
|
|
|
777
865
|
);
|
|
778
866
|
server.tool(
|
|
779
867
|
"verify_signature",
|
|
780
|
-
"Verify the webhook signature on a captured request.",
|
|
868
|
+
"Verify the webhook signature on a captured request. When called without provider/secret, returns the server-side pre-computed result (if signing is configured on the endpoint). When called with provider+secret, performs client-side verification.",
|
|
781
869
|
{
|
|
782
870
|
requestId: import_zod2.z.string().describe("The captured request ID"),
|
|
783
|
-
provider: import_zod2.z.enum(
|
|
784
|
-
secret: import_zod2.z.string().optional().describe(
|
|
871
|
+
provider: import_zod2.z.enum(import_sdk.VERIFY_PROVIDERS).optional().describe("Provider whose signature scheme should be verified. Omit to use stored result."),
|
|
872
|
+
secret: import_zod2.z.string().optional().describe(
|
|
873
|
+
"Shared signing secret. Required for non-Discord providers when doing client-side verification."
|
|
874
|
+
),
|
|
785
875
|
publicKey: import_zod2.z.string().optional().describe("Discord application public key. Required for provider=discord."),
|
|
786
876
|
url: httpUrlSchema.optional().describe("Original signed URL. Required for Twilio verification.")
|
|
787
877
|
},
|
|
788
878
|
withErrorHandling(async ({ requestId, provider, secret, publicKey, url }) => {
|
|
789
879
|
const request = await client.requests.get(requestId);
|
|
880
|
+
if (!provider && !secret && !publicKey) {
|
|
881
|
+
const verified = request.signatureVerified;
|
|
882
|
+
if (verified === null || verified === void 0) {
|
|
883
|
+
return jsonContent({
|
|
884
|
+
valid: null,
|
|
885
|
+
details: "No signature verification configured on this endpoint. Pass provider and secret to verify client-side."
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
let errorData = null;
|
|
889
|
+
if (request.signatureError) {
|
|
890
|
+
try {
|
|
891
|
+
errorData = JSON.parse(request.signatureError);
|
|
892
|
+
} catch {
|
|
893
|
+
errorData = null;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
const isSkipped = !verified && errorData?.code && (errorData.code === "missing_header" || errorData.code === "unsupported");
|
|
897
|
+
return jsonContent({
|
|
898
|
+
valid: isSkipped ? null : verified,
|
|
899
|
+
provider: request.signingProvider ?? null,
|
|
900
|
+
details: verified ? "Signature verified by server." : isSkipped ? `Verification skipped: ${errorData?.message ?? errorData?.code}` : `Signature invalid: ${request.signatureError ?? "unknown error"}`,
|
|
901
|
+
error: request.signatureError ?? null,
|
|
902
|
+
skipped: isSkipped ?? false
|
|
903
|
+
});
|
|
904
|
+
}
|
|
790
905
|
const verificationOptions = ensureVerifyArgs({ provider, secret, publicKey, url });
|
|
791
906
|
const result = await (0, import_sdk.verifySignature)(request, verificationOptions);
|
|
792
907
|
return jsonContent({
|
|
@@ -815,7 +930,7 @@ function registerTools(server, client) {
|
|
|
815
930
|
method: methodSchema,
|
|
816
931
|
headers: import_zod2.z.record(import_zod2.z.string()).optional().describe("HTTP headers to include"),
|
|
817
932
|
body: import_zod2.z.unknown().optional().describe("Request body"),
|
|
818
|
-
provider: import_zod2.z.enum(
|
|
933
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
819
934
|
template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
|
|
820
935
|
event: import_zod2.z.string().optional().describe("Provider event or topic name"),
|
|
821
936
|
secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -846,7 +961,7 @@ function registerTools(server, client) {
|
|
|
846
961
|
method: methodSchema,
|
|
847
962
|
headers: import_zod2.z.record(import_zod2.z.string()).optional().describe("HTTP headers to include"),
|
|
848
963
|
body: import_zod2.z.unknown().optional().describe("Request body"),
|
|
849
|
-
provider: import_zod2.z.enum(
|
|
964
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
850
965
|
template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
|
|
851
966
|
event: import_zod2.z.string().optional().describe("Provider event or topic name"),
|
|
852
967
|
secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -868,7 +983,7 @@ function registerTools(server, client) {
|
|
|
868
983
|
"list_provider_templates",
|
|
869
984
|
"List supported webhook providers, templates, and signing metadata.",
|
|
870
985
|
{
|
|
871
|
-
provider: import_zod2.z.enum(
|
|
986
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Filter to a single provider")
|
|
872
987
|
},
|
|
873
988
|
withErrorHandling(async ({ provider }) => {
|
|
874
989
|
if (provider) {
|
|
@@ -895,7 +1010,7 @@ function registerTools(server, client) {
|
|
|
895
1010
|
"test_webhook_flow",
|
|
896
1011
|
"Run a full webhook test flow: create endpoint, optionally mock, send, wait, verify, replay, and clean up.",
|
|
897
1012
|
{
|
|
898
|
-
provider: import_zod2.z.enum(
|
|
1013
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to use when sending the webhook"),
|
|
899
1014
|
event: import_zod2.z.string().optional().describe("Optional provider event or topic name"),
|
|
900
1015
|
secret: import_zod2.z.string().optional().describe(
|
|
901
1016
|
"Signing secret. Required when provider is set or signature verification is enabled."
|
|
@@ -981,7 +1096,7 @@ function registerTools(server, client) {
|
|
|
981
1096
|
}
|
|
982
1097
|
|
|
983
1098
|
// src/index.ts
|
|
984
|
-
var VERSION = true ? "1.
|
|
1099
|
+
var VERSION = true ? "1.3.0" : "0.0.0-dev";
|
|
985
1100
|
function createServer(options = {}) {
|
|
986
1101
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
987
1102
|
if (!apiKey) {
|
package/dist/index.js
CHANGED
|
@@ -266,35 +266,6 @@ var import_zod2 = require("zod");
|
|
|
266
266
|
var import_sdk = require("@webhooks-cc/sdk");
|
|
267
267
|
var MAX_BODY_SIZE = 32768;
|
|
268
268
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
269
|
-
var TEMPLATE_PROVIDER_VALUES = [
|
|
270
|
-
"stripe",
|
|
271
|
-
"github",
|
|
272
|
-
"shopify",
|
|
273
|
-
"twilio",
|
|
274
|
-
"slack",
|
|
275
|
-
"paddle",
|
|
276
|
-
"linear",
|
|
277
|
-
"sendgrid",
|
|
278
|
-
"clerk",
|
|
279
|
-
"discord",
|
|
280
|
-
"vercel",
|
|
281
|
-
"gitlab",
|
|
282
|
-
"standard-webhooks"
|
|
283
|
-
];
|
|
284
|
-
var VERIFY_PROVIDER_VALUES = [
|
|
285
|
-
"stripe",
|
|
286
|
-
"github",
|
|
287
|
-
"shopify",
|
|
288
|
-
"twilio",
|
|
289
|
-
"slack",
|
|
290
|
-
"paddle",
|
|
291
|
-
"linear",
|
|
292
|
-
"clerk",
|
|
293
|
-
"discord",
|
|
294
|
-
"vercel",
|
|
295
|
-
"gitlab",
|
|
296
|
-
"standard-webhooks"
|
|
297
|
-
];
|
|
298
269
|
var TIME_SEPARATOR = " \u2014 ";
|
|
299
270
|
var httpUrlSchema = import_zod2.z.string().url().refine(
|
|
300
271
|
(value) => {
|
|
@@ -309,11 +280,43 @@ var httpUrlSchema = import_zod2.z.string().url().refine(
|
|
|
309
280
|
);
|
|
310
281
|
var methodSchema = import_zod2.z.enum(HTTP_METHODS).default("POST").describe("HTTP method (default: POST)");
|
|
311
282
|
var durationOrTimestampSchema = import_zod2.z.union([import_zod2.z.string(), import_zod2.z.number()]);
|
|
283
|
+
var ruleConditionSchema = import_zod2.z.object({
|
|
284
|
+
field: import_zod2.z.enum(["method", "path", "header", "body_contains", "body_path", "query"]).describe("Request field to match against"),
|
|
285
|
+
op: import_zod2.z.enum(["eq", "contains", "starts_with", "matches", "exists"]).describe("Comparison operator"),
|
|
286
|
+
value: import_zod2.z.string().max(import_sdk.MAX_CONDITION_VALUE_LEN).optional().describe(
|
|
287
|
+
`Value to compare against (required unless op is "exists", max ${import_sdk.MAX_CONDITION_VALUE_LEN} chars)`
|
|
288
|
+
),
|
|
289
|
+
name: import_zod2.z.string().max(import_sdk.MAX_CONDITION_NAME_LEN).optional().describe(
|
|
290
|
+
`Header name or query param name (required for header/query, max ${import_sdk.MAX_CONDITION_NAME_LEN} chars)`
|
|
291
|
+
),
|
|
292
|
+
path: import_zod2.z.string().max(import_sdk.MAX_CONDITION_PATH_LEN).optional().describe(
|
|
293
|
+
`JSON dot-notation path (required for body_path, max ${import_sdk.MAX_CONDITION_PATH_LEN} chars)`
|
|
294
|
+
)
|
|
295
|
+
}).refine((c) => c.op === "exists" || c.value !== void 0 && c.value.length > 0, {
|
|
296
|
+
message: 'value is required when op is not "exists"'
|
|
297
|
+
}).refine(
|
|
298
|
+
(c) => !(c.field === "header" || c.field === "query") || c.name !== void 0 && c.name.length > 0,
|
|
299
|
+
{ message: "name is required for header and query conditions" }
|
|
300
|
+
).refine((c) => c.field !== "body_path" || c.path !== void 0 && c.path.length > 0, {
|
|
301
|
+
message: "path is required for body_path conditions"
|
|
302
|
+
}).refine((c) => c.op !== "matches" || !c.value || c.value.length <= import_sdk.MAX_GLOB_PATTERN_LEN, {
|
|
303
|
+
message: `matches pattern must be ${import_sdk.MAX_GLOB_PATTERN_LEN} chars or less`
|
|
304
|
+
});
|
|
305
|
+
var responseRuleSchema = import_zod2.z.object({
|
|
306
|
+
name: import_zod2.z.string().max(import_sdk.MAX_RULE_NAME_LEN).optional().describe(`Human-readable rule name (max ${import_sdk.MAX_RULE_NAME_LEN} chars)`),
|
|
307
|
+
enabled: import_zod2.z.boolean().optional().default(true).describe("Whether this rule is active"),
|
|
308
|
+
logic: import_zod2.z.enum(["and", "or"]).optional().default("and").describe('How to combine conditions: "and" (all match) or "or" (any match)'),
|
|
309
|
+
conditions: import_zod2.z.array(ruleConditionSchema).min(1).max(10).describe("Conditions to evaluate (1-10)"),
|
|
310
|
+
response: import_zod2.z.lazy(() => mockResponseSchema).describe("Response when conditions match")
|
|
311
|
+
});
|
|
312
|
+
var responseRulesSchema = import_zod2.z.array(responseRuleSchema).max(50).describe("Ordered conditional response rules (first match wins, max 50)");
|
|
312
313
|
var mockResponseSchema = import_zod2.z.object({
|
|
313
|
-
status: import_zod2.z.number().int().min(
|
|
314
|
+
status: import_zod2.z.number().int().min(import_sdk.MOCK_RESPONSE_STATUS_MIN).max(import_sdk.MOCK_RESPONSE_STATUS_MAX).describe(`HTTP status code (${import_sdk.MOCK_RESPONSE_STATUS_MIN}-${import_sdk.MOCK_RESPONSE_STATUS_MAX})`),
|
|
314
315
|
body: import_zod2.z.string().default("").describe("Response body string (default: empty)"),
|
|
315
316
|
headers: import_zod2.z.record(import_zod2.z.string()).default({}).describe("Response headers (default: none)"),
|
|
316
|
-
delay: import_zod2.z.number().int().min(
|
|
317
|
+
delay: import_zod2.z.number().int().min(import_sdk.MOCK_RESPONSE_DELAY_MIN).max(import_sdk.MOCK_RESPONSE_DELAY_MAX).optional().describe(
|
|
318
|
+
`Response delay in milliseconds (${import_sdk.MOCK_RESPONSE_DELAY_MIN}-${import_sdk.MOCK_RESPONSE_DELAY_MAX}, default: none)`
|
|
319
|
+
)
|
|
317
320
|
});
|
|
318
321
|
function textContent(text) {
|
|
319
322
|
return { content: [{ type: "text", text }] };
|
|
@@ -510,12 +513,69 @@ function registerTools(server, client) {
|
|
|
510
513
|
name: import_zod2.z.string().optional().describe("Display name for the endpoint"),
|
|
511
514
|
ephemeral: import_zod2.z.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
|
|
512
515
|
expiresIn: durationOrTimestampSchema.optional().describe('Auto-expire after this duration, for example "12h"'),
|
|
513
|
-
mockResponse: mockResponseSchema.optional().describe("Optional mock response to return when the endpoint receives a request")
|
|
516
|
+
mockResponse: mockResponseSchema.optional().describe("Optional mock response to return when the endpoint receives a request"),
|
|
517
|
+
responseRules: responseRulesSchema.optional().describe(
|
|
518
|
+
"Conditional response rules. Each rule has conditions and a response. First matching rule wins."
|
|
519
|
+
),
|
|
520
|
+
notificationUrl: import_zod2.z.string().url().optional().describe(
|
|
521
|
+
"URL to POST a JSON summary to after each captured request (e.g. Slack/Discord webhook)"
|
|
522
|
+
),
|
|
523
|
+
signingProvider: import_zod2.z.string().optional().describe(
|
|
524
|
+
"Signing provider for automatic signature verification (e.g. stripe, github, shopify)"
|
|
525
|
+
),
|
|
526
|
+
signingSecret: import_zod2.z.string().optional().describe(
|
|
527
|
+
"Signing secret (encrypted server-side, never returned). Required when signingProvider is set."
|
|
528
|
+
),
|
|
529
|
+
signingHeader: import_zod2.z.string().optional().describe("Custom signature header name. Only used with generic-hmac provider.")
|
|
514
530
|
},
|
|
515
|
-
withErrorHandling(
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
531
|
+
withErrorHandling(
|
|
532
|
+
async ({
|
|
533
|
+
name,
|
|
534
|
+
ephemeral,
|
|
535
|
+
expiresIn,
|
|
536
|
+
mockResponse,
|
|
537
|
+
responseRules,
|
|
538
|
+
notificationUrl,
|
|
539
|
+
signingProvider,
|
|
540
|
+
signingSecret,
|
|
541
|
+
signingHeader
|
|
542
|
+
}) => {
|
|
543
|
+
if (signingProvider) {
|
|
544
|
+
if (signingProvider !== "discord" && !signingSecret) {
|
|
545
|
+
throw new Error(`signingSecret is required for provider "${signingProvider}"`);
|
|
546
|
+
}
|
|
547
|
+
if (signingProvider === "generic-hmac" && !signingHeader) {
|
|
548
|
+
throw new Error("signingHeader is required for generic-hmac provider");
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (signingSecret && !signingProvider) {
|
|
552
|
+
throw new Error("signingProvider is required when signingSecret is set");
|
|
553
|
+
}
|
|
554
|
+
const endpoint = await client.endpoints.create({
|
|
555
|
+
name,
|
|
556
|
+
ephemeral,
|
|
557
|
+
expiresIn,
|
|
558
|
+
mockResponse,
|
|
559
|
+
responseRules,
|
|
560
|
+
notificationUrl
|
|
561
|
+
});
|
|
562
|
+
if (signingProvider && signingSecret) {
|
|
563
|
+
try {
|
|
564
|
+
await client.endpoints.update(endpoint.slug, {
|
|
565
|
+
signingProvider,
|
|
566
|
+
signingSecret,
|
|
567
|
+
signingHeader
|
|
568
|
+
});
|
|
569
|
+
} catch (err) {
|
|
570
|
+
return jsonContent({
|
|
571
|
+
...endpoint,
|
|
572
|
+
warning: `Endpoint created but signing configuration failed: ${err instanceof Error ? err.message : String(err)}. Update endpoint "${endpoint.slug}" to add signing.`
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return jsonContent(endpoint);
|
|
577
|
+
}
|
|
578
|
+
)
|
|
519
579
|
);
|
|
520
580
|
server.tool(
|
|
521
581
|
"list_endpoints",
|
|
@@ -537,16 +597,44 @@ function registerTools(server, client) {
|
|
|
537
597
|
);
|
|
538
598
|
server.tool(
|
|
539
599
|
"update_endpoint",
|
|
540
|
-
"Update an endpoint name
|
|
600
|
+
"Update an endpoint name, mock response, conditional response rules, or signing configuration.",
|
|
541
601
|
{
|
|
542
602
|
slug: import_zod2.z.string().describe("The endpoint slug to update"),
|
|
543
603
|
name: import_zod2.z.string().optional().describe("New display name"),
|
|
544
|
-
mockResponse: mockResponseSchema.nullable().optional().describe("
|
|
604
|
+
mockResponse: mockResponseSchema.nullable().optional().describe("Default mock response (used when no rule matches), or null to clear"),
|
|
605
|
+
responseRules: responseRulesSchema.nullable().optional().describe("Conditional response rules (first match wins), or null to clear"),
|
|
606
|
+
notificationUrl: import_zod2.z.string().url().nullable().optional().describe("Notification webhook URL, or null to clear it"),
|
|
607
|
+
signingProvider: import_zod2.z.string().nullable().optional().describe(
|
|
608
|
+
"Signing provider for automatic verification (e.g. stripe, github), or null to disable"
|
|
609
|
+
),
|
|
610
|
+
signingSecret: import_zod2.z.string().nullable().optional().describe("New signing secret (encrypted server-side). Pass null to clear."),
|
|
611
|
+
signingHeader: import_zod2.z.string().nullable().optional().describe(
|
|
612
|
+
"Custom signature header name (only for generic-hmac provider), or null to clear"
|
|
613
|
+
)
|
|
545
614
|
},
|
|
546
|
-
withErrorHandling(
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
615
|
+
withErrorHandling(
|
|
616
|
+
async ({
|
|
617
|
+
slug,
|
|
618
|
+
name,
|
|
619
|
+
mockResponse,
|
|
620
|
+
responseRules,
|
|
621
|
+
notificationUrl,
|
|
622
|
+
signingProvider,
|
|
623
|
+
signingSecret,
|
|
624
|
+
signingHeader
|
|
625
|
+
}) => {
|
|
626
|
+
const endpoint = await client.endpoints.update(slug, {
|
|
627
|
+
name,
|
|
628
|
+
mockResponse,
|
|
629
|
+
responseRules,
|
|
630
|
+
notificationUrl,
|
|
631
|
+
signingProvider,
|
|
632
|
+
signingSecret,
|
|
633
|
+
signingHeader
|
|
634
|
+
});
|
|
635
|
+
return jsonContent(endpoint);
|
|
636
|
+
}
|
|
637
|
+
)
|
|
550
638
|
);
|
|
551
639
|
server.tool(
|
|
552
640
|
"delete_endpoint",
|
|
@@ -616,7 +704,7 @@ function registerTools(server, client) {
|
|
|
616
704
|
method: methodSchema,
|
|
617
705
|
headers: import_zod2.z.record(import_zod2.z.string()).optional().describe("HTTP headers to include"),
|
|
618
706
|
body: import_zod2.z.unknown().optional().describe("Request body"),
|
|
619
|
-
provider: import_zod2.z.enum(
|
|
707
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to send with signed headers"),
|
|
620
708
|
template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
|
|
621
709
|
event: import_zod2.z.string().optional().describe("Provider event or topic name"),
|
|
622
710
|
secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -798,16 +886,43 @@ function registerTools(server, client) {
|
|
|
798
886
|
);
|
|
799
887
|
server.tool(
|
|
800
888
|
"verify_signature",
|
|
801
|
-
"Verify the webhook signature on a captured request.",
|
|
889
|
+
"Verify the webhook signature on a captured request. When called without provider/secret, returns the server-side pre-computed result (if signing is configured on the endpoint). When called with provider+secret, performs client-side verification.",
|
|
802
890
|
{
|
|
803
891
|
requestId: import_zod2.z.string().describe("The captured request ID"),
|
|
804
|
-
provider: import_zod2.z.enum(
|
|
805
|
-
secret: import_zod2.z.string().optional().describe(
|
|
892
|
+
provider: import_zod2.z.enum(import_sdk.VERIFY_PROVIDERS).optional().describe("Provider whose signature scheme should be verified. Omit to use stored result."),
|
|
893
|
+
secret: import_zod2.z.string().optional().describe(
|
|
894
|
+
"Shared signing secret. Required for non-Discord providers when doing client-side verification."
|
|
895
|
+
),
|
|
806
896
|
publicKey: import_zod2.z.string().optional().describe("Discord application public key. Required for provider=discord."),
|
|
807
897
|
url: httpUrlSchema.optional().describe("Original signed URL. Required for Twilio verification.")
|
|
808
898
|
},
|
|
809
899
|
withErrorHandling(async ({ requestId, provider, secret, publicKey, url }) => {
|
|
810
900
|
const request = await client.requests.get(requestId);
|
|
901
|
+
if (!provider && !secret && !publicKey) {
|
|
902
|
+
const verified = request.signatureVerified;
|
|
903
|
+
if (verified === null || verified === void 0) {
|
|
904
|
+
return jsonContent({
|
|
905
|
+
valid: null,
|
|
906
|
+
details: "No signature verification configured on this endpoint. Pass provider and secret to verify client-side."
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
let errorData = null;
|
|
910
|
+
if (request.signatureError) {
|
|
911
|
+
try {
|
|
912
|
+
errorData = JSON.parse(request.signatureError);
|
|
913
|
+
} catch {
|
|
914
|
+
errorData = null;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
const isSkipped = !verified && errorData?.code && (errorData.code === "missing_header" || errorData.code === "unsupported");
|
|
918
|
+
return jsonContent({
|
|
919
|
+
valid: isSkipped ? null : verified,
|
|
920
|
+
provider: request.signingProvider ?? null,
|
|
921
|
+
details: verified ? "Signature verified by server." : isSkipped ? `Verification skipped: ${errorData?.message ?? errorData?.code}` : `Signature invalid: ${request.signatureError ?? "unknown error"}`,
|
|
922
|
+
error: request.signatureError ?? null,
|
|
923
|
+
skipped: isSkipped ?? false
|
|
924
|
+
});
|
|
925
|
+
}
|
|
811
926
|
const verificationOptions = ensureVerifyArgs({ provider, secret, publicKey, url });
|
|
812
927
|
const result = await (0, import_sdk.verifySignature)(request, verificationOptions);
|
|
813
928
|
return jsonContent({
|
|
@@ -836,7 +951,7 @@ function registerTools(server, client) {
|
|
|
836
951
|
method: methodSchema,
|
|
837
952
|
headers: import_zod2.z.record(import_zod2.z.string()).optional().describe("HTTP headers to include"),
|
|
838
953
|
body: import_zod2.z.unknown().optional().describe("Request body"),
|
|
839
|
-
provider: import_zod2.z.enum(
|
|
954
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
840
955
|
template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
|
|
841
956
|
event: import_zod2.z.string().optional().describe("Provider event or topic name"),
|
|
842
957
|
secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -867,7 +982,7 @@ function registerTools(server, client) {
|
|
|
867
982
|
method: methodSchema,
|
|
868
983
|
headers: import_zod2.z.record(import_zod2.z.string()).optional().describe("HTTP headers to include"),
|
|
869
984
|
body: import_zod2.z.unknown().optional().describe("Request body"),
|
|
870
|
-
provider: import_zod2.z.enum(
|
|
985
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
871
986
|
template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
|
|
872
987
|
event: import_zod2.z.string().optional().describe("Provider event or topic name"),
|
|
873
988
|
secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -889,7 +1004,7 @@ function registerTools(server, client) {
|
|
|
889
1004
|
"list_provider_templates",
|
|
890
1005
|
"List supported webhook providers, templates, and signing metadata.",
|
|
891
1006
|
{
|
|
892
|
-
provider: import_zod2.z.enum(
|
|
1007
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Filter to a single provider")
|
|
893
1008
|
},
|
|
894
1009
|
withErrorHandling(async ({ provider }) => {
|
|
895
1010
|
if (provider) {
|
|
@@ -916,7 +1031,7 @@ function registerTools(server, client) {
|
|
|
916
1031
|
"test_webhook_flow",
|
|
917
1032
|
"Run a full webhook test flow: create endpoint, optionally mock, send, wait, verify, replay, and clean up.",
|
|
918
1033
|
{
|
|
919
|
-
provider: import_zod2.z.enum(
|
|
1034
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to use when sending the webhook"),
|
|
920
1035
|
event: import_zod2.z.string().optional().describe("Optional provider event or topic name"),
|
|
921
1036
|
secret: import_zod2.z.string().optional().describe(
|
|
922
1037
|
"Signing secret. Required when provider is set or signature verification is enabled."
|
|
@@ -1002,7 +1117,7 @@ function registerTools(server, client) {
|
|
|
1002
1117
|
}
|
|
1003
1118
|
|
|
1004
1119
|
// src/index.ts
|
|
1005
|
-
var VERSION = true ? "1.
|
|
1120
|
+
var VERSION = true ? "1.3.0" : "0.0.0-dev";
|
|
1006
1121
|
function createServer(options = {}) {
|
|
1007
1122
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
1008
1123
|
if (!apiKey) {
|
package/dist/index.mjs
CHANGED
|
@@ -239,44 +239,26 @@ import { z as z2 } from "zod";
|
|
|
239
239
|
import {
|
|
240
240
|
diffRequests,
|
|
241
241
|
extractJsonField,
|
|
242
|
+
MOCK_RESPONSE_DELAY_MAX,
|
|
243
|
+
MOCK_RESPONSE_DELAY_MIN,
|
|
244
|
+
MOCK_RESPONSE_STATUS_MAX,
|
|
245
|
+
MOCK_RESPONSE_STATUS_MIN,
|
|
246
|
+
MAX_CONDITION_VALUE_LEN,
|
|
247
|
+
MAX_CONDITION_NAME_LEN,
|
|
248
|
+
MAX_CONDITION_PATH_LEN,
|
|
249
|
+
MAX_RULE_NAME_LEN,
|
|
250
|
+
MAX_GLOB_PATTERN_LEN,
|
|
242
251
|
NotFoundError,
|
|
243
252
|
RateLimitError,
|
|
253
|
+
TEMPLATE_PROVIDERS,
|
|
244
254
|
TimeoutError,
|
|
245
255
|
UnauthorizedError,
|
|
256
|
+
VERIFY_PROVIDERS,
|
|
246
257
|
verifySignature,
|
|
247
258
|
WebhooksCCError
|
|
248
259
|
} from "@webhooks-cc/sdk";
|
|
249
260
|
var MAX_BODY_SIZE = 32768;
|
|
250
261
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
251
|
-
var TEMPLATE_PROVIDER_VALUES = [
|
|
252
|
-
"stripe",
|
|
253
|
-
"github",
|
|
254
|
-
"shopify",
|
|
255
|
-
"twilio",
|
|
256
|
-
"slack",
|
|
257
|
-
"paddle",
|
|
258
|
-
"linear",
|
|
259
|
-
"sendgrid",
|
|
260
|
-
"clerk",
|
|
261
|
-
"discord",
|
|
262
|
-
"vercel",
|
|
263
|
-
"gitlab",
|
|
264
|
-
"standard-webhooks"
|
|
265
|
-
];
|
|
266
|
-
var VERIFY_PROVIDER_VALUES = [
|
|
267
|
-
"stripe",
|
|
268
|
-
"github",
|
|
269
|
-
"shopify",
|
|
270
|
-
"twilio",
|
|
271
|
-
"slack",
|
|
272
|
-
"paddle",
|
|
273
|
-
"linear",
|
|
274
|
-
"clerk",
|
|
275
|
-
"discord",
|
|
276
|
-
"vercel",
|
|
277
|
-
"gitlab",
|
|
278
|
-
"standard-webhooks"
|
|
279
|
-
];
|
|
280
262
|
var TIME_SEPARATOR = " \u2014 ";
|
|
281
263
|
var httpUrlSchema = z2.string().url().refine(
|
|
282
264
|
(value) => {
|
|
@@ -291,11 +273,43 @@ var httpUrlSchema = z2.string().url().refine(
|
|
|
291
273
|
);
|
|
292
274
|
var methodSchema = z2.enum(HTTP_METHODS).default("POST").describe("HTTP method (default: POST)");
|
|
293
275
|
var durationOrTimestampSchema = z2.union([z2.string(), z2.number()]);
|
|
276
|
+
var ruleConditionSchema = z2.object({
|
|
277
|
+
field: z2.enum(["method", "path", "header", "body_contains", "body_path", "query"]).describe("Request field to match against"),
|
|
278
|
+
op: z2.enum(["eq", "contains", "starts_with", "matches", "exists"]).describe("Comparison operator"),
|
|
279
|
+
value: z2.string().max(MAX_CONDITION_VALUE_LEN).optional().describe(
|
|
280
|
+
`Value to compare against (required unless op is "exists", max ${MAX_CONDITION_VALUE_LEN} chars)`
|
|
281
|
+
),
|
|
282
|
+
name: z2.string().max(MAX_CONDITION_NAME_LEN).optional().describe(
|
|
283
|
+
`Header name or query param name (required for header/query, max ${MAX_CONDITION_NAME_LEN} chars)`
|
|
284
|
+
),
|
|
285
|
+
path: z2.string().max(MAX_CONDITION_PATH_LEN).optional().describe(
|
|
286
|
+
`JSON dot-notation path (required for body_path, max ${MAX_CONDITION_PATH_LEN} chars)`
|
|
287
|
+
)
|
|
288
|
+
}).refine((c) => c.op === "exists" || c.value !== void 0 && c.value.length > 0, {
|
|
289
|
+
message: 'value is required when op is not "exists"'
|
|
290
|
+
}).refine(
|
|
291
|
+
(c) => !(c.field === "header" || c.field === "query") || c.name !== void 0 && c.name.length > 0,
|
|
292
|
+
{ message: "name is required for header and query conditions" }
|
|
293
|
+
).refine((c) => c.field !== "body_path" || c.path !== void 0 && c.path.length > 0, {
|
|
294
|
+
message: "path is required for body_path conditions"
|
|
295
|
+
}).refine((c) => c.op !== "matches" || !c.value || c.value.length <= MAX_GLOB_PATTERN_LEN, {
|
|
296
|
+
message: `matches pattern must be ${MAX_GLOB_PATTERN_LEN} chars or less`
|
|
297
|
+
});
|
|
298
|
+
var responseRuleSchema = z2.object({
|
|
299
|
+
name: z2.string().max(MAX_RULE_NAME_LEN).optional().describe(`Human-readable rule name (max ${MAX_RULE_NAME_LEN} chars)`),
|
|
300
|
+
enabled: z2.boolean().optional().default(true).describe("Whether this rule is active"),
|
|
301
|
+
logic: z2.enum(["and", "or"]).optional().default("and").describe('How to combine conditions: "and" (all match) or "or" (any match)'),
|
|
302
|
+
conditions: z2.array(ruleConditionSchema).min(1).max(10).describe("Conditions to evaluate (1-10)"),
|
|
303
|
+
response: z2.lazy(() => mockResponseSchema).describe("Response when conditions match")
|
|
304
|
+
});
|
|
305
|
+
var responseRulesSchema = z2.array(responseRuleSchema).max(50).describe("Ordered conditional response rules (first match wins, max 50)");
|
|
294
306
|
var mockResponseSchema = z2.object({
|
|
295
|
-
status: z2.number().int().min(
|
|
307
|
+
status: z2.number().int().min(MOCK_RESPONSE_STATUS_MIN).max(MOCK_RESPONSE_STATUS_MAX).describe(`HTTP status code (${MOCK_RESPONSE_STATUS_MIN}-${MOCK_RESPONSE_STATUS_MAX})`),
|
|
296
308
|
body: z2.string().default("").describe("Response body string (default: empty)"),
|
|
297
309
|
headers: z2.record(z2.string()).default({}).describe("Response headers (default: none)"),
|
|
298
|
-
delay: z2.number().int().min(
|
|
310
|
+
delay: z2.number().int().min(MOCK_RESPONSE_DELAY_MIN).max(MOCK_RESPONSE_DELAY_MAX).optional().describe(
|
|
311
|
+
`Response delay in milliseconds (${MOCK_RESPONSE_DELAY_MIN}-${MOCK_RESPONSE_DELAY_MAX}, default: none)`
|
|
312
|
+
)
|
|
299
313
|
});
|
|
300
314
|
function textContent(text) {
|
|
301
315
|
return { content: [{ type: "text", text }] };
|
|
@@ -492,12 +506,69 @@ function registerTools(server, client) {
|
|
|
492
506
|
name: z2.string().optional().describe("Display name for the endpoint"),
|
|
493
507
|
ephemeral: z2.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
|
|
494
508
|
expiresIn: durationOrTimestampSchema.optional().describe('Auto-expire after this duration, for example "12h"'),
|
|
495
|
-
mockResponse: mockResponseSchema.optional().describe("Optional mock response to return when the endpoint receives a request")
|
|
509
|
+
mockResponse: mockResponseSchema.optional().describe("Optional mock response to return when the endpoint receives a request"),
|
|
510
|
+
responseRules: responseRulesSchema.optional().describe(
|
|
511
|
+
"Conditional response rules. Each rule has conditions and a response. First matching rule wins."
|
|
512
|
+
),
|
|
513
|
+
notificationUrl: z2.string().url().optional().describe(
|
|
514
|
+
"URL to POST a JSON summary to after each captured request (e.g. Slack/Discord webhook)"
|
|
515
|
+
),
|
|
516
|
+
signingProvider: z2.string().optional().describe(
|
|
517
|
+
"Signing provider for automatic signature verification (e.g. stripe, github, shopify)"
|
|
518
|
+
),
|
|
519
|
+
signingSecret: z2.string().optional().describe(
|
|
520
|
+
"Signing secret (encrypted server-side, never returned). Required when signingProvider is set."
|
|
521
|
+
),
|
|
522
|
+
signingHeader: z2.string().optional().describe("Custom signature header name. Only used with generic-hmac provider.")
|
|
496
523
|
},
|
|
497
|
-
withErrorHandling(
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
524
|
+
withErrorHandling(
|
|
525
|
+
async ({
|
|
526
|
+
name,
|
|
527
|
+
ephemeral,
|
|
528
|
+
expiresIn,
|
|
529
|
+
mockResponse,
|
|
530
|
+
responseRules,
|
|
531
|
+
notificationUrl,
|
|
532
|
+
signingProvider,
|
|
533
|
+
signingSecret,
|
|
534
|
+
signingHeader
|
|
535
|
+
}) => {
|
|
536
|
+
if (signingProvider) {
|
|
537
|
+
if (signingProvider !== "discord" && !signingSecret) {
|
|
538
|
+
throw new Error(`signingSecret is required for provider "${signingProvider}"`);
|
|
539
|
+
}
|
|
540
|
+
if (signingProvider === "generic-hmac" && !signingHeader) {
|
|
541
|
+
throw new Error("signingHeader is required for generic-hmac provider");
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (signingSecret && !signingProvider) {
|
|
545
|
+
throw new Error("signingProvider is required when signingSecret is set");
|
|
546
|
+
}
|
|
547
|
+
const endpoint = await client.endpoints.create({
|
|
548
|
+
name,
|
|
549
|
+
ephemeral,
|
|
550
|
+
expiresIn,
|
|
551
|
+
mockResponse,
|
|
552
|
+
responseRules,
|
|
553
|
+
notificationUrl
|
|
554
|
+
});
|
|
555
|
+
if (signingProvider && signingSecret) {
|
|
556
|
+
try {
|
|
557
|
+
await client.endpoints.update(endpoint.slug, {
|
|
558
|
+
signingProvider,
|
|
559
|
+
signingSecret,
|
|
560
|
+
signingHeader
|
|
561
|
+
});
|
|
562
|
+
} catch (err) {
|
|
563
|
+
return jsonContent({
|
|
564
|
+
...endpoint,
|
|
565
|
+
warning: `Endpoint created but signing configuration failed: ${err instanceof Error ? err.message : String(err)}. Update endpoint "${endpoint.slug}" to add signing.`
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return jsonContent(endpoint);
|
|
570
|
+
}
|
|
571
|
+
)
|
|
501
572
|
);
|
|
502
573
|
server.tool(
|
|
503
574
|
"list_endpoints",
|
|
@@ -519,16 +590,44 @@ function registerTools(server, client) {
|
|
|
519
590
|
);
|
|
520
591
|
server.tool(
|
|
521
592
|
"update_endpoint",
|
|
522
|
-
"Update an endpoint name
|
|
593
|
+
"Update an endpoint name, mock response, conditional response rules, or signing configuration.",
|
|
523
594
|
{
|
|
524
595
|
slug: z2.string().describe("The endpoint slug to update"),
|
|
525
596
|
name: z2.string().optional().describe("New display name"),
|
|
526
|
-
mockResponse: mockResponseSchema.nullable().optional().describe("
|
|
597
|
+
mockResponse: mockResponseSchema.nullable().optional().describe("Default mock response (used when no rule matches), or null to clear"),
|
|
598
|
+
responseRules: responseRulesSchema.nullable().optional().describe("Conditional response rules (first match wins), or null to clear"),
|
|
599
|
+
notificationUrl: z2.string().url().nullable().optional().describe("Notification webhook URL, or null to clear it"),
|
|
600
|
+
signingProvider: z2.string().nullable().optional().describe(
|
|
601
|
+
"Signing provider for automatic verification (e.g. stripe, github), or null to disable"
|
|
602
|
+
),
|
|
603
|
+
signingSecret: z2.string().nullable().optional().describe("New signing secret (encrypted server-side). Pass null to clear."),
|
|
604
|
+
signingHeader: z2.string().nullable().optional().describe(
|
|
605
|
+
"Custom signature header name (only for generic-hmac provider), or null to clear"
|
|
606
|
+
)
|
|
527
607
|
},
|
|
528
|
-
withErrorHandling(
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
608
|
+
withErrorHandling(
|
|
609
|
+
async ({
|
|
610
|
+
slug,
|
|
611
|
+
name,
|
|
612
|
+
mockResponse,
|
|
613
|
+
responseRules,
|
|
614
|
+
notificationUrl,
|
|
615
|
+
signingProvider,
|
|
616
|
+
signingSecret,
|
|
617
|
+
signingHeader
|
|
618
|
+
}) => {
|
|
619
|
+
const endpoint = await client.endpoints.update(slug, {
|
|
620
|
+
name,
|
|
621
|
+
mockResponse,
|
|
622
|
+
responseRules,
|
|
623
|
+
notificationUrl,
|
|
624
|
+
signingProvider,
|
|
625
|
+
signingSecret,
|
|
626
|
+
signingHeader
|
|
627
|
+
});
|
|
628
|
+
return jsonContent(endpoint);
|
|
629
|
+
}
|
|
630
|
+
)
|
|
532
631
|
);
|
|
533
632
|
server.tool(
|
|
534
633
|
"delete_endpoint",
|
|
@@ -598,7 +697,7 @@ function registerTools(server, client) {
|
|
|
598
697
|
method: methodSchema,
|
|
599
698
|
headers: z2.record(z2.string()).optional().describe("HTTP headers to include"),
|
|
600
699
|
body: z2.unknown().optional().describe("Request body"),
|
|
601
|
-
provider: z2.enum(
|
|
700
|
+
provider: z2.enum(TEMPLATE_PROVIDERS).optional().describe("Optional provider template to send with signed headers"),
|
|
602
701
|
template: z2.string().optional().describe("Provider-specific template preset"),
|
|
603
702
|
event: z2.string().optional().describe("Provider event or topic name"),
|
|
604
703
|
secret: z2.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -780,16 +879,43 @@ function registerTools(server, client) {
|
|
|
780
879
|
);
|
|
781
880
|
server.tool(
|
|
782
881
|
"verify_signature",
|
|
783
|
-
"Verify the webhook signature on a captured request.",
|
|
882
|
+
"Verify the webhook signature on a captured request. When called without provider/secret, returns the server-side pre-computed result (if signing is configured on the endpoint). When called with provider+secret, performs client-side verification.",
|
|
784
883
|
{
|
|
785
884
|
requestId: z2.string().describe("The captured request ID"),
|
|
786
|
-
provider: z2.enum(
|
|
787
|
-
secret: z2.string().optional().describe(
|
|
885
|
+
provider: z2.enum(VERIFY_PROVIDERS).optional().describe("Provider whose signature scheme should be verified. Omit to use stored result."),
|
|
886
|
+
secret: z2.string().optional().describe(
|
|
887
|
+
"Shared signing secret. Required for non-Discord providers when doing client-side verification."
|
|
888
|
+
),
|
|
788
889
|
publicKey: z2.string().optional().describe("Discord application public key. Required for provider=discord."),
|
|
789
890
|
url: httpUrlSchema.optional().describe("Original signed URL. Required for Twilio verification.")
|
|
790
891
|
},
|
|
791
892
|
withErrorHandling(async ({ requestId, provider, secret, publicKey, url }) => {
|
|
792
893
|
const request = await client.requests.get(requestId);
|
|
894
|
+
if (!provider && !secret && !publicKey) {
|
|
895
|
+
const verified = request.signatureVerified;
|
|
896
|
+
if (verified === null || verified === void 0) {
|
|
897
|
+
return jsonContent({
|
|
898
|
+
valid: null,
|
|
899
|
+
details: "No signature verification configured on this endpoint. Pass provider and secret to verify client-side."
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
let errorData = null;
|
|
903
|
+
if (request.signatureError) {
|
|
904
|
+
try {
|
|
905
|
+
errorData = JSON.parse(request.signatureError);
|
|
906
|
+
} catch {
|
|
907
|
+
errorData = null;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
const isSkipped = !verified && errorData?.code && (errorData.code === "missing_header" || errorData.code === "unsupported");
|
|
911
|
+
return jsonContent({
|
|
912
|
+
valid: isSkipped ? null : verified,
|
|
913
|
+
provider: request.signingProvider ?? null,
|
|
914
|
+
details: verified ? "Signature verified by server." : isSkipped ? `Verification skipped: ${errorData?.message ?? errorData?.code}` : `Signature invalid: ${request.signatureError ?? "unknown error"}`,
|
|
915
|
+
error: request.signatureError ?? null,
|
|
916
|
+
skipped: isSkipped ?? false
|
|
917
|
+
});
|
|
918
|
+
}
|
|
793
919
|
const verificationOptions = ensureVerifyArgs({ provider, secret, publicKey, url });
|
|
794
920
|
const result = await verifySignature(request, verificationOptions);
|
|
795
921
|
return jsonContent({
|
|
@@ -818,7 +944,7 @@ function registerTools(server, client) {
|
|
|
818
944
|
method: methodSchema,
|
|
819
945
|
headers: z2.record(z2.string()).optional().describe("HTTP headers to include"),
|
|
820
946
|
body: z2.unknown().optional().describe("Request body"),
|
|
821
|
-
provider: z2.enum(
|
|
947
|
+
provider: z2.enum(TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
822
948
|
template: z2.string().optional().describe("Provider-specific template preset"),
|
|
823
949
|
event: z2.string().optional().describe("Provider event or topic name"),
|
|
824
950
|
secret: z2.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -849,7 +975,7 @@ function registerTools(server, client) {
|
|
|
849
975
|
method: methodSchema,
|
|
850
976
|
headers: z2.record(z2.string()).optional().describe("HTTP headers to include"),
|
|
851
977
|
body: z2.unknown().optional().describe("Request body"),
|
|
852
|
-
provider: z2.enum(
|
|
978
|
+
provider: z2.enum(TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
853
979
|
template: z2.string().optional().describe("Provider-specific template preset"),
|
|
854
980
|
event: z2.string().optional().describe("Provider event or topic name"),
|
|
855
981
|
secret: z2.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -871,7 +997,7 @@ function registerTools(server, client) {
|
|
|
871
997
|
"list_provider_templates",
|
|
872
998
|
"List supported webhook providers, templates, and signing metadata.",
|
|
873
999
|
{
|
|
874
|
-
provider: z2.enum(
|
|
1000
|
+
provider: z2.enum(TEMPLATE_PROVIDERS).optional().describe("Filter to a single provider")
|
|
875
1001
|
},
|
|
876
1002
|
withErrorHandling(async ({ provider }) => {
|
|
877
1003
|
if (provider) {
|
|
@@ -898,7 +1024,7 @@ function registerTools(server, client) {
|
|
|
898
1024
|
"test_webhook_flow",
|
|
899
1025
|
"Run a full webhook test flow: create endpoint, optionally mock, send, wait, verify, replay, and clean up.",
|
|
900
1026
|
{
|
|
901
|
-
provider: z2.enum(
|
|
1027
|
+
provider: z2.enum(TEMPLATE_PROVIDERS).optional().describe("Optional provider template to use when sending the webhook"),
|
|
902
1028
|
event: z2.string().optional().describe("Optional provider event or topic name"),
|
|
903
1029
|
secret: z2.string().optional().describe(
|
|
904
1030
|
"Signing secret. Required when provider is set or signature verification is enabled."
|
|
@@ -984,7 +1110,7 @@ function registerTools(server, client) {
|
|
|
984
1110
|
}
|
|
985
1111
|
|
|
986
1112
|
// src/index.ts
|
|
987
|
-
var VERSION = true ? "1.
|
|
1113
|
+
var VERSION = true ? "1.3.0" : "0.0.0-dev";
|
|
988
1114
|
function createServer(options = {}) {
|
|
989
1115
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
990
1116
|
if (!apiKey) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webhooks-cc/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
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",
|
|
@@ -36,14 +36,15 @@
|
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://webhooks.cc",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
40
40
|
"zod": "^3.25.76",
|
|
41
|
-
"@webhooks-cc/sdk": "1.
|
|
41
|
+
"@webhooks-cc/sdk": "1.4.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
+
"@types/node": "^24.12.2",
|
|
44
45
|
"tsup": "^8.5.1",
|
|
45
|
-
"typescript": "^
|
|
46
|
-
"vitest": "^
|
|
46
|
+
"typescript": "^6.0.2",
|
|
47
|
+
"vitest": "^4.1.4"
|
|
47
48
|
},
|
|
48
49
|
"scripts": {
|
|
49
50
|
"build": "tsup",
|