Skip to main content
Glama
README.md
<p align="center">
  <img src="assets/banner.svg" alt="CodeCheck MCP" width="100%">
</p>

<h1 align="center">CodeCheck MCP</h1>

<p align="center">
  <a href="#installation">Install</a> ·
  <a href="#the-fix-loop-build--audit--fix--re-audit">Fix loop</a> ·
  <a href="#tools">Tools</a> ·
  <a href="llms-install.md">For AI agents</a> ·
  <a href="CONTRIBUTING.md">Contributing</a> ·
  <a href="SECURITY.md">Security</a>
</p>

<p align="center">
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-2ea44f" alt="License: MIT"></a>
  <img src="https://img.shields.io/badge/python-3.10%2B-3776ab" alt="Python 3.10+">
  <img src="https://img.shields.io/badge/MCP-server-8a2be2" alt="MCP server">
  <a href="README.md"><img src="https://img.shields.io/badge/lang-English-0d9488" alt="English"></a>
  <a href="README.ru.md"><img src="https://img.shields.io/badge/lang-%D0%A0%D1%83%D1%81%D1%81%D0%BA%D0%B8%D0%B9-d64545" alt="Русский"></a>
</p>

An MCP server that **audits a finished web project in a real browser** and writes a report another AI can fix code
from. It opens the site with Playwright, crawls its pages, clicks the buttons, watches the console and the network,
measures layout at several screen widths, runs axe-core for accessibility, checks SEO, images, fonts, performance
and basic security, and gives every problem a stable ID, a CSS selector, evidence and a screenshot.

Run it again after the fix and it tells you what was fixed, what is new and what is still there.

Tested on Windows and Python 3.14. Other operating systems and Python versions have not been tested.

| | |
|---|---|
| **A real browser** | Chromium via Playwright: real clicks, real layout, real network. Nothing is guessed from the source code. |
| **Made for a fix loop** | `current.json` with one record per problem: rule, page, selector or URL, evidence and a fingerprint that stays the same between runs. |
| **Evidence, not opinions** | Every finding says what was measured. Critical findings and warnings come with a screenshot, the element outlined in red. |
| **Prioritized issues** | Findings are grouped by cause: one root cause, its related issues, affected pages, impact 1-10 and a fix. Inferred links are marked as likely. |
| **Regression view** | Each run is compared with the previous one: ✅ Fixed, 🔴 New, ⚠️ Unchanged. |
| **Safe by design** | Read-only for your project, isolated browser context, navigation to other domains blocked, only passive security checks. |

## Installation

You need Python 3.10+ and an internet connection (Chromium is about 150 MB).

```bash
# 1. Create an environment and install straight from GitHub
python -m venv codecheck-env
codecheck-env/Scripts/pip install git+https://github.com/aleks-fw/CodeCheck-MCP.git      # Windows
# codecheck-env/bin/pip install git+https://github.com/aleks-fw/CodeCheck-MCP.git        # macOS / Linux

# 2. Download the browser for Playwright (once)
codecheck-env/Scripts/python -m playwright install chromium
```

### Connect to Claude Code

```bash
claude mcp add --scope user codecheck -- "<path>/codecheck-env/Scripts/codecheck-mcp"
```

### Connect to other clients (Claude Desktop, Cursor, ...)

```json
{
  "mcpServers": {
    "codecheck": {
      "command": "<path>/codecheck-env/Scripts/codecheck-mcp",
      "env": { "CODECHECK_REPORTS_DIR": "<where reports go>" }
    }
  }
}
```

`<path>` is the absolute path to the folder where you created the environment. `CODECHECK_REPORTS_DIR` is optional
(default `~/codecheck-reports`). Restart the client and `codecheck` appears in its MCP list.

### If you are an AI agent

Install by following [llms-install.md](llms-install.md).

## The fix loop: Build → Audit → Fix → Re-audit

1. **Build** the site (or start it locally).
2. **Audit.** Ask the agent: *"Run audit_project on http://localhost:5173 with criticalSelectors ["#checkout"]"*.
   You get counts by severity, the critical findings, the top prioritized issue groups and the paths to `report.md` and `current.json`.
3. **Fix.** *"Read current.json and fix the critical findings and warnings. Use the selector, url and evidence of
   each finding to locate the code."* Each finding names the rule, the page, the element or resource and what was
   observed, so the agent does not have to reproduce the bug first.
4. **Re-audit.** Run `audit_project` again with the same `url`. The answer starts with
   `Since the previous run: ✅ Fixed 3 · 🔴 New 0 · ⚠️ Unchanged 12`; repeat until nothing critical is left.

A finding only counts as fixed if the new run checked the same category, page and viewport; otherwise it is listed
as "not rechecked", so a narrower run never reports false fixes.

## Tools

| Tool | Parameters | What it does |
|---|---|---|
| `audit_project` | `url`, `maxPages=10`, `viewports=[375, 768, 1280]`, `checks=all`, `criticalSelectors=[]`, `outputDir` | Full audit for the fix loop, see below |
| `compare_reports` | `previous`, `current` (paths to JSON reports) | Fixed / New / Unchanged between any two `audit_project` reports |
| `full_qa` | `target`, `max_pages=10` | Quick QA with the checks below, report in Russian |
| `test_interactions` | `target`, `max_pages` | Dead buttons, disabled controls that react, covered buttons, forms, double submit, broken anchors |
| `test_layout` | `target`, `max_pages`, `widths=[320, 375, 768, 1024, 1440]` | Overlapping and clipped text, horizontal scroll, contrast (also on images), tap targets under 44 px |
| `test_fonts` | `target`, `max_pages` | Font families, outlier fonts, failed web fonts, size sprawl, heading hierarchy |
| `test_images` | `target`, `max_pages` | Broken, stretched, blurry, heavy images, missing `alt` |
| `quick_security` | `target`, `max_pages=3` | Secrets in files and git history, `.env` in git, headers, cookies, mixed content |

`url` / `target` is a URL or a path to a project folder or file; for a folder a temporary local server is started.

### audit_project

| Parameter | Default | Meaning |
|---|---|---|
| `url` | required | Site URL or project folder / file |
| `maxPages` | `10` | Pages to crawl, same origin only |
| `viewports` | `[375, 768, 1280]` | Screen widths in px (heights 812, 1024, 800) |
| `checks` | all | Any of `interaction`, `layout`, `images`, `fonts`, `console`, `accessibility`, `seo`, `performance`, `security`, `network` |
| `criticalSelectors` | `[]` | Selectors of key actions (e.g. `#checkout`); if one does nothing on click, the finding is critical |
| `outputDir` | `<CODECHECK_REPORTS_DIR>/<project>` | Where `current.json`, `previous.json`, `report.md` and `screenshots/` go |

Example call:

```json
{
  "url": "http://127.0.0.1:8765/",
  "criticalSelectors": ["#checkout"],
  "viewports": [375, 768, 1280]
}
```

Answer (real run on [examples/demo-shop](examples/demo-shop) with `criticalSelectors: ["#checkout", "#place-order"]`, second run after adding a missing `<title>`):

```text
CodeCheck audit of http://127.0.0.1:8765/: 9 page(s), viewports 375x812, 768x1024, 1280x800.
Critical: 8 · Warnings: 5 · Notices: 31

Critical:
- CC-001 `interaction/no-effect` on /buttons.html: Clicking #checkout does nothing
- CC-002 `interaction/action-request-failed` on /cart.html: Clicking #place-order sends POST /api/order, which answers HTTP 500
- CC-003 `interaction/action-request-failed` on /checkout.html: Clicking #place-order sends POST /api/order, which answers HTTP 500
- CC-004 `console/uncaught-exception` on /errors.html: Uncaught exception: ReferenceError: cartItems is not defined
- CC-005 `accessibility/image-alt` on /media.html: Images must have alternative text
- CC-006 `network/api-5xx` on /api.html: API request returned HTTP 500
- CC-007 `network/api-5xx` on /checkout.html: API request returned HTTP 500
- CC-008 `network/script-failed` on /media.html: JavaScript file did not load

Top issues (5 of 17 groups):
- G-01 🔴 CRITICAL · impact 10/10 · API request returned HTTP 500 (/api/order) · pages: /cart.html, /checkout.html · 3 related
- G-02 🔴 CRITICAL · impact 10/10 · Clicking #checkout does nothing · pages: /buttons.html
- G-03 🔴 CRITICAL · impact 9/10 · Uncaught exception: ReferenceError: cartItems is not defined · pages: /errors.html
- G-04 🔴 CRITICAL · impact 9/10 · API request returned HTTP 500 (/api/orders) · pages: /api.html
- G-05 🔴 CRITICAL · impact 9/10 · JavaScript file did not load (/js/missing.js) · pages: /media.html

Since the previous run: ✅ Fixed 1 · 🔴 New 0 · ⚠️ Unchanged 44
Fixed:
  CC-013 `seo/missing-title` on `/notitle.html`: Page has no title

Report: .../report.md
JSON: .../current.json
Screenshots: .../screenshots
```

The top group in the same `report.md`:

```markdown
## Prioritized Issues

### 🔴 CRITICAL — API request returned HTTP 500 (/api/order)

- **Impact:** 10/10 (severity 8, breaks a function or exposes data +1, on several pages +1,
  key action (criticalSelectors) +1, causes other issues +1)
- **Category:** functionality
- **Affected pages:** `/cart.html`, `/checkout.html`
- **Root cause:** GET http://127.0.0.1:8765/api/order answered HTTP 500 on /checkout.html. (CC-007)
- **Related issues:**
  - CC-002 `interaction/action-request-failed` on `/cart.html`: Clicking #place-order sends POST /api/order,
    which answers HTTP 500 (confirmed: the click sends a request to the same failing endpoint /api/order)
  - CC-003 `interaction/action-request-failed` on `/checkout.html`: Clicking #place-order sends POST /api/order,
    which answers HTTP 500 (confirmed: the click sends a request to the same failing endpoint /api/order)
  - CC-010 `console/error` on `/checkout.html`: console.error: Could not load the order summary: GET /api/order
    returned 500 (confirmed: the error text mentions /api/order)
- **Recommendation:** The API endpoint in evidence fails on the server: check its server logs, fix the handler,
  and make the page show an error state when the request fails.
- **Evidence:** screenshots: [screenshots/CC-002.png](screenshots/CC-002.png), ... · urls:
  `http://127.0.0.1:8765/api/order` · selectors: `#place-order` · requests:
  `POST http://127.0.0.1:8765/api/order → 500`, `GET http://127.0.0.1:8765/api/order → 500` · console:
  `Could not load the order summary: GET /api/order returned 500`
```

Below it come the Summary, every finding by severity and category (with its own screenshot and evidence), the list of tested pages and one recommendation per rule that was found.

### Prioritized Issues

After all checks, findings are merged into groups, one per cause, and sorted by severity, then impact, then the
number of affected pages. Links between findings come only from the findings themselves:

| Link | Based on | Marked |
|---|---|---|
| The same problem on several pages | same resource URL, or same rule and selector, or same message | same group |
| A failing request and a click that sends a request to the same endpoint | same URL path | confirmed |
| A failing request and a console error that names its URL | the URL in the error text | confirmed |
| A heavy file and the page weight | the file is in the page's largest downloads | confirmed |
| A missing script and "is not defined" errors or dead buttons on the page | same page | likely |
| An uncaught exception and a dead button on the page | same page | likely |
| A failing request and a console error about a request that does not name the URL | same page | likely |
| A missing stylesheet and layout findings; a slow request and a slow load | same page | likely |

If every link of a group is confirmed, the report says **Root cause**; if any link is inferred, it says
**Likely root cause**. Console errors are recorded while the page loads, before the clicks, so they are never
linked to requests sent by a click.

Each group has:

- **severity:** CRITICAL (a critical finding), HIGH (a warning about functionality or security), MEDIUM (another
  warning), LOW (notices only);
- **category:** `functionality`, `responsive`, `performance`, `accessibility`, `security`, `visual` or `other`;
- **impact 1-10:** 8 / 5 / 2 for critical / warning / notice, +1 if it breaks a function or exposes data, +1 on
  several pages or +2 on most pages, +1 for a key action from `criticalSelectors`, +1 if it causes 2+ other issues;
  the parts are listed next to the number;
- **affected pages, root cause, related issues** (each with the reason of the link), **recommendation** and
  **evidence** (screenshots, URLs, selectors, requests with status, console text).

In `current.json` the groups are under `groups`:

```json
{
  "id": "G-01", "title": "API request returned HTTP 500 (/api/order)", "severity": "CRITICAL",
  "category": "functionality", "impact": 10, "impactBasis": {"severity": 8, "on several pages": 1, "...": 1},
  "affectedPages": ["/cart.html", "/checkout.html"],
  "rootCause": {"text": "GET http://127.0.0.1:8765/api/order answered HTTP 500 on /checkout.html.",
                "confirmed": true, "finding": "CC-007", "rule": "network/api-5xx"},
  "relatedIssues": [{"finding": "CC-002", "rule": "interaction/action-request-failed", "page": "/cart.html",
                     "message": "...", "confirmed": true,
                     "link": "the click sends a request to the same failing endpoint /api/order"}],
  "recommendation": "...", "evidence": {"screenshots": [], "urls": [], "selectors": [], "requests": [], "console": []},
  "findings": ["CC-002", "CC-003", "CC-007", "CC-010"]
}
```

### Finding format (`current.json`)

```ts
interface Finding {
  id: string;           // CC-001, CC-002... numbered within the run
  fingerprint: string;  // stable hash of rule + page + (selector or url) for comparing runs
  severity: "critical" | "warning" | "notice";
  category: "interaction" | "layout" | "images" | "fonts" | "console"
          | "accessibility" | "seo" | "performance" | "security" | "network";
  rule: string;         // e.g. "seo/missing-title"
  page: string;         // e.g. "/checkout"
  message: string;      // short description
  details: string;      // what exactly was observed
  selector?: string;    // shortest unique CSS selector
  url?: string;         // for resources and requests
  viewport?: string;    // e.g. "375x812"
  screenshot?: string;  // relative path, critical and warning only
  evidence?: Record<string, unknown>;  // status, size, duration, stack...
}
```

`current.json` also holds the run metadata (`tool`, `version`, `project`, `url`, `date`, `pages`, `viewports`,
`checks`, `errors`, `summary`), the `groups` described above and, from the second run on, a `comparison` block with the fingerprints of fixed,
new and unchanged findings.

### What audit_project checks

| Category | Rules (severity) |
|---|---|
| console | uncaught exception (critical), unhandled promise rejection (warning), `console.error` (warning); stack and source file:line, repeats counted |
| network | API 5xx (critical), API 4xx (warning, except 401/403 on a login page), JS/CSS failed (critical), image/font failed (warning), request failed (warning), request over 1 s / 3 s (notice / warning) |
| seo | missing or empty title, missing meta description, missing `lang`, no `<h1>` (warning); several `<h1>`, skipped heading levels, no canonical, no favicon, no Open Graph, no robots.txt / sitemap.xml (notice) |
| images | image did not load, missing `alt` (warning); file over 200 KB / 1 MB (notice / warning); natural size over 2× the displayed size (notice) |
| accessibility | [axe-core](https://github.com/dequelabs/axe-core) WCAG 2.x A/AA rules (critical / serious / moderate+minor → critical / warning / notice); `onclick` elements unreachable by keyboard, no visible focus on Tab (warning) |
| layout | horizontal scroll with the deepest element past the edge (warning), text clipped by `overflow: hidden` (notice), tap targets under 24×24 px at 375 px (notice); every viewport |
| fonts | `@font-face` font failed to load (warning), text shown in a fallback because the declared font never loaded (notice) |
| performance | load over 3 s, LCP over 2.5 / 4 s, CLS over 0.1 / 0.25, page over 3 MB, over 100 requests, JS file over 500 KB, CSS file over 150 KB; thresholds in [thresholds.py](codecheck_mcp/audit/thresholds.py) |
| interaction | clicks up to 20 buttons, `href="#"` / `javascript:` links and `role="button"` per page on a fresh load and watches 2 s for navigation, URL change, requests, DOM changes, dialogs, new tabs; skips logout / delete; critical for `criticalSelectors`. If the click works but the request it sends fails, `interaction/action-request-failed` (critical for 5xx, warning for 4xx; 401/403 and 4xx from an empty form are ignored). A critical selector that was never clicked is reported too: critical if it matched no element on any page, warning if it was hidden, invalid, destructive or past the click limit |
| security | plain HTTP (not localhost), mixed content (warning); no CSP, no `nosniff`, no HSTS (notice); cookies without `Secure` or session cookies without `HttpOnly` (warning); public source maps (notice) |

If axe and another check find the same thing (for example a missing `alt` or `lang`), it is reported once.

## Try it on the demo shop

[examples/demo-shop](examples/demo-shop) is a small site with deliberate bugs (script errors, a broken image and
script, APIs that answer 500, horizontal scroll on phones, a dead checkout button, "Place order" buttons whose
request fails, a page without a title) and a clean home page.

```bash
python examples/demo-shop/serve.py 8765
# then ask your agent: run audit_project on http://127.0.0.1:8765/ with criticalSelectors ["#checkout", "#place-order"]
```

## Architecture

```text
codecheck_mcp/
  server.py                 MCP tools
  browser.py                Playwright launch, local server for folders, external-domain blocking
  checks/                   checks of the quick tools (full_qa, test_*)
  audit/
    runner.py               audit_project: crawl, run checks per viewport, merge duplicates, write reports
    thresholds.py           every threshold in one place
    core/                   finding, fingerprint, selector, screenshot, crawler, session (events per page)
    checks/                 one module per category; each has run(page, ctx) -> list[Finding]
    report/                 json_report, markdown, diff, priority (grouping)
  vendor/axe.min.js         axe-core 4.13.0, unmodified (MPL-2.0)
```

Every page is loaded once per viewport; console and network events are recorded before the page starts loading,
then each check reads the same page. Checks are registered in `audit/checks/__init__.py`.

## Safety of the server itself

- Read-only: the project under test is never modified.
- Pages open in an isolated browser context, without your cookies or sessions.
- Navigation to other domains is blocked; `audit_project` does not fill in or submit forms with data and skips
  buttons that look like logout or delete.
- Security checks are passive: they read headers, cookies and public files, and never attack the site.
- Secrets found by `quick_security` are masked in reports.
- Test only your own projects, or sites you have the owner's permission to test.

## Known limitations

- A full `audit_project` takes about 30 s for a small site; pages with many dead buttons take longer (up to 2 s per
  click). Lower `maxPages` or narrow `checks` for large sites.
- Console, network, SEO, images, fonts, accessibility, performance and security run at the widest viewport only;
  layout runs at every viewport.
- Server headers and cookies are not checked for a local folder (they come from the temporary server, not from
  your project).
- Only the `onclick` attribute is seen, not listeners added with `addEventListener`.
- Sizes are transferred bytes: a local folder is served without compression, so real hosting may be lighter.
- The comparison is with the previous run in the same output folder only.

## Roadmap

- Optional session setup (cookies or a login script) to audit pages behind a login.
- Check console and network at every viewport.
- A `max duration` limit for very large sites.
- Test on macOS and Linux.

## Development

```bash
pip install -e ".[dev]"
python -m playwright install chromium
pytest tests -q
ruff check codecheck_mcp tests
mypy codecheck_mcp
```

The tests use pages with deliberate bugs, clean pages that must produce no findings, and regressions found on real
sites. See [CONTRIBUTING.md](CONTRIBUTING.md).

## License

MIT, see [LICENSE](LICENSE). `codecheck_mcp/vendor/axe.min.js` is [axe-core](https://github.com/dequelabs/axe-core)
by Deque Systems, MPL-2.0, see [codecheck_mcp/vendor](codecheck_mcp/vendor).

TDQS

C2.7/5.0

Scored across 8 tools

Disambiguation2/5

full_qa overlaps heavily with the individual test_* tools and quick_security, since it bundles the same checks. audit_project also subsumes most of these checks, making it unclear when to use the broad tools versus the granular ones.

Naming Consistency3/5

The test_* tools follow a consistent pattern, but full_qa, quick_security, audit_project, and compare_reports break it. The mix of adjective_noun, verb_noun, and test_* styles creates a somewhat inconsistent naming scheme.

Tool Count4/5

8 tools is a reasonable count for a QA/audit server. However, full_qa and audit_project duplicate much of the test_* suite, so the same functionality could be consolidated slightly.

Completeness4/5

The set covers interactions, layout, fonts, images, security, full audits, and report comparison, forming a solid QA workflow. Minor gaps like dedicated performance or SEO checks exist, but they are not essential to the stated purpose.

Maintenance

ActivityMaintained
ResponsivenessNo issues