mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-20 20:44:17 +02:00
chore: Update SDK documentation (#2932)
Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>
This commit is contained in:
parent
4e663593b8
commit
e98e04fb83
16 changed files with 876 additions and 657 deletions
322
docs/python-sdk/fastmcp-server-auth-oauth_proxy-proxy.mdx
Normal file
322
docs/python-sdk/fastmcp-server-auth-oauth_proxy-proxy.mdx
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
---
|
||||
title: proxy
|
||||
sidebarTitle: proxy
|
||||
---
|
||||
|
||||
# `fastmcp.server.auth.oauth_proxy.proxy`
|
||||
|
||||
|
||||
OAuth Proxy Provider for FastMCP.
|
||||
|
||||
This provider acts as a transparent proxy to an upstream OAuth Authorization Server,
|
||||
handling Dynamic Client Registration locally while forwarding all other OAuth flows.
|
||||
This enables authentication with upstream providers that don't support DCR or have
|
||||
restricted client registration policies.
|
||||
|
||||
Key features:
|
||||
- Proxies authorization and token endpoints to upstream server
|
||||
- Implements local Dynamic Client Registration with fixed upstream credentials
|
||||
- Validates tokens using upstream JWKS
|
||||
- Maintains minimal local state for bookkeeping
|
||||
- Enhanced logging with request correlation
|
||||
|
||||
This implementation is based on the OAuth 2.1 specification and is designed for
|
||||
production use with enterprise identity providers.
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `OAuthProxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L81" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
OAuth provider that presents a DCR-compliant interface while proxying to non-DCR IDPs.
|
||||
|
||||
Purpose
|
||||
-------
|
||||
MCP clients expect OAuth providers to support Dynamic Client Registration (DCR),
|
||||
where clients can register themselves dynamically and receive unique credentials.
|
||||
Most enterprise IDPs (Google, GitHub, Azure AD, etc.) don't support DCR and require
|
||||
pre-registered OAuth applications with fixed credentials.
|
||||
|
||||
This proxy bridges that gap by:
|
||||
- Presenting a full DCR-compliant OAuth interface to MCP clients
|
||||
- Translating DCR registration requests to use pre-configured upstream credentials
|
||||
- Proxying all OAuth flows to the upstream IDP with appropriate translations
|
||||
- Managing the state and security requirements of both protocols
|
||||
|
||||
Architecture Overview
|
||||
--------------------
|
||||
The proxy maintains a single OAuth app registration with the upstream provider
|
||||
while allowing unlimited MCP clients to register and authenticate dynamically.
|
||||
It implements the complete OAuth 2.1 + DCR specification for clients while
|
||||
translating to whatever OAuth variant the upstream provider requires.
|
||||
|
||||
Key Translation Challenges Solved
|
||||
---------------------------------
|
||||
1. Dynamic Client Registration:
|
||||
- MCP clients expect to register dynamically and get unique credentials
|
||||
- Upstream IDPs require pre-registered apps with fixed credentials
|
||||
- Solution: Accept DCR requests, return shared upstream credentials
|
||||
|
||||
2. Dynamic Redirect URIs:
|
||||
- MCP clients use random localhost ports that change between sessions
|
||||
- Upstream IDPs require fixed, pre-registered redirect URIs
|
||||
- Solution: Use proxy's fixed callback URL with upstream, forward to client's dynamic URI
|
||||
|
||||
3. Authorization Code Mapping:
|
||||
- Upstream returns codes for the proxy's redirect URI
|
||||
- Clients expect codes for their own redirect URIs
|
||||
- Solution: Exchange upstream code server-side, issue new code to client
|
||||
|
||||
4. State Parameter Collision:
|
||||
- Both client and proxy need to maintain state through the flow
|
||||
- Only one state parameter available in OAuth
|
||||
- Solution: Use transaction ID as state with upstream, preserve client's state
|
||||
|
||||
5. Token Management:
|
||||
- Clients may expect different token formats/claims than upstream provides
|
||||
- Need to track tokens for revocation and refresh
|
||||
- Solution: Store token relationships, forward upstream tokens transparently
|
||||
|
||||
OAuth Flow Implementation
|
||||
------------------------
|
||||
1. Client Registration (DCR):
|
||||
- Accept any client registration request
|
||||
- Store ProxyDCRClient that accepts dynamic redirect URIs
|
||||
|
||||
2. Authorization:
|
||||
- Store transaction mapping client details to proxy flow
|
||||
- Redirect to upstream with proxy's fixed redirect URI
|
||||
- Use transaction ID as state parameter with upstream
|
||||
|
||||
3. Upstream Callback:
|
||||
- Exchange upstream authorization code for tokens (server-side)
|
||||
- Generate new authorization code bound to client's PKCE challenge
|
||||
- Redirect to client's original dynamic redirect URI
|
||||
|
||||
4. Token Exchange:
|
||||
- Validate client's code and PKCE verifier
|
||||
- Return previously obtained upstream tokens
|
||||
- Clean up one-time use authorization code
|
||||
|
||||
5. Token Refresh:
|
||||
- Forward refresh requests to upstream using authlib
|
||||
- Handle token rotation if upstream issues new refresh token
|
||||
- Update local token mappings
|
||||
|
||||
State Management
|
||||
---------------
|
||||
The proxy maintains minimal but crucial state via pluggable storage (client_storage):
|
||||
- _oauth_transactions: Active authorization flows with client context
|
||||
- _client_codes: Authorization codes with PKCE challenges and upstream tokens
|
||||
- _jti_mapping_store: Maps FastMCP token JTIs to upstream token IDs
|
||||
- _refresh_token_store: Refresh token metadata (keyed by token hash)
|
||||
|
||||
All state is stored in the configured client_storage backend (Redis, disk, etc.)
|
||||
enabling horizontal scaling across multiple instances.
|
||||
|
||||
Security Considerations
|
||||
----------------------
|
||||
- Refresh tokens stored by hash only (defense in depth if storage compromised)
|
||||
- PKCE enforced end-to-end (client to proxy, proxy to upstream)
|
||||
- Authorization codes are single-use with short expiry
|
||||
- Transaction IDs are cryptographically random
|
||||
- All state is cleaned up after use to prevent replay
|
||||
- Token validation delegates to upstream provider
|
||||
|
||||
Provider Compatibility
|
||||
---------------------
|
||||
Works with any OAuth 2.0 provider that supports:
|
||||
- Authorization code flow
|
||||
- Fixed redirect URI (configured in provider's app settings)
|
||||
- Standard token endpoint
|
||||
|
||||
Handles provider-specific requirements:
|
||||
- Google: Ensures minimum scope requirements
|
||||
- GitHub: Compatible with OAuth Apps and GitHub Apps
|
||||
- Azure AD: Handles tenant-specific endpoints
|
||||
- Generic: Works with any spec-compliant provider
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `set_mcp_path` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L472" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_mcp_path(self, mcp_path: str | None) -> None
|
||||
```
|
||||
|
||||
Set the MCP endpoint path and create JWTIssuer with correct audience.
|
||||
|
||||
This method is called by get_routes() to configure the resource URL
|
||||
and create the JWTIssuer. The JWT audience is set to the full resource
|
||||
URL (e.g., http://localhost:8000/mcp) to ensure tokens are bound to
|
||||
this specific MCP endpoint.
|
||||
|
||||
**Args:**
|
||||
- `mcp_path`: The path where the MCP endpoint is mounted (e.g., "/mcp")
|
||||
|
||||
|
||||
#### `jwt_issuer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L496" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
jwt_issuer(self) -> JWTIssuer
|
||||
```
|
||||
|
||||
Get the JWT issuer, ensuring it has been initialized.
|
||||
|
||||
The JWT issuer is created when set_mcp_path() is called (via get_routes()).
|
||||
This property ensures a clear error if used before initialization.
|
||||
|
||||
|
||||
#### `get_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L533" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_client(self, client_id: str) -> OAuthClientInformationFull | None
|
||||
```
|
||||
|
||||
Get client information by ID. This is generally the random ID
|
||||
provided to the DCR client during registration, not the upstream client ID.
|
||||
|
||||
For unregistered clients, returns None (which will raise an error in the SDK).
|
||||
|
||||
|
||||
#### `register_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L549" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
register_client(self, client_info: OAuthClientInformationFull) -> None
|
||||
```
|
||||
|
||||
Register a client locally
|
||||
|
||||
When a client registers, we create a ProxyDCRClient that is more
|
||||
forgiving about validating redirect URIs, since the DCR client's
|
||||
redirect URI will likely be localhost or unknown to the proxied IDP. The
|
||||
proxied IDP only knows about this server's fixed redirect URI.
|
||||
|
||||
|
||||
#### `authorize` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L602" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str
|
||||
```
|
||||
|
||||
Start OAuth transaction and route through consent interstitial.
|
||||
|
||||
Flow:
|
||||
1. Validate client's resource matches server's resource URL (security check)
|
||||
2. Store transaction with client details and PKCE (if forwarding)
|
||||
3. Return local /consent URL; browser visits consent first
|
||||
4. Consent handler redirects to upstream IdP if approved/already approved
|
||||
|
||||
If consent is disabled (require_authorization_consent=False), skip the consent screen
|
||||
and redirect directly to the upstream IdP.
|
||||
|
||||
|
||||
#### `load_authorization_code` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L698" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_authorization_code(self, client: OAuthClientInformationFull, authorization_code: str) -> AuthorizationCode | None
|
||||
```
|
||||
|
||||
Load authorization code for validation.
|
||||
|
||||
Look up our client code and return authorization code object
|
||||
with PKCE challenge for validation.
|
||||
|
||||
|
||||
#### `exchange_authorization_code` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L746" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
exchange_authorization_code(self, client: OAuthClientInformationFull, authorization_code: AuthorizationCode) -> OAuthToken
|
||||
```
|
||||
|
||||
Exchange authorization code for FastMCP-issued tokens.
|
||||
|
||||
Implements the token factory pattern:
|
||||
1. Retrieves upstream tokens from stored authorization code
|
||||
2. Extracts user identity from upstream token
|
||||
3. Encrypts and stores upstream tokens
|
||||
4. Issues FastMCP-signed JWT tokens
|
||||
5. Returns FastMCP tokens (NOT upstream tokens)
|
||||
|
||||
PKCE validation is handled by the MCP framework before this method is called.
|
||||
|
||||
|
||||
#### `load_refresh_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L946" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str) -> RefreshToken | None
|
||||
```
|
||||
|
||||
Load refresh token metadata from distributed storage.
|
||||
|
||||
Looks up by token hash and reconstructs the RefreshToken object.
|
||||
Validates that the token belongs to the requesting client.
|
||||
|
||||
|
||||
#### `exchange_refresh_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L975" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
exchange_refresh_token(self, client: OAuthClientInformationFull, refresh_token: RefreshToken, scopes: list[str]) -> OAuthToken
|
||||
```
|
||||
|
||||
Exchange FastMCP refresh token for new FastMCP access token.
|
||||
|
||||
Implements two-tier refresh:
|
||||
1. Verify FastMCP refresh token
|
||||
2. Look up upstream token via JTI mapping
|
||||
3. Refresh upstream token with upstream provider
|
||||
4. Update stored upstream token
|
||||
5. Issue new FastMCP access token
|
||||
6. Keep same FastMCP refresh token (unless upstream rotates)
|
||||
|
||||
|
||||
#### `load_access_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1198" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_access_token(self, token: str) -> AccessToken | None
|
||||
```
|
||||
|
||||
Validate FastMCP JWT by swapping for upstream token.
|
||||
|
||||
This implements the token swap pattern:
|
||||
1. Verify FastMCP JWT signature (proves it's our token)
|
||||
2. Look up upstream token via JTI mapping
|
||||
3. Decrypt upstream token
|
||||
4. Validate upstream token with provider (GitHub API, JWT validation, etc.)
|
||||
5. Return upstream validation result
|
||||
|
||||
The FastMCP JWT is a reference token - all authorization data comes
|
||||
from validating the upstream token via the TokenVerifier.
|
||||
|
||||
|
||||
#### `revoke_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1257" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
revoke_token(self, token: AccessToken | RefreshToken) -> None
|
||||
```
|
||||
|
||||
Revoke token locally and with upstream server if supported.
|
||||
|
||||
For refresh tokens, removes from local storage by hash.
|
||||
For all tokens, attempts upstream revocation if endpoint is configured.
|
||||
Access token JTI mappings expire via TTL.
|
||||
|
||||
|
||||
#### `get_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1290" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_routes(self, mcp_path: str | None = None) -> list[Route]
|
||||
```
|
||||
|
||||
Get OAuth routes with custom handlers for better error UX.
|
||||
|
||||
This method creates standard OAuth routes and replaces:
|
||||
- /authorize endpoint: Enhanced error responses for unregistered clients
|
||||
- /token endpoint: OAuth 2.1 compliant error codes
|
||||
|
||||
**Args:**
|
||||
- `mcp_path`: The path where the MCP endpoint is mounted (e.g., "/mcp")
|
||||
This is used to advertise the resource URL in metadata.
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue