mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-23 22:14:18 +02:00
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Jeremiah Lowin <jlowin@users.noreply.github.com> Co-authored-by: Marvin Context Protocol <41898282+Marvin Context Protocol@users.noreply.github.com> Co-authored-by: voidborne-d <voidborne-d@users.noreply.github.com> Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: d 🔹 <258577966+voidborne-d@users.noreply.github.com> Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Co-authored-by: nightcityblade <nightcityblade@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Bill Easton <strawgate@users.noreply.github.com> Co-authored-by: Sumanshu Nankana <sumanshunankana@gmail.com> Co-authored-by: Eric Robinson <ericrobinson@indeed.com> Co-authored-by: Martim Santos <martimfasantos@gmail.com> Co-authored-by: d 🔹 <liusway405@gmail.com> Co-authored-by: Matthieu B <66959271+mtthidoteu@users.noreply.github.com> Co-authored-by: Sascha Buehrle <47737812+saschabuehrle@users.noreply.github.com> Co-authored-by: Hakancan <142545736+hkc5@users.noreply.github.com> Co-authored-by: nightcityblade <jackchen@haloailabs.com> Co-authored-by: Matt Hallowell <17804673+mhallo@users.noreply.github.com> Co-authored-by: nate nowack <thrast36@gmail.com> Co-authored-by: Bill Easton <williamseaston@gmail.com> Co-authored-by: Marcus Shu <46469249+shulkx@users.noreply.github.com> Co-authored-by: Rushabh Doshi <radoshi@gmail.com> Co-authored-by: AIKAWA Shigechika <shige@aikawa.jp> Co-authored-by: Jeremy Simon <simonjer805@gmail.com> Co-authored-by: Miguel Miranda Dias <7780875+pandego@users.noreply.github.com> Co-authored-by: Anthony James Padavano <padavano.anthony@gmail.com> Co-authored-by: Mostafa Kamal <hiremostafa@gmail.com> Fix auto-close MRE script posting comment without closing (#3386) Fix WorkOS token scope verification bypass 🤖 Generated with Codex (#3407) Fix initialize McpError fallthrough 🤖 Generated with Codex (#3413) Fix transform arg collisions with passthrough params (#3431) Fix get_* returning None when latest version is disabled (#3439) Fix get_* returning None when latest version is disabled (#3421) Fix server lifespan overlap teardown (#3415) Fix $ref output schema object detection regression (#3420) resolved annotations (#3429) Fix async partial callables rejected by iscoroutinefunction (#3438) Fix async partial callables rejected by iscoroutinefunction (#3423) fix: add version to components (#3458) fix: use intent-based flag for OIDC scope patch in load_access_token (#3465) Fixes #3461 fix: normalize Google scope shorthands and surface valid_scopes (#3477) fix: resolve ty 0.0.23 type-checking errors and bump pin (#3481) fix: shield lifespan teardown from cancellation (#3480) fix: forward custom_route endpoints from mounted servers (#3462) fix updates _get_additional_http_routes() to traverse providers, Fixes #3457 fix: remove hardcoded version from CLI help text (#3456) fix: monty 0.0.8 compatibility, drop external_functions from constructor (#3468) fix: task test teardown hanging 5s per test (#3499) Closes #3498 fix: validate workspace path is a directory before cursor install (#3440) Fixes #3426 fix: handle re.error from malformed URI templates in build_regex (#3501) fix: reject empty/OIDC-only required_scopes in AzureProvider (#3503) fix: restrict $ref resolution to local refs only (SSRF/LFI) (#3502) fix warnings and timeouts (#3504) close upgrade check issue when build passes (#3505) Closes #3484 fix: URL-encode path params to prevent SSRF/path traversal (GHSA-vv7q-7jx5-f767) (#3507) fix: prevent path traversal in skill download (#3493) fix: prefer IdP-granted scopes over client-requested scopes in OAuthProxy (#3492) fix: remove unrelated transform and http.py changes from PR scope fix: remove forced follow_redirects from httpx_client_factory calls (#3496) fix: stop passing follow_redirects to httpx_client_factory fix: restore follow_redirects=True for custom httpx client factories Closes #3509 fix: CSRF double-submit cookie check in consent flow (#3519) fix: validate server names in install commands (#3522) fix: use raw strings for regex in pytest.raises match (#3523) fix: reject refresh tokens used as Bearer access tokens (#3524) fix: route ResourcesAsTools/PromptsAsTools through server middleware (#3495) fix: resolve Pyright "Module is not callable" on @tool, @resource, @prompt decorators (#3540) fix: filter warnings by message in KEY_PREFIX test (#3549) fix: suppress output schema for ToolResult subclass annotations (#3548) fix: increase sleep duration in proxy cache tests (#3567) fix: store absolute token expiry to prevent stale expires_in on reload (#3572) fix: preserve tool properties named 'title' during schema compression (#3582) Fix loopback redirect URI port matching per RFC 8252 §7.3 (#3589) Fix app tool routing: visibility check and middleware propagation (#3591) Fix query parameter serialization to respect OpenAPI explode/style settings (#3595) Fix dev apps form: union types, textarea support, JSON parsing (#3597) fix(google): replace deprecated /oauth2/v1/tokeninfo with /oauth2/v3/userinfo (#3603) fix: resolve EntraOBOToken dependency injection through MultiAuth (#3609) fix(docs): correct misleading stateless_http header (#3622) fix: filesystem provider import machinery (#3626) Closes #3625 (issues 2, 3, 6) fix: recover StdioTransport after subprocess exits (#3630) fix(server): preserve mounted tool task metadata (#3632) fix: scope deprecation warning filter to FastMCPDeprecationWarning (#3649) fix imports, add PrefabAppConfig (#3650) fix: resolve CurrentFastMCP/ctx.fastmcp to child server in mounted background tasks (#3651) Fix blocking docs issues: chart imports, Select API, Rx consistency (#3652) closed by default (#3657) Fix prompt caching middleware missing wrap/unwrap round-trip (#3666) fix: serialize object query params per OpenAPI style/explode rules (#3662) Fixes #2857 fix: HTTP request headers not accessible in background task workers (#3631) fix: restore HTTP headers in worker execution path for background tasks (#3681) fix: strip discriminator after dereferencing schemas (#3682) fix: remove stale ty:ignore directives for ty 0.0.26 (#3684) Fix docs gaps in app provider pages (#3690) fix: dev apps log panel UX improvements (#3698) fix dev server empty string args (#3700)
281 lines
11 KiB
Text
281 lines
11 KiB
Text
---
|
|
title: OIDC Proxy
|
|
sidebarTitle: OIDC Proxy
|
|
description: Bridge OIDC providers to work seamlessly with MCP's authentication flow.
|
|
icon: share
|
|
---
|
|
|
|
import { VersionBadge } from "/snippets/version-badge.mdx";
|
|
|
|
<VersionBadge version="2.12.4" />
|
|
|
|
The OIDC proxy enables FastMCP servers to authenticate with OIDC providers that **don't support Dynamic Client Registration (DCR)** out of the box. This includes OAuth providers like: Auth0, Google, Azure, AWS, etc. For providers that do support DCR (like WorkOS AuthKit), use [`RemoteAuthProvider`](/servers/auth/remote-oauth) instead.
|
|
|
|
The OIDC proxy is built upon [`OAuthProxy`](/servers/auth/oauth-proxy) so it has all the same functionality under the covers.
|
|
|
|
## Implementation
|
|
|
|
### Provider Setup Requirements
|
|
|
|
Before using the OIDC proxy, you need to register your application with your OAuth provider:
|
|
|
|
1. **Register your application** in the provider's developer console (Auth0 Applications, Google Cloud Console, Azure Portal, etc.)
|
|
2. **Configure the redirect URI** as your FastMCP server URL plus your chosen callback path:
|
|
- Default: `https://your-server.com/auth/callback`
|
|
- Custom: `https://your-server.com/your/custom/path` (if you set `redirect_path`)
|
|
- Development: `http://localhost:8000/auth/callback`
|
|
3. **Obtain your credentials**: Client ID and Client Secret
|
|
|
|
<Warning>
|
|
The redirect URI you configure with your provider must exactly match your
|
|
FastMCP server's URL plus the callback path. If you customize `redirect_path`
|
|
in the OIDC proxy, update your provider's redirect URI accordingly.
|
|
</Warning>
|
|
|
|
### Basic Setup
|
|
|
|
Here's how to implement the OIDC proxy with any provider:
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.server.auth.oidc_proxy import OIDCProxy
|
|
|
|
# Create the OIDC proxy
|
|
auth = OIDCProxy(
|
|
# Provider's configuration URL
|
|
config_url="https://provider.com/.well-known/openid-configuration",
|
|
|
|
# Your registered app credentials
|
|
client_id="your-client-id",
|
|
client_secret="your-client-secret",
|
|
|
|
# Your FastMCP server's public URL
|
|
base_url="https://your-server.com",
|
|
|
|
# Optional: customize the callback path (default is "/auth/callback")
|
|
# redirect_path="/custom/callback",
|
|
)
|
|
|
|
mcp = FastMCP(name="My Server", auth=auth)
|
|
```
|
|
|
|
### Configuration Parameters
|
|
|
|
<Card icon="code" title="OIDCProxy Parameters">
|
|
<ParamField body="config_url" type="str" required>
|
|
URL of your OAuth provider's OIDC configuration
|
|
</ParamField>
|
|
|
|
<ParamField body="client_id" type="str" required>
|
|
Client ID from your registered OAuth application
|
|
</ParamField>
|
|
|
|
<ParamField body="client_secret" type="str | None">
|
|
Client secret from your registered OAuth application. Optional for PKCE public
|
|
clients. When omitted, `jwt_signing_key` must be provided.
|
|
</ParamField>
|
|
|
|
<ParamField body="base_url" type="AnyHttpUrl | str" required>
|
|
Public URL of your FastMCP server (e.g., `https://your-server.com`)
|
|
</ParamField>
|
|
|
|
<ParamField body="strict" type="bool | None">
|
|
Strict flag for configuration validation. When True, requires all OIDC
|
|
mandatory fields.
|
|
</ParamField>
|
|
|
|
<ParamField body="audience" type="str | None">
|
|
Audience parameter for OIDC providers that require it (e.g., Auth0). This is
|
|
typically your API identifier.
|
|
</ParamField>
|
|
|
|
<ParamField body="timeout_seconds" type="int | None" default="10">
|
|
HTTP request timeout in seconds for fetching OIDC configuration
|
|
</ParamField>
|
|
|
|
<ParamField body="token_verifier" type="TokenVerifier | None">
|
|
|
|
<VersionBadge version="2.13.1" />
|
|
Custom token verifier for validating tokens. When provided, FastMCP uses your custom verifier instead of creating a default `JWTVerifier`.
|
|
|
|
Cannot be used with `algorithm` or `required_scopes` parameters - configure these on your verifier instead. The verifier's `required_scopes` are automatically loaded and advertised.
|
|
</ParamField>
|
|
|
|
<ParamField body="algorithm" type="str | None">
|
|
JWT algorithm to use for token verification (e.g., "RS256"). If not specified,
|
|
uses the provider's default. Only used when `token_verifier` is not provided.
|
|
</ParamField>
|
|
|
|
<ParamField body="required_scopes" type="list[str] | None">
|
|
List of OAuth scopes for token validation. These are automatically
|
|
included in authorization requests. Only used when `token_verifier` is not provided.
|
|
</ParamField>
|
|
|
|
<ParamField body="redirect_path" type="str" default="/auth/callback">
|
|
Path for OAuth callbacks. Must match the redirect URI configured in your OAuth
|
|
application
|
|
</ParamField>
|
|
|
|
<ParamField body="allowed_client_redirect_uris" type="list[str] | None">
|
|
List of allowed redirect URI patterns for MCP clients. Patterns support wildcards (e.g., `"http://localhost:*"`, `"https://*.example.com/*"`).
|
|
- `None` (default): All redirect URIs allowed (for MCP/DCR compatibility)
|
|
- Empty list `[]`: No redirect URIs allowed
|
|
- Custom list: Only matching patterns allowed
|
|
|
|
These patterns apply to MCP client loopback redirects, NOT the upstream OAuth app redirect URI.
|
|
|
|
</ParamField>
|
|
|
|
<ParamField body="token_endpoint_auth_method" type="str | None">
|
|
Token endpoint authentication method for the upstream OAuth server. Controls how the proxy authenticates when exchanging authorization codes and refresh tokens with the upstream provider.
|
|
- `"client_secret_basic"`: Send credentials in Authorization header (most common)
|
|
- `"client_secret_post"`: Send credentials in request body (required by some providers)
|
|
- `"none"`: No authentication (for public clients)
|
|
- `None` (default): Uses authlib's default (typically `"client_secret_basic"`)
|
|
|
|
Set this if your provider requires a specific authentication method and the default doesn't work.
|
|
|
|
</ParamField>
|
|
|
|
<ParamField body="jwt_signing_key" type="str | bytes | None">
|
|
|
|
<VersionBadge version="2.13.0" />
|
|
Secret used to sign FastMCP JWT tokens issued to clients. Accepts any string or bytes - will be derived into a proper 32-byte cryptographic key using HKDF.
|
|
|
|
**Default behavior (`None`):**
|
|
- **Mac/Windows**: Auto-managed via system keyring. Keys are generated once and persisted, surviving server restarts with zero configuration. Keys are automatically derived from server attributes, so this approach, while convenient, is **only** suitable for development and local testing. For production, you must provide an explicit secret.
|
|
- **Linux**: Ephemeral (random salt at startup). Tokens become invalid on server restart, triggering client re-authentication.
|
|
|
|
**For production:**
|
|
Provide an explicit secret (e.g., from environment variable) to use a fixed key instead of the auto-generated one.
|
|
</ParamField>
|
|
|
|
<ParamField body="client_storage" type="AsyncKeyValue | None">
|
|
|
|
<VersionBadge version="2.13.0" />
|
|
Storage backend for persisting OAuth client registrations and upstream tokens.
|
|
|
|
**Default behavior:**
|
|
- **Mac/Windows**: Encrypted DiskStore in your platform's data directory (derived from `platformdirs`)
|
|
- **Linux**: MemoryStore (ephemeral - clients lost on restart)
|
|
|
|
By default on Mac/Windows, clients are automatically persisted to encrypted disk storage, allowing them to survive server restarts as long as the filesystem remains accessible. This means MCP clients only need to register once and can reconnect seamlessly. On Linux where keyring isn't available, ephemeral storage is used to match the ephemeral key strategy.
|
|
|
|
For production deployments with multiple servers or cloud deployments, use a network-accessible storage backend rather than local disk storage. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest.** See [Storage Backends](/servers/storage-backends) for available options.
|
|
|
|
Testing with in-memory storage (unencrypted):
|
|
|
|
```python
|
|
from key_value.aio.stores.memory import MemoryStore
|
|
|
|
# Use in-memory storage for testing (clients lost on restart)
|
|
auth = OIDCProxy(..., client_storage=MemoryStore())
|
|
```
|
|
|
|
Production with encrypted Redis storage:
|
|
|
|
```python
|
|
from key_value.aio.stores.redis import RedisStore
|
|
from key_value.aio.wrappers.encryption import FernetEncryptionWrapper
|
|
from cryptography.fernet import Fernet
|
|
import os
|
|
|
|
auth = OIDCProxy(
|
|
...,
|
|
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
|
|
client_storage=FernetEncryptionWrapper(
|
|
key_value=RedisStore(host="redis.example.com", port=6379),
|
|
fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"])
|
|
)
|
|
)
|
|
```
|
|
|
|
</ParamField>
|
|
|
|
<ParamField body="require_authorization_consent" type="bool" default="True">
|
|
Whether to require user consent before authorizing MCP clients. When enabled (default), users see a consent screen that displays which client is requesting access. See [OAuthProxy documentation](/servers/auth/oauth-proxy#confused-deputy-attacks) for details on confused deputy attack protection.
|
|
</ParamField>
|
|
|
|
<ParamField body="consent_csp_policy" type="str | None" default="None">
|
|
Content Security Policy for the consent page.
|
|
|
|
- `None` (default): Uses the built-in CSP policy with appropriate directives for form submission
|
|
- Empty string `""`: Disables CSP entirely (no meta tag rendered)
|
|
- Custom string: Uses the provided value as the CSP policy
|
|
|
|
This is useful for organizations that have their own CSP policies and need to override or disable FastMCP's built-in CSP directives.
|
|
</ParamField>
|
|
</Card>
|
|
|
|
### Using Built-in Providers
|
|
|
|
FastMCP includes pre-configured OIDC providers for common services:
|
|
|
|
```python
|
|
from fastmcp.server.auth.providers.auth0 import Auth0Provider
|
|
|
|
auth = Auth0Provider(
|
|
config_url="https://.../.well-known/openid-configuration",
|
|
client_id="your-auth0-client-id",
|
|
client_secret="your-auth0-client-secret",
|
|
audience="https://...",
|
|
base_url="https://localhost:8000"
|
|
)
|
|
|
|
mcp = FastMCP(name="My Server", auth=auth)
|
|
```
|
|
|
|
Available providers include `Auth0Provider` at present.
|
|
|
|
### Scope Configuration
|
|
|
|
OAuth scopes are configured with `required_scopes` to automatically request the permissions your application needs.
|
|
|
|
Dynamic clients created by the proxy will automatically include these scopes in their authorization requests.
|
|
|
|
## CIMD Support
|
|
|
|
<VersionBadge version="3.0.0" />
|
|
|
|
The OIDC proxy inherits full CIMD (Client ID Metadata Document) support from `OAuthProxy`. Clients can use HTTPS URLs as their `client_id` instead of registering dynamically, and the proxy will fetch and validate their metadata document.
|
|
|
|
See the [OAuth Proxy CIMD documentation](/servers/auth/oauth-proxy#cimd-support) for complete details on how CIMD works, including private key JWT authentication and security considerations.
|
|
|
|
The CIMD-related parameters available on `OIDCProxy` are:
|
|
|
|
<Card icon="code" title="CIMD Parameters">
|
|
<ParamField body="enable_cimd" type="bool" default="True">
|
|
Whether to accept CIMD URLs as client identifiers.
|
|
</ParamField>
|
|
</Card>
|
|
|
|
## Production Configuration
|
|
|
|
For production deployments, load sensitive credentials from environment variables:
|
|
|
|
```python
|
|
import os
|
|
from fastmcp import FastMCP
|
|
from fastmcp.server.auth.providers.auth0 import Auth0Provider
|
|
|
|
# Load secrets from environment variables
|
|
auth = Auth0Provider(
|
|
config_url=os.environ.get("AUTH0_CONFIG_URL"),
|
|
client_id=os.environ.get("AUTH0_CLIENT_ID"),
|
|
client_secret=os.environ.get("AUTH0_CLIENT_SECRET"),
|
|
audience=os.environ.get("AUTH0_AUDIENCE"),
|
|
base_url=os.environ.get("BASE_URL", "https://localhost:8000")
|
|
)
|
|
|
|
mcp = FastMCP(name="My Server", auth=auth)
|
|
|
|
@mcp.tool
|
|
def protected_tool(data: str) -> str:
|
|
"""This tool is now protected by OAuth."""
|
|
return f"Processed: {data}"
|
|
|
|
if __name__ == "__main__":
|
|
mcp.run(transport="http", port=8000)
|
|
```
|
|
|
|
This keeps secrets out of your codebase while maintaining explicit configuration.
|