What it detects
Offline auditor for MCP server OAuth discovery metadata.
Remote MCP servers that require sign-in publish two JSON documents: OAuth 2.0 Protected Resource Metadata (RFC 9728, at /.well-known/oauth-protected-resource) and Authorization Server Metadata (RFC 8414 or OpenID Connect discovery). MCP clients trust those documents to decide where to send users and tokens. mcpoauth reads saved copies and flags settings that the MCP authorization spec and OAuth 2.1 warn against: no PKCE S256, implicit or password grants, cleartext endpoints, tokens in URLs, a resource that does not match the server, broad scopes, and secrets or private keys published by mistake.
Standard library only. It never connects to a network; fetch the documents yourself (for example with curl) and point the tool at the files.
Rules
| Rule | Severity | What it flags |
|---|---|---|
| MO01 | critical | Authorization server does not advertise PKCE S256 (field missing, or no S256) |
| MO02 | high | PKCE plain method advertised |
| MO03 | high | Implicit flow (response_type containing token, or implicit grant) |
| MO04 | high | Resource-owner password grant |
| MO05 | high | http:// endpoint or identifier on a non-loopback host, including redirect_uris / post_logout_redirect_uris; file: / data: / javascript: schemes are flagged as critical |
| MO06 | medium | Issuer not https, or has a query or fragment |
| MO07 | high | bearer_methods_supported includes query (tokens in URLs) |
| MO08 | high / medium | Protected resource metadata missing authorization_servers (high) or resource (medium) |
| MO09 | high | resource does not match --resource-url (medium: has a fragment) |
| MO10 | medium | Broad scope advertised (*, all, admin, root, full_access, admin:org, delete_repo, write:packages, ...) |
| MO11 | critical | Secret or private key material in metadata (client_secret, private JWK members d/k/...) |
| MO12 | critical / high | Endpoint on a cloud-metadata or link-local address (critical) or private IP / internal hostname (high) |
| MO13 | info | Open dynamic client registration endpoint (MCP SHOULD support DCR; review confused-deputy risk for proxy servers) |
| MO14 | info | Inventory of metadata documents found |
| MO15 | critical | JWT none algorithm advertised for token or request signing |
Usage
python3 mcpoauth.py PATH [PATH ...] [--resource-url URL] [--json | --sarif]
[--min-severity LEVEL] [--fail-on LEVEL|none] [--disable RULE ...]
python3 mcpoauth.py --list-rules
# or, from a kit install:
agentsec mcpoauth examples/riskyAfter pip install of the kit, the same options work as agentsec mcpoauth …. Every tool also accepts --help and almost every tool accepts --json.
Example output
examples/clean shows the same server configured well and produces only the MO14 inventory line.
$ python3 mcpoauth.py examples/risky --resource-url https://mcp.example.com/mcp --min-severity high
CRITICAL MO01 examples/risky/oauth-authorization-server.json:9 /code_challenge_methods_supported PKCE methods ['plain'] do not include S256
CRITICAL MO11 examples/risky/oauth-authorization-server.json:12 /client_secret metadata contains 'client_secret' value (FAKE…00)
CRITICAL MO11 examples/risky/oauth-authorization-server.json:13 /jwks/keys/0/k JWK publishes private member 'k' (FAKE…00)
CRITICAL MO12 examples/risky/oauth-protected-resource.json:3 /authorization_servers/1 authorization_servers/1 points at a metadata address (169.254.169.254)
HIGH MO05 examples/risky/oauth-authorization-server.json:2 /issuer issuer uses cleartext http: http://auth.example.com?tenant=1
HIGH MO05 examples/risky/oauth-authorization-server.json:3 /authorization_endpoint authorization_endpoint uses cleartext http: http://auth.example.com/authorize
HIGH MO03 examples/risky/oauth-authorization-server.json:7 /response_types_supported implicit flow returns tokens in the browser redirect; OAuth 2.1 removes it
HIGH MO04 examples/risky/oauth-authorization-server.json:8 /grant_types_supported password grant lets a client collect user credentials; OAuth 2.1 removes it
...
SARIF and CI
agentsec mcpoauth path/to/input --sarif > mcpoauth.sarif
# GitHub Actions (composite action ships with the repo)
- uses: dunkleschitz/agentsec@v0
with:
tools: mcpoauth
paths: path/to/input
fail-on: high
SARIF 2.1.0 can be uploaded to GitHub code scanning. The composite action writes per-tool reports under agentsec-sarif/ by default.
OWASP mapping
Heuristic mapping of what this tool helps a reviewer look for. Not an official OWASP endorsement, and not complete coverage of any item. Sources: OWASP Top 10 for LLM Applications 2025 and OWASP Top 10 for Agentic Applications 2026.
- LLM06 Excessive Agency — OWASP LLM Top 10 (2025)
- ASI03 Identity and Privilege Abuse — OWASP Agentic Top 10 (2026)
Known limits
- It reads saved JSON only. It does not fetch metadata, follow
authorization_servers,
validate JWKS signatures, or test the live flow (redirect URI checks, token audience validation, consent screens). Those need a dynamic test.
- Document type is inferred from keys; an unusual document with neither
resource
nor standard authorization-server keys is ignored.
- MO09 compares scheme, host, and path (case-insensitive host, trailing slash ignored)
only when you pass --resource-url.
- The broad-scope list (MO10) is a short heuristic list, not a policy.
- Hostname classification for MO12 uses literal IPs and a few internal suffixes
(.internal, .local, .corp, ...); it does not resolve DNS.
Full source, tests, and fixtures: tools/mcpoauth/ in the repository. Install the kit with pip install git+https://github.com/dunkleschitz/agentsec.git.