mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-20 12:34: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
|
|
@ -1,450 +0,0 @@
|
|||
---
|
||||
title: oauth_proxy
|
||||
sidebarTitle: oauth_proxy
|
||||
---
|
||||
|
||||
# `fastmcp.server.auth.oauth_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.
|
||||
|
||||
|
||||
## Functions
|
||||
|
||||
### `create_consent_html` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L256" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
create_consent_html(client_id: str, redirect_uri: str, scopes: list[str], txn_id: str, csrf_token: str, client_name: str | None = None, title: str = 'Application Access Request', server_name: str | None = None, server_icon_url: str | None = None, server_website_url: str | None = None, client_website_url: str | None = None, csp_policy: str | None = None) -> str
|
||||
```
|
||||
|
||||
|
||||
Create a styled HTML consent page for OAuth authorization requests.
|
||||
|
||||
**Args:**
|
||||
- `csp_policy`: Content Security Policy override.
|
||||
If None, uses the built-in CSP policy with appropriate directives.
|
||||
If empty string "", disables CSP entirely (no meta tag is rendered).
|
||||
If a non-empty string, uses that as the CSP policy value.
|
||||
|
||||
|
||||
### `create_error_html` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L426" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
create_error_html(error_title: str, error_message: str, error_details: dict[str, str] | None = None, server_name: str | None = None, server_icon_url: str | None = None) -> str
|
||||
```
|
||||
|
||||
|
||||
Create a styled HTML error page for OAuth errors.
|
||||
|
||||
**Args:**
|
||||
- `error_title`: The error title (e.g., "OAuth Error", "Authorization Failed")
|
||||
- `error_message`: The main error message to display
|
||||
- `error_details`: Optional dictionary of error details to show (e.g., `{"Error Code"\: "invalid_client"}`)
|
||||
- `server_name`: Optional server name to display
|
||||
- `server_icon_url`: Optional URL to server icon/logo
|
||||
|
||||
**Returns:**
|
||||
- Complete HTML page as a string
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `OAuthTransaction` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L106" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
OAuth transaction state for consent flow.
|
||||
|
||||
Stored server-side to track active authorization flows with client context.
|
||||
Includes CSRF tokens for consent protection per MCP security best practices.
|
||||
|
||||
|
||||
### `ClientCode` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L127" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Client authorization code with PKCE and upstream tokens.
|
||||
|
||||
Stored server-side after upstream IdP callback. Contains the upstream
|
||||
tokens bound to the client's PKCE challenge for secure token exchange.
|
||||
|
||||
|
||||
### `UpstreamTokenSet` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L145" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Stored upstream OAuth tokens from identity provider.
|
||||
|
||||
These tokens are obtained from the upstream provider (Google, GitHub, etc.)
|
||||
and stored in plaintext within this model. Encryption is handled transparently
|
||||
at the storage layer via FernetEncryptionWrapper. Tokens are never exposed to MCP clients.
|
||||
|
||||
|
||||
### `JTIMapping` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L167" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Maps FastMCP token JTI to upstream token ID.
|
||||
|
||||
This allows stateless JWT validation while still being able to look up
|
||||
the corresponding upstream token when tools need to access upstream APIs.
|
||||
|
||||
|
||||
### `RefreshTokenMetadata` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L179" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Metadata for a refresh token, stored keyed by token hash.
|
||||
|
||||
We store only metadata (not the token itself) for security - if storage
|
||||
is compromised, attackers get hashes they can't reverse into usable tokens.
|
||||
|
||||
|
||||
### `ProxyDCRClient` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L201" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Client for DCR proxy with configurable redirect URI validation.
|
||||
|
||||
This special client class is critical for the OAuth proxy to work correctly
|
||||
with Dynamic Client Registration (DCR). Here's why it exists:
|
||||
|
||||
Problem:
|
||||
--------
|
||||
When MCP clients use OAuth, they dynamically register with random localhost
|
||||
ports (e.g., http://localhost:55454/callback). The OAuth proxy needs to:
|
||||
1. Accept these dynamic redirect URIs from clients based on configured patterns
|
||||
2. Use its own fixed redirect URI with the upstream provider (Google, GitHub, etc.)
|
||||
3. Forward the authorization code back to the client's dynamic URI
|
||||
|
||||
Solution:
|
||||
---------
|
||||
This class validates redirect URIs against configurable patterns,
|
||||
while the proxy internally uses its own fixed redirect URI with the upstream
|
||||
provider. This allows the flow to work even when clients reconnect with
|
||||
different ports or when tokens are cached.
|
||||
|
||||
Without proper validation, clients could get "Redirect URI not registered" errors
|
||||
when trying to authenticate with cached tokens, or security vulnerabilities could
|
||||
arise from accepting arbitrary redirect URIs.
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `validate_redirect_uri` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L230" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
validate_redirect_uri(self, redirect_uri: AnyUrl | None) -> AnyUrl
|
||||
```
|
||||
|
||||
Validate redirect URI against allowed patterns.
|
||||
|
||||
Since we're acting as a proxy and clients register dynamically,
|
||||
we validate their redirect URIs against configurable patterns.
|
||||
This is essential for cached token scenarios where the client may
|
||||
reconnect with a different port.
|
||||
|
||||
|
||||
### `OAuthProxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L514" 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.py#L905" 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.py#L929" 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.py#L966" 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.py#L982" 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.py#L1035" 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.py#L1131" 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.py#L1179" 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.py#L1379" 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.py#L1408" 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.py#L1631" 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.py#L1690" 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.py#L1723" 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