nfunc-mcp 0.3.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 +84 -376
- package/dist/index.js +4 -0
- package/dist/index.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/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/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/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/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 +13 -3
package/README.md
CHANGED
|
@@ -2,38 +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
|
-
- [Mobile vs desktop](#mobile-vs-desktop)
|
|
26
|
-
- [Choosing an accessibility engine](#choosing-an-accessibility-engine)
|
|
27
|
-
8. [Priority system](#priority-system)
|
|
28
|
-
9. [Project layout](#project-layout)
|
|
29
|
-
10. [How to prompt](#how-to-prompt)
|
|
10
|
+
> **[Operating manual →](docs/manual.md)** — installation options, per-tool
|
|
11
|
+
> reference, output shapes, troubleshooting.
|
|
30
12
|
|
|
31
13
|
---
|
|
32
14
|
|
|
33
15
|
## What it does
|
|
34
16
|
|
|
35
|
-
`nfunc-mcp` wires four QA tools into Claude's tool-use loop:
|
|
36
|
-
|
|
37
17
|
| Capability | Tools | What it checks |
|
|
38
18
|
|---|---|---|
|
|
39
19
|
| Performance | Lighthouse | LCP, TTI, TBT, CLS, bundle size, caching |
|
|
@@ -42,399 +22,127 @@ A local MCP server that gives Claude (or any MCP client) a full non-functional Q
|
|
|
42
22
|
| Best practices | Lighthouse | HTTPS, deprecated APIs, third-party cookies |
|
|
43
23
|
| Code quality | ESLint | Dead code, undeclared vars, swallowed errors |
|
|
44
24
|
| Security patterns | Semgrep | OWASP JS/TS patterns |
|
|
25
|
+
| Real-user vitals | PageSpeed Insights + CrUX | What actual visitors experienced, versus what the lab measures |
|
|
45
26
|
|
|
46
|
-
|
|
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.
|
|
47
30
|
|
|
48
31
|
---
|
|
49
32
|
|
|
50
|
-
##
|
|
51
|
-
|
|
52
|
-
`nfunc-mcp` is a thin wrapper around four CLI tools. Install the ones you need before registering the server:
|
|
53
|
-
|
|
54
|
-
| Tool | Install | Used by |
|
|
55
|
-
|---|---|---|
|
|
56
|
-
| Lighthouse | `npm install -g lighthouse` | `run_lighthouse`, `run_qa_gate` (URL) |
|
|
57
|
-
| pa11y | `npm install -g pa11y` | `run_accessibility_check`, `run_qa_gate` (URL) |
|
|
58
|
-
| ESLint | `npm install -g eslint` | `run_static_analysis`, `run_qa_gate` (path) |
|
|
59
|
-
| Semgrep | `brew install semgrep` or `pip install semgrep` | `run_static_analysis`, `run_qa_gate` (path) |
|
|
60
|
-
|
|
61
|
-
Verify each is reachable:
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
lighthouse --version
|
|
65
|
-
pa11y --version
|
|
66
|
-
eslint --version
|
|
67
|
-
semgrep --version
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
**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.
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## Install & register
|
|
75
|
-
|
|
76
|
-
### Option A — npm (recommended, no cloning needed)
|
|
33
|
+
## Quick start
|
|
77
34
|
|
|
78
35
|
```bash
|
|
79
36
|
claude mcp add nfunc-mcp -- npx -y nfunc-mcp
|
|
80
37
|
```
|
|
81
38
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
Add to `~/.claude.json` under `mcpServers`:
|
|
87
|
-
|
|
88
|
-
```json
|
|
89
|
-
{
|
|
90
|
-
"mcpServers": {
|
|
91
|
-
"nfunc-mcp": {
|
|
92
|
-
"command": "npx",
|
|
93
|
-
"args": ["-y", "nfunc-mcp"]
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### Option C — From source (contributors / local dev)
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
git clone https://github.com/Hiddensound/NFunc_MCP.git
|
|
103
|
-
cd NFunc_MCP
|
|
104
|
-
npm install
|
|
105
|
-
npm run build
|
|
106
|
-
claude mcp add nfunc-mcp -- node /absolute/path/to/NFunc_MCP/dist/index.js
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Scripts (source only)
|
|
110
|
-
|
|
111
|
-
| Script | Purpose |
|
|
112
|
-
|---|---|
|
|
113
|
-
| `npm run build` | Compile TypeScript → `dist/` |
|
|
114
|
-
| `npm start` | Run the compiled server |
|
|
115
|
-
| `npm run dev` | Run from source with hot reload (`tsx watch`) |
|
|
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).
|
|
116
43
|
|
|
117
|
-
|
|
44
|
+
Verify with `/mcp`, then ask Claude to *"call the nfunc-mcp ping tool."*
|
|
118
45
|
|
|
119
|
-
|
|
120
|
-
2. Ask Claude: *"Call the nfunc-mcp ping tool."*
|
|
121
|
-
3. Expected response:
|
|
122
|
-
```json
|
|
123
|
-
{ "status": "ok", "timestamp": "2026-05-20T12:00:00.000Z" }
|
|
124
|
-
```
|
|
46
|
+
[Other install options →](docs/manual.md#install-and-register)
|
|
125
47
|
|
|
126
48
|
---
|
|
127
49
|
|
|
128
|
-
##
|
|
129
|
-
|
|
130
|
-
| Tool | Description | Inputs |
|
|
131
|
-
|---|---|---|
|
|
132
|
-
| `ping` | Health check — confirms the server is up | — |
|
|
133
|
-
| `run_lighthouse` | Full Lighthouse audit for a URL | `url`, `form_factor` (optional), `categories` (optional), `thresholds` (optional) |
|
|
134
|
-
| `run_accessibility_check` | pa11y WCAG audit for a URL | `url`, `runner` (optional), `standard` (optional), `ignore` (optional) |
|
|
135
|
-
| `run_static_analysis` | ESLint + Semgrep scan for a local codebase | `path` |
|
|
136
|
-
| `run_qa_gate` | All tools in parallel + correlation + HTML report | `url` and/or `path`, `form_factor` (optional), `a11y_runner` (optional) |
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
## run_qa_gate — the main tool
|
|
141
|
-
|
|
142
|
-
This is the tool to reach for in nearly every QA workflow. It replaces running tools individually and adds cross-tool intelligence on top.
|
|
143
|
-
|
|
144
|
-
### Inputs
|
|
145
|
-
|
|
146
|
-
Both inputs are **optional** — provide whichever you have. At least one is required.
|
|
50
|
+
## The tools
|
|
147
51
|
|
|
148
|
-
|
|
|
149
|
-
|---|---|---|
|
|
150
|
-
| `url` | string (URL) | You have a running page — production, staging, preview URL, or localhost. Enables Lighthouse and pa11y. |
|
|
151
|
-
| `path` | string (path) | You have a local codebase. Enables ESLint and Semgrep. |
|
|
152
|
-
| `context` | string | Optional. Free-text description of the project (e.g. `"React e-commerce checkout"`). Helps Claude interpret results. |
|
|
153
|
-
| `form_factor` | `mobile` \| `desktop` \| `both` | Optional, default `mobile`. Lighthouse device profile — see [Mobile vs desktop](#mobile-vs-desktop). |
|
|
154
|
-
| `a11y_runner` | `htmlcs` \| `axe` \| `both` | Optional, default `htmlcs`. pa11y engine — see [Choosing an accessibility engine](#choosing-an-accessibility-engine). |
|
|
155
|
-
|
|
156
|
-
**URL only** — browser-based checks, static analysis skipped:
|
|
157
|
-
```
|
|
158
|
-
QA snapshot — https://staging.myapp.com
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
**Path only** — static analysis only, browser checks skipped:
|
|
162
|
-
```
|
|
163
|
-
QA snapshot — /path/to/my-feature-branch
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
**Both** — full suite:
|
|
167
|
-
```
|
|
168
|
-
QA snapshot — https://staging.myapp.com, code at /path/to/repo
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### Release readiness tiers
|
|
172
|
-
|
|
173
|
-
The `release_readiness` field replaces a binary pass/fail with four actionable tiers:
|
|
174
|
-
|
|
175
|
-
| Value | Meaning | Condition |
|
|
176
|
-
|---|---|---|
|
|
177
|
-
| `BLOCKED` | Cannot ship — P1 issues exist | Any P1 finding |
|
|
178
|
-
| `CONDITIONAL` | Shippable with caveats — track P2s before merging | P2 findings, no P1s |
|
|
179
|
-
| `ADVISORY` | Safe to ship — P3s are tech debt to log | Only P3 findings |
|
|
180
|
-
| `CLEAR` | No issues detected | Zero findings |
|
|
181
|
-
|
|
182
|
-
### Composite score
|
|
183
|
-
|
|
184
|
-
A single `composite_score` (0–100) gives a continuous health measure across all tools.
|
|
185
|
-
|
|
186
|
-
**Formula:** Start at 100, deduct by finding severity:
|
|
187
|
-
|
|
188
|
-
| Priority | Deduction |
|
|
189
|
-
|---|---|
|
|
190
|
-
| P1 | −15 per finding |
|
|
191
|
-
| P2 | −7 per finding |
|
|
192
|
-
| P3 | −3 per finding |
|
|
193
|
-
|
|
194
|
-
Score is floored at 0. Tracks improvement over time — a score trending upward sprint-over-sprint is a healthy signal.
|
|
195
|
-
|
|
196
|
-
### Scorecard
|
|
197
|
-
|
|
198
|
-
A compact `scorecard` array gives a one-line status per tool:
|
|
199
|
-
|
|
200
|
-
```json
|
|
201
|
-
[
|
|
202
|
-
{ "tool": "Lighthouse", "gate": "WARN", "score": 75,
|
|
203
|
-
"breakdown": { "performance": 52, "accessibility": 98, "seo": 100, "best-practices": 58 } },
|
|
204
|
-
{ "tool": "pa11y", "gate": "PASS", "issues": 0 },
|
|
205
|
-
{ "tool": "ESLint / Semgrep", "gate": "SKIPPED" }
|
|
206
|
-
]
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
Gate values:
|
|
210
|
-
|
|
211
|
-
| Gate | Meaning |
|
|
52
|
+
| Tool | What it does |
|
|
212
53
|
|---|---|
|
|
213
|
-
|
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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.
|
|
222
73
|
|
|
223
|
-
|
|
224
|
-
2. **Priority-promoted** one tier (P3→P2, P2→P1)
|
|
225
|
-
3. **Annotated** with `confidence: "high"` and `confirmed_by: ["lighthouse", "pa11y"]`
|
|
226
|
-
|
|
227
|
-
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`.
|
|
228
|
-
|
|
229
|
-
**Corroboration mapping (Rule 1):**
|
|
230
|
-
|
|
231
|
-
| Lighthouse audit | pa11y technique |
|
|
232
|
-
|---|---|
|
|
233
|
-
| `color-contrast` | `.G18`, `.G145`, `.G174` |
|
|
234
|
-
| `image-alt` | `.H37`, `.H67`, `.F65` |
|
|
235
|
-
| `label` | `.H44`, `.F68`, `.H91.Input` |
|
|
236
|
-
| `link-name` | `.H30`, `.H91.A.` |
|
|
237
|
-
| `html-has-lang` | `.H57` |
|
|
238
|
-
| `button-name` | `.H91.Button` |
|
|
239
|
-
| *(and more)* | |
|
|
240
|
-
|
|
241
|
-
**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.
|
|
242
|
-
|
|
243
|
-
### HTML report
|
|
74
|
+
---
|
|
244
75
|
|
|
245
|
-
|
|
76
|
+
## How to ask for it
|
|
246
77
|
|
|
247
|
-
```json
|
|
248
|
-
{
|
|
249
|
-
"report_file": "file:///tmp/qa-report-myapp-com-1234567890.html"
|
|
250
|
-
}
|
|
251
78
|
```
|
|
79
|
+
# Full suite
|
|
80
|
+
QA snapshot — https://myapp.com, code at /path/to/repo
|
|
252
81
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
- Release readiness banner (colour-coded)
|
|
256
|
-
- Composite score gauge (SVG arc, 0–100)
|
|
257
|
-
- Per-tool scorecard table
|
|
258
|
-
- Cross-confirmed findings section (highlighted)
|
|
259
|
-
- Collapsible finding cards grouped by P1 / P2 / P3
|
|
260
|
-
- Evidence and selector for each finding
|
|
261
|
-
|
|
262
|
-
No server required — the file is fully self-contained with inline CSS.
|
|
263
|
-
|
|
264
|
-
### Output shape
|
|
265
|
-
|
|
266
|
-
```jsonc
|
|
267
|
-
{
|
|
268
|
-
"release_readiness": "BLOCKED", // BLOCKED | CONDITIONAL | ADVISORY | CLEAR
|
|
269
|
-
"composite_score": 22, // 0–100
|
|
270
|
-
"report_file": "file:///tmp/qa-report-xxx.html",
|
|
271
|
-
"scorecard": [ ... ], // per-tool gate + score/issues
|
|
272
|
-
"eslint_config_used": "project", // present only when path was supplied
|
|
273
|
-
"summary": "110 findings (101 P1, 9 P2) across 2 tools. ...",
|
|
274
|
-
"corroborated_findings": [ ... ], // cross-confirmed, confidence: "high"
|
|
275
|
-
"top_issues": [ ... ], // top 3 findings (corroborated first)
|
|
276
|
-
"all_findings": [ ... ], // all findings sorted by priority
|
|
277
|
-
"correlations_found": 1,
|
|
278
|
-
"errors": [ ... ] // present only if a tool errored
|
|
279
|
-
}
|
|
280
|
-
```
|
|
82
|
+
# URL only (Lighthouse + pa11y)
|
|
83
|
+
QA snapshot — https://myapp.com
|
|
281
84
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
```jsonc
|
|
285
|
-
{
|
|
286
|
-
"priority": "P1", // P1 | P2 | P3
|
|
287
|
-
"title": "Largest Contentful Paint",
|
|
288
|
-
"description": "Users see main content 34s after navigation...",
|
|
289
|
-
"evidence": { "audit_id": "largest-contentful-paint", "value": "34.3 s" },
|
|
290
|
-
"source_tool": "lighthouse",
|
|
291
|
-
// corroborated findings also have:
|
|
292
|
-
"confirmed_by": ["lighthouse", "pa11y"],
|
|
293
|
-
"confidence": "high"
|
|
294
|
-
}
|
|
85
|
+
# Local branch only (ESLint + Semgrep)
|
|
86
|
+
QA snapshot — /path/to/my-feature-branch
|
|
295
87
|
```
|
|
296
88
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
## Individual tools
|
|
300
|
-
|
|
301
|
-
### run_lighthouse
|
|
302
|
-
|
|
303
|
-
Runs a full Lighthouse audit against a URL.
|
|
89
|
+
These all work too:
|
|
304
90
|
|
|
305
91
|
```
|
|
306
|
-
|
|
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
|
|
307
95
|
Run Lighthouse on https://myapp.com for mobile and desktop
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
Returns: `url`, `form_factor`, `scores` (per category), `ttfb_ms`, `findings` (priority-ordered).
|
|
311
|
-
|
|
312
|
-
#### Mobile vs desktop
|
|
313
|
-
|
|
314
|
-
`form_factor` accepts `desktop` (default), `mobile`, or `both`.
|
|
315
|
-
|
|
316
|
-
**This default deliberately differs from the Lighthouse CLI's**, which is mobile: a 412×823 screen, a mid-range Android user agent, simulated slow 4G, and a **4× CPU slowdown**. That profile is much harsher and reports substantially lower performance scores for the same page, so passing `mobile` here is not a like-for-like comparison with a default run — check `form_factor` in the response before comparing two reports.
|
|
317
|
-
|
|
318
|
-
**The two are not interchangeable.** They render different DOM, so they find different defects — not just different performance numbers. Measured against one commerce category page:
|
|
319
|
-
|
|
320
|
-
| | Mobile | Desktop |
|
|
321
|
-
|---|---|---|
|
|
322
|
-
| performance | 54 | 62 |
|
|
323
|
-
| accessibility | **87** | **73** |
|
|
324
|
-
| seo | 77 | 69 |
|
|
325
|
-
|
|
326
|
-
Five accessibility audits failed on desktop that mobile never reported — `image-alt`, `aria-required-children`, `aria-required-parent`, `aria-allowed-attr`, `aria-valid-attr-value` — while three others failed only on mobile. Neither profile is a superset of the other.
|
|
327
|
-
|
|
328
|
-
With `form_factor: "both"`, the two run concurrently (so it costs little more wall time than one), `scores` is keyed by form factor, and each finding carries `affects_form_factors` and `form_factor_specific` so device-only regressions are obvious at a glance.
|
|
329
|
-
|
|
330
|
-
### run_accessibility_check
|
|
331
|
-
|
|
332
|
-
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.
|
|
333
|
-
|
|
334
|
-
```
|
|
335
|
-
Run an accessibility check on https://myapp.com
|
|
336
|
-
Run accessibility check at AAA standard on https://myapp.com
|
|
337
96
|
Run an accessibility check on https://myapp.com using the axe runner
|
|
97
|
+
Plan a PageSpeed Insights audit for https://myapp.com
|
|
338
98
|
```
|
|
339
99
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
#### Choosing an accessibility engine
|
|
343
|
-
|
|
344
|
-
`runner` accepts `htmlcs` (default), `axe`, or `both`.
|
|
345
|
-
|
|
346
|
-
| Engine | Strongest at | Severity source |
|
|
347
|
-
|---|---|---|
|
|
348
|
-
| `htmlcs` | WCAG techniques, document structure, form labelling, duplicate ids | WCAG technique class |
|
|
349
|
-
| `axe` | **ARIA** — invalid roles, missing required parent/child relationships, prohibited and unsupported attributes — and computed colour contrast | axe's own `impact` rating |
|
|
350
|
-
|
|
351
|
-
**Reach for `axe` whenever the work under test involves ARIA, a component library, or a design system.** The overlap between the engines is smaller than you would expect. On the same page:
|
|
352
|
-
|
|
353
|
-
- htmlcs found unlabelled inputs, forms with no submit mechanism, and ten duplicate ids that axe did not report.
|
|
354
|
-
- axe found `aria-allowed-attr`, `aria-prohibited-attr`, `aria-required-parent`, `aria-required-children` and `image-alt` failures that htmlcs missed entirely.
|
|
355
|
-
|
|
356
|
-
`both` runs them concurrently and merges the results. Note that an element flagged by both engines appears twice, because they emit different rule codes for the same defect — that is deliberate, since two independent engines agreeing is corroboration worth seeing.
|
|
357
|
-
|
|
358
|
-
Findings from axe carry `axe_impact` in evidence, and `needs_manual_review: true` where axe wants a human to confirm (those are demoted one priority tier — a maybe should not gate a release as hard as a certainty).
|
|
359
|
-
|
|
360
|
-
### run_static_analysis
|
|
361
|
-
|
|
362
|
-
Runs ESLint and Semgrep in parallel against a local directory. Automatically uses the project's own ESLint config if one is found; otherwise falls back to a QA-focused baseline config.
|
|
363
|
-
|
|
364
|
-
```
|
|
365
|
-
Run static analysis on /path/to/repo
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
Returns: `path`, `tools_run`, `eslint_config_used`, `issue_count`, `findings`, `warnings`.
|
|
100
|
+
`run_qa_gate` returns a `report_file` path — open it in a browser for the
|
|
101
|
+
visual dashboard.
|
|
369
102
|
|
|
370
103
|
---
|
|
371
104
|
|
|
372
|
-
##
|
|
373
|
-
|
|
374
|
-
| Priority | Meaning | Lighthouse threshold | WCAG level | ESLint / Semgrep |
|
|
375
|
-
|---|---|---|---|---|
|
|
376
|
-
| P1 | Blocker — fix before shipping | Score < 50 | Level A | Semgrep security, ESLint error |
|
|
377
|
-
| P2 | Warning — track before merging | Score 50–79 | Level AA | ESLint warning |
|
|
378
|
-
| P3 | Advisory — log as tech debt | Score 80–89 | Level AAA | — |
|
|
379
|
-
| *(suppressed)* | Passing — not reported | Score ≥ 90 | — | — |
|
|
105
|
+
## What makes it different from running the CLIs yourself
|
|
380
106
|
|
|
381
|
-
|
|
107
|
+
**Findings, not output.** Every result is a prioritised defect with QA-native
|
|
108
|
+
prose and traceable evidence, not a wall of audit JSON.
|
|
382
109
|
|
|
383
|
-
|
|
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.
|
|
384
113
|
|
|
385
|
-
|
|
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)
|
|
386
117
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
│ ├── config/
|
|
393
|
-
│ │ └── qa-mcp-baseline.eslint.config.js # Fallback ESLint config
|
|
394
|
-
│ ├── tools/
|
|
395
|
-
│ │ ├── qaGate.ts # Orchestrator — runs all tools, builds report
|
|
396
|
-
│ │ ├── lighthouse.ts # run_lighthouse tool
|
|
397
|
-
│ │ ├── accessibility.ts # run_accessibility_check tool
|
|
398
|
-
│ │ └── staticAnalysis.ts # run_static_analysis tool
|
|
399
|
-
│ ├── mappers/
|
|
400
|
-
│ │ ├── correlator.ts # Cross-tool correlation engine (Rule 1 + 2)
|
|
401
|
-
│ │ ├── defectFormatter.ts # Raw tool output → Finding objects
|
|
402
|
-
│ │ └── priorityMapper.ts # Score/severity → P1/P2/P3
|
|
403
|
-
│ └── utils/
|
|
404
|
-
│ ├── reportGenerator.ts # HTML report builder
|
|
405
|
-
│ ├── shellRunner.ts # CLI execution with timeout + error handling
|
|
406
|
-
│ ├── outputParsers.ts # JSON parsers for each tool's output
|
|
407
|
-
│ ├── eslintConfigDetector.ts # Detects project ESLint config
|
|
408
|
-
│ └── toolResponse.ts # MCP error response helpers
|
|
409
|
-
├── dist/ # Compiled output (gitignored)
|
|
410
|
-
├── package.json
|
|
411
|
-
├── tsconfig.json
|
|
412
|
-
└── README.md
|
|
413
|
-
```
|
|
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)
|
|
414
123
|
|
|
415
124
|
---
|
|
416
125
|
|
|
417
|
-
##
|
|
418
|
-
|
|
419
|
-
The shortest working prompts:
|
|
126
|
+
## Priority levels
|
|
420
127
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
128
|
+
| | Meaning |
|
|
129
|
+
|---|---|
|
|
130
|
+
| **P1** | Blocker — fix before shipping |
|
|
131
|
+
| **P2** | Warning — track before merging |
|
|
132
|
+
| **P3** | Advisory — log as tech debt |
|
|
424
133
|
|
|
425
|
-
|
|
426
|
-
|
|
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)
|
|
427
137
|
|
|
428
|
-
|
|
429
|
-
QA snapshot — /path/to/my-feature-branch
|
|
430
|
-
```
|
|
138
|
+
---
|
|
431
139
|
|
|
432
|
-
|
|
140
|
+
## Docs
|
|
433
141
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
```
|
|
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 |
|
|
439
146
|
|
|
440
|
-
|
|
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
|
@@ -7,6 +7,8 @@ import { registerLighthouseTool } from "./tools/lighthouse.js";
|
|
|
7
7
|
import { registerAccessibilityTool } from "./tools/accessibility.js";
|
|
8
8
|
import { registerStaticAnalysisTool } from "./tools/staticAnalysis.js";
|
|
9
9
|
import { registerQaGateTool } from "./tools/qaGate.js";
|
|
10
|
+
import { registerPerformanceAuditPlanTool } from "./tools/performanceAuditPlan.js";
|
|
11
|
+
import { registerPerformanceAuditTool } from "./tools/performanceAudit.js";
|
|
10
12
|
/**
|
|
11
13
|
* Identity comes from package.json rather than being written out here, so the
|
|
12
14
|
* two cannot drift — they already had, declaring qa-mcp/0.1.0 against
|
|
@@ -44,6 +46,8 @@ registerLighthouseTool(server);
|
|
|
44
46
|
registerAccessibilityTool(server);
|
|
45
47
|
registerStaticAnalysisTool(server);
|
|
46
48
|
registerQaGateTool(server);
|
|
49
|
+
registerPerformanceAuditPlanTool(server);
|
|
50
|
+
registerPerformanceAuditTool(server);
|
|
47
51
|
async function main() {
|
|
48
52
|
const transport = new StdioServerTransport();
|
|
49
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,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;
|
|
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,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lab versus field disagreement — the reason this tool exists.
|
|
3
|
+
*
|
|
4
|
+
* A Lighthouse run is a simulation on one machine under one throttling
|
|
5
|
+
* profile. CrUX is what actually happened to real people. When the two agree,
|
|
6
|
+
* confidence is high. When they disagree, the direction of the disagreement is
|
|
7
|
+
* itself the finding, and it is not available from any other tool in this MCP.
|
|
8
|
+
*
|
|
9
|
+
* The Five Below homepage is the canonical case: lab CLS 0 against field CLS
|
|
10
|
+
* 0.55, and lab LCP 11.6 s against field LCP 2.8 s — both metrics inverted, in
|
|
11
|
+
* opposite directions, on one page. A report built on the lab numbers alone
|
|
12
|
+
* led with an LCP emergency real users were not experiencing and missed a
|
|
13
|
+
* layout-shift failure that 70% of them were.
|
|
14
|
+
*/
|
|
15
|
+
import type { Priority } from "../types.js";
|
|
16
|
+
import type { CruxMetric, LabMetrics, ParsedCrux, WebVital } from "../utils/psiParser.js";
|
|
17
|
+
export type LabFieldVerdict = "confirmed" | "worse_in_field" | "worse_in_lab" | "both_pass";
|
|
18
|
+
export interface MetricComparison {
|
|
19
|
+
metric: WebVital;
|
|
20
|
+
label: string;
|
|
21
|
+
lab: number | null;
|
|
22
|
+
lab_display: string | null;
|
|
23
|
+
field_p75: number;
|
|
24
|
+
field_display: string;
|
|
25
|
+
field_source: CruxMetric["source"];
|
|
26
|
+
verdict: LabFieldVerdict;
|
|
27
|
+
note: string;
|
|
28
|
+
}
|
|
29
|
+
/** Lighthouse audit ids, for matching a lab finding back to the vital it measures. */
|
|
30
|
+
export declare const AUDIT_TO_VITAL: Record<string, WebVital>;
|
|
31
|
+
/**
|
|
32
|
+
* Compare every field metric that has a lab counterpart.
|
|
33
|
+
*
|
|
34
|
+
* `both_pass` rows are returned rather than filtered, because the comparison
|
|
35
|
+
* table in the report is a statement about coverage — a reader needs to see
|
|
36
|
+
* that LCP was checked and agreed, not infer it from an absence.
|
|
37
|
+
*/
|
|
38
|
+
export declare function compareLabField(lab: LabMetrics, field: ParsedCrux | null): MetricComparison[];
|
|
39
|
+
/**
|
|
40
|
+
* Promote, but never past a diagnostic's ceiling.
|
|
41
|
+
*
|
|
42
|
+
* `webVitalsMapper` caps FCP and TTFB at P2 because they explain a Core Web
|
|
43
|
+
* Vital rather than being one. Field confirmation makes a finding more
|
|
44
|
+
* certain, not more important, so an unguarded promotion quietly defeated that
|
|
45
|
+
* cap and put FCP at P1 above the LCP it was describing.
|
|
46
|
+
*/
|
|
47
|
+
export declare function promote(priority: Priority, vital?: WebVital): Priority;
|
|
48
|
+
export declare function demote(priority: Priority): Priority;
|
|
49
|
+
/**
|
|
50
|
+
* How a verdict should move a finding's priority.
|
|
51
|
+
*
|
|
52
|
+
* Field observation outranks simulation, so the two adjustments are not
|
|
53
|
+
* symmetric in what they mean: a promotion says "real users confirm this", a
|
|
54
|
+
* demotion says "only the simulation saw this". A demoted finding is tagged
|
|
55
|
+
* rather than dropped — the lab number is still true, it just is not evidence
|
|
56
|
+
* of user harm, and silently deleting it would hide a genuine regression
|
|
57
|
+
* signal from anyone comparing runs over time.
|
|
58
|
+
*/
|
|
59
|
+
export declare function adjustmentFor(verdict: LabFieldVerdict): {
|
|
60
|
+
direction: "promote" | "demote" | "none";
|
|
61
|
+
tag?: string;
|
|
62
|
+
};
|