Skip to main content

mcp-authscan logo

VATA, MCP Security Scanner

Tracked by Cyber Toolchain

Auth scanner for live MCP servers.

VATA checks the security posture of Model Context Protocol deployments. It runs in two modes.

Live endpoint assessment (active). Given an MCP server URL, mcp-authscan sends real HTTP requests to that running server and records what comes back. It discovers the OAuth surface through RFC 9728 protected-resource metadata and RFC 8414 authorization-server metadata, then runs its declared set of eleven authorization-server probes. All eleven are implemented: issuer identity, resource binding, token endpoint Cache-Control, token endpoint CORS, JWKS key types, password grant rejection, PKCE S256 (pkce-s256), redirect URI exact match (redirect-uri-exact), client authentication, cross-tenant isolation and token audience. PKCE S256 needs a client id and redirect URI exact match also needs that client's registered redirect URI. The scanner takes them, in order, from dynamic client registration at the advertised registration_endpoint (with a scanner-chosen loopback redirect, never fetched, tried in order http://localhost/mcp-authscan/callback, http://127.0.0.1/mcp-authscan/callback, http://[::1]/mcp-authscan/callback, moving to the next only when the server refuses the redirect with invalid_redirect_uri; before the report prints the client is deleted by RFC 7592 DELETE on its registration_client_uri when the registration returned one with a registration access token, and otherwise the scanner prints one line naming the client_id: client removal is out of scope for this tool; the registered client is never sent to the token revocation endpoint. The report states whether a client was registered, the candidates tried and the one accepted, and the client removal result; a client from the environment reads "no client was registered by this run"; the client secret and registration access token are never printed), else from MCPAUTHSCAN_CLIENT_ID and MCPAUTHSCAN_REDIRECT_URI; without either each reports EXCLUDED, with the limitation "No client_id to build an authorization request (MCPAUTHSCAN_CLIENT_ID not set)" or "No registered redirect_uri to compare against". Either also reports EXCLUDED ("No authorization endpoint") when the server metadata publishes no authorization endpoint. Both send GET authorization requests, never follow a redirect, and never request a returned Location. With --acquire-token (off by default) the token endpoint CORS probe first obtains a real access token through an attended authorization-code exchange with PKCE S256 (same client source): the scanner prints an authorization URL to stderr, you open it in a browser and approve, then paste back the full address the browser lands on (the loopback redirect; the page not loading is expected). The scanner checks that address against the registered redirect and the state it sent, redeems the code at the token endpoint with a hostile Origin, tests the cross-origin read on that response, and revokes the token before the report prints (RFC 7009: token, token_type_hint, client_id and client_secret, empty for a public client). If a token's revocation is not a 2xx or no revocation_endpoint is advertised, the scanner prints [!] token revocation not confirmed with the token type (never its value) and the server's answer, and the report says Token Status: revocation not confirmed; otherwise it says Token Status: revoked, or Token Status: none acquired. The token, verifier, code and pasted address are never printed or stored. An empty paste, a refusal (error=access_denied), a different redirect or a state mismatch reports NOT_OBSERVED with a named limitation; there is one prompt and no retry. Without the flag the scanner never prompts or reads stdin. See PROBE_CONTRACT.md [4].

Repository scan (static). Given a GitHub repository URL, it reads the source instead of sending requests.

Want your own MCP server reviewed?

I run authorised, evidence-backed security reviews of MCP server deployments. You get a written report with every finding by severity, the request and response behind each one, and an explicit list of what was not tested, including probes not yet built.

Two ways in: a single-server review at $1,750, or a full assessment at $3,500 including one retest after you fix.

I only scan servers I hold written permission to test. Email lhmisme2011@gmail.com with the endpoint and who authorises it.

Authorized use

Every scan needs an authorization basis. The CLI refuses to start without --authorized-by, --auth-basis and --contact, and that validation runs before any network call, so an unauthorized run exits 2 having sent nothing.

Only point this at systems you own or are permitted to test.

Install

Python 3.11 or newer.

pip install mcp-authscan

Or from source:

git clone https://github.com/LHMisme420/mcp-authscan.git
cd mcp-authscan
pip install -e .

Build from source: python3 -m build

Run

mcp-authscan https://api.example.com/mcp \
  --authorized-by "Your Name" \
  --auth-basis "written consent" \
  --contact "you@example.com"

What a probe reports

Every probe returns one record: status, HTTP status, the response headers captured, the body sha256, whether its prerequisite was met, and its declared limitation. A FAIL cannot be constructed without a severity and the endpoint it was observed on. EXCLUDED requires prerequisite_met: false plus a named prerequisite.

Statuses are PASS, FAIL, NOT_OBSERVED, EXCLUDED and NOT_IMPLEMENTED.

  • PASS: the response positively demonstrated the property.
  • FAIL: the response demonstrated the property is absent or broken.
  • NOT_OBSERVED: the request went out and the property was not demonstrated. A 404, 405, 5xx, timeout or HTML body is not a pass.
  • EXCLUDED: a prerequisite, such as a token endpoint, a jwks_uri or a metadata document, was missing, or the target was outside the authorized scope, so the probe was not sent. The summary prints each EXCLUDED probe on its own line with its reason, e.g. jwks-keys: No JWKS endpoint. Token endpoint CORS also reports EXCLUDED for a 4xx token response with no OAuth error body.
  • NOT_IMPLEMENTED: the probe is not built yet and sends no request. No probe in the current set reports this.

Coverage is PASS plus FAIL over the testable probes, the declared eleven less any EXCLUDED (prerequisite missing, never sent). A run without client credentials excludes pkce-s256 and redirect-uri-exact, so when the other seven reach a verdict it reads 7 of 7 testable (1.00), 2 excluded, and the report names each excluded probe with its missing prerequisite. Zero testable probes give no coverage figure and exit 5. Not observed means sent and not demonstrated.

Exit codes

  • 0 clean, no FAIL at or above --fail-on and no in-scope probe without a verdict
  • 1 FAIL at or above --fail-on
  • 2 authorization validation failed, no network requests made
  • 3 target unreachable
  • 4 target is not a valid MCP server
  • 5 incomplete coverage, at least one in-scope probe with no verdict (NOT_OBSERVED)
  • 6 egress refused, a discovered host was not in the authorized set

A FAIL at or above the threshold outranks incomplete coverage, so a run with both exits 1.

Validation

The negative control is run locally against the fixture on port 9002. It is not currently wired into CI: the cors-scanner-tests job does not run this fixture.

Known limits

  • The eleven probes assess the authorization server. They do not test the MCP resource endpoints themselves.
  • An endpoint prober cannot verify a three-party OAuth flow. Issuer binding is enforced client-side, so the probe checks what the metadata declares, not what a client will do.
  • PKCE S256 and redirect URI exact match judge only the authorization response. They never follow a redirect or exchange a code, so they cannot show what a client does with the code.
  • No dependency CVE check, no secrets-in-history scan.
  • Source mode does not detect a hardcoded token whose value contains whitespace, such as "Bearer <token>" or "Token <token>". The hardcoded-credential rule deliberately skips values with whitespace so error strings are not flagged. A literal like api_key = "<token>" is detected.
  • Source mode does not detect wildcard CORS with credentials allowed (Access-Control-Allow-Origin: * together with Access-Control-Allow-Credentials: true) set in code. Only the live token-cors probe tests CORS, against a running endpoint.
  • Without --acquire-token, a server that properly requires client authentication at its token endpoint reports token-cors NOT_OBSERVED. The plain token-cors probe sends no client credentials, such a server answers 401 invalid_client, and only a 2xx token response can reach a verdict. A full token-cors verdict on such a server needs an attended --acquire-token run (PROBE_CONTRACT.md [4]). The converse also holds: a token-cors FAIL from a plain run means the server returned a 2xx token response to a request that carried no client authentication.
  • Absence of a finding is not proof of safety.

A version string is a claim. A response from the running server is a receipt.

Published findings

Credited reporter on GHSA-v693-3rfr-6q3q, critical DNS rebind protection bypass in microsandbox, fixed in 0.6.3. Credited reporter on GHSA-vqqp-x5qm-5qp4, client credentials redirected to an attacker-controlled authorization server in fastmcp, fixed in 4.1.2. Credited reporter on CVE-2026-60085, sandbox policy bypass in PraisonAI, rated high. Credited reporter on GHSA-q8pm-q3r2-q7cg, argument injection in mcp-shell-server.

Findings from the official MCP reference servers were reported to the maintainers in October 2026 and are awaiting publication. That analysis is pinned to modelcontextprotocol/python-sdk commit 9972c21 and package 2.2.0.

Versions

Current source version 1.1.9 (latest PyPI release 1.1.8) and probe set 2026.11.01.4 are versioned separately, so adding a probe never rewrites how a past report reads. A probe set stamp is YYYY.MM.DD.N, the date (America/New_York) the probe set changed plus a per-day counter; a test fails if the probe set changes and the stamp does not. Earlier date-only stamps and the four that were mis-dated are listed in CHANGELOG.md under "Probe set stamps".

About

Independent AI security research by Leroy H. Mason, who also publishes as Elior Malak. GitHub: LHMisme420 · ORCID: 0009-0002-3115-534X Contact: lhmisme2011@gmail.com

Metadata

Release files for mcp-authscan 1.1.9

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-authscan 1.1.9
File Size Uploaded Provenance Actions
mcp_authscan-1.1.9.tar.gz 187.9 kB Without provenance Details for mcp_authscan-1.1.9.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for mcp-authscan 1.1.9
File Interpreter ABI Platform Actions
mcp_authscan-1.1.9-py3-none-any.whl Python 3 none any Details for mcp_authscan-1.1.9-py3-none-any.whl

Total release size: 294.5 kB

Release files / mcp_authscan-1.1.9.tar.gz

Download URL mcp_authscan-1.1.9.tar.gz
Size 187.9 kB
Tags Source
SHA-256 checksum
How to use checksums
8135333cbab4945c9886f831639afd37e754fb0cc9b6c36c4370fffb153b9559
BLAKE2b-256 checksum
How to use checksums
602b665fe464375b59b2a9582bfccffc835d13e019c93be7dafeaf58e34bb2c3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release files / mcp_authscan-1.1.9-py3-none-any.whl

Download URL mcp_authscan-1.1.9-py3-none-any.whl
Size 106.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
96fa76fb8cdad1e93df1e27f481e533588cc4adb0759062e5c0ed19554af833f
BLAKE2b-256 checksum
How to use checksums
32a69646bcd0472f271a3f15bd10da2dcdb63c48579bd3242d9a4cf95b2a612b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Security for mcp-authscan 1.1.9

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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