MCP server adding an independent second LLM perspective (Judge B) with full project graph context
Project description
Contrarian
MCP server that adds a second LLM perspective (Judge B) directly inside Claude Code.
Claude Code is Judge A — it already has full project context. Contrarian adds Judge B, an independent model from a different lab, via a single tool call. Same rubric, different priors, no context switch.
How it works
- Claude Code invokes
contrarian_review()— no args for auto-detect, or pass specific files. - Contrarian resolves the input: git diff, last commit, or full audit (in that order).
- Judge B (external model) reviews against
JUDGE.mdin the project root. - Findings are returned inline and appended to
REPORT.mdfor the dialogue log.
Auto-detect order:
- diff — any staged/unstaged changes or untracked files
- last-commit — clean tree → reviews the last commit
- audit — forced with
audit=true, or nothing else found
Installation
Requires Python 3.10+ and git (auto-detect mode uses git diff/git log; without a git repo, Contrarian falls back to a full audit). You'll need a free API key from Google AI Studio (no credit card required).
pip install contrarian
contrarian setup
contrarian setup prompts for your API key, then writes the MCP server config to ~/.claude/.claude.json automatically. Restart Claude Code after running it.
That's it.
Updates: pip install --upgrade contrarian. When a newer version is on PyPI, Contrarian prints a one-line notice on use (checked at most once/day, fail-silent). Opt out with CONTRARIAN_NO_UPDATE_CHECK=1.
Manual config (alternative)
If you prefer to configure manually, add to ~/.claude/.claude.json:
{
"mcpServers": {
"contrarian": {
"type": "stdio",
"command": "contrarian",
"args": [],
"env": {
"JUDGE_B_API_KEY": "AI...",
"JUDGE_B_BASE_URL": "https://generativelanguage.googleapis.com/v1beta/openai/"
}
}
}
}
Default provider is Gemini (generativelanguage.googleapis.com, model gemini-2.5-pro). Change JUDGE_B_BASE_URL and JUDGE_B_MODEL to use any OpenAI-compatible provider (DeepSeek, Groq, Ollama, OpenRouter, etc.).
Anthropic models are blocked at runtime — Judge B must come from a different lab than Judge A.
Provider switching
Judge B has two kinds of provider:
API providers (per-token billing) — any OpenAI-compatible endpoint, selected by JUDGE_B_BASE_URL + JUDGE_B_MODEL (or contrarian use <preset>). JUDGE_B_API_KEY required.
CLI-agent providers (flat subscription cost) — drive a local agent CLI instead of an API. No API key, no per-token cost. Two providers:
cli:codex— runs Judge B on your Codex subscription viacodex exec, agentic: read-only inside the project, so it reads files beyond the diff and catches cross-file ripple effects the diff-only API path misses. Optional model pin:cli:codex:<model>(e.g.cli:codex:gpt-5.2).cli:agy— runs Judge B on Antigravity (agy), a different lab (Gemini). Isolated (not agentic): runs sandboxed from a throwaway workspace and sees only the inline diff. Optional model pin:cli:agy:<model>(e.g.cli:agy:gemini-3.1-pro-high).
JUDGE_B_MODEL=cli:codex # persistent: all reviews via Codex subscription
contrarian_review({ model: "cli:agy" }) # per-call override
Fan out to several models at once — one review, N independent opinions, never merged:
JUDGE_B_MODEL="cli:codex+cli:agy" # persistent multi-model
contrarian_review({ models: ["cli:codex", "cli:agy"] }) # per-call
Each model's review is returned as a separate labelled block; you remain the supreme judge (no reconciliation, no auto-merge). One provider failing (timeout, outage, auth error — any reason) shows as a FAILED block and doesn't sink the others; if OpenAI is down, a cli:codex+cli:agy fan-out still returns agy's review. JUDGE_B_CLI_TIMEOUT (default 600s) bounds each CLI call. codex reviews at a bounded reasoning effort (JUDGE_B_CODEX_EFFORT, default low) so an agentic review completes instead of over-exploring; raise it (medium/high/xhigh) for more depth at the cost of latency.
Above the reviews sits a convergence hint — a code-only, lexical pass that flags when several judges appear to have raised the same finding independently (⚠ 2/3 judges:, every member printed and attributed so you can check the claim). It never merges, ranks, or decides: there is no Judge C.
Read it asymmetrically, because that is how it measures. On the archived rounds it caught 3 of 7 real agreements. So:
- When it fires — treat it as a pointer, not a verdict. It matched words, not meaning. Every member of a group is printed and attributed precisely so you can confirm by reading; this half is not measured, and there is a known way for it to be wrong (below).
- When it is silent — that means nothing. It is the absence of a detection, not the presence of disagreement. 4 of 7 real agreements looked exactly like silence. Always read every review.
Its calibration is still Python-shaped — the archived rounds it is tuned against are all this repo. The matcher is no longer Python-shaped: file-path words are stripped by shape, not by an extension list, so src/router.ts, cmd/server.go, src/my-service/retry-policy.py and src/.env.local strip the same as .py (INCIDENT-006: until 2026-07-26 they did not, and two unrelated findings about one file linked on the filename alone). It is still Unix-path-shaped, which is a weaker claim and the honest one — a path outside that grammar (packages/@scope/core/index.ts, a bare .eslintrc) leaks part of its filename into the score and can manufacture one of these hints. That residue is filed, not fixed (INCIDENT-007): closing it by widening the character set is the same mistake as the extension list. What remains repo-shaped is the threshold, fitted to those rounds: on a codebase with very different finding length or vocabulary, expect the recall figure above to move.
Security invariant: a Judge never mutates the code it judges. codex is forced --sandbox read-only (non-configurable, runtime-guarded); agy runs sandboxed from a throwaway cwd where headless mode auto-denies all file tools. Independence is preserved — Judge B is always a different lab than Judge A (Claude), and an explicit adversarial preamble overrides the agent CLI's own collaboration framing (verified by a differential: same diff, project vs neutral cwd, no softening of verdict or core findings).
Usage
Inside any Claude Code session, the tool is available as contrarian_review.
contrarian_review() # auto-detect mode
contrarian_review({ path: "src/auth.py" }) # single file, diff
contrarian_review({ path: "src/auth.py", full: true }) # full file
contrarian_review({ paths: ["src/a.py", "src/b.py"] }) # multi-file, reviewed as a unit
contrarian_review({ audit: true }) # force full repo walk
contrarian_review({ model: "google/gemini-3.1-pro-preview" }) # one-off model override
Same engine is also available as a CLI command (prints to stdout — full visibility, always the latest installed code, no /mcp reconnect; Claude can invoke it via its Bash tool and stay in-conversation):
contrarian review # auto-detect
contrarian review src/auth.py --full
contrarian review --models cli:codex cli:agy # fan out, two independent reviews
Rubric
Place a JUDGE.md at the project root to customize what Judge B looks for. Without it, a built-in fallback rubric applies.
The built-in rubric covers four dimensions:
| Dimension | What to find |
|---|---|
| Exactitude | Logic errors, wrong assumptions, incorrect implementation |
| Missing | Unhandled cases, absent validation, unconsidered implications |
| Premises | Assumptions that could be wrong |
| Alternatives | Fundamentally different approaches |
Output is always JSON:
{
"exactitude": { "verdict": "ok|warn|fail", "findings": [] },
"missing": [],
"premises": [],
"alternatives": []
}
Report log
Findings are appended to REPORT.md at the project root under [Judge B] blocks. Claude Code annotates findings under [Judge A] blocks. Judge B reads previous exchanges before each review — closed findings (addressed, won't fix, disagree) are not re-raised.
Run contrarian stats from the project root for a summary: review count, model breakdown, exactitude verdicts, and finding totals parsed from REPORT.md.
Phase roadmap
- Phase 2: MCP server + project graph context. Auto-detect mode. Python rewrite.
- Phase 3: Operational hardening — token budget guard, graph latency, detached HEAD handling.
- Phase 4 (current): PyPI distribution, CLI provider switching (
setup/model/use), usage stats (contrarian stats). Next: agent watchdog — autonomous background review on commit.
License
Dual-licensed: AGPL-3.0-or-later, with a commercial license available — see LICENSING.md. Versions ≤ 0.2.4 remain MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file contrarian-0.4.2.tar.gz.
File metadata
- Download URL: contrarian-0.4.2.tar.gz
- Upload date:
- Size: 92.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bc3278644ca54032f670949defdaaf62dfb06b6864c160f3babbe6f2096ae353
|
|
| MD5 |
ec15e02aadbeef124526d8a82e157023
|
|
| BLAKE2b-256 |
a8482831bbca902f2fdff6b1855a450674147ac230546c1b163e3cc2c0987ebb
|
File details
Details for the file contrarian-0.4.2-py3-none-any.whl.
File metadata
- Download URL: contrarian-0.4.2-py3-none-any.whl
- Upload date:
- Size: 72.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ded60aadc001e911495f3ac2795cef268da1196fa74a3a498fbd942f639021ef
|
|
| MD5 |
fee8780191ea90b1a1282596fbc800c6
|
|
| BLAKE2b-256 |
061c973dd56eada911c89e667d4c9ac245542a61193b4a5dc9ec01dd8d140523
|