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.
Files changed (42) hide show
  1. package/README.md +84 -376
  2. package/dist/index.js +4 -0
  3. package/dist/index.js.map +1 -1
  4. package/dist/mappers/labFieldComparator.d.ts +62 -0
  5. package/dist/mappers/labFieldComparator.js +134 -0
  6. package/dist/mappers/labFieldComparator.js.map +1 -0
  7. package/dist/mappers/psiAggregator.d.ts +130 -0
  8. package/dist/mappers/psiAggregator.js +293 -0
  9. package/dist/mappers/psiAggregator.js.map +1 -0
  10. package/dist/mappers/webVitalsMapper.d.ts +52 -0
  11. package/dist/mappers/webVitalsMapper.js +131 -0
  12. package/dist/mappers/webVitalsMapper.js.map +1 -0
  13. package/dist/tools/performanceAudit.d.ts +2 -0
  14. package/dist/tools/performanceAudit.js +446 -0
  15. package/dist/tools/performanceAudit.js.map +1 -0
  16. package/dist/tools/performanceAuditPlan.d.ts +2 -0
  17. package/dist/tools/performanceAuditPlan.js +438 -0
  18. package/dist/tools/performanceAuditPlan.js.map +1 -0
  19. package/dist/utils/csvReader.d.ts +20 -0
  20. package/dist/utils/csvReader.js +172 -0
  21. package/dist/utils/csvReader.js.map +1 -0
  22. package/dist/utils/httpClient.d.ts +84 -0
  23. package/dist/utils/httpClient.js +171 -0
  24. package/dist/utils/httpClient.js.map +1 -0
  25. package/dist/utils/psiAuth.d.ts +26 -0
  26. package/dist/utils/psiAuth.js +36 -0
  27. package/dist/utils/psiAuth.js.map +1 -0
  28. package/dist/utils/psiParser.d.ts +124 -0
  29. package/dist/utils/psiParser.js +200 -0
  30. package/dist/utils/psiParser.js.map +1 -0
  31. package/dist/utils/publicUrl.d.ts +17 -0
  32. package/dist/utils/publicUrl.js +115 -0
  33. package/dist/utils/publicUrl.js.map +1 -0
  34. package/dist/utils/sitemapReader.d.ts +27 -0
  35. package/dist/utils/sitemapReader.js +272 -0
  36. package/dist/utils/sitemapReader.js.map +1 -0
  37. package/dist/utils/urlClassifier.d.ts +45 -0
  38. package/dist/utils/urlClassifier.js +267 -0
  39. package/dist/utils/urlClassifier.js.map +1 -0
  40. package/docs/manual.md +558 -0
  41. package/docs/psi-report-spec.md +174 -0
  42. package/package.json +13 -3
package/README.md CHANGED
@@ -2,38 +2,18 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/nfunc-mcp)](https://www.npmjs.com/package/nfunc-mcp)
4
4
 
5
- A local MCP server that gives Claude (or any MCP client) a full non-functional QA toolkit. Run Lighthouse, WCAG accessibility checks, and static code analysis — individually or all at once — and get back structured, prioritised findings you can act on immediately.
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
- The `run_qa_gate` orchestrator runs all of them in parallel, cross-correlates findings across tools, and produces a single structured report with a release readiness verdict, a composite health score, a per-tool scorecard, and a browser-openable HTML report.
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
- ## Prerequisites
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
- That's it. `npx` downloads and runs the server on your machine automatically. No repo clone, no build step.
83
-
84
- ### Option B Manual config (npm)
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
- ### Verify the connection
44
+ Verify with `/mcp`, then ask Claude to *"call the nfunc-mcp ping tool."*
118
45
 
119
- 1. Run `/mcp` in Claude Code — `nfunc-mcp` should show as `connected`.
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
- ## Tools
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
- | Input | Type | When to provide |
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
- | `PASS` | No issues at this tool's threshold |
214
- | `WARN` | Issues exist but below the FAIL threshold |
215
- | `FAIL` | Issues at P1 level (or Lighthouse avg < 50) |
216
- | `SKIPPED` | Input not provided (URL or path not supplied) |
217
- | `UNAVAILABLE` | Tool was invoked but is not installed or errored |
218
-
219
- ### Cross-tool corroboration
220
-
221
- When Lighthouse and pa11y independently flag the same accessibility gap, those findings are:
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
- 1. **Merged** into a single entry in `corroborated_findings`
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
- Every `run_qa_gate` call automatically writes a self-contained HTML file to `/tmp` and returns its path as `report_file`:
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
- Open the path in any browser to get:
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
- Each finding:
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
- Run Lighthouse on https://myapp.com
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
- Returns: `url`, `standard`, `runners`, `violation_count`, `raw_violation_count`, `findings`.
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
- ## Priority system
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
- Corroborated findings are promoted one tier above where either tool would place them individually.
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
- ## Project layout
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
- qa-mcp/
389
- ├── src/
390
- │ ├── index.ts # MCP server bootstrap + tool registration
391
- │ ├── types.ts # Shared types (Finding, Priority)
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
- ## How to prompt
418
-
419
- The shortest working prompts:
126
+ ## Priority levels
420
127
 
421
- ```
422
- # Full suite
423
- QA snapshothttps://myapp.com, code at /path/to/repo
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
- # URL only (Lighthouse + pa11y)
426
- QA snapshot https://myapp.com
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
- # Local branch only (ESLint + Semgrep)
429
- QA snapshot — /path/to/my-feature-branch
430
- ```
138
+ ---
431
139
 
432
- Alternative trigger phrases (all invoke `run_qa_gate`):
140
+ ## Docs
433
141
 
434
- ```
435
- Health check on https://myapp.com
436
- Is https://myapp.com ready to ship? Code at /path/to/repo
437
- Any red flags? /path/to/repo
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
- After the run, Claude will surface the `report_file` path. Open it in your browser for the full visual dashboard.
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;AAEvD;;;;;;;;;;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;AAE3B,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"}
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
+ };