mcp-supply-audit
# mcp-supply-audit
<!-- mcp-name: io.github.kimmawla/mcp-supply-audit -->
> Audits MCP servers as software artifacts: registry entries, packages, configs, tool definitions.
An MCP server. Static checks only. It never runs the code it inspects.
**Status: pre-release, in development.** Not yet published. Nothing here is a security guarantee.
## Why this exists
Most MCP security scanners focus on tool descriptions. This project looks at the server as a
supply-chain artifact: does the registry entry match its package and repo, is the name close to a
popular one, what do the package version's own metadata and advisories say, and what does the
config you are about to copy actually do.
## What works today
Six tools, served over stdio by a FastMCP server ([tool surface](docs/tool-surface.md)). All are
read-only and take names or pasted text, never a path.
- `audit_server`: runs the checks below that apply to one server (a registry name, or a package
name plus `pypi`/`npm`) concurrently under a 30 s deadline, and returns one merged report. A check
that fails or times out is listed as skipped; the rest of the report still returns.
- `audit_registry_entry`: an MCP Registry entry's repository consistency with its packages, owner
match, package and version existence, ownership markers, status, and remote URLs (as text).
- `audit_package`: one PyPI or npm package version's OSV.dev advisories, yanked or deprecated
state, install scripts (npm only), provenance, publish recency (PyPI only) and declared repository.
- `check_typosquat`: whether a name is close to one in a dated reference list (curated PyPI and
npm names; for registry names, also names crawled from the MCP Registry).
- `scan_mcp_config`: an MCP client config's servers, for unpinned runners, plain-http remotes,
hardcoded secrets, shell wrappers, broad paths, non-registry sources and risky env.
- `scan_tool_definitions`: checks a `tools/list` snapshot for hidden Unicode, instruction-like
phrasing, cross-tool references, exfiltration phrasing, encoded blobs, and other indicators.
Outbound requests go only to registry.modelcontextprotocol.io, pypi.org, registry.npmjs.org and
api.osv.dev.
## Quick start
Not on PyPI yet: these commands work once the package is published. Until then, run it
[from a source checkout](#from-a-source-checkout).
For Claude Code, with [uv](https://docs.astral.sh/uv/):
```bash
claude mcp add mcp-supply-audit -- uvx mcp-supply-audit
```
Pinned to one version (uvx's `package@version` form):
```bash
claude mcp add mcp-supply-audit -- uvx mcp-supply-audit@0.1.0
```
`scan_mcp_config` flags the first form in the configs it scans (CF001, unpinned package runner).
### From a source checkout
Run it from a checkout of this repository with [uv](https://docs.astral.sh/uv/):
```bash
cd mcp-supply-audit
uv sync
```
Then point an MCP client at the `mcp-supply-audit` console script in that checkout. For Claude
Code:
```bash
claude mcp add mcp-supply-audit -- uv --directory /path/to/mcp-supply-audit run mcp-supply-audit
```
For clients configured with JSON (e.g. Claude Desktop's `mcpServers`):
```json
{
"mcpServers": {
"mcp-supply-audit": {
"command": "uv",
"args": ["--directory", "/path/to/mcp-supply-audit", "run", "mcp-supply-audit"]
}
}
}
```
Then ask, for example, "Is the MCP server `<namespace>/<name>` safe to install?" and the client
can call `audit_server`. Read the report's skipped checks: a report with no findings is not proof
of safety.
## Design principles
- **Never executes a scan target.** Static analysis and metadata only. No tool accepts a filesystem path.
- **Hostile input by default.** Text from a target is sanitized, capped, and returned only as quoted
evidence under `untrusted_text`. Hidden characters are shown as visible `\uXXXX` escapes.
- **Deterministic rules, no LLM in detection.** Results are reproducible and unit-tested.
- **Findings are indicators, not verdicts.** Every report lists which checks ran and which were
skipped, and states that no findings is not proof of safety.
- **Mapped to the OWASP MCP Top 10** (beta, v0.1), by ID.
## Limits
Static checks cannot see runtime behavior. Audit and telemetry gaps, shadow servers, and context
over-sharing are outside what this tool can detect.
Per-check limits: [registry checks](docs/known-limits-registry.md),
[package checks](docs/rules-package.md#known-limits),
[typosquat checks](docs/rules-typosquat.md#known-limits-quoted-by-the-readme),
[config checks](docs/known-limits-config.md).
## Evaluation
No benchmark has been run yet.
## Development
```bash
uv sync
uv run pytest -q
uv run ruff check
uv run ruff format --check
```
## Security
To report a vulnerability in this project, see [SECURITY.md](SECURITY.md).
## License
MITTDQS
Scored across 6 tools
Each tool targets a distinct input artifact: a registry listing, a tools/list snapshot, a package name, a client config, a name for typosquat comparison, or the aggregate server audit. The descriptions explicitly state when to prefer another tool, and audit_server's overlap with the individual checks is disambiguated by its 'prefer another tool when the user wants only one specific check' guidance.
All names are lowercase snake_case with a verb_noun shape, so the pattern is predictable. The mix of audit_ (composite analysis), scan_ (pattern detection over pasted text) and check_ (single yes/no question) is meaningful rather than arbitrary, though three different verbs is a minor deviation from a single consistent verb.
Six tools map cleanly onto the five distinct supply-chain inputs plus one aggregate entry point, with no redundant members. Each tool earns its place and the count is well within a comfortable range.
The surface covers registry entries, package metadata, typosquatting, tool-definition poisoning, client configs, and an aggregator that composes them with documented limits and skipped-check reporting. Minor gaps exist around server runtime/behavioral auditing and bulk multi-server sweeps, but these are outside the stated static-audit scope and are workable around.