Codebase Archaeology MCP
by Joel-Wwalker
README.md
# Codebase Archaeology MCP
An MCP server that answers **"why is this line here?"** for a local git repo by
tracing a line back to the commit that actually introduced it, then to the PR
and review discussion behind that commit.
## The problem
`git blame` attributes a line to whoever **last** touched it. In real repos
that is usually a formatter run, an import reorder, or a rename. The naive
pipeline — blame → commit → PR — returns *"Bob, prettier migration, 2024"*
and tells you nothing about intent.
This server defeats that:
- **blame** runs with `-w -C -C` (whitespace ignored, moved/copied code
followed) and honours `.git-blame-ignore-revs`.
- **line history** follows a range backwards through edits and renames
(`git log -L`), detects **cosmetic commits** (`git show -w` produces no
output → the change was whitespace-only), and compensates for `-L` range
drift, so `likely_origin` is the commit that genuinely introduced the
content — not the formatter, and not the neighboring lines' history.
- **PR lookup** uses GitHub's `commits/{sha}/pulls` endpoint, which works for
squash and rebase merges where commit-message parsing silently fails.
- When the trace bottoms out at the commit that **created the file**, the
answer says so — `git log -L` cannot see across a file split, and
pretending otherwise would be lying.
## Tools
| Tool | Question it answers |
|------|---------------------|
| `why_this_line(repo, path, line, context=0)` | **Start here.** The whole story: last touch, true origin, evolution, origin PR + review discussion, and an interpretation when blame's answer is misleading. |
| `blame_range(repo, path, start, end)` | Who last touched these lines (formatters already discounted)? |
| `line_history(repo, path, start, end, limit=15)` | Every commit that changed this range, with `likely_origin` and a `cosmetic_skipped` count. |
| `find_removal(repo, pattern, path="", limit=10)` | "There used to be a retry here — where did it go?" Pickaxe (`git log -S`), each hit labelled `added` / `removed` / `modified`. |
| `commit_context(repo, sha)` | Full message, author, files, stat for one commit. |
| `pr_for_commit(repo, sha, include_comments=True)` | The PR that introduced a commit, with inline review comments and discussion. |
`repo` may be any path inside the work tree; file paths are resolved against
the repo root and anything escaping it is rejected.
## Setup
Python 3.11+ and git on PATH.
```
pip install -r requirements.txt
```
Run over stdio:
```
python server.py
```
or explore interactively:
```
fastmcp dev server.py
```
### Client configuration (stdio)
```json
{
"mcpServers": {
"codebase-archaeology": {
"command": "python",
"args": ["C:/path/to/code-arch/server.py"],
"env": {
"ARCHAEOLOGY_REPOS": "C:/Users/you/src/repo-a;C:/Users/you/src/repo-b",
"GITHUB_TOKEN": "ghp_...",
"ARCHAEOLOGY_CACHE": "C:/Users/you/.cache/archaeology.sqlite"
}
}
}
}
```
## Environment variables
| Variable | Meaning |
|----------|---------|
| `ARCHAEOLOGY_REPOS` | Allowlist of repositories the server may read, separated by the platform path separator (`:` on Unix, `;` on Windows). A repo qualifies if its toplevel is one of these paths or inside one. **Unset = any local repo is allowed** — set it when exposing the server to anything you don't fully trust. |
| `GITHUB_TOKEN` | GitHub token for PR lookups. Optional, but unauthenticated calls are capped at 60 req/hr; one `why_this_line` costs up to 3. Rate-limit errors name this variable. |
| `ARCHAEOLOGY_CACHE` | Path of the SQLite HTTP cache (default `~/.cache/codebase-archaeology/http_cache.sqlite`). Responses are kept ~7 days; commit→PR mappings are effectively immutable, so cached answers cost zero API calls. |
## Honest limits
- `git log -L` cannot trace across a **file split** (content extracted into a
new file). The origin will be the splitting commit, flagged with
`origin_is_file_creation: true` and an interpretation pointing at
`find_removal` to dig further.
- Cosmetic detection is whitespace-based (`git show -w`). A formatter that
changes quote characters is not "cosmetic" by this test, but the lineage
trace still walks through it to the real origin.
- `git log -L` on a huge file with deep history is genuinely slow; that is
why `limit` defaults to 15 and git calls time out at 30s.
## Tests
```
python -m pytest
```
The suite builds a throwaway repo reproducing the failure mode (real origin →
whitespace-only reformat → reword) and asserts, among other things, that
naive blame credits the formatter while every tool here names the true
origin. GitHub tests run against a fake transport — no network needed.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues