@raeseoklee/mcp-workbench-plugin-html-report 0.4.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.
@@ -0,0 +1,3 @@
1
+ import type { RunReport } from "@mcp-workbench/plugin-sdk";
2
+ export declare function generateHtml(report: RunReport, specFile?: string): string;
3
+ //# sourceMappingURL=generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAqB,MAAM,2BAA2B,CAAC;AAE9E,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAyFzE"}
@@ -0,0 +1,134 @@
1
+ export function generateHtml(report, specFile) {
2
+ const date = new Date().toLocaleString();
3
+ const title = specFile ? `MCP Workbench — ${specFile}` : "MCP Workbench Report";
4
+ const allPassed = report.failed === 0 && report.errors === 0;
5
+ const statusBadge = allPassed
6
+ ? `<span class="badge pass">PASS</span>`
7
+ : `<span class="badge fail">FAIL</span>`;
8
+ const testRows = report.tests.map(renderTest).join("\n");
9
+ return `<!DOCTYPE html>
10
+ <html lang="en">
11
+ <head>
12
+ <meta charset="UTF-8" />
13
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
14
+ <title>${escHtml(title)}</title>
15
+ <style>
16
+ *, *::before, *::after { box-sizing: border-box; }
17
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 0; background: #f5f7fa; color: #1a1d23; }
18
+ header { background: #1a1d23; color: #fff; padding: 20px 32px; }
19
+ header h1 { margin: 0; font-size: 18px; font-weight: 600; }
20
+ header p { margin: 4px 0 0; font-size: 13px; opacity: 0.6; }
21
+ .summary { display: flex; gap: 16px; padding: 20px 32px; background: #fff; border-bottom: 1px solid #e5e8ed; align-items: center; flex-wrap: wrap; }
22
+ .badge { display: inline-block; padding: 4px 12px; border-radius: 4px; font-size: 13px; font-weight: 700; letter-spacing: 0.05em; }
23
+ .badge.pass { background: #d1fae5; color: #065f46; }
24
+ .badge.fail { background: #fee2e2; color: #991b1b; }
25
+ .stat { font-size: 13px; color: #6b7280; }
26
+ .stat strong { color: #1a1d23; }
27
+ .stat.green strong { color: #059669; }
28
+ .stat.red strong { color: #dc2626; }
29
+ .stat.yellow strong { color: #d97706; }
30
+ .duration { margin-left: auto; font-size: 12px; color: #9ca3af; }
31
+ main { padding: 24px 32px; }
32
+ .test { background: #fff; border: 1px solid #e5e8ed; border-radius: 8px; margin-bottom: 10px; overflow: hidden; }
33
+ .test-header { display: flex; align-items: center; gap: 10px; padding: 12px 16px; cursor: pointer; }
34
+ .test-header:hover { background: #f9fafb; }
35
+ .icon { font-size: 14px; flex-shrink: 0; }
36
+ .icon.pass { color: #059669; }
37
+ .icon.fail { color: #dc2626; }
38
+ .icon.error { color: #dc2626; }
39
+ .icon.skip { color: #d97706; }
40
+ .test-id { font-size: 13px; font-weight: 600; color: #1a1d23; }
41
+ .test-desc { font-size: 12px; color: #6b7280; margin-left: 4px; }
42
+ .test-ms { margin-left: auto; font-size: 11px; color: #9ca3af; }
43
+ .assertions { padding: 0 16px 12px 40px; display: none; }
44
+ .assertions.open { display: block; }
45
+ .assertion { font-size: 12px; padding: 4px 0; border-top: 1px solid #f3f4f6; display: flex; align-items: flex-start; gap: 6px; }
46
+ .assertion:first-child { border-top: none; }
47
+ .a-icon.pass { color: #059669; }
48
+ .a-icon.fail { color: #dc2626; }
49
+ .a-kind { color: #6b7280; font-family: monospace; }
50
+ .a-msg { color: #dc2626; }
51
+ .a-diff { background: #f9fafb; border: 1px solid #e5e8ed; border-radius: 4px; padding: 8px; font-family: monospace; font-size: 11px; white-space: pre; overflow-x: auto; margin-top: 4px; color: #374151; }
52
+ .error-msg { font-size: 12px; color: #dc2626; padding: 4px 0 4px 40px; }
53
+ footer { text-align: center; padding: 24px; font-size: 12px; color: #9ca3af; }
54
+ </style>
55
+ </head>
56
+ <body>
57
+ <header>
58
+ <h1>MCP Workbench Test Report</h1>
59
+ <p>${escHtml(specFile ?? "")} &nbsp;·&nbsp; Generated ${escHtml(date)}</p>
60
+ </header>
61
+
62
+ <div class="summary">
63
+ ${statusBadge}
64
+ <span class="stat green"><strong>${report.passed}</strong> passed</span>
65
+ ${report.failed > 0 ? `<span class="stat red"><strong>${report.failed}</strong> failed</span>` : ""}
66
+ ${report.errors > 0 ? `<span class="stat red"><strong>${report.errors}</strong> errors</span>` : ""}
67
+ ${report.skipped > 0 ? `<span class="stat yellow"><strong>${report.skipped}</strong> skipped</span>` : ""}
68
+ <span class="stat"><strong>${report.total}</strong> total</span>
69
+ <span class="duration">${report.durationMs}ms</span>
70
+ </div>
71
+
72
+ <main>
73
+ ${testRows}
74
+ </main>
75
+
76
+ <footer>Generated by <strong>MCP Workbench</strong> · plugin-html-report</footer>
77
+
78
+ <script>
79
+ document.querySelectorAll('.test-header').forEach(function(el) {
80
+ el.addEventListener('click', function() {
81
+ var assertions = el.nextElementSibling;
82
+ if (assertions) assertions.classList.toggle('open');
83
+ });
84
+ });
85
+ </script>
86
+ </body>
87
+ </html>`;
88
+ }
89
+ function renderTest(t) {
90
+ const iconClass = t.status === "passed" ? "pass" : t.status === "skipped" ? "skip" : t.status === "error" ? "error" : "fail";
91
+ const iconChar = t.status === "passed" ? "✓" : t.status === "skipped" ? "○" : "✗";
92
+ const desc = t.description ? `<span class="test-desc">— ${escHtml(t.description)}</span>` : "";
93
+ const assertionHtml = t.assertionResults.map((a) => {
94
+ const ac = a.passed ? "pass" : "fail";
95
+ const label = a.assertion.label ?? a.assertion.kind;
96
+ const msgHtml = !a.passed && a.message
97
+ ? `<span class="a-msg">${escHtml(a.message)}</span>`
98
+ : "";
99
+ const diffHtml = a.diff
100
+ ? `<pre class="a-diff">${escHtml(a.diff)}</pre>`
101
+ : "";
102
+ return `<div class="assertion">
103
+ <span class="a-icon ${ac}">${a.passed ? "✓" : "✗"}</span>
104
+ <span>
105
+ <span class="a-kind">${escHtml(label)}</span>
106
+ ${msgHtml}
107
+ ${diffHtml}
108
+ </span>
109
+ </div>`;
110
+ }).join("\n");
111
+ const errorHtml = t.error
112
+ ? `<div class="error-msg">${escHtml(t.error)}</div>`
113
+ : "";
114
+ return `<div class="test">
115
+ <div class="test-header">
116
+ <span class="icon ${iconClass}">${iconChar}</span>
117
+ <span class="test-id">${escHtml(t.testId)}</span>
118
+ ${desc}
119
+ <span class="test-ms">${t.durationMs}ms</span>
120
+ </div>
121
+ ${errorHtml}
122
+ <div class="assertions">
123
+ ${assertionHtml}
124
+ </div>
125
+ </div>`;
126
+ }
127
+ function escHtml(str) {
128
+ return str
129
+ .replace(/&/g, "&amp;")
130
+ .replace(/</g, "&lt;")
131
+ .replace(/>/g, "&gt;")
132
+ .replace(/"/g, "&quot;");
133
+ }
134
+ //# sourceMappingURL=generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.js","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { WorkbenchPlugin } from "@mcp-workbench/plugin-sdk";
2
+ declare const plugin: WorkbenchPlugin;
3
+ export default plugin;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,2BAA2B,CAAC;AAIhF,QAAA,MAAM,MAAM,EAAE,eAqBb,CAAC;AAEF,eAAe,MAAM,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import { PLUGIN_API_VERSION } from "./version.js";
4
+ import { generateHtml } from "./generator.js";
5
+ const plugin = {
6
+ manifest: {
7
+ name: "@mcp-workbench/plugin-html-report",
8
+ version: "0.4.0",
9
+ apiVersion: PLUGIN_API_VERSION,
10
+ description: "Generates a self-contained HTML test report",
11
+ contributes: { reporters: ["html"] },
12
+ },
13
+ register(ctx) {
14
+ ctx.registerReporter({
15
+ name: "html",
16
+ description: "Self-contained HTML report",
17
+ async generate({ report, specFile, outputPath }) {
18
+ const html = generateHtml(report, specFile);
19
+ const out = resolve(outputPath ?? "mcp-workbench-report.html");
20
+ await writeFile(out, html, "utf8");
21
+ ctx.logger.info(`HTML report written to ${out}`);
22
+ },
23
+ });
24
+ },
25
+ };
26
+ export default plugin;
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,MAAM,MAAM,GAAoB;IAC9B,QAAQ,EAAE;QACR,IAAI,EAAE,mCAAmC;QACzC,OAAO,EAAE,OAAO;QAChB,UAAU,EAAE,kBAAkB;QAC9B,WAAW,EAAE,6CAA6C;QAC1D,WAAW,EAAE,EAAE,SAAS,EAAE,CAAC,MAAM,CAAC,EAAE;KACrC;IAED,QAAQ,CAAC,GAAkB;QACzB,GAAG,CAAC,gBAAgB,CAAC;YACnB,IAAI,EAAE,MAAM;YACZ,WAAW,EAAE,4BAA4B;YACzC,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE;gBAC7C,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;gBAC5C,MAAM,GAAG,GAAG,OAAO,CAAC,UAAU,IAAI,2BAA2B,CAAC,CAAC;gBAC/D,MAAM,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;gBACnC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,0BAA0B,GAAG,EAAE,CAAC,CAAC;YACnD,CAAC;SACF,CAAC,CAAC;IACL,CAAC;CACF,CAAC;AAEF,eAAe,MAAM,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** Must match PLUGIN_API_VERSION in @mcp-workbench/plugin-runtime */
2
+ export declare const PLUGIN_API_VERSION = "0.4";
3
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,eAAO,MAAM,kBAAkB,QAAQ,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** Must match PLUGIN_API_VERSION in @mcp-workbench/plugin-runtime */
2
+ export const PLUGIN_API_VERSION = "0.4";
3
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,CAAC"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@raeseoklee/mcp-workbench-plugin-html-report",
3
+ "version": "0.4.0",
4
+ "type": "module",
5
+ "description": "MCP Workbench plugin — generates a self-contained HTML test report",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "dev": "tsc --watch",
17
+ "typecheck": "tsc --noEmit",
18
+ "test": "vitest run --passWithNoTests",
19
+ "clean": "rm -rf dist"
20
+ },
21
+ "dependencies": {
22
+ "@raeseoklee/mcp-workbench-plugin-sdk": "workspace:*"
23
+ },
24
+ "devDependencies": {
25
+ "typescript": "^5.6.0",
26
+ "vitest": "^2.0.0",
27
+ "@types/node": "^22.0.0"
28
+ },
29
+ "license": "Apache-2.0",
30
+ "keywords": ["mcp", "model-context-protocol", "plugin", "reporter", "html"],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/raeseoklee/mcp-workbench.git"
34
+ },
35
+ "engines": {
36
+ "node": ">=20.0.0"
37
+ },
38
+ "files": ["dist"],
39
+ "publishConfig": {
40
+ "access": "public"
41
+ }
42
+ }