Skip to content

Commit 6bd2e1d

Browse files
committed
Moved the changes from README.md to README.v2.md.
1 parent 375af79 commit 6bd2e1d

File tree

2 files changed

+211
-225
lines changed

2 files changed

+211
-225
lines changed

README.md

Lines changed: 0 additions & 225 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@
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)
7372
- [Parsing Tool Results](#parsing-tool-results)
7473
- [MCP Primitives](#mcp-primitives)
7574
- [Server Capabilities](#server-capabilities)
@@ -2463,230 +2462,6 @@ _Full example: [examples/snippets/clients/oauth_client.py](https://github.com/mo
24632462

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

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-
**Token Exchange Flow:**
2479-
2480-
1. **Obtain ID Token** from your enterprise IdP (e.g., Okta, Azure AD)
2481-
2. **Exchange ID Token for ID-JAG** using RFC 8693 Token Exchange
2482-
3. **Exchange ID-JAG for Access Token** using RFC 7523 JWT Bearer Grant
2483-
4. **Use Access Token** to call protected MCP server tools
2484-
2485-
**Using the Access Token with MCP Server:**
2486-
2487-
1. Once you have obtained the access token, you can use it to authenticate requests to the MCP server
2488-
2. The access token is automatically included in all subsequent requests to the MCP server, allowing you to access protected tools and resources based on your enterprise identity and permissions.
2489-
2490-
**Handling Token Expiration and Refresh:**
2491-
2492-
Access tokens have a limited lifetime and will expire. When tokens expire:
2493-
2494-
- **Check Token Expiration**: Use the `expires_in` field to determine when the token expires
2495-
- **Refresh Flow**: When expired, repeat the token exchange flow with a fresh ID token from your IdP
2496-
- **Automatic Refresh**: Implement automatic token refresh before expiration (recommended for production)
2497-
- **Error Handling**: Catch authentication errors and retry with refreshed tokens
2498-
2499-
**Important Notes:**
2500-
2501-
- **ID Token Expiration**: If the ID token from your IdP expires, you must re-authenticate with the IdP to obtain a new ID token before performing token exchange
2502-
- **Token Storage**: Store tokens securely and implement the `TokenStorage` interface to persist tokens between application restarts
2503-
- **Scope Changes**: If you need different scopes, you must obtain a new ID token from the IdP with the required scopes
2504-
- **Security**: Never log or expose access tokens or ID tokens in production environments
2505-
2506-
**Example Usage:**
2507-
2508-
<!-- snippet-source examples/snippets/clients/enterprise_managed_auth_client.py -->
2509-
```python
2510-
import asyncio
2511-
2512-
import httpx
2513-
from pydantic import AnyUrl
2514-
2515-
from mcp import ClientSession
2516-
from mcp.client.auth import TokenStorage
2517-
from mcp.client.auth.extensions import (
2518-
EnterpriseAuthOAuthClientProvider,
2519-
TokenExchangeParameters,
2520-
)
2521-
from mcp.client.streamable_http import streamable_http_client
2522-
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
2523-
2524-
2525-
# Placeholder function for IdP authentication
2526-
async def get_id_token_from_idp() -> str:
2527-
"""Placeholder function to get ID token from your IdP.
2528-
2529-
In production, implement actual IdP authentication flow.
2530-
"""
2531-
raise NotImplementedError("Implement your IdP authentication flow here")
2532-
2533-
2534-
# Define token storage implementation
2535-
class SimpleTokenStorage(TokenStorage):
2536-
def __init__(self) -> None:
2537-
self._tokens: OAuthToken | None = None
2538-
self._client_info: OAuthClientInformationFull | None = None
2539-
2540-
async def get_tokens(self) -> OAuthToken | None:
2541-
return self._tokens
2542-
2543-
async def set_tokens(self, tokens: OAuthToken) -> None:
2544-
self._tokens = tokens
2545-
2546-
async def get_client_info(self) -> OAuthClientInformationFull | None:
2547-
return self._client_info
2548-
2549-
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
2550-
self._client_info = client_info
2551-
2552-
2553-
async def main() -> None:
2554-
"""Example demonstrating enterprise managed authorization with MCP."""
2555-
# Step 1: Get ID token from your IdP (e.g., Okta, Azure AD)
2556-
id_token = await get_id_token_from_idp()
2557-
2558-
# Step 2: Configure token exchange parameters
2559-
token_exchange_params = TokenExchangeParameters.from_id_token(
2560-
id_token=id_token,
2561-
mcp_server_auth_issuer="https://auth.mcp-server.example.com", # MCP server's auth issuer
2562-
mcp_server_resource_id="https://mcp-server.example.com", # MCP server resource ID
2563-
scope="mcp:tools mcp:resources", # Optional scopes
2564-
)
2565-
2566-
# Step 3: Create enterprise auth provider
2567-
enterprise_auth = EnterpriseAuthOAuthClientProvider(
2568-
server_url="https://mcp-server.example.com",
2569-
client_metadata=OAuthClientMetadata(
2570-
client_name="Enterprise MCP Client",
2571-
redirect_uris=[AnyUrl("http://localhost:3000/callback")],
2572-
grant_types=["urn:ietf:params:oauth:grant-type:jwt-bearer"],
2573-
response_types=["token"],
2574-
),
2575-
storage=SimpleTokenStorage(),
2576-
idp_token_endpoint="https://your-idp.com/oauth2/v1/token", # Your IdP's token endpoint
2577-
token_exchange_params=token_exchange_params,
2578-
)
2579-
2580-
# Step 4: Create authenticated HTTP client
2581-
# The auth provider automatically handles the two-step token exchange:
2582-
# 1. ID Token → ID-JAG (via IDP)
2583-
# 2. ID-JAG → Access Token (via MCP server)
2584-
client = httpx.AsyncClient(auth=enterprise_auth, timeout=30.0)
2585-
2586-
# Step 5: Connect to MCP server with authenticated client
2587-
async with streamable_http_client(url="https://mcp-server.example.com", http_client=client) as (read, write):
2588-
async with ClientSession(read, write) as session:
2589-
await session.initialize()
2590-
2591-
# List available tools
2592-
tools_result = await session.list_tools()
2593-
print(f"Available tools: {[t.name for t in tools_result.tools]}")
2594-
2595-
# Call a tool - auth tokens are automatically managed
2596-
if tools_result.tools:
2597-
tool_name = tools_result.tools[0].name
2598-
result = await session.call_tool(tool_name, {})
2599-
print(f"Tool result: {result.content}")
2600-
2601-
# List available resources
2602-
resources = await session.list_resources()
2603-
for resource in resources.resources:
2604-
print(f"Resource: {resource.uri}")
2605-
2606-
2607-
async def advanced_manual_flow() -> None:
2608-
"""Advanced example showing manual token exchange (for special use cases)."""
2609-
id_token = await get_id_token_from_idp()
2610-
2611-
token_exchange_params = TokenExchangeParameters.from_id_token(
2612-
id_token=id_token,
2613-
mcp_server_auth_issuer="https://auth.mcp-server.example.com",
2614-
mcp_server_resource_id="https://mcp-server.example.com",
2615-
)
2616-
2617-
enterprise_auth = EnterpriseAuthOAuthClientProvider(
2618-
server_url="https://mcp-server.example.com",
2619-
client_metadata=OAuthClientMetadata(
2620-
redirect_uris=[AnyUrl("http://localhost:3000/callback")],
2621-
),
2622-
storage=SimpleTokenStorage(),
2623-
idp_token_endpoint="https://your-idp.com/oauth2/v1/token",
2624-
token_exchange_params=token_exchange_params,
2625-
)
2626-
2627-
# Manual token exchange (for debugging or special use cases)
2628-
async with httpx.AsyncClient() as client:
2629-
# Step 1: Exchange ID token for ID-JAG
2630-
id_jag = await enterprise_auth.exchange_token_for_id_jag(client)
2631-
print(f"Obtained ID-JAG: {id_jag[:50]}...")
2632-
2633-
# Step 2: Build JWT bearer grant request
2634-
jwt_bearer_request = await enterprise_auth.exchange_id_jag_for_access_token(id_jag)
2635-
print(f"Built JWT bearer grant request to: {jwt_bearer_request.url}")
2636-
2637-
# Step 3: Execute the request to get access token
2638-
response = await client.send(jwt_bearer_request)
2639-
response.raise_for_status()
2640-
token_data = response.json()
2641-
2642-
access_token = OAuthToken(
2643-
access_token=token_data["access_token"],
2644-
token_type=token_data["token_type"],
2645-
expires_in=token_data.get("expires_in"),
2646-
)
2647-
print(f"Access token obtained, expires in: {access_token.expires_in}s")
2648-
2649-
# Use the access token for API calls
2650-
_ = {"Authorization": f"Bearer {access_token.access_token}"}
2651-
# ... make authenticated requests with headers
2652-
2653-
2654-
if __name__ == "__main__":
2655-
asyncio.run(main())
2656-
```
2657-
2658-
_Full example: [examples/snippets/clients/enterprise_managed_auth_client.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/enterprise_managed_auth_client.py)_
2659-
<!-- /snippet-source -->
2660-
2661-
**Working with SAML Assertions:**
2662-
2663-
If your enterprise uses SAML instead of OIDC, you can exchange SAML assertions:
2664-
2665-
```python
2666-
token_exchange_params = TokenExchangeParameters.from_saml_assertion(
2667-
saml_assertion=saml_assertion_string,
2668-
mcp_server_auth_issuer="https://your-idp.com",
2669-
mcp_server_resource_id="https://mcp-server.example.com",
2670-
scope="mcp:tools",
2671-
)
2672-
```
2673-
2674-
**Decoding and Inspecting ID-JAG Tokens:**
2675-
2676-
You can decode ID-JAG tokens to inspect their claims:
2677-
2678-
```python
2679-
from mcp.client.auth.extensions import decode_id_jag
2680-
2681-
# Decode without signature verification (for inspection only)
2682-
claims = decode_id_jag(id_jag)
2683-
print(f"Subject: {claims.sub}")
2684-
print(f"Issuer: {claims.iss}")
2685-
print(f"Audience: {claims.aud}")
2686-
print(f"Client ID: {claims.client_id}")
2687-
print(f"Resource: {claims.resource}")
2688-
```
2689-
26902465
### Parsing Tool Results
26912466

26922467
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)