Skip to content

Conversation

@cbcoutinho
Copy link

@cbcoutinho cbcoutinho commented Oct 25, 2025

Motivation and Context

This PR addresses two OAuth-related improvements:

  1. SEP-985 Implementation: Aligns OAuth 2.0 Protected Resource Metadata handling with RFC 9728 by making the WWW-Authenticate header optional and implementing graceful fallback behavior.

  2. Scope Handling Fix: Fixes bug where MCP clients don't respect scope parameters from WWW-Authenticate headers and Protected Resource Metadata, as specified in the MCP OAuth specification.

Changes

SEP-985: Optional Protected Resource Metadata

  • Updated discoverOAuthProtectedResourceMetadata() to return undefined instead of throwing on 404, making protected resource metadata optional
  • Enhanced JSDoc comments to document SEP-985 fallback behavior
  • Updated authInternal() to handle optional metadata with proper null checks

Per SEP-985, clients now:

  1. Check WWW-Authenticate header for resource_metadata parameter
  2. Fallback to /.well-known/oauth-protected-resource if not present
  3. Continue gracefully using the MCP server as auth server if metadata unavailable

Scope Priority Implementation

  • Added extractChallengeScope() to parse scope from WWW-Authenticate header per RFC 6750
  • Added selectScopes() helper implementing proper scope selection priority:
    1. Explicit scope parameter (user override)
    2. WWW-Authenticate challenge scope (authoritative per MCP spec)
    3. Protected Resource Metadata scopes_supported
    4. Client default scope
  • Updated auth() and authInternal() signatures to accept challengeScope parameter
  • Updated all transports (SSE, StreamableHTTP, middleware) to extract and pass challenge scope
  • Protected Resource Metadata scopes_supported array is now properly joined with spaces

This directly addresses the requirements from #672:

  • ✅ Uses scope from 401 WWW-Authenticate header response when present
  • ✅ Falls back to scopes_supported from Protected Resource Metadata
  • ✅ Omits scope parameter when no sources provide it (letting server use defaults)

Test Coverage

Added comprehensive test suites:

SEP-985 tests (4 scenarios):

  • WWW-Authenticate header with resource_metadata present
  • WWW-Authenticate header without resource_metadata (fallback to well-known)
  • 404 on well-known endpoint (graceful degradation)
  • CORS errors on metadata discovery (graceful fallback)

extractChallengeScope() tests (7 scenarios):

  • Extracts scope from WWW-Authenticate header
  • Extracts scope when combined with resource_metadata
  • Returns undefined when no header present
  • Returns undefined when scope not in header
  • Handles empty scope string
  • Returns undefined for non-Bearer authentication
  • Handles single scope value

Scope selection priority tests (6 scenarios):

  • Explicit scope overrides all other sources
  • Challenge scope overrides PRM scopes_supported
  • PRM scopes_supported used when no explicit/challenge scope
  • Client default used when no other sources
  • Proceeds without scope when no sources available
  • Properly joins multiple scopes_supported values with spaces

How Has This Been Tested?

$ npm test

All 869 tests pass (13 new tests added).

Related Issues

Fixes: #672
Related: #920
Implements: SEP-985 (modelcontextprotocol/modelcontextprotocol#971)

Breaking Changes

None - all changes are backward compatible and maintain existing behavior when new parameters are not provided.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional Context

The scope handling implementation follows the MCP OAuth specification's requirements for Protected Resource Metadata discovery. The WWW-Authenticate scope parameter is now properly extracted and prioritized over other scope sources, ensuring clients request the correct scopes as dictated by the authorization server.

Aligns OAuth 2.0 Protected Resource Metadata handling with RFC 9728 and
SEP-985 by making the WWW-Authenticate header optional and implementing
graceful fallback behavior.

Changes:
- Updated discoverOAuthProtectedResourceMetadata() to return undefined
  instead of throwing on 404, making protected resource metadata optional
- Enhanced JSDoc comments to document SEP-985 fallback behavior
- Updated authInternal() to handle optional metadata with proper null checks
- Added comprehensive test suite for SEP-985 scenarios:
  - WWW-Authenticate header with resource_metadata present
  - WWW-Authenticate header without resource_metadata (fallback to well-known)
  - Missing WWW-Authenticate header (fallback to well-known)
  - 404 on well-known endpoint (graceful degradation)
  - CORS errors on metadata discovery (graceful fallback)
- Updated existing tests to expect undefined instead of errors on 404

Per SEP-985, clients now:
1. Check WWW-Authenticate header for resource_metadata parameter
2. Fallback to /.well-known/oauth-protected-resource if not present
3. Continue gracefully using the MCP server as auth server if metadata unavailable

All 856 tests pass.

Related: modelcontextprotocol#920
Implements: SEP-985 (modelcontextprotocol/modelcontextprotocol#971)
@cbcoutinho cbcoutinho requested a review from a team as a code owner October 25, 2025 20:30
…e Metadata

Implements proper scope selection priority per MCP OAuth spec:
1. Explicit scope parameter (user override)
2. WWW-Authenticate challenge scope (authoritative per spec)
3. Protected Resource Metadata scopes_supported
4. Client default scope

Changes:
- Add extractChallengeScope() to parse scope from WWW-Authenticate header
- Add selectScopes() helper with priority logic
- Update auth(), authInternal() to accept and use challengeScope
- Update SSE, StreamableHTTP, and middleware transports to extract and pass challenge scope
- Add comprehensive tests for extractChallengeScope() and scope selection priority

All 869 tests passing.
@cbcoutinho cbcoutinho requested a review from a team as a code owner October 25, 2025 21:07
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.

[auth] Handle dynamically selecting scopes in client authorization url

1 participant