Skip to main content
Glama
RepoDoctor MCP Server

RepoDoctor MCP Server

by daamles
README.md
<!-- repodoctor: ignore-file (this README quotes example findings) -->
<div align="center">

# ๐Ÿฉบ RepoDoctor

**A read-only health check for your repository โ€” leaked secrets, code quality and project structure, in one command.**

[![CI](https://github.com/daamles/repodoctor/actions/workflows/ci.yml/badge.svg)](https://github.com/daamles/repodoctor/actions/workflows/ci.yml)
[![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue.svg)](pyproject.toml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![MCP](https://img.shields.io/badge/MCP-ready-8A2BE2.svg)](#use-it-from-claude-mcp)

_made by **daamles**_

</div>

RepoDoctor scans a project and tells you what you would rather find before
`git push` does: API keys sitting in source files, forgotten `FIXME` notes,
oversized files and code that has grown hard to review. It works from the
terminal, in CI, and as an [MCP](https://modelcontextprotocol.io) server so
Claude can analyse your repositories for you.

```text
โ•ญโ”€ RepoDoctor v0.1.0 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ Target  demo-shop                        โ”‚
โ”‚ Mode    read-only ยท .gitignore respected โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ made by daamles โ”€โ•ฏ

Findings
 Level   Rule                     Location              Message
 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 ERROR   RD005 hardcoded-secret   app/main.py:3         Possible AWS access key ID: AK********LK
 ERROR   RD005 hardcoded-secret   public/payment.js:1   Possible Stripe live key: sk_l********Xa9S
 WARN    RD004 long-file          app/legacy.py         File has 1200 lines (limit 1000); considerโ€ฆ
 WARN    RD002 todo-comment       includes/db.php:3     FIXME: query built by string concatenationโ€ฆ
 INFO    RD002 todo-comment       app/main.py:7         TODO: validate the cart before charging

โ€ข Scanned 7 files (1.2 MB) in 0.02s ยท 1 dir pruned ยท 2 files ignored by .gitignore
โœ– 3 errors ยท 4 warnings ยท 3 info
```

Full examples: [terminal output](examples/example-output.txt) ยท
[Markdown report](examples/example-report.md) ยท
[JSON report](examples/example-report.json)

## Why RepoDoctor

- **Safe by design.** It never executes, imports or modifies the code it scans.
- **Finds real problems.** Committed credentials are the most damaging mistake
  in public repositories; RepoDoctor catches them and **masks every value** in
  its output, so its own reports are safe to share.
- **Low noise.** Respects `.gitignore`, skips binaries and minified bundles,
  filters placeholders like `your_api_key_here`, and only reports work markers
  that are actually in comments.
- **Fits your workflow.** Rich terminal report, JSON for tooling, Markdown for
  pull requests, exit codes for CI, and MCP tools for AI assistants.
- **Small.** Two runtime dependencies (Typer, Rich); MCP support is optional.

## Safety guarantees

These are part of the contract and are covered by tests:

| Guarantee | How |
| --- | --- |
| Project code is never executed | Files are read as bytes and decoded as text; nothing is imported, evaluated or run. |
| Scanned files are never modified | Only `.gitignore`-style files and source text are read. The only file RepoDoctor ever writes is the report you ask for with `--output`. |
| Scans cannot escape the target | Symbolic links are not followed. |
| Secrets stay secret | Values are masked (`AK********LK`) in the terminal, JSON, Markdown and MCP output. |
| Huge or binary files are not read | Files over 2 MB and files containing NUL bytes are skipped. |
| MCP access is confined | The MCP server only scans directories inside the roots you allow. |

## Installation

Requires Python 3.11 or newer.

```bash
# From GitHub
pip install "git+https://github.com/daamles/repodoctor.git"

# With MCP server support
pip install "repodoctor[mcp] @ git+https://github.com/daamles/repodoctor.git"
```

From a local clone (recommended for development):

```bash
git clone https://github.com/daamles/repodoctor.git
cd repodoctor
python -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
```

## Usage

```bash
repodoctor scan .                         # scan the current directory
repodoctor scan path/to/project --top 10  # list the 10 largest files
repodoctor scan . -o report.md            # also write a Markdown report
repodoctor scan . -f json > report.json   # JSON to stdout (pipe-friendly)
repodoctor rules                          # list all rules
```

### `scan` options

| Option | Description |
| --- | --- |
| `-f`, `--format` | `text` (default), `json` or `markdown`. JSON and Markdown print to stdout unless `--output` is set. |
| `-o`, `--output FILE` | Write a report file. With `--format text`, the format is taken from the extension (`.json`, `.md`). |
| `--fail-on LEVEL` | Exit with code 1 when a finding at or above `error`, `warning` (default) or `info` exists; `never` disables it. |
| `--no-gitignore` | Also scan files that `.gitignore` excludes. |
| `-t`, `--top N` | Number of largest files to list; `0` hides the section. Default `5`. |
| `-l`, `--limit N` | Maximum findings to print; `0` shows all. Default `50`. |

### Exit codes

| Code | Meaning |
| --- | --- |
| `0` | No findings at or above `--fail-on` |
| `1` | Findings at or above `--fail-on` |
| `2` | The scan could not run (bad path, invalid option, unwritable output) |

### Use it in GitHub Actions

```yaml
- uses: actions/setup-python@v5
  with:
    python-version: "3.12"
- run: pip install "git+https://github.com/daamles/repodoctor.git"
- run: repodoctor scan . --fail-on error --output repodoctor-report.md
```

## Rules

| ID | Name | Level | Reports |
| --- | --- | --- | --- |
| `RD001` | `large-file` | warning | Files larger than 1 MB (consider Git LFS) |
| `RD002` | `todo-comment` | info / warning | `TODO` and `XXX` (info), `FIXME` and `HACK` (warning) in comments |
| `RD003` | `long-line` | info | Source files with lines over 120 characters (one finding per file) |
| `RD004` | `long-file` | warning | Source files with more than 1000 lines |
| `RD005` | `hardcoded-secret` | error / warning | Credentials committed to source, always masked |

Line-based rules apply to Python, PHP and JavaScript and skip minified files
(`*.min.js`). In other text files such as Markdown, work markers are reported
anywhere on the line.

### Secret detection

Known formats are reported as **errors**:

- AWS access key IDs, GitHub tokens (classic and fine-grained), Stripe live
  keys, Slack tokens, Google API keys, OpenAI and Anthropic API keys
- Private key blocks (`BEGIN โ€ฆ PRIVATE KEY`)
- Passwords embedded in database URLs (PostgreSQL, MySQL, MongoDB, Redis, โ€ฆ)

Less certain matches are reported as **warnings**: JSON Web Tokens, and
generic assignments such as `password = "โ€ฆ"` or `'api_key' => 'โ€ฆ'`. Generic
values must look random (Shannon entropy โ‰ฅ 3.0) and must not be placeholders
(`example`, `changeme`, `your_โ€ฆ`, `<โ€ฆ>`, `${โ€ฆ}`, `********`, โ€ฆ). Unquoted
`KEY=value` lines are only considered in `.env`, `.ini`, `.cfg`, `.conf` and
`.properties` files.

If RepoDoctor reports a real secret: **rotate it first**, then remove it from
the code and from Git history. Deleting the line is not enough once it has
been pushed.

### Suppressing findings

```python
EXAMPLE_KEY = "..."  # repodoctor: ignore
```

- `repodoctor: ignore` anywhere on a line hides findings on that line.
- `repodoctor: ignore-file` in the first five lines skips the whole file.
  Generated Markdown reports include this marker, so committing a report does
  not make the next scan report it again.

## Use it from Claude (MCP)

RepoDoctor ships a read-only [Model Context Protocol](https://modelcontextprotocol.io)
server. Install the extra, then point your MCP client at `repodoctor mcp`:

```bash
pip install "repodoctor[mcp] @ git+https://github.com/daamles/repodoctor.git"
repodoctor mcp --root ~/projects          # stdio server; --root is repeatable
```

**Claude Desktop** โ€” add this to `claude_desktop_config.json` (on Windows use
the full path to `repodoctor.exe` inside your virtual environment):

```json
{
  "mcpServers": {
    "repodoctor": {
      "command": "repodoctor",
      "args": ["mcp", "--root", "/path/to/your/projects"]
    }
  }
}
```

**Claude Code** โ€” register it from your project directory (see the
[Claude Code MCP documentation](https://docs.claude.com/en/docs/claude-code/mcp)):

```bash
claude mcp add repodoctor -- repodoctor mcp --root .
```

Then ask things like _"Scan this repository and explain the most serious
findings"_ or _"Are there any secrets under src/?"_.

| Tool | Purpose |
| --- | --- |
| `scan_repository(path, respect_gitignore)` | Fresh scan; returns a summary and the 20 most severe findings. |
| `get_report(path, format, respect_gitignore, refresh)` | Full report as `markdown` or `json` text. |
| `list_findings(path, min_severity, rule, file, limit, โ€ฆ)` | Filter findings by severity, rule (`RD005` or `hardcoded-secret`) and path prefix. |

Every tool is annotated `readOnlyHint: true`. Paths are resolved against the
first `--root`, and anything outside the allowed roots is refused, so an AI
client cannot use RepoDoctor to read arbitrary directories.

## How it works

```text
cli.py โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                       โ”Œโ”€โ”€ ui.py          (Rich terminal output)
mcp_server.py โ”€โ”€โ”€โ”ผโ”€> scanner.analyze() โ”€โ”€โ”ผโ”€โ”€ reporters/     (JSON, Markdown)
service.py โ”€โ”€โ”€โ”€โ”€โ”€โ”˜        โ”‚              โ””โ”€โ”€ formatting.py
                          โ”œโ”€โ”€ walker.py    which files: .gitignore, pruning, no symlinks
                          โ”‚     โ””โ”€โ”€ ignore.py   dependency-free gitignore matcher
                          โ””โ”€โ”€ checks/      what is wrong: one class per rule
                                โ”œโ”€โ”€ base.py     FileContext reads each file once via reader.py
                                โ”œโ”€โ”€ size.py ยท todos.py ยท lines.py ยท secrets.py
```

- `walker.py` decides **which** files to look at; `checks/` decides **what is
  wrong** with them. Both feed a plain `ScanResult` dataclass.
- `reader.py` is the only module that opens source files: size-capped, binary
  files rejected, decoding never fails.
- The CLI and the MCP server are thin layers over the same `analyze()` call.
- Adding a rule means writing one `Check` subclass and registering it in
  `checks/__init__.py`.

## Development

```bash
pip install -e ".[dev]"
pytest                          # 200+ tests
ruff check . && ruff format --check .
repodoctor scan . --fail-on warning   # RepoDoctor checks itself in CI
python examples/make_demo.py    # regenerate the example outputs
```

Test data never contains a complete fake credential: values in
`tests/fakes.py` are assembled at runtime, so neither RepoDoctor nor GitHub
push protection flags this repository.

## Roadmap

- Configuration through `[tool.repodoctor]` in `pyproject.toml`
- SARIF output for GitHub code scanning
- More languages and secret formats
- Git history scanning for secrets that were removed but never rotated

## License

[MIT](LICENSE) ยฉ 2026 **daamles**