OAuth Discovery Chain Integrity (RFC 9728 → RFC 8414)
What it checks
Walks the full credential-free authorization discovery path an MCP client must traverse — 401 challenge, WWW-Authenticate resource_metadata, Protected Resource Metadata document, authorization server metadata — and asserts every MUST-level validation gate the client will apply. Ends before any token is requested, so it needs no credentials.
Why it matters
The spec makes RFC 9728 mandatory for MCP servers, and makes clients apply two hard identity checks. RFC 9728 §3.3 requires the PRM’s resource value to be string-identical to the resource identifier used to construct the request URL. The MCP AS-discovery rules require the fetched AS metadata’s issuer to be string-identical to the issuer used to construct the well-known URL. On either mismatch the client MUST NOT use the metadata.
MCP additionally strengthens RFC 9728 by requiring authorization_servers to carry at least one entry (it is merely OPTIONAL in the RFC). Each of these is a silent, total blocker. The discovery chain either resolves end to end, or the agent never reaches an authorization prompt. A single character of drift between the deployed endpoint URL and the resource claim makes the server unusable to every conforming client — while the server’s own logs show nothing but 401s.
Evidence
- MCP Specification 2026-07-28 — Authorization — Model Context Protocol (spec, URL verified 2026-08-20)
- ‘MCP servers MUST implement OAuth 2.0 Protected Resource Metadata (RFC9728).’ Authorization servers MUST provide RFC8414 or OIDC Discovery. Servers SHOULD include a scope parameter in the WWW-Authenticate challenge. Example verbatim:
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource", scope="files:read". Insufficient scope -> 403 witherror="insufficient_scope". Servers SHOULD NOT include offline_access in WWW-Authenticate scope or in PRM scopes_supported. Canonical server URI rules: no fragment, scheme required, prefer no trailing slash. Servers MUST validate token audience; MUST NOT accept or transit other tokens. - MCP Specification 2026-07-28 — Authorization Server Discovery — Model Context Protocol (spec, URL verified 2026-08-20)
- PRM document returned by the MCP server MUST include authorization_servers with at least one entry (stronger than RFC 9728, where it is OPTIONAL). Two discovery mechanisms, both of which clients MUST support: WWW-Authenticate resource_metadata, then well-known probing in order — path-inserted (example.com/…/mcp -> example.com/…/mcp) then root. AS metadata probing order for issuers with a path: /.well-known/oauth-authorization-server/{path}, /.well-known/openid-configuration/{path}, {path}/.well-known/openid-configuration; without a path: /.well-known/oauth-authorization-server then /.well-known/openid-configuration. Clients MUST reject a metadata doc whose issuer differs from the issuer used to build the URL.
- MCP Security Best Practices (2026-07-28) — Model Context Protocol (spec, URL verified 2026-08-20)
- Token passthrough: ‘MCP servers MUST NOT accept any tokens that were not explicitly issued for the MCP server.’ Scope minimization: ‘Common Mistakes’ list names publishing all possible scopes in scopes_supported and using wildcard/omnibus scopes (*, all, full-access). State handle hijacking replaces session hijacking now that MCP is stateless: servers MUST NOT treat possession of a state handle as authentication; SHOULD use non-deterministic handles bound server-side to the authenticated user. SSRF section: clients SHOULD require HTTPS for all OAuth-related URLs and block private/link-local ranges (169.254.0.0/16 etc.).
- RFC 9728 — OAuth 2.0 Protected Resource Metadata — IETF (spec, URL verified 2026-08-20)
resourceis the only REQUIRED metadata parameter; scopes_supported and resource_name are RECOMMENDED; authorization_servers is OPTIONAL at the RFC level. Section 3 well-known construction: insert /.well-known/oauth-protected-resource between host and path, removing any terminating slash after the host (resource.example.com/resource1 -> resource.example.com/…/resource1). Section 3.3 validation: the retrievedresourcevalue MUST be identical to the resource identifier used to build the request URL; on mismatch the response data MUST NOT be used. Section 7.7 recommends blocking private/reserved IP ranges.
How it scores
Tier per evidence policy: scored — grade A meets the A/B bar required for scored audits.
Example failure
A company deploys its MCP endpoint at https://api.example.com/mcp behind a load balancer, and serves PRM only at the root https://api.example.com/.well-known/oauth-protected-resource with "resource": "https://api.example.com". A client canonicalizes the server it is talking to as https://api.example.com/mcp, fetches the root PRM as its second fallback, and finds resource !== the canonical URI — RFC 9728 §3.3 obliges it to discard the document. No authorization server is ever learned, the OAuth flow never starts, and every agent sees an unresolvable 401 loop. The fix is one line: also serve the document at /.well-known/oauth-protected-resource/mcp with "resource": "https://api.example.com/mcp".
Sources
- MCP Specification 2026-07-28 — Authorization — Model Context Protocol, spec (verified 2026-08-20)
- MCP Specification 2026-07-28 — Authorization Server Discovery — Model Context Protocol, spec (verified 2026-08-20)
- MCP Security Best Practices (2026-07-28) — Model Context Protocol, spec (verified 2026-08-20)
- RFC 9728 — OAuth 2.0 Protected Resource Metadata — IETF, spec (verified 2026-08-20)