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.
Files changed (65) hide show
  1. package/README.md +85 -335
  2. package/dist/index.js +27 -4
  3. package/dist/index.js.map +1 -1
  4. package/dist/mappers/a11yDedupe.d.ts +25 -0
  5. package/dist/mappers/a11yDedupe.js +95 -0
  6. package/dist/mappers/a11yDedupe.js.map +1 -0
  7. package/dist/mappers/compositeScore.d.ts +23 -0
  8. package/dist/mappers/compositeScore.js +107 -0
  9. package/dist/mappers/compositeScore.js.map +1 -0
  10. package/dist/mappers/correlator.js +94 -33
  11. package/dist/mappers/correlator.js.map +1 -1
  12. package/dist/mappers/defectFormatter.js +129 -15
  13. package/dist/mappers/defectFormatter.js.map +1 -1
  14. package/dist/mappers/labFieldComparator.d.ts +62 -0
  15. package/dist/mappers/labFieldComparator.js +134 -0
  16. package/dist/mappers/labFieldComparator.js.map +1 -0
  17. package/dist/mappers/priorityMapper.d.ts +38 -0
  18. package/dist/mappers/priorityMapper.js +126 -0
  19. package/dist/mappers/priorityMapper.js.map +1 -1
  20. package/dist/mappers/psiAggregator.d.ts +130 -0
  21. package/dist/mappers/psiAggregator.js +293 -0
  22. package/dist/mappers/psiAggregator.js.map +1 -0
  23. package/dist/mappers/webVitalsMapper.d.ts +52 -0
  24. package/dist/mappers/webVitalsMapper.js +131 -0
  25. package/dist/mappers/webVitalsMapper.js.map +1 -0
  26. package/dist/tools/accessibility.js +72 -31
  27. package/dist/tools/accessibility.js.map +1 -1
  28. package/dist/tools/lighthouse.d.ts +14 -0
  29. package/dist/tools/lighthouse.js +134 -34
  30. package/dist/tools/lighthouse.js.map +1 -1
  31. package/dist/tools/performanceAudit.d.ts +2 -0
  32. package/dist/tools/performanceAudit.js +446 -0
  33. package/dist/tools/performanceAudit.js.map +1 -0
  34. package/dist/tools/performanceAuditPlan.d.ts +2 -0
  35. package/dist/tools/performanceAuditPlan.js +438 -0
  36. package/dist/tools/performanceAuditPlan.js.map +1 -0
  37. package/dist/tools/qaGate.js +109 -34
  38. package/dist/tools/qaGate.js.map +1 -1
  39. package/dist/utils/csvReader.d.ts +20 -0
  40. package/dist/utils/csvReader.js +172 -0
  41. package/dist/utils/csvReader.js.map +1 -0
  42. package/dist/utils/httpClient.d.ts +84 -0
  43. package/dist/utils/httpClient.js +171 -0
  44. package/dist/utils/httpClient.js.map +1 -0
  45. package/dist/utils/outputParsers.d.ts +25 -0
  46. package/dist/utils/outputParsers.js +42 -0
  47. package/dist/utils/outputParsers.js.map +1 -1
  48. package/dist/utils/psiAuth.d.ts +26 -0
  49. package/dist/utils/psiAuth.js +36 -0
  50. package/dist/utils/psiAuth.js.map +1 -0
  51. package/dist/utils/psiParser.d.ts +124 -0
  52. package/dist/utils/psiParser.js +200 -0
  53. package/dist/utils/psiParser.js.map +1 -0
  54. package/dist/utils/publicUrl.d.ts +17 -0
  55. package/dist/utils/publicUrl.js +115 -0
  56. package/dist/utils/publicUrl.js.map +1 -0
  57. package/dist/utils/sitemapReader.d.ts +27 -0
  58. package/dist/utils/sitemapReader.js +272 -0
  59. package/dist/utils/sitemapReader.js.map +1 -0
  60. package/dist/utils/urlClassifier.d.ts +45 -0
  61. package/dist/utils/urlClassifier.js +267 -0
  62. package/dist/utils/urlClassifier.js.map +1 -0
  63. package/docs/manual.md +558 -0
  64. package/docs/psi-report-spec.md +174 -0
  65. package/package.json +14 -4
package/README.md CHANGED
@@ -2,36 +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
- 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
- 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.
45
30
 
46
31
  ---
47
32
 
48
- ## Prerequisites
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
- That's it. `npx` downloads and runs the server on your machine automatically. No repo clone, no build step.
81
-
82
- ### Option B Manual config (npm)
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
- ## Tools
44
+ Verify with `/mcp`, then ask Claude to *"call the nfunc-mcp ping tool."*
127
45
 
128
- | Tool | Description | Inputs |
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
- ## run_qa_gate — the main tool
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
- Both inputs are **optional** provide whichever you have. At least one is required.
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
- | P1 | −15 per finding |
187
- | P2 | −7 per finding |
188
- | P3 | −3 per finding |
189
-
190
- Score is floored at 0. Tracks improvement over time a score trending upward sprint-over-sprint is a healthy signal.
191
-
192
- ### Scorecard
193
-
194
- A compact `scorecard` array gives a one-line status per tool:
195
-
196
- ```json
197
- [
198
- { "tool": "Lighthouse", "gate": "WARN", "score": 75,
199
- "breakdown": { "performance": 52, "accessibility": 98, "seo": 100, "best-practices": 58 } },
200
- { "tool": "pa11y", "gate": "PASS", "issues": 0 },
201
- { "tool": "ESLint / Semgrep", "gate": "SKIPPED" }
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 auditfinds 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
- ## Individual tools
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
- Returns: `url`, `scores` (per category), `ttfb_ms`, `findings` (priority-ordered).
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
- 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.
89
+ These all work too:
321
90
 
322
91
  ```
323
- Run static analysis on /path/to/repo
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
- 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.
327
102
 
328
103
  ---
329
104
 
330
- ## Priority system
105
+ ## What makes it different from running the CLIs yourself
331
106
 
332
- | Priority | Meaning | Lighthouse threshold | WCAG level | ESLint / Semgrep |
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
- Corroborated findings are promoted one tier above where either tool would place them individually.
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
- ## 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)
344
117
 
345
- ```
346
- qa-mcp/
347
- ├── src/
348
- │ ├── index.ts # MCP server bootstrap + tool registration
349
- │ ├── types.ts # Shared types (Finding, Priority)
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
- ## How to prompt
126
+ ## Priority levels
376
127
 
377
- The shortest working prompts:
378
-
379
- ```
380
- # Full suite
381
- 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 |
382
133
 
383
- # URL only (Lighthouse + pa11y)
384
- 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)
385
137
 
386
- # Local branch only (ESLint + Semgrep)
387
- QA snapshot — /path/to/my-feature-branch
388
- ```
138
+ ---
389
139
 
390
- Alternative trigger phrases (all invoke `run_qa_gate`):
140
+ ## Docs
391
141
 
392
- ```
393
- Health check on https://myapp.com
394
- Is https://myapp.com ready to ship? Code at /path/to/repo
395
- Any red flags? /path/to/repo
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
- 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
@@ -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
- const server = new McpServer({
9
- name: "qa-mcp",
10
- version: "0.1.0",
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;AAEvD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,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,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