codequality-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@codequality-mcpcheck if my Python project is review ready"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
codequality-mcp
An MCP server that evaluates Python code against a deterministic quality bar and only says review_ready when every tool ran and found nothing serious.
review_ready means the deterministic layer found no blocker; professional is a word only
the review pass may use.
Five dimensions: functionality, security, efficiency, maintainability, design (SOLID and module structure). Deterministic analyzers (ruff, radon, bandit, vulture, mypy, plus in-process design and hygiene analyzers) produce findings and per-dimension scores. Markdown rubrics are served to the calling model for the judgement the tools cannot make.
Scoring is conservative by construction: one critical finding caps its dimension at 30, one
high at 60, one medium at 75. That medium cap sits below the professional floor of 85, so a
single medium finding from any analyzer costs the verdict, with no carve-outs. The overall
score is the minimum dimension; any analyzer that fails leaves its dimension unscored and
the verdict incomplete; targets cannot reconfigure how they are scored.
Scores also disclose how much was read: every analyzer reports the files it examined and the files it skipped, and a dimension whose weakest analyzer read less than 90% of its eligible files is left unscored rather than scored on the fraction that was read.
Install
uv sync
uv run codequality analyzers # every analyzer should report a versionRelated MCP server: LLM Evaluation Harness MCP Server
Use from the command line
uv run codequality evaluate path/to/project # markdown report, exit 0 only if review_ready
uv run codequality evaluate . --range main..HEAD --json
uv run codequality rules designExit codes: 0 review_ready, 1 needs_work, 2 incomplete, 3 error.
Use from Claude Code
Add to .mcp.json (project) or ~/.claude.json (user):
{
"mcpServers": {
"codequality": {
"command": "uv",
"args": ["run", "--directory", "/home/fearsidhe/projects/codequality_mcp", "codequality-mcp"]
}
}
}This repository's own .mcp.json uses "--directory", "." instead, because a project
.mcp.json is resolved relative to the project root; a user-level config, or any config
living outside this repository, needs the absolute path shown above.
Tools: evaluate(path, git_range?, dimensions?), explain_rule(rule_id), list_analyzers().
Resources: rubric://index, rubric://{dimension}, rules://{source}.
Prompt: review(path, git_range?) runs an evaluation and returns the report with every checklist.
Per-target configuration
Everything honoured lives in the target's pyproject.toml, and none of it can raise a score:
[project]
requires-python = ">=3.10" # its lower bound is the Python ruff and mypy are told to assume
[tool.codequality]
layers = ["domain", "application", "infrastructure"] # bottom first; imports may only point downward
exclude = ["migrations/*"] # disclosed in the report; >10% excluded = incomplete
composition_root = "myapp.wiring" # module where the object graph is assembled
io_boundary = "myapp.io" # module or package that owns process and file accessLayers are bottom first. The first name is the lowest layer and the last is the highest.
The report states the resolved order as a note, layers: bottom=domain top=infrastructure,
so a table read the wrong way round shows up in the report rather than in the findings. A
repeated name is dropped with a note and the first occurrence keeps its place.
Exclude globs are anchored at the target root and matched against the root-relative
POSIX path, one path segment at a time. A * never crosses a /, so a pattern reaches
exactly as deep as it says:
Pattern | Excludes |
| top-level |
| every |
| the files directly under |
| the whole |
| the whole |
| the same, when |
Two shorthands keep a pattern from quietly covering nothing. A pattern ending in / means
the directory and everything under it, whether or not that directory exists. A bare name
with no / and no glob characters means the same thing, but only when a directory of that
name exists at the root, and the report says how it was read: exclude pattern migrations names a directory; read as migrations/**. A name that is not a directory still means that
file, so exclude = ["conftest.py"] excludes the top-level conftest.py and nothing else.
An empty pattern, or an entry that is not a string, is dropped and disclosed as a note
rather than read as a pattern that matches everything. A well-formed pattern that covered
none of the discovered files is disclosed too: exclude pattern nope/* matched no files.
Between the two, an exclude setting can never fail silently.
The Python version comes from requires-python. Its lower bound (>=3.9, >=3.9,<4,
~=3.10 and ==3.11.* all state one) becomes ruff's --target-version and mypy's
--python-version, so both judge the code as the dialect it claims to support. A specifier
with no lower bound leaves the version unknown and the default 3.12 is used. Either way the
report says which version ran and where it came from: python version: 3.10 (from requires-python) or python version: 3.12 (default). A version outside the range a tool
accepts (ruff py37 to py313, mypy 3.8 to 3.13) is clamped to the nearest one it knows,
with a note naming both.
Suppression comments
A comment is configuration a target controls, so where a tool can be told to disregard it, it
is. ruff runs with --ignore-noqa and bandit with --ignore-nosec, so a line carrying
# noqa or # nosec still reports.
Two markers still work, because their tools offer no flag to disarm them. # noqa hides a
line from vulture, and # type: ignore hides it from mypy. Both are counted, and the
hygiene analyzer reports one note across the discovered files, test files included:
suppression markers: noqa 3 (ignored by ruff, honoured by vulture),
type: ignore 1 (honoured by mypy), nosec 2 (ignored by bandit)The note is omitted when every count is zero. Read a non-zero noqa or type: ignore count
as a prompt to go and look: the score is not evidence about the lines those markers cover.
Known limitations
Two test files with the same name in sibling directories that have no __init__.py (say
tests/unit/test_x.py and tests/integration/test_x.py) make mypy exit with a
duplicate-module error. Functionality is then unscored and the verdict is incomplete. The
same layout breaks pytest's default import mode, so the fix is to add __init__.py files or
to give the modules distinct names.
Target configuration is neutralised, but host configuration is not: radon reads
~/.radon.cfg (and $RADONCFG) unconditionally, from the account the server runs as. Keep
the server's host free of both, or its complexity and maintainability results can be changed
for every target at once.
Development
uv run pytest -q && uv run ruff check . && uv run mypy
uv run codequality evaluate . # the repo must earn review_ready; tests/calibration/test_dogfood.py enforces itDesign: docs/superpowers/specs/2026-09-11-codequality-mcp-design.md.
Plan: docs/superpowers/plans/2026-09-11-codequality-mcp.md.
Available Tools
3 toolsevaluateA
Evaluate a file or directory against the deterministic quality bar.
Returns findings, per-dimension scores, hotspots, and a verdict. With git_range (for example main..HEAD) findings on touched lines are marked in_scope; scores always cover the whole target. Restricting dimensions leaves the others unscored, so the verdict is incomplete.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | ||
| git_range | No | ||
| dimensions | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| loc | Yes | |
| notes | No | Report-level disclosures that belong to no single dimension |
| scores | Yes | |
| target | Yes | |
| overall | Yes | |
| verdict | Yes | |
| findings | Yes | |
| hotspots | Yes | |
| language | Yes | |
| git_range | Yes | |
| generated_at | No | |
| scope_summary | Yes | |
| weighted_mean | Yes | |
| excluded_files | Yes | |
| files_analyzed | Yes | |
| analyzer_status | Yes | |
| verdict_reasons | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does disclose meaningful behavior: git_range marks findings in_scope while scores still cover the whole target, and partial dimension selection produces an incomplete verdict. It omits any statement of read-only/safety profile or cost, which keeps it short of a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, then two tightly focused sentences on parameter behavior; no filler. The trailing clause 'the verdict is incomplete' is slightly clipped but still informative.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be spelled out, and the description still names findings, scores, hotspots and verdict. Combined with the parameter-effect notes, an agent has enough to invoke it correctly, though the absence of any annotation-level safety context leaves a small gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate and largely does: git_range gets a concrete format example ('main..HEAD') and its in_scope effect, and dimensions gets its scoring consequence explained. Only path is left self-evident rather than documented.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Evaluate a file or directory') plus the object of evaluation ('the deterministic quality bar'). It clearly reads as the primary analysis action among siblings explain_rule and list_analyzers, but never explicitly contrasts itself with them.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives real operational guidance: git_range scopes findings to touched lines, and restricting dimensions leaves the rest unscored. However, it never states when to reach for this tool versus explain_rule or list_analyzers, so the usage boundary is implied rather than declared.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
explain_ruleB
What a rule checks, why it matters, and how to fix it (e.g. design:CYCLE, ruff:F821).
| Name | Required | Description | Default |
|---|---|---|---|
| rule_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| fix | Yes | |
| title | Yes | |
| source | Yes | |
| rule_id | Yes | |
| dimension | Yes | |
| rationale | Yes | |
| references | No | |
| max_severity | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full behavioral burden. It describes the shape of the returned explanation but says nothing about side effects, permission needs, or what happens for an unknown/misspelled rule ID, and the return content is largely already covered by the existing output schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single efficient fragment that front-loads the output contract (what it checks, why it matters, how to fix) and tucks examples at the end. No wasted words, though it is a sentence fragment rather than a fully formed statement.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter lookup tool with an output schema already documenting return values, this covers the essentials: purpose, output content, and input format by example. The main gap is routing to list_analyzers for valid rule IDs, which a slightly fuller description would include.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate for the single rule_id parameter. The examples 'design:CYCLE' and 'ruff:F821' usefully convey the expected 'analyzer:code' format, but nothing explains where to obtain valid IDs, whether the prefix is required, or how invalid input is handled.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies the resource (a lint/analysis rule) and its output substance: what the rule checks, why it matters, and remediation guidance, with example rule IDs like design:CYCLE and ruff:F821. It stops short of a clean verb+resource statement and never distinguishes itself from siblings evaluate or list_analyzers.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit when-to-use guidance and no mention of alternatives, even though list_analyzers is the obvious place to discover rule IDs like the examples shown. The agent must infer that this is a lookup companion to evaluate rather than a fixer or lister.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_analyzersB
Health check: each analyzer tool version and availability.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It implies a safe read (listing versions/availability) but never states that it is read-only, whether it requires auth, or how costly it is to call. 'Health check' hints at a diagnostic purpose but adds little behavioral substance.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single compact fragment with no waste. It is front-loaded and appropriately sized, though the telegraphic phrasing ('each analyzer tool version') is slightly awkward as prose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return-value details need not be described. For a zero-param listing tool this is nearly adequate, but with no annotations the description should state that the call is read-only and safe, which it omits.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters and the schema is fully documented, so there are no parameter semantics for the description to explain. Baseline 4 applies for a parameterless tool.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific resource and its content: analyzer tool versions and availability. The 'Health check' framing is vague, but the second clause disambiguates it as a listing/diagnostic read. It is distinguishable from siblings evaluate and explain_rule, which are action/explanation tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit when-to-use or when-not-to-use guidance. The 'Health check' label weakly implies a diagnostic use case, but the agent is not told under what conditions to call this instead of evaluate or explain_rule.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v0.1.0- First observed
evaluate - First observed
explain_rule - First observed
list_analyzers
TDQS
Scored across 3 tools
Each tool has a clearly distinct purpose: evaluate runs the quality analysis, explain_rule provides details on a specific rule, and list_analyzers checks analyzer health. No overlap or ambiguity in selection.
Two tools follow a verb_noun pattern (explain_rule, list_analyzers), while evaluate is a bare verb. This minor deviation is still readable and consistent in snake_case, but prevents a perfect score.
Three tools are well-scoped for a focused code-quality analysis server; each tool (evaluate, explain_rule, list_analyzers) earns its place without redundancy.
The surface covers running evaluations, explaining rules, and checking analyzer availability, but lacks a way to list all rules or dimensions directly. This minor gap is workable via evaluate findings, but agents cannot discover rules upfront.
Maintenance
Related MCP Connectors
Statically audits MCP tool surfaces for token cost, schema quality, and design issues.
Prompt evals over MCP: run a prompt on your dataset, score each output 1-5 with an LLM judge.
Free MCP tools: the only MCP linter, health checks, cost estimation, and trust evaluation.
Evidence-backed architecture-quality analysis for Python agent applications.
Related MCP Servers
AlicenseNot gradedqualityDmaintenanceEnables running LLM evaluations, experiments, and custom evaluators through a standardized MCP interface.16Apache 2.0- AlicenseNot gradedqualityCmaintenanceEvaluates RAG outputs on faithfulness, answer relevancy, and context precision using an LLM-as-a-Judge backend. Exposes tools for running evaluations, scoring individual samples, and checking thresholds, enabling CI gating and on-demand assessment via MCP.MIT
- AlicenseAqualityDmaintenanceProvides 28 MCP tools across 16 analysis engines for comprehensive Python code quality assessment, including complexity scoring, security scanning, dead code detection, dependency auditing, and test quality analysis.28MIT
- AlicenseNot gradedqualityBmaintenanceEnables MCP clients to run security and code review on pull requests and diffs, exposing review_pr and review_diff capabilities with local-first analyzers and LLM explanations.2MIT