@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 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(100).max(599).describe("HTTP status code (100-599)"),
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(0).max(3e4).optional().describe("Response delay in milliseconds (0-30000, default: none)")
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(async ({ name, ephemeral, expiresIn, mockResponse }) => {
495
- const endpoint = await client.endpoints.create({ name, ephemeral, expiresIn, mockResponse });
496
- return jsonContent(endpoint);
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 or mock response configuration.",
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("Mock response config, or null to clear it")
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(async ({ slug, name, mockResponse }) => {
526
- const endpoint = await client.endpoints.update(slug, { name, mockResponse });
527
- return jsonContent(endpoint);
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(TEMPLATE_PROVIDER_VALUES).optional().describe("Optional provider template to send with signed headers"),
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(VERIFY_PROVIDER_VALUES).describe("Provider whose signature scheme should be verified"),
784
- secret: import_zod2.z.string().optional().describe("Shared signing secret. Required for non-Discord providers."),
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(TEMPLATE_PROVIDER_VALUES).optional().describe("Optional provider template for signing"),
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(TEMPLATE_PROVIDER_VALUES).optional().describe("Optional provider template for signing"),
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(TEMPLATE_PROVIDER_VALUES).optional().describe("Filter to a single provider")
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(TEMPLATE_PROVIDER_VALUES).optional().describe("Optional provider template to use when sending the webhook"),
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.1.0" : "0.0.0-dev";
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(100).max(599).describe("HTTP status code (100-599)"),
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(0).max(3e4).optional().describe("Response delay in milliseconds (0-30000, default: none)")
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(async ({ name, ephemeral, expiresIn, mockResponse }) => {
516
- const endpoint = await client.endpoints.create({ name, ephemeral, expiresIn, mockResponse });
517
- return jsonContent(endpoint);
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 or mock response configuration.",
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("Mock response config, or null to clear it")
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(async ({ slug, name, mockResponse }) => {
547
- const endpoint = await client.endpoints.update(slug, { name, mockResponse });
548
- return jsonContent(endpoint);
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(TEMPLATE_PROVIDER_VALUES).optional().describe("Optional provider template to send with signed headers"),
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(VERIFY_PROVIDER_VALUES).describe("Provider whose signature scheme should be verified"),
805
- secret: import_zod2.z.string().optional().describe("Shared signing secret. Required for non-Discord providers."),
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(TEMPLATE_PROVIDER_VALUES).optional().describe("Optional provider template for signing"),
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(TEMPLATE_PROVIDER_VALUES).optional().describe("Optional provider template for signing"),
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(TEMPLATE_PROVIDER_VALUES).optional().describe("Filter to a single provider")
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(TEMPLATE_PROVIDER_VALUES).optional().describe("Optional provider template to use when sending the webhook"),
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.1.0" : "0.0.0-dev";
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(100).max(599).describe("HTTP status code (100-599)"),
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(0).max(3e4).optional().describe("Response delay in milliseconds (0-30000, default: none)")
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(async ({ name, ephemeral, expiresIn, mockResponse }) => {
498
- const endpoint = await client.endpoints.create({ name, ephemeral, expiresIn, mockResponse });
499
- return jsonContent(endpoint);
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 or mock response configuration.",
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("Mock response config, or null to clear it")
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(async ({ slug, name, mockResponse }) => {
529
- const endpoint = await client.endpoints.update(slug, { name, mockResponse });
530
- return jsonContent(endpoint);
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(TEMPLATE_PROVIDER_VALUES).optional().describe("Optional provider template to send with signed headers"),
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(VERIFY_PROVIDER_VALUES).describe("Provider whose signature scheme should be verified"),
787
- secret: z2.string().optional().describe("Shared signing secret. Required for non-Discord providers."),
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(TEMPLATE_PROVIDER_VALUES).optional().describe("Optional provider template for signing"),
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(TEMPLATE_PROVIDER_VALUES).optional().describe("Optional provider template for signing"),
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(TEMPLATE_PROVIDER_VALUES).optional().describe("Filter to a single provider")
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(TEMPLATE_PROVIDER_VALUES).optional().describe("Optional provider template to use when sending the webhook"),
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.1.0" : "0.0.0-dev";
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.1.0",
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.27.1",
39
+ "@modelcontextprotocol/sdk": "^1.29.0",
40
40
  "zod": "^3.25.76",
41
- "@webhooks-cc/sdk": "1.1.0"
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": "^5.9.3",
46
- "vitest": "^3.2.4"
46
+ "typescript": "^6.0.2",
47
+ "vitest": "^4.1.4"
47
48
  },
48
49
  "scripts": {
49
50
  "build": "tsup",