nfunc-mcp 0.2.0 → 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.
- package/README.md +85 -335
- package/dist/index.js +27 -4
- package/dist/index.js.map +1 -1
- package/dist/mappers/a11yDedupe.d.ts +25 -0
- package/dist/mappers/a11yDedupe.js +95 -0
- package/dist/mappers/a11yDedupe.js.map +1 -0
- package/dist/mappers/compositeScore.d.ts +23 -0
- package/dist/mappers/compositeScore.js +107 -0
- package/dist/mappers/compositeScore.js.map +1 -0
- package/dist/mappers/correlator.js +94 -33
- package/dist/mappers/correlator.js.map +1 -1
- package/dist/mappers/defectFormatter.js +129 -15
- package/dist/mappers/defectFormatter.js.map +1 -1
- package/dist/mappers/labFieldComparator.d.ts +62 -0
- package/dist/mappers/labFieldComparator.js +134 -0
- package/dist/mappers/labFieldComparator.js.map +1 -0
- package/dist/mappers/priorityMapper.d.ts +38 -0
- package/dist/mappers/priorityMapper.js +126 -0
- package/dist/mappers/priorityMapper.js.map +1 -1
- package/dist/mappers/psiAggregator.d.ts +130 -0
- package/dist/mappers/psiAggregator.js +293 -0
- package/dist/mappers/psiAggregator.js.map +1 -0
- package/dist/mappers/webVitalsMapper.d.ts +52 -0
- package/dist/mappers/webVitalsMapper.js +131 -0
- package/dist/mappers/webVitalsMapper.js.map +1 -0
- package/dist/tools/accessibility.js +72 -31
- package/dist/tools/accessibility.js.map +1 -1
- package/dist/tools/lighthouse.d.ts +14 -0
- package/dist/tools/lighthouse.js +134 -34
- package/dist/tools/lighthouse.js.map +1 -1
- package/dist/tools/performanceAudit.d.ts +2 -0
- package/dist/tools/performanceAudit.js +446 -0
- package/dist/tools/performanceAudit.js.map +1 -0
- package/dist/tools/performanceAuditPlan.d.ts +2 -0
- package/dist/tools/performanceAuditPlan.js +438 -0
- package/dist/tools/performanceAuditPlan.js.map +1 -0
- package/dist/tools/qaGate.js +109 -34
- package/dist/tools/qaGate.js.map +1 -1
- package/dist/utils/csvReader.d.ts +20 -0
- package/dist/utils/csvReader.js +172 -0
- package/dist/utils/csvReader.js.map +1 -0
- package/dist/utils/httpClient.d.ts +84 -0
- package/dist/utils/httpClient.js +171 -0
- package/dist/utils/httpClient.js.map +1 -0
- package/dist/utils/outputParsers.d.ts +25 -0
- package/dist/utils/outputParsers.js +42 -0
- package/dist/utils/outputParsers.js.map +1 -1
- package/dist/utils/psiAuth.d.ts +26 -0
- package/dist/utils/psiAuth.js +36 -0
- package/dist/utils/psiAuth.js.map +1 -0
- package/dist/utils/psiParser.d.ts +124 -0
- package/dist/utils/psiParser.js +200 -0
- package/dist/utils/psiParser.js.map +1 -0
- package/dist/utils/publicUrl.d.ts +17 -0
- package/dist/utils/publicUrl.js +115 -0
- package/dist/utils/publicUrl.js.map +1 -0
- package/dist/utils/sitemapReader.d.ts +27 -0
- package/dist/utils/sitemapReader.js +272 -0
- package/dist/utils/sitemapReader.js.map +1 -0
- package/dist/utils/urlClassifier.d.ts +45 -0
- package/dist/utils/urlClassifier.js +267 -0
- package/dist/utils/urlClassifier.js.map +1 -0
- package/docs/manual.md +558 -0
- package/docs/psi-report-spec.md +174 -0
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -2,36 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/nfunc-mcp)
|
|
4
4
|
|
|
5
|
-
A local MCP server that gives Claude
|
|
5
|
+
A local MCP server that gives Claude a non-functional QA toolkit. Performance,
|
|
6
|
+
accessibility, SEO, code quality and real-user Core Web Vitals — run
|
|
7
|
+
individually or all at once, returned as prioritised findings you can act on
|
|
8
|
+
rather than raw tool output.
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## Table of contents
|
|
10
|
-
|
|
11
|
-
1. [What it does](#what-it-does)
|
|
12
|
-
2. [Prerequisites](#prerequisites)
|
|
13
|
-
3. [Install & build](#install--build)
|
|
14
|
-
4. [Register with Claude Code](#register-with-claude-code)
|
|
15
|
-
5. [Tools](#tools)
|
|
16
|
-
6. [run_qa_gate — the main tool](#run_qa_gate--the-main-tool)
|
|
17
|
-
- [Inputs](#inputs)
|
|
18
|
-
- [Release readiness tiers](#release-readiness-tiers)
|
|
19
|
-
- [Composite score](#composite-score)
|
|
20
|
-
- [Scorecard](#scorecard)
|
|
21
|
-
- [Cross-tool corroboration](#cross-tool-corroboration)
|
|
22
|
-
- [HTML report](#html-report)
|
|
23
|
-
- [Output shape](#output-shape)
|
|
24
|
-
7. [Individual tools](#individual-tools)
|
|
25
|
-
8. [Priority system](#priority-system)
|
|
26
|
-
9. [Project layout](#project-layout)
|
|
27
|
-
10. [How to prompt](#how-to-prompt)
|
|
10
|
+
> **[Operating manual →](docs/manual.md)** — installation options, per-tool
|
|
11
|
+
> reference, output shapes, troubleshooting.
|
|
28
12
|
|
|
29
13
|
---
|
|
30
14
|
|
|
31
15
|
## What it does
|
|
32
16
|
|
|
33
|
-
`nfunc-mcp` wires four QA tools into Claude's tool-use loop:
|
|
34
|
-
|
|
35
17
|
| Capability | Tools | What it checks |
|
|
36
18
|
|---|---|---|
|
|
37
19
|
| Performance | Lighthouse | LCP, TTI, TBT, CLS, bundle size, caching |
|
|
@@ -40,359 +22,127 @@ A local MCP server that gives Claude (or any MCP client) a full non-functional Q
|
|
|
40
22
|
| Best practices | Lighthouse | HTTPS, deprecated APIs, third-party cookies |
|
|
41
23
|
| Code quality | ESLint | Dead code, undeclared vars, swallowed errors |
|
|
42
24
|
| Security patterns | Semgrep | OWASP JS/TS patterns |
|
|
25
|
+
| Real-user vitals | PageSpeed Insights + CrUX | What actual visitors experienced, versus what the lab measures |
|
|
43
26
|
|
|
44
|
-
|
|
27
|
+
Findings arrive prioritised **P1 / P2 / P3**, written as defect-ticket prose
|
|
28
|
+
rather than audit jargon, with passing checks filtered out. Nothing that passes
|
|
29
|
+
is ever reported.
|
|
45
30
|
|
|
46
31
|
---
|
|
47
32
|
|
|
48
|
-
##
|
|
49
|
-
|
|
50
|
-
`nfunc-mcp` is a thin wrapper around four CLI tools. Install the ones you need before registering the server:
|
|
51
|
-
|
|
52
|
-
| Tool | Install | Used by |
|
|
53
|
-
|---|---|---|
|
|
54
|
-
| Lighthouse | `npm install -g lighthouse` | `run_lighthouse`, `run_qa_gate` (URL) |
|
|
55
|
-
| pa11y | `npm install -g pa11y` | `run_accessibility_check`, `run_qa_gate` (URL) |
|
|
56
|
-
| ESLint | `npm install -g eslint` | `run_static_analysis`, `run_qa_gate` (path) |
|
|
57
|
-
| Semgrep | `brew install semgrep` or `pip install semgrep` | `run_static_analysis`, `run_qa_gate` (path) |
|
|
58
|
-
|
|
59
|
-
Verify each is reachable:
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
lighthouse --version
|
|
63
|
-
pa11y --version
|
|
64
|
-
eslint --version
|
|
65
|
-
semgrep --version
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
**You don't need all four.** If a tool is missing or not installed, the gate still runs — that tool's scorecard entry shows `UNAVAILABLE` and its findings are skipped. URL-only runs only need Lighthouse and pa11y; path-only runs only need ESLint and Semgrep.
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## Install & register
|
|
73
|
-
|
|
74
|
-
### Option A — npm (recommended, no cloning needed)
|
|
33
|
+
## Quick start
|
|
75
34
|
|
|
76
35
|
```bash
|
|
77
36
|
claude mcp add nfunc-mcp -- npx -y nfunc-mcp
|
|
78
37
|
```
|
|
79
38
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
Add to `~/.claude.json` under `mcpServers`:
|
|
85
|
-
|
|
86
|
-
```json
|
|
87
|
-
{
|
|
88
|
-
"mcpServers": {
|
|
89
|
-
"nfunc-mcp": {
|
|
90
|
-
"command": "npx",
|
|
91
|
-
"args": ["-y", "nfunc-mcp"]
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### Option C — From source (contributors / local dev)
|
|
98
|
-
|
|
99
|
-
```bash
|
|
100
|
-
git clone https://github.com/Hiddensound/NFunc_MCP.git
|
|
101
|
-
cd NFunc_MCP
|
|
102
|
-
npm install
|
|
103
|
-
npm run build
|
|
104
|
-
claude mcp add nfunc-mcp -- node /absolute/path/to/NFunc_MCP/dist/index.js
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### Scripts (source only)
|
|
108
|
-
|
|
109
|
-
| Script | Purpose |
|
|
110
|
-
|---|---|
|
|
111
|
-
| `npm run build` | Compile TypeScript → `dist/` |
|
|
112
|
-
| `npm start` | Run the compiled server |
|
|
113
|
-
| `npm run dev` | Run from source with hot reload (`tsx watch`) |
|
|
114
|
-
|
|
115
|
-
### Verify the connection
|
|
116
|
-
|
|
117
|
-
1. Run `/mcp` in Claude Code — `nfunc-mcp` should show as `connected`.
|
|
118
|
-
2. Ask Claude: *"Call the nfunc-mcp ping tool."*
|
|
119
|
-
3. Expected response:
|
|
120
|
-
```json
|
|
121
|
-
{ "status": "ok", "timestamp": "2026-05-20T12:00:00.000Z" }
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
---
|
|
39
|
+
Then install whichever CLIs you need — `lighthouse`, `pa11y`, `eslint`,
|
|
40
|
+
`semgrep`. Missing tools are skipped rather than fatal, so start with what you
|
|
41
|
+
have. For real-user field data, add a
|
|
42
|
+
[PageSpeed Insights key](docs/manual.md#pagespeed-insights-api-key).
|
|
125
43
|
|
|
126
|
-
|
|
44
|
+
Verify with `/mcp`, then ask Claude to *"call the nfunc-mcp ping tool."*
|
|
127
45
|
|
|
128
|
-
|
|
129
|
-
|---|---|---|
|
|
130
|
-
| `ping` | Health check — confirms the server is up | — |
|
|
131
|
-
| `run_lighthouse` | Full Lighthouse audit for a URL | `url` |
|
|
132
|
-
| `run_accessibility_check` | pa11y WCAG audit for a URL | `url`, `standard` (optional), `ignore` (optional) |
|
|
133
|
-
| `run_static_analysis` | ESLint + Semgrep scan for a local codebase | `path` |
|
|
134
|
-
| `run_qa_gate` | All tools in parallel + correlation + HTML report | `url` and/or `path` |
|
|
46
|
+
[Other install options →](docs/manual.md#install-and-register)
|
|
135
47
|
|
|
136
48
|
---
|
|
137
49
|
|
|
138
|
-
##
|
|
139
|
-
|
|
140
|
-
This is the tool to reach for in nearly every QA workflow. It replaces running tools individually and adds cross-tool intelligence on top.
|
|
141
|
-
|
|
142
|
-
### Inputs
|
|
50
|
+
## The tools
|
|
143
51
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
| Input | Type | When to provide |
|
|
147
|
-
|---|---|---|
|
|
148
|
-
| `url` | string (URL) | You have a running page — production, staging, preview URL, or localhost. Enables Lighthouse and pa11y. |
|
|
149
|
-
| `path` | string (path) | You have a local codebase. Enables ESLint and Semgrep. |
|
|
150
|
-
| `context` | string | Optional. Free-text description of the project (e.g. `"React e-commerce checkout"`). Helps Claude interpret results. |
|
|
151
|
-
|
|
152
|
-
**URL only** — browser-based checks, static analysis skipped:
|
|
153
|
-
```
|
|
154
|
-
QA snapshot — https://staging.myapp.com
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
**Path only** — static analysis only, browser checks skipped:
|
|
158
|
-
```
|
|
159
|
-
QA snapshot — /path/to/my-feature-branch
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
**Both** — full suite:
|
|
163
|
-
```
|
|
164
|
-
QA snapshot — https://staging.myapp.com, code at /path/to/repo
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
### Release readiness tiers
|
|
168
|
-
|
|
169
|
-
The `release_readiness` field replaces a binary pass/fail with four actionable tiers:
|
|
170
|
-
|
|
171
|
-
| Value | Meaning | Condition |
|
|
172
|
-
|---|---|---|
|
|
173
|
-
| `BLOCKED` | Cannot ship — P1 issues exist | Any P1 finding |
|
|
174
|
-
| `CONDITIONAL` | Shippable with caveats — track P2s before merging | P2 findings, no P1s |
|
|
175
|
-
| `ADVISORY` | Safe to ship — P3s are tech debt to log | Only P3 findings |
|
|
176
|
-
| `CLEAR` | No issues detected | Zero findings |
|
|
177
|
-
|
|
178
|
-
### Composite score
|
|
179
|
-
|
|
180
|
-
A single `composite_score` (0–100) gives a continuous health measure across all tools.
|
|
181
|
-
|
|
182
|
-
**Formula:** Start at 100, deduct by finding severity:
|
|
183
|
-
|
|
184
|
-
| Priority | Deduction |
|
|
52
|
+
| Tool | What it does |
|
|
185
53
|
|---|---|
|
|
186
|
-
|
|
|
187
|
-
|
|
|
188
|
-
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
Gate values:
|
|
206
|
-
|
|
207
|
-
| Gate | Meaning |
|
|
208
|
-
|---|---|
|
|
209
|
-
| `PASS` | No issues at this tool's threshold |
|
|
210
|
-
| `WARN` | Issues exist but below the FAIL threshold |
|
|
211
|
-
| `FAIL` | Issues at P1 level (or Lighthouse avg < 50) |
|
|
212
|
-
| `SKIPPED` | Input not provided (URL or path not supplied) |
|
|
213
|
-
| `UNAVAILABLE` | Tool was invoked but is not installed or errored |
|
|
214
|
-
|
|
215
|
-
### Cross-tool corroboration
|
|
216
|
-
|
|
217
|
-
When Lighthouse and pa11y independently flag the same accessibility gap, those findings are:
|
|
218
|
-
|
|
219
|
-
1. **Merged** into a single entry in `corroborated_findings`
|
|
220
|
-
2. **Priority-promoted** one tier (P3→P2, P2→P1)
|
|
221
|
-
3. **Annotated** with `confidence: "high"` and `confirmed_by: ["lighthouse", "pa11y"]`
|
|
222
|
-
|
|
223
|
-
These are the highest-confidence findings in any report — two independent tools agreeing is stronger evidence than either alone. They appear in their own dedicated section above all other findings, and bubble to the top of `top_issues`.
|
|
224
|
-
|
|
225
|
-
**Corroboration mapping (Rule 1):**
|
|
226
|
-
|
|
227
|
-
| Lighthouse audit | pa11y technique |
|
|
228
|
-
|---|---|
|
|
229
|
-
| `color-contrast` | `.G18`, `.G145`, `.G174` |
|
|
230
|
-
| `image-alt` | `.H37`, `.H67`, `.F65` |
|
|
231
|
-
| `label` | `.H44`, `.F68`, `.H91.Input` |
|
|
232
|
-
| `link-name` | `.H30`, `.H91.A.` |
|
|
233
|
-
| `html-has-lang` | `.H57` |
|
|
234
|
-
| `button-name` | `.H91.Button` |
|
|
235
|
-
| *(and more)* | |
|
|
236
|
-
|
|
237
|
-
**Performance ↔ code linkage (Rule 2):** If a Lighthouse performance finding's display value contains a filename that also appears in a static analysis finding, the static finding is added as `related_findings` on the Lighthouse entry. Findings are not merged — they remain linked by reference.
|
|
238
|
-
|
|
239
|
-
### HTML report
|
|
240
|
-
|
|
241
|
-
Every `run_qa_gate` call automatically writes a self-contained HTML file to `/tmp` and returns its path as `report_file`:
|
|
242
|
-
|
|
243
|
-
```json
|
|
244
|
-
{
|
|
245
|
-
"report_file": "file:///tmp/qa-report-myapp-com-1234567890.html"
|
|
246
|
-
}
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
Open the path in any browser to get:
|
|
250
|
-
|
|
251
|
-
- Release readiness banner (colour-coded)
|
|
252
|
-
- Composite score gauge (SVG arc, 0–100)
|
|
253
|
-
- Per-tool scorecard table
|
|
254
|
-
- Cross-confirmed findings section (highlighted)
|
|
255
|
-
- Collapsible finding cards grouped by P1 / P2 / P3
|
|
256
|
-
- Evidence and selector for each finding
|
|
257
|
-
|
|
258
|
-
No server required — the file is fully self-contained with inline CSS.
|
|
259
|
-
|
|
260
|
-
### Output shape
|
|
261
|
-
|
|
262
|
-
```jsonc
|
|
263
|
-
{
|
|
264
|
-
"release_readiness": "BLOCKED", // BLOCKED | CONDITIONAL | ADVISORY | CLEAR
|
|
265
|
-
"composite_score": 22, // 0–100
|
|
266
|
-
"report_file": "file:///tmp/qa-report-xxx.html",
|
|
267
|
-
"scorecard": [ ... ], // per-tool gate + score/issues
|
|
268
|
-
"eslint_config_used": "project", // present only when path was supplied
|
|
269
|
-
"summary": "110 findings (101 P1, 9 P2) across 2 tools. ...",
|
|
270
|
-
"corroborated_findings": [ ... ], // cross-confirmed, confidence: "high"
|
|
271
|
-
"top_issues": [ ... ], // top 3 findings (corroborated first)
|
|
272
|
-
"all_findings": [ ... ], // all findings sorted by priority
|
|
273
|
-
"correlations_found": 1,
|
|
274
|
-
"errors": [ ... ] // present only if a tool errored
|
|
275
|
-
}
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
Each finding:
|
|
279
|
-
|
|
280
|
-
```jsonc
|
|
281
|
-
{
|
|
282
|
-
"priority": "P1", // P1 | P2 | P3
|
|
283
|
-
"title": "Largest Contentful Paint",
|
|
284
|
-
"description": "Users see main content 34s after navigation...",
|
|
285
|
-
"evidence": { "audit_id": "largest-contentful-paint", "value": "34.3 s" },
|
|
286
|
-
"source_tool": "lighthouse",
|
|
287
|
-
// corroborated findings also have:
|
|
288
|
-
"confirmed_by": ["lighthouse", "pa11y"],
|
|
289
|
-
"confidence": "high"
|
|
290
|
-
}
|
|
291
|
-
```
|
|
54
|
+
| **`run_qa_gate`** | **The one to reach for.** Runs everything applicable in parallel, correlates findings across tools, and returns a release verdict, a composite score, a per-tool scorecard and an HTML report. |
|
|
55
|
+
| `run_lighthouse` | Lighthouse for a URL. `form_factor: "both"` finds device-specific defects the single profiles miss. |
|
|
56
|
+
| `run_accessibility_check` | pa11y WCAG audit. `runner: "axe"` for ARIA and design systems; `"both"` for the widest sweep. |
|
|
57
|
+
| `run_static_analysis` | ESLint + Semgrep over a local codebase. Uses your ESLint config if it finds one. |
|
|
58
|
+
| `plan_performance_audit` | Plans a PageSpeed Insights audit — finds your URLs, groups them into page templates, costs the run. **Spends no quota.** |
|
|
59
|
+
| `run_performance_audit` | Runs it. Lab scores, real-user field data, and the disagreements between them. |
|
|
60
|
+
| `ping` | Health check. |
|
|
61
|
+
|
|
62
|
+
### Which one when
|
|
63
|
+
|
|
64
|
+
- **Shipping something?** `run_qa_gate`. It is the default answer.
|
|
65
|
+
- **One dimension in depth?** The individual tool — `run_lighthouse` for a perf
|
|
66
|
+
regression, `run_accessibility_check` for an a11y pass.
|
|
67
|
+
- **"Is the site actually fast for real people?"** The PSI pair. This is the
|
|
68
|
+
only thing here that measures real visitors instead of a simulation, and it
|
|
69
|
+
routinely disagrees with the lab.
|
|
70
|
+
|
|
71
|
+
The PSI tools are **opt-in** — `run_qa_gate` never calls them, because they
|
|
72
|
+
spend API quota and take minutes rather than seconds.
|
|
292
73
|
|
|
293
74
|
---
|
|
294
75
|
|
|
295
|
-
##
|
|
76
|
+
## How to ask for it
|
|
296
77
|
|
|
297
|
-
### run_lighthouse
|
|
298
|
-
|
|
299
|
-
Runs a full Lighthouse audit against a URL.
|
|
300
|
-
|
|
301
|
-
```
|
|
302
|
-
Run Lighthouse on https://myapp.com
|
|
303
78
|
```
|
|
79
|
+
# Full suite
|
|
80
|
+
QA snapshot — https://myapp.com, code at /path/to/repo
|
|
304
81
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
### run_accessibility_check
|
|
308
|
-
|
|
309
|
-
Runs pa11y against a URL at WCAG 2 AA by default. Returns only violations (errors) — use the CLI directly with `--include-notices --include-warnings` for the full checklist.
|
|
82
|
+
# URL only (Lighthouse + pa11y)
|
|
83
|
+
QA snapshot — https://myapp.com
|
|
310
84
|
|
|
85
|
+
# Local branch only (ESLint + Semgrep)
|
|
86
|
+
QA snapshot — /path/to/my-feature-branch
|
|
311
87
|
```
|
|
312
|
-
Run an accessibility check on https://myapp.com
|
|
313
|
-
Run accessibility check at AAA standard on https://myapp.com
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
Returns: `url`, `standard`, `violation_count`, `findings`.
|
|
317
|
-
|
|
318
|
-
### run_static_analysis
|
|
319
88
|
|
|
320
|
-
|
|
89
|
+
These all work too:
|
|
321
90
|
|
|
322
91
|
```
|
|
323
|
-
|
|
92
|
+
Health check on https://myapp.com
|
|
93
|
+
Is https://myapp.com ready to ship? Code at /path/to/repo
|
|
94
|
+
Any red flags? /path/to/repo
|
|
95
|
+
Run Lighthouse on https://myapp.com for mobile and desktop
|
|
96
|
+
Run an accessibility check on https://myapp.com using the axe runner
|
|
97
|
+
Plan a PageSpeed Insights audit for https://myapp.com
|
|
324
98
|
```
|
|
325
99
|
|
|
326
|
-
|
|
100
|
+
`run_qa_gate` returns a `report_file` path — open it in a browser for the
|
|
101
|
+
visual dashboard.
|
|
327
102
|
|
|
328
103
|
---
|
|
329
104
|
|
|
330
|
-
##
|
|
105
|
+
## What makes it different from running the CLIs yourself
|
|
331
106
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
| P1 | Blocker — fix before shipping | Score < 50 | Level A | Semgrep security, ESLint error |
|
|
335
|
-
| P2 | Warning — track before merging | Score 50–79 | Level AA | ESLint warning |
|
|
336
|
-
| P3 | Advisory — log as tech debt | Score 80–89 | Level AAA | — |
|
|
337
|
-
| *(suppressed)* | Passing — not reported | Score ≥ 90 | — | — |
|
|
107
|
+
**Findings, not output.** Every result is a prioritised defect with QA-native
|
|
108
|
+
prose and traceable evidence, not a wall of audit JSON.
|
|
338
109
|
|
|
339
|
-
|
|
110
|
+
**Cross-tool corroboration.** When Lighthouse and pa11y independently flag the
|
|
111
|
+
same accessibility gap, the finding is merged, promoted a tier and marked
|
|
112
|
+
`confidence: "high"`. Two tools agreeing is stronger evidence than either alone.
|
|
340
113
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
114
|
+
**Systemic collapse.** One duplicate-id component failing on eleven elements is
|
|
115
|
+
reported as one defect, not eleven.
|
|
116
|
+
[How that works →](docs/manual.md#run_accessibility_check)
|
|
344
117
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
│ ├── config/
|
|
351
|
-
│ │ └── qa-mcp-baseline.eslint.config.js # Fallback ESLint config
|
|
352
|
-
│ ├── tools/
|
|
353
|
-
│ │ ├── qaGate.ts # Orchestrator — runs all tools, builds report
|
|
354
|
-
│ │ ├── lighthouse.ts # run_lighthouse tool
|
|
355
|
-
│ │ ├── accessibility.ts # run_accessibility_check tool
|
|
356
|
-
│ │ └── staticAnalysis.ts # run_static_analysis tool
|
|
357
|
-
│ ├── mappers/
|
|
358
|
-
│ │ ├── correlator.ts # Cross-tool correlation engine (Rule 1 + 2)
|
|
359
|
-
│ │ ├── defectFormatter.ts # Raw tool output → Finding objects
|
|
360
|
-
│ │ └── priorityMapper.ts # Score/severity → P1/P2/P3
|
|
361
|
-
│ └── utils/
|
|
362
|
-
│ ├── reportGenerator.ts # HTML report builder
|
|
363
|
-
│ ├── shellRunner.ts # CLI execution with timeout + error handling
|
|
364
|
-
│ ├── outputParsers.ts # JSON parsers for each tool's output
|
|
365
|
-
│ ├── eslintConfigDetector.ts # Detects project ESLint config
|
|
366
|
-
│ └── toolResponse.ts # MCP error response helpers
|
|
367
|
-
├── dist/ # Compiled output (gitignored)
|
|
368
|
-
├── package.json
|
|
369
|
-
├── tsconfig.json
|
|
370
|
-
└── README.md
|
|
371
|
-
```
|
|
118
|
+
**Lab versus field.** A metric that passes in the lab but fails for real users
|
|
119
|
+
means your test environment is not reproducing production — and no local tool
|
|
120
|
+
can detect it. On one real homepage the lab reported a perfect CLS of 0 while
|
|
121
|
+
70% of real users were experiencing a rating of poor.
|
|
122
|
+
[More →](docs/manual.md#what-psi-adds-over-run_lighthouse)
|
|
372
123
|
|
|
373
124
|
---
|
|
374
125
|
|
|
375
|
-
##
|
|
126
|
+
## Priority levels
|
|
376
127
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
128
|
+
| | Meaning |
|
|
129
|
+
|---|---|
|
|
130
|
+
| **P1** | Blocker — fix before shipping |
|
|
131
|
+
| **P2** | Warning — track before merging |
|
|
132
|
+
| **P3** | Advisory — log as tech debt |
|
|
382
133
|
|
|
383
|
-
|
|
384
|
-
|
|
134
|
+
Corroborated and field-confirmed findings are promoted a tier; lab-only
|
|
135
|
+
findings that real users don't experience are demoted.
|
|
136
|
+
[Full mapping →](docs/manual.md#priority-system)
|
|
385
137
|
|
|
386
|
-
|
|
387
|
-
QA snapshot — /path/to/my-feature-branch
|
|
388
|
-
```
|
|
138
|
+
---
|
|
389
139
|
|
|
390
|
-
|
|
140
|
+
## Docs
|
|
391
141
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
```
|
|
142
|
+
| | |
|
|
143
|
+
|---|---|
|
|
144
|
+
| [Operating manual](docs/manual.md) | Install, per-tool reference, output shapes, troubleshooting |
|
|
145
|
+
| [PSI report spec](docs/psi-report-spec.md) | How to turn a PSI audit into a written report |
|
|
397
146
|
|
|
398
|
-
|
|
147
|
+
MIT-compatible ISC licence. Issues and PRs welcome at
|
|
148
|
+
[Hiddensound/NFunc_MCP](https://github.com/Hiddensound/NFunc_MCP).
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
2
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
6
|
import { registerLighthouseTool } from "./tools/lighthouse.js";
|
|
5
7
|
import { registerAccessibilityTool } from "./tools/accessibility.js";
|
|
6
8
|
import { registerStaticAnalysisTool } from "./tools/staticAnalysis.js";
|
|
7
9
|
import { registerQaGateTool } from "./tools/qaGate.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
import { registerPerformanceAuditPlanTool } from "./tools/performanceAuditPlan.js";
|
|
11
|
+
import { registerPerformanceAuditTool } from "./tools/performanceAudit.js";
|
|
12
|
+
/**
|
|
13
|
+
* Identity comes from package.json rather than being written out here, so the
|
|
14
|
+
* two cannot drift — they already had, declaring qa-mcp/0.1.0 against
|
|
15
|
+
* nfunc-mcp/0.2.0 after the npm rename, and `npm version` would reintroduce
|
|
16
|
+
* the gap on every release if these were hardcoded.
|
|
17
|
+
*
|
|
18
|
+
* "../package.json" resolves to the repo root from both src/index.ts and
|
|
19
|
+
* dist/index.js, and npm always ships package.json, so the same path works in
|
|
20
|
+
* dev, in a local build, and in an installed package. Falls back rather than
|
|
21
|
+
* failing to boot if it is ever unreadable.
|
|
22
|
+
*/
|
|
23
|
+
function readIdentity() {
|
|
24
|
+
try {
|
|
25
|
+
const pkg = JSON.parse(readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
|
|
26
|
+
return { name: pkg.name ?? "nfunc-mcp", version: pkg.version ?? "0.0.0" };
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return { name: "nfunc-mcp", version: "0.0.0" };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const server = new McpServer(readIdentity());
|
|
12
33
|
server.registerTool("ping", {
|
|
13
34
|
description: "Health-check tool. Returns ok status and current ISO timestamp.",
|
|
14
35
|
inputSchema: {},
|
|
@@ -25,6 +46,8 @@ registerLighthouseTool(server);
|
|
|
25
46
|
registerAccessibilityTool(server);
|
|
26
47
|
registerStaticAnalysisTool(server);
|
|
27
48
|
registerQaGateTool(server);
|
|
49
|
+
registerPerformanceAuditPlanTool(server);
|
|
50
|
+
registerPerformanceAuditTool(server);
|
|
28
51
|
async function main() {
|
|
29
52
|
const transport = new StdioServerTransport();
|
|
30
53
|
await server.connect(transport);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,gCAAgC,EAAE,MAAM,iCAAiC,CAAC;AACnF,OAAO,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAE3E;;;;;;;;;;GAUG;AACH,SAAS,YAAY;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CACpB,YAAY,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAC1C,CAAC;QACzC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,WAAW,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,OAAO,EAAE,CAAC;IAC5E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IACjD,CAAC;AACH,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,YAAY,EAAE,CAAC,CAAC;AAE7C,MAAM,CAAC,YAAY,CACjB,MAAM,EACN;IACE,WAAW,EAAE,iEAAiE;IAC9E,WAAW,EAAE,EAAE;CAChB,EACD,KAAK,IAAI,EAAE;IACT,MAAM,OAAO,GAAG;QACd,MAAM,EAAE,IAAI;QACZ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;IACF,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;KAC3D,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAC/B,yBAAyB,CAAC,MAAM,CAAC,CAAC;AAClC,0BAA0B,CAAC,MAAM,CAAC,CAAC;AACnC,kBAAkB,CAAC,MAAM,CAAC,CAAC;AAC3B,gCAAgC,CAAC,MAAM,CAAC,CAAC;AACzC,4BAA4B,CAAC,MAAM,CAAC,CAAC;AAErC,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,qBAAqB,EAAE,GAAG,CAAC,CAAC;IAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Finding } from "../types.js";
|
|
2
|
+
export interface A11yDedupeResult {
|
|
3
|
+
findings: Finding[];
|
|
4
|
+
rawCount: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Collapses the two ways pa11y over-reports a single defect.
|
|
8
|
+
*
|
|
9
|
+
* Pass 1 — exact repeats. pa11y emits one violation per occurrence, and for
|
|
10
|
+
* page-wide rules (F77, duplicate id, above all) every occurrence resolves
|
|
11
|
+
* back to the same selector. A real commerce page produced 70 of 85 findings
|
|
12
|
+
* from two defects repeated 35 times each, burying the genuine issues and
|
|
13
|
+
* flooring the qa_gate composite score. Collapsing on (rule_code, selector)
|
|
14
|
+
* removes those while keeping two different elements failing the same rule
|
|
15
|
+
* separate, since they are separate fixes.
|
|
16
|
+
*
|
|
17
|
+
* Pass 2 — systemic defects. One rule failing across many *different*
|
|
18
|
+
* elements, which (rule_code, selector) cannot catch. The axe runner produces
|
|
19
|
+
* these routinely: one mis-authored component reused across a page gave 41
|
|
20
|
+
* distinct aria-allowed-attr findings. See SYSTEMIC_THRESHOLD.
|
|
21
|
+
*
|
|
22
|
+
* Counts survive as evidence.occurrences (written only when > 1), so the
|
|
23
|
+
* volume signal is preserved without N copies of the finding.
|
|
24
|
+
*/
|
|
25
|
+
export declare function dedupeA11yFindings(findings: Finding[]): A11yDedupeResult;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const PRIORITY_ORDER = { P1: 0, P2: 1, P3: 2 };
|
|
2
|
+
/**
|
|
3
|
+
* Above this many distinct elements failing one rule, the individual findings
|
|
4
|
+
* stop being separately actionable and start being one systemic defect — a
|
|
5
|
+
* component used everywhere, not N unrelated bugs. At or below it, each
|
|
6
|
+
* element keeps its own line.
|
|
7
|
+
*
|
|
8
|
+
* Set at 10 rather than lower because distinct elements failing the same rule
|
|
9
|
+
* are often still separate fixes: ten duplicate ids on a page are ten ids to
|
|
10
|
+
* rename, and collapsing them hides the list a developer needs. It takes a
|
|
11
|
+
* genuine flood — axe reported 41 aria-allowed-attr failures from one reused
|
|
12
|
+
* component — before the group is more useful than its members.
|
|
13
|
+
*/
|
|
14
|
+
const SYSTEMIC_THRESHOLD = 10;
|
|
15
|
+
/**
|
|
16
|
+
* Collapses the two ways pa11y over-reports a single defect.
|
|
17
|
+
*
|
|
18
|
+
* Pass 1 — exact repeats. pa11y emits one violation per occurrence, and for
|
|
19
|
+
* page-wide rules (F77, duplicate id, above all) every occurrence resolves
|
|
20
|
+
* back to the same selector. A real commerce page produced 70 of 85 findings
|
|
21
|
+
* from two defects repeated 35 times each, burying the genuine issues and
|
|
22
|
+
* flooring the qa_gate composite score. Collapsing on (rule_code, selector)
|
|
23
|
+
* removes those while keeping two different elements failing the same rule
|
|
24
|
+
* separate, since they are separate fixes.
|
|
25
|
+
*
|
|
26
|
+
* Pass 2 — systemic defects. One rule failing across many *different*
|
|
27
|
+
* elements, which (rule_code, selector) cannot catch. The axe runner produces
|
|
28
|
+
* these routinely: one mis-authored component reused across a page gave 41
|
|
29
|
+
* distinct aria-allowed-attr findings. See SYSTEMIC_THRESHOLD.
|
|
30
|
+
*
|
|
31
|
+
* Counts survive as evidence.occurrences (written only when > 1), so the
|
|
32
|
+
* volume signal is preserved without N copies of the finding.
|
|
33
|
+
*/
|
|
34
|
+
export function dedupeA11yFindings(findings) {
|
|
35
|
+
// Pass 1 — exact repeats: same rule on the same selector.
|
|
36
|
+
const byKey = new Map();
|
|
37
|
+
for (const f of findings) {
|
|
38
|
+
const key = `${String(f.evidence["rule_code"] ?? "")}|` +
|
|
39
|
+
`${String(f.evidence["selector"] ?? "")}`;
|
|
40
|
+
const existing = byKey.get(key);
|
|
41
|
+
if (!existing) {
|
|
42
|
+
byKey.set(key, { finding: f, count: 1 });
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
existing.count += 1;
|
|
46
|
+
// pa11y can grade occurrences of one rule differently (error vs warning);
|
|
47
|
+
// keep the most severe so dedup never downgrades a defect.
|
|
48
|
+
if (PRIORITY_ORDER[f.priority] < PRIORITY_ORDER[existing.finding.priority]) {
|
|
49
|
+
existing.finding = f;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const exact = Array.from(byKey.values()).map(({ finding, count }) => count > 1
|
|
53
|
+
? { ...finding, evidence: { ...finding.evidence, occurrences: count } }
|
|
54
|
+
: finding);
|
|
55
|
+
// Pass 2 — systemic defects: one rule failing across many *different*
|
|
56
|
+
// elements. htmlcs rarely does this (its repeats share a selector, which
|
|
57
|
+
// pass 1 already caught) but axe routinely does: a single mis-authored
|
|
58
|
+
// component reused across a page produced 41 separate aria-allowed-attr
|
|
59
|
+
// findings, which drowned everything else and inflated the P1 count.
|
|
60
|
+
const byRule = new Map();
|
|
61
|
+
for (const f of exact) {
|
|
62
|
+
const rule = String(f.evidence["rule_code"] ?? "");
|
|
63
|
+
const group = byRule.get(rule);
|
|
64
|
+
if (group)
|
|
65
|
+
group.push(f);
|
|
66
|
+
else
|
|
67
|
+
byRule.set(rule, [f]);
|
|
68
|
+
}
|
|
69
|
+
const out = [];
|
|
70
|
+
for (const [, group] of byRule) {
|
|
71
|
+
if (group.length <= SYSTEMIC_THRESHOLD) {
|
|
72
|
+
out.push(...group);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const worst = group.reduce((a, b) => PRIORITY_ORDER[a.priority] <= PRIORITY_ORDER[b.priority] ? a : b);
|
|
76
|
+
const elements = group.reduce((n, f) => n + Number(f.evidence["occurrences"] ?? 1), 0);
|
|
77
|
+
out.push({
|
|
78
|
+
...worst,
|
|
79
|
+
description: `${worst.description} This rule fails on ${elements} elements across the page, ` +
|
|
80
|
+
`which usually means one shared component rather than ${elements} separate defects — ` +
|
|
81
|
+
`fix the component and all of them clear.`,
|
|
82
|
+
evidence: {
|
|
83
|
+
...worst.evidence,
|
|
84
|
+
occurrences: elements,
|
|
85
|
+
distinct_elements: group.length,
|
|
86
|
+
systemic: true,
|
|
87
|
+
sample_selectors: group
|
|
88
|
+
.slice(0, 10)
|
|
89
|
+
.map((f) => String(f.evidence["selector"] ?? "")),
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return { findings: out, rawCount: findings.length };
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=a11yDedupe.js.map
|