Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions docs/MCP-EXPOSURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Should devrepro-doctor expose an MCP server?

**Verdict: yes — the strongest case of the four sibling projects — for the
read-only commands only, and never for `fix`.**

This is an assessment, not a feature. Nothing here ships today.

## Why this project fits better than the others

*"Why won't this build on my machine?"* is a question agents get asked
constantly and answer badly, because the honest answer requires looking at the
machine. This tool looks at the machine and reports evidence.

Two properties it already has make the fit unusually good:

- **Read-only is the design, not a mode.** The scanning path does not modify
anything; that is the project's stated promise, not a flag. An MCP tool built
on it inherits that.
- **Output is already sanitized.** `devrepro/privacy/` redacts before anything
is serialized, and `tests/test_privacy.py` holds it there. An MCP server
hands its output to a model, which may forward it anywhere; a diagnostics
tool without that gate would be a genuinely bad thing to expose.

The second point is the one worth dwelling on. Most machine-inspection tools
are unsafe to put behind an agent precisely because their output is full of
paths, usernames, tokens and environment variables. This one already treats
that as a correctness property.

## What would be exposed

| Tool | Answers |
|---|---|
| `doctor` | What is wrong with this machine, with evidence |
| `check` | Does this machine meet this project's declared requirements? |
| `info` | What is installed, and which versions? |
| `which` / `path` | Which binary actually resolves, and why that one? |
| `preflight` | Is this machine ready before a long build? |
| `diff` | Why does it work there and not here? |
| `snapshot` | Produce a sanitized manifest to hand to someone else |
| `rules` | What is checked, and what does each finding mean? |

Every one already supports `--json`, so the MCP result schema is the existing
`ScanReport`.

## What must never be exposed

**`fix`.** It executes remediations, gated on an explicit `--yes`. That gate
exists because the project's rule is that nothing above LOW risk is applied
without a human agreeing. An MCP tool call has no human in it: the model
decides to call it. Exposing `fix` would move the confirmation from a person to
a model, which is precisely the thing the gate was built to prevent.

`serve`, `server-backup` and `server-restore` are out for the same reason as
elsewhere: they start processes or move data.

## What has to be true first

1. **Exit codes do not survive the translation.** MCP returns structured
content, not a process status, and this project's most important signal is
`BLOCKED = 2` — which, as [EXIT-CODES.md](EXIT-CODES.md) records, means
something entirely different in the sibling projects. The tool result would
need to carry the verdict explicitly rather than relying on a convention
that does not cross the boundary.
2. **Scan cost.** A full `doctor` run takes seconds and shells out repeatedly.
That is fine for a CLI and slow for an interactive tool call; the server
would want a cached report with an explicit refresh.
3. **Confinement.** `check --project` takes a path. Same concern as anywhere
else: the server needs a configured root rather than trusting an argument.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ nav:
- Plugins: PLUGINS.md
- Privacy: PRIVACY.md
- Exit codes: EXIT-CODES.md
- MCP exposure: MCP-EXPOSURE.md
- Releasing: release.md
- Project:
- Contributing: contributing.md
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ ignore = [
]

[tool.ruff.lint.per-file-ignores]
"scripts/check_docs_site.py" = ["S603", "RUF100"]
# Same shared-script situation: the gh endpoint is built from the hardcoded
# REPOS list above it, not from user input.
"scripts/fetch_competitor_meta.py" = ["S603", "S607", "RUF100"]
Expand Down