Content
# mcp-drift-detector
**Catch tool-definition drift in MCP servers before your agent trusts a mutated tool.**
[](https://github.com/shriramkv/mcp-drift-detector/actions/workflows/ci.yml)
[](LICENSE)
[](pyproject.toml)
An MCP server can change the tools it advertises at any time. It can widen an
input schema, quietly rewrite a tool's description to smuggle an instruction to
the model, or swap what a tool does entirely, all without the client noticing.
This is the *rug-pull* / tool-definition-mutation problem, and most agent
stacks approve a server once and never look again.
`mcp-drift-detector` gives you a simple, dependency-free way to pin a baseline
of what a server advertises and detect when that surface moves.
---
## What it does
- **Snapshots** an MCP server's advertised tools into a canonical, hashable form.
- **Hashes the semantic triple** of each tool: its description, input schema,
and output schema. Reordering keys or reformatting JSON does not trip a
false positive; changing meaning does.
- **Diffs** a current snapshot against a trusted baseline and classifies every
change: added, removed, description changed, input-schema changed,
output-schema changed.
- **Flags surface-widening changes** separately, because a widened input schema
or a mutated description is the shape of an attack, while a removed tool is
usually just housekeeping.
- **Returns CI-friendly exit codes** so you can gate a deploy on `--fail-on
widening` and let benign changes through.
No network calls, no dependencies outside the standard library. It reads a
captured `tools/list` response, so it works with whatever transport your client
already uses and is trivial to run in CI or against fixtures.
---
## Install
```bash
pip install mcp-drift-detector
```
Or from source:
```bash
git clone https://github.com/shriramkv/mcp-drift-detector
cd mcp-drift-detector
pip install -e ".[dev]"
```
---
## Quick start
Capture what a server advertises today as your trusted baseline:
```bash
mcp-drift snapshot \
--server payments-mcp \
--input tools_list.json \
--out baseline.json
```
Later (in CI, on a schedule, or before a deploy), snapshot it again and diff:
```bash
mcp-drift snapshot --server payments-mcp --input tools_list_now.json --out current.json
mcp-drift diff --baseline baseline.json --current current.json --fail-on widening
```
If a server has widened its tool surface, you get a clear report and a non-zero
exit code:
```
DRIFT DETECTED on payments-mcp
baseline: 2026-08-25T08:36:14+00:00
current: 2026-08-25T09:12:03+00:00
!! [input_schema_changed] read_file - added fields: exfiltrate_to
!! [description_changed] search_docs - tool description was modified
!! [description_changed] send_email - tool description was modified
3 change(s) widen the tool surface. Re-review before trusting this server.
```
> `tools_list.json` is a captured `tools/list` JSON-RPC response. Both the full
> envelope (`{"result": {"tools": [...]}}`) and a bare `{"tools": [...]}` object
> are accepted. See [`examples/`](examples/) for working fixtures.
---
## Exit codes
| Code | Meaning |
|------|---------|
| `0` | No drift (or `--fail-on none`) |
| `1` | Drift detected, but nothing widens the tool surface (e.g. a removal) |
| `2` | Surface-widening drift: a tool can now do or say more than the approved version |
`--fail-on any` (default) fails on 1 or 2. `--fail-on widening` fails only on 2.
---
## Use as a library
```python
import json
from mcp_drift import snapshot_from_tools_list, diff, render_text
baseline = snapshot_from_tools_list("payments-mcp", json.load(open("baseline_tools.json")))
current = snapshot_from_tools_list("payments-mcp", json.load(open("current_tools.json")))
report = diff(baseline, current)
if report.surface_widening_changes:
print(render_text(report))
raise SystemExit(2)
```
---
## Use in CI
```yaml
# .github/workflows/mcp-drift.yml
- name: Check MCP tool drift
run: |
mcp-drift snapshot --server payments-mcp --input tools_list_now.json --out current.json
mcp-drift diff --baseline baseline.json --current current.json --fail-on widening
```
Commit `baseline.json` to your repo, regenerate `tools_list_now.json` from your
running server, and the job fails the build the moment the served surface
widens past what you reviewed.
---
## Scope, stated honestly
This is a **detection** tool, not an enforcement gateway. It tells you *that*
a server's advertised surface changed and *how*. It deliberately does not:
- **Enforce policy or block calls at runtime.** For hardware-attested,
in-line enforcement of MCP tool calls, see
[cMCP](https://github.com/agentrust-io/cmcp). This tool is a lightweight,
standalone complement to that: run it in CI or on a schedule where a full
gateway is more than you need.
- **Judge whether a change is malicious.** A description change might be a
typo fix or an injected instruction. The tool surfaces the change and marks
whether it widens the surface; a human still decides.
- **Inspect tool *behaviour*.** It compares what a server *advertises*, not
what a tool actually does when called. A server can honour its advertised
schema and still misbehave.
- **Detect mid-session mutation on its own.** Drift is caught whenever you take
a fresh snapshot. Wire that into a schedule or a `tools/list_changed`
handler for tighter coverage.
Knowing the boundary is the point: a tool that overstates what it protects is
worse than one that states its limits plainly.
---
## Why the semantic triple
The `(description, inputSchema, outputSchema)` triple is what actually reaches
the model and shapes what a tool can be asked to do. A mutated description can
carry an instruction the model will follow; a widened input schema can add an
exfiltration channel; a changed output schema can alter what flows back into
the agent's context. Hashing these three, and nothing cosmetic, keeps the
signal high and the false positives near zero.
---
## Development
```bash
pip install -e ".[dev]"
ruff check src/ tests/ # lint
mypy src/mcp_drift # strict type check
pytest -q # tests
```
All three gate CI. Contributions welcome; please keep the standard-library-only
constraint on the runtime package.
---
## License
MIT. See [LICENSE](LICENSE).
---
*Built by [Shriram K. Vasudevan](https://github.com/shriramkv) as an Agentic AI
Foundation ambassador project, alongside contributions to the confidential-MCP
ecosystem.*
Connection Info
You Might Also Like
No recommendations available