@vibecodeqa/mcp 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/README.md +91 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +349 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# @vibecodeqa/mcp
|
|
2
|
+
|
|
3
|
+
MCP server for VibeCode QA — gives AI coding agents real-time code health context.
|
|
4
|
+
|
|
5
|
+
When an AI agent writes or modifies code, it can query vcqa to check: "Is this file healthy? What patterns should I follow? Will this change degrade quality?"
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
### Claude Code
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
claude mcp add vcqa -- npx @vibecodeqa/mcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Claude Desktop
|
|
16
|
+
|
|
17
|
+
Add to `claude_desktop_config.json`:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"vcqa": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": ["@vibecodeqa/mcp"]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Cursor / other MCP clients
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx @vibecodeqa/mcp
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Tools
|
|
37
|
+
|
|
38
|
+
| Tool | Description |
|
|
39
|
+
|------|-------------|
|
|
40
|
+
| `vcqa_score` | Quick score + grade + check summary (uses cache) |
|
|
41
|
+
| `vcqa_scan` | Full scan — all 34 checks with issues and details |
|
|
42
|
+
| `vcqa_file_health` | Issues for a specific file (use before/after editing) |
|
|
43
|
+
| `vcqa_check` | Detailed results for one check (e.g., "complexity") |
|
|
44
|
+
| `vcqa_explain` | What a check measures, why it matters, how to fix |
|
|
45
|
+
| `vcqa_fix` | AI-powered fix for code issues (needs ANTHROPIC_API_KEY) |
|
|
46
|
+
| `vcqa_delta` | Compare current scan vs previous — shows fixed/new issues |
|
|
47
|
+
|
|
48
|
+
## How agents use it
|
|
49
|
+
|
|
50
|
+
**Before modifying a file:**
|
|
51
|
+
```
|
|
52
|
+
→ vcqa_file_health({ file: "src/auth.ts" })
|
|
53
|
+
← 3 issues: complexity 28 (max 15), empty catch block, no test file
|
|
54
|
+
```
|
|
55
|
+
The agent now knows to simplify the function, add error handling, and write a test.
|
|
56
|
+
|
|
57
|
+
**After generating code:**
|
|
58
|
+
```
|
|
59
|
+
→ vcqa_score()
|
|
60
|
+
← B 78/100 (was B 80 — dropped 2 pts)
|
|
61
|
+
→ vcqa_check({ check: "security" })
|
|
62
|
+
← 1 new issue: innerHTML assignment in src/dashboard.tsx:42
|
|
63
|
+
```
|
|
64
|
+
The agent fixes the security issue before committing.
|
|
65
|
+
|
|
66
|
+
**After a fix session:**
|
|
67
|
+
```
|
|
68
|
+
→ vcqa_delta()
|
|
69
|
+
← Score: C 64 → B 80 (+16)
|
|
70
|
+
Fixed: 89 issues | New: 3 issues
|
|
71
|
+
+73 confusion (0 → 73), +55 lint (45 → 100), +12 standards (36 → 48)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Understanding a check:**
|
|
75
|
+
```
|
|
76
|
+
→ vcqa_explain({ check: "confusion" })
|
|
77
|
+
← Measures naming ambiguity that causes LLMs to edit the wrong file...
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Performance
|
|
81
|
+
|
|
82
|
+
- Uses cached `report.json` from previous CLI runs (< 5 min old)
|
|
83
|
+
- Falls back to live scan via `npx @vibecodeqa/cli --skip-tests --json`
|
|
84
|
+
- In-memory cache with 60s TTL prevents redundant scans
|
|
85
|
+
- Typical response: < 100ms (cached), 3-8s (live scan)
|
|
86
|
+
|
|
87
|
+
## Links
|
|
88
|
+
|
|
89
|
+
- [VibeCode QA CLI](https://www.npmjs.com/package/@vibecodeqa/cli)
|
|
90
|
+
- [Website](https://vibecodeqa.online)
|
|
91
|
+
- [GitHub](https://github.com/vibecodeqa/mcp)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* VibeCode QA MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Gives AI coding agents real-time code health context.
|
|
6
|
+
* Tools:
|
|
7
|
+
* vcqa_score — Quick score + grade (fastest, uses cache)
|
|
8
|
+
* vcqa_scan — Full scan with all 25 check results
|
|
9
|
+
* vcqa_file_health — Get issues for a specific file
|
|
10
|
+
* vcqa_check — Get details for a specific check
|
|
11
|
+
* vcqa_explain — Explain what a check measures and how to fix it
|
|
12
|
+
* vcqa_fix — AI-powered fix for code issues (needs ANTHROPIC_API_KEY)
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* npx @vibecodeqa/mcp # stdio transport (for Claude Code, Cursor, etc.)
|
|
16
|
+
* Add to claude_desktop_config.json:
|
|
17
|
+
* { "mcpServers": { "vcqa": { "command": "npx", "args": ["@vibecodeqa/mcp"] } } }
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* VibeCode QA MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Gives AI coding agents real-time code health context.
|
|
6
|
+
* Tools:
|
|
7
|
+
* vcqa_score — Quick score + grade (fastest, uses cache)
|
|
8
|
+
* vcqa_scan — Full scan with all 25 check results
|
|
9
|
+
* vcqa_file_health — Get issues for a specific file
|
|
10
|
+
* vcqa_check — Get details for a specific check
|
|
11
|
+
* vcqa_explain — Explain what a check measures and how to fix it
|
|
12
|
+
* vcqa_fix — AI-powered fix for code issues (needs ANTHROPIC_API_KEY)
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* npx @vibecodeqa/mcp # stdio transport (for Claude Code, Cursor, etc.)
|
|
16
|
+
* Add to claude_desktop_config.json:
|
|
17
|
+
* { "mcpServers": { "vcqa": { "command": "npx", "args": ["@vibecodeqa/mcp"] } } }
|
|
18
|
+
*/
|
|
19
|
+
import { execSync } from "node:child_process";
|
|
20
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
23
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
24
|
+
import { z } from "zod";
|
|
25
|
+
// Check metadata — embedded to avoid shelling out for explain
|
|
26
|
+
const CHECK_META = {
|
|
27
|
+
structure: { label: "Project Structure", category: "Foundations", weight: 6, description: "Checks for standard project files: package.json, tsconfig.json, LICENSE, README, .gitignore, lockfile. Verifies test-to-source ratio.", risk: "Missing config files cause build failures in CI. No lockfile means non-reproducible builds.", recommendation: "Ensure every project has package.json, tsconfig.json, LICENSE, .gitignore, and a lockfile." },
|
|
28
|
+
lint: { label: "Lint", category: "Foundations", weight: 5, description: "Runs the project's linter (Biome or ESLint) and counts errors and warnings.", risk: "Unlinted code accumulates inconsistencies and latent bugs.", recommendation: "Fix all lint errors. Add Biome if no linter configured." },
|
|
29
|
+
types: { label: "Type Check", category: "Foundations", weight: 6, description: "Runs tsc --noEmit to find TypeScript compilation errors.", risk: "Type errors are bugs — every unresolved error is a potential runtime crash.", recommendation: "Fix all type errors. Enable strict mode." },
|
|
30
|
+
"type-safety": { label: "Type Safety", category: "Foundations", weight: 3, description: "Counts unsafe type patterns: 'as any', ': any', @ts-ignore, non-null assertions.", risk: "'as any' silences the type checker. Accumulated any usage correlates with higher defect density.", recommendation: "Replace 'as any' with proper types or type guards." },
|
|
31
|
+
standards: { label: "Code Standards", category: "Foundations", weight: 3, description: "Checks naming conventions, file size, code smells (console.log, var, ==, eval).", risk: "Large files are hard to review. console.log in production leaks data.", recommendation: "Split files over 300 lines. Use const/let, ===, and safe DOM APIs." },
|
|
32
|
+
complexity: { label: "Complexity", category: "Quality", weight: 5, description: "Measures cognitive complexity per function.", risk: "Complex functions are the #1 source of bugs.", recommendation: "Extract complex functions into smaller ones. Use early returns." },
|
|
33
|
+
duplication: { label: "Duplication", category: "Quality", weight: 5, description: "Detects copy-pasted code blocks of 6+ lines.", risk: "Duplicated code means bugs must be fixed in multiple places.", recommendation: "Extract duplicated logic into shared functions." },
|
|
34
|
+
"error-handling": { label: "Error Handling", category: "Quality", weight: 3, description: "Detects empty catch, throw string, floating promises, unsafe JSON.parse.", risk: "Empty catch blocks silently swallow errors.", recommendation: "Handle or log every catch. Use throw new Error()." },
|
|
35
|
+
react: { label: "React Patterns", category: "Quality", weight: 3, description: "Checks conditional hooks, missing keys, index as key, prop spreading.", risk: "Conditional hooks crash React. Missing keys cause incorrect reconciliation.", recommendation: "Never call hooks inside conditions. Always provide stable keys." },
|
|
36
|
+
accessibility: { label: "Accessibility", category: "Quality", weight: 4, description: "Checks img alt, click handlers without keyboard, unlabeled forms.", risk: "1 in 4 adults has a disability.", recommendation: "Add alt text. Use <button> for clickable elements." },
|
|
37
|
+
docs: { label: "Documentation", category: "Quality", weight: 3, description: "Checks README quality and JSDoc coverage.", risk: "Undocumented code is hard to onboard to.", recommendation: "Write README with install/usage. Add JSDoc to public exports." },
|
|
38
|
+
"best-practices": { label: "Best Practices", category: "Quality", weight: 3, description: "CI/CD, supply chain, OIDC, pinned actions, SECURITY.md, CODEOWNERS.", risk: "Missing CI practices lead to supply chain attacks.", recommendation: "Pin actions to SHA. Use OIDC. Add SECURITY.md." },
|
|
39
|
+
testing: { label: "Testing", category: "Testing", weight: 15, description: "Test pyramid, execution, coverage, file pairing, quality metrics.", risk: "Code without tests is code you can't safely change.", recommendation: "Follow testing pyramid. Aim for >80% branch coverage." },
|
|
40
|
+
secrets: { label: "Secrets", category: "Security", weight: 6, description: "Scans for hardcoded secrets: AWS, GitHub, Stripe, OpenAI keys. Delegates to gitleaks.", risk: "Hardcoded secrets are the #1 cause of credential leaks.", recommendation: "Use environment variables or a secret manager." },
|
|
41
|
+
security: { label: "Security Patterns", category: "Security", weight: 5, description: "31 CWE patterns: XSS, injection, weak crypto, prototype pollution, path traversal.", risk: "These patterns represent OWASP Top 10 vulnerabilities.", recommendation: "Replace innerHTML with textContent. Never use eval(). Use parameterized queries." },
|
|
42
|
+
dependencies: { label: "Dependencies", category: "Security", weight: 5, description: "npm audit for CVEs, outdated package detection.", risk: "Vulnerable dependencies are the most common supply chain attack vector.", recommendation: "Run audit regularly. Use Dependabot or Renovate." },
|
|
43
|
+
architecture: { label: "Architecture", category: "Architecture", weight: 5, description: "Circular deps, god modules, orphans, fan-out, import graph analysis.", risk: "Circular deps make refactoring impossible. God modules become bottlenecks.", recommendation: "Break circular deps. Split god modules by concern." },
|
|
44
|
+
performance: { label: "Performance", category: "Architecture", weight: 4, description: "Barrel imports, heavy deps, dynamic import opportunities, CSS-in-JS overhead.", risk: "Barrel files prevent tree-shaking, bloating bundles 2-10x.", recommendation: "Replace barrel re-exports with direct imports." },
|
|
45
|
+
confusion: { label: "Confusion Index", category: "LLM Readiness", weight: 6, description: "Naming ambiguity that causes LLMs to misunderstand code.", risk: "LLMs drop 28.6% on code tasks when names are ambiguous.", recommendation: "Use descriptive, unique names. Avoid synonym files." },
|
|
46
|
+
context: { label: "Context Locality", category: "LLM Readiness", weight: 5, description: "Token density, import depth, circular dep impact on LLM navigation.", risk: "Files over 4000 tokens exceed the LLM attention sweet spot.", recommendation: "Keep files under 400 lines. Limit imports to <15 per file." },
|
|
47
|
+
"doc-coherence": { label: "Doc Coherence", category: "AI Analysis", weight: 0, description: "LLM-powered: stale README claims, incorrect JSDoc, outdated CHANGELOG.", risk: "Stale docs actively mislead developers and LLMs.", recommendation: "Pro feature — set VCQA_PRO_KEY." },
|
|
48
|
+
"code-coherence": { label: "Code Coherence", category: "AI Analysis", weight: 0, description: "LLM-powered: inconsistent validation, conflicting defaults, naming drift.", risk: "Incoherent codebases cause 'works on my machine' bugs.", recommendation: "Pro feature — set VCQA_PRO_KEY." },
|
|
49
|
+
"comment-staleness": { label: "Comment Staleness", category: "AI Analysis", weight: 0, description: "Stale TODOs, numeric mismatches, commented-out code, @deprecated without replacement.", risk: "Stale comments mislead developers and AI agents.", recommendation: "Delete old TODOs. Remove commented-out code." },
|
|
50
|
+
"dead-patterns": { label: "Dead Patterns", category: "AI Analysis", weight: 0, description: "Leftover code from incomplete refactors: fallbacks, parallel impls, dead guards.", risk: "Vibe-coded projects accumulate dead patterns fast.", recommendation: "Pro feature — set VCQA_PRO_KEY." },
|
|
51
|
+
"test-audit": { label: "Test Audit", category: "AI Analysis", weight: 0, description: "Fake/shallow tests: empty bodies, trivial assertions, mock abuse.", risk: "AI-generated tests often look real but test nothing.", recommendation: "Pro feature — set VCQA_PRO_KEY." },
|
|
52
|
+
};
|
|
53
|
+
// Cache scan results to avoid re-scanning on every tool call
|
|
54
|
+
let cachedReport = null;
|
|
55
|
+
let cachedCwd = null;
|
|
56
|
+
let cachedAt = 0;
|
|
57
|
+
const CACHE_TTL = 60_000; // 1 minute
|
|
58
|
+
function runScan(cwd) {
|
|
59
|
+
const now = Date.now();
|
|
60
|
+
if (cachedReport && cachedCwd === cwd && now - cachedAt < CACHE_TTL) {
|
|
61
|
+
return cachedReport;
|
|
62
|
+
}
|
|
63
|
+
// Try reading cached report.json first (from previous CLI run)
|
|
64
|
+
const reportPath = join(cwd, ".vibe-check", "report.json");
|
|
65
|
+
if (existsSync(reportPath)) {
|
|
66
|
+
try {
|
|
67
|
+
const stat = statSync(reportPath);
|
|
68
|
+
if (now - stat.mtimeMs < 300_000) {
|
|
69
|
+
const report = JSON.parse(readFileSync(reportPath, "utf-8"));
|
|
70
|
+
cachedReport = report;
|
|
71
|
+
cachedCwd = cwd;
|
|
72
|
+
cachedAt = now;
|
|
73
|
+
return report;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch { /* fall through to live scan */ }
|
|
77
|
+
}
|
|
78
|
+
// Run live scan
|
|
79
|
+
try {
|
|
80
|
+
const stdout = execSync("npx @vibecodeqa/cli --skip-tests --json .", {
|
|
81
|
+
cwd,
|
|
82
|
+
encoding: "utf-8",
|
|
83
|
+
timeout: 60_000,
|
|
84
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
85
|
+
});
|
|
86
|
+
const report = JSON.parse(stdout);
|
|
87
|
+
cachedReport = report;
|
|
88
|
+
cachedCwd = cwd;
|
|
89
|
+
cachedAt = now;
|
|
90
|
+
return report;
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
throw new Error(`vcqa scan failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const server = new McpServer({
|
|
97
|
+
name: "vcqa",
|
|
98
|
+
version: "0.2.0",
|
|
99
|
+
});
|
|
100
|
+
// ── Tool: vcqa_score ──
|
|
101
|
+
server.tool("vcqa_score", "Get the code health score and grade for the current project. Fast — uses cached results when available.", { path: z.string().optional().describe("Project directory path (defaults to cwd)") }, async ({ path }) => {
|
|
102
|
+
const cwd = path || process.cwd();
|
|
103
|
+
const report = runScan(cwd);
|
|
104
|
+
return {
|
|
105
|
+
content: [{
|
|
106
|
+
type: "text",
|
|
107
|
+
text: JSON.stringify({
|
|
108
|
+
score: report.score,
|
|
109
|
+
grade: report.grade,
|
|
110
|
+
summary: `${report.grade} ${report.score}/100`,
|
|
111
|
+
checks: report.checks.map(c => ({
|
|
112
|
+
name: c.name,
|
|
113
|
+
score: c.score,
|
|
114
|
+
grade: c.grade,
|
|
115
|
+
issues: c.issues.length,
|
|
116
|
+
})),
|
|
117
|
+
}, null, 2),
|
|
118
|
+
}],
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
// ── Tool: vcqa_scan ──
|
|
122
|
+
server.tool("vcqa_scan", "Run a full code health scan. Returns score, grade, and all 25 check results with issues. Use vcqa_score for a quicker summary.", { path: z.string().optional().describe("Project directory path (defaults to cwd)") }, async ({ path }) => {
|
|
123
|
+
const cwd = path || process.cwd();
|
|
124
|
+
const report = runScan(cwd);
|
|
125
|
+
return {
|
|
126
|
+
content: [{
|
|
127
|
+
type: "text",
|
|
128
|
+
text: JSON.stringify(report, null, 2),
|
|
129
|
+
}],
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
// ── Tool: vcqa_file_health ──
|
|
133
|
+
server.tool("vcqa_file_health", "Get all code health issues for a specific file. Use before modifying a file to understand existing problems, or after to check for new issues.", {
|
|
134
|
+
file: z.string().describe("File path relative to project root (e.g., 'src/auth.ts')"),
|
|
135
|
+
path: z.string().optional().describe("Project directory path (defaults to cwd)"),
|
|
136
|
+
}, async ({ file, path }) => {
|
|
137
|
+
const cwd = path || process.cwd();
|
|
138
|
+
const report = runScan(cwd);
|
|
139
|
+
const fileIssues = [];
|
|
140
|
+
for (const c of report.checks) {
|
|
141
|
+
for (const i of c.issues) {
|
|
142
|
+
if (i.file && (i.file === file || i.file.endsWith(`/${file}`) || file.endsWith(i.file))) {
|
|
143
|
+
fileIssues.push({ check: c.name, severity: i.severity, message: i.message, line: i.line, rule: i.rule });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
content: [{
|
|
149
|
+
type: "text",
|
|
150
|
+
text: JSON.stringify({
|
|
151
|
+
file,
|
|
152
|
+
issues: fileIssues.length,
|
|
153
|
+
details: fileIssues,
|
|
154
|
+
advice: fileIssues.length === 0
|
|
155
|
+
? "No issues found for this file."
|
|
156
|
+
: `${fileIssues.length} issues found. Fix errors first, then warnings.`,
|
|
157
|
+
}, null, 2),
|
|
158
|
+
}],
|
|
159
|
+
};
|
|
160
|
+
});
|
|
161
|
+
// ── Tool: vcqa_check ──
|
|
162
|
+
server.tool("vcqa_check", "Get detailed results for a specific check (e.g., 'complexity', 'security', 'testing'). Shows score, issues, and metadata.", {
|
|
163
|
+
check: z.string().describe("Check name (e.g., 'complexity', 'security', 'testing', 'architecture')"),
|
|
164
|
+
path: z.string().optional().describe("Project directory path (defaults to cwd)"),
|
|
165
|
+
}, async ({ check, path }) => {
|
|
166
|
+
const cwd = path || process.cwd();
|
|
167
|
+
const report = runScan(cwd);
|
|
168
|
+
const c = report.checks.find(ch => ch.name === check);
|
|
169
|
+
if (!c) {
|
|
170
|
+
return {
|
|
171
|
+
content: [{
|
|
172
|
+
type: "text",
|
|
173
|
+
text: JSON.stringify({ error: `Unknown check: ${check}`, available: report.checks.map(ch => ch.name) }, null, 2),
|
|
174
|
+
}],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const meta = CHECK_META[check];
|
|
178
|
+
return {
|
|
179
|
+
content: [{
|
|
180
|
+
type: "text",
|
|
181
|
+
text: JSON.stringify({
|
|
182
|
+
name: c.name,
|
|
183
|
+
label: meta?.label || c.name,
|
|
184
|
+
score: c.score,
|
|
185
|
+
grade: c.grade,
|
|
186
|
+
category: meta?.category,
|
|
187
|
+
weight: meta ? `${meta.weight}%` : undefined,
|
|
188
|
+
details: c.details,
|
|
189
|
+
issues: c.issues,
|
|
190
|
+
recommendation: meta?.recommendation,
|
|
191
|
+
}, null, 2),
|
|
192
|
+
}],
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
// ── Tool: vcqa_explain ──
|
|
196
|
+
server.tool("vcqa_explain", "Explain what a specific check measures, why it matters, and how to fix issues. Use to understand WHY a check is flagging something.", {
|
|
197
|
+
check: z.string().describe("Check name to explain (e.g., 'confusion', 'context', 'architecture')"),
|
|
198
|
+
}, async ({ check }) => {
|
|
199
|
+
const meta = CHECK_META[check];
|
|
200
|
+
if (!meta) {
|
|
201
|
+
return {
|
|
202
|
+
content: [{
|
|
203
|
+
type: "text",
|
|
204
|
+
text: JSON.stringify({ error: `Unknown check: ${check}`, available: Object.keys(CHECK_META) }, null, 2),
|
|
205
|
+
}],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
content: [{
|
|
210
|
+
type: "text",
|
|
211
|
+
text: JSON.stringify({
|
|
212
|
+
name: check,
|
|
213
|
+
label: meta.label,
|
|
214
|
+
category: meta.category,
|
|
215
|
+
weight: `${meta.weight}%`,
|
|
216
|
+
what: meta.description,
|
|
217
|
+
risk: meta.risk,
|
|
218
|
+
fix: meta.recommendation,
|
|
219
|
+
}, null, 2),
|
|
220
|
+
}],
|
|
221
|
+
};
|
|
222
|
+
});
|
|
223
|
+
// ── Tool: vcqa_fix ──
|
|
224
|
+
server.tool("vcqa_fix", "AI-powered fix for code quality issues. Scans the project, identifies fixable issues, and uses Claude to generate and apply fixes. Requires ANTHROPIC_API_KEY env var.", {
|
|
225
|
+
path: z.string().optional().describe("Project directory path (defaults to cwd)"),
|
|
226
|
+
check: z.string().optional().describe("Only fix issues from a specific check (e.g., 'security')"),
|
|
227
|
+
dryRun: z.boolean().optional().describe("Preview fixes without applying (default: false)"),
|
|
228
|
+
}, async ({ path, check, dryRun }) => {
|
|
229
|
+
const cwd = path || process.cwd();
|
|
230
|
+
const flags = ["fix", "--ai"];
|
|
231
|
+
if (check)
|
|
232
|
+
flags.push("--check", check);
|
|
233
|
+
if (dryRun)
|
|
234
|
+
flags.push("--dry-run");
|
|
235
|
+
flags.push(cwd);
|
|
236
|
+
try {
|
|
237
|
+
const stdout = execSync(`npx @vibecodeqa/cli ${flags.join(" ")}`, {
|
|
238
|
+
cwd,
|
|
239
|
+
encoding: "utf-8",
|
|
240
|
+
timeout: 120_000,
|
|
241
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
242
|
+
env: { ...process.env },
|
|
243
|
+
});
|
|
244
|
+
// Invalidate cache after fix
|
|
245
|
+
cachedReport = null;
|
|
246
|
+
return {
|
|
247
|
+
content: [{
|
|
248
|
+
type: "text",
|
|
249
|
+
text: stdout.replace(/\x1b\[[0-9;]*m/g, ""), // strip ANSI
|
|
250
|
+
}],
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
const output = err.stdout || err.stderr || String(err);
|
|
255
|
+
return {
|
|
256
|
+
content: [{
|
|
257
|
+
type: "text",
|
|
258
|
+
text: output.replace(/\x1b\[[0-9;]*m/g, ""),
|
|
259
|
+
}],
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
// ── Tool: vcqa_delta ──
|
|
264
|
+
server.tool("vcqa_delta", "Compare current scan against previous scan. Shows what changed: score delta, fixed issues, new issues, per-check changes. Useful after making fixes to see impact.", {
|
|
265
|
+
path: z.string().optional().describe("Project directory path (defaults to cwd)"),
|
|
266
|
+
}, async ({ path }) => {
|
|
267
|
+
const cwd = path || process.cwd();
|
|
268
|
+
const reportPath = join(cwd, ".vibe-check", "report.json");
|
|
269
|
+
// Load previous report
|
|
270
|
+
let prevReport = null;
|
|
271
|
+
if (existsSync(reportPath)) {
|
|
272
|
+
try {
|
|
273
|
+
prevReport = JSON.parse(readFileSync(reportPath, "utf-8"));
|
|
274
|
+
}
|
|
275
|
+
catch { /* corrupt */ }
|
|
276
|
+
}
|
|
277
|
+
if (!prevReport) {
|
|
278
|
+
return { content: [{ type: "text", text: "No previous report found. Run vcqa_scan first to establish a baseline, then make changes and run vcqa_delta." }] };
|
|
279
|
+
}
|
|
280
|
+
// Run fresh scan (invalidate cache to get live results)
|
|
281
|
+
cachedReport = null;
|
|
282
|
+
const currentReport = runScan(cwd);
|
|
283
|
+
// Compute delta (issue-level diffing)
|
|
284
|
+
const beforeIssues = new Map();
|
|
285
|
+
const afterIssues = new Map();
|
|
286
|
+
for (const c of prevReport.checks) {
|
|
287
|
+
for (const iss of c.issues) {
|
|
288
|
+
const key = `${c.name}|${iss.rule || ""}|${iss.file || ""}|${iss.message}`;
|
|
289
|
+
beforeIssues.set(key, (beforeIssues.get(key) || 0) + 1);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
for (const c of currentReport.checks) {
|
|
293
|
+
for (const iss of c.issues) {
|
|
294
|
+
const key = `${c.name}|${iss.rule || ""}|${iss.file || ""}|${iss.message}`;
|
|
295
|
+
afterIssues.set(key, (afterIssues.get(key) || 0) + 1);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
let fixedCount = 0;
|
|
299
|
+
let newCount = 0;
|
|
300
|
+
const fixedSamples = [];
|
|
301
|
+
const newSamples = [];
|
|
302
|
+
for (const [key, bCount] of beforeIssues) {
|
|
303
|
+
const aCount = afterIssues.get(key) || 0;
|
|
304
|
+
if (bCount > aCount) {
|
|
305
|
+
fixedCount += bCount - aCount;
|
|
306
|
+
if (fixedSamples.length < 5)
|
|
307
|
+
fixedSamples.push(key.split("|").slice(0, 2).join(": "));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
for (const [key, aCount] of afterIssues) {
|
|
311
|
+
const bCount = beforeIssues.get(key) || 0;
|
|
312
|
+
if (aCount > bCount) {
|
|
313
|
+
newCount += aCount - bCount;
|
|
314
|
+
if (newSamples.length < 5)
|
|
315
|
+
newSamples.push(key.split("|").slice(0, 2).join(": "));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const scoreDelta = currentReport.score - prevReport.score;
|
|
319
|
+
const checkChanges = currentReport.checks
|
|
320
|
+
.map((c) => {
|
|
321
|
+
const prev = prevReport.checks.find((p) => p.name === c.name);
|
|
322
|
+
return { name: c.name, before: prev?.score ?? 0, after: c.score, delta: c.score - (prev?.score ?? 0) };
|
|
323
|
+
})
|
|
324
|
+
.filter((c) => c.delta !== 0)
|
|
325
|
+
.sort((a, b) => b.delta - a.delta);
|
|
326
|
+
let text = `Score: ${prevReport.grade} ${prevReport.score} → ${currentReport.grade} ${currentReport.score} (${scoreDelta > 0 ? "+" : ""}${scoreDelta})\n`;
|
|
327
|
+
text += `Fixed: ${fixedCount} issues | New: ${newCount} issues\n\n`;
|
|
328
|
+
if (checkChanges.length > 0) {
|
|
329
|
+
text += "Check changes:\n";
|
|
330
|
+
for (const c of checkChanges.slice(0, 10)) {
|
|
331
|
+
text += ` ${c.delta > 0 ? "+" : ""}${c.delta} ${c.name} (${c.before} → ${c.after})\n`;
|
|
332
|
+
}
|
|
333
|
+
text += "\n";
|
|
334
|
+
}
|
|
335
|
+
if (fixedSamples.length > 0)
|
|
336
|
+
text += `Fixed examples: ${fixedSamples.join(", ")}\n`;
|
|
337
|
+
if (newSamples.length > 0)
|
|
338
|
+
text += `New examples: ${newSamples.join(", ")}\n`;
|
|
339
|
+
return { content: [{ type: "text", text }] };
|
|
340
|
+
});
|
|
341
|
+
// ── Start server ──
|
|
342
|
+
async function main() {
|
|
343
|
+
const transport = new StdioServerTransport();
|
|
344
|
+
await server.connect(transport);
|
|
345
|
+
}
|
|
346
|
+
main().catch((err) => {
|
|
347
|
+
console.error("vcqa-mcp error:", err);
|
|
348
|
+
process.exit(1);
|
|
349
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vibecodeqa/mcp",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "MCP server for VibeCode QA — gives AI coding agents real-time code health context",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vcqa-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"LICENSE",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"dev": "tsc --watch"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mcp",
|
|
20
|
+
"model-context-protocol",
|
|
21
|
+
"code-quality",
|
|
22
|
+
"code-health",
|
|
23
|
+
"ai-agent",
|
|
24
|
+
"vibecodeqa"
|
|
25
|
+
],
|
|
26
|
+
"author": "VibeCode QA <hello@vibecodeqa.online>",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/vibecodeqa/mcp"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://vibecodeqa.online",
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=20"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
38
|
+
"zod": "^3.25.32"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^22.15.0",
|
|
42
|
+
"typescript": "^5.8.3"
|
|
43
|
+
}
|
|
44
|
+
}
|