mcp server
pr-guardian
AI-assisted code review and release automation for Python repos — built to learn every Claude Code feature (agents, hooks, skills, slash commands, MCP).
Set up
Runs on your computer. Pick your agent and operating system.
Before you start
- uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
{
"mcpServers": {
"pr-guardian": {
"command": "uvx",
"args": [
"pr-guardian"
]
}
}
}same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12
Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs
README
synced Oct 25, 2026PR Guardian
AI-assisted code review and release automation for Python repos — built to teach every major Claude Code feature by doing.
PR Guardian reviews a Python diff across four specialized passes — security, style, test coverage, docs — each with a pass/fail verdict, and generates Keep-a-Changelog release notes from git history. The same review categories run in two cooperating layers: a fast, offline, fully unit-tested deterministic Python CLI, and a Claude Code AI layer that drives the same passes with subagents, skills, slash commands, hooks, and an MCP server.
Why this project exists. It is a hands-on playground for learning Claude Code. Every feature below does real work in this repo — nothing is a toy — so by the time you've read this and run it, you will have used CLAUDE.md, agents, hooks, slash commands, skills, and MCP in one coherent project, and you'll understand why each feature exists and when to reach for it.
Table of contents
- The big picture: two layers, one format
- Quick start
- Setting up on a new machine
- Feature-by-feature tour
- How the two layers mirror each other
- End-to-end flows
- Project layout
- Usage reference
- The demo repository
- Testing
- Troubleshooting
- Further reading
The big picture: two layers, one format
PR GUARDIAN — one codebase, two layers, one finding format
==========================================================
LAYER 1 · DETERMINISTIC ENGINE LAYER 2 · CLAUDE CODE AI LAYER
Python · offline · fully tested Claude running in your editor
pr-guardian CLI /review
├─ review /security-scan
├─ scan /release-notes
└─ release-notes
src/pr_guardian/ .claude/
├─ diff_parser.py ├─ agents/ 4 scoped review subagents
├─ git_utils.py ├─ skills/ security-finding, changelog-entry
├─ reviews/ 4 review passes ├─ commands/ the slash commands above
├─ changelog.py ├─ hooks/ guard_bash · format_on_save · summary
└─ output.py └─ CLAUDE.md session context + conventions
MCP: .mcp.json → filesystem server tools
both layers emit the SAME finding shape:
category · severity · file:line · explanation · suggested fix
- Layer 1 — deterministic engine (
src/pr_guardian/): pure Python, no API keys, no network. It parses git history and unified diffs, runs static analyzers for each review category, and renders Rich terminal reports. Fully covered by unit + end-to-end tests. - Layer 2 — Claude Code AI layer (
.claude/): the richer version of the same categories, run by Claude. Each category is a scoped subagent; findings are formatted by reusable skills; slash commands orchestrate the pipeline; hooks gate destructive commands and auto-format on save; an MCP server gives Claude filesystem tools; CLAUDE.md tells Claude how to behave in this repo.
Why two layers? You get the best of both: the CLI is instant, offline, and
testable (great for CI), while the AI layer understands intent, reads
surrounding context, and can fix issues — not just report them. And because
both layers speak the same "finding shape," a human can move between a
pr-guardian scan report and a /security-scan report without relearning the
format.
Quick start
Prerequisites
- Python 3.11+ and
giton your PATH - Claude Code (for the AI layer)
- Node.js +
npx(only for the MCP server — see MCP)
1. Install
python -m pip install -e ".[dev]"
2. Try the deterministic CLI against a throwaway demo repo that is deliberately full of issues:
python tests/fixtures/build_demo_repo.py demo-repo-build/
cd demo-repo-build
python -m pr_guardian.cli review main # or `pr-guardian review main` once installed
python -m pr_guardian.cli release-notes v0.1.0 v0.2.0
cd ..
You'll see FAIL verdicts on every category — that is the point. Each finding is a real, deliberate bug in the demo app.
3. Try the Claude Code AI layer — from inside this repo, ask Claude Code:
/review— full four-pass review of the current diff/security-scan— fast security-only pass/release-notes— release notes from a commit range
4. Try MCP — see the MCP section below for the one-time approval step, then just ask Claude: "list the files in this project with the filesystem MCP server."
Setting up on a new machine
PR Guardian is designed so a fresh clone works with as little setup as possible. How much you need depends on which layer you want:
| You want to… | What you need | Extra step |
|---|---|---|
| Use the CLI and run the tests | Python 3.11+ and git | none — works immediately |
| Run the AI review passes | Claude Code, logged in | start claude from repo root |
| Use the MCP filesystem tools | Node.js + npx (network once) | approve the server on first run |
1. Clone and install
git clone <your-repo-url>
cd pr-guardian
python -m pip install -e ".[dev]"
2. Verify the deterministic layer — no keys, no network, no extra config:
pytest # full suite (engine + CLI end-to-end)
ruff check . # lint clean
pr-guardian --help # CLI installed as a `pr-guardian` command
3. The AI layer runs inside Claude Code. The .claude/ agents, commands,
skills, and hooks ship with the repo, but they only execute when you're in a
Claude Code session — and they run on your Claude Code account, not the
repo author's. Install
Claude Code, log in,
then start a session from the repo root (the hooks use project-relative
paths):
claude
/review
On your first session Claude will ask you to approve the filesystem MCP
server — this is the standard security prompt for a project-scoped server,
and it can only read/write this project directory. First use downloads the
server via npx, so you need network once.
4. CI needs nothing from you. .github/workflows/ci.yml
runs the same ruff + pytest checks automatically on every push and PR.
Feature-by-feature tour
For each feature: what it is → where it lives → what this project does with it → try it.
CLAUDE.md
What it is. A project-instructions file that Claude Code reads at session start and treats as standing context. It's how you tell Claude who you are, what the project is, how code should be written, and what not to touch — no need to re-explain in every prompt.
Where it lives. CLAUDE.md at the repo root.
What this project does with it. It defines the whole project: the
two-layer architecture, the directory layout, the enforced Python conventions
(type hints, docstrings, no bare except:, line length 100 — which ruff also
enforces in CI and the style-reviewer subagent enforces in the AI layer),
and how to run everything.
Try it. Open CLAUDE.md and note how style-reviewer reads it before
reviewing — a subagent that pulls its rules from the same file humans read is
the reason conventions stay consistent. Ask Claude Code "what are the coding
conventions here?" and watch it answer from this file.
Agents
What they are. Scoped, reusable Claude sessions with a specific role, narrowed tools, and a focused system prompt. Agents keep each task small, safe, and parallelizable — instead of one giant Claude doing everything, you dispatch specialists.
Where they live. .claude/agents/*.md. Each file's frontmatter declares
the agent's name, description (when Claude auto-selects it), and the
tools it may use.
What this project does with it. Four review subagents, one per pass:
| Agent | Job | Tools |
|---|---|---|
security-reviewer | secrets, eval/exec, shell=True, SQL injection, unsafe deserialization, missing validation | Read, Glob, Grep |
test-coverage-reviewer | changed code vs. its test file; flags untested branches | Read, Glob, Grep |
style-reviewer | CLAUDE.md conventions (type hints, docstrings, bare excepts, naming) | Read, Glob, Grep |
docs-writer | missing docstrings / README gaps on changed public API — may edit | Read, Glob, Grep, Edit, Write |
/review dispatches all four in parallel and consolidates their findings.
Note the intentional tool restriction: three agents are read-only (a reviewer
should report, not modify), while docs-writer gets Edit/Write because its
job is to fix documentation.
Try it. Run /review and watch four agents fan out. Or ask directly:
"use the security-reviewer agent on the current diff."
Slash commands
What they are. Reusable prompt templates you invoke with /name. They turn
a one-word command into a precise, multi-step instruction — so a routine task
always runs the same way.
Where they live. .claude/commands/*.md (the filename without .md is the
command name; the frontmatter description shows in the command picker).
What this project does with it.
| Command | What it does | Layers used |
|---|---|---|
/review | Finds the base branch, captures the diff, dispatches all four review subagents in parallel, consolidates into a verdict report | agents, skills, (docs-writer may edit) |
/security-scan | Security-only fast pass | security-reviewer + security-finding skill |
/release-notes | Resolves a commit range, formats each commit via a skill, assembles a Keep-a-Changelog doc | changelog-entry skill |
Try it. Run /review now. Then open .claude/commands/review.md and see exactly what steps Claude follows.
Skills
What they are. Reusable procedures — packaged instructions for a repeatable piece of work. A skill is invoked explicitly by Claude when the task calls for it, and can be shared across commands and projects. Think: "how to do X well," not "do X once."
Where they live. .claude/skills/<name>/SKILL.md.
What this project does with it. Two skills that keep output consistent across the AI layer:
security-finding— turns any flagged security issue into the standard finding shape (severity, location, category, explanation, suggested fix) with a severity rubric. Every agent that reports a security issue formats it through this skill, so a/reviewfinding and apr-guardian scanfinding read identically.changelog-entry— turns one commit (message + diff) into exactly one Keep-a-Changelog line: parses conventional-commit prefixes, handles breaking changes, writes from behavior not code./release-notesinvokes it once per commit.
Try it. Run /release-notes, then compare a line Claude produced with the
skill's documented output format — they match, because the skill drove it.
Hooks
What they are. Scripts Claude Code runs automatically around tool calls and
session events. This is the feature that makes behavior deterministic: hooks
fire on real events (before a tool runs, after it runs, on stop), regardless
of what Claude "feels" like doing. Configured in .claude/settings.json.
Where they live. .claude/settings.json (config) + .claude/hooks/*.py
(the scripts). Each hook reads a JSON payload from stdin and writes a JSON
response to stdout.
What this project does with it. Three hooks, one per lifecycle stage:
| Event | Hook | Behavior |
|---|---|---|
PreToolUse (Bash) | guard_bash.py | Detects destructive commands (rm -rf, git push --force, git reset --hard, git clean -f, …) and routes them through the permission prompt with a warning instead of letting them run blindly. Fail-open: safe commands pass straight through. |
PostToolUse (Edit/Write) | format_on_save.py | Runs ruff format + ruff check --fix on every Python file Claude edits, and reports back what changed. Fail-open: missing ruff, non-Python files, or bad payloads are no-ops. |
Stop | summary.py | Tails the session transcript and prints a short "✓ Done: …" summary of the last thing Claude said. |
Try it. Make a trivial edit to any Python file and watch the auto-format
hook report what it reformatted. Then try to convince Claude to run
git reset --hard and watch guard_bash intercept it. (Note: hook commands
use project-relative paths, so launch Claude Code from the repo root.)
MCP
What it is. Model Context Protocol — a standard way to plug external tools and data into Claude. An MCP server exposes a set of tools (plus optionally resources and prompts) over a simple protocol; Claude Code discovers them and calls them like any other tool. It's how Claude gets access to things it can't do natively — in this project, filesystem operations via a well-known community server.
Where it lives. MCP servers are registered in .mcp.json at
the project root. This file is read by Claude Code at session start, so
after editing it you must start a new session.
What this project does with it. We connect the official reference filesystem server, scoped to this project directory:
{
"mcpServers": {
"filesystem": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"C:/Claude_Projects/pr-guardian"
]
}
}
}
type: "stdio"— the server speaks over standard input/output (the default for local servers).command: "npx"+-y— Node's package runner downloads and launches the server (first run needs network; later runs are instant).- The last argument is an allowlist of directories the server may touch. We scope it to the repo root so Claude can read and write files inside the project only.
Once connected, Claude gains tools named mcp__filesystem__*, including:
read_file, write_file, edit_file, list_directory, directory_tree,
search_files, create_directory, move_file, and more.
Try it.
- Verify the server is registered — from a terminal in this repo:
The first time a session sees a project-scoped server it asks you to approve it (a cloned repo must never silently launch processes on your machine). If you declined earlier, reset withclaude mcp list # should list "filesystem" claude mcp get filesystemclaude mcp reset-project-choices. - Inside a Claude Code session, the
/mcppanel shows connected servers and their tool counts. - Use it — ask: "Use the filesystem MCP server to list the top-level
files in this project." Claude will call
mcp__filesystem__list_directoryand show you the tree.
Gotchas. MCP tools only appear after the session that reads .mcp.json
is started — restart after editing the file. If npx isn't found, install
Node.js or point command at its full path. The server can only reach the
directories you listed, so it's safe by construction.
How the two layers mirror each other
One idea drives the whole repo: a finding from the CLI and a finding from Claude should be readable the same way. The table shows the mirrors:
| Concept | Deterministic engine (src/pr_guardian/) | Claude Code layer (.claude/) |
|---|---|---|
| Review command | pr-guardian review | /review |
| Security pass | reviews/security.py | agents/security-reviewer.md + skills/security-finding |
| Style pass | reviews/style.py | agents/style-reviewer.md |
| Test-coverage pass | reviews/test_coverage.py | agents/test-coverage-reviewer.md |
| Docs pass | reviews/docs.py | agents/docs-writer.md |
| Release notes | pr-guardian release-notes | /release-notes + skills/changelog-entry |
| Finding shape | Finding dataclass (reviews/base.py) | security-finding skill's YAML fields |
| Conventions | ruff + CLAUDE.md | style-reviewer (reads CLAUDE.md) |
End-to-end flows
Deterministic review (CLI)
pr-guardian review main
│
▼
discover repo (git) ──► git diff main...HEAD ──► parse unified diff (diff_parser.py)
│
▼
run 4 reviewers over the added lines (reviews/)
├─ security (regex + SQL-injection heuristics)
├─ test-coverage (does the test file exist / changed?)
├─ style (AST: type hints, bare excepts, naming)
└─ docs (AST: docstrings on new public API)
│
▼
render verdict table + findings (Rich) ──► exit code 0 (pass) / 1 (fail)
AI review (Claude Code)
/review
│
▼
find base branch ──► git diff <base>...HEAD ──► list changed files
│
▼
dispatch 4 subagents in PARALLEL
├─ security-reviewer findings formatted with the security-finding skill
├─ test-coverage-reviewer
├─ style-reviewer reads CLAUDE.md for the conventions
└─ docs-writer may Edit docstrings/README — surface edits for approval
│
▼
consolidate into one verdict table (PASS/FAIL per category) + findings
Release notes (both layers)
CLI (changelog.py) AI (/release-notes)
classify_commit() changelog-entry skill, once per commit
feat→Added · fix→Fixed parses conventional-commit prefixes
refactor!→Changed (BREAKING) handles breaking changes
docs/chore→skipped writes from behavior, not code
│ │
└───────────────► assemble the same Keep-a-Changelog layout:
## [version] - date
### Added / ### Changed / ### Fixed
MCP flow
.mcp.json (project root — read by Claude Code at session start)
│
▼
Claude Code spawns: npx @modelcontextprotocol/server-filesystem <dir>
│
▼
Claude gains tools named mcp__filesystem__*
│ read_file · write_file · edit_file · list_directory
│ directory_tree · search_files · create_directory · move_file
│
▼
you ask "list the project files" ──► Claude calls a tool ──►
server reads the allowlisted directory ──► result shown to you
Project layout
pr-guardian/
├── CLAUDE.md # session context for Claude Code (this is a feature!)
├── README.md # you are here
├── pyproject.toml # packaging + ruff + pytest config
├── .mcp.json # MCP server registration (the MCP feature)
├── .gitignore
├── .claude/ # the Claude Code AI layer
│ ├── settings.json # hooks config (PreToolUse / PostToolUse / Stop)
│ ├── agents/ # 4 scoped review subagents
│ ├── skills/ # security-finding, changelog-entry
│ ├── commands/ # /review, /security-scan, /release-notes
│ └── hooks/ # guard_bash.py, format_on_save.py, summary.py
├── src/pr_guardian/ # the deterministic engine
│ ├── cli.py # Typer app: review, scan, release-notes
│ ├── diff_parser.py # unified diff → structured FileChange/Hunk/Line
│ ├── git_utils.py # GitPython: diff + commit history extraction
│ ├── changelog.py # commits → categorized changelog
│ ├── output.py # Rich rendering of reports
│ └── reviews/ # one analyzer module per review category
│ ├── base.py # Reviewer protocol + Finding dataclass
│ ├── security.py / style.py / test_coverage.py / docs.py
└── tests/
├── conftest.py # demo_repo fixture (builds real git history)
├── test_*.py # unit + CLI end-to-end tests
└── fixtures/
├── demo_repo/ # final-state working tree (deliberate issues)
└── build_demo_repo.py # materializes history, tags, a feature branch
Usage reference
CLI
pr-guardian review [BASE] # full four-pass review vs BASE (default: main)
pr-guardian scan [BASE] # security-only pass
pr-guardian release-notes FROM TO # Keep-a-Changelog from a commit range
pr-guardian --help # all commands/options
Options: review --categories security,style limits passes; release-notes --date YYYY-MM-DD sets the release date. Exit code is 0 when no ERROR-level
findings, 1 when there are — CI-friendly.
Slash commands (Claude Code)
/review · /security-scan · /release-notes
MCP tools (Claude Code)
mcp__filesystem__* — read_file, write_file, edit_file,
list_directory, directory_tree, search_files, create_directory,
move_file, …
The demo repository
tests/fixtures/build_demo_repo.py materializes a throwaway git repo — real
commits, two tags (v0.1.0, v0.2.0), and a feature/ai-review branch whose
diff is deliberately full of issues. It is the review pipeline's own test
data: this is exactly the kind of code a reviewer is supposed to catch.
| Demo file | Deliberate issue | Pass that flags it |
|---|---|---|
auth.py | hardcoded API key + eval() | security |
review_pipeline.py | subprocess.run(..., shell=True), bare except:, missing type hints/docstrings | security, style, docs |
slack.py | hardcoded webhook URL, no tests, missing hints/docstrings | security, test-coverage, style, docs |
db.py | SQL built by f-string interpolation | security |
The fixture tree is excluded from ruff and pytest (pyproject.toml) —
linting it would fail by design. A nested .git isn't committed (git treats it
as an embedded repo), so the repo ships the final-state working tree
(tests/fixtures/demo_repo/) plus the builder script that recreates the full
history on demand.
Testing
python -m pytest # 35 tests: unit + CLI end-to-end (green)
python -m ruff check . # lint (src + tests) — green
python -m ruff format --check . # formatting — green
Tests cover the diff parser, changelog categorization, each reviewer pass, and
the full CLI via typer.testing.CliRunner against the real built demo repo
(the demo_repo fixture in tests/conftest.py).
Troubleshooting
| Symptom | Fix |
|---|---|
MCP server shows ⏸ Pending approval | Approve it in your session, or claude mcp reset-project-choices and re-approve |
MCP server shows ✘ Failed to connect | claude mcp get filesystem for the failure detail; make sure npx is on PATH and you have network on first run |
| MCP tools not visible | .mcp.json is read at session start — restart Claude Code after editing it |
spawn ... ENOENT for MCP/hooks | The command isn't on PATH — use the full path (e.g. .venv\Scripts\python.exe) |
| Hook errors mention a subdirectory | Hook commands use project-relative paths — launch Claude Code from the repo root |
pr-guardian: command not found | The console script is created by pip install -e ".[dev]"; or run python -m pr_guardian.cli |
python opens the Microsoft Store | Use the full path to your Python, or py -3 -m ... |
Further reading
- Low-level design (LLD) — this README is the what/how; the LLD is the how it works: data structures, control flow, the AI-layer contract, and the testing strategy
- Claude Code docs — CLAUDE.md, agents, hooks, slash commands, skills
- Claude Code MCP —
.mcp.json, approval flow, troubleshooting - MCP: build a server — the official
MCPServerwalkthrough (a great next step: turn PR Guardian's engine into custom MCP tools) - Keep a Changelog — the release-notes format