Audit your whole stack with a single question to your agent.
An MCP server (stdio) that gives Claude Code (and any MCP-compatible agent) a complete audit of your project: dead
code, dependencies, security, CVEs, complexity and lint. It covers Laravel/PHP, Python (FastAPI, Flask),
TypeScript/React/Next.js and Flutter/Dart, including monorepos (backend/ + frontend/, Laravel with React,
a Flutter app next to its backend).
"Audit this repo" -> within a minute you get a report prioritized by severity, with false positives already flagged and the next step for every finding.
- Zero installation in the project.
vendor/,node_modules, the venv and.dart_toolstay untouched. Tools run throughuvx/npxor from~/.cache/code-audit-mcp, and the first run downloads them on its own. - Detects stacks by itself. It walks the repo, finds every project (even when several coexist) and audits each one with its native tools.
- Built to be used by an LLM. Findings are normalized (file, line, rule, severity, message and how to fix it), sorted by severity and free of the noise of ten different tools.
- Flags false positives and explains why. Routes registered by decorator, Laravel and Livewire hooks, Pydantic
fields or the
HEADstrings that bandit mistakes for passwords do not count in the summary. This way the agent never proposes deleting code that is in fact used. - Checks git history, not just the current code. A secret committed a year ago and deleted later is still compromised, and it finds it.
- Secrets never appear in the output. They are used to group findings and to tell whether they are still in the code, but the value never leaves the server.
| Tool | Purpose |
|---|---|
audit(path=".") |
Overview: runs the 6 checks in parallel and returns a summary per check and per stack, totals by severity and the top findings |
check(name, path=".", severity, file, offset, limit, false_positives) |
Detail for one aspect: the findings of a check sorted by severity, filterable and paginated |
Checks: dead_code · dependencies · security · vulnerabilities · complexity · lint
Context savings. Responses are a single compact JSON (no structuredContent copy and no indentation). Messages
that repeat identically across several findings of a rule are emitted only once in rules. check returns 50
findings per engine (limit) and leaves out likely false positives (false_positives=true to see them). Narrow
results with severity (minimum) and file (glob or prefix, e.g. app/Http/**) and paginate with offset; the
response tells you in next how to continue.
| Check | Laravel / PHP | Python (FastAPI, Flask) | TypeScript / React / Next.js | Flutter / Dart |
|---|---|---|---|---|
dead_code |
PHPStan + shipmonk (understands Eloquent, Blade and Livewire) | vulture | knip | — |
dependencies |
composer-unused + composer-require-checker | deptry | knip | — |
vulnerabilities |
composer audit |
pip-audit (venv, uv.lock or requirements) |
npm / pnpm / bun audit; yarn.lock in OSV.dev |
pubspec.lock in OSV.dev |
lint |
the project's PHPStan (with its Larastan) | ruff | the project's tsc + eslint |
dart analyze with its analysis_options.yaml |
complexity |
lizard | lizard | lizard | — |
security |
semgrep | bandit + semgrep | semgrep | semgrep |
In every stack, security adds gitleaks over the whole git history and sensitive files that are tracked, not
ignored, or deleted but still in history.
Flutter/Dart. dart analyze needs .dart_tool/ (flutter pub get); without it, it is skipped, because
generating it would write to the project. The dart from the Flutter SDK is used if available. The Pub ecosystem
has few advisories published in OSV: 0 CVEs does not guarantee there are none.
On top of the public semgrep packs, it ships its own rules (code_audit_mcp/rules/stack.yml) for the problems of
these stacks:
- Postgres/SQL: SQL with interpolation or concatenation in
DB::raw/select,whereRaw/orderByRaw…,cursor.execute(f"…"),text(f"…"),pool.query(\…${x}`),$queryRawUnsafe`; connection URLs with a password in the code. - Redis:
KEYS(blocks the server; useSCAN) andFLUSHALL/FLUSHDBin application code. - Laravel:
env()outsideconfig/(breaks withconfig:cache),$guarded = [],{!! !!}in Blade,eval. - FastAPI/Flask:
debug=True, CORS*with credentials,SECRET_KEYin the code. - React:
dangerouslySetInnerHTML, tokens inlocalStorage. - Flutter/Android/iOS:
badCertificateCallbackaccepting any certificate, tokens and passwords inSharedPreferences,usesCleartextTraffic/debuggablein the releaseAndroidManifest.xmlandNSAllowsArbitraryLoadsinInfo.plist.
Secrets and sensitive files:
- Files:
.env*, private keys and certificates (.pem,.key,.p8,id_rsa,.p12…), credentials (.pypirc,.netrc,.pgpass,.git-credentials,.htpasswd, Androidkey.properties;.npmrc/.yarnrc.ymland Composer'sauth.jsononly if they carry tokens), data dumps (.sqlite,.db,*dump*.sql,.sql.gz,.bak) and logs. They are reported when tracked in git, onegit add .away from being committed (not ignored) and deleted but still present in history, with commit, author and date so you know what to rotate. - Secrets in history (gitleaks): tokens and keys that were ever committed on branches, tags or remotes, even if they are no longer in the code. One finding per secret, with the oldest commit and whether it is still in the code.
Dead-code detectors cannot see implicit usages. Every likely false positive of this kind comes out with
likely_false_positive and the reason, and does not count in the severity summary:
- Python: functions registered by decorator (
@app.get,@router.post,@bp.route,@command…), Pydantic/SQLAlchemy/dataclass/Enum model fields, Alembic migrations, framework hooks. In dependencies, packages that are used without being imported (asyncpg/psycopgthrough the connection URL,python-multipart,uvicorn,alembic…). - Laravel: Laravel/Livewire/Filament hooks, Eloquent accessors and scopes, public members of Livewire/Filament components and names referenced from Blade views.
- knip:
laravel-vite-pluginentries (passed to knip as entry) and files of plugins that could not be loaded. - bandit:
import subprocess(B404), commands with literal arguments (["git", "status"], B603/B607) and "passwords" that are not (HEAD,refresh_token:, uppercase constants; B105-B107).
Tests count as usage, but their findings are not reported.
exclude = ["legacy/**"] # paths to skip
ignore = ["B404", "DEP002:ruff", "unused-method:*Resource::form"] # rule or rule:name (globs)
[dead_code]
ignore_names = ["on_*"]
ignore_decorators = ["@command"] # Python
min_confidence = 60 # Python (vulture)
[complexity]
threshold = 11 # minimum cyclomatic complexity (C = 11-20, D = 21-30…)
[security]
registry = true # public semgrep packs (needs network)- uv (always).
- PHP 8.2+ and Composer for PHP projects; Node.js for JS/TS projects; bun if the project uses
bun.lock; Flutter or Dart SDK for linting Dart projects. - gitleaks downloads itself (official GitHub binary, sha256 verified) to
~/.cache/code-audit-mcp/bin, unless it is already in the PATH. CVEs foryarn.lockandpubspec.lockare queried against the OSV.dev API (network). - For complete results, the project must have its dependencies installed (
composer install,npm ci, venv). Without them, each affected engine says so inskippedornotes.
The first run downloads the tools (a few minutes). On a large Laravel project, dead code takes 1-2 minutes; the rest of the checks, seconds.
Globally in Claude Code:
claude mcp add -s user code-audit /path/to/code-audit-mcp/bin/code-audit-mcp-serverPer project (.mcp.json):
{
"mcpServers": {
"code-audit": {
"command": "/path/to/code-audit-mcp/bin/code-audit-mcp-server",
"args": []
}
}
}| Variable | Default | Purpose |
|---|---|---|
CODE_AUDIT_TIMEOUT |
900 |
maximum seconds per tool |
CODE_AUDIT_JOBS |
3 |
simultaneous heavy processes |
CODE_AUDIT_CACHE |
~/.cache/code-audit-mcp |
cache for PHP tools, PHPStan and gitleaks |
CODE_AUDIT_UVX, CODE_AUDIT_UV, CODE_AUDIT_NPX, CODE_AUDIT_NPM, CODE_AUDIT_PHP, CODE_AUDIT_COMPOSER, CODE_AUDIT_BUN, CODE_AUDIT_DART, CODE_AUDIT_FLUTTER, CODE_AUDIT_GITLEAKS |
from PATH | alternative binaries |
uv sync
uv run pytest
uvx ruff check .Layout: core.py (processes, cache, finding format), detect.py (stacks), config.py,
engines/{python,php,node,dart,shared}.py, checks.py (which engine resolves each check), server.py.
The own semgrep rules are tested against tests/fixtures/rules, where every line that must be detected carries
BAD:<rule>.
Issues and pull requests are welcome; see CONTRIBUTING.md. Security reports go through SECURITY.md. Release notes live in CHANGELOG.md and planned work in ROADMAP.md. Licensed under the MIT License.
code-audit-mcp: the quality and security report your agent can read, prioritize and fix.