Metadata-Version: 2.4
Name: sovereign-mcp-audit
Version: 1.0.0
Summary: Probe an MCP server for input it should refuse. Connects as a client, inventories the tools, and reports what was accepted.
Author-email: Mattijs Moens <contact@sovereign-shield.net>
License-Expression: Apache-2.0
Project-URL: Homepage, https://sovereign-shield.net
Project-URL: Source, https://github.com/mattijsmoens/sovereign-mcp-audit
Project-URL: Issues, https://github.com/mattijsmoens/sovereign-mcp-audit/issues
Keywords: mcp,model-context-protocol,security,audit,scanner,ssrf,input-validation,ai-agents
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Security
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: mcp>=1.29
Dynamic: license-file

# sovereign-mcp-audit

**Find out what your MCP servers will accept.**

You have connected some MCP servers to an agent that holds your credentials. Can you say, right now, which tools it can call, which of them change state, and what those tools will accept as input?

This answers that in about a minute, against servers you already run.

```bash
pip install sovereign-mcp-audit
sovereign-mcp-audit -- mcp-server-sqlite --db-path ./app.db
```

Apache 2.0. No configuration, no account, nothing to sign up for.

---

## What it does

It connects to your server as an ordinary MCP client, reads the tool list, works out which tools change state, and then sends every field the sort of input a careless or hostile caller sends. Then it reports what came back **accepted**.

```
TOOL INVENTORY  (5 tools)
  tool                         risk     parameters
  read_query                   READ     query
  write_query                  WRITE    query
  list_tables                  READ     (none)
  describe_table               READ     table_name
  append_insight               WRITE    insight

FINDINGS  (35 probes sent, 7 with an observable effect)

  HIGH  (5)
    describe_table.table_name  <- sql_metacharacters
        SQL metacharacters accepted - check whether this field is
        interpolated into a query
```

**State-changing tools are skipped** unless you ask for them with `--include-writes`. Nothing it sends asks a tool to destroy anything.

## What it looks for

| class | CWE |
| --- | --- |
| SQL metacharacters, shell metacharacters, path traversal, NUL bytes | CWE-89, CWE-78, CWE-22 |
| Server-side request forgery against cloud metadata, loopback and private ranges | CWE-918 |
| State handles accepted without an ownership check | CWE-639 |
| Errors that disclose queries, stack traces, paths or credentials | CWE-209 |
| Prompt injection accepted verbatim into text a model will read | |
| Missing length and range limits, undeclared parameters, type confusion | |

SSRF and state-handle probes are only sent to fields that look like they take a URL or a handle. Firing a cloud-metadata address at every string field would produce noise, not findings.

## What it does *not* do

This is a black-box prober. It does not read a line of your server's source, so it cannot see the defect class that matters most: **a security check that is present, is called, and silently does nothing.** Finding those means reading each implementation and proving every control can actually fail.

It also cannot see anything in the authorization layer — token audience validation, confused-deputy problems in OAuth proxies, mix-up attacks. Those happen before a tool call exists.

**A clean run here is the beginning of an audit, not the end of one.**

## Accepted is not vulnerable

A field that legitimately takes free text accepting free text is its contract, not a defect. Every probe is compared against a benign baseline, and counts only if the response actually differs or the payload comes back out. Probes that were accepted but changed nothing are counted separately, under *accepted but with no observable effect*, so the number stays honest in both directions.

This matters more than it sounds. An earlier version of this tool reported five HIGH findings against a published server whose every response was `{"entities": [], "relations": []}` — a search that found nothing and said so. Reporting that as a vulnerability is how a security tool gets uninstalled.

Refusals written into the response body are recognised too. `[DENIED]`, `Invalid library ID format`, `No matches found` are servers doing their job. Unless the refusal leaks while refusing: `Invalid lookup. Query was: SELECT ...` declines the request and hands over the SQL in the same breath, and that is still a finding.

## Usage

```
sovereign-mcp-audit [options] -- <command to launch the server>

  --include-writes   Also probe state-changing tools. Off by default,
                     because a permissive server will perform the operation.
  --dry-run          Inventory and classify the tools; send no probes.
  --json PATH        Write the full report as JSON.
  --timeout SECONDS  Per-call timeout (default 20).
  --quiet            Suppress progress output.
```

Exit code is `0` when nothing was found, `1` when something was, so it fits in CI.

Examples:

```bash
# a server from PyPI
sovereign-mcp-audit -- python -m mcp_server_git --repository /tmp/scratch

# one from npm
sovereign-mcp-audit -- npx -y @modelcontextprotocol/server-filesystem /tmp/sandbox

# see the tool inventory without sending anything
sovereign-mcp-audit --dry-run -- npx -y @your/server
```

## Auditing servers you do not control

Probing a **hosted** service you do not own may be unlawful where you are, and it is not what this is for. Point it at servers you run: the ones in your own Claude Desktop, Cursor or VS Code configuration, and the packages you have installed locally.

If you find something real in someone else's package, tell the maintainer before you tell anyone else.

## Licence

Apache 2.0. It contains none of the patented technology in the SovereignShield libraries, and its job is to tell you what your agents can reach, which is worth more unrestricted than licensed.

The rest of the family — [sovereign-shield](https://pypi.org/project/sovereign-shield/), [intentshield](https://pypi.org/project/intentshield/), [logicshield](https://pypi.org/project/logicshield/), [sovereign-mcp](https://pypi.org/project/sovereign-mcp/) and [sovereign-mcp-gateway](https://pypi.org/project/sovereign-mcp-gateway/) — is source-available under the Business Source License 1.1. This one is not.

## If the answer is "quite a lot"

If the inventory surprises you, or the findings do, the gateway is the thing that puts a check in front of those calls rather than telling you about them afterwards:

```bash
pip install "sovereign-mcp-gateway[all]"
sovereign-mcp-gateway --init
```

[sovereign-shield.net](https://sovereign-shield.net)
