Skip to main content

mcp-posture

CI Action self-test PyPI License: MIT

Security posture scanner for remote MCP servers. Point it at a Streamable HTTP endpoint and it audits the OAuth setup (MCP Authorization spec, RFC 9728, RFC 8414, PKCE, RFC 8707, Client ID Metadata Documents), transport hardening and the tool surface (poisoning, shadowing, rug pulls), with spec-cited findings and CI-native output (SARIF, JSON, Markdown).

Only scan servers you own or are authorized to test.

Why

Most MCP scanners inspect local client configs and stdio servers. mcp-posture focuses on remote servers and what an attacker sees from the outside before logging in: how the server challenges, which authorization server it trusts, whether that server enforces PKCE S256, whether tokens are audience-bound, whether the tool descriptions hide instructions. Every finding cites the spec section and the MCP revision it applies to (2025-03-26 through 2026-07-28).

Quickstart

# no install needed (or: pipx install mcp-posture / uv tool install mcp-posture)
uvx mcp-posture scan https://mcp.example.com/mcp

# machine-readable outputs, fail the build on high or worse
mcp-posture scan https://mcp.example.com/mcp --sarif results.sarif --markdown summary.md --fail-on high

# lint your own client's Client ID Metadata Document
mcp-posture cimd lint https://app.example.com/oauth/client.json

# scan every remote server declared in your MCP clients (.mcp.json, Claude, VS Code, Cursor, Windsurf)
mcp-posture discover
mcp-posture scan --from-client-config auto

# list tools behind authentication: log in to your own server in the browser (token never shown)
mcp-posture scan https://mcp.example.com/mcp --login
# ... or pass a token through the environment, never on the command line
MCP_TOKEN=... mcp-posture scan https://mcp.example.com/mcp --token-env MCP_TOKEN

Docker (distroless, nonroot, signed with cosign):

docker run --rm ghcr.io/batou9150/mcp-posture scan https://mcp.example.com/mcp

GitHub Action (SARIF to code scanning, Markdown to the job summary):

- uses: batou9150/mcp-posture@main   # pin a tag or SHA
  with:
    targets-file: mcp-servers.txt
    fail-on: high

Claude Code skill: the scanner plus a semantic review of tool descriptions, prioritization and remediation snippets for common authorization servers and gateways.

/plugin marketplace add batou9150/mcp-posture
/plugin install mcp-posture@mcp-posture

Then ask "audit the security of https://mcp.example.com/mcp".

mcp-posture scanning local fixtures

Sample output (local misconfigured fixture, scripts/demo_servers.py):

http://127.0.0.1:8402/mcp
  spec 2025-06-18 (negotiated) · transport streamable-http · auth not required
critical MCPP-ASM04   code_challenge_methods_supported ['plain'] does not include S256.
high     MCPP-ASM02   Metadata declares issuer 'http://127.0.0.1:8402/', expected 'http://127.0.0.1:8402'.
high     MCPP-AUTHN01 tools/list answered without credentials (2 tool(s) including delete_note).
high     MCPP-PRM06   bearer_methods_supported includes `query`.
high     MCPP-TRN08   Session IDs look sequential (numerically close across two sessions).
medium   MCPP-PRM03   resource '.../mcp/' does not match '.../mcp' (trailing slash only).
...

What it checks

Family Checks Covers
TRN 11 HTTPS, TLS version and certificate, HTTP→HTTPS, HSTS, legacy SSE, session IDs in URLs, Mcp-Session-Id entropy, metadata headers, endpoint redirects
AUTHN 6 Anonymous tools/list, Bearer challenge, resource_metadata discovery, error leakage
PRM 11 RFC 9728 Protected Resource Metadata: presence, resource exact match, authorization servers, query tokens, scopes, signed metadata
ASM 13 RFC 8414 / OIDC metadata: issuer match, HTTPS endpoints, PKCE S256 / plain, implicit and password grants, DCR, iss (RFC 9207)
CIMD 10 Client ID Metadata Document support and registration strategy; cimd lint rules for your own client's document
SCP 3 Over-broad scopes, missing scope in challenges, PRM/AS scope consistency
TOOL 10 Instruction-like text, invisible/bidi/tag Unicode, encoded blobs, shadowing, secret paths and exfil URLs, annotations, unconstrained URL/path/code inputs, confusable names, nesting too deep to inspect
PIN 4 Rug pulls: tools/prompts/resources added, removed or changed since mcp-posture pin

mcp-posture checks list prints the catalogue; mcp-posture checks show MCPP-ASM04 explains one check (rationale, remediation, references). Check IDs are stable and never reused. The research behind the catalogue, with every MUST/SHOULD mapped to a check, is in docs/spec-notes.md.

The default mode is passive: metadata GETs plus the standard MCP handshake and list calls. No tool is ever called. Behaviours that only an active probe could confirm (Origin validation, token audience enforcement, PKCE enforcement) are listed as not verified in docs/spec-notes.md.

How it works

flowchart LR
  T[Targets<br/>URLs · targets file · client configs] --> C[Collect<br/>hardened HTTP client]
  C --> P[MCP prober<br/>server/discover → initialize → SSE]
  C --> D[OAuth discovery<br/>401 challenge · PRM · AS metadata · TLS]
  P & D --> X[Immutable scan context]
  X --> K[Checks<br/>pure functions, registry]
  K --> S[Suppressions · baseline]
  S --> R[Reports<br/>table · JSON · SARIF · Markdown]

Each target is collected once (all network I/O, through a client that refuses private addresses at connect time), frozen into a context, then every check runs as a pure function over it. A failing check becomes an MCPP-ERR00 finding instead of aborting the scan.

CI usage

# mcp-posture.toml (CLI flags override it)
[scan]
targets_file = "mcp-servers.txt"
fail_on = "high"
baseline = "mcp-posture.lock.json"   # rug-pull detection, created with `mcp-posture pin`
disable = ["ASM08"]
# .mcp-posture-ignore: every suppression needs a justification; expired ones resurface
[[ignore]]
check = "MCPP-ASM09"
target = "https://mcp.example.com/*"
justification = "Open DCR is intended: public client registry"
expires = 2026-12-31

SARIF results are anchored to the line of the file that declares each target (targets file, mcp-posture.toml, .mcp.json), so GitHub code scanning can display them.

Exit code Meaning
0 No finding at or above --fail-on, every target reachable
1 At least one unsuppressed finding at or above --fail-on
2 Usage or configuration error
3 A target was unreachable (and no blocking finding)

Safety of the scanner

  • Tokens are read only from an env var, a file or stdin, and are redacted from every output and log.
  • Private, loopback, link-local and cloud-metadata addresses are refused at connect time (after DNS resolution, every redirect hop included) unless --allow-private is passed.
  • Credentials are never forwarded across origins; response size and time are capped.
  • Server-controlled text is neutralized in reports: no raw control, bidi or invisible characters reach your terminal or PR comments.
  • No telemetry.

Compared with other MCP scanners

Several good tools exist; they solve different problems. Facts as of 2026-10 (see docs/spec-notes.md for the survey):

mcp-posture Snyk agent-scan Cisco mcp-scanner Ramparts MCPJam OAuth conformance
Focus remote server posture local agent configs and tools configs, stdio and remote servers servers, configs, skills live OAuth flow conformance
OAuth / PRM / AS metadata audit (pre-login) yes, per RFC, per MCP revision no no no yes (needs a login)
Tool poisoning analysis regex heuristics (+ semantic review via the skill) remote API analysis YARA + LLM YARA + LLM no
Rug-pull pinning yes signatures no yes no
SARIF yes no no yes no (JUnit)

Pair mcp-posture with a runtime proxy (e.g. mcp-context-protector) for enforcement; it reports, it does not block.

Responsible use

Scan only servers you own or have written permission to test. Passive mode sends a handful of standard requests; even so, unsolicited scanning of third-party infrastructure may violate their terms or the law.

Contributing and security

See CONTRIBUTING.md (how to add a check) and SECURITY.md (reporting vulnerabilities, verifying release signatures and attestations).

Development

uv sync
uv run ruff check && uv run ruff format --check && uv run mypy && uv run pytest
uv run python scripts/demo_servers.py   # local fixtures to scan with --allow-private
uv run --group docs mkdocs serve         # docs site

License

MIT © Baptiste PIRAULT

Metadata

Release files for mcp-posture 1.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mcp-posture 1.1.1
File Size Uploaded
mcp_posture-1.1.1.tar.gz 295.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mcp-posture 1.1.1
File Interpreter ABI Platform
mcp_posture-1.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 405.5 kB

Release files / mcp_posture-1.1.1.tar.gz

Download URL mcp_posture-1.1.1.tar.gz
Size 295.8 kB
Tags Source
SHA-256 checksum
How to use checksums
69af2bcaa67ca8d579011d8e119a681c3c068dcd41523548d585bd84c3444a7b
BLAKE2b-256 checksum
How to use checksums
480d9519851499fec35fb7c32211008e89be4b4fb661ada42b4cce90a68f2460
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

Release files / mcp_posture-1.1.1-py3-none-any.whl

Download URL mcp_posture-1.1.1-py3-none-any.whl
Size 109.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
28beb97b25bdad0fe37cad4330674291128194dc4dbf5cb21d8a1fc847a006b0
BLAKE2b-256 checksum
How to use checksums
3496790bbdbc4a7572ca9075c45cf4f19585c260f5f9a703d4dcf94b4996c118
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.1.1 This release

2 release files

1.1.0

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page