@roeeash/vibecheck-mcp 0.1.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 +77 -0
- package/dist/index.js +143 -0
- package/package.json +45 -0
- package/vendor/server.bundle.js +8850 -0
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# vibecheck-mcp
|
|
2
|
+
|
|
3
|
+
Audit your local dev server with Claude. Drop one `.mcp.json` into any project and call `audit_dev_server()` to get a full performance report — Vibe-Score, ranked findings, and actionable recommendations — without leaving your editor.
|
|
4
|
+
|
|
5
|
+
Powered by [VibeCheck Ultra](https://github.com/roeeash/VibeCheck).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- Node.js 20+
|
|
12
|
+
- Playwright Chromium (one-time install):
|
|
13
|
+
```bash
|
|
14
|
+
npx playwright install chromium
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
Add `.mcp.json` to your project root:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"mcpServers": {
|
|
26
|
+
"vibecheck": {
|
|
27
|
+
"command": "npx",
|
|
28
|
+
"args": ["vibecheck-mcp@latest"],
|
|
29
|
+
"env": {
|
|
30
|
+
"DEV_PORT": "3000"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Change `DEV_PORT` to match your dev server's port (default: `5173`).
|
|
38
|
+
|
|
39
|
+
Open the project in **Claude Code** — the MCP server loads automatically. Start your dev server, then call:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
audit_dev_server()
|
|
43
|
+
audit_dev_server(/dashboard)
|
|
44
|
+
get_last_audit()
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Tools
|
|
50
|
+
|
|
51
|
+
| Tool | Description |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `audit_dev_server(path?)` | Audit your dev server at the given path (default `/`). Starts the audit API automatically. |
|
|
54
|
+
| `get_last_audit()` | Return results from the most recently completed audit without re-running. |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Environment Variables
|
|
59
|
+
|
|
60
|
+
| Variable | Default | Description |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `DEV_PORT` | `5173` | Port your dev server runs on |
|
|
63
|
+
| `VIBECHECK_API_URL` | — | Point at a hosted VibeCheck API instead of starting one locally |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## What Gets Audited
|
|
68
|
+
|
|
69
|
+
VibeCheck runs a headless Chromium browser against your dev server and reports on:
|
|
70
|
+
|
|
71
|
+
- **Web Vitals** — LCP, CLS, INP, FCP with reproduction traces
|
|
72
|
+
- **Network** — N+1 chains, waterfalls, duplicate fetches, missing cache headers
|
|
73
|
+
- **Assets** — oversized images, render-blocking scripts, unused CSS, bloated JS bundles
|
|
74
|
+
- **Render** — React re-render storms, missing memoization, unvirtualized lists
|
|
75
|
+
- **Memory** — leaked intervals, detached listeners, unbounded state growth
|
|
76
|
+
|
|
77
|
+
Every finding includes what was observed, where, confidence level, and a specific recommendation.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
import { existsSync } from "fs";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
import { resolve, dirname } from "path";
|
|
11
|
+
var API_URL = process.env.VIBECHECK_API_URL ?? "http://localhost:4000";
|
|
12
|
+
var DEV_PORT = process.env.DEV_PORT ?? "5173";
|
|
13
|
+
var POLL_INTERVAL_MS = 3e3;
|
|
14
|
+
var POLL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
15
|
+
var API_BIN = resolve(dirname(fileURLToPath(import.meta.url)), "../vendor/server.bundle.js");
|
|
16
|
+
var lastAuditId = null;
|
|
17
|
+
async function isApiReady() {
|
|
18
|
+
try {
|
|
19
|
+
const res = await fetch(`${API_URL}/health`, { signal: AbortSignal.timeout(1e3) });
|
|
20
|
+
return res.ok;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function assertApiBundleExists() {
|
|
26
|
+
if (!existsSync(API_BIN)) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`vibecheck-mcp: vendor/server.bundle.js is missing at ${API_BIN}.
|
|
29
|
+
Try reinstalling: npx clear-npx-cache && npx vibecheck-mcp@latest
|
|
30
|
+
If using a local install: npm install vibecheck-mcp`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function ensureApiRunning() {
|
|
35
|
+
if (process.env.VIBECHECK_API_URL) return;
|
|
36
|
+
assertApiBundleExists();
|
|
37
|
+
if (await isApiReady()) return;
|
|
38
|
+
process.stderr.write(`VibeCheck API not running \u2014 starting ${API_BIN}
|
|
39
|
+
`);
|
|
40
|
+
const child = spawn("node", [API_BIN], {
|
|
41
|
+
detached: true,
|
|
42
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
43
|
+
env: { ...process.env }
|
|
44
|
+
});
|
|
45
|
+
child.unref();
|
|
46
|
+
for (let i = 0; i < 10; i++) {
|
|
47
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
48
|
+
if (await isApiReady()) return;
|
|
49
|
+
}
|
|
50
|
+
throw new Error(
|
|
51
|
+
"VibeCheck API failed to start within 5 seconds.\nMake sure Playwright Chromium is installed: npx playwright install chromium"
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
function buildDevUrl(path) {
|
|
55
|
+
const normalised = path.startsWith("/") ? path : `/${path}`;
|
|
56
|
+
return `http://localhost:${DEV_PORT}${normalised}`;
|
|
57
|
+
}
|
|
58
|
+
async function startAudit(url) {
|
|
59
|
+
const res = await fetch(`${API_URL}/api/audit`, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers: { "Content-Type": "application/json" },
|
|
62
|
+
body: JSON.stringify({ url, output: { dir: "/tmp/vibecheck-mcp" } })
|
|
63
|
+
});
|
|
64
|
+
if (!res.ok) {
|
|
65
|
+
const body = await res.text();
|
|
66
|
+
throw new Error(`Failed to start audit (${res.status}): ${body}`);
|
|
67
|
+
}
|
|
68
|
+
const data = await res.json();
|
|
69
|
+
return data.id;
|
|
70
|
+
}
|
|
71
|
+
async function pollAudit(id) {
|
|
72
|
+
const res = await fetch(`${API_URL}/api/audit/${id}`);
|
|
73
|
+
if (!res.ok) throw new Error(`Failed to fetch audit ${id}: ${res.status}`);
|
|
74
|
+
return res.json();
|
|
75
|
+
}
|
|
76
|
+
async function runAuditAndWait(url) {
|
|
77
|
+
const id = await startAudit(url);
|
|
78
|
+
lastAuditId = id;
|
|
79
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
80
|
+
while (Date.now() < deadline) {
|
|
81
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
82
|
+
const result = await pollAudit(id);
|
|
83
|
+
if (result.status === "completed" || result.status === "failed") {
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
throw new Error(`Audit timed out after 5 minutes (id: ${id})`);
|
|
88
|
+
}
|
|
89
|
+
function formatAuditResult(result) {
|
|
90
|
+
if (result.status === "failed") {
|
|
91
|
+
return `Audit ID: ${result.id}
|
|
92
|
+
Audit failed: ${result.error ?? "unknown error"}`;
|
|
93
|
+
}
|
|
94
|
+
const findings = result.findings ?? [];
|
|
95
|
+
const top = [...findings].sort((a, b) => b.scoreImpact - a.scoreImpact).slice(0, 10);
|
|
96
|
+
const lines = [
|
|
97
|
+
`Audit ID: ${result.id}`,
|
|
98
|
+
`Vibe-Score: ${result.score ?? "N/A"}/100 (${result.scoreResult?.grade ?? "?"})`,
|
|
99
|
+
`${result.scoreResult?.summary ?? ""}`,
|
|
100
|
+
"",
|
|
101
|
+
`Top findings (${findings.length} total):`
|
|
102
|
+
];
|
|
103
|
+
for (const f of top) {
|
|
104
|
+
lines.push(`[${f.severity.toUpperCase()}] ${f.title}`);
|
|
105
|
+
lines.push(` ${f.description}`);
|
|
106
|
+
lines.push(` Recommendation: ${f.recommendation}`);
|
|
107
|
+
lines.push("");
|
|
108
|
+
}
|
|
109
|
+
return lines.join("\n");
|
|
110
|
+
}
|
|
111
|
+
var server = new McpServer({
|
|
112
|
+
name: "vibecheck",
|
|
113
|
+
version: "0.1.0"
|
|
114
|
+
});
|
|
115
|
+
server.tool(
|
|
116
|
+
"audit_dev_server",
|
|
117
|
+
`Audit the local dev server running on port ${DEV_PORT}. Starts the VibeCheck API if needed, runs a full performance audit, and returns Vibe-Score and prioritized findings.`,
|
|
118
|
+
{ path: z.string().default("/").describe('URL path to audit (e.g. "/dashboard"). Defaults to "/".') },
|
|
119
|
+
async ({ path }) => {
|
|
120
|
+
await ensureApiRunning();
|
|
121
|
+
const url = buildDevUrl(path);
|
|
122
|
+
process.stderr.write(`Auditing ${url}
|
|
123
|
+
`);
|
|
124
|
+
const result = await runAuditAndWait(url);
|
|
125
|
+
return { content: [{ type: "text", text: formatAuditResult(result) }] };
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
server.tool(
|
|
129
|
+
"get_last_audit",
|
|
130
|
+
"Return the results of the most recently completed audit without re-running it.",
|
|
131
|
+
{},
|
|
132
|
+
async () => {
|
|
133
|
+
if (!lastAuditId) {
|
|
134
|
+
return { content: [{ type: "text", text: "No audit has been run yet in this session." }] };
|
|
135
|
+
}
|
|
136
|
+
const result = await pollAudit(lastAuditId);
|
|
137
|
+
return { content: [{ type: "text", text: formatAuditResult(result) }] };
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
var transport = new StdioServerTransport();
|
|
141
|
+
await server.connect(transport);
|
|
142
|
+
process.stderr.write(`VibeCheck MCP server running (dev port: ${DEV_PORT})
|
|
143
|
+
`);
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@roeeash/vibecheck-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Audit your local dev server with Claude — MCP server for VibeCheck",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vibecheck-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/",
|
|
11
|
+
"vendor/"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup src/index.ts --format esm --outDir dist",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
19
|
+
"cors": "^2.8.5",
|
|
20
|
+
"express": "^4.19.0",
|
|
21
|
+
"pino": "^9.0.0",
|
|
22
|
+
"pino-http": "^10.0.0",
|
|
23
|
+
"zod": "^3.25.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"playwright": "^1.44.0"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20.0.0"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"mcp",
|
|
33
|
+
"performance",
|
|
34
|
+
"audit",
|
|
35
|
+
"vibecheck",
|
|
36
|
+
"claude",
|
|
37
|
+
"playwright"
|
|
38
|
+
],
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^25.7.0",
|
|
42
|
+
"tsup": "^8.5.1",
|
|
43
|
+
"typescript": "^6.0.3"
|
|
44
|
+
}
|
|
45
|
+
}
|