@webhooks-cc/mcp 1.12.1 → 1.13.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/README.md +14 -3
- package/dist/bin/mcp.js +86 -3
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +84 -1
- package/dist/index.mjs +88 -2
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -11,6 +11,16 @@ The server reads `WHK_API_KEY` from the environment. It also supports:
|
|
|
11
11
|
- `WHK_BASE_URL` for a custom API base URL
|
|
12
12
|
- `WHK_WEBHOOK_URL` for a custom receiver base URL
|
|
13
13
|
|
|
14
|
+
### Claude Code plugin
|
|
15
|
+
|
|
16
|
+
The plugin installs the server together with a webhook testing skill that tells Claude when to use it:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
/plugin install webhooks-cc --marketplace kroqdotdev/webhooks-cc
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
For other agents, `npx skills add kroqdotdev/webhooks-cc` installs the same skill. See [Agent skill](https://webhooks.cc/docs/mcp/skills).
|
|
23
|
+
|
|
14
24
|
### One-click
|
|
15
25
|
|
|
16
26
|
- **Cursor**: [Add to Cursor](https://cursor.com/en/install-mcp?name=webhooks-cc&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB3ZWJob29rcy1jYy9tY3AiXSwiZW52Ijp7IldIS19BUElfS0VZIjoid2hjY18uLi4ifX0=)
|
|
@@ -54,11 +64,11 @@ npx @webhooks-cc/mcp setup claude-desktop --api-key whcc_...
|
|
|
54
64
|
}
|
|
55
65
|
```
|
|
56
66
|
|
|
57
|
-
Get your API key at [webhooks.cc/account](https://webhooks.cc/account).
|
|
67
|
+
Get your API key at [webhooks.cc/account/api-keys](https://webhooks.cc/account/api-keys).
|
|
58
68
|
|
|
59
69
|
## What it exposes
|
|
60
70
|
|
|
61
|
-
The server exposes `
|
|
71
|
+
The server exposes `39` tools, `3` prompts, and `3` resource surfaces with an API key, and `11` sandbox tools without one.
|
|
62
72
|
|
|
63
73
|
### Endpoint tools
|
|
64
74
|
|
|
@@ -80,6 +90,7 @@ The server exposes `38` tools, `3` prompts, and `3` resource surfaces with an AP
|
|
|
80
90
|
- `wait_for_requests`
|
|
81
91
|
- `replay_request`
|
|
82
92
|
- `compare_requests`
|
|
93
|
+
- `generate_payload_schema` (TypeScript, Zod, Pydantic, or JSON Schema from captured bodies)
|
|
83
94
|
- `extract_from_request`
|
|
84
95
|
- `verify_signature`
|
|
85
96
|
- `clear_requests`
|
|
@@ -132,7 +143,7 @@ Endpoints shared with you already appear in `list_endpoints` (with `fromTeam`) a
|
|
|
132
143
|
|
|
133
144
|
Started without `WHK_API_KEY`, the server registers sandbox tools instead ([auth.md](https://webhooks.cc/auth.md)):
|
|
134
145
|
|
|
135
|
-
- `create_endpoint`, `list_endpoints`, `get_endpoint`, `delete_endpoint`, `list_requests`, `get_request`, `wait_for_request`: the same names and arguments as the full tools, in a sandbox of up to 3 endpoints, 25 captured requests each and 100 in all, for 24 hours. The first `create_endpoint` registers it (a few seconds of proof of work).
|
|
146
|
+
- `create_endpoint`, `list_endpoints`, `get_endpoint`, `delete_endpoint`, `list_requests`, `get_request`, `wait_for_request`, `generate_payload_schema`: the same names and arguments as the full tools, in a sandbox of up to 3 endpoints, 25 captured requests each and 100 in all, for 24 hours. The first `create_endpoint` registers it (a few seconds of proof of work).
|
|
136
147
|
- `connect_account`: returns a link and a 6-digit code for a human to connect the agent to their account.
|
|
137
148
|
- `wait_for_connection`: waits for the human, then replaces the sandbox tools with the full tools for that account (`notifications/tools/list_changed`).
|
|
138
149
|
- `about_sandbox`: the limits and how to connect, without a network call.
|
package/dist/bin/mcp.js
CHANGED
|
@@ -76,6 +76,7 @@ function registerPrompts(server) {
|
|
|
76
76
|
"If a target URL is provided, use preview_webhook before send_to so the request shape is visible.",
|
|
77
77
|
targetUrl ? `Target URL: ${targetUrl}.` : null,
|
|
78
78
|
"Send a realistic provider webhook, wait for capture, and verify the signature if a secret is available.",
|
|
79
|
+
"Once real deliveries are captured, use generate_payload_schema to write the payload types for the handler.",
|
|
79
80
|
"Return the endpoint URL, the exact tools used, and the next step for the developer."
|
|
80
81
|
].filter(Boolean).join(" ")
|
|
81
82
|
}
|
|
@@ -493,6 +494,67 @@ var waitForRequestShape = {
|
|
|
493
494
|
timeout: durationOrTimestampSchema.default("30s").describe('How long to wait, for example "30s"'),
|
|
494
495
|
pollInterval: durationOrTimestampSchema.optional().describe('Interval between polls, for example "500ms" or "1s"')
|
|
495
496
|
};
|
|
497
|
+
var payloadSchemaShape = {
|
|
498
|
+
endpointSlug: import_zod2.z.string().describe("The endpoint slug"),
|
|
499
|
+
format: import_zod2.z.enum(import_sdk.PAYLOAD_SCHEMA_FORMATS).default("typescript").describe(
|
|
500
|
+
'Output: "typescript" interfaces, "zod" schemas, "pydantic" models, or "json-schema"'
|
|
501
|
+
),
|
|
502
|
+
name: import_zod2.z.string().max(100).optional().describe("Root type name (default: from the detected event, else WebhookPayload)"),
|
|
503
|
+
event: import_zod2.z.string().optional().describe(
|
|
504
|
+
'Only requests whose detected provider event matches, e.g. "invoice.paid" or "push". One event per schema gives the tightest types.'
|
|
505
|
+
),
|
|
506
|
+
method: import_zod2.z.string().optional().describe("Only requests with this HTTP method"),
|
|
507
|
+
path: import_zod2.z.string().optional().describe("Only requests with this path"),
|
|
508
|
+
requestIds: import_zod2.z.array(import_zod2.z.string()).min(1).max(50).optional().describe("Use exactly these requests instead of the latest ones"),
|
|
509
|
+
limit: import_zod2.z.number().int().min(1).max(100).default(20).describe("How many of the latest requests to read before filtering")
|
|
510
|
+
};
|
|
511
|
+
function eventName(event) {
|
|
512
|
+
const words = event.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
513
|
+
const name = words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
514
|
+
if (!name) return "WebhookPayload";
|
|
515
|
+
return /^[0-9]/.test(name) ? `Webhook${name}Event` : `${name}Event`;
|
|
516
|
+
}
|
|
517
|
+
function payloadSchemaResult(requests, args2) {
|
|
518
|
+
const method = args2.method?.toUpperCase();
|
|
519
|
+
const matching = requests.filter(
|
|
520
|
+
(request) => request.kind !== "email" && (!method || request.method.toUpperCase() === method) && (!args2.path || request.path === args2.path) && (!args2.event || (0, import_sdk.detectWebhookInfo)(request)?.event === args2.event)
|
|
521
|
+
);
|
|
522
|
+
if (matching.length === 0) {
|
|
523
|
+
throw new Error(
|
|
524
|
+
`None of the ${requests.length} requests read matched. Capture a request first, or loosen event, method or path.`
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
const events = [
|
|
528
|
+
...new Set(
|
|
529
|
+
matching.map((request) => (0, import_sdk.detectWebhookInfo)(request)?.event).filter((e) => !!e)
|
|
530
|
+
)
|
|
531
|
+
];
|
|
532
|
+
const name = args2.name ?? (events.length === 1 ? eventName(events[0]) : void 0);
|
|
533
|
+
const result = (0, import_sdk.generateRequestSchema)(matching, { format: args2.format, name });
|
|
534
|
+
if (result.samples === 0) {
|
|
535
|
+
throw new Error(
|
|
536
|
+
`None of the ${matching.length} matching requests has a JSON or form body to learn from.`
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
const output = {
|
|
540
|
+
format: result.format,
|
|
541
|
+
name: result.name,
|
|
542
|
+
samples: result.samples,
|
|
543
|
+
skipped: result.skipped,
|
|
544
|
+
...events.length > 0 ? { events } : {},
|
|
545
|
+
...events.length > 1 ? {
|
|
546
|
+
warning: `The samples mix ${events.length} events, so fields of one event show up as optional. Pass event for one schema per event.`
|
|
547
|
+
} : {},
|
|
548
|
+
code: result.code
|
|
549
|
+
};
|
|
550
|
+
const size = JSON.stringify(output, null, 2).length;
|
|
551
|
+
if (size > MAX_OUTPUT) {
|
|
552
|
+
throw new Error(
|
|
553
|
+
`The schema is ${result.code.length} characters, more than this tool can return. Narrow it with event or requestIds, or call generateRequestSchema() from @webhooks-cc/sdk in code.`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
return jsonContent(output);
|
|
557
|
+
}
|
|
496
558
|
function textContent(text) {
|
|
497
559
|
return { content: [{ type: "text", text }] };
|
|
498
560
|
}
|
|
@@ -1189,6 +1251,15 @@ function registerTools(server, client) {
|
|
|
1189
1251
|
return jsonContent(diff);
|
|
1190
1252
|
})
|
|
1191
1253
|
);
|
|
1254
|
+
server.tool(
|
|
1255
|
+
"generate_payload_schema",
|
|
1256
|
+
"Generate TypeScript interfaces, Zod schemas, Pydantic models, or JSON Schema from captured request bodies. Several requests of one event teach which fields are optional or nullable, so filter with event (or pass requestIds) and write the result into the codebase instead of hand-typing the payload.",
|
|
1257
|
+
payloadSchemaShape,
|
|
1258
|
+
withErrorHandling(async ({ endpointSlug, requestIds, limit, ...args2 }) => {
|
|
1259
|
+
const requests = requestIds ? await Promise.all(requestIds.map((id) => client.requests.get(id))) : await client.requests.list(endpointSlug, { limit, kind: "http" });
|
|
1260
|
+
return payloadSchemaResult(requests, args2);
|
|
1261
|
+
})
|
|
1262
|
+
);
|
|
1192
1263
|
server.tool(
|
|
1193
1264
|
"extract_from_request",
|
|
1194
1265
|
"Extract specific JSON fields from a captured request body.",
|
|
@@ -1879,6 +1950,18 @@ function registerSandboxTools(server, options = {}) {
|
|
|
1879
1950
|
})
|
|
1880
1951
|
)
|
|
1881
1952
|
);
|
|
1953
|
+
registered.push(
|
|
1954
|
+
server.tool(
|
|
1955
|
+
"generate_payload_schema",
|
|
1956
|
+
"Generate TypeScript interfaces, Zod schemas, Pydantic models, or JSON Schema from the bodies of requests captured by a sandbox endpoint. Several requests of one event teach which fields are optional or nullable.",
|
|
1957
|
+
payloadSchemaShape,
|
|
1958
|
+
withErrorHandling(async ({ endpointSlug, requestIds, limit, ...args2 }) => {
|
|
1959
|
+
const sandbox = await session.requireSandbox();
|
|
1960
|
+
const requests = requestIds ? await Promise.all(requestIds.map((id) => sandbox.requests.get(id))) : await sandbox.requests.list(endpointSlug, { limit });
|
|
1961
|
+
return payloadSchemaResult(requests, args2);
|
|
1962
|
+
})
|
|
1963
|
+
)
|
|
1964
|
+
);
|
|
1882
1965
|
registered.push(
|
|
1883
1966
|
server.tool(
|
|
1884
1967
|
"connect_account",
|
|
@@ -2034,7 +2117,7 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2034
2117
|
}
|
|
2035
2118
|
|
|
2036
2119
|
// src/index.ts
|
|
2037
|
-
var VERSION = true ? "1.
|
|
2120
|
+
var VERSION = true ? "1.13.0" : "0.0.0-dev";
|
|
2038
2121
|
function createServer(options = {}) {
|
|
2039
2122
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
2040
2123
|
const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
|
|
@@ -2255,7 +2338,7 @@ function runSetup(args2) {
|
|
|
2255
2338
|
const apiKey = resolveApiKey(flags);
|
|
2256
2339
|
if (!apiKey) {
|
|
2257
2340
|
console.error("Error: API key required. Pass --api-key or set WHK_API_KEY env var.");
|
|
2258
|
-
console.error("Get your API key at https://webhooks.cc/account\n");
|
|
2341
|
+
console.error("Get your API key at https://webhooks.cc/account/api-keys\n");
|
|
2259
2342
|
process.exit(1);
|
|
2260
2343
|
}
|
|
2261
2344
|
validateApiKeyFormat(apiKey);
|
|
@@ -2310,7 +2393,7 @@ Setup:
|
|
|
2310
2393
|
npx @webhooks-cc/mcp setup windsurf --api-key whcc_...
|
|
2311
2394
|
npx @webhooks-cc/mcp setup claude-desktop --api-key whcc_...
|
|
2312
2395
|
|
|
2313
|
-
Get your API key at https://webhooks.cc/account
|
|
2396
|
+
Get your API key at https://webhooks.cc/account/api-keys
|
|
2314
2397
|
`);
|
|
2315
2398
|
} else if (command && command !== "--") {
|
|
2316
2399
|
console.error(`Unknown command: "${command}". Run with --help for usage.`);
|
package/dist/index.d.mts
CHANGED
|
@@ -53,4 +53,5 @@ interface CreateServerOptions {
|
|
|
53
53
|
*/
|
|
54
54
|
declare function createServer(options?: CreateServerOptions): McpServer;
|
|
55
55
|
|
|
56
|
-
export {
|
|
56
|
+
export { createServer, registerAgentRegistrationTools, registerPrompts, registerResources, registerSandboxTools, registerTools };
|
|
57
|
+
export type { CreateServerOptions, SandboxToolOptions };
|
package/dist/index.d.ts
CHANGED
|
@@ -53,4 +53,5 @@ interface CreateServerOptions {
|
|
|
53
53
|
*/
|
|
54
54
|
declare function createServer(options?: CreateServerOptions): McpServer;
|
|
55
55
|
|
|
56
|
-
export {
|
|
56
|
+
export { createServer, registerAgentRegistrationTools, registerPrompts, registerResources, registerSandboxTools, registerTools };
|
|
57
|
+
export type { CreateServerOptions, SandboxToolOptions };
|
package/dist/index.js
CHANGED
|
@@ -99,6 +99,7 @@ function registerPrompts(server) {
|
|
|
99
99
|
"If a target URL is provided, use preview_webhook before send_to so the request shape is visible.",
|
|
100
100
|
targetUrl ? `Target URL: ${targetUrl}.` : null,
|
|
101
101
|
"Send a realistic provider webhook, wait for capture, and verify the signature if a secret is available.",
|
|
102
|
+
"Once real deliveries are captured, use generate_payload_schema to write the payload types for the handler.",
|
|
102
103
|
"Return the endpoint URL, the exact tools used, and the next step for the developer."
|
|
103
104
|
].filter(Boolean).join(" ")
|
|
104
105
|
}
|
|
@@ -516,6 +517,67 @@ var waitForRequestShape = {
|
|
|
516
517
|
timeout: durationOrTimestampSchema.default("30s").describe('How long to wait, for example "30s"'),
|
|
517
518
|
pollInterval: durationOrTimestampSchema.optional().describe('Interval between polls, for example "500ms" or "1s"')
|
|
518
519
|
};
|
|
520
|
+
var payloadSchemaShape = {
|
|
521
|
+
endpointSlug: import_zod2.z.string().describe("The endpoint slug"),
|
|
522
|
+
format: import_zod2.z.enum(import_sdk.PAYLOAD_SCHEMA_FORMATS).default("typescript").describe(
|
|
523
|
+
'Output: "typescript" interfaces, "zod" schemas, "pydantic" models, or "json-schema"'
|
|
524
|
+
),
|
|
525
|
+
name: import_zod2.z.string().max(100).optional().describe("Root type name (default: from the detected event, else WebhookPayload)"),
|
|
526
|
+
event: import_zod2.z.string().optional().describe(
|
|
527
|
+
'Only requests whose detected provider event matches, e.g. "invoice.paid" or "push". One event per schema gives the tightest types.'
|
|
528
|
+
),
|
|
529
|
+
method: import_zod2.z.string().optional().describe("Only requests with this HTTP method"),
|
|
530
|
+
path: import_zod2.z.string().optional().describe("Only requests with this path"),
|
|
531
|
+
requestIds: import_zod2.z.array(import_zod2.z.string()).min(1).max(50).optional().describe("Use exactly these requests instead of the latest ones"),
|
|
532
|
+
limit: import_zod2.z.number().int().min(1).max(100).default(20).describe("How many of the latest requests to read before filtering")
|
|
533
|
+
};
|
|
534
|
+
function eventName(event) {
|
|
535
|
+
const words = event.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
536
|
+
const name = words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
537
|
+
if (!name) return "WebhookPayload";
|
|
538
|
+
return /^[0-9]/.test(name) ? `Webhook${name}Event` : `${name}Event`;
|
|
539
|
+
}
|
|
540
|
+
function payloadSchemaResult(requests, args) {
|
|
541
|
+
const method = args.method?.toUpperCase();
|
|
542
|
+
const matching = requests.filter(
|
|
543
|
+
(request) => request.kind !== "email" && (!method || request.method.toUpperCase() === method) && (!args.path || request.path === args.path) && (!args.event || (0, import_sdk.detectWebhookInfo)(request)?.event === args.event)
|
|
544
|
+
);
|
|
545
|
+
if (matching.length === 0) {
|
|
546
|
+
throw new Error(
|
|
547
|
+
`None of the ${requests.length} requests read matched. Capture a request first, or loosen event, method or path.`
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
const events = [
|
|
551
|
+
...new Set(
|
|
552
|
+
matching.map((request) => (0, import_sdk.detectWebhookInfo)(request)?.event).filter((e) => !!e)
|
|
553
|
+
)
|
|
554
|
+
];
|
|
555
|
+
const name = args.name ?? (events.length === 1 ? eventName(events[0]) : void 0);
|
|
556
|
+
const result = (0, import_sdk.generateRequestSchema)(matching, { format: args.format, name });
|
|
557
|
+
if (result.samples === 0) {
|
|
558
|
+
throw new Error(
|
|
559
|
+
`None of the ${matching.length} matching requests has a JSON or form body to learn from.`
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
const output = {
|
|
563
|
+
format: result.format,
|
|
564
|
+
name: result.name,
|
|
565
|
+
samples: result.samples,
|
|
566
|
+
skipped: result.skipped,
|
|
567
|
+
...events.length > 0 ? { events } : {},
|
|
568
|
+
...events.length > 1 ? {
|
|
569
|
+
warning: `The samples mix ${events.length} events, so fields of one event show up as optional. Pass event for one schema per event.`
|
|
570
|
+
} : {},
|
|
571
|
+
code: result.code
|
|
572
|
+
};
|
|
573
|
+
const size = JSON.stringify(output, null, 2).length;
|
|
574
|
+
if (size > MAX_OUTPUT) {
|
|
575
|
+
throw new Error(
|
|
576
|
+
`The schema is ${result.code.length} characters, more than this tool can return. Narrow it with event or requestIds, or call generateRequestSchema() from @webhooks-cc/sdk in code.`
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
return jsonContent(output);
|
|
580
|
+
}
|
|
519
581
|
function textContent(text) {
|
|
520
582
|
return { content: [{ type: "text", text }] };
|
|
521
583
|
}
|
|
@@ -1212,6 +1274,15 @@ function registerTools(server, client) {
|
|
|
1212
1274
|
return jsonContent(diff);
|
|
1213
1275
|
})
|
|
1214
1276
|
);
|
|
1277
|
+
server.tool(
|
|
1278
|
+
"generate_payload_schema",
|
|
1279
|
+
"Generate TypeScript interfaces, Zod schemas, Pydantic models, or JSON Schema from captured request bodies. Several requests of one event teach which fields are optional or nullable, so filter with event (or pass requestIds) and write the result into the codebase instead of hand-typing the payload.",
|
|
1280
|
+
payloadSchemaShape,
|
|
1281
|
+
withErrorHandling(async ({ endpointSlug, requestIds, limit, ...args }) => {
|
|
1282
|
+
const requests = requestIds ? await Promise.all(requestIds.map((id) => client.requests.get(id))) : await client.requests.list(endpointSlug, { limit, kind: "http" });
|
|
1283
|
+
return payloadSchemaResult(requests, args);
|
|
1284
|
+
})
|
|
1285
|
+
);
|
|
1215
1286
|
server.tool(
|
|
1216
1287
|
"extract_from_request",
|
|
1217
1288
|
"Extract specific JSON fields from a captured request body.",
|
|
@@ -1902,6 +1973,18 @@ function registerSandboxTools(server, options = {}) {
|
|
|
1902
1973
|
})
|
|
1903
1974
|
)
|
|
1904
1975
|
);
|
|
1976
|
+
registered.push(
|
|
1977
|
+
server.tool(
|
|
1978
|
+
"generate_payload_schema",
|
|
1979
|
+
"Generate TypeScript interfaces, Zod schemas, Pydantic models, or JSON Schema from the bodies of requests captured by a sandbox endpoint. Several requests of one event teach which fields are optional or nullable.",
|
|
1980
|
+
payloadSchemaShape,
|
|
1981
|
+
withErrorHandling(async ({ endpointSlug, requestIds, limit, ...args }) => {
|
|
1982
|
+
const sandbox = await session.requireSandbox();
|
|
1983
|
+
const requests = requestIds ? await Promise.all(requestIds.map((id) => sandbox.requests.get(id))) : await sandbox.requests.list(endpointSlug, { limit });
|
|
1984
|
+
return payloadSchemaResult(requests, args);
|
|
1985
|
+
})
|
|
1986
|
+
)
|
|
1987
|
+
);
|
|
1905
1988
|
registered.push(
|
|
1906
1989
|
server.tool(
|
|
1907
1990
|
"connect_account",
|
|
@@ -2060,7 +2143,7 @@ function registerAgentRegistrationTools(server) {
|
|
|
2060
2143
|
}
|
|
2061
2144
|
|
|
2062
2145
|
// src/index.ts
|
|
2063
|
-
var VERSION = true ? "1.
|
|
2146
|
+
var VERSION = true ? "1.13.0" : "0.0.0-dev";
|
|
2064
2147
|
function createServer(options = {}) {
|
|
2065
2148
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
2066
2149
|
const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
|
package/dist/index.mjs
CHANGED
|
@@ -70,6 +70,7 @@ function registerPrompts(server) {
|
|
|
70
70
|
"If a target URL is provided, use preview_webhook before send_to so the request shape is visible.",
|
|
71
71
|
targetUrl ? `Target URL: ${targetUrl}.` : null,
|
|
72
72
|
"Send a realistic provider webhook, wait for capture, and verify the signature if a secret is available.",
|
|
73
|
+
"Once real deliveries are captured, use generate_payload_schema to write the payload types for the handler.",
|
|
73
74
|
"Return the endpoint URL, the exact tools used, and the next step for the developer."
|
|
74
75
|
].filter(Boolean).join(" ")
|
|
75
76
|
}
|
|
@@ -417,7 +418,10 @@ import {
|
|
|
417
418
|
verifySignature,
|
|
418
419
|
WebhooksCCError,
|
|
419
420
|
extractCode,
|
|
420
|
-
extractLink
|
|
421
|
+
extractLink,
|
|
422
|
+
detectWebhookInfo,
|
|
423
|
+
generateRequestSchema,
|
|
424
|
+
PAYLOAD_SCHEMA_FORMATS
|
|
421
425
|
} from "@webhooks-cc/sdk";
|
|
422
426
|
var MAX_BODY_SIZE = MAX_OUTPUT;
|
|
423
427
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
@@ -513,6 +517,67 @@ var waitForRequestShape = {
|
|
|
513
517
|
timeout: durationOrTimestampSchema.default("30s").describe('How long to wait, for example "30s"'),
|
|
514
518
|
pollInterval: durationOrTimestampSchema.optional().describe('Interval between polls, for example "500ms" or "1s"')
|
|
515
519
|
};
|
|
520
|
+
var payloadSchemaShape = {
|
|
521
|
+
endpointSlug: z2.string().describe("The endpoint slug"),
|
|
522
|
+
format: z2.enum(PAYLOAD_SCHEMA_FORMATS).default("typescript").describe(
|
|
523
|
+
'Output: "typescript" interfaces, "zod" schemas, "pydantic" models, or "json-schema"'
|
|
524
|
+
),
|
|
525
|
+
name: z2.string().max(100).optional().describe("Root type name (default: from the detected event, else WebhookPayload)"),
|
|
526
|
+
event: z2.string().optional().describe(
|
|
527
|
+
'Only requests whose detected provider event matches, e.g. "invoice.paid" or "push". One event per schema gives the tightest types.'
|
|
528
|
+
),
|
|
529
|
+
method: z2.string().optional().describe("Only requests with this HTTP method"),
|
|
530
|
+
path: z2.string().optional().describe("Only requests with this path"),
|
|
531
|
+
requestIds: z2.array(z2.string()).min(1).max(50).optional().describe("Use exactly these requests instead of the latest ones"),
|
|
532
|
+
limit: z2.number().int().min(1).max(100).default(20).describe("How many of the latest requests to read before filtering")
|
|
533
|
+
};
|
|
534
|
+
function eventName(event) {
|
|
535
|
+
const words = event.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
536
|
+
const name = words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
537
|
+
if (!name) return "WebhookPayload";
|
|
538
|
+
return /^[0-9]/.test(name) ? `Webhook${name}Event` : `${name}Event`;
|
|
539
|
+
}
|
|
540
|
+
function payloadSchemaResult(requests, args) {
|
|
541
|
+
const method = args.method?.toUpperCase();
|
|
542
|
+
const matching = requests.filter(
|
|
543
|
+
(request) => request.kind !== "email" && (!method || request.method.toUpperCase() === method) && (!args.path || request.path === args.path) && (!args.event || detectWebhookInfo(request)?.event === args.event)
|
|
544
|
+
);
|
|
545
|
+
if (matching.length === 0) {
|
|
546
|
+
throw new Error(
|
|
547
|
+
`None of the ${requests.length} requests read matched. Capture a request first, or loosen event, method or path.`
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
const events = [
|
|
551
|
+
...new Set(
|
|
552
|
+
matching.map((request) => detectWebhookInfo(request)?.event).filter((e) => !!e)
|
|
553
|
+
)
|
|
554
|
+
];
|
|
555
|
+
const name = args.name ?? (events.length === 1 ? eventName(events[0]) : void 0);
|
|
556
|
+
const result = generateRequestSchema(matching, { format: args.format, name });
|
|
557
|
+
if (result.samples === 0) {
|
|
558
|
+
throw new Error(
|
|
559
|
+
`None of the ${matching.length} matching requests has a JSON or form body to learn from.`
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
const output = {
|
|
563
|
+
format: result.format,
|
|
564
|
+
name: result.name,
|
|
565
|
+
samples: result.samples,
|
|
566
|
+
skipped: result.skipped,
|
|
567
|
+
...events.length > 0 ? { events } : {},
|
|
568
|
+
...events.length > 1 ? {
|
|
569
|
+
warning: `The samples mix ${events.length} events, so fields of one event show up as optional. Pass event for one schema per event.`
|
|
570
|
+
} : {},
|
|
571
|
+
code: result.code
|
|
572
|
+
};
|
|
573
|
+
const size = JSON.stringify(output, null, 2).length;
|
|
574
|
+
if (size > MAX_OUTPUT) {
|
|
575
|
+
throw new Error(
|
|
576
|
+
`The schema is ${result.code.length} characters, more than this tool can return. Narrow it with event or requestIds, or call generateRequestSchema() from @webhooks-cc/sdk in code.`
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
return jsonContent(output);
|
|
580
|
+
}
|
|
516
581
|
function textContent(text) {
|
|
517
582
|
return { content: [{ type: "text", text }] };
|
|
518
583
|
}
|
|
@@ -1209,6 +1274,15 @@ function registerTools(server, client) {
|
|
|
1209
1274
|
return jsonContent(diff);
|
|
1210
1275
|
})
|
|
1211
1276
|
);
|
|
1277
|
+
server.tool(
|
|
1278
|
+
"generate_payload_schema",
|
|
1279
|
+
"Generate TypeScript interfaces, Zod schemas, Pydantic models, or JSON Schema from captured request bodies. Several requests of one event teach which fields are optional or nullable, so filter with event (or pass requestIds) and write the result into the codebase instead of hand-typing the payload.",
|
|
1280
|
+
payloadSchemaShape,
|
|
1281
|
+
withErrorHandling(async ({ endpointSlug, requestIds, limit, ...args }) => {
|
|
1282
|
+
const requests = requestIds ? await Promise.all(requestIds.map((id) => client.requests.get(id))) : await client.requests.list(endpointSlug, { limit, kind: "http" });
|
|
1283
|
+
return payloadSchemaResult(requests, args);
|
|
1284
|
+
})
|
|
1285
|
+
);
|
|
1212
1286
|
server.tool(
|
|
1213
1287
|
"extract_from_request",
|
|
1214
1288
|
"Extract specific JSON fields from a captured request body.",
|
|
@@ -1899,6 +1973,18 @@ function registerSandboxTools(server, options = {}) {
|
|
|
1899
1973
|
})
|
|
1900
1974
|
)
|
|
1901
1975
|
);
|
|
1976
|
+
registered.push(
|
|
1977
|
+
server.tool(
|
|
1978
|
+
"generate_payload_schema",
|
|
1979
|
+
"Generate TypeScript interfaces, Zod schemas, Pydantic models, or JSON Schema from the bodies of requests captured by a sandbox endpoint. Several requests of one event teach which fields are optional or nullable.",
|
|
1980
|
+
payloadSchemaShape,
|
|
1981
|
+
withErrorHandling(async ({ endpointSlug, requestIds, limit, ...args }) => {
|
|
1982
|
+
const sandbox = await session.requireSandbox();
|
|
1983
|
+
const requests = requestIds ? await Promise.all(requestIds.map((id) => sandbox.requests.get(id))) : await sandbox.requests.list(endpointSlug, { limit });
|
|
1984
|
+
return payloadSchemaResult(requests, args);
|
|
1985
|
+
})
|
|
1986
|
+
)
|
|
1987
|
+
);
|
|
1902
1988
|
registered.push(
|
|
1903
1989
|
server.tool(
|
|
1904
1990
|
"connect_account",
|
|
@@ -2057,7 +2143,7 @@ function registerAgentRegistrationTools(server) {
|
|
|
2057
2143
|
}
|
|
2058
2144
|
|
|
2059
2145
|
// src/index.ts
|
|
2060
|
-
var VERSION = true ? "1.
|
|
2146
|
+
var VERSION = true ? "1.13.0" : "0.0.0-dev";
|
|
2061
2147
|
function createServer(options = {}) {
|
|
2062
2148
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
2063
2149
|
const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webhooks-cc/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
|
+
"mcpName": "io.github.kroqdotdev/webhooks-cc",
|
|
4
5
|
"description": "MCP server for webhooks.cc — AI agent integration for webhook testing",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"module": "dist/index.mjs",
|
|
@@ -31,20 +32,20 @@
|
|
|
31
32
|
"license": "MIT",
|
|
32
33
|
"repository": {
|
|
33
34
|
"type": "git",
|
|
34
|
-
"url": "https://github.com/
|
|
35
|
+
"url": "https://github.com/kroqdotdev/webhooks-cc",
|
|
35
36
|
"directory": "packages/mcp"
|
|
36
37
|
},
|
|
37
38
|
"homepage": "https://webhooks.cc",
|
|
38
39
|
"dependencies": {
|
|
39
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
40
|
-
"zod": "^4.6.
|
|
41
|
-
"@webhooks-cc/sdk": "1.
|
|
40
|
+
"@modelcontextprotocol/sdk": "^1.32.0",
|
|
41
|
+
"zod": "^4.6.7",
|
|
42
|
+
"@webhooks-cc/sdk": "1.17.0"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
44
|
-
"@types/node": "^26.6.
|
|
45
|
-
"tsup": "^
|
|
45
|
+
"@types/node": "^26.6.5",
|
|
46
|
+
"tsup": "^9.0.0",
|
|
46
47
|
"typescript": "^6.0.3",
|
|
47
|
-
"vitest": "^5.0.
|
|
48
|
+
"vitest": "^5.0.5"
|
|
48
49
|
},
|
|
49
50
|
"scripts": {
|
|
50
51
|
"build": "tsup",
|