Content
# mcp-server-audit
**Static security and hygiene audit for MCP servers.** Point it at any Model Context Protocol server (or an offline dump of its tool definitions) and get a ranked report of tool-poisoning indicators, lax schemas, misleading annotations, and high-impact capabilities. Drop it into CI and block merges on critical findings.
```
mcp-server-audit — docker run -i --rm mcp/github (32 tools)
┌──────────┬───────────┬──────────────────┬──────────────────────────────────────────────┐
│ Severity │ Check │ Tool │ Finding │
├──────────┼───────────┼──────────────────┼──────────────────────────────────────────────┤
│ CRITICAL │ POISON001 │ summarise_issue │ Possible tool poisoning: user concealment │
│ WARNING │ SCHEMA002 │ create_issue │ Schema allows additionalProperties │
│ INFO │ ANNOT001 │ list_repos │ No tool annotations declared │
└──────────┴───────────┴──────────────────┴──────────────────────────────────────────────┘
Summary: 1 critical, 1 warning, 1 info
```
## Why
MCP has become the standard way to give AI agents tools, and ecosystems like the Docker MCP Catalog now host hundreds of servers. But an MCP server's tool *descriptions are executed by the model*: a malicious or compromised server can bury instructions in a description ("do not tell the user...", zero-width Unicode payloads), mislabel a destructive tool as read-only to dodge confirmation prompts, or declare schemas so loose that anything goes.
Runtime gateways and sandboxes are the enforcement layer. **mcp-server-audit is the pre-deployment layer**: a fast, static, read-only inspection you can run before wiring a server into your agents, and again on every update to catch rug-pulls.
The audit never calls any tools. It only reads what the server declares.
## Install
```bash
pip install mcp-server-audit
# or from source
pip install git+https://github.com/shriramkv/mcp-server-audit
```
## Usage
**Audit a containerised server over stdio** (works with anything from the Docker MCP Catalog):
```bash
mcp-server-audit stdio "docker run -i --rm mcp/github"
```
**Audit a streamable-HTTP endpoint:**
```bash
mcp-server-audit http http://localhost:8000/mcp -H "Authorization=Bearer $TOKEN"
```
**Audit an offline JSON dump** of a `tools/list` result:
```bash
mcp-server-audit file examples/poisoned-tools.json
```
**Output formats and CI mode:**
```bash
mcp-server-audit file tools.json --output json
mcp-server-audit file tools.json --output markdown --out-file audit-report.md
mcp-server-audit stdio "docker run -i --rm mcp/github" --fail-on warning # exit code 2 on findings
```
## Checks
| ID | Severity | What it catches |
|---|---|---|
| POISON001 | Critical | Instruction-injection phrases in descriptions or schemas (instruction override, user concealment, cross-tool coercion, context exfiltration, credential solicitation) |
| POISON002 | Critical | Hidden/zero-width Unicode characters that conceal payloads from human reviewers |
| POISON003 | Warning | Long base64-like encoded blobs in tool metadata |
| ANNOT002 | Critical | Destructive-looking tool annotated `readOnlyHint=true` (rug-pull indicator) |
| SRV001 | Critical | Duplicate tool names (shadowing) |
| SRV002 | Warning | Near-duplicate tool names (tool-confusion) |
| SCHEMA001 | Warning | Tool declares no input schema |
| SCHEMA002 | Warning | `additionalProperties: true` (accepts undeclared arguments) |
| CAP001 | Warning/Info | High-impact capabilities: exec/shell, destructive ops, privilege escalation, network egress |
| DESC001-003 | Warning/Info | Missing, trivially short, or suspiciously long descriptions |
| SCHEMA003-004 | Info | Undocumented or unconstrained sensitive-looking parameters |
| ANNOT001 | Info | No MCP tool annotations declared |
| SRV003 | Info | Oversized tool surface (context-window bloat) |
## CI example (GitHub Actions)
```yaml
- name: Audit MCP server
run: |
pip install mcp-server-audit
mcp-server-audit stdio "docker run -i --rm my-org/my-mcp-server" --fail-on critical
```
## Scope and honest limitations
This is a static heuristic scanner, not a proof of safety. It cannot detect a server that lies about its tools at call time, behaves differently per client, or turns malicious after passing an audit (pin versions and re-audit on update). Pair it with runtime controls: containerised isolation, a gateway with policy enforcement, and least-privilege credentials.
## Related work
- [MCP specification](https://modelcontextprotocol.io) — the protocol itself, including tool annotations
- [mcp-guardrail-gateway](https://github.com/shriramkv/mcp-guardrail-gateway) — runtime policy enforcement, the complement to this pre-deployment audit
- MCP Inspector — interactive debugging UI; this project instead targets non-interactive, CI-friendly security checks
## Contributing
New heuristics are very welcome, especially real-world poisoning patterns. Add the pattern to `checks.py`, a fixture to `tests/`, and open a PR.
## Licence
MIT