Skip to main content
Glama
Joel-Wwalker

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.