Reproducible lab note

An MCP OAuth 401 fixture you can debug from left to right.

This fictional example isolates discovery and identifier failures without exposing a real tenant, token, or private endpoint. Each mutation breaks one edge in the chain.

Metadata fixture · August 10, 2026 · No live credentials

Debugging rule

Stop at the first broken edge. A valid token endpoint cannot compensate for a missing challenge, and a successful login cannot compensate for a token minted for another resource.

Step 1: the unauthenticated challenge

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"

The first failure mutation is to remove resource_metadata. Expected result: the client has no advertised path to protected-resource metadata and may never open an authorization flow.

Step 2: protected-resource metadata

{
  "resource": "https://api.example.com/mcp",
  "authorization_servers": ["https://login.example.com"],
  "scopes_supported": ["mcp:tools"]
}

The second mutation changes resource to an internal host such as http://mcp:3000. Expected result: the public endpoint and advertised resource identifier no longer agree.

Step 3: authorization-server metadata

{
  "issuer": "https://login.example.com",
  "authorization_endpoint": "https://login.example.com/authorize",
  "token_endpoint": "https://login.example.com/token",
  "code_challenge_methods_supported": ["S256"]
}

The third mutation changes the issuer to https://login.example.com/tenant-a while the resource document names the root issuer. Expected result: documents remain reachable but their identifiers describe different authorization servers.

Step 4: requested authorization

response_type=code
resource=https%3A%2F%2Fapi.example.com%2Fmcp
scope=mcp%3Atools
code_challenge_method=S256

The fourth mutation requests mcp:admin, which the fixture does not advertise. Expected result: the authorization server rejects the scope or omits it from the grant.

Step 5: token acceptance

ClaimExpectedFailure mutation
isshttps://login.example.comDifferent tenant
audhttps://api.example.com/mcpAPI homepage
scopemcp:toolsMissing scope
expFuture timestampExpired token

Do not paste a live token into a public tool. Decode synthetic or redacted claims locally, then compare identifiers exactly. A cryptographically valid token with the wrong audience should still be rejected by the MCP resource.

Proxy-specific check

If the fixture works locally but the deployment fails, inspect the URLs generated behind the proxy. A missing forwarded protocol or host can turn every public HTTPS identifier into an internal HTTP address even though all application routes return 200.

Validate the chain

Use the fictional values above in the metadata validator, then apply one mutation at a time and confirm the first failing edge changes as expected.

Open MCP OAuth Lab →