Skip to content

Conversation

@BinoyOza-okta
Copy link

This PR implements the client-side components of SEP-990: Enterprise Managed Authorization. It introduces the EnterpriseAuthOAuthClientProvider to handle the full token exchange flow required for Enterprise SSO, including RFC 8693 (Token Exchange) and RFC 7523 (JWT Bearer Grant).

Motivation and Context

Implements: SEP-990

To support enterprise environments where direct API keys are not compliant, the Python SDK needs to support "Managed Authorization." This implementation allows the SDK to:

  1. Exchange Tokens: Convert an existing Identity Provider (IdP) token (e.g., OIDC ID Token or SAML Assertion) into an MCP-specific "ID-JAG" token using RFC 8693.
  2. Authenticate: Exchange the ID-JAG token for a usable Access Token via RFC 7523.

This aligns the Python SDK with the architecture defined in the SEP-990 implementation guide.

Implementation Details

The following components have been added to src/mcp/client/auth/extensions/enterprise_managed_auth.py:

  • Data Models: TokenExchangeParameters and TokenExchangeResponse using Pydantic to strictly type the exchange payloads.
  • Client Provider: EnterpriseAuthOAuthClientProvider, which extends the base OAuthClientProvider to orchestrate the exchange logic.
  • ID-JAG Logic: Support for urn:ietf:params:oauth:token-type:id-jag token types.

How Has This Been Tested?

I have implemented comprehensive unit tests in tests/client/auth/test_enterprise_managed_auth_client.py using pytest and unittest.mock.

The testing suite covers the following scenarios:

  1. Data Model Validation:

    • Verified TokenExchangeParameters correctly generates requests for both OIDC ID Tokens (test_token_exchange_params_from_id_token) and SAML Assertions (test_token_exchange_params_from_saml_assertion).
    • Validated enforcement of required fields (audience, resource, subject_token).
  2. RFC 8693 Token Exchange Logic:

    • Success Path: Mocked httpx to verify the correct payload structure (grant types, token types) is sent to the IdP.
    • Client Authentication: Verified that client_id and client_secret are correctly injected into the request body when configured (test_exchange_token_with_client_authentication).
    • Error Handling: Validated graceful handling of HTTP 400/500 errors, non-JSON error responses, and unexpected token types.
  3. RFC 7523 JWT Bearer Grant Logic:

    • Success Path: Verified the exchange of an ID-JAG token for a final Access Token.
    • Metadata Checks: Ensured the flow fails appropriately if the MCP server's OAuth metadata (token endpoint) is missing.
  4. Network Edge Cases:

    • Simulated network failures (httpx.ConnectError, httpx.ReadTimeout) to ensure OAuthTokenError is raised with descriptive messages.

Breaking Changes

No.
This is an additive extension. The core OAuthClientProvider remains backward compatible. Only users specifically importing and using EnterpriseAuthOAuthClientProvider will be affected.

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

  • Dependencies: Utilizes pydantic for model validation and httpx for async requests.
  • Scope: As agreed, this PR includes the Client implementation (src/mcp/client/auth/extensions/).

@felixweinberger felixweinberger added auth Issues and PRs related to Authentication / OAuth enhancement Request for a new feature that's not currently supported labels Dec 9, 2025
@maxisbey maxisbey requested a review from pcarleton December 9, 2025 15:20
Copy link
Contributor

@maxisbey maxisbey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I was missing something,

Comment on lines +20 to +22
# ============================================================================
# Data Models
# ============================================================================
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

Comment on lines +169 to +170
redirect_handler: Any = None,
callback_handler: Any = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these please be correctly typed

Comment on lines +217 to +224
token_data = {
"grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
"requested_token_type": self.token_exchange_params.requested_token_type,
"audience": self.token_exchange_params.audience,
"resource": self.token_exchange_params.resource,
"subject_token": self.token_exchange_params.subject_token,
"subject_token_type": self.token_exchange_params.subject_token_type,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this be typed, either a BaseModel or a TypedDict. (@Kludex would you have a preference here?)

Comment on lines +356 to +358
# ============================================================================
# Helper Functions
# ============================================================================
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

Note:
For verification, use server-side validation instead.
"""
import jwt
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to top of file

Comment on lines +152 to +154
# ============================================================================
# Tests for TokenExchangeResponse
# ============================================================================
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

Comment on lines +188 to +190
# ============================================================================
# Tests for IDJAGClaims
# ============================================================================
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

# ============================================================================


def decode_id_jag(id_jag: str, verify: bool = False) -> IDJAGClaims:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verify is unused


**Example Usage:**

```python
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this instead be put in the snippets folder and then use the scripts/update_readme_snippets.py script to update the README accordingly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Need to explain how to use the access token once you get it.
  2. How does this work when the client ID is expired?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auth Issues and PRs related to Authentication / OAuth enhancement Request for a new feature that's not currently supported

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants