@webhooks-cc/mcp 1.1.0 → 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 +72 -47
- package/dist/index.js +72 -47
- package/dist/index.mjs +83 -47
- package/package.json +6 -5
package/dist/bin/mcp.js
CHANGED
|
@@ -245,35 +245,6 @@ var import_zod2 = require("zod");
|
|
|
245
245
|
var import_sdk = require("@webhooks-cc/sdk");
|
|
246
246
|
var MAX_BODY_SIZE = 32768;
|
|
247
247
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
248
|
-
var TEMPLATE_PROVIDER_VALUES = [
|
|
249
|
-
"stripe",
|
|
250
|
-
"github",
|
|
251
|
-
"shopify",
|
|
252
|
-
"twilio",
|
|
253
|
-
"slack",
|
|
254
|
-
"paddle",
|
|
255
|
-
"linear",
|
|
256
|
-
"sendgrid",
|
|
257
|
-
"clerk",
|
|
258
|
-
"discord",
|
|
259
|
-
"vercel",
|
|
260
|
-
"gitlab",
|
|
261
|
-
"standard-webhooks"
|
|
262
|
-
];
|
|
263
|
-
var VERIFY_PROVIDER_VALUES = [
|
|
264
|
-
"stripe",
|
|
265
|
-
"github",
|
|
266
|
-
"shopify",
|
|
267
|
-
"twilio",
|
|
268
|
-
"slack",
|
|
269
|
-
"paddle",
|
|
270
|
-
"linear",
|
|
271
|
-
"clerk",
|
|
272
|
-
"discord",
|
|
273
|
-
"vercel",
|
|
274
|
-
"gitlab",
|
|
275
|
-
"standard-webhooks"
|
|
276
|
-
];
|
|
277
248
|
var TIME_SEPARATOR = " \u2014 ";
|
|
278
249
|
var httpUrlSchema = import_zod2.z.string().url().refine(
|
|
279
250
|
(value) => {
|
|
@@ -288,11 +259,43 @@ var httpUrlSchema = import_zod2.z.string().url().refine(
|
|
|
288
259
|
);
|
|
289
260
|
var methodSchema = import_zod2.z.enum(HTTP_METHODS).default("POST").describe("HTTP method (default: POST)");
|
|
290
261
|
var durationOrTimestampSchema = import_zod2.z.union([import_zod2.z.string(), import_zod2.z.number()]);
|
|
262
|
+
var ruleConditionSchema = import_zod2.z.object({
|
|
263
|
+
field: import_zod2.z.enum(["method", "path", "header", "body_contains", "body_path", "query"]).describe("Request field to match against"),
|
|
264
|
+
op: import_zod2.z.enum(["eq", "contains", "starts_with", "matches", "exists"]).describe("Comparison operator"),
|
|
265
|
+
value: import_zod2.z.string().max(import_sdk.MAX_CONDITION_VALUE_LEN).optional().describe(
|
|
266
|
+
`Value to compare against (required unless op is "exists", max ${import_sdk.MAX_CONDITION_VALUE_LEN} chars)`
|
|
267
|
+
),
|
|
268
|
+
name: import_zod2.z.string().max(import_sdk.MAX_CONDITION_NAME_LEN).optional().describe(
|
|
269
|
+
`Header name or query param name (required for header/query, max ${import_sdk.MAX_CONDITION_NAME_LEN} chars)`
|
|
270
|
+
),
|
|
271
|
+
path: import_zod2.z.string().max(import_sdk.MAX_CONDITION_PATH_LEN).optional().describe(
|
|
272
|
+
`JSON dot-notation path (required for body_path, max ${import_sdk.MAX_CONDITION_PATH_LEN} chars)`
|
|
273
|
+
)
|
|
274
|
+
}).refine((c) => c.op === "exists" || c.value !== void 0 && c.value.length > 0, {
|
|
275
|
+
message: 'value is required when op is not "exists"'
|
|
276
|
+
}).refine(
|
|
277
|
+
(c) => !(c.field === "header" || c.field === "query") || c.name !== void 0 && c.name.length > 0,
|
|
278
|
+
{ message: "name is required for header and query conditions" }
|
|
279
|
+
).refine((c) => c.field !== "body_path" || c.path !== void 0 && c.path.length > 0, {
|
|
280
|
+
message: "path is required for body_path conditions"
|
|
281
|
+
}).refine((c) => c.op !== "matches" || !c.value || c.value.length <= import_sdk.MAX_GLOB_PATTERN_LEN, {
|
|
282
|
+
message: `matches pattern must be ${import_sdk.MAX_GLOB_PATTERN_LEN} chars or less`
|
|
283
|
+
});
|
|
284
|
+
var responseRuleSchema = import_zod2.z.object({
|
|
285
|
+
name: import_zod2.z.string().max(import_sdk.MAX_RULE_NAME_LEN).optional().describe(`Human-readable rule name (max ${import_sdk.MAX_RULE_NAME_LEN} chars)`),
|
|
286
|
+
enabled: import_zod2.z.boolean().optional().default(true).describe("Whether this rule is active"),
|
|
287
|
+
logic: import_zod2.z.enum(["and", "or"]).optional().default("and").describe('How to combine conditions: "and" (all match) or "or" (any match)'),
|
|
288
|
+
conditions: import_zod2.z.array(ruleConditionSchema).min(1).max(10).describe("Conditions to evaluate (1-10)"),
|
|
289
|
+
response: import_zod2.z.lazy(() => mockResponseSchema).describe("Response when conditions match")
|
|
290
|
+
});
|
|
291
|
+
var responseRulesSchema = import_zod2.z.array(responseRuleSchema).max(50).describe("Ordered conditional response rules (first match wins, max 50)");
|
|
291
292
|
var mockResponseSchema = import_zod2.z.object({
|
|
292
|
-
status: import_zod2.z.number().int().min(
|
|
293
|
+
status: import_zod2.z.number().int().min(import_sdk.MOCK_RESPONSE_STATUS_MIN).max(import_sdk.MOCK_RESPONSE_STATUS_MAX).describe(`HTTP status code (${import_sdk.MOCK_RESPONSE_STATUS_MIN}-${import_sdk.MOCK_RESPONSE_STATUS_MAX})`),
|
|
293
294
|
body: import_zod2.z.string().default("").describe("Response body string (default: empty)"),
|
|
294
295
|
headers: import_zod2.z.record(import_zod2.z.string()).default({}).describe("Response headers (default: none)"),
|
|
295
|
-
delay: import_zod2.z.number().int().min(
|
|
296
|
+
delay: import_zod2.z.number().int().min(import_sdk.MOCK_RESPONSE_DELAY_MIN).max(import_sdk.MOCK_RESPONSE_DELAY_MAX).optional().describe(
|
|
297
|
+
`Response delay in milliseconds (${import_sdk.MOCK_RESPONSE_DELAY_MIN}-${import_sdk.MOCK_RESPONSE_DELAY_MAX}, default: none)`
|
|
298
|
+
)
|
|
296
299
|
});
|
|
297
300
|
function textContent(text) {
|
|
298
301
|
return { content: [{ type: "text", text }] };
|
|
@@ -489,12 +492,27 @@ 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
|
+
)
|
|
493
502
|
},
|
|
494
|
-
withErrorHandling(
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
+
)
|
|
498
516
|
);
|
|
499
517
|
server.tool(
|
|
500
518
|
"list_endpoints",
|
|
@@ -516,14 +534,21 @@ function registerTools(server, client) {
|
|
|
516
534
|
);
|
|
517
535
|
server.tool(
|
|
518
536
|
"update_endpoint",
|
|
519
|
-
"Update an endpoint name or
|
|
537
|
+
"Update an endpoint name, mock response, or conditional response rules.",
|
|
520
538
|
{
|
|
521
539
|
slug: import_zod2.z.string().describe("The endpoint slug to update"),
|
|
522
540
|
name: import_zod2.z.string().optional().describe("New display name"),
|
|
523
|
-
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")
|
|
524
544
|
},
|
|
525
|
-
withErrorHandling(async ({ slug, name, mockResponse }) => {
|
|
526
|
-
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
|
+
});
|
|
527
552
|
return jsonContent(endpoint);
|
|
528
553
|
})
|
|
529
554
|
);
|
|
@@ -595,7 +620,7 @@ function registerTools(server, client) {
|
|
|
595
620
|
method: methodSchema,
|
|
596
621
|
headers: import_zod2.z.record(import_zod2.z.string()).optional().describe("HTTP headers to include"),
|
|
597
622
|
body: import_zod2.z.unknown().optional().describe("Request body"),
|
|
598
|
-
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"),
|
|
599
624
|
template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
|
|
600
625
|
event: import_zod2.z.string().optional().describe("Provider event or topic name"),
|
|
601
626
|
secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -780,7 +805,7 @@ function registerTools(server, client) {
|
|
|
780
805
|
"Verify the webhook signature on a captured request.",
|
|
781
806
|
{
|
|
782
807
|
requestId: import_zod2.z.string().describe("The captured request ID"),
|
|
783
|
-
provider: import_zod2.z.enum(
|
|
808
|
+
provider: import_zod2.z.enum(import_sdk.VERIFY_PROVIDERS).describe("Provider whose signature scheme should be verified"),
|
|
784
809
|
secret: import_zod2.z.string().optional().describe("Shared signing secret. Required for non-Discord providers."),
|
|
785
810
|
publicKey: import_zod2.z.string().optional().describe("Discord application public key. Required for provider=discord."),
|
|
786
811
|
url: httpUrlSchema.optional().describe("Original signed URL. Required for Twilio verification.")
|
|
@@ -815,7 +840,7 @@ function registerTools(server, client) {
|
|
|
815
840
|
method: methodSchema,
|
|
816
841
|
headers: import_zod2.z.record(import_zod2.z.string()).optional().describe("HTTP headers to include"),
|
|
817
842
|
body: import_zod2.z.unknown().optional().describe("Request body"),
|
|
818
|
-
provider: import_zod2.z.enum(
|
|
843
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
819
844
|
template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
|
|
820
845
|
event: import_zod2.z.string().optional().describe("Provider event or topic name"),
|
|
821
846
|
secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -846,7 +871,7 @@ function registerTools(server, client) {
|
|
|
846
871
|
method: methodSchema,
|
|
847
872
|
headers: import_zod2.z.record(import_zod2.z.string()).optional().describe("HTTP headers to include"),
|
|
848
873
|
body: import_zod2.z.unknown().optional().describe("Request body"),
|
|
849
|
-
provider: import_zod2.z.enum(
|
|
874
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
850
875
|
template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
|
|
851
876
|
event: import_zod2.z.string().optional().describe("Provider event or topic name"),
|
|
852
877
|
secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -868,7 +893,7 @@ function registerTools(server, client) {
|
|
|
868
893
|
"list_provider_templates",
|
|
869
894
|
"List supported webhook providers, templates, and signing metadata.",
|
|
870
895
|
{
|
|
871
|
-
provider: import_zod2.z.enum(
|
|
896
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Filter to a single provider")
|
|
872
897
|
},
|
|
873
898
|
withErrorHandling(async ({ provider }) => {
|
|
874
899
|
if (provider) {
|
|
@@ -895,7 +920,7 @@ function registerTools(server, client) {
|
|
|
895
920
|
"test_webhook_flow",
|
|
896
921
|
"Run a full webhook test flow: create endpoint, optionally mock, send, wait, verify, replay, and clean up.",
|
|
897
922
|
{
|
|
898
|
-
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"),
|
|
899
924
|
event: import_zod2.z.string().optional().describe("Optional provider event or topic name"),
|
|
900
925
|
secret: import_zod2.z.string().optional().describe(
|
|
901
926
|
"Signing secret. Required when provider is set or signature verification is enabled."
|
|
@@ -981,7 +1006,7 @@ function registerTools(server, client) {
|
|
|
981
1006
|
}
|
|
982
1007
|
|
|
983
1008
|
// src/index.ts
|
|
984
|
-
var VERSION = true ? "1.
|
|
1009
|
+
var VERSION = true ? "1.2.0" : "0.0.0-dev";
|
|
985
1010
|
function createServer(options = {}) {
|
|
986
1011
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
987
1012
|
if (!apiKey) {
|
package/dist/index.js
CHANGED
|
@@ -266,35 +266,6 @@ var import_zod2 = require("zod");
|
|
|
266
266
|
var import_sdk = require("@webhooks-cc/sdk");
|
|
267
267
|
var MAX_BODY_SIZE = 32768;
|
|
268
268
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
269
|
-
var TEMPLATE_PROVIDER_VALUES = [
|
|
270
|
-
"stripe",
|
|
271
|
-
"github",
|
|
272
|
-
"shopify",
|
|
273
|
-
"twilio",
|
|
274
|
-
"slack",
|
|
275
|
-
"paddle",
|
|
276
|
-
"linear",
|
|
277
|
-
"sendgrid",
|
|
278
|
-
"clerk",
|
|
279
|
-
"discord",
|
|
280
|
-
"vercel",
|
|
281
|
-
"gitlab",
|
|
282
|
-
"standard-webhooks"
|
|
283
|
-
];
|
|
284
|
-
var VERIFY_PROVIDER_VALUES = [
|
|
285
|
-
"stripe",
|
|
286
|
-
"github",
|
|
287
|
-
"shopify",
|
|
288
|
-
"twilio",
|
|
289
|
-
"slack",
|
|
290
|
-
"paddle",
|
|
291
|
-
"linear",
|
|
292
|
-
"clerk",
|
|
293
|
-
"discord",
|
|
294
|
-
"vercel",
|
|
295
|
-
"gitlab",
|
|
296
|
-
"standard-webhooks"
|
|
297
|
-
];
|
|
298
269
|
var TIME_SEPARATOR = " \u2014 ";
|
|
299
270
|
var httpUrlSchema = import_zod2.z.string().url().refine(
|
|
300
271
|
(value) => {
|
|
@@ -309,11 +280,43 @@ var httpUrlSchema = import_zod2.z.string().url().refine(
|
|
|
309
280
|
);
|
|
310
281
|
var methodSchema = import_zod2.z.enum(HTTP_METHODS).default("POST").describe("HTTP method (default: POST)");
|
|
311
282
|
var durationOrTimestampSchema = import_zod2.z.union([import_zod2.z.string(), import_zod2.z.number()]);
|
|
283
|
+
var ruleConditionSchema = import_zod2.z.object({
|
|
284
|
+
field: import_zod2.z.enum(["method", "path", "header", "body_contains", "body_path", "query"]).describe("Request field to match against"),
|
|
285
|
+
op: import_zod2.z.enum(["eq", "contains", "starts_with", "matches", "exists"]).describe("Comparison operator"),
|
|
286
|
+
value: import_zod2.z.string().max(import_sdk.MAX_CONDITION_VALUE_LEN).optional().describe(
|
|
287
|
+
`Value to compare against (required unless op is "exists", max ${import_sdk.MAX_CONDITION_VALUE_LEN} chars)`
|
|
288
|
+
),
|
|
289
|
+
name: import_zod2.z.string().max(import_sdk.MAX_CONDITION_NAME_LEN).optional().describe(
|
|
290
|
+
`Header name or query param name (required for header/query, max ${import_sdk.MAX_CONDITION_NAME_LEN} chars)`
|
|
291
|
+
),
|
|
292
|
+
path: import_zod2.z.string().max(import_sdk.MAX_CONDITION_PATH_LEN).optional().describe(
|
|
293
|
+
`JSON dot-notation path (required for body_path, max ${import_sdk.MAX_CONDITION_PATH_LEN} chars)`
|
|
294
|
+
)
|
|
295
|
+
}).refine((c) => c.op === "exists" || c.value !== void 0 && c.value.length > 0, {
|
|
296
|
+
message: 'value is required when op is not "exists"'
|
|
297
|
+
}).refine(
|
|
298
|
+
(c) => !(c.field === "header" || c.field === "query") || c.name !== void 0 && c.name.length > 0,
|
|
299
|
+
{ message: "name is required for header and query conditions" }
|
|
300
|
+
).refine((c) => c.field !== "body_path" || c.path !== void 0 && c.path.length > 0, {
|
|
301
|
+
message: "path is required for body_path conditions"
|
|
302
|
+
}).refine((c) => c.op !== "matches" || !c.value || c.value.length <= import_sdk.MAX_GLOB_PATTERN_LEN, {
|
|
303
|
+
message: `matches pattern must be ${import_sdk.MAX_GLOB_PATTERN_LEN} chars or less`
|
|
304
|
+
});
|
|
305
|
+
var responseRuleSchema = import_zod2.z.object({
|
|
306
|
+
name: import_zod2.z.string().max(import_sdk.MAX_RULE_NAME_LEN).optional().describe(`Human-readable rule name (max ${import_sdk.MAX_RULE_NAME_LEN} chars)`),
|
|
307
|
+
enabled: import_zod2.z.boolean().optional().default(true).describe("Whether this rule is active"),
|
|
308
|
+
logic: import_zod2.z.enum(["and", "or"]).optional().default("and").describe('How to combine conditions: "and" (all match) or "or" (any match)'),
|
|
309
|
+
conditions: import_zod2.z.array(ruleConditionSchema).min(1).max(10).describe("Conditions to evaluate (1-10)"),
|
|
310
|
+
response: import_zod2.z.lazy(() => mockResponseSchema).describe("Response when conditions match")
|
|
311
|
+
});
|
|
312
|
+
var responseRulesSchema = import_zod2.z.array(responseRuleSchema).max(50).describe("Ordered conditional response rules (first match wins, max 50)");
|
|
312
313
|
var mockResponseSchema = import_zod2.z.object({
|
|
313
|
-
status: import_zod2.z.number().int().min(
|
|
314
|
+
status: import_zod2.z.number().int().min(import_sdk.MOCK_RESPONSE_STATUS_MIN).max(import_sdk.MOCK_RESPONSE_STATUS_MAX).describe(`HTTP status code (${import_sdk.MOCK_RESPONSE_STATUS_MIN}-${import_sdk.MOCK_RESPONSE_STATUS_MAX})`),
|
|
314
315
|
body: import_zod2.z.string().default("").describe("Response body string (default: empty)"),
|
|
315
316
|
headers: import_zod2.z.record(import_zod2.z.string()).default({}).describe("Response headers (default: none)"),
|
|
316
|
-
delay: import_zod2.z.number().int().min(
|
|
317
|
+
delay: import_zod2.z.number().int().min(import_sdk.MOCK_RESPONSE_DELAY_MIN).max(import_sdk.MOCK_RESPONSE_DELAY_MAX).optional().describe(
|
|
318
|
+
`Response delay in milliseconds (${import_sdk.MOCK_RESPONSE_DELAY_MIN}-${import_sdk.MOCK_RESPONSE_DELAY_MAX}, default: none)`
|
|
319
|
+
)
|
|
317
320
|
});
|
|
318
321
|
function textContent(text) {
|
|
319
322
|
return { content: [{ type: "text", text }] };
|
|
@@ -510,12 +513,27 @@ 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
|
+
)
|
|
514
523
|
},
|
|
515
|
-
withErrorHandling(
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
+
)
|
|
519
537
|
);
|
|
520
538
|
server.tool(
|
|
521
539
|
"list_endpoints",
|
|
@@ -537,14 +555,21 @@ function registerTools(server, client) {
|
|
|
537
555
|
);
|
|
538
556
|
server.tool(
|
|
539
557
|
"update_endpoint",
|
|
540
|
-
"Update an endpoint name or
|
|
558
|
+
"Update an endpoint name, mock response, or conditional response rules.",
|
|
541
559
|
{
|
|
542
560
|
slug: import_zod2.z.string().describe("The endpoint slug to update"),
|
|
543
561
|
name: import_zod2.z.string().optional().describe("New display name"),
|
|
544
|
-
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")
|
|
545
565
|
},
|
|
546
|
-
withErrorHandling(async ({ slug, name, mockResponse }) => {
|
|
547
|
-
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
|
+
});
|
|
548
573
|
return jsonContent(endpoint);
|
|
549
574
|
})
|
|
550
575
|
);
|
|
@@ -616,7 +641,7 @@ function registerTools(server, client) {
|
|
|
616
641
|
method: methodSchema,
|
|
617
642
|
headers: import_zod2.z.record(import_zod2.z.string()).optional().describe("HTTP headers to include"),
|
|
618
643
|
body: import_zod2.z.unknown().optional().describe("Request body"),
|
|
619
|
-
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"),
|
|
620
645
|
template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
|
|
621
646
|
event: import_zod2.z.string().optional().describe("Provider event or topic name"),
|
|
622
647
|
secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -801,7 +826,7 @@ function registerTools(server, client) {
|
|
|
801
826
|
"Verify the webhook signature on a captured request.",
|
|
802
827
|
{
|
|
803
828
|
requestId: import_zod2.z.string().describe("The captured request ID"),
|
|
804
|
-
provider: import_zod2.z.enum(
|
|
829
|
+
provider: import_zod2.z.enum(import_sdk.VERIFY_PROVIDERS).describe("Provider whose signature scheme should be verified"),
|
|
805
830
|
secret: import_zod2.z.string().optional().describe("Shared signing secret. Required for non-Discord providers."),
|
|
806
831
|
publicKey: import_zod2.z.string().optional().describe("Discord application public key. Required for provider=discord."),
|
|
807
832
|
url: httpUrlSchema.optional().describe("Original signed URL. Required for Twilio verification.")
|
|
@@ -836,7 +861,7 @@ function registerTools(server, client) {
|
|
|
836
861
|
method: methodSchema,
|
|
837
862
|
headers: import_zod2.z.record(import_zod2.z.string()).optional().describe("HTTP headers to include"),
|
|
838
863
|
body: import_zod2.z.unknown().optional().describe("Request body"),
|
|
839
|
-
provider: import_zod2.z.enum(
|
|
864
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
840
865
|
template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
|
|
841
866
|
event: import_zod2.z.string().optional().describe("Provider event or topic name"),
|
|
842
867
|
secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -867,7 +892,7 @@ function registerTools(server, client) {
|
|
|
867
892
|
method: methodSchema,
|
|
868
893
|
headers: import_zod2.z.record(import_zod2.z.string()).optional().describe("HTTP headers to include"),
|
|
869
894
|
body: import_zod2.z.unknown().optional().describe("Request body"),
|
|
870
|
-
provider: import_zod2.z.enum(
|
|
895
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
871
896
|
template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
|
|
872
897
|
event: import_zod2.z.string().optional().describe("Provider event or topic name"),
|
|
873
898
|
secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -889,7 +914,7 @@ function registerTools(server, client) {
|
|
|
889
914
|
"list_provider_templates",
|
|
890
915
|
"List supported webhook providers, templates, and signing metadata.",
|
|
891
916
|
{
|
|
892
|
-
provider: import_zod2.z.enum(
|
|
917
|
+
provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Filter to a single provider")
|
|
893
918
|
},
|
|
894
919
|
withErrorHandling(async ({ provider }) => {
|
|
895
920
|
if (provider) {
|
|
@@ -916,7 +941,7 @@ function registerTools(server, client) {
|
|
|
916
941
|
"test_webhook_flow",
|
|
917
942
|
"Run a full webhook test flow: create endpoint, optionally mock, send, wait, verify, replay, and clean up.",
|
|
918
943
|
{
|
|
919
|
-
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"),
|
|
920
945
|
event: import_zod2.z.string().optional().describe("Optional provider event or topic name"),
|
|
921
946
|
secret: import_zod2.z.string().optional().describe(
|
|
922
947
|
"Signing secret. Required when provider is set or signature verification is enabled."
|
|
@@ -1002,7 +1027,7 @@ function registerTools(server, client) {
|
|
|
1002
1027
|
}
|
|
1003
1028
|
|
|
1004
1029
|
// src/index.ts
|
|
1005
|
-
var VERSION = true ? "1.
|
|
1030
|
+
var VERSION = true ? "1.2.0" : "0.0.0-dev";
|
|
1006
1031
|
function createServer(options = {}) {
|
|
1007
1032
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
1008
1033
|
if (!apiKey) {
|
package/dist/index.mjs
CHANGED
|
@@ -239,44 +239,26 @@ import { z as z2 } from "zod";
|
|
|
239
239
|
import {
|
|
240
240
|
diffRequests,
|
|
241
241
|
extractJsonField,
|
|
242
|
+
MOCK_RESPONSE_DELAY_MAX,
|
|
243
|
+
MOCK_RESPONSE_DELAY_MIN,
|
|
244
|
+
MOCK_RESPONSE_STATUS_MAX,
|
|
245
|
+
MOCK_RESPONSE_STATUS_MIN,
|
|
246
|
+
MAX_CONDITION_VALUE_LEN,
|
|
247
|
+
MAX_CONDITION_NAME_LEN,
|
|
248
|
+
MAX_CONDITION_PATH_LEN,
|
|
249
|
+
MAX_RULE_NAME_LEN,
|
|
250
|
+
MAX_GLOB_PATTERN_LEN,
|
|
242
251
|
NotFoundError,
|
|
243
252
|
RateLimitError,
|
|
253
|
+
TEMPLATE_PROVIDERS,
|
|
244
254
|
TimeoutError,
|
|
245
255
|
UnauthorizedError,
|
|
256
|
+
VERIFY_PROVIDERS,
|
|
246
257
|
verifySignature,
|
|
247
258
|
WebhooksCCError
|
|
248
259
|
} from "@webhooks-cc/sdk";
|
|
249
260
|
var MAX_BODY_SIZE = 32768;
|
|
250
261
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
251
|
-
var TEMPLATE_PROVIDER_VALUES = [
|
|
252
|
-
"stripe",
|
|
253
|
-
"github",
|
|
254
|
-
"shopify",
|
|
255
|
-
"twilio",
|
|
256
|
-
"slack",
|
|
257
|
-
"paddle",
|
|
258
|
-
"linear",
|
|
259
|
-
"sendgrid",
|
|
260
|
-
"clerk",
|
|
261
|
-
"discord",
|
|
262
|
-
"vercel",
|
|
263
|
-
"gitlab",
|
|
264
|
-
"standard-webhooks"
|
|
265
|
-
];
|
|
266
|
-
var VERIFY_PROVIDER_VALUES = [
|
|
267
|
-
"stripe",
|
|
268
|
-
"github",
|
|
269
|
-
"shopify",
|
|
270
|
-
"twilio",
|
|
271
|
-
"slack",
|
|
272
|
-
"paddle",
|
|
273
|
-
"linear",
|
|
274
|
-
"clerk",
|
|
275
|
-
"discord",
|
|
276
|
-
"vercel",
|
|
277
|
-
"gitlab",
|
|
278
|
-
"standard-webhooks"
|
|
279
|
-
];
|
|
280
262
|
var TIME_SEPARATOR = " \u2014 ";
|
|
281
263
|
var httpUrlSchema = z2.string().url().refine(
|
|
282
264
|
(value) => {
|
|
@@ -291,11 +273,43 @@ var httpUrlSchema = z2.string().url().refine(
|
|
|
291
273
|
);
|
|
292
274
|
var methodSchema = z2.enum(HTTP_METHODS).default("POST").describe("HTTP method (default: POST)");
|
|
293
275
|
var durationOrTimestampSchema = z2.union([z2.string(), z2.number()]);
|
|
276
|
+
var ruleConditionSchema = z2.object({
|
|
277
|
+
field: z2.enum(["method", "path", "header", "body_contains", "body_path", "query"]).describe("Request field to match against"),
|
|
278
|
+
op: z2.enum(["eq", "contains", "starts_with", "matches", "exists"]).describe("Comparison operator"),
|
|
279
|
+
value: z2.string().max(MAX_CONDITION_VALUE_LEN).optional().describe(
|
|
280
|
+
`Value to compare against (required unless op is "exists", max ${MAX_CONDITION_VALUE_LEN} chars)`
|
|
281
|
+
),
|
|
282
|
+
name: z2.string().max(MAX_CONDITION_NAME_LEN).optional().describe(
|
|
283
|
+
`Header name or query param name (required for header/query, max ${MAX_CONDITION_NAME_LEN} chars)`
|
|
284
|
+
),
|
|
285
|
+
path: z2.string().max(MAX_CONDITION_PATH_LEN).optional().describe(
|
|
286
|
+
`JSON dot-notation path (required for body_path, max ${MAX_CONDITION_PATH_LEN} chars)`
|
|
287
|
+
)
|
|
288
|
+
}).refine((c) => c.op === "exists" || c.value !== void 0 && c.value.length > 0, {
|
|
289
|
+
message: 'value is required when op is not "exists"'
|
|
290
|
+
}).refine(
|
|
291
|
+
(c) => !(c.field === "header" || c.field === "query") || c.name !== void 0 && c.name.length > 0,
|
|
292
|
+
{ message: "name is required for header and query conditions" }
|
|
293
|
+
).refine((c) => c.field !== "body_path" || c.path !== void 0 && c.path.length > 0, {
|
|
294
|
+
message: "path is required for body_path conditions"
|
|
295
|
+
}).refine((c) => c.op !== "matches" || !c.value || c.value.length <= MAX_GLOB_PATTERN_LEN, {
|
|
296
|
+
message: `matches pattern must be ${MAX_GLOB_PATTERN_LEN} chars or less`
|
|
297
|
+
});
|
|
298
|
+
var responseRuleSchema = z2.object({
|
|
299
|
+
name: z2.string().max(MAX_RULE_NAME_LEN).optional().describe(`Human-readable rule name (max ${MAX_RULE_NAME_LEN} chars)`),
|
|
300
|
+
enabled: z2.boolean().optional().default(true).describe("Whether this rule is active"),
|
|
301
|
+
logic: z2.enum(["and", "or"]).optional().default("and").describe('How to combine conditions: "and" (all match) or "or" (any match)'),
|
|
302
|
+
conditions: z2.array(ruleConditionSchema).min(1).max(10).describe("Conditions to evaluate (1-10)"),
|
|
303
|
+
response: z2.lazy(() => mockResponseSchema).describe("Response when conditions match")
|
|
304
|
+
});
|
|
305
|
+
var responseRulesSchema = z2.array(responseRuleSchema).max(50).describe("Ordered conditional response rules (first match wins, max 50)");
|
|
294
306
|
var mockResponseSchema = z2.object({
|
|
295
|
-
status: z2.number().int().min(
|
|
307
|
+
status: z2.number().int().min(MOCK_RESPONSE_STATUS_MIN).max(MOCK_RESPONSE_STATUS_MAX).describe(`HTTP status code (${MOCK_RESPONSE_STATUS_MIN}-${MOCK_RESPONSE_STATUS_MAX})`),
|
|
296
308
|
body: z2.string().default("").describe("Response body string (default: empty)"),
|
|
297
309
|
headers: z2.record(z2.string()).default({}).describe("Response headers (default: none)"),
|
|
298
|
-
delay: z2.number().int().min(
|
|
310
|
+
delay: z2.number().int().min(MOCK_RESPONSE_DELAY_MIN).max(MOCK_RESPONSE_DELAY_MAX).optional().describe(
|
|
311
|
+
`Response delay in milliseconds (${MOCK_RESPONSE_DELAY_MIN}-${MOCK_RESPONSE_DELAY_MAX}, default: none)`
|
|
312
|
+
)
|
|
299
313
|
});
|
|
300
314
|
function textContent(text) {
|
|
301
315
|
return { content: [{ type: "text", text }] };
|
|
@@ -492,12 +506,27 @@ 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
|
+
)
|
|
496
516
|
},
|
|
497
|
-
withErrorHandling(
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
+
)
|
|
501
530
|
);
|
|
502
531
|
server.tool(
|
|
503
532
|
"list_endpoints",
|
|
@@ -519,14 +548,21 @@ function registerTools(server, client) {
|
|
|
519
548
|
);
|
|
520
549
|
server.tool(
|
|
521
550
|
"update_endpoint",
|
|
522
|
-
"Update an endpoint name or
|
|
551
|
+
"Update an endpoint name, mock response, or conditional response rules.",
|
|
523
552
|
{
|
|
524
553
|
slug: z2.string().describe("The endpoint slug to update"),
|
|
525
554
|
name: z2.string().optional().describe("New display name"),
|
|
526
|
-
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")
|
|
527
558
|
},
|
|
528
|
-
withErrorHandling(async ({ slug, name, mockResponse }) => {
|
|
529
|
-
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
|
+
});
|
|
530
566
|
return jsonContent(endpoint);
|
|
531
567
|
})
|
|
532
568
|
);
|
|
@@ -598,7 +634,7 @@ function registerTools(server, client) {
|
|
|
598
634
|
method: methodSchema,
|
|
599
635
|
headers: z2.record(z2.string()).optional().describe("HTTP headers to include"),
|
|
600
636
|
body: z2.unknown().optional().describe("Request body"),
|
|
601
|
-
provider: z2.enum(
|
|
637
|
+
provider: z2.enum(TEMPLATE_PROVIDERS).optional().describe("Optional provider template to send with signed headers"),
|
|
602
638
|
template: z2.string().optional().describe("Provider-specific template preset"),
|
|
603
639
|
event: z2.string().optional().describe("Provider event or topic name"),
|
|
604
640
|
secret: z2.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -783,7 +819,7 @@ function registerTools(server, client) {
|
|
|
783
819
|
"Verify the webhook signature on a captured request.",
|
|
784
820
|
{
|
|
785
821
|
requestId: z2.string().describe("The captured request ID"),
|
|
786
|
-
provider: z2.enum(
|
|
822
|
+
provider: z2.enum(VERIFY_PROVIDERS).describe("Provider whose signature scheme should be verified"),
|
|
787
823
|
secret: z2.string().optional().describe("Shared signing secret. Required for non-Discord providers."),
|
|
788
824
|
publicKey: z2.string().optional().describe("Discord application public key. Required for provider=discord."),
|
|
789
825
|
url: httpUrlSchema.optional().describe("Original signed URL. Required for Twilio verification.")
|
|
@@ -818,7 +854,7 @@ function registerTools(server, client) {
|
|
|
818
854
|
method: methodSchema,
|
|
819
855
|
headers: z2.record(z2.string()).optional().describe("HTTP headers to include"),
|
|
820
856
|
body: z2.unknown().optional().describe("Request body"),
|
|
821
|
-
provider: z2.enum(
|
|
857
|
+
provider: z2.enum(TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
822
858
|
template: z2.string().optional().describe("Provider-specific template preset"),
|
|
823
859
|
event: z2.string().optional().describe("Provider event or topic name"),
|
|
824
860
|
secret: z2.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -849,7 +885,7 @@ function registerTools(server, client) {
|
|
|
849
885
|
method: methodSchema,
|
|
850
886
|
headers: z2.record(z2.string()).optional().describe("HTTP headers to include"),
|
|
851
887
|
body: z2.unknown().optional().describe("Request body"),
|
|
852
|
-
provider: z2.enum(
|
|
888
|
+
provider: z2.enum(TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
|
|
853
889
|
template: z2.string().optional().describe("Provider-specific template preset"),
|
|
854
890
|
event: z2.string().optional().describe("Provider event or topic name"),
|
|
855
891
|
secret: z2.string().optional().describe("Signing secret. Required when provider is set.")
|
|
@@ -871,7 +907,7 @@ function registerTools(server, client) {
|
|
|
871
907
|
"list_provider_templates",
|
|
872
908
|
"List supported webhook providers, templates, and signing metadata.",
|
|
873
909
|
{
|
|
874
|
-
provider: z2.enum(
|
|
910
|
+
provider: z2.enum(TEMPLATE_PROVIDERS).optional().describe("Filter to a single provider")
|
|
875
911
|
},
|
|
876
912
|
withErrorHandling(async ({ provider }) => {
|
|
877
913
|
if (provider) {
|
|
@@ -898,7 +934,7 @@ function registerTools(server, client) {
|
|
|
898
934
|
"test_webhook_flow",
|
|
899
935
|
"Run a full webhook test flow: create endpoint, optionally mock, send, wait, verify, replay, and clean up.",
|
|
900
936
|
{
|
|
901
|
-
provider: z2.enum(
|
|
937
|
+
provider: z2.enum(TEMPLATE_PROVIDERS).optional().describe("Optional provider template to use when sending the webhook"),
|
|
902
938
|
event: z2.string().optional().describe("Optional provider event or topic name"),
|
|
903
939
|
secret: z2.string().optional().describe(
|
|
904
940
|
"Signing secret. Required when provider is set or signature verification is enabled."
|
|
@@ -984,7 +1020,7 @@ function registerTools(server, client) {
|
|
|
984
1020
|
}
|
|
985
1021
|
|
|
986
1022
|
// src/index.ts
|
|
987
|
-
var VERSION = true ? "1.
|
|
1023
|
+
var VERSION = true ? "1.2.0" : "0.0.0-dev";
|
|
988
1024
|
function createServer(options = {}) {
|
|
989
1025
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
990
1026
|
if (!apiKey) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webhooks-cc/mcp",
|
|
3
|
-
"version": "1.
|
|
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.
|
|
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",
|