mcp-multi-agent-code-review
by Jk180603
README.md
# Multi-Agent Code Review MCP Server
An **MCP (Model Context Protocol) server** that reviews a git diff with three specialised LLM agents and returns one verdict. Point any MCP host (Claude Code, Claude Desktop, Cursor) at it, hand it a diff, and a LangGraph graph does the rest.


## How it works
```
validate -> rate limit -> audit log (deterministic, before any model call)
|
prepare (redact secrets before any model sees the diff)
|
+-----------------+-----------------+
v v v
security quality logic (3 reviewers, run in parallel)
+-----------------+-----------------+
v
supervisor (add secret scan, merge duplicates, apply gate)
|
v
pass | fail | incomplete
```
- **LangGraph** runs the three reviewers as parallel branches over one shared state, and the supervisor waits for all of them.
- Each reviewer returns **structured output** (a Pydantic `AgentReport`), so malformed model output is rejected at the boundary.
- The **supervisor** adds the results of a deterministic secret scan, collapses the same issue reported by two agents, and applies a severity gate.
## Three verdicts, on purpose
| Verdict | Meaning |
|---|---|
| `pass` | All three reviewers ran and nothing reached the severity gate. |
| `fail` | At least one finding is at or above the gate (default `high`), or a secret was found. |
| `incomplete` | No blocking finding, **but a reviewer could not run** (bad key, retired model, rate limit, unparseable answer). |
An earlier version swallowed reviewer errors and returned `pass` with zero findings. A review gate that fails open is dangerous, because a broken model then looks like clean code. Now a failing reviewer is recorded in `agent_errors` and the verdict becomes `incomplete`. Set `CODEREVIEW_FAIL_CLOSED=false` if you really want the old behaviour.
## Guardrails
- **Input validation:** size limit, file-count limit, empty or non-string input.
- **Secret pre-scan and redaction:** AWS keys, private keys, bearer and Slack tokens and key assignments are detected by regex. They are replaced by `[REDACTED-SECRET]` before the diff goes to any model, and they fail the review even when every model is down.
- **Rate limiting:** a sliding window over 60 seconds. It is one shared limit, not per caller.
- **Audit log:** every call is logged with a SHA-256 of the diff and its size, never the source code.
- **Untrusted input:** each reviewer is told that the diff is data and that instructions inside it must be ignored. This reduces prompt injection through code comments, but it does not remove it (see limits).
## Setup
```bash
python3.11 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # add GROQ_API_KEY (free at console.groq.com)
```
Run the tests (no key needed):
```bash
pytest
```
Watch the whole graph work on a deliberately flawed diff (needs a key):
```bash
python scripts/live_demo.py
```
Register the server with Claude Code, then call the `review_diff` tool with a unified diff:
```bash
claude mcp add code-review -- python -m src.server
```
### Review a GitHub pull request
`review_pull_request(repo, pr_number, post_comment=false)` downloads the pull request through the GitHub REST API, runs the same guarded review and returns the verdict. Put a token with read access in `.env` as `GITHUB_TOKEN` (public repositories also work without one, with a lower rate limit).
```text
review_pull_request("owner/name", 42) # read only, nothing is written
review_pull_request("owner/name", 42, post_comment=True) # also adds one summary comment
```
- Repository and number are validated before a request is built, so nothing can steer the call to another path.
- The token goes only into the `Authorization` header and never into a result, an error or the audit log.
- The download is cut off at the size limit while streaming.
- Writing to GitHub is opt-in per call. Without a token the request is refused before any model call is paid for.
- The comment holds the verdict, counts and finding titles with file and line, never source code.
## Configuration
Environment variables with the prefix `CODEREVIEW_` (or a `.env` file):
| Variable | Default | |
|---|---|---|
| `CODEREVIEW_LLM_PROVIDER` | `groq` | `groq`, `anthropic` or `openai`. |
| `CODEREVIEW_LLM_MODEL` | `openai/gpt-oss-120b` | A model id of the chosen provider. Provider model lists change, so check them when you get a "model not found" error. For another provider set this too. |
| `CODEREVIEW_FAIL_ON_SEVERITY` | `high` | Gate: `low`, `medium`, `high` or `critical`. |
| `CODEREVIEW_FAIL_CLOSED` | `true` | `false` brings back "pass when a reviewer failed". |
| `CODEREVIEW_MAX_DIFF_BYTES` | `100000` | Larger diffs are rejected. |
| `CODEREVIEW_RATE_LIMIT_PER_MINUTE` | `20` | Reviews per minute, shared. |
The API key itself uses the provider's usual variable (`GROQ_API_KEY`, `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`).
## Tests
61 tests, no network and no key. The model is replaced by a fake that answers for each reviewer, so the tests run the real graph:
- the verdict logic, the gate and the duplicate merging
- a failing reviewer gives `incomplete`, never `pass`, and a real finding still wins over an outage
- a leaked secret fails the review even when all three models are down
- secrets never reach the model, and every reviewer is told the diff is untrusted
- validation, rate limiting and the audit log (hash only, no source)
- the GitHub integration against a mocked API: bad repo names, error codes, token never leaked, size cut-off, nothing written by default, one comment when asked
CI runs them on every push (`.github/workflows/tests.yml`).
## Known limits
- Prompt injection is reduced, not solved. A diff can still try to talk a reviewer into silence. Treat the result as help for a human reviewer, not as a replacement for one.
- The secret scan knows a few common formats. A secret in an unusual format reaches the model.
- The rate limit is in memory and shared. It does not survive a restart and does not separate users.
- The reviewers see only the diff, not the rest of the repository.
- Pull request review is pull-based (a tool call). There is no webhook receiver yet, so it does not run by itself when a pull request opens.
- Model quality decides what is found. The tests prove the plumbing, not the quality of the reviews.
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo recent community issues