@webhooks-cc/mcp 1.0.1 → 1.2.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 +77 -32
- package/dist/index.js +77 -32
- package/dist/index.mjs +88 -32
- package/package.json +6 -5
package/dist/bin/mcp.js
CHANGED
|
@@ -245,20 +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
|
-
"standard-webhooks"
|
|
257
|
-
];
|
|
258
|
-
var VERIFY_PROVIDER_VALUES = [
|
|
259
|
-
...TEMPLATE_PROVIDER_VALUES,
|
|
260
|
-
"discord"
|
|
261
|
-
];
|
|
262
248
|
var TIME_SEPARATOR = " \u2014 ";
|
|
263
249
|
var httpUrlSchema = import_zod2.z.string().url().refine(
|
|
264
250
|
(value) => {
|
|
@@ -273,11 +259,43 @@ var httpUrlSchema = import_zod2.z.string().url().refine(
|
|
|
273
259
|
);
|
|
274
260
|
var methodSchema = import_zod2.z.enum(HTTP_METHODS).default("POST").describe("HTTP method (default: POST)");
|
|
275
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)");
|
|
276
292
|
var mockResponseSchema = import_zod2.z.object({
|
|
277
|
-
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})`),
|
|
278
294
|
body: import_zod2.z.string().default("").describe("Response body string (default: empty)"),
|
|
279
295
|
headers: import_zod2.z.record(import_zod2.z.string()).default({}).describe("Response headers (default: none)"),
|
|
280
|
-
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
|
+
)
|
|
281
299
|
});
|
|
282
300
|
function textContent(text) {
|
|
283
301
|
return { content: [{ type: "text", text }] };
|
|
@@ -474,12 +492,27 @@ function registerTools(server, client) {
|
|
|
474
492
|
name: import_zod2.z.string().optional().describe("Display name for the endpoint"),
|
|
475
493
|
ephemeral: import_zod2.z.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
|
|
476
494
|
expiresIn: durationOrTimestampSchema.optional().describe('Auto-expire after this duration, for example "12h"'),
|
|
477
|
-
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
|
+
)
|
|
478
502
|
},
|
|
479
|
-
withErrorHandling(
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
503
|
+
withErrorHandling(
|
|
504
|
+
async ({ name, ephemeral, expiresIn, mockResponse, responseRules, notificationUrl }) => {
|
|
505
|
+
const endpoint = await client.endpoints.create({
|
|
506
|
+
name,
|
|
507
|
+
ephemeral,
|
|
508
|
+
expiresIn,
|
|
509
|
+
mockResponse,
|
|
510
|
+
responseRules,
|
|
511
|
+
notificationUrl
|
|
512
|
+
});
|
|
513
|
+
return jsonContent(endpoint);
|
|
514
|
+
}
|
|
515
|
+
)
|
|
483
516
|
);
|
|
484
517
|
server.tool(
|
|
485
518
|
"list_endpoints",
|
|
@@ -501,14 +534,21 @@ function registerTools(server, client) {
|
|
|
501
534
|
);
|
|
502
535
|
server.tool(
|
|
503
536
|
"update_endpoint",
|
|
504
|
-
"Update an endpoint name or
|
|
537
|
+
"Update an endpoint name, mock response, or conditional response rules.",
|
|
505
538
|
{
|
|
506
539
|
slug: import_zod2.z.string().describe("The endpoint slug to update"),
|
|
507
540
|
name: import_zod2.z.string().optional().describe("New display name"),
|
|
508
|
-
mockResponse: mockResponseSchema.nullable().optional().describe("
|
|
541
|
+
mockResponse: mockResponseSchema.nullable().optional().describe("Default mock response (used when no rule matches), or null to clear"),
|
|
542
|
+
responseRules: responseRulesSchema.nullable().optional().describe("Conditional response rules (first match wins), or null to clear"),
|
|
543
|
+
notificationUrl: import_zod2.z.string().url().nullable().optional().describe("Notification webhook URL, or null to clear it")
|
|
509
544
|
},
|
|
510
|
-
withErrorHandling(async ({ slug, name, mockResponse }) => {
|
|
511
|
-
const endpoint = await client.endpoints.update(slug, {
|
|
545
|
+
withErrorHandling(async ({ slug, name, mockResponse, responseRules, notificationUrl }) => {
|
|
546
|
+
const endpoint = await client.endpoints.update(slug, {
|
|
547
|
+
name,
|
|
548
|
+
mockResponse,
|
|
549
|
+
responseRules,
|
|
550
|
+
notificationUrl
|
|
551
|
+
});
|
|
512
552
|
return jsonContent(endpoint);
|
|
513
553
|
})
|
|
514
554
|
);
|
|
@@ -580,7 +620,7 @@ function registerTools(server, client) {
|
|
|
580
620
|
method: methodSchema,
|
|
581
621
|
headers: import_zod2.z.record(import_zod2.z.string()).optional().describe("HTTP headers to include"),
|
|
582
622
|
body: import_zod2.z.unknown().optional().describe("Request body"),
|
|
583
|
-
provider: import_zod2.z.enum(
|
|
623
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to send with signed headers"),
|
|
584
624
|
template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
|
|
585
625
|
event: import_zod2.z.string().optional().describe("Provider event or topic name"),
|
|
586
626
|
secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -765,7 +805,7 @@ function registerTools(server, client) {
|
|
|
765
805
|
"Verify the webhook signature on a captured request.",
|
|
766
806
|
{
|
|
767
807
|
requestId: import_zod2.z.string().describe("The captured request ID"),
|
|
768
|
-
provider: import_zod2.z.enum(
|
|
808
|
+
provider: import_zod2.z.enum(import_sdk.VERIFY_PROVIDERS).describe("Provider whose signature scheme should be verified"),
|
|
769
809
|
secret: import_zod2.z.string().optional().describe("Shared signing secret. Required for non-Discord providers."),
|
|
770
810
|
publicKey: import_zod2.z.string().optional().describe("Discord application public key. Required for provider=discord."),
|
|
771
811
|
url: httpUrlSchema.optional().describe("Original signed URL. Required for Twilio verification.")
|
|
@@ -800,7 +840,7 @@ function registerTools(server, client) {
|
|
|
800
840
|
method: methodSchema,
|
|
801
841
|
headers: import_zod2.z.record(import_zod2.z.string()).optional().describe("HTTP headers to include"),
|
|
802
842
|
body: import_zod2.z.unknown().optional().describe("Request body"),
|
|
803
|
-
provider: import_zod2.z.enum(
|
|
843
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
804
844
|
template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
|
|
805
845
|
event: import_zod2.z.string().optional().describe("Provider event or topic name"),
|
|
806
846
|
secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -831,7 +871,7 @@ function registerTools(server, client) {
|
|
|
831
871
|
method: methodSchema,
|
|
832
872
|
headers: import_zod2.z.record(import_zod2.z.string()).optional().describe("HTTP headers to include"),
|
|
833
873
|
body: import_zod2.z.unknown().optional().describe("Request body"),
|
|
834
|
-
provider: import_zod2.z.enum(
|
|
874
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
835
875
|
template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
|
|
836
876
|
event: import_zod2.z.string().optional().describe("Provider event or topic name"),
|
|
837
877
|
secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -853,7 +893,7 @@ function registerTools(server, client) {
|
|
|
853
893
|
"list_provider_templates",
|
|
854
894
|
"List supported webhook providers, templates, and signing metadata.",
|
|
855
895
|
{
|
|
856
|
-
provider: import_zod2.z.enum(
|
|
896
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Filter to a single provider")
|
|
857
897
|
},
|
|
858
898
|
withErrorHandling(async ({ provider }) => {
|
|
859
899
|
if (provider) {
|
|
@@ -880,7 +920,7 @@ function registerTools(server, client) {
|
|
|
880
920
|
"test_webhook_flow",
|
|
881
921
|
"Run a full webhook test flow: create endpoint, optionally mock, send, wait, verify, replay, and clean up.",
|
|
882
922
|
{
|
|
883
|
-
provider: import_zod2.z.enum(
|
|
923
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to use when sending the webhook"),
|
|
884
924
|
event: import_zod2.z.string().optional().describe("Optional provider event or topic name"),
|
|
885
925
|
secret: import_zod2.z.string().optional().describe(
|
|
886
926
|
"Signing secret. Required when provider is set or signature verification is enabled."
|
|
@@ -921,6 +961,11 @@ function registerTools(server, client) {
|
|
|
921
961
|
secret: templateSecret
|
|
922
962
|
});
|
|
923
963
|
if (shouldVerify) {
|
|
964
|
+
if (provider === "discord") {
|
|
965
|
+
throw new Error(
|
|
966
|
+
"test_webhook_flow cannot verify Discord signatures (Ed25519 requires a public key, not a secret)"
|
|
967
|
+
);
|
|
968
|
+
}
|
|
924
969
|
flow.verifySignature({
|
|
925
970
|
provider,
|
|
926
971
|
secret: templateSecret
|
|
@@ -961,7 +1006,7 @@ function registerTools(server, client) {
|
|
|
961
1006
|
}
|
|
962
1007
|
|
|
963
1008
|
// src/index.ts
|
|
964
|
-
var VERSION = true ? "1.0
|
|
1009
|
+
var VERSION = true ? "1.2.0" : "0.0.0-dev";
|
|
965
1010
|
function createServer(options = {}) {
|
|
966
1011
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
967
1012
|
if (!apiKey) {
|
package/dist/index.js
CHANGED
|
@@ -266,20 +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
|
-
"standard-webhooks"
|
|
278
|
-
];
|
|
279
|
-
var VERIFY_PROVIDER_VALUES = [
|
|
280
|
-
...TEMPLATE_PROVIDER_VALUES,
|
|
281
|
-
"discord"
|
|
282
|
-
];
|
|
283
269
|
var TIME_SEPARATOR = " \u2014 ";
|
|
284
270
|
var httpUrlSchema = import_zod2.z.string().url().refine(
|
|
285
271
|
(value) => {
|
|
@@ -294,11 +280,43 @@ var httpUrlSchema = import_zod2.z.string().url().refine(
|
|
|
294
280
|
);
|
|
295
281
|
var methodSchema = import_zod2.z.enum(HTTP_METHODS).default("POST").describe("HTTP method (default: POST)");
|
|
296
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)");
|
|
297
313
|
var mockResponseSchema = import_zod2.z.object({
|
|
298
|
-
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})`),
|
|
299
315
|
body: import_zod2.z.string().default("").describe("Response body string (default: empty)"),
|
|
300
316
|
headers: import_zod2.z.record(import_zod2.z.string()).default({}).describe("Response headers (default: none)"),
|
|
301
|
-
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
|
+
)
|
|
302
320
|
});
|
|
303
321
|
function textContent(text) {
|
|
304
322
|
return { content: [{ type: "text", text }] };
|
|
@@ -495,12 +513,27 @@ function registerTools(server, client) {
|
|
|
495
513
|
name: import_zod2.z.string().optional().describe("Display name for the endpoint"),
|
|
496
514
|
ephemeral: import_zod2.z.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
|
|
497
515
|
expiresIn: durationOrTimestampSchema.optional().describe('Auto-expire after this duration, for example "12h"'),
|
|
498
|
-
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
|
+
)
|
|
499
523
|
},
|
|
500
|
-
withErrorHandling(
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
524
|
+
withErrorHandling(
|
|
525
|
+
async ({ name, ephemeral, expiresIn, mockResponse, responseRules, notificationUrl }) => {
|
|
526
|
+
const endpoint = await client.endpoints.create({
|
|
527
|
+
name,
|
|
528
|
+
ephemeral,
|
|
529
|
+
expiresIn,
|
|
530
|
+
mockResponse,
|
|
531
|
+
responseRules,
|
|
532
|
+
notificationUrl
|
|
533
|
+
});
|
|
534
|
+
return jsonContent(endpoint);
|
|
535
|
+
}
|
|
536
|
+
)
|
|
504
537
|
);
|
|
505
538
|
server.tool(
|
|
506
539
|
"list_endpoints",
|
|
@@ -522,14 +555,21 @@ function registerTools(server, client) {
|
|
|
522
555
|
);
|
|
523
556
|
server.tool(
|
|
524
557
|
"update_endpoint",
|
|
525
|
-
"Update an endpoint name or
|
|
558
|
+
"Update an endpoint name, mock response, or conditional response rules.",
|
|
526
559
|
{
|
|
527
560
|
slug: import_zod2.z.string().describe("The endpoint slug to update"),
|
|
528
561
|
name: import_zod2.z.string().optional().describe("New display name"),
|
|
529
|
-
mockResponse: mockResponseSchema.nullable().optional().describe("
|
|
562
|
+
mockResponse: mockResponseSchema.nullable().optional().describe("Default mock response (used when no rule matches), or null to clear"),
|
|
563
|
+
responseRules: responseRulesSchema.nullable().optional().describe("Conditional response rules (first match wins), or null to clear"),
|
|
564
|
+
notificationUrl: import_zod2.z.string().url().nullable().optional().describe("Notification webhook URL, or null to clear it")
|
|
530
565
|
},
|
|
531
|
-
withErrorHandling(async ({ slug, name, mockResponse }) => {
|
|
532
|
-
const endpoint = await client.endpoints.update(slug, {
|
|
566
|
+
withErrorHandling(async ({ slug, name, mockResponse, responseRules, notificationUrl }) => {
|
|
567
|
+
const endpoint = await client.endpoints.update(slug, {
|
|
568
|
+
name,
|
|
569
|
+
mockResponse,
|
|
570
|
+
responseRules,
|
|
571
|
+
notificationUrl
|
|
572
|
+
});
|
|
533
573
|
return jsonContent(endpoint);
|
|
534
574
|
})
|
|
535
575
|
);
|
|
@@ -601,7 +641,7 @@ function registerTools(server, client) {
|
|
|
601
641
|
method: methodSchema,
|
|
602
642
|
headers: import_zod2.z.record(import_zod2.z.string()).optional().describe("HTTP headers to include"),
|
|
603
643
|
body: import_zod2.z.unknown().optional().describe("Request body"),
|
|
604
|
-
provider: import_zod2.z.enum(
|
|
644
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to send with signed headers"),
|
|
605
645
|
template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
|
|
606
646
|
event: import_zod2.z.string().optional().describe("Provider event or topic name"),
|
|
607
647
|
secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -786,7 +826,7 @@ function registerTools(server, client) {
|
|
|
786
826
|
"Verify the webhook signature on a captured request.",
|
|
787
827
|
{
|
|
788
828
|
requestId: import_zod2.z.string().describe("The captured request ID"),
|
|
789
|
-
provider: import_zod2.z.enum(
|
|
829
|
+
provider: import_zod2.z.enum(import_sdk.VERIFY_PROVIDERS).describe("Provider whose signature scheme should be verified"),
|
|
790
830
|
secret: import_zod2.z.string().optional().describe("Shared signing secret. Required for non-Discord providers."),
|
|
791
831
|
publicKey: import_zod2.z.string().optional().describe("Discord application public key. Required for provider=discord."),
|
|
792
832
|
url: httpUrlSchema.optional().describe("Original signed URL. Required for Twilio verification.")
|
|
@@ -821,7 +861,7 @@ function registerTools(server, client) {
|
|
|
821
861
|
method: methodSchema,
|
|
822
862
|
headers: import_zod2.z.record(import_zod2.z.string()).optional().describe("HTTP headers to include"),
|
|
823
863
|
body: import_zod2.z.unknown().optional().describe("Request body"),
|
|
824
|
-
provider: import_zod2.z.enum(
|
|
864
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
825
865
|
template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
|
|
826
866
|
event: import_zod2.z.string().optional().describe("Provider event or topic name"),
|
|
827
867
|
secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -852,7 +892,7 @@ function registerTools(server, client) {
|
|
|
852
892
|
method: methodSchema,
|
|
853
893
|
headers: import_zod2.z.record(import_zod2.z.string()).optional().describe("HTTP headers to include"),
|
|
854
894
|
body: import_zod2.z.unknown().optional().describe("Request body"),
|
|
855
|
-
provider: import_zod2.z.enum(
|
|
895
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
856
896
|
template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
|
|
857
897
|
event: import_zod2.z.string().optional().describe("Provider event or topic name"),
|
|
858
898
|
secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -874,7 +914,7 @@ function registerTools(server, client) {
|
|
|
874
914
|
"list_provider_templates",
|
|
875
915
|
"List supported webhook providers, templates, and signing metadata.",
|
|
876
916
|
{
|
|
877
|
-
provider: import_zod2.z.enum(
|
|
917
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Filter to a single provider")
|
|
878
918
|
},
|
|
879
919
|
withErrorHandling(async ({ provider }) => {
|
|
880
920
|
if (provider) {
|
|
@@ -901,7 +941,7 @@ function registerTools(server, client) {
|
|
|
901
941
|
"test_webhook_flow",
|
|
902
942
|
"Run a full webhook test flow: create endpoint, optionally mock, send, wait, verify, replay, and clean up.",
|
|
903
943
|
{
|
|
904
|
-
provider: import_zod2.z.enum(
|
|
944
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to use when sending the webhook"),
|
|
905
945
|
event: import_zod2.z.string().optional().describe("Optional provider event or topic name"),
|
|
906
946
|
secret: import_zod2.z.string().optional().describe(
|
|
907
947
|
"Signing secret. Required when provider is set or signature verification is enabled."
|
|
@@ -942,6 +982,11 @@ function registerTools(server, client) {
|
|
|
942
982
|
secret: templateSecret
|
|
943
983
|
});
|
|
944
984
|
if (shouldVerify) {
|
|
985
|
+
if (provider === "discord") {
|
|
986
|
+
throw new Error(
|
|
987
|
+
"test_webhook_flow cannot verify Discord signatures (Ed25519 requires a public key, not a secret)"
|
|
988
|
+
);
|
|
989
|
+
}
|
|
945
990
|
flow.verifySignature({
|
|
946
991
|
provider,
|
|
947
992
|
secret: templateSecret
|
|
@@ -982,7 +1027,7 @@ function registerTools(server, client) {
|
|
|
982
1027
|
}
|
|
983
1028
|
|
|
984
1029
|
// src/index.ts
|
|
985
|
-
var VERSION = true ? "1.0
|
|
1030
|
+
var VERSION = true ? "1.2.0" : "0.0.0-dev";
|
|
986
1031
|
function createServer(options = {}) {
|
|
987
1032
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
988
1033
|
if (!apiKey) {
|
package/dist/index.mjs
CHANGED
|
@@ -239,29 +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
|
-
"standard-webhooks"
|
|
260
|
-
];
|
|
261
|
-
var VERIFY_PROVIDER_VALUES = [
|
|
262
|
-
...TEMPLATE_PROVIDER_VALUES,
|
|
263
|
-
"discord"
|
|
264
|
-
];
|
|
265
262
|
var TIME_SEPARATOR = " \u2014 ";
|
|
266
263
|
var httpUrlSchema = z2.string().url().refine(
|
|
267
264
|
(value) => {
|
|
@@ -276,11 +273,43 @@ var httpUrlSchema = z2.string().url().refine(
|
|
|
276
273
|
);
|
|
277
274
|
var methodSchema = z2.enum(HTTP_METHODS).default("POST").describe("HTTP method (default: POST)");
|
|
278
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)");
|
|
279
306
|
var mockResponseSchema = z2.object({
|
|
280
|
-
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})`),
|
|
281
308
|
body: z2.string().default("").describe("Response body string (default: empty)"),
|
|
282
309
|
headers: z2.record(z2.string()).default({}).describe("Response headers (default: none)"),
|
|
283
|
-
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
|
+
)
|
|
284
313
|
});
|
|
285
314
|
function textContent(text) {
|
|
286
315
|
return { content: [{ type: "text", text }] };
|
|
@@ -477,12 +506,27 @@ function registerTools(server, client) {
|
|
|
477
506
|
name: z2.string().optional().describe("Display name for the endpoint"),
|
|
478
507
|
ephemeral: z2.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
|
|
479
508
|
expiresIn: durationOrTimestampSchema.optional().describe('Auto-expire after this duration, for example "12h"'),
|
|
480
|
-
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
|
+
)
|
|
481
516
|
},
|
|
482
|
-
withErrorHandling(
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
517
|
+
withErrorHandling(
|
|
518
|
+
async ({ name, ephemeral, expiresIn, mockResponse, responseRules, notificationUrl }) => {
|
|
519
|
+
const endpoint = await client.endpoints.create({
|
|
520
|
+
name,
|
|
521
|
+
ephemeral,
|
|
522
|
+
expiresIn,
|
|
523
|
+
mockResponse,
|
|
524
|
+
responseRules,
|
|
525
|
+
notificationUrl
|
|
526
|
+
});
|
|
527
|
+
return jsonContent(endpoint);
|
|
528
|
+
}
|
|
529
|
+
)
|
|
486
530
|
);
|
|
487
531
|
server.tool(
|
|
488
532
|
"list_endpoints",
|
|
@@ -504,14 +548,21 @@ function registerTools(server, client) {
|
|
|
504
548
|
);
|
|
505
549
|
server.tool(
|
|
506
550
|
"update_endpoint",
|
|
507
|
-
"Update an endpoint name or
|
|
551
|
+
"Update an endpoint name, mock response, or conditional response rules.",
|
|
508
552
|
{
|
|
509
553
|
slug: z2.string().describe("The endpoint slug to update"),
|
|
510
554
|
name: z2.string().optional().describe("New display name"),
|
|
511
|
-
mockResponse: mockResponseSchema.nullable().optional().describe("
|
|
555
|
+
mockResponse: mockResponseSchema.nullable().optional().describe("Default mock response (used when no rule matches), or null to clear"),
|
|
556
|
+
responseRules: responseRulesSchema.nullable().optional().describe("Conditional response rules (first match wins), or null to clear"),
|
|
557
|
+
notificationUrl: z2.string().url().nullable().optional().describe("Notification webhook URL, or null to clear it")
|
|
512
558
|
},
|
|
513
|
-
withErrorHandling(async ({ slug, name, mockResponse }) => {
|
|
514
|
-
const endpoint = await client.endpoints.update(slug, {
|
|
559
|
+
withErrorHandling(async ({ slug, name, mockResponse, responseRules, notificationUrl }) => {
|
|
560
|
+
const endpoint = await client.endpoints.update(slug, {
|
|
561
|
+
name,
|
|
562
|
+
mockResponse,
|
|
563
|
+
responseRules,
|
|
564
|
+
notificationUrl
|
|
565
|
+
});
|
|
515
566
|
return jsonContent(endpoint);
|
|
516
567
|
})
|
|
517
568
|
);
|
|
@@ -583,7 +634,7 @@ function registerTools(server, client) {
|
|
|
583
634
|
method: methodSchema,
|
|
584
635
|
headers: z2.record(z2.string()).optional().describe("HTTP headers to include"),
|
|
585
636
|
body: z2.unknown().optional().describe("Request body"),
|
|
586
|
-
provider: z2.enum(
|
|
637
|
+
provider: z2.enum(TEMPLATE_PROVIDERS).optional().describe("Optional provider template to send with signed headers"),
|
|
587
638
|
template: z2.string().optional().describe("Provider-specific template preset"),
|
|
588
639
|
event: z2.string().optional().describe("Provider event or topic name"),
|
|
589
640
|
secret: z2.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -768,7 +819,7 @@ function registerTools(server, client) {
|
|
|
768
819
|
"Verify the webhook signature on a captured request.",
|
|
769
820
|
{
|
|
770
821
|
requestId: z2.string().describe("The captured request ID"),
|
|
771
|
-
provider: z2.enum(
|
|
822
|
+
provider: z2.enum(VERIFY_PROVIDERS).describe("Provider whose signature scheme should be verified"),
|
|
772
823
|
secret: z2.string().optional().describe("Shared signing secret. Required for non-Discord providers."),
|
|
773
824
|
publicKey: z2.string().optional().describe("Discord application public key. Required for provider=discord."),
|
|
774
825
|
url: httpUrlSchema.optional().describe("Original signed URL. Required for Twilio verification.")
|
|
@@ -803,7 +854,7 @@ function registerTools(server, client) {
|
|
|
803
854
|
method: methodSchema,
|
|
804
855
|
headers: z2.record(z2.string()).optional().describe("HTTP headers to include"),
|
|
805
856
|
body: z2.unknown().optional().describe("Request body"),
|
|
806
|
-
provider: z2.enum(
|
|
857
|
+
provider: z2.enum(TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
807
858
|
template: z2.string().optional().describe("Provider-specific template preset"),
|
|
808
859
|
event: z2.string().optional().describe("Provider event or topic name"),
|
|
809
860
|
secret: z2.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -834,7 +885,7 @@ function registerTools(server, client) {
|
|
|
834
885
|
method: methodSchema,
|
|
835
886
|
headers: z2.record(z2.string()).optional().describe("HTTP headers to include"),
|
|
836
887
|
body: z2.unknown().optional().describe("Request body"),
|
|
837
|
-
provider: z2.enum(
|
|
888
|
+
provider: z2.enum(TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
838
889
|
template: z2.string().optional().describe("Provider-specific template preset"),
|
|
839
890
|
event: z2.string().optional().describe("Provider event or topic name"),
|
|
840
891
|
secret: z2.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -856,7 +907,7 @@ function registerTools(server, client) {
|
|
|
856
907
|
"list_provider_templates",
|
|
857
908
|
"List supported webhook providers, templates, and signing metadata.",
|
|
858
909
|
{
|
|
859
|
-
provider: z2.enum(
|
|
910
|
+
provider: z2.enum(TEMPLATE_PROVIDERS).optional().describe("Filter to a single provider")
|
|
860
911
|
},
|
|
861
912
|
withErrorHandling(async ({ provider }) => {
|
|
862
913
|
if (provider) {
|
|
@@ -883,7 +934,7 @@ function registerTools(server, client) {
|
|
|
883
934
|
"test_webhook_flow",
|
|
884
935
|
"Run a full webhook test flow: create endpoint, optionally mock, send, wait, verify, replay, and clean up.",
|
|
885
936
|
{
|
|
886
|
-
provider: z2.enum(
|
|
937
|
+
provider: z2.enum(TEMPLATE_PROVIDERS).optional().describe("Optional provider template to use when sending the webhook"),
|
|
887
938
|
event: z2.string().optional().describe("Optional provider event or topic name"),
|
|
888
939
|
secret: z2.string().optional().describe(
|
|
889
940
|
"Signing secret. Required when provider is set or signature verification is enabled."
|
|
@@ -924,6 +975,11 @@ function registerTools(server, client) {
|
|
|
924
975
|
secret: templateSecret
|
|
925
976
|
});
|
|
926
977
|
if (shouldVerify) {
|
|
978
|
+
if (provider === "discord") {
|
|
979
|
+
throw new Error(
|
|
980
|
+
"test_webhook_flow cannot verify Discord signatures (Ed25519 requires a public key, not a secret)"
|
|
981
|
+
);
|
|
982
|
+
}
|
|
927
983
|
flow.verifySignature({
|
|
928
984
|
provider,
|
|
929
985
|
secret: templateSecret
|
|
@@ -964,7 +1020,7 @@ function registerTools(server, client) {
|
|
|
964
1020
|
}
|
|
965
1021
|
|
|
966
1022
|
// src/index.ts
|
|
967
|
-
var VERSION = true ? "1.0
|
|
1023
|
+
var VERSION = true ? "1.2.0" : "0.0.0-dev";
|
|
968
1024
|
function createServer(options = {}) {
|
|
969
1025
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
970
1026
|
if (!apiKey) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webhooks-cc/mcp",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.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.0
|
|
41
|
+
"@webhooks-cc/sdk": "1.3.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
+
"@types/node": "^24.0.0",
|
|
44
45
|
"tsup": "^8.5.1",
|
|
45
|
-
"typescript": "^
|
|
46
|
-
"vitest": "^
|
|
46
|
+
"typescript": "^6.0.2",
|
|
47
|
+
"vitest": "^4.1.2"
|
|
47
48
|
},
|
|
48
49
|
"scripts": {
|
|
49
50
|
"build": "tsup",
|