Skip to content

Commit db2f02c

Browse files
committed
- Added README.md changes for SEP-990 implementation for enterprise managed auth.
1 parent d4392ae commit db2f02c

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
@@ -61,6 +61,7 @@
6161
- [Writing MCP Clients](#writing-mcp-clients)
6262
- [Client Display Utilities](#client-display-utilities)
6363
- [OAuth Authentication for Clients](#oauth-authentication-for-clients)
64+
- [Enterprise Managed Authorization](#enterprise-managed-authorization)
6465
- [Parsing Tool Results](#parsing-tool-results)
6566
- [MCP Primitives](#mcp-primitives)
6667
- [Server Capabilities](#server-capabilities)
@@ -2356,6 +2357,140 @@ _Full example: [examples/snippets/clients/oauth_client.py](https://github.com/mo
23562357

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

2360+
#### Enterprise Managed Authorization
2361+
2362+
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:
2363+
2364+
- **RFC 8693**: OAuth 2.0 Token Exchange (ID Token → ID-JAG)
2365+
- **RFC 7523**: JSON Web Token (JWT) Profile for OAuth 2.0 Authorization Grants (ID-JAG → Access Token)
2366+
- Integration with enterprise identity providers (Okta, Azure AD, etc.)
2367+
2368+
**Key Components:**
2369+
2370+
The `EnterpriseAuthOAuthClientProvider` class extends the standard OAuth provider to implement the enterprise authorization flow:
2371+
2372+
```python
2373+
from mcp.client.auth.extensions import (
2374+
EnterpriseAuthOAuthClientProvider,
2375+
TokenExchangeParameters,
2376+
IDJAGClaims,
2377+
decode_id_jag,
2378+
)
2379+
from mcp.shared.auth import OAuthClientMetadata, OAuthToken
2380+
from mcp.client.auth import TokenStorage
2381+
```
2382+
2383+
**Token Exchange Flow:**
2384+
2385+
1. **Obtain ID Token** from your enterprise IdP (e.g., Okta, Azure AD)
2386+
2. **Exchange ID Token for ID-JAG** using RFC 8693 Token Exchange
2387+
3. **Exchange ID-JAG for Access Token** using RFC 7523 JWT Bearer Grant
2388+
4. **Use Access Token** to call protected MCP server tools
2389+
2390+
**Example Usage:**
2391+
2392+
```python
2393+
import asyncio
2394+
import httpx
2395+
from pydantic import AnyUrl
2396+
2397+
from mcp.client.auth.extensions import (
2398+
EnterpriseAuthOAuthClientProvider,
2399+
TokenExchangeParameters,
2400+
)
2401+
from mcp.shared.auth import OAuthClientMetadata, OAuthToken
2402+
from mcp.client.auth import TokenStorage
2403+
2404+
# Define token storage implementation
2405+
class SimpleTokenStorage(TokenStorage):
2406+
def __init__(self):
2407+
self._tokens = None
2408+
self._client_info = None
2409+
2410+
async def get_tokens(self):
2411+
return self._tokens
2412+
2413+
async def set_tokens(self, tokens):
2414+
self._tokens = tokens
2415+
2416+
async def get_client_info(self):
2417+
return self._client_info
2418+
2419+
async def set_client_info(self, client_info):
2420+
self._client_info = client_info
2421+
2422+
async def main():
2423+
# Step 1: Get ID token from your IdP (example with Okta)
2424+
id_token = await get_id_token_from_idp() # Your IdP authentication
2425+
2426+
# Step 2: Configure token exchange parameters
2427+
token_exchange_params = TokenExchangeParameters.from_id_token(
2428+
id_token=id_token,
2429+
mcp_server_auth_issuer="https://your-idp.com", # IdP issuer URL
2430+
mcp_server_resource_id="https://mcp-server.example.com", # MCP server resource ID
2431+
scope="mcp:tools mcp:resources", # Optional scopes
2432+
)
2433+
2434+
# Step 3: Create enterprise auth provider
2435+
enterprise_auth = EnterpriseAuthOAuthClientProvider(
2436+
server_url="https://mcp-server.example.com",
2437+
client_metadata=OAuthClientMetadata(
2438+
client_name="Enterprise MCP Client",
2439+
client_id="your-client-id",
2440+
redirect_uris=[AnyUrl("http://localhost:3000/callback")],
2441+
grant_types=["urn:ietf:params:oauth:grant-type:jwt-bearer"],
2442+
response_types=["token"],
2443+
),
2444+
storage=SimpleTokenStorage(),
2445+
idp_token_endpoint="https://your-idp.com/oauth2/v1/token",
2446+
token_exchange_params=token_exchange_params,
2447+
)
2448+
2449+
# Step 4: Perform token exchange and get access token
2450+
async with httpx.AsyncClient() as client:
2451+
# Exchange ID token for ID-JAG
2452+
id_jag = await enterprise_auth.exchange_token_for_id_jag(client)
2453+
print(f"Obtained ID-JAG: {id_jag[:50]}...")
2454+
2455+
# Exchange ID-JAG for access token
2456+
access_token = await enterprise_auth.exchange_id_jag_for_access_token(
2457+
client, id_jag
2458+
)
2459+
print(f"Access token obtained, expires in: {access_token.expires_in}s")
2460+
2461+
if __name__ == "__main__":
2462+
asyncio.run(main())
2463+
```
2464+
2465+
**Working with SAML Assertions:**
2466+
2467+
If your enterprise uses SAML instead of OIDC, you can exchange SAML assertions:
2468+
2469+
```python
2470+
token_exchange_params = TokenExchangeParameters.from_saml_assertion(
2471+
saml_assertion=saml_assertion_string,
2472+
mcp_server_auth_issuer="https://your-idp.com",
2473+
mcp_server_resource_id="https://mcp-server.example.com",
2474+
scope="mcp:tools",
2475+
)
2476+
```
2477+
2478+
**Decoding and Inspecting ID-JAG Tokens:**
2479+
2480+
You can decode ID-JAG tokens to inspect their claims:
2481+
2482+
```python
2483+
from mcp.client.auth.extensions import decode_id_jag
2484+
2485+
# Decode without signature verification (for inspection only)
2486+
claims = decode_id_jag(id_jag)
2487+
print(f"Subject: {claims.sub}")
2488+
print(f"Issuer: {claims.iss}")
2489+
print(f"Audience: {claims.aud}")
2490+
print(f"Client ID: {claims.client_id}")
2491+
print(f"Resource: {claims.resource}")
2492+
```
2493+
23592494
### Parsing Tool Results
23602495

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