Skip to content

Serve OAuth protected resource metadata (RFC 9728) on the MCP server#48

Open
ennsharma wants to merge 1 commit into
modelcontextprotocol:mainfrom
ennsharma:fix/serve-protected-resource-metadata
Open

Serve OAuth protected resource metadata (RFC 9728) on the MCP server#48
ennsharma wants to merge 1 commit into
modelcontextprotocol:mainfrom
ennsharma:fix/serve-protected-resource-metadata

Conversation

@ennsharma

Copy link
Copy Markdown

Fixes #22.

The MCP module's requireBearerAuth already advertises a resource_metadata URL in 401 WWW-Authenticate headers (via getOAuthProtectedResourceMetadataUrl), but nothing ever served that path — so clients that follow the spec's discovery flow got a 404. In EXTERNAL mode this made authorization-server discovery effectively impossible except through the legacy shim.

Changes (in src/index.ts, mirroring the existing inline metadata shim style):

  • Serve RFC 9728 Protected Resource Metadata at /.well-known/oauth-protected-resource (the exact URL advertised in 401s) and /.well-known/oauth-protected-resource/mcp (RFC 9728 §3.1 path insertion, for clients that derive the metadata URL from the /mcp endpoint themselves). authorization_servers points at the in-process server in INTERNAL mode and at AUTH_SERVER_URL in EXTERNAL mode.
  • Kept the /.well-known/oauth-authorization-server shim rather than removing it as the issue suggests: clients of the 2025-03-26 spec revision discover auth by querying that path on the MCP server directly, and in EXTERNAL mode the shim correctly points them at the external AS endpoints. It's now commented as a back-compat shim; happy to remove it instead if you'd prefer strict current-spec behavior.

Verified by booting both modes and curling:

  • EXTERNAL (AUTH_MODE=external AUTH_SERVER_URL=http://localhost:3001): both PRM endpoints return authorization_servers: ["http://localhost:3001"], and the URL in the 401's WWW-Authenticate: ... resource_metadata="…" now resolves.
  • INTERNAL: PRM returns the server itself as the authorization server.

npm run build, npx eslint, and the jest suite (79 tests, 6 suites) all pass.

🤖 Generated with Claude Code

The bearer-auth middleware already advertises a resource_metadata URL
in 401 WWW-Authenticate headers, but no route served it, so
spec-compliant clients following the pointer got a 404 (modelcontextprotocol#22). In
EXTERNAL mode there was no way to discover the authorization server
at all besides the legacy RFC 8414 shim.

- Serve RFC 9728 protected resource metadata at
  /.well-known/oauth-protected-resource (the URL advertised in 401s)
  and /.well-known/oauth-protected-resource/mcp (RFC 9728 path
  insertion), listing the mode-appropriate authorization server
- Keep the legacy /.well-known/oauth-authorization-server shim for
  clients of the 2025-03-26 spec revision, and label it as such

Fixes modelcontextprotocol#22

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Confusion with WWW-Authenticate and EXTERNAL server mode

1 participant