webhook-crash-lab-mcp 0.1.0 → 0.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/.agents/skills/webhook-crash-lab/SKILL.md +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +5 -0
- package/README.md +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-MEQIW5WQ.js → chunk-UR6UXUAN.js} +8 -16
- package/dist/chunk-UR6UXUAN.js.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +1 -1
- package/package.json +4 -3
- package/skills/webhook-crash-lab/SKILL.md +1 -1
- package/dist/chunk-MEQIW5WQ.js.map +0 -1
|
@@ -12,7 +12,7 @@ business state (for example "credits granted exactly once").
|
|
|
12
12
|
## Loop
|
|
13
13
|
|
|
14
14
|
1. **Find the handler** (`/webhook` route, `app/api/webhooks/*/route.ts`, `constructEvent` call).
|
|
15
|
-
2. **Config.** If `crash-lab.config.mjs`
|
|
15
|
+
2. **Config.** If no `crash-lab.config.{mjs,js,ts}` exists, run `npx webhook-crash-lab init` and fill it in:
|
|
16
16
|
- `target`: the local dev URL of the webhook route, or an in-process `(req: Request) => Response` handler.
|
|
17
17
|
- `secret`: a **test** signing secret the handler verifies with (set it in the handler's env too).
|
|
18
18
|
- `event`: a realistic event (for example `checkout.session.completed`).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webhook-crash-lab",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Test webhook handlers against duplicate, concurrent, out-of-order and forged deliveries, then fix and re-run until they pass.",
|
|
5
5
|
"author": { "name": "Krrish Kumar", "url": "https://github.com/Krrishkumar125" },
|
|
6
6
|
"homepage": "https://github.com/Krrishkumar125/webhook-crash-lab",
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 (2026-10-22)
|
|
4
|
+
|
|
5
|
+
- `run_crash_lab` accepts TypeScript configs (`.ts`/`.mts`/`.cts`) by delegating config loading to
|
|
6
|
+
webhook-crash-lab 0.3.0; jiti is bundled so this works on Node 20 too.
|
|
7
|
+
|
|
3
8
|
## 0.1.0 (2026-10-22)
|
|
4
9
|
|
|
5
10
|
- MCP server with `run_crash_lab`, `run_demo`, `explain_report`, `list_scenarios`.
|
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ Paste [`docs/AGENTS-snippet.md`](docs/AGENTS-snippet.md) into `AGENTS.md` / `CLA
|
|
|
59
59
|
|
|
60
60
|
## Safety
|
|
61
61
|
|
|
62
|
-
`run_crash_lab` imports your
|
|
62
|
+
`run_crash_lab` imports your crash-lab config (`.mjs`/`.js`/`.cjs`, or `.ts`/`.mts`/`.cts` via the bundled jiti) (it is code, like any config file) and sends requests to
|
|
63
63
|
its `target`. Point configs at a local server and a **test** database and secret only. The skill tells
|
|
64
64
|
agents never to use production URLs or live secrets.
|
|
65
65
|
|
package/dist/bin.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// src/core.ts
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
-
import { dirname,
|
|
4
|
-
import { pathToFileURL } from "url";
|
|
3
|
+
import { dirname, resolve } from "path";
|
|
5
4
|
import {
|
|
6
5
|
demoConfig,
|
|
6
|
+
loadConfig as loadLabConfig,
|
|
7
7
|
runCrashLab,
|
|
8
8
|
SCENARIOS,
|
|
9
9
|
toJson
|
|
@@ -63,17 +63,7 @@ function writeReport(result, reportPath) {
|
|
|
63
63
|
writeFileSync(reportPath, toJson(result));
|
|
64
64
|
}
|
|
65
65
|
async function loadConfig(configPath, cwd = process.cwd()) {
|
|
66
|
-
|
|
67
|
-
if ([".ts", ".mts", ".cts"].includes(extname(path))) {
|
|
68
|
-
throw new Error(".ts configs are not supported yet; use crash-lab.config.mjs");
|
|
69
|
-
}
|
|
70
|
-
if (!existsSync(path)) throw new Error(`Config not found: ${path}`);
|
|
71
|
-
const mod = await import(`${pathToFileURL(path).href}?t=${Date.now()}`);
|
|
72
|
-
const config = mod.default;
|
|
73
|
-
if (!config || !config.target || typeof config.secret !== "string" || !config.event?.id) {
|
|
74
|
-
throw new Error(`Invalid config ${path}: must export default { target, secret, event }`);
|
|
75
|
-
}
|
|
76
|
-
return config;
|
|
66
|
+
return loadLabConfig(configPath, cwd);
|
|
77
67
|
}
|
|
78
68
|
async function runFromConfig(opts) {
|
|
79
69
|
for (const id of opts.scenarios ?? []) {
|
|
@@ -138,7 +128,7 @@ function renderSummary(s) {
|
|
|
138
128
|
// src/server.ts
|
|
139
129
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
140
130
|
import { z } from "zod";
|
|
141
|
-
var VERSION = "0.
|
|
131
|
+
var VERSION = "0.2.0";
|
|
142
132
|
var summaryShape = {
|
|
143
133
|
name: z.string(),
|
|
144
134
|
ok: z.boolean(),
|
|
@@ -177,7 +167,9 @@ function createServer() {
|
|
|
177
167
|
title: "Run webhook-crash-lab",
|
|
178
168
|
description: "Replay hostile webhook deliveries (duplicates, concurrent duplicates, out-of-order events, forged/missing/stale signatures, malformed JSON) against the handler described by a crash-lab config file, and return structured failures with fix hints. Use a TEST database and test secret only. Create a config with `npx webhook-crash-lab init` if none exists.",
|
|
179
169
|
inputSchema: {
|
|
180
|
-
configPath: z.string().describe(
|
|
170
|
+
configPath: z.string().describe(
|
|
171
|
+
"Path to a crash-lab config (.mjs, .js, .cjs, .ts, .mts or .cts), relative to cwd"
|
|
172
|
+
),
|
|
181
173
|
cwd: z.string().optional().describe("Project directory; defaults to the server working directory"),
|
|
182
174
|
scenarios: z.array(z.enum(SCENARIO_IDS)).optional().describe("Run only these scenarios"),
|
|
183
175
|
reportPath: z.string().optional().describe("Also write the full JSON report here")
|
|
@@ -261,4 +253,4 @@ export {
|
|
|
261
253
|
VERSION,
|
|
262
254
|
createServer
|
|
263
255
|
};
|
|
264
|
-
//# sourceMappingURL=chunk-
|
|
256
|
+
//# sourceMappingURL=chunk-UR6UXUAN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core.ts","../src/server.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport {\n demoConfig,\n loadConfig as loadLabConfig,\n runCrashLab,\n SCENARIOS,\n toJson,\n type CrashLabConfig,\n type CrashLabResult,\n type ScenarioId,\n} from 'webhook-crash-lab';\n\nexport type { CrashLabResult, ScenarioId };\n\n/** Fix security first, then duplicates, then ordering: later fixes depend on earlier ones. */\nconst PRIORITY: ScenarioId[] = [\n 'missing-signature',\n 'invalid-signature',\n 'stale-timestamp',\n 'malformed-json',\n 'single-valid',\n 'duplicate-sequential',\n 'duplicate-concurrent',\n 'out-of-order',\n];\nexport const SCENARIO_IDS = SCENARIOS.map((s) => s.id) as ScenarioId[];\n\nexport interface Failure {\n id: ScenarioId;\n title: string;\n why: string;\n fix: string;\n failedChecks: { name: string; detail: string }[];\n responses: number[];\n}\n\nexport interface Summary {\n name: string;\n ok: boolean;\n summary: { pass: number; fail: number; notTested: number };\n /** Failing scenarios, most important first. */\n failures: Failure[];\n notTested: ScenarioId[];\n scenarios: { id: ScenarioId; status: string }[];\n nextSteps: string[];\n reportPath?: string;\n}\n\nexport function summarize(result: CrashLabResult, reportPath?: string): Summary {\n const rank = (id: ScenarioId) => PRIORITY.indexOf(id);\n const failures = result.scenarios\n .filter((s) => s.status === 'fail')\n .sort((a, b) => rank(a.id) - rank(b.id))\n .map<Failure>((s) => ({\n id: s.id,\n title: s.title,\n why: s.why,\n fix: s.fix,\n failedChecks: s.checks\n .filter((c) => c.status === 'fail')\n .map(({ name, detail }) => ({ name, detail })),\n responses: s.responses,\n }));\n const notTested = result.scenarios.filter((s) => s.status === 'not-tested').map((s) => s.id);\n\n const nextSteps: string[] = [];\n if (failures.length) {\n nextSteps.push(\n `Fix in this order: ${failures.map((f) => f.id).join(' → ')}. Each failure has a \"fix\" hint.`,\n 'Change the webhook handler (not the test config), then re-run the same tool with the same arguments. Stop when ok is true.',\n 'Static checks for the same bugs: eslint-plugin-webhook-safety and eslint-plugin-bullmq.',\n );\n } else {\n nextSteps.push(\n 'All scenarios pass. Keep `npx webhook-crash-lab run` in CI so this stays true.',\n );\n }\n if (notTested.length) {\n nextSteps.push(\n `${notTested.length} scenario(s) not tested (${notTested.join(', ')}): duplicate checks need an \\`adapter\\` + \\`expect.once\\`; out-of-order needs \\`sequence\\` in the config.`,\n );\n }\n return {\n name: result.name,\n ok: result.ok,\n summary: result.summary,\n failures,\n notTested,\n scenarios: result.scenarios.map((s) => ({ id: s.id, status: s.status })),\n nextSteps,\n ...(reportPath ? { reportPath } : {}),\n };\n}\n\nfunction writeReport(result: CrashLabResult, reportPath: string): void {\n mkdirSync(dirname(reportPath), { recursive: true });\n writeFileSync(reportPath, toJson(result));\n}\n\nexport interface RunOptions {\n configPath: string;\n /** Directory relative paths resolve against. Defaults to process.cwd(). */\n cwd?: string;\n scenarios?: ScenarioId[];\n /** If set, the JSON report is written here too. */\n reportPath?: string;\n}\n\n/**\n * Load a crash-lab config (.mjs/.js/.cjs, or .ts/.mts/.cts via jiti or Node type stripping).\n * Delegates to webhook-crash-lab so the CLI and the MCP server accept exactly the same files,\n * and re-reads the file each call so an agent re-running after edits gets the new config.\n */\nexport async function loadConfig(configPath: string, cwd = process.cwd()): Promise<CrashLabConfig> {\n return loadLabConfig(configPath, cwd);\n}\n\nexport async function runFromConfig(opts: RunOptions): Promise<Summary> {\n for (const id of opts.scenarios ?? []) {\n if (!SCENARIO_IDS.includes(id)) {\n throw new Error(`Unknown scenario \"${id}\". Known: ${SCENARIO_IDS.join(', ')}`);\n }\n }\n const config = await loadConfig(opts.configPath, opts.cwd);\n const result = await runCrashLab(\n opts.scenarios?.length ? { ...config, scenarios: opts.scenarios } : config,\n );\n const reportPath = opts.reportPath && resolve(opts.cwd ?? process.cwd(), opts.reportPath);\n if (reportPath) writeReport(result, reportPath);\n return summarize(result, reportPath);\n}\n\nexport async function runDemo(mode: 'broken' | 'fixed'): Promise<Summary> {\n return summarize(await runCrashLab(demoConfig(mode)));\n}\n\nfunction isReport(x: unknown): x is CrashLabResult {\n const r = x as CrashLabResult;\n return (\n !!r &&\n typeof r === 'object' &&\n typeof r.ok === 'boolean' &&\n Array.isArray(r.scenarios) &&\n !!r.summary\n );\n}\n\nexport function explainReport(input: {\n reportPath?: string;\n report?: unknown;\n cwd?: string;\n}): Summary {\n let report: unknown = input.report;\n if (input.reportPath) {\n const path = resolve(input.cwd ?? process.cwd(), input.reportPath);\n if (!existsSync(path)) throw new Error(`Report not found: ${path}`);\n report = readFileSync(path, 'utf8');\n }\n if (report === undefined)\n throw new Error('Pass reportPath or report (the JSON from `webhook-crash-lab run --json`).');\n if (typeof report === 'string') {\n try {\n report = JSON.parse(report);\n } catch {\n throw new Error('Report is not valid JSON.');\n }\n }\n if (!isReport(report))\n throw new Error(\n 'This is not a webhook-crash-lab JSON report (expected ok, summary, scenarios).',\n );\n return summarize(report, input.reportPath);\n}\n\nexport function listScenarios() {\n return SCENARIOS.map(({ id, title, what, why, fix }) => ({ id, title, what, why, fix }));\n}\n\n/** Short human/agent-readable rendering of a summary. */\nexport function renderSummary(s: Summary): string {\n const lines = [\n `${s.ok ? 'PASS' : 'FAIL'} ${s.name}: ${s.summary.pass} pass, ${s.summary.fail} fail, ${s.summary.notTested} not tested`,\n ];\n for (const f of s.failures) {\n lines.push('', `✗ ${f.id}: ${f.title}`);\n for (const c of f.failedChecks) lines.push(` ${c.name}: ${c.detail}`);\n lines.push(` why: ${f.why}`, ` fix: ${f.fix}`);\n }\n lines.push('', 'Next steps:', ...s.nextSteps.map((n) => `- ${n}`));\n if (s.reportPath) lines.push(`Report written to ${s.reportPath}`);\n return lines.join('\\n');\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport {\n explainReport,\n listScenarios,\n renderSummary,\n runDemo,\n runFromConfig,\n SCENARIO_IDS,\n type Summary,\n} from './core.js';\n\nexport const VERSION = '0.2.0';\n\nconst summaryShape = {\n name: z.string(),\n ok: z.boolean(),\n summary: z.object({ pass: z.number(), fail: z.number(), notTested: z.number() }),\n failures: z.array(\n z.object({\n id: z.string(),\n title: z.string(),\n why: z.string(),\n fix: z.string(),\n failedChecks: z.array(z.object({ name: z.string(), detail: z.string() })),\n responses: z.array(z.number()),\n }),\n ),\n notTested: z.array(z.string()),\n scenarios: z.array(z.object({ id: z.string(), status: z.string() })),\n nextSteps: z.array(z.string()),\n reportPath: z.string().optional(),\n};\n\nasync function respond(work: () => Promise<Summary> | Summary) {\n try {\n const s = await work();\n return {\n content: [{ type: 'text' as const, text: renderSummary(s) }],\n structuredContent: s as unknown as Record<string, unknown>,\n };\n } catch (err) {\n return { isError: true, content: [{ type: 'text' as const, text: (err as Error).message }] };\n }\n}\n\nexport function createServer(): McpServer {\n const server = new McpServer({ name: 'webhook-crash-lab', version: VERSION });\n\n server.registerTool(\n 'run_crash_lab',\n {\n title: 'Run webhook-crash-lab',\n description:\n 'Replay hostile webhook deliveries (duplicates, concurrent duplicates, out-of-order events, forged/missing/stale signatures, malformed JSON) against the handler described by a crash-lab config file, and return structured failures with fix hints. Use a TEST database and test secret only. Create a config with `npx webhook-crash-lab init` if none exists.',\n inputSchema: {\n configPath: z\n .string()\n .describe(\n 'Path to a crash-lab config (.mjs, .js, .cjs, .ts, .mts or .cts), relative to cwd',\n ),\n cwd: z\n .string()\n .optional()\n .describe('Project directory; defaults to the server working directory'),\n scenarios: z\n .array(z.enum(SCENARIO_IDS as [string, ...string[]]))\n .optional()\n .describe('Run only these scenarios'),\n reportPath: z.string().optional().describe('Also write the full JSON report here'),\n },\n outputSchema: summaryShape,\n annotations: { readOnlyHint: false, openWorldHint: true },\n },\n (args) => respond(() => runFromConfig(args as Parameters<typeof runFromConfig>[0])),\n );\n\n server.registerTool(\n 'run_demo',\n {\n title: 'Run the built-in demo',\n description:\n 'Run crash-lab against its built-in demo credits app. \"broken\" shows the typical AI-generated Stripe handler failing; \"fixed\" shows the same app done right. Needs no config. Use it to show what crash-lab checks before setting up a real config.',\n inputSchema: { mode: z.enum(['broken', 'fixed']).default('broken') },\n outputSchema: summaryShape,\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n ({ mode }) => respond(() => runDemo(mode)),\n );\n\n server.registerTool(\n 'explain_report',\n {\n title: 'Explain a crash-lab report',\n description:\n 'Read a JSON report produced by `webhook-crash-lab run --json <file>` (e.g. from CI) and return the failures in fix order, with why/fix hints and next steps.',\n inputSchema: {\n reportPath: z.string().optional(),\n report: z\n .union([z.string(), z.record(z.string(), z.unknown())])\n .optional()\n .describe('Report JSON, as text or object'),\n cwd: z.string().optional(),\n },\n outputSchema: summaryShape,\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n (args) => respond(() => explainReport(args)),\n );\n\n server.registerTool(\n 'list_scenarios',\n {\n title: 'List crash-lab scenarios',\n description:\n 'List every scenario crash-lab can run, with what it sends, why it matters in production, and how to fix a failure.',\n inputSchema: {},\n outputSchema: {\n scenarios: z.array(\n z.object({\n id: z.string(),\n title: z.string(),\n what: z.string(),\n why: z.string(),\n fix: z.string(),\n }),\n ),\n },\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n () => {\n const scenarios = listScenarios();\n return {\n content: [\n {\n type: 'text',\n text: scenarios.map((s) => `${s.id}: ${s.what}\\n fix: ${s.fix}`).join('\\n'),\n },\n ],\n structuredContent: { scenarios },\n };\n },\n );\n\n return server;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,11 @@ interface RunOptions {
|
|
|
41
41
|
/** If set, the JSON report is written here too. */
|
|
42
42
|
reportPath?: string;
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Load a crash-lab config (.mjs/.js/.cjs, or .ts/.mts/.cts via jiti or Node type stripping).
|
|
46
|
+
* Delegates to webhook-crash-lab so the CLI and the MCP server accept exactly the same files,
|
|
47
|
+
* and re-reads the file each call so an agent re-running after edits gets the new config.
|
|
48
|
+
*/
|
|
44
49
|
declare function loadConfig(configPath: string, cwd?: string): Promise<CrashLabConfig>;
|
|
45
50
|
declare function runFromConfig(opts: RunOptions): Promise<Summary>;
|
|
46
51
|
declare function runDemo(mode: 'broken' | 'fixed'): Promise<Summary>;
|
|
@@ -59,7 +64,7 @@ declare function listScenarios(): {
|
|
|
59
64
|
/** Short human/agent-readable rendering of a summary. */
|
|
60
65
|
declare function renderSummary(s: Summary): string;
|
|
61
66
|
|
|
62
|
-
declare const VERSION = "0.
|
|
67
|
+
declare const VERSION = "0.2.0";
|
|
63
68
|
declare function createServer(): McpServer;
|
|
64
69
|
|
|
65
70
|
export { type Failure, type RunOptions, SCENARIO_IDS, type Summary, VERSION, createServer, explainReport, listScenarios, loadConfig, renderSummary, runDemo, runFromConfig, summarize };
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webhook-crash-lab-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server (plus Claude Code plugin and Codex skill) that lets coding agents run webhook-crash-lab, read structured failures with fix hints, and re-run until the webhook handler survives retries, duplicates and forged signatures.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Krrish Kumar (https://github.com/Krrishkumar125)",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
],
|
|
24
24
|
"type": "module",
|
|
25
25
|
"bin": {
|
|
26
|
-
"webhook-crash-lab-mcp": "
|
|
26
|
+
"webhook-crash-lab-mcp": "dist/bin.js"
|
|
27
27
|
},
|
|
28
28
|
"main": "./dist/index.js",
|
|
29
29
|
"types": "./dist/index.d.ts",
|
|
@@ -60,7 +60,8 @@
|
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
63
|
-
"
|
|
63
|
+
"jiti": "^2.7.0",
|
|
64
|
+
"webhook-crash-lab": "^0.3.0",
|
|
64
65
|
"zod": "^4.6.7"
|
|
65
66
|
},
|
|
66
67
|
"devDependencies": {
|
|
@@ -12,7 +12,7 @@ business state (for example "credits granted exactly once").
|
|
|
12
12
|
## Loop
|
|
13
13
|
|
|
14
14
|
1. **Find the handler** (`/webhook` route, `app/api/webhooks/*/route.ts`, `constructEvent` call).
|
|
15
|
-
2. **Config.** If `crash-lab.config.mjs`
|
|
15
|
+
2. **Config.** If no `crash-lab.config.{mjs,js,ts}` exists, run `npx webhook-crash-lab init` and fill it in:
|
|
16
16
|
- `target`: the local dev URL of the webhook route, or an in-process `(req: Request) => Response` handler.
|
|
17
17
|
- `secret`: a **test** signing secret the handler verifies with (set it in the handler's env too).
|
|
18
18
|
- `event`: a realistic event (for example `checkout.session.completed`).
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core.ts","../src/server.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, extname, isAbsolute, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport {\n demoConfig,\n runCrashLab,\n SCENARIOS,\n toJson,\n type CrashLabConfig,\n type CrashLabResult,\n type ScenarioId,\n} from 'webhook-crash-lab';\n\nexport type { CrashLabResult, ScenarioId };\n\n/** Fix security first, then duplicates, then ordering: later fixes depend on earlier ones. */\nconst PRIORITY: ScenarioId[] = [\n 'missing-signature',\n 'invalid-signature',\n 'stale-timestamp',\n 'malformed-json',\n 'single-valid',\n 'duplicate-sequential',\n 'duplicate-concurrent',\n 'out-of-order',\n];\nexport const SCENARIO_IDS = SCENARIOS.map((s) => s.id) as ScenarioId[];\n\nexport interface Failure {\n id: ScenarioId;\n title: string;\n why: string;\n fix: string;\n failedChecks: { name: string; detail: string }[];\n responses: number[];\n}\n\nexport interface Summary {\n name: string;\n ok: boolean;\n summary: { pass: number; fail: number; notTested: number };\n /** Failing scenarios, most important first. */\n failures: Failure[];\n notTested: ScenarioId[];\n scenarios: { id: ScenarioId; status: string }[];\n nextSteps: string[];\n reportPath?: string;\n}\n\nexport function summarize(result: CrashLabResult, reportPath?: string): Summary {\n const rank = (id: ScenarioId) => PRIORITY.indexOf(id);\n const failures = result.scenarios\n .filter((s) => s.status === 'fail')\n .sort((a, b) => rank(a.id) - rank(b.id))\n .map<Failure>((s) => ({\n id: s.id,\n title: s.title,\n why: s.why,\n fix: s.fix,\n failedChecks: s.checks\n .filter((c) => c.status === 'fail')\n .map(({ name, detail }) => ({ name, detail })),\n responses: s.responses,\n }));\n const notTested = result.scenarios.filter((s) => s.status === 'not-tested').map((s) => s.id);\n\n const nextSteps: string[] = [];\n if (failures.length) {\n nextSteps.push(\n `Fix in this order: ${failures.map((f) => f.id).join(' → ')}. Each failure has a \"fix\" hint.`,\n 'Change the webhook handler (not the test config), then re-run the same tool with the same arguments. Stop when ok is true.',\n 'Static checks for the same bugs: eslint-plugin-webhook-safety and eslint-plugin-bullmq.',\n );\n } else {\n nextSteps.push(\n 'All scenarios pass. Keep `npx webhook-crash-lab run` in CI so this stays true.',\n );\n }\n if (notTested.length) {\n nextSteps.push(\n `${notTested.length} scenario(s) not tested (${notTested.join(', ')}): duplicate checks need an \\`adapter\\` + \\`expect.once\\`; out-of-order needs \\`sequence\\` in the config.`,\n );\n }\n return {\n name: result.name,\n ok: result.ok,\n summary: result.summary,\n failures,\n notTested,\n scenarios: result.scenarios.map((s) => ({ id: s.id, status: s.status })),\n nextSteps,\n ...(reportPath ? { reportPath } : {}),\n };\n}\n\nfunction writeReport(result: CrashLabResult, reportPath: string): void {\n mkdirSync(dirname(reportPath), { recursive: true });\n writeFileSync(reportPath, toJson(result));\n}\n\nexport interface RunOptions {\n configPath: string;\n /** Directory relative paths resolve against. Defaults to process.cwd(). */\n cwd?: string;\n scenarios?: ScenarioId[];\n /** If set, the JSON report is written here too. */\n reportPath?: string;\n}\n\nexport async function loadConfig(configPath: string, cwd = process.cwd()): Promise<CrashLabConfig> {\n const path = isAbsolute(configPath) ? configPath : resolve(cwd, configPath);\n if (['.ts', '.mts', '.cts'].includes(extname(path))) {\n throw new Error('.ts configs are not supported yet; use crash-lab.config.mjs');\n }\n if (!existsSync(path)) throw new Error(`Config not found: ${path}`);\n // Cache-bust so an agent re-running after edits gets the new config.\n const mod = (await import(`${pathToFileURL(path).href}?t=${Date.now()}`)) as {\n default?: CrashLabConfig;\n };\n const config = mod.default;\n if (!config || !config.target || typeof config.secret !== 'string' || !config.event?.id) {\n throw new Error(`Invalid config ${path}: must export default { target, secret, event }`);\n }\n return config;\n}\n\nexport async function runFromConfig(opts: RunOptions): Promise<Summary> {\n for (const id of opts.scenarios ?? []) {\n if (!SCENARIO_IDS.includes(id)) {\n throw new Error(`Unknown scenario \"${id}\". Known: ${SCENARIO_IDS.join(', ')}`);\n }\n }\n const config = await loadConfig(opts.configPath, opts.cwd);\n const result = await runCrashLab(\n opts.scenarios?.length ? { ...config, scenarios: opts.scenarios } : config,\n );\n const reportPath = opts.reportPath && resolve(opts.cwd ?? process.cwd(), opts.reportPath);\n if (reportPath) writeReport(result, reportPath);\n return summarize(result, reportPath);\n}\n\nexport async function runDemo(mode: 'broken' | 'fixed'): Promise<Summary> {\n return summarize(await runCrashLab(demoConfig(mode)));\n}\n\nfunction isReport(x: unknown): x is CrashLabResult {\n const r = x as CrashLabResult;\n return (\n !!r &&\n typeof r === 'object' &&\n typeof r.ok === 'boolean' &&\n Array.isArray(r.scenarios) &&\n !!r.summary\n );\n}\n\nexport function explainReport(input: {\n reportPath?: string;\n report?: unknown;\n cwd?: string;\n}): Summary {\n let report: unknown = input.report;\n if (input.reportPath) {\n const path = resolve(input.cwd ?? process.cwd(), input.reportPath);\n if (!existsSync(path)) throw new Error(`Report not found: ${path}`);\n report = readFileSync(path, 'utf8');\n }\n if (report === undefined)\n throw new Error('Pass reportPath or report (the JSON from `webhook-crash-lab run --json`).');\n if (typeof report === 'string') {\n try {\n report = JSON.parse(report);\n } catch {\n throw new Error('Report is not valid JSON.');\n }\n }\n if (!isReport(report))\n throw new Error(\n 'This is not a webhook-crash-lab JSON report (expected ok, summary, scenarios).',\n );\n return summarize(report, input.reportPath);\n}\n\nexport function listScenarios() {\n return SCENARIOS.map(({ id, title, what, why, fix }) => ({ id, title, what, why, fix }));\n}\n\n/** Short human/agent-readable rendering of a summary. */\nexport function renderSummary(s: Summary): string {\n const lines = [\n `${s.ok ? 'PASS' : 'FAIL'} ${s.name}: ${s.summary.pass} pass, ${s.summary.fail} fail, ${s.summary.notTested} not tested`,\n ];\n for (const f of s.failures) {\n lines.push('', `✗ ${f.id}: ${f.title}`);\n for (const c of f.failedChecks) lines.push(` ${c.name}: ${c.detail}`);\n lines.push(` why: ${f.why}`, ` fix: ${f.fix}`);\n }\n lines.push('', 'Next steps:', ...s.nextSteps.map((n) => `- ${n}`));\n if (s.reportPath) lines.push(`Report written to ${s.reportPath}`);\n return lines.join('\\n');\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport {\n explainReport,\n listScenarios,\n renderSummary,\n runDemo,\n runFromConfig,\n SCENARIO_IDS,\n type Summary,\n} from './core.js';\n\nexport const VERSION = '0.1.0';\n\nconst summaryShape = {\n name: z.string(),\n ok: z.boolean(),\n summary: z.object({ pass: z.number(), fail: z.number(), notTested: z.number() }),\n failures: z.array(\n z.object({\n id: z.string(),\n title: z.string(),\n why: z.string(),\n fix: z.string(),\n failedChecks: z.array(z.object({ name: z.string(), detail: z.string() })),\n responses: z.array(z.number()),\n }),\n ),\n notTested: z.array(z.string()),\n scenarios: z.array(z.object({ id: z.string(), status: z.string() })),\n nextSteps: z.array(z.string()),\n reportPath: z.string().optional(),\n};\n\nasync function respond(work: () => Promise<Summary> | Summary) {\n try {\n const s = await work();\n return {\n content: [{ type: 'text' as const, text: renderSummary(s) }],\n structuredContent: s as unknown as Record<string, unknown>,\n };\n } catch (err) {\n return { isError: true, content: [{ type: 'text' as const, text: (err as Error).message }] };\n }\n}\n\nexport function createServer(): McpServer {\n const server = new McpServer({ name: 'webhook-crash-lab', version: VERSION });\n\n server.registerTool(\n 'run_crash_lab',\n {\n title: 'Run webhook-crash-lab',\n description:\n 'Replay hostile webhook deliveries (duplicates, concurrent duplicates, out-of-order events, forged/missing/stale signatures, malformed JSON) against the handler described by a crash-lab config file, and return structured failures with fix hints. Use a TEST database and test secret only. Create a config with `npx webhook-crash-lab init` if none exists.',\n inputSchema: {\n configPath: z.string().describe('Path to crash-lab.config.mjs (relative to cwd)'),\n cwd: z\n .string()\n .optional()\n .describe('Project directory; defaults to the server working directory'),\n scenarios: z\n .array(z.enum(SCENARIO_IDS as [string, ...string[]]))\n .optional()\n .describe('Run only these scenarios'),\n reportPath: z.string().optional().describe('Also write the full JSON report here'),\n },\n outputSchema: summaryShape,\n annotations: { readOnlyHint: false, openWorldHint: true },\n },\n (args) => respond(() => runFromConfig(args as Parameters<typeof runFromConfig>[0])),\n );\n\n server.registerTool(\n 'run_demo',\n {\n title: 'Run the built-in demo',\n description:\n 'Run crash-lab against its built-in demo credits app. \"broken\" shows the typical AI-generated Stripe handler failing; \"fixed\" shows the same app done right. Needs no config. Use it to show what crash-lab checks before setting up a real config.',\n inputSchema: { mode: z.enum(['broken', 'fixed']).default('broken') },\n outputSchema: summaryShape,\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n ({ mode }) => respond(() => runDemo(mode)),\n );\n\n server.registerTool(\n 'explain_report',\n {\n title: 'Explain a crash-lab report',\n description:\n 'Read a JSON report produced by `webhook-crash-lab run --json <file>` (e.g. from CI) and return the failures in fix order, with why/fix hints and next steps.',\n inputSchema: {\n reportPath: z.string().optional(),\n report: z\n .union([z.string(), z.record(z.string(), z.unknown())])\n .optional()\n .describe('Report JSON, as text or object'),\n cwd: z.string().optional(),\n },\n outputSchema: summaryShape,\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n (args) => respond(() => explainReport(args)),\n );\n\n server.registerTool(\n 'list_scenarios',\n {\n title: 'List crash-lab scenarios',\n description:\n 'List every scenario crash-lab can run, with what it sends, why it matters in production, and how to fix a failure.',\n inputSchema: {},\n outputSchema: {\n scenarios: z.array(\n z.object({\n id: z.string(),\n title: z.string(),\n what: z.string(),\n why: z.string(),\n fix: z.string(),\n }),\n ),\n },\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n () => {\n const scenarios = listScenarios();\n return {\n content: [\n {\n type: 'text',\n text: scenarios.map((s) => `${s.id}: ${s.what}\\n fix: ${s.fix}`).join('\\n'),\n },\n ],\n structuredContent: { scenarios },\n };\n },\n );\n\n return server;\n}\n"],"mappings":"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","names":[]}
|