Skip to content

Commit 053ac31

Browse files
committed
- Added README.md changes for SEP-990 implementation for enterprise managed auth.
1 parent ed2943a commit 053ac31

File tree

1 file changed

+135
-0
lines changed

1 file changed

+135
-0
lines changed

README.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
- [Writing MCP Clients](#writing-mcp-clients)
7070
- [Client Display Utilities](#client-display-utilities)
7171
- [OAuth Authentication for Clients](#oauth-authentication-for-clients)
72+
- [Enterprise Managed Authorization](#enterprise-managed-authorization)
7273
- [Parsing Tool Results](#parsing-tool-results)
7374
- [MCP Primitives](#mcp-primitives)
7475
- [Server Capabilities](#server-capabilities)
@@ -2462,6 +2463,140 @@ _Full example: [examples/snippets/clients/oauth_client.py](https://github.com/mo
24622463

24632464
For a complete working example, see [`examples/clients/simple-auth-client/`](examples/clients/simple-auth-client/).
24642465

2466+
#### Enterprise Managed Authorization
2467+
2468+
The SDK includes support for Enterprise Managed Authorization (SEP-990), which enables MCP clients to connect to protected servers using enterprise Single Sign-On (SSO) systems. This implementation supports:
2469+
2470+
- **RFC 8693**: OAuth 2.0 Token Exchange (ID Token → ID-JAG)
2471+
- **RFC 7523**: JSON Web Token (JWT) Profile for OAuth 2.0 Authorization Grants (ID-JAG → Access Token)
2472+
- Integration with enterprise identity providers (Okta, Azure AD, etc.)
2473+
2474+
**Key Components:**
2475+
2476+
The `EnterpriseAuthOAuthClientProvider` class extends the standard OAuth provider to implement the enterprise authorization flow:
2477+
2478+
```python
2479+
from mcp.client.auth.extensions import (
2480+
EnterpriseAuthOAuthClientProvider,
2481+
TokenExchangeParameters,
2482+
IDJAGClaims,
2483+
decode_id_jag,
2484+
)
2485+
from mcp.shared.auth import OAuthClientMetadata, OAuthToken
2486+
from mcp.client.auth import TokenStorage
2487+
```
2488+
2489+
**Token Exchange Flow:**
2490+
2491+
1. **Obtain ID Token** from your enterprise IdP (e.g., Okta, Azure AD)
2492+
2. **Exchange ID Token for ID-JAG** using RFC 8693 Token Exchange
2493+
3. **Exchange ID-JAG for Access Token** using RFC 7523 JWT Bearer Grant
2494+
4. **Use Access Token** to call protected MCP server tools
2495+
2496+
**Example Usage:**
2497+
2498+
```python
2499+
import asyncio
2500+
import httpx
2501+
from pydantic import AnyUrl
2502+
2503+
from mcp.client.auth.extensions import (
2504+
EnterpriseAuthOAuthClientProvider,
2505+
TokenExchangeParameters,
2506+
)
2507+
from mcp.shared.auth import OAuthClientMetadata, OAuthToken
2508+
from mcp.client.auth import TokenStorage
2509+
2510+
# Define token storage implementation
2511+
class SimpleTokenStorage(TokenStorage):
2512+
def __init__(self):
2513+
self._tokens = None
2514+
self._client_info = None
2515+
2516+
async def get_tokens(self):
2517+
return self._tokens
2518+
2519+
async def set_tokens(self, tokens):
2520+
self._tokens = tokens
2521+
2522+
async def get_client_info(self):
2523+
return self._client_info
2524+
2525+
async def set_client_info(self, client_info):
2526+
self._client_info = client_info
2527+
2528+
async def main():
2529+
# Step 1: Get ID token from your IdP (example with Okta)
2530+
id_token = await get_id_token_from_idp() # Your IdP authentication
2531+
2532+
# Step 2: Configure token exchange parameters
2533+
token_exchange_params = TokenExchangeParameters.from_id_token(
2534+
id_token=id_token,
2535+
mcp_server_auth_issuer="https://your-idp.com", # IdP issuer URL
2536+
mcp_server_resource_id="https://mcp-server.example.com", # MCP server resource ID
2537+
scope="mcp:tools mcp:resources", # Optional scopes
2538+
)
2539+
2540+
# Step 3: Create enterprise auth provider
2541+
enterprise_auth = EnterpriseAuthOAuthClientProvider(
2542+
server_url="https://mcp-server.example.com",
2543+
client_metadata=OAuthClientMetadata(
2544+
client_name="Enterprise MCP Client",
2545+
client_id="your-client-id",
2546+
redirect_uris=[AnyUrl("http://localhost:3000/callback")],
2547+
grant_types=["urn:ietf:params:oauth:grant-type:jwt-bearer"],
2548+
response_types=["token"],
2549+
),
2550+
storage=SimpleTokenStorage(),
2551+
idp_token_endpoint="https://your-idp.com/oauth2/v1/token",
2552+
token_exchange_params=token_exchange_params,
2553+
)
2554+
2555+
# Step 4: Perform token exchange and get access token
2556+
async with httpx.AsyncClient() as client:
2557+
# Exchange ID token for ID-JAG
2558+
id_jag = await enterprise_auth.exchange_token_for_id_jag(client)
2559+
print(f"Obtained ID-JAG: {id_jag[:50]}...")
2560+
2561+
# Exchange ID-JAG for access token
2562+
access_token = await enterprise_auth.exchange_id_jag_for_access_token(
2563+
client, id_jag
2564+
)
2565+
print(f"Access token obtained, expires in: {access_token.expires_in}s")
2566+
2567+
if __name__ == "__main__":
2568+
asyncio.run(main())
2569+
```
2570+
2571+
**Working with SAML Assertions:**
2572+
2573+
If your enterprise uses SAML instead of OIDC, you can exchange SAML assertions:
2574+
2575+
```python
2576+
token_exchange_params = TokenExchangeParameters.from_saml_assertion(
2577+
saml_assertion=saml_assertion_string,
2578+
mcp_server_auth_issuer="https://your-idp.com",
2579+
mcp_server_resource_id="https://mcp-server.example.com",
2580+
scope="mcp:tools",
2581+
)
2582+
```
2583+
2584+
**Decoding and Inspecting ID-JAG Tokens:**
2585+
2586+
You can decode ID-JAG tokens to inspect their claims:
2587+
2588+
```python
2589+
from mcp.client.auth.extensions import decode_id_jag
2590+
2591+
# Decode without signature verification (for inspection only)
2592+
claims = decode_id_jag(id_jag)
2593+
print(f"Subject: {claims.sub}")
2594+
print(f"Issuer: {claims.iss}")
2595+
print(f"Audience: {claims.aud}")
2596+
print(f"Client ID: {claims.client_id}")
2597+
print(f"Resource: {claims.resource}")
2598+
```
2599+
24652600
### Parsing Tool Results
24662601

24672602
When calling tools through MCP, the `CallToolResult` object contains the tool's response in a structured format. Understanding how to parse this result is essential for properly handling tool outputs.

0 commit comments

Comments
 (0)