AI-Code-Reviewer
# ai-code-reviewer-mcp
An AI code reviewer, shipped as an installable [MCP](https://modelcontextprotocol.io)
server. It reviews a diff/PR against a target repository's own coding conventions —
learned via RAG over that repo's real code, docs, and lint config — rather than a fixed,
generic linter ruleset.
Point it at any repo, ask it to review a diff, and it comes back with findings that cite
the *actual* file:line in that repo the convention came from — not generic advice.
## How it works
```
MCP Host (Claude Code / Cursor / Claude Desktop)
│ stdio (JSON-RPC over MCP)
▼
FastMCP server (Python)
├── index_repo / get_index_status — RAG indexing pipeline
├── explain_convention — one-shot grounded Q&A over the index
└── review_diff / review_file — LangGraph multi-step review agent
│
▼
Local state: ~/.ai-code-reviewer-mcp/<repo-hash>/{chroma/, manifest.sqlite}
│
▼ (only the review-generation/critic LLM calls leave the laptop)
Gemini API (gemini-flash-lite-latest, free tier)
```
Retrieval, chunking, and the vector store all run **locally and for free** — chunking
is done with Python's `ast` module at symbol boundaries (one chunk per function/method/
class, not prose-style sliding windows), and embeddings are computed on-device via
[fastembed](https://github.com/qdrant/fastembed) (`sentence-transformers/all-MiniLM-L6-v2`,
ONNX runtime, no GPU/torch needed). Only the review-generation and critic steps call
Gemini's API.
### The review agent (LangGraph)
`review_diff` runs a mostly-linear graph with exactly one bounded conditional loop:
```
parse_diff → retrieve_conventions → generate_hunk_review → critic_selfcheck ─┐
▲ │
└──────────── (weak citation, retry-capped at 1) ───────┘
│
aggregate_and_dedup ◄┘
│
format_output
```
- **`generate_hunk_review`** — one structured-output Gemini call per diff hunk. The
model must cite a `chunk_id` from the retrieved conventions — it can't free-type a
file:line, which is what prevents hallucinated citations.
- **`critic_selfcheck`** — two independent passes: (a) a **deterministic** check (no
LLM) that every cited chunk really exists in the retrieved set and that the repo
hasn't changed since indexing; (b) an **LLM judgment** pass that keeps/downgrades/
suppresses each finding and can trigger one bounded re-retrieval if the citation looks
weak.
The graph is kept linear everywhere else on purpose — the parse → retrieve → generate →
critique → aggregate sequence is fully knowable upfront, so a dynamic planner node would
add complexity without payoff. The one retry loop is the actual justification for using
LangGraph over a plain function pipeline here.
## Tools exposed
| Tool | Purpose |
|---|---|
| `index_repo` | (Re)index a local repo's source/docs/lint-config. Incremental — only files whose git blob sha changed are re-embedded. |
| `get_index_status` | Whether a repo is indexed, and whether the index is stale vs. current HEAD. |
| `review_diff` | Review a diff (or `git diff base..head` computed for you) against the repo's own conventions. |
| `review_file` | Whole-file review fallback — same pipeline, file treated as fully added. |
| `explain_convention` | Ask a plain-English question about the repo's conventions, grounded in retrieved chunks. |
| `ping` | Health check. |
## Install
Requires [`uv`](https://docs.astral.sh/uv/) and a free Gemini API key from
[Google AI Studio](https://aistudio.google.com/apikey) (no credit card).
```bash
git clone <this-repo>
cd ai-code-reviewer-mcp
uv sync
```
Create a `.env` file (gitignored) with your key:
```
GEMINI_API_KEY=AIza...
```
### Add to an MCP host
Point your host's MCP config at this directory:
```json
{
"mcpServers": {
"ai-code-reviewer": {
"command": "uv",
"args": ["--directory", "/absolute/path/to/ai-code-reviewer-mcp", "run", "ai-code-reviewer-mcp"],
"env": { "GEMINI_API_KEY": "AIza..." }
}
}
}
```
(Config file location differs per host — Claude Desktop, Claude Code, and Cursor each
have their own; check that host's current docs.)
## Usage
1. `index_repo(repo_path="/path/to/some/repo")`
2. `review_diff(repo_path="/path/to/some/repo", base_ref="HEAD~1", head_ref="HEAD")` — or
pass an explicit `diff` string.
3. `explain_convention(repo_path="...", question="how should I raise a custom error here?")`
## Eval results
`uv run python evals/run_eval.py` runs 6 hand-authored fixtures (2 clean, 4 with a
known convention violation) against a small fixture repo, and checks two things:
- **Grounding** — for every finding produced, the cited `(file, line_start, line_end)`
is verified to really exist in the repo and the cited snippet is checked against the
real file content. Last run: **5/5 citations verified (100%)** — this is the claim
that matters most for this product.
- **Directional precision/recall** against the hand-labeled expected findings. Last
run: **4/6 cases passed** on `gemini-flash-lite-latest` (free tier) — zero false
positives across all 6 cases, but two recall misses (one of two co-located issues in
a single hunk, and a print-vs-logging convention). This is a small, honest sanity
check on a tiny fixture set, not a rigorous benchmark, and it's *not* prompt-tuned
against its own fixtures — the whole point of the grounding check above is to keep
that discipline honest.
## Design notes / scope cuts
- **Python-only chunking** via the stdlib `ast` module — deliberate v1 scope cut.
Multi-language support would mean `tree-sitter`, whose grammar-package compatibility
churns across versions; not worth the risk for a portfolio-scoped v1.
- **Dense-only retrieval** (Chroma + fastembed) — no BM25/hybrid fusion or reranking
yet. A complete, legitimate v1 on its own; hybrid search is the first thing to add
if this project continues.
- **Per-repo index state lives under `~/.ai-code-reviewer-mcp/<hash>/`**, not inside the
target repo — avoids writing generated vector-store files into someone else's repo.
- **No GitHub API integration** — `review_diff` takes diff text directly or computes it
from local git refs; no automated PR fetching or webhook bot in v1.
- **Rate-limit aware** — free-tier Gemini keys have real per-minute quotas; LLM calls
retry with backoff on 429 rather than failing the whole review.
### v2 ideas
GitHub webhook bot that posts review comments automatically; Java support via
tree-sitter; per-repo `.ai-code-reviewer.yml` for severity/category tuning; real hybrid
search + reranking; a standalone trace-visualizer; multi-repo convention-drift
detection; prompt caching for the repeated retrieved-context portion of prompts.
TDQS
Scored across 6 tools
Each tool serves a distinct purpose: ping for health, index_repo for indexing, get_index_status for checking indexing state, explain_convention for Q&A on conventions, and review_diff/review_file for reviewing code changes. No two tools overlap in functionality.
Most tool names follow a consistent verb_noun pattern (e.g., index_repo, review_diff), but 'ping' deviates as a noun-only name. This minor inconsistency is acceptable.
With 6 tools, the server is well-scoped for its purpose of indexing and reviewing code conventions. Each tool earns its place, covering indexing, status, health, and two review methods.
The tool set covers the core workflow (index, check status, review diff/file, explain conventions). Missing a tool to retrieve all conventions or clear the index are minor gaps, but the essential review cycle is complete.