feat: MCP server (analyze / explain_rule) — closes #24 - #28
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…24) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 48 minutes and 33 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughIntroduces Changes
Sequence Diagram(s)sequenceDiagram
participant Agent as MCP Agent
participant Server as createServer (McpServer)
participant AnalyzeTool as handleAnalyze
participant ExplainTool as handleExplainRule
participant CLI as analyzeProject
participant Core as buildJsonReport / explainRule
rect rgba(100, 150, 255, 0.5)
note over Agent,Core: analyze tool call
Agent->>Server: tool call: analyze(path, rules, ...)
Server->>AnalyzeTool: zod-validated AnalyzeArgs
AnalyzeTool->>CLI: analyzeProject({ cwd, rules, ignore, failOn })
CLI-->>AnalyzeTool: { results, config, version }
AnalyzeTool->>Core: buildJsonReport(results, config, { version })
Core-->>AnalyzeTool: JsonReport
AnalyzeTool-->>Agent: McpToolResult { content, structuredContent }
end
rect rgba(100, 200, 150, 0.5)
note over Agent,Core: explain_rule tool call
Agent->>Server: tool call: explain_rule(id)
Server->>ExplainTool: zod-validated ExplainRuleArgs
ExplainTool->>Core: explainRule(id)
Core-->>ExplainTool: RuleInfo | undefined
ExplainTool-->>Agent: McpToolResult { content, structuredContent }
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (4)
packages/mcp/test/analyze-tool.test.ts (2)
11-18: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winAssert the serialized JSON matches
structuredContent.This tool returns both shapes; sampling only
score/routescan miss a drift betweencontent[0].textandstructuredContent, or a regression insummary/finding metadata. Parse the text and compare it to the structured payload.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/mcp/test/analyze-tool.test.ts` around lines 11 - 18, The test for the analyze tool in handleAnalyze only validates individual fields of structuredContent but does not verify that the serialized JSON text matches the structured payload. Parse the JSON from res.content[0].text and compare it against the structuredContent object to ensure they match completely. This will catch any drift between the text representation and the structured data, and also validate that all fields including summary are properly serialized in both formats.
20-30: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winTighten the error-path checks.
These branches only look for the failing rule id /
SvelteKit, so they won’t catch regressions in the actual MCP error contract (Unknown rule id(s): … Known rule ids: …and propagatedProjectErrortext). Assert the full message shape, or at least the known-rule list/prefix.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/mcp/test/analyze-tool.test.ts` around lines 20 - 30, The error assertions in both test cases are too loose and only check for partial substrings. In the test for unknown rule ids, tighten the expectation to verify the full error message shape including "Unknown rule id(s):" prefix and the known-rule list, not just the presence of 'NOPE999'. Similarly, in the test for non-SvelteKit path, enhance the assertion to check for the complete error message format and context beyond just the substring 'SvelteKit'. This ensures regressions in the actual MCP error contract structure will be caught, not just that certain keywords appear somewhere in the response.packages/mcp/test/explain-rule-tool.test.ts (2)
5-12: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winCover the full
RuleInfopayload.
explain_ruleis supposed to return title, category, rationale, docs URL, and optional fix data, but this test only checksid,severity, anddocsUrl. Add assertions for the remaining metadata so regressions in the agent-facing contract are caught.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/mcp/test/explain-rule-tool.test.ts` around lines 5 - 12, The test case for handleExplainRule in the 'returns rule info for a known id' test is incomplete and only validates id, severity, and docsUrl. Extend the assertions to also verify all fields in the full RuleInfo payload including title, category, rationale, and optional fix data. Update the type cast for the info variable to include all these expected fields and add corresponding expect statements to validate that each field contains the expected values.
14-18: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winAssert the full unknown-id error text.
Checking only for
NOPE999leaves theUnknown rule idprefix and known-rule list untested. Tightening this to the full message will catch contract regressions inhandleExplainRule.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/mcp/test/explain-rule-tool.test.ts` around lines 14 - 18, In the test "reports an error for an unknown id", the assertion on res.content[0]!.text is too lenient by only checking that it contains 'NOPE999'. Replace the .toContain('NOPE999') check with an exact match assertion using .toBe() or .toEqual() that validates the complete error message including the "Unknown rule id" prefix and the full list of known rules returned by handleExplainRule. This will ensure the contract of the error response is fully tested and catch any regressions in the error message format or content.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/superpowers/specs/2026-06-22-mcp-server-design.md`:
- Around line 21-25: Remove the reference to `docsUrl` from the `analyze`
function description as the core report shape does not actually include this
field. The `docsUrl` should only be promised in the `explain_rule` function
specification, not in `analyze`. Update the `analyze` bullet point to only
mention the fields that are actually present in the report (per-finding
`fix`/`recommendation`, per-route and site-wide scores, and the summary), while
keeping `docsUrl` listed only in the `explain_rule` function specification where
it belongs.
- Around line 44-58: Add the language identifier `text` to the opening code
fence of the ASCII architecture diagram in the file. The bare code fence
(starting with just triple backticks) is causing markdownlint failures; changing
it to a properly labeled fence with `text` as the language identifier will keep
the ASCII diagram readable while satisfying lint requirements.
In `@packages/mcp/package.json`:
- Around line 1-51: The package.json manifest for the `@svelte-vitals/mcp` package
is missing the `devEngines.runtime.version` field that CI checks expect to read.
Add a `devEngines` object to the root level of the package.json file with a
`runtime` property containing a `version` field. Check other workspace
package.json files in the repository to determine the standard Node runtime
version value to use, then apply that same version value to match the repo's
standard.
In `@packages/mcp/README.md`:
- Around line 7-8: The `analyze` command description claims that findings
include `docs` metadata, but this field is not actually part of the structured
report returned by analyze. Remove the mention of `docs` from the `analyze`
description since that documentation metadata is only available through the
`explain_rule` command. Keep only the fields that are actually present in the
analyze report (such as `fix` and `recommendation`).
---
Nitpick comments:
In `@packages/mcp/test/analyze-tool.test.ts`:
- Around line 11-18: The test for the analyze tool in handleAnalyze only
validates individual fields of structuredContent but does not verify that the
serialized JSON text matches the structured payload. Parse the JSON from
res.content[0].text and compare it against the structuredContent object to
ensure they match completely. This will catch any drift between the text
representation and the structured data, and also validate that all fields
including summary are properly serialized in both formats.
- Around line 20-30: The error assertions in both test cases are too loose and
only check for partial substrings. In the test for unknown rule ids, tighten the
expectation to verify the full error message shape including "Unknown rule
id(s):" prefix and the known-rule list, not just the presence of 'NOPE999'.
Similarly, in the test for non-SvelteKit path, enhance the assertion to check
for the complete error message format and context beyond just the substring
'SvelteKit'. This ensures regressions in the actual MCP error contract structure
will be caught, not just that certain keywords appear somewhere in the response.
In `@packages/mcp/test/explain-rule-tool.test.ts`:
- Around line 5-12: The test case for handleExplainRule in the 'returns rule
info for a known id' test is incomplete and only validates id, severity, and
docsUrl. Extend the assertions to also verify all fields in the full RuleInfo
payload including title, category, rationale, and optional fix data. Update the
type cast for the info variable to include all these expected fields and add
corresponding expect statements to validate that each field contains the
expected values.
- Around line 14-18: In the test "reports an error for an unknown id", the
assertion on res.content[0]!.text is too lenient by only checking that it
contains 'NOPE999'. Replace the .toContain('NOPE999') check with an exact match
assertion using .toBe() or .toEqual() that validates the complete error message
including the "Unknown rule id" prefix and the full list of known rules returned
by handleExplainRule. This will ensure the contract of the error response is
fully tested and catch any regressions in the error message format or content.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d761358a-9acb-4633-93ee-3cfb66a6bf8c
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (31)
.changeset/mcp-server.mdREADME.mddocs/superpowers/plans/2026-06-22-mcp-server.mddocs/superpowers/specs/2026-06-22-mcp-server-design.mdpackage.jsonpackages/cli/src/index.tspackages/cli/test/analyze-project.test.tspackages/core/src/index.tspackages/core/src/reporter/json.tspackages/core/src/rule.tspackages/core/src/rules/index.tspackages/core/src/rules/seo/head-tag-rule.tspackages/core/src/rules/seo/project-rules.tspackages/core/src/rules/seo/seo001-title.tspackages/core/src/rules/seo/seo002-005-008.tspackages/core/test/config-apply.test.tspackages/core/test/explain-rule.test.tspackages/core/test/json-report.test.tspackages/mcp/README.mdpackages/mcp/package.jsonpackages/mcp/src/bin.tspackages/mcp/src/index.tspackages/mcp/src/server.tspackages/mcp/src/tools/analyze.tspackages/mcp/src/tools/explain-rule.tspackages/mcp/test/analyze-tool.test.tspackages/mcp/test/explain-rule-tool.test.tspackages/mcp/test/server.test.tspackages/mcp/tsconfig.jsonpackages/mcp/tsup.config.tspnpm-workspace.yaml
…tests (#24) Address CodeRabbit review on #28: - json report findings now carry docsUrl (matches docs; gives agents the rule docs link) - analyze/explain_rule tests assert full error contract and text/structured parity - explain_rule test covers the full RuleInfo payload - add handler docstrings; label the spec architecture fence as text Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds a new @svelte-vitals/mcp package that exposes svelte-vitals’ static-mode SEO analysis via an MCP stdio server, reusing the existing CLI/core pipelines (no duplicated rule logic). It also refactors core/CLI to expose structured report building and rule metadata lookup needed by MCP tool handlers.
Changes:
- Add
@svelte-vitals/mcpwithanalyzeandexplain_ruletools over stdio, plus tests and docs. - Refactor
@svelte-vitals/coreto exposebuildJsonReport,docsUrlFor, andexplainRule/RuleInfo, and promoterationale/fixontoRule. - Refactor
svelte-vitals(CLI) to extractanalyzeProject()for reuse, and re-export rules-config helpers /ProjectError.
Reviewed changes
Copilot reviewed 31 out of 32 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| README.md | Moves MCP/dev overlay into “Shipped” and adds the new package to the workspace table. |
| pnpm-workspace.yaml | Adds catalog entries for @modelcontextprotocol/sdk and zod. |
| pnpm-lock.yaml | Locks new MCP + zod dependencies and transitive graph. |
| package.json | Extends check:publish to include @svelte-vitals/mcp. |
| .changeset/mcp-server.md | Changeset for new MCP package + core/cli minor bumps. |
| docs/superpowers/specs/2026-06-22-mcp-server-design.md | Design spec documenting MCP server goals, contracts, and architecture. |
| docs/superpowers/plans/2026-06-22-mcp-server.md | Detailed implementation plan for the MCP server and supporting refactors. |
| packages/mcp/package.json | New MCP package manifest with stdio bin and dependencies. |
| packages/mcp/README.md | Usage docs for configuring the MCP server via npx. |
| packages/mcp/tsconfig.json | TypeScript config for the MCP package. |
| packages/mcp/tsup.config.ts | MCP build config (ESM-only) producing library + bin outputs. |
| packages/mcp/src/index.ts | Public MCP package entrypoint exports. |
| packages/mcp/src/bin.ts | svelte-vitals-mcp stdio binary that starts the MCP server. |
| packages/mcp/src/server.ts | MCP server wiring that registers analyze and explain_rule tools. |
| packages/mcp/src/tools/analyze.ts | Implements analyze tool handler that runs CLI pipeline + returns buildJsonReport. |
| packages/mcp/src/tools/explain-rule.ts | Implements explain_rule tool handler backed by core explainRule(). |
| packages/mcp/test/analyze-tool.test.ts | Tests analyze tool handler (happy path + error cases). |
| packages/mcp/test/explain-rule-tool.test.ts | Tests explain_rule tool handler (known/unknown ids). |
| packages/mcp/test/server.test.ts | Smoke test that the MCP server can be constructed. |
| packages/core/src/rule.ts | Adds rationale/fix to Rule and introduces docsUrlFor(). |
| packages/core/src/rules/index.ts | Adds RuleInfo and explainRule() helper for MCP/tooling use. |
| packages/core/src/rules/seo/head-tag-rule.ts | Extends headTagRule to accept/store rationale and derives docs URL via docsUrlFor. |
| packages/core/src/rules/seo/seo001-title.ts | Moves SEO001 rationale/fix into the rule and derives docs URL via docsUrlFor. |
| packages/core/src/rules/seo/seo002-005-008.ts | Adds rationale to the head-tag rules (SEO002/003/004/005/008). |
| packages/core/src/rules/seo/project-rules.ts | Adds rationale/canonical Fix constants for project rules (SEO006/007/009) + uses docsUrlFor. |
| packages/core/src/reporter/json.ts | Extracts buildJsonReport() and includes docsUrl in per-finding objects when present. |
| packages/core/src/index.ts | Re-exports buildJsonReport, JsonReport, docsUrlFor, explainRule, and RuleInfo. |
| packages/core/test/json-report.test.ts | Updates JSON report tests and adds coverage for buildJsonReport parity with formatJsonReport. |
| packages/core/test/explain-rule.test.ts | Adds tests for explainRule() and rationale/docs URL invariants. |
| packages/core/test/config-apply.test.ts | Updates test fixtures to satisfy new required Rule.rationale. |
| packages/cli/src/index.ts | Extracts analyzeProject() and refactors run() to consume it; re-exports helpers for MCP. |
| packages/cli/test/analyze-project.test.ts | Adds tests for analyzeProject() behavior and error cases. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ontract) Address Copilot review on #28: a non-ProjectError during analysis now returns exit 2 (documented 'internal error') instead of propagating as an uncaught throw — uniform with how pipeline/reporter errors were already handled. Add an intent comment and correct the 'behaviour unchanged' wording in the spec. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The analyze tool now accepts a metaComponents array, mirroring the CLI's --meta-components flag, so projects using a meta-wrapper component (e.g. <Seo />) are not falsely flagged for missing head tags via MCP. Both tools now resolve rule ids case-insensitively: explainRule (core) normalizes to the canonical uppercase id, and the analyze tool uppercases rules/ignore before validation and config building. CLI behaviour is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/mcp/test/analyze-tool.test.ts`:
- Around line 41-43: The test assertion that iterates over the ids Set can
vacuously pass when no issues are returned, since an empty Set means the for
loop never executes and no assertions are checked. Add a guard assertion before
the for loop that iterates over ids to ensure the Set is not empty, verifying
that at least one issue exists so the case-insensitive allow-list behavior is
actually being tested.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 204d4fa8-f40f-4fe5-804d-1c5c82a492b2
📒 Files selected for processing (12)
.changeset/mcp-server.mddocs/superpowers/specs/2026-06-22-mcp-server-design.mdpackages/cli/src/index.tspackages/core/src/reporter/json.tspackages/core/src/rules/index.tspackages/core/test/explain-rule.test.tspackages/core/test/json-report.test.tspackages/mcp/README.mdpackages/mcp/src/tools/analyze.tspackages/mcp/src/tools/explain-rule.tspackages/mcp/test/analyze-tool.test.tspackages/mcp/test/explain-rule-tool.test.ts
✅ Files skipped from review due to trivial changes (2)
- packages/mcp/README.md
- .changeset/mcp-server.md
🚧 Files skipped from review as they are similar to previous changes (8)
- packages/core/test/explain-rule.test.ts
- packages/core/test/json-report.test.ts
- packages/core/src/rules/index.ts
- packages/mcp/src/tools/explain-rule.ts
- packages/mcp/src/tools/analyze.ts
- docs/superpowers/specs/2026-06-22-mcp-server-design.md
- packages/cli/src/index.ts
- packages/core/src/reporter/json.ts
…pass (#24) The for-of over the rule-id set would pass even with zero issues. Assert the set is non-empty so the single-rule allow-list is actually exercised. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Ships
@svelte-vitals/mcp, a Model Context Protocol server exposing svelte-vitals' static-mode SEO analysis to AI agents — the final increment of the agent-native epic (#18). Closes #24.Two tools over stdio:
analyze— run static-mode analysis on a project path; returns the structured JSON report (per-route + site-wide scores, findings withfix/recommendation/docsUrl). Inputs mirror the CLI:path?,route?,treatDynamicAs?,rules?,ignore?,failOn?.explain_rule— given a rule id (e.g.SEO001), returns its title, category, severity, rationale, docs URL, and fix template.The MCP layer is an adapter over the existing pipeline — no rule logic is duplicated.
Changes
buildJsonReportfrom the json reporter; promoterationale/fixonto theRuleobject as the single source of truth; addexplainRule,RuleInfo,docsUrlFor; the JSON report's per-finding objects now carrydocsUrl.analyzeProject()(the analysis pipeline) so bothrun()and the MCP server reuse it; re-exportProjectError+ rules-config helpers.run()keeps its documented 0/1/2 exit-code contract, with one deliberate consistency change: a non-ProjectErrorfailure during project detection now returns exit 2 (the documented "internal error" code) instead of propagating as an uncaught throw — uniform with how pipeline/reporter errors were already handled.analyze+explain_ruletool handlers, stdio server (svelte-vitals-mcpbin), library entry.--fix, move dev overlay + MCP to Shipped), package README,check:publishextended, changeset (mcp/core/cli minor).Note on #11
--fixautofix (#11) was closed as agent-delegated during design: the only mechanically-safe fixes (robots.txt/sitemap.xml) are trivial for an agent, while the valuable ones (canonical/description/og) need the real domain & page content an agent already has. MCP is the higher-leverage path — give the agent fixable context, let it apply the fix.Validation
pnpm -r test— 194 passed (core 69, vite 31, cli 88, mcp 6)pnpm -r typecheck,pnpm lint,pnpm check:publish(publint, all 4 packages) — greeninitialize+tools/listreturn both tools;tools/call explain_ruleround-trips withstructuredContent.🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
@svelte-vitals/mcp, an MCP server for integrating Svelte Vitals analysis with AI agents viaanalyzeandexplain_ruletoolsexplainRule()API for retrieving rule metadata including documentation URLsDocumentation
Chores
analyzeProject()API@svelte-vitals/mcppackage