From 1e485520f546db2af6030d6cf206750ceebbe7ec Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Fri, 26 Dec 2025 13:45:42 -0500
Subject: [PATCH 1/7] Remove automatic env var settings for auth providers
---
docs/integrations/auth0.mdx | 78 ++------
docs/integrations/authkit.mdx | 58 +-----
docs/integrations/aws-cognito.mdx | 78 ++------
docs/integrations/azure.mdx | 110 ++--------
docs/integrations/descope.mdx | 54 +----
docs/integrations/discord.mdx | 68 ++-----
docs/integrations/github.mdx | 76 ++-----
docs/integrations/google.mdx | 76 ++-----
docs/integrations/oci.mdx | 108 +++-------
docs/integrations/scalekit.mdx | 62 ++----
docs/integrations/supabase.mdx | 55 ++---
docs/integrations/workos.mdx | 90 +--------
.../fastmcp-server-auth-providers-auth0.mdx | 6 -
.../fastmcp-server-auth-providers-aws.mdx | 6 -
.../fastmcp-server-auth-providers-azure.mdx | 6 -
.../fastmcp-server-auth-providers-descope.mdx | 2 -
.../fastmcp-server-auth-providers-discord.mdx | 6 -
.../fastmcp-server-auth-providers-github.mdx | 6 -
.../fastmcp-server-auth-providers-google.mdx | 6 -
.../fastmcp-server-auth-providers-oci.mdx | 37 ++--
...fastmcp-server-auth-providers-scalekit.mdx | 2 -
...fastmcp-server-auth-providers-supabase.mdx | 2 -
.../fastmcp-server-auth-providers-workos.mdx | 8 -
docs/servers/auth/authentication.mdx | 78 ++------
docs/servers/auth/oauth-proxy.mdx | 32 ++-
docs/servers/auth/oidc-proxy.mdx | 36 ++--
docs/servers/auth/token-verification.mdx | 42 ++--
src/fastmcp/server/auth/providers/auth0.py | 118 +++--------
src/fastmcp/server/auth/providers/aws.py | 125 +++---------
src/fastmcp/server/auth/providers/azure.py | 184 +++++------------
src/fastmcp/server/auth/providers/descope.py | 67 ++-----
src/fastmcp/server/auth/providers/discord.py | 115 +++--------
src/fastmcp/server/auth/providers/github.py | 114 +++--------
src/fastmcp/server/auth/providers/google.py | 115 +++--------
src/fastmcp/server/auth/providers/oci.py | 117 +++--------
src/fastmcp/server/auth/providers/scalekit.py | 84 +++-----
src/fastmcp/server/auth/providers/supabase.py | 55 ++---
src/fastmcp/server/auth/providers/workos.py | 165 ++++-----------
tests/server/auth/providers/test_auth0.py | 188 +-----------------
tests/server/auth/providers/test_aws.py | 144 --------------
tests/server/auth/providers/test_azure.py | 174 +++-------------
tests/server/auth/providers/test_descope.py | 42 +---
tests/server/auth/providers/test_discord.py | 60 ------
tests/server/auth/providers/test_github.py | 127 ------------
tests/server/auth/providers/test_google.py | 60 ------
tests/server/auth/providers/test_scalekit.py | 35 ----
tests/server/auth/providers/test_supabase.py | 47 +----
tests/server/auth/providers/test_workos.py | 67 -------
48 files changed, 569 insertions(+), 2822 deletions(-)
diff --git a/docs/integrations/auth0.mdx b/docs/integrations/auth0.mdx
index 160af738fd..77c00acb02 100644
--- a/docs/integrations/auth0.mdx
+++ b/docs/integrations/auth0.mdx
@@ -195,73 +195,25 @@ For complete details on these parameters, see the [OAuth Proxy documentation](/s
The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache.
-## Environment Variables
-
-For production deployments, use environment variables instead of hardcoding credentials.
-
-### Provider Selection
-
-Setting this environment variable allows the Auth0 provider to be used automatically without explicitly instantiating it in code.
-
-
-
-Set to `fastmcp.server.auth.providers.auth0.Auth0Provider` to use Auth0 authentication.
-
-
-
-### Auth0-Specific Configuration
-
-These environment variables provide default values for the Auth0 provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
-
-
-
-Your Auth0 Application Configuration URL (e.g., `https://.../.well-known/openid-configuration`)
-
-
-
-Your Auth0 Application Client ID (e.g., `tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB`)
-
-
-
-Your Auth0 Application Client Secret (e.g., `vPYqbjemq...`)
-
-
-
-Your Auth0 API Audience
-
-
-
-Public URL where OAuth endpoints will be accessible (includes any mount path)
-
-
-
-Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details.
-
+## Production Configuration
-
-Redirect path configured in your Auth0 Application
-
+For production deployments, load sensitive credentials from environment variables:
-
-Comma-, space-, or JSON-separated list of required AUth0 scopes (e.g., `openid email` or `["openid","email"]`)
-
-
+```python server.py
+import os
+from fastmcp import FastMCP
+from fastmcp.server.auth.providers.auth0 import Auth0Provider
-Example `.env` file:
-```bash
-# Use the Auth0 provider
-FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.auth0.Auth0Provider
-
-# Auth0 configuration and credentials
-FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL=https://.../.well-known/openid-configuration
-FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID=tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB
-FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET=vPYqbjemq...
-FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE=https://...
-FASTMCP_SERVER_AUTH_AUTH0_BASE_URL=https://your-server.com
-FASTMCP_SERVER_AUTH_AUTH0_REQUIRED_SCOPES=openid,email
-```
+# 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://your-server.com")
+)
-With environment variables set, your server code simplifies to:
+mcp = FastMCP(name="Auth0 Secured App", auth=auth)
```python server.py
from fastmcp import FastMCP
diff --git a/docs/integrations/authkit.mdx b/docs/integrations/authkit.mdx
index 2072638fac..1242df2bbe 100644
--- a/docs/integrations/authkit.mdx
+++ b/docs/integrations/authkit.mdx
@@ -78,56 +78,20 @@ if __name__ == "__main__":
```
-## Environment Variables
+## Production Configuration
-
-
-For production deployments, use environment variables instead of hardcoding credentials.
-
-### Provider Selection
-
-Setting this environment variable allows the AuthKit provider to be used automatically without explicitly instantiating it in code.
-
-
-
-Set to `fastmcp.server.auth.providers.workos.AuthKitProvider` to use AuthKit authentication.
-
-
-
-### AuthKit-Specific Configuration
-
-These environment variables provide default values for the AuthKit provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
-
-
-
-Your AuthKit domain (e.g., `https://your-project-12345.authkit.app`)
-
-
-
-Public URL of your FastMCP server (e.g., `https://your-server.com` or `http://localhost:8000` for development)
-
-
-
-Comma-, space-, or JSON-separated list of required OAuth scopes (e.g., `openid profile email` or `["openid", "profile", "email"]`)
-
-
-
-Example `.env` file:
-```bash
-# Use the AuthKit provider
-FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.workos.AuthKitProvider
-
-# AuthKit configuration
-FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_AUTHKIT_DOMAIN=https://your-project-12345.authkit.app
-FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_BASE_URL=https://your-server.com
-FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_REQUIRED_SCOPES=openid,profile,email
-```
-
-With environment variables set, your server code simplifies to:
+For production deployments, load sensitive configuration from environment variables:
```python server.py
+import os
from fastmcp import FastMCP
+from fastmcp.server.auth.providers.workos import AuthKitProvider
+
+# Load configuration from environment variables
+auth = AuthKitProvider(
+ authkit_domain=os.environ.get("AUTHKIT_DOMAIN"),
+ base_url=os.environ.get("BASE_URL", "https://your-server.com")
+)
-# Authentication is automatically configured from environment
-mcp = FastMCP(name="AuthKit Secured App")
+mcp = FastMCP(name="AuthKit Secured App", auth=auth)
```
\ No newline at end of file
diff --git a/docs/integrations/aws-cognito.mdx b/docs/integrations/aws-cognito.mdx
index 965791c38d..58c6c8fdc6 100644
--- a/docs/integrations/aws-cognito.mdx
+++ b/docs/integrations/aws-cognito.mdx
@@ -238,73 +238,25 @@ Parameters (`jwt_signing_key` and `client_storage`) work together to ensure toke
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
-## Environment Variables
-
-For production deployments, use environment variables instead of hardcoding credentials.
-
-### Provider Selection
-
-Setting this environment variable allows the AWS Cognito provider to be used automatically without explicitly instantiating it in code.
-
-
-
-Set to `fastmcp.server.auth.providers.aws.AWSCognitoProvider` to use AWS Cognito authentication.
-
-
-
-### AWS Cognito-Specific Configuration
-
-These environment variables provide default values for the AWS Cognito provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
-
-
-
-Your AWS Cognito user pool ID (e.g., `eu-central-1_XXXXXXXXX`)
-
-
-
-AWS region where your AWS Cognito user pool is located
-
-
-
-Your AWS Cognito app client ID
-
-
-
-Your AWS Cognito app client secret
-
-
-
-Public URL where OAuth endpoints will be accessible (includes any mount path)
-
-
-
-Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details.
-
+## Production Configuration
-
-One of the redirect paths configured in your AWS Cognito app client
-
+For production deployments, load sensitive credentials from environment variables:
-
-Comma-, space-, or JSON-separated list of required OAuth scopes (e.g., `openid email` or `["openid","email","profile"]`)
-
-
+```python server.py
+import os
+from fastmcp import FastMCP
+from fastmcp.server.auth.providers.aws import AWSCognitoProvider
-Example `.env` file:
-```bash
-# Use the AWS Cognito provider
-FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.aws.AWSCognitoProvider
-
-# AWS Cognito credentials
-FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID=eu-central-1_XXXXXXXXX
-FASTMCP_SERVER_AUTH_AWS_COGNITO_AWS_REGION=eu-central-1
-FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID=your-app-client-id
-FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET=your-app-client-secret
-FASTMCP_SERVER_AUTH_AWS_COGNITO_BASE_URL=https://your-server.com
-FASTMCP_SERVER_AUTH_AWS_COGNITO_REQUIRED_SCOPES=openid,email,profile
-```
+# Load secrets from environment variables
+auth = AWSCognitoProvider(
+ user_pool_id=os.environ.get("AWS_COGNITO_USER_POOL_ID"),
+ client_id=os.environ.get("AWS_COGNITO_CLIENT_ID"),
+ client_secret=os.environ.get("AWS_COGNITO_CLIENT_SECRET"),
+ base_url=os.environ.get("BASE_URL", "https://your-server.com"),
+ aws_region=os.environ.get("AWS_REGION", "eu-central-1")
+)
-With environment variables set, your server code simplifies to:
+mcp = FastMCP(name="AWS Cognito Secured App", auth=auth)
```python server.py
from fastmcp import FastMCP
diff --git a/docs/integrations/azure.mdx b/docs/integrations/azure.mdx
index e18843abf6..c27c8ba2c6 100644
--- a/docs/integrations/azure.mdx
+++ b/docs/integrations/azure.mdx
@@ -280,107 +280,25 @@ Parameters (`jwt_signing_key` and `client_storage`) work together to ensure toke
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
-## Environment Variables
-
-
-
-For production deployments, use environment variables instead of hardcoding credentials.
-
-### Provider Selection
-
-Setting this environment variable allows the Azure provider to be used automatically without explicitly instantiating it in code.
-
-
-
-Set to `fastmcp.server.auth.providers.azure.AzureProvider` to use Azure authentication.
-
-
-
-### Azure-Specific Configuration
-
-These environment variables provide default values for the Azure provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
-
-
-
-Your Azure App registration Client ID (e.g., `835f09b6-0f0f-40cc-85cb-f32c5829a149`)
-
-
-
-Your Azure App registration Client Secret
-
-
-
-Your Azure tenant ID (specific ID, "organizations", or "consumers")
-
-
-This is **REQUIRED**. Find your tenant ID in Azure Portal under Microsoft Entra ID → Overview.
-
-
-
-
-Public URL where OAuth endpoints will be accessible (includes any mount path)
-
-
-
-Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details.
-
-
-
-Redirect path configured in your Azure App registration
-
-
-
-Comma-, space-, or JSON-separated list of required scopes for your API (at least one scope required). These are validated on tokens and used as defaults if the client does not request specific scopes. Use unprefixed scope names from your Azure App registration (e.g., `read,write`).
-
-You can include standard OIDC scopes (`openid`, `profile`, `email`, `offline_access`) in `required_scopes`. FastMCP automatically handles them correctly: they're sent to Azure unprefixed and excluded from token validation (since Azure doesn't include OIDC scopes in access token `scp` claims).
-
-
-Azure's OAuth API requires the `scope` parameter - you must provide at least one scope.
-
-
-
-
-Comma-, space-, or JSON-separated list of additional scopes to include in the authorization request without prefixing. Use this to request upstream scopes such as Microsoft Graph permissions. These are not used for token validation.
-
-
-
-Application ID URI used to prefix scopes during authorization.
-
-
-
-Azure authority base URL. Override this to use Azure Government:
-
-- `login.microsoftonline.com` - Azure Public Cloud (default)
-- `login.microsoftonline.us` - Azure Government
-
-This setting affects all Azure OAuth endpoints (authorization, token, issuer, JWKS).
-
-
-
-Example `.env` file:
-```bash
-# Use the Azure provider
-FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.azure.AzureProvider
-
-# Azure OAuth credentials
-FASTMCP_SERVER_AUTH_AZURE_CLIENT_ID=835f09b6-0f0f-40cc-85cb-f32c5829a149
-FASTMCP_SERVER_AUTH_AZURE_CLIENT_SECRET=your-client-secret-here
-FASTMCP_SERVER_AUTH_AZURE_TENANT_ID=08541b6e-646d-43de-a0eb-834e6713d6d5
-FASTMCP_SERVER_AUTH_AZURE_BASE_URL=https://your-server.com
-FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES=read,write
-# Optional custom API configuration
-# FASTMCP_SERVER_AUTH_AZURE_IDENTIFIER_URI=api://your-api-id
-# Request additional upstream scopes (optional)
-# FASTMCP_SERVER_AUTH_AZURE_ADDITIONAL_AUTHORIZE_SCOPES=User.Read,Mail.Read
-```
+## Production Configuration
-With environment variables set, your server code simplifies to:
+For production deployments, load sensitive credentials from environment variables:
```python server.py
+import os
from fastmcp import FastMCP
+from fastmcp.server.auth.providers.azure import AzureProvider
+
+# Load secrets from environment variables
+auth = AzureProvider(
+ client_id=os.environ.get("AZURE_CLIENT_ID"),
+ client_secret=os.environ.get("AZURE_CLIENT_SECRET"),
+ tenant_id=os.environ.get("AZURE_TENANT_ID"),
+ required_scopes=["read", "write"], # At least one scope required
+ base_url=os.environ.get("BASE_URL", "https://your-server.com")
+)
-# Authentication is automatically configured from environment
-mcp = FastMCP(name="Azure Secured App")
+mcp = FastMCP(name="Azure Secured App", auth=auth)
@mcp.tool
async def protected_tool(query: str) -> str:
diff --git a/docs/integrations/descope.mdx b/docs/integrations/descope.mdx
index 81eb0642b4..bd569aed63 100644
--- a/docs/integrations/descope.mdx
+++ b/docs/integrations/descope.mdx
@@ -95,52 +95,20 @@ if __name__ == "__main__":
asyncio.run(main())
```
-## Environment Variables
+## Production Configuration
-For production deployments, use environment variables instead of hardcoding credentials.
-
-### Provider Selection
-
-Setting this environment variable allows the Descope provider to be used automatically without explicitly instantiating it in code.
-
-
-
- Set to `fastmcp.server.auth.providers.descope.DescopeProvider` to use
- Descope authentication.
-
-
-
-### Descope-Specific Configuration
-
-These environment variables provide default values for the Descope provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
-
-
-
-Your Well-Known URL from the [Descope Console](https://app.descope.com/mcp-servers)
-
-
-
- Public URL of your FastMCP server (e.g., `https://your-server.com` or
- `http://localhost:8000` for development)
-
-
-
-Example `.env` file:
-
-```bash
-# Use the Descope provider
-FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.descope.DescopeProvider
-
-# Descope configuration
-FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_CONFIG_URL=https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration
-FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_BASE_URL=https://your-server.com
-```
-
-With environment variables set, your server code simplifies to:
+For production deployments, load configuration from environment variables:
```python server.py
+import os
from fastmcp import FastMCP
+from fastmcp.server.auth.providers.descope import DescopeProvider
+
+# Load configuration from environment variables
+auth = DescopeProvider(
+ config_url=os.environ.get("DESCOPE_CONFIG_URL"),
+ base_url=os.environ.get("BASE_URL", "https://your-server.com")
+)
-# Authentication is automatically configured from environment
-mcp = FastMCP(name="My Descope Protected Server")
+mcp = FastMCP(name="My Descope Protected Server", auth=auth)
```
diff --git a/docs/integrations/discord.mdx b/docs/integrations/discord.mdx
index 4e3670358b..bffb628641 100644
--- a/docs/integrations/discord.mdx
+++ b/docs/integrations/discord.mdx
@@ -183,65 +183,23 @@ Parameters (`jwt_signing_key` and `client_storage`) work together to ensure toke
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
-## Environment Variables
-
-For production deployments, use environment variables instead of hardcoding credentials.
-
-### Provider Selection
-
-Setting this environment variable allows the Discord provider to be used automatically without explicitly instantiating it in code.
-
-
-
-Set to `fastmcp.server.auth.providers.discord.DiscordProvider` to use Discord authentication.
-
-
-
-### Discord-Specific Configuration
-
-These environment variables provide default values for the Discord provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
-
-
-
-Your Discord Application Client ID (e.g., `12345`)
-
-
-
-Your Discord OAuth Client Secret
-
-
-
-Public URL where OAuth endpoints will be accessible (includes any mount path)
-
-
-
-Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details.
-
-
-
-Redirect path configured in your Discord OAuth settings
-
-
-
-Comma-, space-, or JSON-separated list of required Discord scopes (e.g., `identify,email` or `["identify","email"]`)
-
+## Production Configuration
-
-HTTP request timeout for Discord API calls
-
-
+For production deployments, load sensitive credentials from environment variables:
-Example `.env` file:
-```bash
-FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.discord.DiscordProvider
+```python server.py
+import os
+from fastmcp import FastMCP
+from fastmcp.server.auth.providers.discord import DiscordProvider
-FASTMCP_SERVER_AUTH_DISCORD_CLIENT_ID=12345
-FASTMCP_SERVER_AUTH_DISCORD_CLIENT_SECRET=your-client-secret
-FASTMCP_SERVER_AUTH_DISCORD_BASE_URL=https://your-server.com
-FASTMCP_SERVER_AUTH_DISCORD_REQUIRED_SCOPES=identify,email
-```
+# Load secrets from environment variables
+auth = DiscordProvider(
+ client_id=os.environ.get("DISCORD_CLIENT_ID"),
+ client_secret=os.environ.get("DISCORD_CLIENT_SECRET"),
+ base_url=os.environ.get("BASE_URL", "https://your-server.com")
+)
-With environment variables set, your server code simplifies to:
+mcp = FastMCP(name="Discord Secured App", auth=auth)
```python server.py
from fastmcp import FastMCP
diff --git a/docs/integrations/github.mdx b/docs/integrations/github.mdx
index f1b646b676..627db1173d 100644
--- a/docs/integrations/github.mdx
+++ b/docs/integrations/github.mdx
@@ -175,75 +175,23 @@ Parameters (`jwt_signing_key` and `client_storage`) work together to ensure toke
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
-## Environment Variables
-
-
-
-For production deployments, use environment variables instead of hardcoding credentials.
-
-### Provider Selection
-
-Setting this environment variable allows the GitHub provider to be used automatically without explicitly instantiating it in code.
-
-
-
-Set to `fastmcp.server.auth.providers.github.GitHubProvider` to use GitHub authentication.
-
-
-
-### GitHub-Specific Configuration
-
-These environment variables provide default values for the GitHub provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
-
-
-
-Your GitHub OAuth App Client ID (e.g., `Ov23liAbcDefGhiJkLmN`)
-
-
-
-Your GitHub OAuth App Client Secret
-
-
-
-Public URL where OAuth endpoints will be accessible (includes any mount path)
-
-
-
-Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details.
-
-
-
-Redirect path configured in your GitHub OAuth App
-
-
-
-Comma-, space-, or JSON-separated list of required GitHub scopes (e.g., `user repo` or `["user","repo"]`)
-
-
-
-HTTP request timeout for GitHub API calls
-
-
-
-Example `.env` file:
-```bash
-# Use the GitHub provider
-FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.github.GitHubProvider
-
-# GitHub OAuth credentials
-FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID=Ov23liAbcDefGhiJkLmN
-FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET=github_pat_...
-FASTMCP_SERVER_AUTH_GITHUB_BASE_URL=https://your-server.com
-FASTMCP_SERVER_AUTH_GITHUB_REQUIRED_SCOPES=user,repo
-```
+## Production Configuration
-With environment variables set, your server code simplifies to:
+For production deployments, load sensitive credentials from environment variables:
```python server.py
+import os
from fastmcp import FastMCP
+from fastmcp.server.auth.providers.github import GitHubProvider
+
+# Load secrets from environment variables
+auth = GitHubProvider(
+ client_id=os.environ.get("GITHUB_CLIENT_ID"),
+ client_secret=os.environ.get("GITHUB_CLIENT_SECRET"),
+ base_url=os.environ.get("BASE_URL", "https://your-server.com")
+)
-# Authentication is automatically configured from environment
-mcp = FastMCP(name="GitHub Secured App")
+mcp = FastMCP(name="GitHub Secured App", auth=auth)
@mcp.tool
async def list_repos() -> list[str]:
diff --git a/docs/integrations/google.mdx b/docs/integrations/google.mdx
index b673962df7..f478b63dba 100644
--- a/docs/integrations/google.mdx
+++ b/docs/integrations/google.mdx
@@ -189,75 +189,23 @@ Parameters (`jwt_signing_key` and `client_storage`) work together to ensure toke
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
-## Environment Variables
-
-
-
-For production deployments, use environment variables instead of hardcoding credentials.
-
-### Provider Selection
-
-Setting this environment variable allows the Google provider to be used automatically without explicitly instantiating it in code.
-
-
-
-Set to `fastmcp.server.auth.providers.google.GoogleProvider` to use Google authentication.
-
-
-
-### Google-Specific Configuration
-
-These environment variables provide default values for the Google provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
-
-
-
-Your Google OAuth 2.0 Client ID (e.g., `123456789.apps.googleusercontent.com`)
-
-
-
-Your Google OAuth 2.0 Client Secret (e.g., `GOCSPX-abc123...`)
-
-
-
-Public URL where OAuth endpoints will be accessible (includes any mount path)
-
-
-
-Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details.
-
-
-
-Redirect path configured in your Google OAuth Client
-
-
-
-Comma-, space-, or JSON-separated list of required Google scopes (e.g., `"openid,https://www.googleapis.com/auth/userinfo.email"` or `["openid", "https://www.googleapis.com/auth/userinfo.email"]`)
-
-
-
-HTTP request timeout for Google API calls
-
-
-
-Example `.env` file:
-```bash
-# Use the Google provider
-FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.google.GoogleProvider
-
-# Google OAuth credentials
-FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_ID=123456789.apps.googleusercontent.com
-FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET=GOCSPX-abc123...
-FASTMCP_SERVER_AUTH_GOOGLE_BASE_URL=https://your-server.com
-FASTMCP_SERVER_AUTH_GOOGLE_REQUIRED_SCOPES=openid,https://www.googleapis.com/auth/userinfo.email
-```
+## Production Configuration
-With environment variables set, your server code simplifies to:
+For production deployments, load sensitive credentials from environment variables:
```python server.py
+import os
from fastmcp import FastMCP
+from fastmcp.server.auth.providers.google import GoogleProvider
+
+# Load secrets from environment variables
+auth = GoogleProvider(
+ client_id=os.environ.get("GOOGLE_CLIENT_ID"),
+ client_secret=os.environ.get("GOOGLE_CLIENT_SECRET"),
+ base_url=os.environ.get("BASE_URL", "https://your-server.com")
+)
-# Authentication is automatically configured from environment
-mcp = FastMCP(name="Google Secured App")
+mcp = FastMCP(name="Google Secured App", auth=auth)
@mcp.tool
async def protected_tool(query: str) -> str:
diff --git a/docs/integrations/oci.mdx b/docs/integrations/oci.mdx
index ab46486c4e..924e1ace42 100644
--- a/docs/integrations/oci.mdx
+++ b/docs/integrations/oci.mdx
@@ -17,7 +17,7 @@ This guide shows you how to secure your FastMCP server using **OCI IAM OAuth**.
### Prerequisites
1. An OCI cloud Account with access to create an Integrated Application in an Identity Domain.
-2. Your FastMCP server's URL (For dev environments, it is http://localhost:8000. For PROD environments, it could be https://mcp.${DOMAIN}.com)
+2. Your FastMCP server's URL (For dev environments, it is http://localhost:8000. For PROD environments, it could be https://mcp.yourdomain.com)
### Step 1: Make sure client access is enabled for JWK's URL
@@ -88,8 +88,7 @@ Follow the Steps as mentioned below to create an OAuth client.
Click on "Submit" button to update OAuth configuration for the client application.
**Note: You don't need to do any special configuration to support PKCE for the OAuth client.**
Make sure to Activate the client application.
- Note down client ID and client secret for the application. Update .env file and replace FASTMCP_SERVER_AUTH_OCI_CLIENT_ID and FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET values.
- FASTMCP_SERVER_AUTH_OCI_IAM_GUID in the env file is the Identity domain URL that you chose for the MCP server.
+ Note down client ID and client secret for the application. You'll use these values when configuring the OCIProvider in your code.
@@ -99,22 +98,20 @@ This is all you need to implement MCP server authentication against OCI IAM. How
Token exchange helps you exchange a logged-in user's OCI IAM token for an OCI control plane session token, also known as UPST (User Principal Session Token). To learn more about token exchange, refer to my [Workload Identity Federation Blog](https://www.ateam-oracle.com/post/workload-identity-federation)
-For token exchange, we need to configure Identity propagation trust. The blog above discusses setting up the trust using REST APIs. However, you can also use OCI CLI. Before using the CLI command below, ensure that you have created a token exchange OAuth client. In most cases, you can use the same OAuth client that you created above. You will use the client ID of the token exchange OAuth client in the CLI command below and replace it with {FASTMCP_SERVER_AUTH_OCI_CLIENT_ID}.
-
-You will also need to update the client secret for the token exchange OAuth client in the .env file. It is the FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET parameter. Update FASTMCP_SERVER_AUTH_OCI_IAM_GUID and FASTMCP_SERVER_AUTH_OCI_CLIENT_ID as well for the token exchange OAuth client in the .env file.
+For token exchange, we need to configure Identity propagation trust. The blog above discusses setting up the trust using REST APIs. However, you can also use OCI CLI. Before using the CLI command below, ensure that you have created a token exchange OAuth client. In most cases, you can use the same OAuth client that you created above. Replace `` and `` in the CLI command below with your actual values.
```bash
oci identity-domains identity-propagation-trust create \
--schemas '["urn:ietf:params:scim:schemas:oracle:idcs:IdentityPropagationTrust"]' \
---public-key-endpoint "https://{FASTMCP_SERVER_AUTH_OCI_IAM_GUID}.identity.oraclecloud.com/admin/v1/SigningCert/jwk" \
+--public-key-endpoint "https://.identity.oraclecloud.com/admin/v1/SigningCert/jwk" \
--name "For Token Exchange" --type "JWT" \
--issuer "https://identity.oraclecloud.com/" --active true \
---endpoint "https://{FASTMCP_SERVER_AUTH_OCI_IAM_GUID}.identity.oracleclcoud.com" \
+--endpoint "https://.identity.oraclecloud.com" \
--subject-claim-name "sub" --allow-impersonation false \
--subject-mapping-attribute "username" \
--subject-type "User" --client-claim-name "iss" \
--client-claim-values '["https://identity.oraclecloud.com/"]' \
---oauth-clients '["{FASTMCP_SERVER_AUTH_OCI_CLIENT_ID}"]'
+--oauth-clients '[""]'
```
To exchange access token for OCI token and create a signer object, you need to add below code in MCP server. You can then use the signer object to create any OCI control plane client.
@@ -129,9 +126,9 @@ import os
logger = get_logger(__name__)
# Load configuration from environment
-FASTMCP_SERVER_AUTH_OCI_IAM_GUID = os.environ["FASTMCP_SERVER_AUTH_OCI_IAM_GUID"]
-FASTMCP_SERVER_AUTH_OCI_CLIENT_ID = os.environ["FASTMCP_SERVER_AUTH_OCI_CLIENT_ID"]
-FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET = os.environ["FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET"]
+OCI_IAM_GUID = os.environ.get("OCI_IAM_GUID")
+OCI_CLIENT_ID = os.environ.get("OCI_CLIENT_ID")
+OCI_CLIENT_SECRET = os.environ.get("OCI_CLIENT_SECRET")
_global_token_cache = {} #In memory cache for OCI session token signer
@@ -152,9 +149,9 @@ def get_oci_signer() -> TokenExchangeSigner:
logger.debug(f"Creating new signer for token ID: {tokenID}")
signer = TokenExchangeSigner(
jwt_or_func=token,
- oci_domain_id=FASTMCP_SERVER_AUTH_OCI_IAM_GUID.split(".")[0],
- client_id=FASTMCP_SERVER_AUTH_OCI_CLIENT_ID,
- client_secret=FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET,
+ oci_domain_id=OCI_IAM_GUID.split(".")[0] if OCI_IAM_GUID else "",
+ client_id=OCI_CLIENT_ID,
+ client_secret=OCI_CLIENT_SECRET,
)
logger.debug(f"Signer {signer} created for token ID: {tokenID}")
@@ -220,16 +217,12 @@ from key_value.aio.wrappers.encryption import FernetEncryptionWrapper
from cryptography.fernet import Fernet
# Load configuration from environment
-FASTMCP_SERVER_AUTH_OCI_CONFIG_URL = os.environ["FASTMCP_SERVER_AUTH_OCI_CONFIG_URL"]
-FASTMCP_SERVER_AUTH_OCI_CLIENT_ID = os.environ["FASTMCP_SERVER_AUTH_OCI_CLIENT_ID"]
-FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET = os.environ["FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET"]
-
# Production setup with encrypted persistent token storage
auth_provider = OCIProvider(
- config_url=FASTMCP_SERVER_AUTH_OCI_CONFIG_URL,
- client_id=FASTMCP_SERVER_AUTH_OCI_CLIENT_ID,
- client_secret=FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET,
- base_url="https://your-production-domain.com",
+ config_url=os.environ.get("OCI_CONFIG_URL"),
+ client_id=os.environ.get("OCI_CLIENT_ID"),
+ client_secret=os.environ.get("OCI_CLIENT_SECRET"),
+ base_url=os.environ.get("BASE_URL", "https://your-production-domain.com"),
# Production token management
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
@@ -255,65 +248,24 @@ For complete details on these parameters, see the [OAuth Proxy documentation](/s
The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache.
-## Environment Variables
-
-For production deployments, use environment variables instead of hardcoding credentials.
-
-### Provider Selection
-
-Setting this environment variable allows the OCI provider to be used automatically without explicitly instantiating it in code.
-
-
-
-Set to `fastmcp.server.auth.providers.oci.OCIProvider` to use OCI IAM authentication.
-
-
-
-### OCI-Specific Configuration
-
-These environment variables provide default values for the OCI IAM provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
-
-
-
-Your OCI Application Configuration URL (e.g., `idcs-asdascxasd11......identity.oraclecloud.com`)
-
-
-
-Your OCI Application Configuration URL (e.g., `https://{FASTMCP_SERVER_AUTH_OCI_IAM_GUID}.identity.oraclecloud.com/.well-known/openid-configuration`)
-
-
-
-Your OCI Application Client ID (e.g., `tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB`)
-
-
-
-Your OCI Application Client Secret (e.g., `idcsssvPYqbjemq...`)
-
-
-
-Public URL where OAuth endpoints will be accessible (includes any mount path)
-
+## Production Configuration
-
-Redirect path configured in your OCI IAM Integrated Application
-
+For production deployments, load sensitive credentials from environment variables:
-
+```python server.py
+import os
+from fastmcp import FastMCP
+from fastmcp.server.auth.providers.oci import OCIProvider
-Example `.env` file:
-```bash
-# Use the OCI IAM provider
-FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.oci.OCIProvider
-
-# OCI IAM configuration and credentials
-FASTMCP_SERVER_AUTH_OCI_IAM_GUID=idcs-asaacasd1111.....
-FASTMCP_SERVER_AUTH_OCI_CONFIG_URL=https://{FASTMCP_SERVER_AUTH_OCI_IAM_GUID}.identity.oraclecloud.com/.well-known/openid-configuration
-FASTMCP_SERVER_AUTH_OCI_CLIENT_ID=
-FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET=
-FASTMCP_SERVER_AUTH_OCI_BASE_URL=https://your-server.com
-```
+# Load secrets from environment variables
+auth = OCIProvider(
+ config_url=os.environ.get("OCI_CONFIG_URL"),
+ client_id=os.environ.get("OCI_CLIENT_ID"),
+ client_secret=os.environ.get("OCI_CLIENT_SECRET"),
+ base_url=os.environ.get("BASE_URL", "https://your-server.com")
+)
-With environment variables set, your server code simplifies to:
+mcp = FastMCP(name="OCI Secured App", auth=auth)
```python server.py
from fastmcp import FastMCP
diff --git a/docs/integrations/scalekit.mdx b/docs/integrations/scalekit.mdx
index 802fe16ef9..07d9b3b61b 100644
--- a/docs/integrations/scalekit.mdx
+++ b/docs/integrations/scalekit.mdx
@@ -89,61 +89,23 @@ uv run python server.py
Use any MCP client (for example, mcp-inspector, Claude, VS Code, or Windsurf) to connect to the running serve. Verify that authentication succeeds and requests are authorized as expected.
-### Provider selection
+## Production Configuration
-Setting this environment variable allows the Scalekit provider to be used automatically without explicitly instantiating it in code.
-
-
-
-Set to `fastmcp.server.auth.providers.scalekit.ScalekitProvider` to use Scalekit authentication.
-
-
-
-### Scalekit-specific configuration
-
-These environment variables provide default values for the Scalekit provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
-
-
-
-Your Scalekit environment URL from the Admin Portal (e.g., `https://your-env.scalekit.com`)
-
-
-
-Your Scalekit resource server ID from the MCP Servers section
-
-
-
-Public URL of your FastMCP server (e.g., `https://your-server.com` or `http://localhost:8000/` for development)
-
-
-Legacy `FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_MCP_URL` is still recognized for backward compatibility but will be removed soon-rename it to `...BASE_URL`.
-
-
-Comma-, space-, or JSON-separated list of scopes that tokens must include to access your server
-
-
-
-Example `.env`:
-
-```bash
-# Use the Scalekit provider
-FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.scalekit.ScalekitProvider
-
-# Scalekit configuration
-FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_ENVIRONMENT_URL=https://your-env.scalekit.com
-FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_RESOURCE_ID=res_456
-FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_BASE_URL=https://your-server.com/
-# FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_REQUIRED_SCOPES=read,write
-# FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_MCP_URL=https://your-server.com/ # Deprecated
-```
-
-With environment variables set, your server code simplifies to:
+For production deployments, load configuration from environment variables:
```python server.py
+import os
from fastmcp import FastMCP
+from fastmcp.server.auth.providers.scalekit import ScalekitProvider
+
+# Load configuration from environment variables
+auth = ScalekitProvider(
+ environment_url=os.environ.get("SCALEKIT_ENVIRONMENT_URL"),
+ resource_id=os.environ.get("SCALEKIT_RESOURCE_ID"),
+ base_url=os.environ.get("BASE_URL", "https://your-server.com")
+)
-# Authentication is automatically configured from environment
-mcp = FastMCP(name="My Scalekit Protected Server")
+mcp = FastMCP(name="My Scalekit Protected Server", auth=auth)
@mcp.tool
def protected_action() -> str:
diff --git a/docs/integrations/supabase.mdx b/docs/integrations/supabase.mdx
index cbaa48920f..24dbb7ede7 100644
--- a/docs/integrations/supabase.mdx
+++ b/docs/integrations/supabase.mdx
@@ -90,53 +90,22 @@ When you run the client for the first time:
2. After you authorize, you'll be redirected back
3. The client receives the token and can make authenticated requests
-## Environment Variables
+## Production Configuration
-For production deployments, use environment variables instead of hardcoding credentials.
-
-### Provider Selection
-
-Setting this environment variable allows the Supabase provider to be used automatically without explicitly instantiating it in code.
-
-
-
-Set to `fastmcp.server.auth.providers.supabase.SupabaseProvider` to use Supabase authentication.
-
-
-
-### Supabase-Specific Configuration
-
-These environment variables provide default values for the Supabase provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
-
-
-
-Your Supabase project URL (e.g., `https://abc123.supabase.co`)
-
-
-
-Public URL of your FastMCP server (e.g., `https://your-server.com` or `http://localhost:8000` for development)
-
-
-
-Comma-, space-, or JSON-separated list of required OAuth scopes (e.g., `openid email` or `["openid", "email"]`)
-
-
-
-Example `.env` file:
-```bash
-# Use the Supabase provider
-FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.supabase.SupabaseProvider
-
-# Supabase configuration
-FASTMCP_SERVER_AUTH_SUPABASE_PROJECT_URL=https://abc123.supabase.co
-FASTMCP_SERVER_AUTH_SUPABASE_BASE_URL=https://your-server.com
-FASTMCP_SERVER_AUTH_SUPABASE_REQUIRED_SCOPES=openid,email
-```
-
-With environment variables set, your server code simplifies to:
+For production deployments, load configuration from environment variables:
```python server.py
+import os
from fastmcp import FastMCP
+from fastmcp.server.auth.providers.supabase import SupabaseProvider
+
+# Load configuration from environment variables
+auth = SupabaseProvider(
+ project_url=os.environ.get("SUPABASE_PROJECT_URL"),
+ base_url=os.environ.get("BASE_URL", "https://your-server.com")
+)
+
+mcp = FastMCP(name="Supabase Secured App", auth=auth)
# Authentication is automatically configured from environment
mcp = FastMCP(name="Supabase Protected Server")
diff --git a/docs/integrations/workos.mdx b/docs/integrations/workos.mdx
index 190050c9a5..3005e6934e 100644
--- a/docs/integrations/workos.mdx
+++ b/docs/integrations/workos.mdx
@@ -168,92 +168,24 @@ Parameters (`jwt_signing_key` and `client_storage`) work together to ensure toke
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
-## Environment Variables
-
-
-
-For production deployments, use environment variables instead of hardcoding credentials.
-
-### Provider Selection
-
-Setting this environment variable allows the WorkOS provider to be used automatically without explicitly instantiating it in code.
-
-
-
-Set to `fastmcp.server.auth.providers.workos.WorkOSProvider` to use WorkOS authentication.
-
-
-
-### WorkOS-Specific Configuration
-
-These environment variables provide default values for the WorkOS provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
-
-
-
-Your WorkOS OAuth App Client ID (e.g., `client_01K33Y6GGS7T3AWMPJWKW42Y3Q`)
-
-
-
-Your WorkOS OAuth App Client Secret
-
-
-
-Your WorkOS AuthKit domain (e.g., `https://your-app.authkit.app`)
-
-
-
-Public URL where OAuth endpoints will be accessible (includes any mount path)
-
-
-
-Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details.
-
-
-
-Redirect path configured in your WorkOS OAuth App
-
-
-
-Comma-, space-, or JSON-separated list of required OAuth scopes (e.g., `openid profile email` or `["openid","profile","email"]`)
-
-
-
-HTTP request timeout for WorkOS API calls
-
-
-
-Example `.env` file:
-```bash
-# WorkOS OAuth credentials (always used as defaults)
-FASTMCP_SERVER_AUTH_WORKOS_CLIENT_ID=client_01K33Y6GGS7T3AWMPJWKW42Y3Q
-FASTMCP_SERVER_AUTH_WORKOS_CLIENT_SECRET=your_client_secret
-FASTMCP_SERVER_AUTH_WORKOS_AUTHKIT_DOMAIN=https://your-app.authkit.app
-FASTMCP_SERVER_AUTH_WORKOS_BASE_URL=https://your-server.com
-FASTMCP_SERVER_AUTH_WORKOS_REQUIRED_SCOPES=["openid","profile","email"]
-
-# Optional: Automatically provision WorkOS auth for all servers
-FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.workos.WorkOSProvider
-```
+## Production Configuration
-With environment variables set, you can either:
+For production deployments, load sensitive credentials from environment variables:
-**Option 1: Manual instantiation (env vars provide defaults)**
```python server.py
+import os
from fastmcp import FastMCP
from fastmcp.server.auth.providers.workos import WorkOSProvider
-# Env vars provide default values for WorkOSProvider()
-auth = WorkOSProvider() # Uses env var defaults
-mcp = FastMCP(name="WorkOS Protected Server", auth=auth)
-```
-
-**Option 2: Automatic provisioning (requires FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.workos.WorkOSProvider)**
-```python server.py
-from fastmcp import FastMCP
+# Load secrets from environment variables
+auth = WorkOSProvider(
+ client_id=os.environ.get("WORKOS_CLIENT_ID"),
+ client_secret=os.environ.get("WORKOS_CLIENT_SECRET"),
+ authkit_domain=os.environ.get("WORKOS_AUTHKIT_DOMAIN"),
+ base_url=os.environ.get("BASE_URL", "https://your-server.com")
+)
-# Auth is automatically provisioned from FASTMCP_SERVER_AUTH
-mcp = FastMCP(name="WorkOS Protected Server")
-```
+mcp = FastMCP(name="WorkOS Protected Server", auth=auth)
## Configuration Options
diff --git a/docs/python-sdk/fastmcp-server-auth-providers-auth0.mdx b/docs/python-sdk/fastmcp-server-auth-providers-auth0.mdx
index c1c4b0200f..356504ada2 100644
--- a/docs/python-sdk/fastmcp-server-auth-providers-auth0.mdx
+++ b/docs/python-sdk/fastmcp-server-auth-providers-auth0.mdx
@@ -31,12 +31,6 @@ Example:
## Classes
-### `Auth0ProviderSettings`
-
-
-Settings for Auth0 OIDC provider.
-
-
### `Auth0Provider`
diff --git a/docs/python-sdk/fastmcp-server-auth-providers-aws.mdx b/docs/python-sdk/fastmcp-server-auth-providers-aws.mdx
index 56d2527efc..e4b3637e4f 100644
--- a/docs/python-sdk/fastmcp-server-auth-providers-aws.mdx
+++ b/docs/python-sdk/fastmcp-server-auth-providers-aws.mdx
@@ -31,12 +31,6 @@ Example:
## Classes
-### `AWSCognitoProviderSettings`
-
-
-Settings for AWS Cognito OAuth provider.
-
-
### `AWSCognitoTokenVerifier`
diff --git a/docs/python-sdk/fastmcp-server-auth-providers-azure.mdx b/docs/python-sdk/fastmcp-server-auth-providers-azure.mdx
index 686052344a..dc86cb301d 100644
--- a/docs/python-sdk/fastmcp-server-auth-providers-azure.mdx
+++ b/docs/python-sdk/fastmcp-server-auth-providers-azure.mdx
@@ -14,12 +14,6 @@ using the OAuth Proxy pattern for non-DCR OAuth flows.
## Classes
-### `AzureProviderSettings`
-
-
-Settings for Azure OAuth provider.
-
-
### `AzureProvider`
diff --git a/docs/python-sdk/fastmcp-server-auth-providers-descope.mdx b/docs/python-sdk/fastmcp-server-auth-providers-descope.mdx
index e6a51c7c4d..cf122ae85f 100644
--- a/docs/python-sdk/fastmcp-server-auth-providers-descope.mdx
+++ b/docs/python-sdk/fastmcp-server-auth-providers-descope.mdx
@@ -15,8 +15,6 @@ for seamless MCP client authentication.
## Classes
-### `DescopeProviderSettings`
-
### `DescopeProvider`
diff --git a/docs/python-sdk/fastmcp-server-auth-providers-discord.mdx b/docs/python-sdk/fastmcp-server-auth-providers-discord.mdx
index 031adaae98..bb4839cfec 100644
--- a/docs/python-sdk/fastmcp-server-auth-providers-discord.mdx
+++ b/docs/python-sdk/fastmcp-server-auth-providers-discord.mdx
@@ -29,12 +29,6 @@ Example:
## Classes
-### `DiscordProviderSettings`
-
-
-Settings for Discord OAuth provider.
-
-
### `DiscordTokenVerifier`
diff --git a/docs/python-sdk/fastmcp-server-auth-providers-github.mdx b/docs/python-sdk/fastmcp-server-auth-providers-github.mdx
index c4c731a882..b0ae5806d5 100644
--- a/docs/python-sdk/fastmcp-server-auth-providers-github.mdx
+++ b/docs/python-sdk/fastmcp-server-auth-providers-github.mdx
@@ -29,12 +29,6 @@ Example:
## Classes
-### `GitHubProviderSettings`
-
-
-Settings for GitHub OAuth provider.
-
-
### `GitHubTokenVerifier`
diff --git a/docs/python-sdk/fastmcp-server-auth-providers-google.mdx b/docs/python-sdk/fastmcp-server-auth-providers-google.mdx
index 5e2883a0c1..89383de69c 100644
--- a/docs/python-sdk/fastmcp-server-auth-providers-google.mdx
+++ b/docs/python-sdk/fastmcp-server-auth-providers-google.mdx
@@ -29,12 +29,6 @@ Example:
## Classes
-### `GoogleProviderSettings`
-
-
-Settings for Google OAuth provider.
-
-
### `GoogleTokenVerifier`
diff --git a/docs/python-sdk/fastmcp-server-auth-providers-oci.mdx b/docs/python-sdk/fastmcp-server-auth-providers-oci.mdx
index dd42817b60..133f836a63 100644
--- a/docs/python-sdk/fastmcp-server-auth-providers-oci.mdx
+++ b/docs/python-sdk/fastmcp-server-auth-providers-oci.mdx
@@ -27,22 +27,23 @@ Example:
import os
- # Load configuration from environment
- FASTMCP_SERVER_AUTH_OCI_CONFIG_URL = os.environ["FASTMCP_SERVER_AUTH_OCI_CONFIG_URL"]
- FASTMCP_SERVER_AUTH_OCI_CLIENT_ID = os.environ["FASTMCP_SERVER_AUTH_OCI_CLIENT_ID"]
- FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET = os.environ["FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET"]
- FASTMCP_SERVER_AUTH_OCI_IAM_GUID = os.environ["FASTMCP_SERVER_AUTH_OCI_IAM_GUID"]
-
+ import os
import oci
from oci.auth.signers import TokenExchangeSigner
logger = get_logger(__name__)
+ # Load configuration from environment
+ OCI_CONFIG_URL = os.environ.get("OCI_CONFIG_URL")
+ OCI_CLIENT_ID = os.environ.get("OCI_CLIENT_ID")
+ OCI_CLIENT_SECRET = os.environ.get("OCI_CLIENT_SECRET")
+ OCI_IAM_GUID = os.environ.get("OCI_IAM_GUID")
+
# Simple OCI OIDC protection
auth = OCIProvider(
- config_url=FASTMCP_SERVER_AUTH_OCI_CONFIG_URL, #config URL is the OCI IAM Domain OIDC discovery URL.
- client_id=FASTMCP_SERVER_AUTH_OCI_CLIENT_ID, #This is same as the client ID configured for the OCI IAM Domain Integrated Application
- client_secret=FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET, #This is same as the client secret configured for the OCI IAM Domain Integrated Application
+ config_url=OCI_CONFIG_URL, # config URL is the OCI IAM Domain OIDC discovery URL
+ client_id=OCI_CLIENT_ID, # This is same as the client ID configured for the OCI IAM Domain Integrated Application
+ client_secret=OCI_CLIENT_SECRET, # This is same as the client secret configured for the OCI IAM Domain Integrated Application
required_scopes=["openid", "profile", "email"],
redirect_path="/auth/callback",
base_url="http://localhost:8000",
@@ -50,7 +51,7 @@ Example:
# NOTE: For production use, replace this with a thread-safe cache implementation
# such as threading.Lock-protected dict or a proper caching library
- _global_token_cache = {} #In memory cache for OCI session token signer
+ _global_token_cache = {} # In memory cache for OCI session token signer
def get_oci_signer() -> TokenExchangeSigner:
@@ -58,20 +59,20 @@ Example:
tokenID = authntoken.claims.get("jti")
token = authntoken.token
- #Check if the signer exists for the token ID in memory cache
+ # Check if the signer exists for the token ID in memory cache
cached_signer = _global_token_cache.get(tokenID)
logger.debug(f"Global cached signer: {cached_signer}")
if cached_signer:
logger.debug(f"Using globally cached signer for token ID: {tokenID}")
return cached_signer
- #If the signer is not yet created for the token then create new OCI signer object
+ # If the signer is not yet created for the token then create new OCI signer object
logger.debug(f"Creating new signer for token ID: {tokenID}")
signer = TokenExchangeSigner(
jwt_or_func=token,
- oci_domain_id=FASTMCP_SERVER_AUTH_OCI_IAM_GUID.split(".")[0], #This is same as IAM GUID configured for the OCI IAM Domain
- client_id=FASTMCP_SERVER_AUTH_OCI_CLIENT_ID, #This is same as the client ID configured for the OCI IAM Domain Integrated Application
- client_secret=FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET #This is same as the client secret configured for the OCI IAM Domain Integrated Application
+ oci_domain_id=OCI_IAM_GUID.split(".")[0] if OCI_IAM_GUID else "", # This is same as IAM GUID configured for the OCI IAM Domain
+ client_id=OCI_CLIENT_ID, # This is same as the client ID configured for the OCI IAM Domain Integrated Application
+ client_secret=OCI_CLIENT_SECRET # This is same as the client secret configured for the OCI IAM Domain Integrated Application
)
logger.debug(f"Signer {signer} created for token ID: {tokenID}")
@@ -87,12 +88,6 @@ Example:
## Classes
-### `OCIProviderSettings`
-
-
-Settings for OCI IAM domain OIDC provider.
-
-
### `OCIProvider`
diff --git a/docs/python-sdk/fastmcp-server-auth-providers-scalekit.mdx b/docs/python-sdk/fastmcp-server-auth-providers-scalekit.mdx
index 448ad7e9e8..50f9f3c4d9 100644
--- a/docs/python-sdk/fastmcp-server-auth-providers-scalekit.mdx
+++ b/docs/python-sdk/fastmcp-server-auth-providers-scalekit.mdx
@@ -15,8 +15,6 @@ authentication for seamless MCP client authentication.
## Classes
-### `ScalekitProviderSettings`
-
### `ScalekitProvider`
diff --git a/docs/python-sdk/fastmcp-server-auth-providers-supabase.mdx b/docs/python-sdk/fastmcp-server-auth-providers-supabase.mdx
index a5c35964e6..f803af1d58 100644
--- a/docs/python-sdk/fastmcp-server-auth-providers-supabase.mdx
+++ b/docs/python-sdk/fastmcp-server-auth-providers-supabase.mdx
@@ -15,8 +15,6 @@ for seamless MCP client authentication.
## Classes
-### `SupabaseProviderSettings`
-
### `SupabaseProvider`
diff --git a/docs/python-sdk/fastmcp-server-auth-providers-workos.mdx b/docs/python-sdk/fastmcp-server-auth-providers-workos.mdx
index b91f022632..dce09b8b57 100644
--- a/docs/python-sdk/fastmcp-server-auth-providers-workos.mdx
+++ b/docs/python-sdk/fastmcp-server-auth-providers-workos.mdx
@@ -18,12 +18,6 @@ Choose based on your WorkOS setup and authentication requirements.
## Classes
-### `WorkOSProviderSettings`
-
-
-Settings for WorkOS OAuth provider.
-
-
### `WorkOSTokenVerifier`
@@ -65,8 +59,6 @@ Setup Requirements:
4. Note your Client ID and Client Secret
-### `AuthKitProviderSettings`
-
### `AuthKitProvider`
diff --git a/docs/servers/auth/authentication.mdx b/docs/servers/auth/authentication.mdx
index b366066e5f..f2c3d1efcb 100644
--- a/docs/servers/auth/authentication.mdx
+++ b/docs/servers/auth/authentication.mdx
@@ -178,78 +178,28 @@ This example shows the basic structure of a custom OAuth provider. The actual im
→ **Complete guide**: [Full OAuth Server](/servers/auth/full-oauth-server)
-## Configuration Approaches
+## Configuration
-FastMCP supports both programmatic configuration for maximum flexibility and environment-based configuration for deployment simplicity.
+Authentication providers are configured programmatically by instantiating them directly in your code with their required parameters. This makes dependencies explicit and allows your IDE to provide helpful autocompletion and type checking.
-### Programmatic Configuration
-
-Programmatic configuration provides complete control over authentication settings and allows for complex initialization logic. This approach works well during development and when you need to customize authentication behavior based on runtime conditions.
-
-Authentication providers are instantiated directly in your code with their required parameters. This makes dependencies explicit and allows your IDE to provide helpful autocompletion and type checking.
-
-### Environment Configuration
-
-
-
-Environment-based configuration separates authentication settings from application code, enabling the same codebase to work across different deployment environments without modification.
-
-FastMCP automatically detects authentication configuration from environment variables when no explicit `auth` parameter is provided. The configuration system supports all authentication providers and their various options.
-
-#### Provider Configuration
-
-Authentication providers are configured by specifying the full module path to the provider class:
-
-
-The full module path to the authentication provider class. Examples:
-- `fastmcp.server.auth.providers.github.GitHubProvider` - GitHub OAuth
-- `fastmcp.server.auth.providers.google.GoogleProvider` - Google OAuth
-- `fastmcp.server.auth.providers.jwt.JWTVerifier` - JWT token verification
-- `fastmcp.server.auth.providers.workos.WorkOSProvider` - WorkOS OAuth
-- `fastmcp.server.auth.providers.workos.AuthKitProvider` - WorkOS AuthKit
-- `mycompany.auth.CustomProvider` - Your custom provider class
-
-
-When using providers like GitHub or Google, you'll need to set provider-specific environment variables:
-
-```bash
-# GitHub OAuth
-export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.github.GitHubProvider
-export FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID="Ov23li..."
-export FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET="github_pat_..."
-
-# Google OAuth
-export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.google.GoogleProvider
-export FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_ID="123456.apps.googleusercontent.com"
-export FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET="GOCSPX-..."
-```
-
-#### Provider-Specific Configuration
-
-Each provider has its own configuration options set through environment variables:
-
-```bash
-# JWT Token Verification
-export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.jwt.JWTVerifier
-export FASTMCP_SERVER_AUTH_JWT_JWKS_URI="https://auth.example.com/jwks"
-export FASTMCP_SERVER_AUTH_JWT_ISSUER="https://auth.example.com"
-export FASTMCP_SERVER_AUTH_JWT_AUDIENCE="mcp-server"
-
-# Custom Provider
-export FASTMCP_SERVER_AUTH=mycompany.auth.CustomProvider
-# Plus any environment variables your custom provider expects
-```
-
-With these environment variables set, creating an authenticated FastMCP server requires no additional configuration:
+For production deployments, load sensitive values like client secrets from environment variables:
```python
+import os
from fastmcp import FastMCP
+from fastmcp.server.auth.providers.github import GitHubProvider
+
+# Load secrets from environment variables
+auth = GitHubProvider(
+ client_id=os.environ.get("GITHUB_CLIENT_ID"),
+ client_secret=os.environ.get("GITHUB_CLIENT_SECRET"),
+ base_url=os.environ.get("BASE_URL", "http://localhost:8000")
+)
-# Authentication automatically configured from environment
-mcp = FastMCP(name="My Server")
+mcp = FastMCP(name="My Server", auth=auth)
```
-This approach simplifies deployment pipelines and follows twelve-factor app principles for configuration management.
+This approach keeps secrets out of your codebase while maintaining explicit configuration. You can use any environment variable names you prefer - there are no special prefixes required.
## Choosing Your Implementation
diff --git a/docs/servers/auth/oauth-proxy.mdx b/docs/servers/auth/oauth-proxy.mdx
index 40422044a6..3c5b328a76 100644
--- a/docs/servers/auth/oauth-proxy.mdx
+++ b/docs/servers/auth/oauth-proxy.mdx
@@ -560,29 +560,23 @@ The consent page automatically displays your server's name, icon, and website UR
- [MCP Security Best Practices](https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices#confused-deputy-problem) - Official specification guidance
- [Confused Deputy Attacks Explained](https://den.dev/blog/mcp-confused-deputy-api-management/) - Detailed walkthrough by Den Delimarsky
-## Environment Configuration
+## Production Configuration
-
-
-For production deployments, configure the OAuth proxy through environment variables instead of hardcoding credentials:
-
-```bash
-# Specify the provider implementation
-export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.github.GitHubProvider
-
-# Provider-specific credentials
-export FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID="Ov23li..."
-export FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET="abc123..."
-export FASTMCP_SERVER_AUTH_GITHUB_BASE_URL="https://your-production-server.com"
-```
-
-With environment configuration, your server code simplifies to:
+For production deployments, load sensitive credentials from environment variables:
```python
+import os
from fastmcp import FastMCP
+from fastmcp.server.auth.providers.github import GitHubProvider
-# Authentication automatically configured from environment
-mcp = FastMCP(name="My Server")
+# Load secrets from environment variables
+auth = GitHubProvider(
+ client_id=os.environ.get("GITHUB_CLIENT_ID"),
+ client_secret=os.environ.get("GITHUB_CLIENT_SECRET"),
+ base_url=os.environ.get("BASE_URL", "https://your-production-server.com")
+)
+
+mcp = FastMCP(name="My Server", auth=auth)
@mcp.tool
def protected_tool(data: str) -> str:
@@ -592,3 +586,5 @@ def protected_tool(data: str) -> str:
if __name__ == "__main__":
mcp.run(transport="http", port=8000)
```
+
+This keeps secrets out of your codebase while maintaining explicit configuration.
diff --git a/docs/servers/auth/oidc-proxy.mdx b/docs/servers/auth/oidc-proxy.mdx
index 729f9884d6..3bd75db205 100644
--- a/docs/servers/auth/oidc-proxy.mdx
+++ b/docs/servers/auth/oidc-proxy.mdx
@@ -233,31 +233,25 @@ OAuth scopes are configured with `required_scopes` to automatically request the
Dynamic clients created by the proxy will automatically include these scopes in their authorization requests.
-## Environment Configuration
+## Production Configuration
-
-
-For production deployments, configure the OIDC proxy through environment variables instead of hardcoding credentials:
-
-```bash
-# Specify the provider implementation
-export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.auth0.Auth0Provider
-
-# Provider-specific credentials
-export FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL=https://.../.well-known/openid-configuration
-export FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID=tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB
-export FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET=vPYqbjemq...
-export FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE=https://...
-export FASTMCP_SERVER_AUTH_AUTH0_BASE_URL=https://localhost:8000
-```
-
-With environment configuration, your server code simplifies to:
+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")
+)
-# Authentication automatically configured from environment
-mcp = FastMCP(name="My Server")
+mcp = FastMCP(name="My Server", auth=auth)
@mcp.tool
def protected_tool(data: str) -> str:
@@ -267,3 +261,5 @@ def protected_tool(data: str) -> str:
if __name__ == "__main__":
mcp.run(transport="http", port=8000)
```
+
+This keeps secrets out of your codebase while maintaining explicit configuration.
diff --git a/docs/servers/auth/token-verification.mdx b/docs/servers/auth/token-verification.mdx
index e142826630..7e93c129d3 100644
--- a/docs/servers/auth/token-verification.mdx
+++ b/docs/servers/auth/token-verification.mdx
@@ -303,39 +303,27 @@ print(f"Test token: {test_token}")
This pattern enables comprehensive testing of JWT validation logic without depending on external token issuers. The generated tokens are cryptographically valid and will pass all standard JWT validation checks.
-## Environment Configuration
+## Production Configuration
-
-
-FastMCP supports both programmatic and environment-based configuration for token verification, enabling flexible deployment across different environments.
-
-Environment-based configuration separates authentication settings from application code, following twelve-factor app principles and simplifying deployment pipelines.
-
-```bash
-# Enable JWT verification
-export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.jwt.JWTVerifier
-
-# For asymmetric verification with JWKS endpoint:
-export FASTMCP_SERVER_AUTH_JWT_JWKS_URI="https://auth.company.com/.well-known/jwks.json"
-export FASTMCP_SERVER_AUTH_JWT_ISSUER="https://auth.company.com"
-export FASTMCP_SERVER_AUTH_JWT_AUDIENCE="mcp-production-api"
-export FASTMCP_SERVER_AUTH_JWT_REQUIRED_SCOPES="read:data,write:data"
-
-# OR for symmetric key verification (HMAC):
-export FASTMCP_SERVER_AUTH_JWT_PUBLIC_KEY="your-shared-secret-key-minimum-32-chars"
-export FASTMCP_SERVER_AUTH_JWT_ALGORITHM="HS256" # or HS384, HS512
-export FASTMCP_SERVER_AUTH_JWT_ISSUER="internal-auth-service"
-export FASTMCP_SERVER_AUTH_JWT_AUDIENCE="mcp-internal-api"
-```
-
-With these environment variables configured, your FastMCP server automatically enables JWT verification:
+For production deployments, load sensitive configuration from environment variables:
```python
+import os
from fastmcp import FastMCP
+from fastmcp.server.auth.providers.jwt import JWTVerifier
+
+# Load configuration from environment variables
+verifier = JWTVerifier(
+ jwks_uri=os.environ.get("JWT_JWKS_URI"),
+ issuer=os.environ.get("JWT_ISSUER"),
+ audience=os.environ.get("JWT_AUDIENCE"),
+ required_scopes=os.environ.get("JWT_REQUIRED_SCOPES", "").split(",") if os.environ.get("JWT_REQUIRED_SCOPES") else None
+)
-# Authentication automatically configured from environment
-mcp = FastMCP(name="Production API")
+mcp = FastMCP(name="Production API", auth=verifier)
```
+This keeps configuration out of your codebase while maintaining explicit setup.
+
This approach enables the same codebase to run across development, staging, and production environments with different authentication requirements. Development might use static tokens while production uses JWT verification, all controlled through environment configuration.
diff --git a/src/fastmcp/server/auth/providers/auth0.py b/src/fastmcp/server/auth/providers/auth0.py
index 620696b330..42e51ceb92 100644
--- a/src/fastmcp/server/auth/providers/auth0.py
+++ b/src/fastmcp/server/auth/providers/auth0.py
@@ -22,44 +22,15 @@
"""
from key_value.aio.protocols import AsyncKeyValue
-from pydantic import AnyHttpUrl, SecretStr, field_validator
-from pydantic_settings import BaseSettings, SettingsConfigDict
+from pydantic import AnyHttpUrl
from fastmcp.server.auth.oidc_proxy import OIDCProxy
-from fastmcp.settings import ENV_FILE
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
-from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__)
-class Auth0ProviderSettings(BaseSettings):
- """Settings for Auth0 OIDC provider."""
-
- model_config = SettingsConfigDict(
- env_prefix="FASTMCP_SERVER_AUTH_AUTH0_",
- env_file=ENV_FILE,
- extra="ignore",
- )
-
- config_url: AnyHttpUrl | None = None
- client_id: str | None = None
- client_secret: SecretStr | None = None
- audience: str | None = None
- base_url: AnyHttpUrl | None = None
- issuer_url: AnyHttpUrl | None = None
- redirect_path: str | None = None
- required_scopes: list[str] | None = None
- allowed_client_redirect_uris: list[str] | None = None
- jwt_signing_key: str | None = None
-
- @field_validator("required_scopes", mode="before")
- @classmethod
- def _parse_scopes(cls, v):
- return parse_scopes(v)
-
-
class Auth0Provider(OIDCProxy):
"""An Auth0 provider implementation for FastMCP.
@@ -87,17 +58,17 @@ class Auth0Provider(OIDCProxy):
def __init__(
self,
*,
- config_url: AnyHttpUrl | str | NotSetT = NotSet,
- client_id: str | NotSetT = NotSet,
- client_secret: str | NotSetT = NotSet,
- audience: str | NotSetT = NotSet,
- base_url: AnyHttpUrl | str | NotSetT = NotSet,
- issuer_url: AnyHttpUrl | str | NotSetT = NotSet,
- required_scopes: list[str] | NotSetT = NotSet,
- redirect_path: str | NotSetT = NotSet,
- allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
+ config_url: AnyHttpUrl | str,
+ client_id: str,
+ client_secret: str,
+ audience: str,
+ base_url: AnyHttpUrl | str,
+ issuer_url: AnyHttpUrl | str | None = None,
+ required_scopes: list[str] | None = None,
+ redirect_path: str | None = None,
+ allowed_client_redirect_uris: list[str] | None = None,
client_storage: AsyncKeyValue | None = None,
- jwt_signing_key: str | bytes | NotSetT = NotSet,
+ jwt_signing_key: str | bytes | None = None,
require_authorization_consent: bool = True,
) -> None:
"""Initialize Auth0 OAuth provider.
@@ -125,69 +96,28 @@ def __init__(
When False, authorization proceeds directly without user confirmation.
SECURITY WARNING: Only disable for local development or testing environments.
"""
- settings = Auth0ProviderSettings.model_validate(
- {
- k: v
- for k, v in {
- "config_url": config_url,
- "client_id": client_id,
- "client_secret": client_secret,
- "audience": audience,
- "base_url": base_url,
- "issuer_url": issuer_url,
- "required_scopes": required_scopes,
- "redirect_path": redirect_path,
- "allowed_client_redirect_uris": allowed_client_redirect_uris,
- "jwt_signing_key": jwt_signing_key,
- }.items()
- if v is not NotSet
- }
+ # Parse scopes if provided as string
+ auth0_required_scopes = (
+ parse_scopes(required_scopes) if required_scopes is not None else ["openid"]
)
- if not settings.config_url:
- raise ValueError(
- "config_url is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL"
- )
-
- if not settings.client_id:
- raise ValueError(
- "client_id is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID"
- )
-
- if not settings.client_secret:
- raise ValueError(
- "client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET"
- )
-
- if not settings.audience:
- raise ValueError(
- "audience is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE"
- )
-
- if not settings.base_url:
- raise ValueError(
- "base_url is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_BASE_URL"
- )
-
- auth0_required_scopes = settings.required_scopes or ["openid"]
-
super().__init__(
- config_url=settings.config_url,
- client_id=settings.client_id,
- client_secret=settings.client_secret.get_secret_value(),
- audience=settings.audience,
- base_url=settings.base_url,
- issuer_url=settings.issuer_url,
- redirect_path=settings.redirect_path,
+ config_url=config_url,
+ client_id=client_id,
+ client_secret=client_secret,
+ audience=audience,
+ base_url=base_url,
+ issuer_url=issuer_url,
+ redirect_path=redirect_path,
required_scopes=auth0_required_scopes,
- allowed_client_redirect_uris=settings.allowed_client_redirect_uris,
+ allowed_client_redirect_uris=allowed_client_redirect_uris,
client_storage=client_storage,
- jwt_signing_key=settings.jwt_signing_key,
+ jwt_signing_key=jwt_signing_key,
require_authorization_consent=require_authorization_consent,
)
logger.debug(
"Initialized Auth0 OAuth provider for client %s with scopes: %s",
- settings.client_id,
+ client_id,
auth0_required_scopes,
)
diff --git a/src/fastmcp/server/auth/providers/aws.py b/src/fastmcp/server/auth/providers/aws.py
index 6a1a4b67f3..19f2908aac 100644
--- a/src/fastmcp/server/auth/providers/aws.py
+++ b/src/fastmcp/server/auth/providers/aws.py
@@ -24,47 +24,18 @@
from __future__ import annotations
from key_value.aio.protocols import AsyncKeyValue
-from pydantic import AnyHttpUrl, SecretStr, field_validator
-from pydantic_settings import BaseSettings, SettingsConfigDict
+from pydantic import AnyHttpUrl
from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken
from fastmcp.server.auth.oidc_proxy import OIDCProxy
from fastmcp.server.auth.providers.jwt import JWTVerifier
-from fastmcp.settings import ENV_FILE
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
-from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__)
-class AWSCognitoProviderSettings(BaseSettings):
- """Settings for AWS Cognito OAuth provider."""
-
- model_config = SettingsConfigDict(
- env_prefix="FASTMCP_SERVER_AUTH_AWS_COGNITO_",
- env_file=ENV_FILE,
- extra="ignore",
- )
-
- user_pool_id: str | None = None
- aws_region: str | None = None
- client_id: str | None = None
- client_secret: SecretStr | None = None
- base_url: AnyHttpUrl | str | None = None
- issuer_url: AnyHttpUrl | str | None = None
- redirect_path: str | None = None
- required_scopes: list[str] | None = None
- allowed_client_redirect_uris: list[str] | None = None
- jwt_signing_key: str | None = None
-
- @field_validator("required_scopes", mode="before")
- @classmethod
- def _parse_scopes(cls, v):
- return parse_scopes(v)
-
-
class AWSCognitoTokenVerifier(JWTVerifier):
"""Token verifier that filters claims to Cognito-specific subset."""
@@ -126,27 +97,27 @@ class AWSCognitoProvider(OIDCProxy):
def __init__(
self,
*,
- user_pool_id: str | NotSetT = NotSet,
- aws_region: str | NotSetT = NotSet,
- client_id: str | NotSetT = NotSet,
- client_secret: str | NotSetT = NotSet,
- base_url: AnyHttpUrl | str | NotSetT = NotSet,
- issuer_url: AnyHttpUrl | str | NotSetT = NotSet,
- redirect_path: str | NotSetT = NotSet,
- required_scopes: list[str] | NotSetT = NotSet,
- allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
+ user_pool_id: str,
+ client_id: str,
+ client_secret: str,
+ base_url: AnyHttpUrl | str,
+ aws_region: str = "eu-central-1",
+ issuer_url: AnyHttpUrl | str | None = None,
+ redirect_path: str = "/auth/callback",
+ required_scopes: list[str] | None = None,
+ allowed_client_redirect_uris: list[str] | None = None,
client_storage: AsyncKeyValue | None = None,
- jwt_signing_key: str | bytes | NotSetT = NotSet,
+ jwt_signing_key: str | bytes | None = None,
require_authorization_consent: bool = True,
):
"""Initialize AWS Cognito OAuth provider.
Args:
user_pool_id: Your Cognito User Pool ID (e.g., "eu-central-1_XXXXXXXXX")
- aws_region: AWS region where your User Pool is located (defaults to "eu-central-1")
client_id: Cognito app client ID
client_secret: Cognito app client secret
base_url: Public URL where OAuth endpoints will be accessible (includes any mount path)
+ aws_region: AWS region where your User Pool is located (defaults to "eu-central-1")
issuer_url: Issuer URL for OAuth metadata (defaults to base_url). Use root-level URL
to avoid 404s during discovery when mounting under a path.
redirect_path: Redirect path configured in Cognito app (defaults to "/auth/callback")
@@ -164,81 +135,37 @@ def __init__(
When False, authorization proceeds directly without user confirmation.
SECURITY WARNING: Only disable for local development or testing environments.
"""
-
- settings = AWSCognitoProviderSettings.model_validate(
- {
- k: v
- for k, v in {
- "user_pool_id": user_pool_id,
- "aws_region": aws_region,
- "client_id": client_id,
- "client_secret": client_secret,
- "base_url": base_url,
- "issuer_url": issuer_url,
- "redirect_path": redirect_path,
- "required_scopes": required_scopes,
- "allowed_client_redirect_uris": allowed_client_redirect_uris,
- "jwt_signing_key": jwt_signing_key,
- }.items()
- if v is not NotSet
- }
+ # Parse scopes if provided as string
+ required_scopes_final = (
+ parse_scopes(required_scopes) if required_scopes is not None else ["openid"]
)
- # Validate required settings
- if not settings.user_pool_id:
- raise ValueError(
- "user_pool_id is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID"
- )
- if not settings.client_id:
- raise ValueError(
- "client_id is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID"
- )
- if not settings.client_secret:
- raise ValueError(
- "client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET"
- )
- if not settings.base_url:
- raise ValueError(
- "base_url is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_BASE_URL"
- )
-
- # Apply defaults
- required_scopes_final = settings.required_scopes or ["openid"]
- allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
- aws_region_final = settings.aws_region or "eu-central-1"
- redirect_path_final = settings.redirect_path or "/auth/callback"
-
# Construct OIDC discovery URL
- config_url = f"https://cognito-idp.{aws_region_final}.amazonaws.com/{settings.user_pool_id}/.well-known/openid-configuration"
-
- # Extract secret string from SecretStr
- client_secret_str = (
- settings.client_secret.get_secret_value() if settings.client_secret else ""
- )
+ config_url = f"https://cognito-idp.{aws_region}.amazonaws.com/{user_pool_id}/.well-known/openid-configuration"
# Store Cognito-specific info for claim filtering
- self.user_pool_id = settings.user_pool_id
- self.aws_region = aws_region_final
+ self.user_pool_id = user_pool_id
+ self.aws_region = aws_region
# Initialize OIDC proxy with Cognito discovery
super().__init__(
config_url=config_url,
- client_id=settings.client_id,
- client_secret=client_secret_str,
+ client_id=client_id,
+ client_secret=client_secret,
algorithm="RS256",
required_scopes=required_scopes_final,
- base_url=settings.base_url,
- issuer_url=settings.issuer_url,
- redirect_path=redirect_path_final,
- allowed_client_redirect_uris=allowed_client_redirect_uris_final,
+ base_url=base_url,
+ issuer_url=issuer_url,
+ redirect_path=redirect_path,
+ allowed_client_redirect_uris=allowed_client_redirect_uris,
client_storage=client_storage,
- jwt_signing_key=settings.jwt_signing_key,
+ jwt_signing_key=jwt_signing_key,
require_authorization_consent=require_authorization_consent,
)
logger.debug(
"Initialized AWS Cognito OAuth provider for client %s with scopes: %s",
- settings.client_id,
+ client_id,
required_scopes_final,
)
diff --git a/src/fastmcp/server/auth/providers/azure.py b/src/fastmcp/server/auth/providers/azure.py
index 8c08a487c8..514cae4010 100644
--- a/src/fastmcp/server/auth/providers/azure.py
+++ b/src/fastmcp/server/auth/providers/azure.py
@@ -9,15 +9,11 @@
from typing import TYPE_CHECKING, Any
from key_value.aio.protocols import AsyncKeyValue
-from pydantic import SecretStr, field_validator
-from pydantic_settings import BaseSettings, SettingsConfigDict
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.providers.jwt import JWTVerifier
-from fastmcp.settings import ENV_FILE
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
-from fastmcp.utilities.types import NotSet, NotSetT
if TYPE_CHECKING:
from mcp.server.auth.provider import AuthorizationParams
@@ -31,39 +27,6 @@
OIDC_SCOPES = frozenset({"openid", "profile", "email", "offline_access"})
-class AzureProviderSettings(BaseSettings):
- """Settings for Azure OAuth provider."""
-
- model_config = SettingsConfigDict(
- env_prefix="FASTMCP_SERVER_AUTH_AZURE_",
- env_file=ENV_FILE,
- extra="ignore",
- )
-
- client_id: str | None = None
- client_secret: SecretStr | None = None
- tenant_id: str | None = None
- identifier_uri: str | None = None
- base_url: str | None = None
- issuer_url: str | None = None
- redirect_path: str | None = None
- required_scopes: list[str] | None = None
- additional_authorize_scopes: list[str] | None = None
- allowed_client_redirect_uris: list[str] | None = None
- jwt_signing_key: str | None = None
- base_authority: str = "login.microsoftonline.com"
-
- @field_validator("required_scopes", mode="before")
- @classmethod
- def _parse_scopes(cls, v: object) -> list[str] | None:
- return parse_scopes(v)
-
- @field_validator("additional_authorize_scopes", mode="before")
- @classmethod
- def _parse_additional_authorize_scopes(cls, v: object) -> list[str] | None:
- return parse_scopes(v)
-
-
class AzureProvider(OAuthProxy):
"""Azure (Microsoft Entra) OAuth provider for FastMCP.
@@ -127,20 +90,20 @@ class AzureProvider(OAuthProxy):
def __init__(
self,
*,
- client_id: str | NotSetT = NotSet,
- client_secret: str | NotSetT = NotSet,
- tenant_id: str | NotSetT = NotSet,
- identifier_uri: str | NotSetT | None = NotSet,
- base_url: str | NotSetT = NotSet,
- issuer_url: str | NotSetT = NotSet,
- redirect_path: str | NotSetT = NotSet,
- required_scopes: list[str] | NotSetT | None = NotSet,
- additional_authorize_scopes: list[str] | NotSetT | None = NotSet,
- allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
+ client_id: str,
+ client_secret: str,
+ tenant_id: str,
+ required_scopes: list[str],
+ base_url: str,
+ identifier_uri: str | None = None,
+ issuer_url: str | None = None,
+ redirect_path: str | None = None,
+ additional_authorize_scopes: list[str] | None = None,
+ allowed_client_redirect_uris: list[str] | None = None,
client_storage: AsyncKeyValue | None = None,
- jwt_signing_key: str | bytes | NotSetT = NotSet,
+ jwt_signing_key: str | bytes | None = None,
require_authorization_consent: bool = True,
- base_authority: str | NotSetT = NotSet,
+ base_authority: str = "login.microsoftonline.com",
) -> None:
"""Initialize Azure OAuth provider.
@@ -185,131 +148,74 @@ def __init__(
When False, authorization proceeds directly without user confirmation.
SECURITY WARNING: Only disable for local development or testing environments.
"""
- settings = AzureProviderSettings.model_validate(
- {
- k: v
- for k, v in {
- "client_id": client_id,
- "client_secret": client_secret,
- "tenant_id": tenant_id,
- "identifier_uri": identifier_uri,
- "base_url": base_url,
- "issuer_url": issuer_url,
- "redirect_path": redirect_path,
- "required_scopes": required_scopes,
- "additional_authorize_scopes": additional_authorize_scopes,
- "allowed_client_redirect_uris": allowed_client_redirect_uris,
- "jwt_signing_key": jwt_signing_key,
- "base_authority": base_authority,
- }.items()
- if v is not NotSet
- }
+ # Parse scopes if provided as string
+ parsed_required_scopes = parse_scopes(required_scopes)
+ parsed_additional_scopes = (
+ parse_scopes(additional_authorize_scopes)
+ if additional_authorize_scopes
+ else []
)
- # Validate required settings
- if not settings.client_id:
- msg = "client_id is required - set via parameter or FASTMCP_SERVER_AUTH_AZURE_CLIENT_ID"
- raise ValueError(msg)
- if not settings.client_secret:
- msg = "client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AZURE_CLIENT_SECRET"
- raise ValueError(msg)
-
- # Validate tenant_id is provided
- if not settings.tenant_id:
- msg = (
- "tenant_id is required - set via parameter or "
- "FASTMCP_SERVER_AUTH_AZURE_TENANT_ID. Use your Azure tenant ID "
- "(found in Azure Portal), 'organizations', or 'consumers'"
- )
- raise ValueError(msg)
-
- # Validate required_scopes has at least one scope
- if not settings.required_scopes:
- msg = (
- "required_scopes must include at least one scope - set via parameter or "
- "FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES. Azure's OAuth API requires "
- "the 'scope' parameter in authorization requests. Use the unprefixed scope "
- "names from your Azure App registration (e.g., ['read', 'write'])"
- )
- raise ValueError(msg)
-
- if not settings.base_url:
- raise ValueError(
- "base_url is required - set via parameter or FASTMCP_SERVER_AUTH_AZURE_BASE_URL"
- )
-
# Apply defaults
- self.identifier_uri = settings.identifier_uri or f"api://{settings.client_id}"
- self.additional_authorize_scopes = settings.additional_authorize_scopes or []
- tenant_id_final = settings.tenant_id
+ self.identifier_uri = identifier_uri or f"api://{client_id}"
+ self.additional_authorize_scopes = parsed_additional_scopes
# Always validate tokens against the app's API client ID using JWT
- base_authority_final = settings.base_authority
- issuer = f"https://{base_authority_final}/{tenant_id_final}/v2.0"
- jwks_uri = (
- f"https://{base_authority_final}/{tenant_id_final}/discovery/v2.0/keys"
- )
+ issuer = f"https://{base_authority}/{tenant_id}/v2.0"
+ jwks_uri = f"https://{base_authority}/{tenant_id}/discovery/v2.0/keys"
# Azure access tokens only include custom API scopes in the `scp` claim,
# NOT standard OIDC scopes (openid, profile, email, offline_access).
# Filter out OIDC scopes from validation - they'll still be sent to Azure
# during authorization (handled by _prefix_scopes_for_azure).
- validation_scopes = None
- if settings.required_scopes:
- validation_scopes = [
- s for s in settings.required_scopes if s not in OIDC_SCOPES
- ]
- # If all scopes were OIDC scopes, use None (no scope validation)
- if not validation_scopes:
- validation_scopes = None
+ validation_scopes = (
+ [s for s in parsed_required_scopes if s not in OIDC_SCOPES]
+ if parsed_required_scopes
+ else None
+ )
+ # If all scopes were OIDC scopes, use None (no scope validation)
+ if validation_scopes == []:
+ validation_scopes = None
token_verifier = JWTVerifier(
jwks_uri=jwks_uri,
issuer=issuer,
- audience=settings.client_id,
+ audience=client_id,
algorithm="RS256",
required_scopes=validation_scopes, # Only validate non-OIDC scopes
)
- # Extract secret string from SecretStr
- client_secret_str = (
- settings.client_secret.get_secret_value() if settings.client_secret else ""
- )
-
# Build Azure OAuth endpoints with tenant
authorization_endpoint = (
- f"https://{base_authority_final}/{tenant_id_final}/oauth2/v2.0/authorize"
- )
- token_endpoint = (
- f"https://{base_authority_final}/{tenant_id_final}/oauth2/v2.0/token"
+ f"https://{base_authority}/{tenant_id}/oauth2/v2.0/authorize"
)
+ token_endpoint = f"https://{base_authority}/{tenant_id}/oauth2/v2.0/token"
# Initialize OAuth proxy with Azure endpoints
super().__init__(
upstream_authorization_endpoint=authorization_endpoint,
upstream_token_endpoint=token_endpoint,
- upstream_client_id=settings.client_id,
- upstream_client_secret=client_secret_str,
+ upstream_client_id=client_id,
+ upstream_client_secret=client_secret,
token_verifier=token_verifier,
- base_url=settings.base_url,
- redirect_path=settings.redirect_path,
- issuer_url=settings.issuer_url
- or settings.base_url, # Default to base_url if not specified
- allowed_client_redirect_uris=settings.allowed_client_redirect_uris,
+ base_url=base_url,
+ redirect_path=redirect_path,
+ issuer_url=issuer_url or base_url, # Default to base_url if not specified
+ allowed_client_redirect_uris=allowed_client_redirect_uris,
client_storage=client_storage,
- jwt_signing_key=settings.jwt_signing_key,
+ jwt_signing_key=jwt_signing_key,
require_authorization_consent=require_authorization_consent,
# Advertise full scopes including OIDC (even though we only validate non-OIDC)
- valid_scopes=settings.required_scopes,
+ valid_scopes=parsed_required_scopes,
)
authority_info = ""
- if base_authority_final != "login.microsoftonline.com":
- authority_info = f" using authority {base_authority_final}"
+ if base_authority != "login.microsoftonline.com":
+ authority_info = f" using authority {base_authority}"
logger.info(
"Initialized Azure OAuth provider for client %s with tenant %s%s%s",
- settings.client_id,
- tenant_id_final,
+ client_id,
+ tenant_id,
f" and identifier_uri {self.identifier_uri}" if self.identifier_uri else "",
authority_info,
)
diff --git a/src/fastmcp/server/auth/providers/descope.py b/src/fastmcp/server/auth/providers/descope.py
index 610acf4297..f0f99f3997 100644
--- a/src/fastmcp/server/auth/providers/descope.py
+++ b/src/fastmcp/server/auth/providers/descope.py
@@ -10,40 +10,18 @@
from urllib.parse import urlparse
import httpx
-from pydantic import AnyHttpUrl, field_validator
-from pydantic_settings import BaseSettings, SettingsConfigDict
+from pydantic import AnyHttpUrl
from starlette.responses import JSONResponse
from starlette.routing import Route
from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier
from fastmcp.server.auth.providers.jwt import JWTVerifier
-from fastmcp.settings import ENV_FILE
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
-from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__)
-class DescopeProviderSettings(BaseSettings):
- model_config = SettingsConfigDict(
- env_prefix="FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_",
- env_file=ENV_FILE,
- extra="ignore",
- )
-
- config_url: AnyHttpUrl | None = None
- project_id: str | None = None
- descope_base_url: AnyHttpUrl | str | None = None
- base_url: AnyHttpUrl
- required_scopes: list[str] | None = None
-
- @field_validator("required_scopes", mode="before")
- @classmethod
- def _parse_scopes(cls, v):
- return parse_scopes(v)
-
-
class DescopeProvider(RemoteAuthProvider):
"""Descope metadata provider for DCR (Dynamic Client Registration).
@@ -85,46 +63,37 @@ class DescopeProvider(RemoteAuthProvider):
def __init__(
self,
*,
- config_url: AnyHttpUrl | str | NotSetT = NotSet,
- project_id: str | NotSetT = NotSet,
- descope_base_url: AnyHttpUrl | str | NotSetT = NotSet,
- base_url: AnyHttpUrl | str | NotSetT = NotSet,
- required_scopes: list[str] | NotSetT | None = NotSet,
+ base_url: AnyHttpUrl | str,
+ config_url: AnyHttpUrl | str | None = None,
+ project_id: str | None = None,
+ descope_base_url: AnyHttpUrl | str | None = None,
+ required_scopes: list[str] | None = None,
token_verifier: TokenVerifier | None = None,
):
"""Initialize Descope metadata provider.
Args:
+ base_url: Public URL of this FastMCP server
config_url: Your Descope Well-Known URL (e.g., "https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration")
This is the new recommended way. If provided, project_id and descope_base_url are ignored.
project_id: Your Descope Project ID (e.g., "P2abc123"). Used with descope_base_url for backwards compatibility.
descope_base_url: Your Descope base URL (e.g., "https://api.descope.com"). Used with project_id for backwards compatibility.
- base_url: Public URL of this FastMCP server
required_scopes: Optional list of scopes that must be present in validated tokens.
These scopes will be included in the protected resource metadata.
token_verifier: Optional token verifier. If None, creates JWT verifier for Descope
"""
- settings = DescopeProviderSettings.model_validate(
- {
- k: v
- for k, v in {
- "config_url": config_url,
- "project_id": project_id,
- "descope_base_url": descope_base_url,
- "base_url": base_url,
- "required_scopes": required_scopes,
- }.items()
- if v is not NotSet
- }
- )
+ self.base_url = AnyHttpUrl(str(base_url).rstrip("/"))
- self.base_url = AnyHttpUrl(str(settings.base_url).rstrip("/"))
+ # Parse scopes if provided as string
+ parsed_scopes = (
+ parse_scopes(required_scopes) if required_scopes is not None else None
+ )
# Determine which API is being used
- if settings.config_url is not None:
+ if config_url is not None:
# New API: use config_url
# Strip /.well-known/openid-configuration from config_url if present
- issuer_url = str(settings.config_url)
+ issuer_url = str(config_url)
if issuer_url.endswith("/.well-known/openid-configuration"):
issuer_url = issuer_url[: -len("/.well-known/openid-configuration")]
@@ -150,10 +119,10 @@ def __init__(
self.descope_base_url = f"{parsed_url.scheme}://{parsed_url.netloc}".rstrip(
"/"
)
- elif settings.project_id is not None and settings.descope_base_url is not None:
+ elif project_id is not None and descope_base_url is not None:
# Old API: use project_id and descope_base_url
- self.project_id = settings.project_id
- descope_base_url_str = str(settings.descope_base_url).rstrip("/")
+ self.project_id = project_id
+ descope_base_url_str = str(descope_base_url).rstrip("/")
# Ensure descope_base_url has a scheme
if not descope_base_url_str.startswith(("http://", "https://")):
descope_base_url_str = f"https://{descope_base_url_str}"
@@ -172,7 +141,7 @@ def __init__(
issuer=issuer_url,
algorithm="RS256",
audience=self.project_id,
- required_scopes=settings.required_scopes,
+ required_scopes=parsed_scopes,
)
# Initialize RemoteAuthProvider with Descope as the authorization server
diff --git a/src/fastmcp/server/auth/providers/discord.py b/src/fastmcp/server/auth/providers/discord.py
index 9e8d59d0b4..a6882be1ec 100644
--- a/src/fastmcp/server/auth/providers/discord.py
+++ b/src/fastmcp/server/auth/providers/discord.py
@@ -26,45 +26,17 @@
import httpx
from key_value.aio.protocols import AsyncKeyValue
-from pydantic import AnyHttpUrl, SecretStr, field_validator
-from pydantic_settings import BaseSettings, SettingsConfigDict
+from pydantic import AnyHttpUrl
from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken
from fastmcp.server.auth.oauth_proxy import OAuthProxy
-from fastmcp.settings import ENV_FILE
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
-from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__)
-class DiscordProviderSettings(BaseSettings):
- """Settings for Discord OAuth provider."""
-
- model_config = SettingsConfigDict(
- env_prefix="FASTMCP_SERVER_AUTH_DISCORD_",
- env_file=ENV_FILE,
- extra="ignore",
- )
-
- client_id: str | None = None
- client_secret: SecretStr | None = None
- base_url: AnyHttpUrl | str | None = None
- issuer_url: AnyHttpUrl | str | None = None
- redirect_path: str | None = None
- required_scopes: list[str] | None = None
- timeout_seconds: int | None = None
- allowed_client_redirect_uris: list[str] | None = None
- jwt_signing_key: str | None = None
-
- @field_validator("required_scopes", mode="before")
- @classmethod
- def _parse_scopes(cls, v):
- return parse_scopes(v)
-
-
class DiscordTokenVerifier(TokenVerifier):
"""Token verifier for Discord OAuth tokens.
@@ -200,16 +172,16 @@ class DiscordProvider(OAuthProxy):
def __init__(
self,
*,
- client_id: str | NotSetT = NotSet,
- client_secret: str | NotSetT = NotSet,
- base_url: AnyHttpUrl | str | NotSetT = NotSet,
- issuer_url: AnyHttpUrl | str | NotSetT = NotSet,
- redirect_path: str | NotSetT = NotSet,
- required_scopes: list[str] | NotSetT = NotSet,
- timeout_seconds: int | NotSetT = NotSet,
- allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
+ client_id: str,
+ client_secret: str,
+ base_url: AnyHttpUrl | str,
+ issuer_url: AnyHttpUrl | str | None = None,
+ redirect_path: str | None = None,
+ required_scopes: list[str] | None = None,
+ timeout_seconds: int = 10,
+ allowed_client_redirect_uris: list[str] | None = None,
client_storage: AsyncKeyValue | None = None,
- jwt_signing_key: str | bytes | NotSetT = NotSet,
+ jwt_signing_key: str | bytes | None = None,
require_authorization_consent: bool = True,
):
"""Initialize Discord OAuth provider.
@@ -225,7 +197,7 @@ def __init__(
- "identify" for profile info (default)
- "email" for email access
- "guilds" for server membership info
- timeout_seconds: HTTP request timeout for Discord API calls
+ timeout_seconds: HTTP request timeout for Discord API calls (defaults to 10)
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
@@ -239,74 +211,37 @@ def __init__(
When False, authorization proceeds directly without user confirmation.
SECURITY WARNING: Only disable for local development or testing environments.
"""
-
- settings = DiscordProviderSettings.model_validate(
- {
- k: v
- for k, v in {
- "client_id": client_id,
- "client_secret": client_secret,
- "base_url": base_url,
- "issuer_url": issuer_url,
- "redirect_path": redirect_path,
- "required_scopes": required_scopes,
- "timeout_seconds": timeout_seconds,
- "allowed_client_redirect_uris": allowed_client_redirect_uris,
- "jwt_signing_key": jwt_signing_key,
- }.items()
- if v is not NotSet
- }
+ # Parse scopes if provided as string
+ required_scopes_final = (
+ parse_scopes(required_scopes)
+ if required_scopes is not None
+ else ["identify"]
)
- # Validate required settings
- if not settings.client_id:
- raise ValueError(
- "client_id is required - set via parameter or FASTMCP_SERVER_AUTH_DISCORD_CLIENT_ID"
- )
- if not settings.client_secret:
- raise ValueError(
- "client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_DISCORD_CLIENT_SECRET"
- )
- if not settings.base_url:
- raise ValueError(
- "base_url is required - set via parameter or FASTMCP_SERVER_AUTH_DISCORD_BASE_URL"
- )
-
- # Apply defaults
- timeout_seconds_final = settings.timeout_seconds or 10
- required_scopes_final = settings.required_scopes or ["identify"]
- allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
-
# Create Discord token verifier
token_verifier = DiscordTokenVerifier(
required_scopes=required_scopes_final,
- timeout_seconds=timeout_seconds_final,
- )
-
- # Extract secret string from SecretStr
- client_secret_str = (
- settings.client_secret.get_secret_value() if settings.client_secret else ""
+ timeout_seconds=timeout_seconds,
)
# Initialize OAuth proxy with Discord endpoints
super().__init__(
upstream_authorization_endpoint="https://discord.com/oauth2/authorize",
upstream_token_endpoint="https://discord.com/api/oauth2/token",
- upstream_client_id=settings.client_id,
- upstream_client_secret=client_secret_str,
+ upstream_client_id=client_id,
+ upstream_client_secret=client_secret,
token_verifier=token_verifier,
- base_url=settings.base_url,
- redirect_path=settings.redirect_path,
- issuer_url=settings.issuer_url
- or settings.base_url, # Default to base_url if not specified
- allowed_client_redirect_uris=allowed_client_redirect_uris_final,
+ base_url=base_url,
+ redirect_path=redirect_path,
+ issuer_url=issuer_url or base_url, # Default to base_url if not specified
+ allowed_client_redirect_uris=allowed_client_redirect_uris,
client_storage=client_storage,
- jwt_signing_key=settings.jwt_signing_key,
+ jwt_signing_key=jwt_signing_key,
require_authorization_consent=require_authorization_consent,
)
logger.debug(
"Initialized Discord OAuth provider for client %s with scopes: %s",
- settings.client_id,
+ client_id,
required_scopes_final,
)
diff --git a/src/fastmcp/server/auth/providers/github.py b/src/fastmcp/server/auth/providers/github.py
index 04613f254e..ee04a466e5 100644
--- a/src/fastmcp/server/auth/providers/github.py
+++ b/src/fastmcp/server/auth/providers/github.py
@@ -23,45 +23,17 @@
import httpx
from key_value.aio.protocols import AsyncKeyValue
-from pydantic import AnyHttpUrl, SecretStr, field_validator
-from pydantic_settings import BaseSettings, SettingsConfigDict
+from pydantic import AnyHttpUrl
from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken
from fastmcp.server.auth.oauth_proxy import OAuthProxy
-from fastmcp.settings import ENV_FILE
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
-from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__)
-class GitHubProviderSettings(BaseSettings):
- """Settings for GitHub OAuth provider."""
-
- model_config = SettingsConfigDict(
- env_prefix="FASTMCP_SERVER_AUTH_GITHUB_",
- env_file=ENV_FILE,
- extra="ignore",
- )
-
- client_id: str | None = None
- client_secret: SecretStr | None = None
- base_url: AnyHttpUrl | str | None = None
- issuer_url: AnyHttpUrl | str | None = None
- redirect_path: str | None = None
- required_scopes: list[str] | None = None
- timeout_seconds: int | None = None
- allowed_client_redirect_uris: list[str] | None = None
- jwt_signing_key: str | None = None
-
- @field_validator("required_scopes", mode="before")
- @classmethod
- def _parse_scopes(cls, v):
- return parse_scopes(v)
-
-
class GitHubTokenVerifier(TokenVerifier):
"""Token verifier for GitHub OAuth tokens.
@@ -198,16 +170,16 @@ class GitHubProvider(OAuthProxy):
def __init__(
self,
*,
- client_id: str | NotSetT = NotSet,
- client_secret: str | NotSetT = NotSet,
- base_url: AnyHttpUrl | str | NotSetT = NotSet,
- issuer_url: AnyHttpUrl | str | NotSetT = NotSet,
- redirect_path: str | NotSetT = NotSet,
- required_scopes: list[str] | NotSetT = NotSet,
- timeout_seconds: int | NotSetT = NotSet,
- allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
+ client_id: str,
+ client_secret: str,
+ base_url: AnyHttpUrl | str,
+ issuer_url: AnyHttpUrl | str | None = None,
+ redirect_path: str | None = None,
+ required_scopes: list[str] | None = None,
+ timeout_seconds: int = 10,
+ allowed_client_redirect_uris: list[str] | None = None,
client_storage: AsyncKeyValue | None = None,
- jwt_signing_key: str | bytes | NotSetT = NotSet,
+ jwt_signing_key: str | bytes | None = None,
require_authorization_consent: bool = True,
):
"""Initialize GitHub OAuth provider.
@@ -220,7 +192,7 @@ def __init__(
to avoid 404s during discovery when mounting under a path.
redirect_path: Redirect path configured in GitHub OAuth app (defaults to "/auth/callback")
required_scopes: Required GitHub scopes (defaults to ["user"])
- timeout_seconds: HTTP request timeout for GitHub API calls
+ timeout_seconds: HTTP request timeout for GitHub API calls (defaults to 10)
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
@@ -234,75 +206,35 @@ def __init__(
When False, authorization proceeds directly without user confirmation.
SECURITY WARNING: Only disable for local development or testing environments.
"""
-
- settings = GitHubProviderSettings.model_validate(
- {
- k: v
- for k, v in {
- "client_id": client_id,
- "client_secret": client_secret,
- "base_url": base_url,
- "issuer_url": issuer_url,
- "redirect_path": redirect_path,
- "required_scopes": required_scopes,
- "timeout_seconds": timeout_seconds,
- "allowed_client_redirect_uris": allowed_client_redirect_uris,
- "jwt_signing_key": jwt_signing_key,
- }.items()
- if v is not NotSet
- }
+ # Parse scopes if provided as string
+ required_scopes_final = (
+ parse_scopes(required_scopes) if required_scopes is not None else ["user"]
)
- # Validate required settings
- if not settings.client_id:
- raise ValueError(
- "client_id is required - set via parameter or FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID"
- )
- if not settings.client_secret:
- raise ValueError(
- "client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET"
- )
- if not settings.base_url:
- raise ValueError(
- "base_url is required - set via parameter or FASTMCP_SERVER_AUTH_GITHUB_BASE_URL"
- )
-
- # Apply defaults
-
- timeout_seconds_final = settings.timeout_seconds or 10
- required_scopes_final = settings.required_scopes or ["user"]
- allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
-
# Create GitHub token verifier
token_verifier = GitHubTokenVerifier(
required_scopes=required_scopes_final,
- timeout_seconds=timeout_seconds_final,
- )
-
- # Extract secret string from SecretStr
- client_secret_str = (
- settings.client_secret.get_secret_value() if settings.client_secret else ""
+ timeout_seconds=timeout_seconds,
)
# Initialize OAuth proxy with GitHub endpoints
super().__init__(
upstream_authorization_endpoint="https://github.com/login/oauth/authorize",
upstream_token_endpoint="https://github.com/login/oauth/access_token",
- upstream_client_id=settings.client_id,
- upstream_client_secret=client_secret_str,
+ upstream_client_id=client_id,
+ upstream_client_secret=client_secret,
token_verifier=token_verifier,
- base_url=settings.base_url,
- redirect_path=settings.redirect_path,
- issuer_url=settings.issuer_url
- or settings.base_url, # Default to base_url if not specified
- allowed_client_redirect_uris=allowed_client_redirect_uris_final,
+ base_url=base_url,
+ redirect_path=redirect_path,
+ issuer_url=issuer_url or base_url, # Default to base_url if not specified
+ allowed_client_redirect_uris=allowed_client_redirect_uris,
client_storage=client_storage,
- jwt_signing_key=settings.jwt_signing_key,
+ jwt_signing_key=jwt_signing_key,
require_authorization_consent=require_authorization_consent,
)
logger.debug(
"Initialized GitHub OAuth provider for client %s with scopes: %s",
- settings.client_id,
+ client_id,
required_scopes_final,
)
diff --git a/src/fastmcp/server/auth/providers/google.py b/src/fastmcp/server/auth/providers/google.py
index 2ab06c110b..3badddc3b6 100644
--- a/src/fastmcp/server/auth/providers/google.py
+++ b/src/fastmcp/server/auth/providers/google.py
@@ -25,45 +25,17 @@
import httpx
from key_value.aio.protocols import AsyncKeyValue
-from pydantic import AnyHttpUrl, SecretStr, field_validator
-from pydantic_settings import BaseSettings, SettingsConfigDict
+from pydantic import AnyHttpUrl
from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken
from fastmcp.server.auth.oauth_proxy import OAuthProxy
-from fastmcp.settings import ENV_FILE
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
-from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__)
-class GoogleProviderSettings(BaseSettings):
- """Settings for Google OAuth provider."""
-
- model_config = SettingsConfigDict(
- env_prefix="FASTMCP_SERVER_AUTH_GOOGLE_",
- env_file=ENV_FILE,
- extra="ignore",
- )
-
- client_id: str | None = None
- client_secret: SecretStr | None = None
- base_url: AnyHttpUrl | str | None = None
- issuer_url: AnyHttpUrl | str | None = None
- redirect_path: str | None = None
- required_scopes: list[str] | None = None
- timeout_seconds: int | None = None
- allowed_client_redirect_uris: list[str] | None = None
- jwt_signing_key: str | None = None
-
- @field_validator("required_scopes", mode="before")
- @classmethod
- def _parse_scopes(cls, v):
- return parse_scopes(v)
-
-
class GoogleTokenVerifier(TokenVerifier):
"""Token verifier for Google OAuth tokens.
@@ -214,16 +186,16 @@ class GoogleProvider(OAuthProxy):
def __init__(
self,
*,
- client_id: str | NotSetT = NotSet,
- client_secret: str | NotSetT = NotSet,
- base_url: AnyHttpUrl | str | NotSetT = NotSet,
- issuer_url: AnyHttpUrl | str | NotSetT = NotSet,
- redirect_path: str | NotSetT = NotSet,
- required_scopes: list[str] | NotSetT = NotSet,
- timeout_seconds: int | NotSetT = NotSet,
- allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
+ client_id: str,
+ client_secret: str,
+ base_url: AnyHttpUrl | str,
+ issuer_url: AnyHttpUrl | str | None = None,
+ redirect_path: str | None = None,
+ required_scopes: list[str] | None = None,
+ timeout_seconds: int = 10,
+ allowed_client_redirect_uris: list[str] | None = None,
client_storage: AsyncKeyValue | None = None,
- jwt_signing_key: str | bytes | NotSetT = NotSet,
+ jwt_signing_key: str | bytes | None = None,
require_authorization_consent: bool = True,
extra_authorize_params: dict[str, str] | None = None,
):
@@ -240,7 +212,7 @@ def __init__(
- "openid" for OpenID Connect (default)
- "https://www.googleapis.com/auth/userinfo.email" for email access
- "https://www.googleapis.com/auth/userinfo.profile" for profile info
- timeout_seconds: HTTP request timeout for Google API calls
+ timeout_seconds: HTTP request timeout for Google API calls (defaults to 10)
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
@@ -258,54 +230,16 @@ def __init__(
refresh tokens are returned. You can override these defaults or add additional parameters.
Example: {"prompt": "select_account"} to let users choose their Google account.
"""
-
- settings = GoogleProviderSettings.model_validate(
- {
- k: v
- for k, v in {
- "client_id": client_id,
- "client_secret": client_secret,
- "base_url": base_url,
- "issuer_url": issuer_url,
- "redirect_path": redirect_path,
- "required_scopes": required_scopes,
- "timeout_seconds": timeout_seconds,
- "allowed_client_redirect_uris": allowed_client_redirect_uris,
- "jwt_signing_key": jwt_signing_key,
- }.items()
- if v is not NotSet
- }
- )
-
- # Validate required settings
- if not settings.client_id:
- raise ValueError(
- "client_id is required - set via parameter or FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_ID"
- )
- if not settings.client_secret:
- raise ValueError(
- "client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET"
- )
- if not settings.base_url:
- raise ValueError(
- "base_url is required - set via parameter or FASTMCP_SERVER_AUTH_GOOGLE_BASE_URL"
- )
-
- # Apply defaults
- timeout_seconds_final = settings.timeout_seconds or 10
+ # Parse scopes if provided as string
# Google requires at least one scope - openid is the minimal OIDC scope
- required_scopes_final = settings.required_scopes or ["openid"]
- allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
+ required_scopes_final = (
+ parse_scopes(required_scopes) if required_scopes is not None else ["openid"]
+ )
# Create Google token verifier
token_verifier = GoogleTokenVerifier(
required_scopes=required_scopes_final,
- timeout_seconds=timeout_seconds_final,
- )
-
- # Extract secret string from SecretStr
- client_secret_str = (
- settings.client_secret.get_secret_value() if settings.client_secret else ""
+ timeout_seconds=timeout_seconds,
)
# Set Google-specific defaults for extra authorize params
@@ -324,22 +258,21 @@ def __init__(
super().__init__(
upstream_authorization_endpoint="https://accounts.google.com/o/oauth2/v2/auth",
upstream_token_endpoint="https://oauth2.googleapis.com/token",
- upstream_client_id=settings.client_id,
- upstream_client_secret=client_secret_str,
+ upstream_client_id=client_id,
+ upstream_client_secret=client_secret,
token_verifier=token_verifier,
- base_url=settings.base_url,
- redirect_path=settings.redirect_path,
- issuer_url=settings.issuer_url
- or settings.base_url, # Default to base_url if not specified
- allowed_client_redirect_uris=allowed_client_redirect_uris_final,
+ base_url=base_url,
+ redirect_path=redirect_path,
+ issuer_url=issuer_url or base_url, # Default to base_url if not specified
+ allowed_client_redirect_uris=allowed_client_redirect_uris,
client_storage=client_storage,
- jwt_signing_key=settings.jwt_signing_key,
+ jwt_signing_key=jwt_signing_key,
require_authorization_consent=require_authorization_consent,
extra_authorize_params=extra_authorize_params_final,
)
logger.debug(
"Initialized Google OAuth provider for client %s with scopes: %s",
- settings.client_id,
+ client_id,
required_scopes_final,
)
diff --git a/src/fastmcp/server/auth/providers/oci.py b/src/fastmcp/server/auth/providers/oci.py
index 4eaa74ec23..9ca4eb24f1 100644
--- a/src/fastmcp/server/auth/providers/oci.py
+++ b/src/fastmcp/server/auth/providers/oci.py
@@ -78,44 +78,15 @@ def get_oci_signer() -> TokenExchangeSigner:
"""
from key_value.aio.protocols import AsyncKeyValue
-from pydantic import AnyHttpUrl, SecretStr, field_validator
-from pydantic_settings import BaseSettings, SettingsConfigDict
+from pydantic import AnyHttpUrl
from fastmcp.server.auth.oidc_proxy import OIDCProxy
-from fastmcp.settings import ENV_FILE
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
-from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__)
-class OCIProviderSettings(BaseSettings):
- """Settings for OCI IAM domain OIDC provider."""
-
- model_config = SettingsConfigDict(
- env_prefix="FASTMCP_SERVER_AUTH_OCI_",
- env_file=ENV_FILE,
- extra="ignore",
- )
-
- config_url: AnyHttpUrl | None = None
- client_id: str | None = None
- client_secret: SecretStr | None = None
- audience: str | None = None
- base_url: AnyHttpUrl | None = None
- issuer_url: AnyHttpUrl | None = None
- redirect_path: str | None = None
- required_scopes: list[str] | None = None
- allowed_client_redirect_uris: list[str] | None = None
- jwt_signing_key: str | bytes | None = None
-
- @field_validator("required_scopes", mode="before")
- @classmethod
- def _parse_scopes(cls, v):
- return parse_scopes(v)
-
-
class OCIProvider(OIDCProxy):
"""An OCI IAM Domain provider implementation for FastMCP.
@@ -144,17 +115,17 @@ class OCIProvider(OIDCProxy):
def __init__(
self,
*,
- config_url: AnyHttpUrl | str | NotSetT = NotSet,
- client_id: str | NotSetT = NotSet,
- client_secret: str | NotSetT = NotSet,
- audience: str | NotSetT = NotSet,
- base_url: AnyHttpUrl | str | NotSetT = NotSet,
- issuer_url: AnyHttpUrl | str | NotSetT = NotSet,
- required_scopes: list[str] | NotSetT = NotSet,
- redirect_path: str | NotSetT = NotSet,
- allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
+ config_url: AnyHttpUrl | str,
+ client_id: str,
+ client_secret: str,
+ base_url: AnyHttpUrl | str,
+ audience: str | None = None,
+ issuer_url: AnyHttpUrl | str | None = None,
+ required_scopes: list[str] | None = None,
+ redirect_path: str | None = None,
+ allowed_client_redirect_uris: list[str] | None = None,
client_storage: AsyncKeyValue | None = None,
- jwt_signing_key: str | bytes | NotSetT = NotSet,
+ jwt_signing_key: str | bytes | None = None,
require_authorization_consent: bool = True,
) -> None:
"""Initialize OCI OIDC provider.
@@ -163,71 +134,35 @@ def __init__(
config_url: OCI OIDC Discovery URL
client_id: OCI IAM Domain Integrated Application client id
client_secret: OCI Integrated Application client secret
- audience: OCI API audience (optional)
base_url: Public URL where OIDC endpoints will be accessible (includes any mount path)
+ audience: OCI API audience (optional)
issuer_url: Issuer URL for OCI IAM Domain metadata. This will override issuer URL from the discovery URL.
required_scopes: Required OCI scopes (defaults to ["openid"])
redirect_path: Redirect path configured in OCI IAM Domain Integrated Application. The default is "/auth/callback".
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
"""
-
- overrides: dict[str, object] = {
- k: v
- for k, v in {
- "config_url": config_url,
- "client_id": client_id,
- "client_secret": client_secret,
- "audience": audience,
- "base_url": base_url,
- "issuer_url": issuer_url,
- "required_scopes": required_scopes,
- "redirect_path": redirect_path,
- "allowed_client_redirect_uris": allowed_client_redirect_uris,
- "jwt_signing_key": jwt_signing_key,
- }.items()
- if v is not NotSet
- }
- settings = OCIProviderSettings(**overrides) # type: ignore[arg-type]
-
- if not settings.config_url:
- raise ValueError(
- "config_url is required - set via parameter or FASTMCP_SERVER_AUTH_OCI_CONFIG_URL"
- )
-
- if not settings.client_id:
- raise ValueError(
- "client_id is required - set via parameter or FASTMCP_SERVER_AUTH_OCI_CLIENT_ID"
- )
-
- if not settings.client_secret:
- raise ValueError(
- "client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET"
- )
-
- if not settings.base_url:
- raise ValueError(
- "base_url is required - set via parameter or FASTMCP_SERVER_AUTH_OCI_BASE_URL"
- )
-
- oci_required_scopes = settings.required_scopes or ["openid"]
+ # Parse scopes if provided as string
+ oci_required_scopes = (
+ parse_scopes(required_scopes) if required_scopes is not None else ["openid"]
+ )
super().__init__(
- config_url=settings.config_url,
- client_id=settings.client_id,
- client_secret=settings.client_secret.get_secret_value(),
- audience=settings.audience,
- base_url=settings.base_url,
- issuer_url=settings.issuer_url,
- redirect_path=settings.redirect_path,
+ config_url=config_url,
+ client_id=client_id,
+ client_secret=client_secret,
+ audience=audience,
+ base_url=base_url,
+ issuer_url=issuer_url,
+ redirect_path=redirect_path,
required_scopes=oci_required_scopes,
- allowed_client_redirect_uris=settings.allowed_client_redirect_uris,
+ allowed_client_redirect_uris=allowed_client_redirect_uris,
client_storage=client_storage,
- jwt_signing_key=settings.jwt_signing_key,
+ jwt_signing_key=jwt_signing_key,
require_authorization_consent=require_authorization_consent,
)
logger.debug(
"Initialized OCI OAuth provider for client %s with scopes: %s",
- settings.client_id,
+ client_id,
oci_required_scopes,
)
diff --git a/src/fastmcp/server/auth/providers/scalekit.py b/src/fastmcp/server/auth/providers/scalekit.py
index 6f115b867b..e434e95d9c 100644
--- a/src/fastmcp/server/auth/providers/scalekit.py
+++ b/src/fastmcp/server/auth/providers/scalekit.py
@@ -8,50 +8,18 @@
from __future__ import annotations
import httpx
-from pydantic import AnyHttpUrl, field_validator, model_validator
-from pydantic_settings import BaseSettings, SettingsConfigDict
+from pydantic import AnyHttpUrl
from starlette.responses import JSONResponse
from starlette.routing import Route
from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier
from fastmcp.server.auth.providers.jwt import JWTVerifier
-from fastmcp.settings import ENV_FILE
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
-from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__)
-class ScalekitProviderSettings(BaseSettings):
- model_config = SettingsConfigDict(
- env_prefix="FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_",
- env_file=ENV_FILE,
- extra="ignore",
- )
-
- environment_url: AnyHttpUrl
- resource_id: str
- base_url: AnyHttpUrl | None = None
- mcp_url: AnyHttpUrl | None = None
- required_scopes: list[str] | None = None
-
- @field_validator("required_scopes", mode="before")
- @classmethod
- def _parse_scopes(cls, value: object):
- return parse_scopes(value)
-
- @model_validator(mode="after")
- def _resolve_base_url(self):
- resolved = self.base_url or self.mcp_url
- if resolved is None:
- msg = "Either base_url or mcp_url must be provided for ScalekitProvider"
- raise ValueError(msg)
-
- object.__setattr__(self, "base_url", resolved)
- return self
-
-
class ScalekitProvider(RemoteAuthProvider):
"""Scalekit resource server provider for OAuth 2.1 authentication.
@@ -95,12 +63,12 @@ class ScalekitProvider(RemoteAuthProvider):
def __init__(
self,
*,
- environment_url: AnyHttpUrl | str | NotSetT = NotSet,
- client_id: str | NotSetT = NotSet,
- resource_id: str | NotSetT = NotSet,
- base_url: AnyHttpUrl | str | NotSetT = NotSet,
- mcp_url: AnyHttpUrl | str | NotSetT = NotSet,
- required_scopes: list[str] | NotSetT = NotSet,
+ environment_url: AnyHttpUrl | str,
+ resource_id: str,
+ base_url: AnyHttpUrl | str | None = None,
+ mcp_url: AnyHttpUrl | str | None = None,
+ client_id: str | None = None,
+ required_scopes: list[str] | None = None,
token_verifier: TokenVerifier | None = None,
):
"""Initialize Scalekit resource server provider.
@@ -108,42 +76,36 @@ def __init__(
Args:
environment_url: Your Scalekit environment URL (e.g., "https://your-env.scalekit.com")
resource_id: Your Scalekit resource ID
- base_url: Public URL of this FastMCP server
+ base_url: Public URL of this FastMCP server (or use mcp_url for backwards compatibility)
+ mcp_url: Deprecated alias for base_url. Will be removed in a future release.
+ client_id: Deprecated parameter, no longer required. Will be removed in a future release.
required_scopes: Optional list of scopes that must be present in tokens
token_verifier: Optional token verifier. If None, creates JWT verifier for Scalekit
"""
- legacy_client_id = client_id is not NotSet
-
- settings = ScalekitProviderSettings.model_validate(
- {
- k: v
- for k, v in {
- "environment_url": environment_url,
- "resource_id": resource_id,
- "base_url": base_url,
- "mcp_url": mcp_url,
- "required_scopes": required_scopes,
- }.items()
- if v is not NotSet
- }
- )
+ # Resolve base_url from mcp_url if needed (backwards compatibility)
+ resolved_base_url = base_url or mcp_url
+ if not resolved_base_url:
+ raise ValueError("Either base_url or mcp_url must be provided")
- if settings.mcp_url is not None:
+ if mcp_url is not None:
logger.warning(
"ScalekitProvider parameter 'mcp_url' is deprecated and will be removed in a future release. "
"Rename it to 'base_url'."
)
- if legacy_client_id:
+ if client_id is not None:
logger.warning(
"ScalekitProvider no longer requires 'client_id'. The parameter is accepted only for backward "
"compatibility and will be removed in a future release."
)
- self.environment_url = str(settings.environment_url).rstrip("/")
- self.resource_id = settings.resource_id
- self.required_scopes = settings.required_scopes or []
- base_url_value = str(settings.base_url)
+ self.environment_url = str(environment_url).rstrip("/")
+ self.resource_id = resource_id
+ parsed_scopes = (
+ parse_scopes(required_scopes) if required_scopes is not None else []
+ )
+ self.required_scopes = parsed_scopes
+ base_url_value = str(resolved_base_url)
logger.debug(
"Initializing ScalekitProvider: environment_url=%s resource_id=%s base_url=%s required_scopes=%s",
diff --git a/src/fastmcp/server/auth/providers/supabase.py b/src/fastmcp/server/auth/providers/supabase.py
index 418831d484..ffc68d1cc0 100644
--- a/src/fastmcp/server/auth/providers/supabase.py
+++ b/src/fastmcp/server/auth/providers/supabase.py
@@ -10,39 +10,18 @@
from typing import Literal
import httpx
-from pydantic import AnyHttpUrl, field_validator
-from pydantic_settings import BaseSettings, SettingsConfigDict
+from pydantic import AnyHttpUrl
from starlette.responses import JSONResponse
from starlette.routing import Route
from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier
from fastmcp.server.auth.providers.jwt import JWTVerifier
-from fastmcp.settings import ENV_FILE
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
-from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__)
-class SupabaseProviderSettings(BaseSettings):
- model_config = SettingsConfigDict(
- env_prefix="FASTMCP_SERVER_AUTH_SUPABASE_",
- env_file=ENV_FILE,
- extra="ignore",
- )
-
- project_url: AnyHttpUrl
- base_url: AnyHttpUrl
- algorithm: Literal["HS256", "RS256", "ES256"] = "ES256"
- required_scopes: list[str] | None = None
-
- @field_validator("required_scopes", mode="before")
- @classmethod
- def _parse_scopes(cls, v):
- return parse_scopes(v)
-
-
class SupabaseProvider(RemoteAuthProvider):
"""Supabase metadata provider for DCR (Dynamic Client Registration).
@@ -91,10 +70,10 @@ class SupabaseProvider(RemoteAuthProvider):
def __init__(
self,
*,
- project_url: AnyHttpUrl | str | NotSetT = NotSet,
- base_url: AnyHttpUrl | str | NotSetT = NotSet,
- algorithm: Literal["HS256", "RS256", "ES256"] | NotSetT = NotSet,
- required_scopes: list[str] | NotSetT | None = NotSet,
+ project_url: AnyHttpUrl | str,
+ base_url: AnyHttpUrl | str,
+ algorithm: Literal["HS256", "RS256", "ES256"] = "ES256",
+ required_scopes: list[str] | None = None,
token_verifier: TokenVerifier | None = None,
):
"""Initialize Supabase metadata provider.
@@ -109,29 +88,21 @@ def __init__(
scopes are an upcoming feature.
token_verifier: Optional token verifier. If None, creates JWT verifier for Supabase
"""
- settings = SupabaseProviderSettings.model_validate(
- {
- k: v
- for k, v in {
- "project_url": project_url,
- "base_url": base_url,
- "algorithm": algorithm,
- "required_scopes": required_scopes,
- }.items()
- if v is not NotSet
- }
- )
+ self.project_url = str(project_url).rstrip("/")
+ self.base_url = AnyHttpUrl(str(base_url).rstrip("/"))
- self.project_url = str(settings.project_url).rstrip("/")
- self.base_url = AnyHttpUrl(str(settings.base_url).rstrip("/"))
+ # Parse scopes if provided as string
+ parsed_scopes = (
+ parse_scopes(required_scopes) if required_scopes is not None else None
+ )
# Create default JWT verifier if none provided
if token_verifier is None:
token_verifier = JWTVerifier(
jwks_uri=f"{self.project_url}/auth/v1/.well-known/jwks.json",
issuer=f"{self.project_url}/auth/v1",
- algorithm=settings.algorithm,
- required_scopes=settings.required_scopes,
+ algorithm=algorithm,
+ required_scopes=parsed_scopes,
)
# Initialize RemoteAuthProvider with Supabase as the authorization server
diff --git a/src/fastmcp/server/auth/providers/workos.py b/src/fastmcp/server/auth/providers/workos.py
index 7f7d20b2c2..5c0ba73e1c 100644
--- a/src/fastmcp/server/auth/providers/workos.py
+++ b/src/fastmcp/server/auth/providers/workos.py
@@ -12,48 +12,19 @@
import httpx
from key_value.aio.protocols import AsyncKeyValue
-from pydantic import AnyHttpUrl, SecretStr, field_validator
-from pydantic_settings import BaseSettings, SettingsConfigDict
+from pydantic import AnyHttpUrl
from starlette.responses import JSONResponse
from starlette.routing import Route
from fastmcp.server.auth import AccessToken, RemoteAuthProvider, TokenVerifier
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.providers.jwt import JWTVerifier
-from fastmcp.settings import ENV_FILE
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
-from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__)
-class WorkOSProviderSettings(BaseSettings):
- """Settings for WorkOS OAuth provider."""
-
- model_config = SettingsConfigDict(
- env_prefix="FASTMCP_SERVER_AUTH_WORKOS_",
- env_file=ENV_FILE,
- extra="ignore",
- )
-
- client_id: str | None = None
- client_secret: SecretStr | None = None
- authkit_domain: str | None = None # e.g., "https://your-app.authkit.app"
- base_url: AnyHttpUrl | str | None = None
- issuer_url: AnyHttpUrl | str | None = None
- redirect_path: str | None = None
- required_scopes: list[str] | None = None
- timeout_seconds: int | None = None
- allowed_client_redirect_uris: list[str] | None = None
- jwt_signing_key: str | None = None
-
- @field_validator("required_scopes", mode="before")
- @classmethod
- def _parse_scopes(cls, v):
- return parse_scopes(v)
-
-
class WorkOSTokenVerifier(TokenVerifier):
"""Token verifier for WorkOS OAuth tokens.
@@ -163,17 +134,17 @@ class WorkOSProvider(OAuthProxy):
def __init__(
self,
*,
- client_id: str | NotSetT = NotSet,
- client_secret: str | NotSetT = NotSet,
- authkit_domain: str | NotSetT = NotSet,
- base_url: AnyHttpUrl | str | NotSetT = NotSet,
- issuer_url: AnyHttpUrl | str | NotSetT = NotSet,
- redirect_path: str | NotSetT = NotSet,
- required_scopes: list[str] | NotSetT | None = NotSet,
- timeout_seconds: int | NotSetT = NotSet,
- allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
+ client_id: str,
+ client_secret: str,
+ authkit_domain: str,
+ base_url: AnyHttpUrl | str,
+ issuer_url: AnyHttpUrl | str | None = None,
+ redirect_path: str | None = None,
+ required_scopes: list[str] | None = None,
+ timeout_seconds: int = 10,
+ allowed_client_redirect_uris: list[str] | None = None,
client_storage: AsyncKeyValue | None = None,
- jwt_signing_key: str | bytes | NotSetT = NotSet,
+ jwt_signing_key: str | bytes | None = None,
require_authorization_consent: bool = True,
):
"""Initialize WorkOS OAuth provider.
@@ -187,7 +158,7 @@ def __init__(
to avoid 404s during discovery when mounting under a path.
redirect_path: Redirect path configured in WorkOS (defaults to "/auth/callback")
required_scopes: Required OAuth scopes (no default)
- timeout_seconds: HTTP request timeout for WorkOS API calls
+ timeout_seconds: HTTP request timeout for WorkOS API calls (defaults to 10)
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
@@ -201,106 +172,45 @@ def __init__(
When False, authorization proceeds directly without user confirmation.
SECURITY WARNING: Only disable for local development or testing environments.
"""
-
- settings = WorkOSProviderSettings.model_validate(
- {
- k: v
- for k, v in {
- "client_id": client_id,
- "client_secret": client_secret,
- "authkit_domain": authkit_domain,
- "base_url": base_url,
- "issuer_url": issuer_url,
- "redirect_path": redirect_path,
- "required_scopes": required_scopes,
- "timeout_seconds": timeout_seconds,
- "allowed_client_redirect_uris": allowed_client_redirect_uris,
- "jwt_signing_key": jwt_signing_key,
- }.items()
- if v is not NotSet
- }
- )
-
- # Validate required settings
- if not settings.client_id:
- raise ValueError(
- "client_id is required - set via parameter or FASTMCP_SERVER_AUTH_WORKOS_CLIENT_ID"
- )
- if not settings.client_secret:
- raise ValueError(
- "client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_WORKOS_CLIENT_SECRET"
- )
- if not settings.authkit_domain:
- raise ValueError(
- "authkit_domain is required - set via parameter or FASTMCP_SERVER_AUTH_WORKOS_AUTHKIT_DOMAIN"
- )
- if not settings.base_url:
- raise ValueError(
- "base_url is required - set via parameter or FASTMCP_SERVER_AUTH_WORKOS_BASE_URL"
- )
-
# Apply defaults and ensure authkit_domain is a full URL
- authkit_domain_str = settings.authkit_domain
+ authkit_domain_str = authkit_domain
if not authkit_domain_str.startswith(("http://", "https://")):
authkit_domain_str = f"https://{authkit_domain_str}"
authkit_domain_final = authkit_domain_str.rstrip("/")
- timeout_seconds_final = settings.timeout_seconds or 10
- scopes_final = settings.required_scopes or []
- allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
-
- # Extract secret string from SecretStr
- client_secret_str = (
- settings.client_secret.get_secret_value() if settings.client_secret else ""
+ scopes_final = (
+ parse_scopes(required_scopes) if required_scopes is not None else []
)
# Create WorkOS token verifier
token_verifier = WorkOSTokenVerifier(
authkit_domain=authkit_domain_final,
required_scopes=scopes_final,
- timeout_seconds=timeout_seconds_final,
+ timeout_seconds=timeout_seconds,
)
# Initialize OAuth proxy with WorkOS AuthKit endpoints
super().__init__(
upstream_authorization_endpoint=f"{authkit_domain_final}/oauth2/authorize",
upstream_token_endpoint=f"{authkit_domain_final}/oauth2/token",
- upstream_client_id=settings.client_id,
- upstream_client_secret=client_secret_str,
+ upstream_client_id=client_id,
+ upstream_client_secret=client_secret,
token_verifier=token_verifier,
- base_url=settings.base_url,
- redirect_path=settings.redirect_path,
- issuer_url=settings.issuer_url
- or settings.base_url, # Default to base_url if not specified
- allowed_client_redirect_uris=allowed_client_redirect_uris_final,
+ base_url=base_url,
+ redirect_path=redirect_path,
+ issuer_url=issuer_url or base_url, # Default to base_url if not specified
+ allowed_client_redirect_uris=allowed_client_redirect_uris,
client_storage=client_storage,
- jwt_signing_key=settings.jwt_signing_key,
+ jwt_signing_key=jwt_signing_key,
require_authorization_consent=require_authorization_consent,
)
logger.debug(
"Initialized WorkOS OAuth provider for client %s with AuthKit domain %s",
- settings.client_id,
+ client_id,
authkit_domain_final,
)
-class AuthKitProviderSettings(BaseSettings):
- model_config = SettingsConfigDict(
- env_prefix="FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_",
- env_file=ENV_FILE,
- extra="ignore",
- )
-
- authkit_domain: AnyHttpUrl
- base_url: AnyHttpUrl
- required_scopes: list[str] | None = None
-
- @field_validator("required_scopes", mode="before")
- @classmethod
- def _parse_scopes(cls, v):
- return parse_scopes(v)
-
-
class AuthKitProvider(RemoteAuthProvider):
"""AuthKit metadata provider for DCR (Dynamic Client Registration).
@@ -340,9 +250,9 @@ class AuthKitProvider(RemoteAuthProvider):
def __init__(
self,
*,
- authkit_domain: AnyHttpUrl | str | NotSetT = NotSet,
- base_url: AnyHttpUrl | str | NotSetT = NotSet,
- required_scopes: list[str] | NotSetT | None = NotSet,
+ authkit_domain: AnyHttpUrl | str,
+ base_url: AnyHttpUrl | str,
+ required_scopes: list[str] | None = None,
token_verifier: TokenVerifier | None = None,
):
"""Initialize AuthKit metadata provider.
@@ -353,20 +263,13 @@ def __init__(
required_scopes: Optional list of scopes to require for all requests
token_verifier: Optional token verifier. If None, creates JWT verifier for AuthKit
"""
- settings = AuthKitProviderSettings.model_validate(
- {
- k: v
- for k, v in {
- "authkit_domain": authkit_domain,
- "base_url": base_url,
- "required_scopes": required_scopes,
- }.items()
- if v is not NotSet
- }
- )
+ self.authkit_domain = str(authkit_domain).rstrip("/")
+ self.base_url = AnyHttpUrl(str(base_url).rstrip("/"))
- self.authkit_domain = str(settings.authkit_domain).rstrip("/")
- self.base_url = AnyHttpUrl(str(settings.base_url).rstrip("/"))
+ # Parse scopes if provided as string
+ parsed_scopes = (
+ parse_scopes(required_scopes) if required_scopes is not None else None
+ )
# Create default JWT verifier if none provided
if token_verifier is None:
@@ -374,7 +277,7 @@ def __init__(
jwks_uri=f"{self.authkit_domain}/oauth2/jwks",
issuer=self.authkit_domain,
algorithm="RS256",
- required_scopes=settings.required_scopes,
+ required_scopes=parsed_scopes,
)
# Initialize RemoteAuthProvider with AuthKit as the authorization server
diff --git a/tests/server/auth/providers/test_auth0.py b/tests/server/auth/providers/test_auth0.py
index 4dc6e364e4..3b1b5cd8f9 100644
--- a/tests/server/auth/providers/test_auth0.py
+++ b/tests/server/auth/providers/test_auth0.py
@@ -1,12 +1,11 @@
"""Unit tests for Auth0 OAuth provider."""
-import os
from unittest.mock import patch
import pytest
from fastmcp.server.auth.oidc_proxy import OIDCConfiguration
-from fastmcp.server.auth.providers.auth0 import Auth0Provider, Auth0ProviderSettings
+from fastmcp.server.auth.providers.auth0 import Auth0Provider
from fastmcp.server.auth.providers.jwt import JWTVerifier
TEST_CONFIG_URL = "https://example.com/.well-known/openid-configuration"
@@ -32,62 +31,6 @@ def valid_oidc_configuration_dict():
}
-class TestAuth0ProviderSettings:
- """Test settings for Auth0 OAuth provider."""
-
- def test_settings_from_env_vars(self):
- """Test that settings can be loaded from environment variables."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL": TEST_CONFIG_URL,
- "FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID": TEST_CLIENT_ID,
- "FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET": TEST_CLIENT_SECRET,
- "FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE": TEST_AUDIENCE,
- "FASTMCP_SERVER_AUTH_AUTH0_BASE_URL": TEST_BASE_URL,
- "FASTMCP_SERVER_AUTH_AUTH0_REDIRECT_PATH": TEST_REDIRECT_PATH,
- "FASTMCP_SERVER_AUTH_AUTH0_REQUIRED_SCOPES": ",".join(
- TEST_REQUIRED_SCOPES
- ),
- "FASTMCP_SERVER_AUTH_AUTH0_JWT_SIGNING_KEY": "test-secret",
- },
- ):
- settings = Auth0ProviderSettings()
-
- assert str(settings.config_url) == TEST_CONFIG_URL
- assert settings.client_id == TEST_CLIENT_ID
- assert (
- settings.client_secret
- and settings.client_secret.get_secret_value() == TEST_CLIENT_SECRET
- )
- assert settings.audience == TEST_AUDIENCE
- assert str(settings.base_url) == TEST_BASE_URL
- assert settings.redirect_path == TEST_REDIRECT_PATH
- assert settings.required_scopes == TEST_REQUIRED_SCOPES
-
- def test_settings_explicit_override_env(self):
- """Test that explicit settings override environment variables."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID": TEST_CLIENT_ID,
- "FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET": TEST_CLIENT_SECRET,
- },
- ):
- settings = Auth0ProviderSettings.model_validate(
- {
- "client_id": "explicit_client_id",
- "client_secret": "explicit_secret",
- }
- )
-
- assert settings.client_id == "explicit_client_id"
- assert (
- settings.client_secret
- and settings.client_secret.get_secret_value() == "explicit_secret"
- )
-
-
class TestAuth0Provider:
"""Test Auth0Provider initialization."""
@@ -130,135 +73,6 @@ def test_init_with_explicit_params(self, valid_oidc_configuration_dict):
assert provider._redirect_path == TEST_REDIRECT_PATH
assert provider._token_validator.required_scopes == TEST_REQUIRED_SCOPES
- @pytest.mark.parametrize(
- "scopes_env",
- [
- "openid,email",
- '["openid", "email"]',
- ],
- )
- def test_init_with_env_vars(self, scopes_env, valid_oidc_configuration_dict):
- """Test initialization with environment variables."""
- with (
- patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL": TEST_CONFIG_URL,
- "FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID": TEST_CLIENT_ID,
- "FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET": TEST_CLIENT_SECRET,
- "FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE": TEST_AUDIENCE,
- "FASTMCP_SERVER_AUTH_AUTH0_BASE_URL": TEST_BASE_URL,
- "FASTMCP_SERVER_AUTH_AUTH0_REQUIRED_SCOPES": scopes_env,
- "FASTMCP_SERVER_AUTH_AUTH0_JWT_SIGNING_KEY": "test-secret",
- },
- ),
- patch(
- "fastmcp.server.auth.oidc_proxy.OIDCConfiguration.get_oidc_configuration"
- ) as mock_get,
- ):
- oidc_config = OIDCConfiguration.model_validate(
- valid_oidc_configuration_dict
- )
- mock_get.return_value = oidc_config
-
- provider = Auth0Provider()
-
- mock_get.assert_called_once()
-
- call_args = mock_get.call_args
- assert str(call_args[0][0]) == TEST_CONFIG_URL
-
- assert provider._upstream_client_id == TEST_CLIENT_ID
- assert (
- provider._upstream_client_secret.get_secret_value()
- == TEST_CLIENT_SECRET
- )
-
- assert isinstance(provider._token_validator, JWTVerifier)
- assert provider._token_validator.audience == TEST_AUDIENCE
-
- assert str(provider.base_url) == TEST_BASE_URL
- assert provider._token_validator.required_scopes == TEST_REQUIRED_SCOPES
-
- def test_init_explicit_overrides_env(self, valid_oidc_configuration_dict):
- """Test that explicit parameters override environment variables."""
- with (
- patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL": TEST_CONFIG_URL,
- "FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID": TEST_CLIENT_ID,
- "FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET": TEST_CLIENT_SECRET,
- "FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE": TEST_AUDIENCE,
- "FASTMCP_SERVER_AUTH_AUTH0_BASE_URL": TEST_BASE_URL,
- "FASTMCP_SERVER_AUTH_AUTH0_JWT_SIGNING_KEY": "test-secret",
- },
- ),
- patch(
- "fastmcp.server.auth.oidc_proxy.OIDCConfiguration.get_oidc_configuration"
- ) as mock_get,
- ):
- oidc_config = OIDCConfiguration.model_validate(
- valid_oidc_configuration_dict
- )
- mock_get.return_value = oidc_config
-
- provider = Auth0Provider(
- client_id="explicit_client",
- client_secret="explicit_secret",
- )
-
- assert provider._upstream_client_id == "explicit_client"
- assert (
- provider._upstream_client_secret.get_secret_value() == "explicit_secret"
- )
-
- def test_init_missing_config_url_raises_error(self):
- """Test that missing config_url raises ValueError."""
- # Clear environment variables to test proper error handling
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="config_url is required"):
- Auth0Provider()
-
- def test_init_missing_client_id_raises_error(self):
- """Test that missing client_id raises ValueError."""
- # Clear environment variables to test proper error handling
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="client_id is required"):
- Auth0Provider(config_url=TEST_CONFIG_URL)
-
- def test_init_missing_client_secret_raises_error(self):
- """Test that missing client_secret raises ValueError."""
- # Clear environment variables to test proper error handling
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="client_secret is required"):
- Auth0Provider(config_url=TEST_CONFIG_URL, client_id=TEST_CLIENT_ID)
-
- def test_init_missing_audience_raises_error(self):
- """Test that missing audience raises ValueError."""
- # Clear environment variables to test proper error handling
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="audience is required"):
- Auth0Provider(
- config_url=TEST_CONFIG_URL,
- client_id=TEST_CLIENT_ID,
- client_secret=TEST_CLIENT_SECRET,
- jwt_signing_key="test-secret",
- )
-
- def test_init_missing_base_url_raises_error(self):
- """Test that missing base_url raises ValueError."""
- # Clear environment variables to test proper error handling
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="base_url is required"):
- Auth0Provider(
- config_url=TEST_CONFIG_URL,
- client_id=TEST_CLIENT_ID,
- client_secret=TEST_CLIENT_SECRET,
- audience=TEST_AUDIENCE,
- jwt_signing_key="test-secret",
- )
-
def test_init_defaults(self, valid_oidc_configuration_dict):
"""Test that default values are applied correctly."""
with patch(
diff --git a/tests/server/auth/providers/test_aws.py b/tests/server/auth/providers/test_aws.py
index 65f2a098a2..9831dfaf87 100644
--- a/tests/server/auth/providers/test_aws.py
+++ b/tests/server/auth/providers/test_aws.py
@@ -1,14 +1,10 @@
"""Unit tests for AWS Cognito OAuth provider."""
-import os
from contextlib import contextmanager
from unittest.mock import patch
-import pytest
-
from fastmcp.server.auth.providers.aws import (
AWSCognitoProvider,
- AWSCognitoProviderSettings,
)
@@ -38,60 +34,6 @@ def mock_cognito_oidc_discovery():
yield
-class TestAWSCognitoProviderSettings:
- """Test settings for AWS Cognito OAuth provider."""
-
- def test_settings_from_env_vars(self):
- """Test that settings can be loaded from environment variables."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID": "us-east-1_XXXXXXXXX",
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_AWS_REGION": "us-east-1",
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID": "env_client_id",
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET": "env_secret",
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_BASE_URL": "https://example.com",
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_REDIRECT_PATH": "/custom/callback",
- },
- ):
- settings = AWSCognitoProviderSettings()
-
- assert settings.user_pool_id == "us-east-1_XXXXXXXXX"
- assert settings.aws_region == "us-east-1"
- assert settings.client_id == "env_client_id"
- assert (
- settings.client_secret
- and settings.client_secret.get_secret_value() == "env_secret"
- )
- assert settings.base_url == "https://example.com"
- assert settings.redirect_path == "/custom/callback"
-
- def test_settings_explicit_override_env(self):
- """Test that explicit settings override environment variables."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID": "env_pool_id",
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID": "env_client_id",
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET": "env_secret",
- },
- ):
- settings = AWSCognitoProviderSettings.model_validate(
- {
- "user_pool_id": "explicit_pool_id",
- "client_id": "explicit_client_id",
- "client_secret": "explicit_secret",
- }
- )
-
- assert settings.user_pool_id == "explicit_pool_id"
- assert settings.client_id == "explicit_client_id"
- assert (
- settings.client_secret
- and settings.client_secret.get_secret_value() == "explicit_secret"
- )
-
-
class TestAWSCognitoProvider:
"""Test AWSCognitoProvider initialization."""
@@ -126,92 +68,6 @@ def test_init_with_explicit_params(self):
== "https://test.auth.us-east-1.amazoncognito.com/oauth2/token"
)
- @pytest.mark.parametrize(
- "scopes_env",
- [
- "openid,email",
- '["openid", "email"]',
- ],
- )
- def test_init_with_env_vars(self, scopes_env):
- """Test initialization with environment variables."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID": "us-east-1_XXXXXXXXX",
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_AWS_REGION": "us-east-1",
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID": "env_client_id",
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET": "env_secret",
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_BASE_URL": "https://env-example.com",
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_REQUIRED_SCOPES": scopes_env,
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_JWT_SIGNING_KEY": "test-secret",
- },
- ):
- with mock_cognito_oidc_discovery():
- provider = AWSCognitoProvider()
-
- assert provider._upstream_client_id == "env_client_id"
- assert (
- provider._upstream_client_secret.get_secret_value() == "env_secret"
- )
- assert str(provider.base_url) == "https://env-example.com/"
- assert provider._token_validator.required_scopes == ["openid", "email"]
-
- def test_init_explicit_overrides_env(self):
- """Test that explicit parameters override environment variables."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID": "env_pool_id",
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID": "env_client_id",
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET": "env_secret",
- "FASTMCP_SERVER_AUTH_AWS_COGNITO_JWT_SIGNING_KEY": "test-secret",
- },
- ):
- with mock_cognito_oidc_discovery():
- provider = AWSCognitoProvider(
- user_pool_id="explicit_pool_id",
- client_id="explicit_client",
- client_secret="explicit_secret",
- base_url="https://example.com",
- jwt_signing_key="test-secret",
- )
-
- assert provider._upstream_client_id == "explicit_client"
- assert (
- provider._upstream_client_secret.get_secret_value()
- == "explicit_secret"
- )
- # OIDC discovery should have configured the endpoints automatically
- assert provider._upstream_authorization_endpoint is not None
-
- def test_init_missing_user_pool_id_raises_error(self):
- """Test that missing user_pool_id raises ValueError."""
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="user_pool_id is required"):
- AWSCognitoProvider(
- client_id="test_client",
- client_secret="test_secret",
- )
-
- def test_init_missing_client_id_raises_error(self):
- """Test that missing client_id raises ValueError."""
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="client_id is required"):
- AWSCognitoProvider(
- user_pool_id="us-east-1_XXXXXXXXX",
- client_secret="test_secret",
- )
-
- def test_init_missing_client_secret_raises_error(self):
- """Test that missing client_secret raises ValueError."""
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="client_secret is required"):
- AWSCognitoProvider(
- user_pool_id="us-east-1_XXXXXXXXX",
- client_id="test_client",
- )
-
def test_init_defaults(self):
"""Test that default values are applied correctly."""
with mock_cognito_oidc_discovery():
diff --git a/tests/server/auth/providers/test_azure.py b/tests/server/auth/providers/test_azure.py
index c794a68079..688d0332b4 100644
--- a/tests/server/auth/providers/test_azure.py
+++ b/tests/server/auth/providers/test_azure.py
@@ -1,10 +1,7 @@
"""Tests for Azure (Microsoft Entra) OAuth provider."""
-import os
-from unittest.mock import patch
from urllib.parse import parse_qs, urlparse
-import pytest
from mcp.server.auth.provider import AuthorizationParams
from mcp.shared.auth import OAuthClientInformationFull
from pydantic import AnyUrl
@@ -36,115 +33,6 @@ def test_init_with_explicit_params(self):
parsed_token = urlparse(provider._upstream_token_endpoint)
assert "87654321-4321-4321-4321-210987654321" in parsed_token.path
- @pytest.mark.parametrize(
- "scopes_env",
- [
- "read,write",
- '["read", "write"]',
- ],
- )
- def test_init_with_env_vars(self, scopes_env):
- """Test AzureProvider initialization from environment variables."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_AZURE_CLIENT_ID": "env-client-id",
- "FASTMCP_SERVER_AUTH_AZURE_CLIENT_SECRET": "env-secret",
- "FASTMCP_SERVER_AUTH_AZURE_TENANT_ID": "env-tenant-id",
- "FASTMCP_SERVER_AUTH_AZURE_BASE_URL": "https://envserver.com",
- "FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES": scopes_env,
- "FASTMCP_SERVER_AUTH_AZURE_JWT_SIGNING_KEY": "test-secret",
- },
- ):
- provider = AzureProvider()
-
- assert provider._upstream_client_id == "env-client-id"
- assert provider._upstream_client_secret.get_secret_value() == "env-secret"
- assert str(provider.base_url) == "https://envserver.com/"
- # Scopes are stored unprefixed for token validation
- # (Azure returns unprefixed scopes in JWT tokens)
- assert provider._token_validator.required_scopes == [
- "read",
- "write",
- ]
- # Check tenant is in the endpoints
- parsed_auth = urlparse(provider._upstream_authorization_endpoint)
- assert "env-tenant-id" in parsed_auth.path
- parsed_token = urlparse(provider._upstream_token_endpoint)
- assert "env-tenant-id" in parsed_token.path
-
- def test_init_missing_client_id_raises_error(self):
- """Test that missing client_id raises ValueError."""
- # Clear environment variables to ensure we're testing the parameter validation
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="client_id is required"):
- AzureProvider(
- client_secret="test_secret",
- tenant_id="test-tenant",
- required_scopes=["read"],
- )
-
- def test_init_missing_client_secret_raises_error(self):
- """Test that missing client_secret raises ValueError."""
- # Clear environment variables to ensure we're testing the parameter validation
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="client_secret is required"):
- AzureProvider(
- client_id="test_client",
- tenant_id="test-tenant",
- required_scopes=["read"],
- )
-
- def test_init_missing_tenant_id_raises_error(self):
- """Test that missing tenant_id raises ValueError."""
- # Clear environment variables to ensure we're testing the parameter validation
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="tenant_id is required"):
- AzureProvider(
- client_id="test_client",
- client_secret="test_secret",
- required_scopes=["read"],
- )
-
- def test_init_missing_required_scopes_raises_error(self):
- """Test that missing required_scopes raises ValueError."""
- # Clear environment variables to ensure we're testing the parameter validation
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(
- ValueError, match="required_scopes must include at least one scope"
- ):
- AzureProvider(
- client_id="test_client",
- client_secret="test_secret",
- tenant_id="test-tenant",
- )
-
- def test_init_empty_required_scopes_raises_error(self):
- """Test that empty required_scopes raises ValueError."""
- # Clear environment variables to ensure we're testing the parameter validation
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(
- ValueError, match="required_scopes must include at least one scope"
- ):
- AzureProvider(
- client_id="test_client",
- client_secret="test_secret",
- tenant_id="test-tenant",
- required_scopes=[],
- )
-
- def test_init_missing_base_url_raises_error(self):
- """Test that missing base_url raises ValueError."""
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="base_url is required"):
- AzureProvider(
- client_id="test_client",
- client_secret="test_secret",
- tenant_id="test-tenant",
- required_scopes=["read"],
- jwt_signing_key="test-secret",
- )
-
def test_init_defaults(self):
"""Test that default values are applied correctly."""
provider = AzureProvider(
@@ -463,39 +351,35 @@ def test_base_authority_azure_government(self):
== "https://login.microsoftonline.us/gov-tenant-id/discovery/v2.0/keys"
)
- def test_base_authority_from_environment_variable(self):
- """Test that base_authority can be set via environment variable."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_AZURE_CLIENT_ID": "env-client-id",
- "FASTMCP_SERVER_AUTH_AZURE_CLIENT_SECRET": "env-secret",
- "FASTMCP_SERVER_AUTH_AZURE_TENANT_ID": "env-tenant-id",
- "FASTMCP_SERVER_AUTH_AZURE_BASE_URL": "https://myserver.com",
- "FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES": "read",
- "FASTMCP_SERVER_AUTH_AZURE_BASE_AUTHORITY": "login.microsoftonline.us",
- "FASTMCP_SERVER_AUTH_AZURE_JWT_SIGNING_KEY": "test-secret",
- },
- ):
- provider = AzureProvider()
-
- assert (
- provider._upstream_authorization_endpoint
- == "https://login.microsoftonline.us/env-tenant-id/oauth2/v2.0/authorize"
- )
- assert (
- provider._upstream_token_endpoint
- == "https://login.microsoftonline.us/env-tenant-id/oauth2/v2.0/token"
- )
- assert isinstance(provider._token_validator, JWTVerifier)
- assert (
- provider._token_validator.issuer
- == "https://login.microsoftonline.us/env-tenant-id/v2.0"
- )
- assert (
- provider._token_validator.jwks_uri
- == "https://login.microsoftonline.us/env-tenant-id/discovery/v2.0/keys"
- )
+ def test_base_authority_from_parameter(self):
+ """Test that base_authority can be set via parameter."""
+ provider = AzureProvider(
+ client_id="env-client-id",
+ client_secret="env-secret",
+ tenant_id="env-tenant-id",
+ base_url="https://myserver.com",
+ required_scopes=["read"],
+ base_authority="login.microsoftonline.us",
+ jwt_signing_key="test-secret",
+ )
+
+ assert (
+ provider._upstream_authorization_endpoint
+ == "https://login.microsoftonline.us/env-tenant-id/oauth2/v2.0/authorize"
+ )
+ assert (
+ provider._upstream_token_endpoint
+ == "https://login.microsoftonline.us/env-tenant-id/oauth2/v2.0/token"
+ )
+ assert isinstance(provider._token_validator, JWTVerifier)
+ assert (
+ provider._token_validator.issuer
+ == "https://login.microsoftonline.us/env-tenant-id/v2.0"
+ )
+ assert (
+ provider._token_validator.jwks_uri
+ == "https://login.microsoftonline.us/env-tenant-id/discovery/v2.0/keys"
+ )
def test_base_authority_with_special_tenant_values(self):
"""Test that base_authority works with special tenant values like 'organizations'."""
diff --git a/tests/server/auth/providers/test_descope.py b/tests/server/auth/providers/test_descope.py
index 4682cf7a26..7dcfc477f1 100644
--- a/tests/server/auth/providers/test_descope.py
+++ b/tests/server/auth/providers/test_descope.py
@@ -27,42 +27,6 @@ def test_init_with_explicit_params(self):
assert str(provider.base_url) == "https://myserver.com/"
assert str(provider.descope_base_url) == "https://api.descope.com"
- def test_init_with_env_vars(self):
- """Test DescopeProvider initialization from environment variables."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_CONFIG_URL": "https://api.descope.com/v1/apps/agentic/P2env123/M123/.well-known/openid-configuration",
- "FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_BASE_URL": "https://envserver.com",
- },
- ):
- provider = DescopeProvider()
-
- assert provider.project_id == "P2env123"
- assert str(provider.base_url) == "https://envserver.com/"
- assert str(provider.descope_base_url) == "https://api.descope.com"
-
- def test_init_with_old_env_vars(self):
- """Test DescopeProvider initialization from old environment variables (backwards compatibility)."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_PROJECT_ID": "P2oldenv123",
- "FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_DESCOPE_BASE_URL": "https://api.descope.com",
- "FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_BASE_URL": "https://envserver.com",
- },
- ):
- provider = DescopeProvider()
-
- assert provider.project_id == "P2oldenv123"
- assert str(provider.base_url) == "https://envserver.com/"
- assert str(provider.descope_base_url) == "https://api.descope.com"
- assert isinstance(provider.token_verifier, JWTVerifier)
- assert (
- provider.token_verifier.issuer
- == "https://api.descope.com/v1/apps/P2oldenv123"
- )
-
def test_environment_variable_loading(self):
"""Test that environment variables are loaded correctly."""
# This test verifies that the provider can be created with environment variables
@@ -219,7 +183,11 @@ def test_required_scopes_from_env(self):
"FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_REQUIRED_SCOPES": "read,write",
},
):
- provider = DescopeProvider()
+ provider = DescopeProvider(
+ config_url="https://api.descope.com/v1/apps/agentic/P2env123/M123/.well-known/openid-configuration",
+ base_url="https://envserver.com",
+ required_scopes=["read", "write"],
+ )
assert isinstance(provider.token_verifier, JWTVerifier)
assert provider.token_verifier.required_scopes == ["read", "write"]
diff --git a/tests/server/auth/providers/test_discord.py b/tests/server/auth/providers/test_discord.py
index 227714d848..8d79265e6b 100644
--- a/tests/server/auth/providers/test_discord.py
+++ b/tests/server/auth/providers/test_discord.py
@@ -1,10 +1,5 @@
"""Tests for Discord OAuth provider."""
-import os
-from unittest.mock import patch
-
-import pytest
-
from fastmcp.server.auth.providers.discord import DiscordProvider
@@ -25,61 +20,6 @@ def test_init_with_explicit_params(self):
assert provider._upstream_client_secret.get_secret_value() == "GOCSPX-test123"
assert str(provider.base_url) == "https://myserver.com/"
- @pytest.mark.parametrize(
- "scopes_env",
- [
- "identify,email",
- '["identify", "email"]',
- ],
- )
- def test_init_with_env_vars(self, scopes_env):
- """Test DiscordProvider initialization from environment variables."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_DISCORD_CLIENT_ID": "env_client_id",
- "FASTMCP_SERVER_AUTH_DISCORD_CLIENT_SECRET": "GOCSPX-env456",
- "FASTMCP_SERVER_AUTH_DISCORD_BASE_URL": "https://envserver.com",
- "FASTMCP_SERVER_AUTH_DISCORD_REQUIRED_SCOPES": scopes_env,
- "FASTMCP_SERVER_AUTH_DISCORD_JWT_SIGNING_KEY": "test-secret",
- },
- ):
- provider = DiscordProvider()
-
- assert provider._upstream_client_id == "env_client_id"
- assert (
- provider._upstream_client_secret.get_secret_value() == "GOCSPX-env456"
- )
- assert str(provider.base_url) == "https://envserver.com/"
- assert provider._token_validator.required_scopes == [
- "identify",
- "email",
- ]
-
- def test_init_missing_client_id_raises_error(self):
- """Test that missing client_id raises ValueError."""
- # Clear environment variables to test proper error handling
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="client_id is required"):
- DiscordProvider(client_secret="GOCSPX-test123")
-
- def test_init_missing_client_secret_raises_error(self):
- """Test that missing client_secret raises ValueError."""
- # Clear environment variables to test proper error handling
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="client_secret is required"):
- DiscordProvider(client_id="env_client_id")
-
- def test_init_missing_base_url_raises_error(self):
- """Test that missing base_url raises ValueError."""
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="base_url is required"):
- DiscordProvider(
- client_id="env_client_id",
- client_secret="GOCSPX-test123",
- jwt_signing_key="test-secret",
- )
-
def test_init_defaults(self):
"""Test that default values are applied correctly."""
provider = DiscordProvider(
diff --git a/tests/server/auth/providers/test_github.py b/tests/server/auth/providers/test_github.py
index 07853e21bd..e2fcdaa256 100644
--- a/tests/server/auth/providers/test_github.py
+++ b/tests/server/auth/providers/test_github.py
@@ -1,68 +1,13 @@
"""Unit tests for GitHub OAuth provider."""
-import os
from unittest.mock import MagicMock, patch
-import pytest
-
from fastmcp.server.auth.providers.github import (
GitHubProvider,
- GitHubProviderSettings,
GitHubTokenVerifier,
)
-class TestGitHubProviderSettings:
- """Test settings for GitHub OAuth provider."""
-
- def test_settings_from_env_vars(self):
- """Test that settings can be loaded from environment variables."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID": "env_client_id",
- "FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET": "env_secret",
- "FASTMCP_SERVER_AUTH_GITHUB_BASE_URL": "https://example.com",
- "FASTMCP_SERVER_AUTH_GITHUB_REDIRECT_PATH": "/custom/callback",
- "FASTMCP_SERVER_AUTH_GITHUB_TIMEOUT_SECONDS": "30",
- "FASTMCP_SERVER_AUTH_GITHUB_JWT_SIGNING_KEY": "test-secret",
- },
- ):
- settings = GitHubProviderSettings()
-
- assert settings.client_id == "env_client_id"
- assert (
- settings.client_secret
- and settings.client_secret.get_secret_value() == "env_secret"
- )
- assert settings.base_url == "https://example.com"
- assert settings.redirect_path == "/custom/callback"
- assert settings.timeout_seconds == 30
-
- def test_settings_explicit_override_env(self):
- """Test that explicit settings override environment variables."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID": "env_client_id",
- "FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET": "env_secret",
- },
- ):
- settings = GitHubProviderSettings.model_validate(
- {
- "client_id": "explicit_client_id",
- "client_secret": "explicit_secret",
- "jwt_signing_key": "test-secret",
- }
- )
-
- assert settings.client_id == "explicit_client_id"
- assert (
- settings.client_secret
- and settings.client_secret.get_secret_value() == "explicit_secret"
- )
-
-
class TestGitHubProvider:
"""Test GitHubProvider initialization."""
@@ -86,78 +31,6 @@ def test_init_with_explicit_params(self):
) # URLs get normalized with trailing slash
assert provider._redirect_path == "/custom/callback"
- @pytest.mark.parametrize(
- "scopes_env",
- [
- "user,repo",
- '["user", "repo"]',
- ],
- )
- def test_init_with_env_vars(self, scopes_env):
- """Test initialization with environment variables."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID": "env_client_id",
- "FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET": "env_secret",
- "FASTMCP_SERVER_AUTH_GITHUB_BASE_URL": "https://env-example.com",
- "FASTMCP_SERVER_AUTH_GITHUB_REQUIRED_SCOPES": scopes_env,
- "FASTMCP_SERVER_AUTH_GITHUB_JWT_SIGNING_KEY": "test-secret",
- },
- ):
- provider = GitHubProvider()
-
- assert provider._upstream_client_id == "env_client_id"
- assert provider._upstream_client_secret.get_secret_value() == "env_secret"
- assert str(provider.base_url) == "https://env-example.com/"
- assert provider._token_validator.required_scopes == ["user", "repo"]
-
- def test_init_explicit_overrides_env(self):
- """Test that explicit parameters override environment variables."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID": "env_client_id",
- "FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET": "env_secret",
- "FASTMCP_SERVER_AUTH_GITHUB_BASE_URL": "https://env-example.com",
- "FASTMCP_SERVER_AUTH_GITHUB_JWT_SIGNING_KEY": "test-secret",
- },
- ):
- provider = GitHubProvider(
- client_id="explicit_client",
- client_secret="explicit_secret",
- jwt_signing_key="test-secret",
- )
-
- assert provider._upstream_client_id == "explicit_client"
- assert (
- provider._upstream_client_secret.get_secret_value() == "explicit_secret"
- )
-
- def test_init_missing_client_id_raises_error(self):
- """Test that missing client_id raises ValueError."""
- # Clear environment variables to test proper error handling
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="client_id is required"):
- GitHubProvider(client_secret="test_secret")
-
- def test_init_missing_client_secret_raises_error(self):
- """Test that missing client_secret raises ValueError."""
- # Clear environment variables to test proper error handling
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="client_secret is required"):
- GitHubProvider(client_id="test_client")
-
- def test_init_missing_base_url_raises_error(self):
- """Test that missing base_url raises ValueError."""
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="base_url is required"):
- GitHubProvider(
- client_id="test_client",
- client_secret="test_secret",
- jwt_signing_key="test-secret",
- )
-
def test_init_defaults(self):
"""Test that default values are applied correctly."""
provider = GitHubProvider(
diff --git a/tests/server/auth/providers/test_google.py b/tests/server/auth/providers/test_google.py
index 73d76548cc..d578c70566 100644
--- a/tests/server/auth/providers/test_google.py
+++ b/tests/server/auth/providers/test_google.py
@@ -1,10 +1,5 @@
"""Tests for Google OAuth provider."""
-import os
-from unittest.mock import patch
-
-import pytest
-
from fastmcp.server.auth.providers.google import GoogleProvider
@@ -25,61 +20,6 @@ def test_init_with_explicit_params(self):
assert provider._upstream_client_secret.get_secret_value() == "GOCSPX-test123"
assert str(provider.base_url) == "https://myserver.com/"
- @pytest.mark.parametrize(
- "scopes_env",
- [
- "openid,https://www.googleapis.com/auth/userinfo.email",
- '["openid", "https://www.googleapis.com/auth/userinfo.email"]',
- ],
- )
- def test_init_with_env_vars(self, scopes_env):
- """Test GoogleProvider initialization from environment variables."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_ID": "env123.apps.googleusercontent.com",
- "FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET": "GOCSPX-env456",
- "FASTMCP_SERVER_AUTH_GOOGLE_BASE_URL": "https://envserver.com",
- "FASTMCP_SERVER_AUTH_GOOGLE_REQUIRED_SCOPES": scopes_env,
- "FASTMCP_SERVER_AUTH_GOOGLE_JWT_SIGNING_KEY": "test-secret",
- },
- ):
- provider = GoogleProvider()
-
- assert provider._upstream_client_id == "env123.apps.googleusercontent.com"
- assert (
- provider._upstream_client_secret.get_secret_value() == "GOCSPX-env456"
- )
- assert str(provider.base_url) == "https://envserver.com/"
- assert provider._token_validator.required_scopes == [
- "openid",
- "https://www.googleapis.com/auth/userinfo.email",
- ]
-
- def test_init_missing_client_id_raises_error(self):
- """Test that missing client_id raises ValueError."""
- # Clear environment variables to test proper error handling
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="client_id is required"):
- GoogleProvider(client_secret="GOCSPX-test123")
-
- def test_init_missing_client_secret_raises_error(self):
- """Test that missing client_secret raises ValueError."""
- # Clear environment variables to test proper error handling
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="client_secret is required"):
- GoogleProvider(client_id="123456789.apps.googleusercontent.com")
-
- def test_init_missing_base_url_raises_error(self):
- """Test that missing base_url raises ValueError."""
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="base_url is required"):
- GoogleProvider(
- client_id="123456789.apps.googleusercontent.com",
- client_secret="GOCSPX-test123",
- jwt_signing_key="test-secret",
- )
-
def test_init_defaults(self):
"""Test that default values are applied correctly."""
provider = GoogleProvider(
diff --git a/tests/server/auth/providers/test_scalekit.py b/tests/server/auth/providers/test_scalekit.py
index d43b1b92ce..b112e1c68f 100644
--- a/tests/server/auth/providers/test_scalekit.py
+++ b/tests/server/auth/providers/test_scalekit.py
@@ -1,8 +1,5 @@
"""Tests for Scalekit OAuth provider."""
-import os
-from unittest.mock import patch
-
import httpx
import pytest
@@ -51,38 +48,6 @@ def test_init_prefers_base_url_over_mcp_url(self):
assert str(provider.base_url) == "https://preferred-base.com/"
- def test_init_with_env_vars(self):
- """Test ScalekitProvider initialization from environment variables."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_ENVIRONMENT_URL": "https://env-scalekit.com",
- "FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_RESOURCE_ID": "res_456",
- "FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_BASE_URL": "https://envserver.com/mcp",
- "FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_REQUIRED_SCOPES": "read,write",
- },
- ):
- provider = ScalekitProvider()
-
- assert provider.environment_url == "https://env-scalekit.com"
- assert provider.resource_id == "res_456"
- assert str(provider.base_url) == "https://envserver.com/mcp"
- assert provider.required_scopes == ["read", "write"]
-
- def test_init_with_legacy_env_var(self):
- """FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_MCP_URL should still be supported."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_ENVIRONMENT_URL": "https://env-scalekit.com",
- "FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_RESOURCE_ID": "res_456",
- "FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_MCP_URL": "https://legacy-env.com/",
- },
- ):
- provider = ScalekitProvider()
-
- assert str(provider.base_url) == "https://legacy-env.com/"
-
def test_environment_variable_loading(self):
"""Test that environment variables are loaded correctly."""
provider = ScalekitProvider(
diff --git a/tests/server/auth/providers/test_supabase.py b/tests/server/auth/providers/test_supabase.py
index 6799d6d000..1abe4e9a09 100644
--- a/tests/server/auth/providers/test_supabase.py
+++ b/tests/server/auth/providers/test_supabase.py
@@ -1,8 +1,6 @@
"""Tests for Supabase Auth provider."""
-import os
from collections.abc import Generator
-from unittest.mock import patch
import httpx
import pytest
@@ -27,27 +25,6 @@ def test_init_with_explicit_params(self):
assert provider.project_url == "https://abc123.supabase.co"
assert str(provider.base_url) == "https://myserver.com/"
- @pytest.mark.parametrize(
- "scopes_env",
- [
- "openid,email",
- '["openid", "email"]',
- ],
- )
- def test_init_with_env_vars(self, scopes_env):
- """Test SupabaseProvider initialization from environment variables."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_SUPABASE_PROJECT_URL": "https://env123.supabase.co",
- "FASTMCP_SERVER_AUTH_SUPABASE_BASE_URL": "https://envserver.com",
- },
- ):
- provider = SupabaseProvider()
-
- assert provider.project_url == "https://env123.supabase.co"
- assert str(provider.base_url) == "https://envserver.com/"
-
def test_environment_variable_loading(self):
"""Test that environment variables are loaded correctly."""
provider = SupabaseProvider(
@@ -139,20 +116,16 @@ def test_algorithm_default_es256(self):
assert isinstance(provider.token_verifier, JWTVerifier)
assert provider.token_verifier.algorithm == "ES256"
- def test_algorithm_from_env_var(self):
- """Test that algorithm can be configured via environment variable."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_SUPABASE_PROJECT_URL": "https://env123.supabase.co",
- "FASTMCP_SERVER_AUTH_SUPABASE_BASE_URL": "https://envserver.com",
- "FASTMCP_SERVER_AUTH_SUPABASE_ALGORITHM": "RS256",
- },
- ):
- provider = SupabaseProvider()
-
- assert isinstance(provider.token_verifier, JWTVerifier)
- assert provider.token_verifier.algorithm == "RS256"
+ def test_algorithm_from_parameter(self):
+ """Test that algorithm can be configured via parameter."""
+ provider = SupabaseProvider(
+ project_url="https://env123.supabase.co",
+ base_url="https://envserver.com",
+ algorithm="RS256",
+ )
+
+ assert isinstance(provider.token_verifier, JWTVerifier)
+ assert provider.token_verifier.algorithm == "RS256"
def run_mcp_server(host: str, port: int) -> None:
diff --git a/tests/server/auth/providers/test_workos.py b/tests/server/auth/providers/test_workos.py
index 8d477645fc..69ee180124 100644
--- a/tests/server/auth/providers/test_workos.py
+++ b/tests/server/auth/providers/test_workos.py
@@ -1,7 +1,5 @@
"""Tests for WorkOS OAuth provider."""
-import os
-from unittest.mock import patch
from urllib.parse import urlparse
import httpx
@@ -31,60 +29,6 @@ def test_init_with_explicit_params(self):
assert provider._upstream_client_secret.get_secret_value() == "secret_test456"
assert str(provider.base_url) == "https://myserver.com/"
- @pytest.mark.parametrize(
- "scopes_env",
- [
- "openid,email",
- '["openid", "email"]',
- ],
- )
- def test_init_with_env_vars(self, scopes_env):
- """Test WorkOSProvider initialization from environment variables."""
- with patch.dict(
- os.environ,
- {
- "FASTMCP_SERVER_AUTH_WORKOS_CLIENT_ID": "env_client",
- "FASTMCP_SERVER_AUTH_WORKOS_CLIENT_SECRET": "env_secret",
- "FASTMCP_SERVER_AUTH_WORKOS_AUTHKIT_DOMAIN": "https://env.authkit.app",
- "FASTMCP_SERVER_AUTH_WORKOS_BASE_URL": "https://envserver.com",
- "FASTMCP_SERVER_AUTH_WORKOS_REQUIRED_SCOPES": scopes_env,
- "FASTMCP_SERVER_AUTH_WORKOS_JWT_SIGNING_KEY": "test-secret",
- },
- ):
- provider = WorkOSProvider()
-
- assert provider._upstream_client_id == "env_client"
- assert provider._upstream_client_secret.get_secret_value() == "env_secret"
- assert str(provider.base_url) == "https://envserver.com/"
- assert provider._token_validator.required_scopes == [
- "openid",
- "email",
- ]
-
- def test_init_missing_client_id_raises_error(self):
- """Test that missing client_id raises ValueError."""
- with pytest.raises(ValueError, match="client_id is required"):
- WorkOSProvider(
- client_secret="test_secret",
- authkit_domain="https://test.authkit.app",
- )
-
- def test_init_missing_client_secret_raises_error(self):
- """Test that missing client_secret raises ValueError."""
- with pytest.raises(ValueError, match="client_secret is required"):
- WorkOSProvider(
- client_id="test_client",
- authkit_domain="https://test.authkit.app",
- )
-
- def test_init_missing_authkit_domain_raises_error(self):
- """Test that missing authkit_domain raises ValueError."""
- with pytest.raises(ValueError, match="authkit_domain is required"):
- WorkOSProvider(
- client_id="test_client",
- client_secret="test_secret",
- )
-
def test_authkit_domain_https_prefix_handling(self):
"""Test that authkit_domain handles missing https:// prefix."""
# Without https:// - should add it
@@ -126,17 +70,6 @@ def test_authkit_domain_https_prefix_handling(self):
assert parsed.netloc == "localhost:8080"
assert parsed.path == "/oauth2/authorize"
- def test_init_missing_base_url_raises_error(self):
- """Test that missing base_url raises ValueError."""
- with patch.dict(os.environ, {}, clear=True):
- with pytest.raises(ValueError, match="base_url is required"):
- WorkOSProvider(
- client_id="test_client",
- client_secret="test_secret",
- authkit_domain="https://test.authkit.app",
- jwt_signing_key="test-secret",
- )
-
def test_init_defaults(self):
"""Test that default values are applied correctly."""
provider = WorkOSProvider(
From dbc38d4df7de8bcd51d360200a3da049f268829a Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Fri, 26 Dec 2025 13:57:29 -0500
Subject: [PATCH 2/7] Remove env var settings from remaining auth providers and
settings
---
...cp-server-auth-providers-introspection.mdx | 6 --
.../fastmcp-server-auth-providers-jwt.mdx | 6 --
docs/python-sdk/fastmcp-settings.mdx | 6 --
src/fastmcp/server/auth/providers/azure.py | 15 +--
.../server/auth/providers/introspection.py | 85 ++++-------------
src/fastmcp/server/auth/providers/jwt.py | 95 ++++++-------------
src/fastmcp/server/auth/providers/oci.py | 40 ++++----
src/fastmcp/server/server.py | 12 +--
src/fastmcp/settings.py | 57 +----------
tests/deprecated/test_settings.py | 2 -
.../auth/providers/test_introspection.py | 56 ++---------
tests/server/test_server.py | 26 +----
12 files changed, 87 insertions(+), 319 deletions(-)
diff --git a/docs/python-sdk/fastmcp-server-auth-providers-introspection.mdx b/docs/python-sdk/fastmcp-server-auth-providers-introspection.mdx
index 1d7eeda223..603e537f69 100644
--- a/docs/python-sdk/fastmcp-server-auth-providers-introspection.mdx
+++ b/docs/python-sdk/fastmcp-server-auth-providers-introspection.mdx
@@ -31,12 +31,6 @@ Example:
## Classes
-### `IntrospectionTokenVerifierSettings`
-
-
-Settings for OAuth 2.0 Token Introspection verification.
-
-
### `IntrospectionTokenVerifier`
diff --git a/docs/python-sdk/fastmcp-server-auth-providers-jwt.mdx b/docs/python-sdk/fastmcp-server-auth-providers-jwt.mdx
index 0c7cf0c93c..610c658a47 100644
--- a/docs/python-sdk/fastmcp-server-auth-providers-jwt.mdx
+++ b/docs/python-sdk/fastmcp-server-auth-providers-jwt.mdx
@@ -60,12 +60,6 @@ Generate a test JWT token for testing purposes.
- `kid`: Key ID to include in header
-### `JWTVerifierSettings`
-
-
-Settings for JWT token verification.
-
-
### `JWTVerifier`
diff --git a/docs/python-sdk/fastmcp-settings.mdx b/docs/python-sdk/fastmcp-settings.mdx
index 3988965497..21260703cd 100644
--- a/docs/python-sdk/fastmcp-settings.mdx
+++ b/docs/python-sdk/fastmcp-settings.mdx
@@ -48,9 +48,3 @@ treated as a nested setting.
```python
normalize_log_level(cls, v)
```
-
-#### `server_auth_class`
-
-```python
-server_auth_class(self) -> AuthProvider | None
-```
diff --git a/src/fastmcp/server/auth/providers/azure.py b/src/fastmcp/server/auth/providers/azure.py
index 514cae4010..ebedd8d0b9 100644
--- a/src/fastmcp/server/auth/providers/azure.py
+++ b/src/fastmcp/server/auth/providers/azure.py
@@ -168,13 +168,14 @@ def __init__(
# NOT standard OIDC scopes (openid, profile, email, offline_access).
# Filter out OIDC scopes from validation - they'll still be sent to Azure
# during authorization (handled by _prefix_scopes_for_azure).
- validation_scopes = (
- [s for s in parsed_required_scopes if s not in OIDC_SCOPES]
- if parsed_required_scopes
- else None
- )
- # If all scopes were OIDC scopes, use None (no scope validation)
- if validation_scopes == []:
+ if parsed_required_scopes:
+ validation_scopes = [
+ s for s in parsed_required_scopes if s not in OIDC_SCOPES
+ ]
+ # If all scopes were OIDC scopes, use None (no scope validation)
+ if not validation_scopes:
+ validation_scopes = None
+ else:
validation_scopes = None
token_verifier = JWTVerifier(
diff --git a/src/fastmcp/server/auth/providers/introspection.py b/src/fastmcp/server/auth/providers/introspection.py
index b890a90464..ec1ad6b95f 100644
--- a/src/fastmcp/server/auth/providers/introspection.py
+++ b/src/fastmcp/server/auth/providers/introspection.py
@@ -28,40 +28,15 @@
from typing import Any
import httpx
-from pydantic import AnyHttpUrl, SecretStr, field_validator
-from pydantic_settings import BaseSettings, SettingsConfigDict
+from pydantic import AnyHttpUrl, SecretStr
from fastmcp.server.auth import AccessToken, TokenVerifier
-from fastmcp.settings import ENV_FILE
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
-from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__)
-class IntrospectionTokenVerifierSettings(BaseSettings):
- """Settings for OAuth 2.0 Token Introspection verification."""
-
- model_config = SettingsConfigDict(
- env_prefix="FASTMCP_SERVER_AUTH_INTROSPECTION_",
- env_file=ENV_FILE,
- extra="ignore",
- )
-
- introspection_url: str | None = None
- client_id: str | None = None
- client_secret: SecretStr | None = None
- timeout_seconds: int = 10
- required_scopes: list[str] | None = None
- base_url: AnyHttpUrl | str | None = None
-
- @field_validator("required_scopes", mode="before")
- @classmethod
- def _parse_scopes(cls, v):
- return parse_scopes(v)
-
-
class IntrospectionTokenVerifier(TokenVerifier):
"""
OAuth 2.0 Token Introspection verifier (RFC 7662).
@@ -93,12 +68,12 @@ class IntrospectionTokenVerifier(TokenVerifier):
def __init__(
self,
*,
- introspection_url: str | NotSetT = NotSet,
- client_id: str | NotSetT = NotSet,
- client_secret: str | NotSetT = NotSet,
- timeout_seconds: int | NotSetT = NotSet,
- required_scopes: list[str] | NotSetT | None = NotSet,
- base_url: AnyHttpUrl | str | NotSetT | None = NotSet,
+ introspection_url: str,
+ client_id: str,
+ client_secret: str | SecretStr,
+ timeout_seconds: int = 10,
+ required_scopes: list[str] | None = None,
+ base_url: AnyHttpUrl | str | None = None,
):
"""
Initialize the introspection token verifier.
@@ -111,45 +86,21 @@ def __init__(
required_scopes: Required scopes for all tokens (optional)
base_url: Base URL for TokenVerifier protocol
"""
- settings = IntrospectionTokenVerifierSettings.model_validate(
- {
- k: v
- for k, v in {
- "introspection_url": introspection_url,
- "client_id": client_id,
- "client_secret": client_secret,
- "timeout_seconds": timeout_seconds,
- "required_scopes": required_scopes,
- "base_url": base_url,
- }.items()
- if v is not NotSet
- }
+ # Parse scopes if provided as string
+ parsed_required_scopes = (
+ parse_scopes(required_scopes) if required_scopes is not None else None
)
- if not settings.introspection_url:
- raise ValueError(
- "introspection_url is required - set via parameter or "
- "FASTMCP_SERVER_AUTH_INTROSPECTION_INTROSPECTION_URL"
- )
- if not settings.client_id:
- raise ValueError(
- "client_id is required - set via parameter or "
- "FASTMCP_SERVER_AUTH_INTROSPECTION_CLIENT_ID"
- )
- if not settings.client_secret:
- raise ValueError(
- "client_secret is required - set via parameter or "
- "FASTMCP_SERVER_AUTH_INTROSPECTION_CLIENT_SECRET"
- )
+ super().__init__(base_url=base_url, required_scopes=parsed_required_scopes)
- super().__init__(
- base_url=settings.base_url, required_scopes=settings.required_scopes
+ self.introspection_url = introspection_url
+ self.client_id = client_id
+ self.client_secret = (
+ client_secret.get_secret_value()
+ if isinstance(client_secret, SecretStr)
+ else client_secret
)
-
- self.introspection_url = settings.introspection_url
- self.client_id = settings.client_id
- self.client_secret = settings.client_secret.get_secret_value()
- self.timeout_seconds = settings.timeout_seconds
+ self.timeout_seconds = timeout_seconds
self.logger = get_logger(__name__)
def _create_basic_auth_header(self) -> str:
diff --git a/src/fastmcp/server/auth/providers/jwt.py b/src/fastmcp/server/auth/providers/jwt.py
index 1d6f4baa4c..fa84224f50 100644
--- a/src/fastmcp/server/auth/providers/jwt.py
+++ b/src/fastmcp/server/auth/providers/jwt.py
@@ -11,15 +11,12 @@
from authlib.jose.errors import JoseError
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
-from pydantic import AnyHttpUrl, SecretStr, field_validator
-from pydantic_settings import BaseSettings, SettingsConfigDict
+from pydantic import AnyHttpUrl, SecretStr
from typing_extensions import TypedDict
from fastmcp.server.auth import AccessToken, TokenVerifier
-from fastmcp.settings import ENV_FILE
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
-from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__)
@@ -139,29 +136,6 @@ def create_token(
return token_bytes.decode("utf-8")
-class JWTVerifierSettings(BaseSettings):
- """Settings for JWT token verification."""
-
- model_config = SettingsConfigDict(
- env_prefix="FASTMCP_SERVER_AUTH_JWT_",
- env_file=ENV_FILE,
- extra="ignore",
- )
-
- public_key: str | None = None
- jwks_uri: str | None = None
- issuer: str | list[str] | None = None
- algorithm: str | None = None
- audience: str | list[str] | None = None
- required_scopes: list[str] | None = None
- base_url: AnyHttpUrl | str | None = None
-
- @field_validator("required_scopes", mode="before")
- @classmethod
- def _parse_scopes(cls, v):
- return parse_scopes(v)
-
-
class JWTVerifier(TokenVerifier):
"""
JWT token verifier supporting both asymmetric (RSA/ECDSA) and symmetric (HMAC) algorithms.
@@ -184,52 +158,36 @@ class JWTVerifier(TokenVerifier):
def __init__(
self,
*,
- public_key: str | NotSetT | None = NotSet,
- jwks_uri: str | NotSetT | None = NotSet,
- issuer: str | list[str] | NotSetT | None = NotSet,
- audience: str | list[str] | NotSetT | None = NotSet,
- algorithm: str | NotSetT | None = NotSet,
- required_scopes: list[str] | NotSetT | None = NotSet,
- base_url: AnyHttpUrl | str | NotSetT | None = NotSet,
+ public_key: str | None = None,
+ jwks_uri: str | None = None,
+ issuer: str | list[str] | None = None,
+ audience: str | list[str] | None = None,
+ algorithm: str | None = None,
+ required_scopes: list[str] | None = None,
+ base_url: AnyHttpUrl | str | None = None,
):
"""
Initialize a JWTVerifier configured to validate JWTs using either a static key or a JWKS endpoint.
Parameters:
- public_key (str | NotSetT | None): PEM-encoded public key for asymmetric algorithms or shared secret for symmetric algorithms.
- jwks_uri (str | NotSetT | None): URI to fetch a JSON Web Key Set; used when verifying tokens with remote JWKS.
- issuer (str | list[str] | NotSetT | None): Expected issuer claim value or list of allowed issuer values.
- audience (str | list[str] | NotSetT | None): Expected audience claim value or list of allowed audience values.
- algorithm (str | NotSetT | None): JWT signing algorithm to accept (default: "RS256"). Supported: HS256/384/512, RS256/384/512, ES256/384/512, PS256/384/512.
- required_scopes (list[str] | NotSetT | None): Scopes that must be present in validated tokens.
- base_url (AnyHttpUrl | str | NotSetT | None): Base URL passed to the parent TokenVerifier.
+ public_key: PEM-encoded public key for asymmetric algorithms or shared secret for symmetric algorithms.
+ jwks_uri: URI to fetch a JSON Web Key Set; used when verifying tokens with remote JWKS.
+ issuer: Expected issuer claim value or list of allowed issuer values.
+ audience: Expected audience claim value or list of allowed audience values.
+ algorithm: JWT signing algorithm to accept (default: "RS256"). Supported: HS256/384/512, RS256/384/512, ES256/384/512, PS256/384/512.
+ required_scopes: Scopes that must be present in validated tokens.
+ base_url: Base URL passed to the parent TokenVerifier.
Raises:
ValueError: If neither or both of `public_key` and `jwks_uri` are provided, or if `algorithm` is unsupported.
"""
- settings = JWTVerifierSettings.model_validate(
- {
- k: v
- for k, v in {
- "public_key": public_key,
- "jwks_uri": jwks_uri,
- "issuer": issuer,
- "audience": audience,
- "algorithm": algorithm,
- "required_scopes": required_scopes,
- "base_url": base_url,
- }.items()
- if v is not NotSet
- }
- )
-
- if not settings.public_key and not settings.jwks_uri:
+ if not public_key and not jwks_uri:
raise ValueError("Either public_key or jwks_uri must be provided")
- if settings.public_key and settings.jwks_uri:
+ if public_key and jwks_uri:
raise ValueError("Provide either public_key or jwks_uri, not both")
- algorithm = settings.algorithm or "RS256"
+ algorithm = algorithm or "RS256"
if algorithm not in {
"HS256",
"HS384",
@@ -246,17 +204,22 @@ def __init__(
}:
raise ValueError(f"Unsupported algorithm: {algorithm}.")
+ # Parse scopes if provided as string
+ parsed_required_scopes = (
+ parse_scopes(required_scopes) if required_scopes is not None else None
+ )
+
# Initialize parent TokenVerifier
super().__init__(
- base_url=settings.base_url,
- required_scopes=settings.required_scopes,
+ base_url=base_url,
+ required_scopes=parsed_required_scopes,
)
self.algorithm = algorithm
- self.issuer = settings.issuer
- self.audience = settings.audience
- self.public_key = settings.public_key
- self.jwks_uri = settings.jwks_uri
+ self.issuer = issuer
+ self.audience = audience
+ self.public_key = public_key
+ self.jwks_uri = jwks_uri
self.jwt = JsonWebToken([self.algorithm])
self.logger = get_logger(__name__)
diff --git a/src/fastmcp/server/auth/providers/oci.py b/src/fastmcp/server/auth/providers/oci.py
index 9ca4eb24f1..c884b6c2c1 100644
--- a/src/fastmcp/server/auth/providers/oci.py
+++ b/src/fastmcp/server/auth/providers/oci.py
@@ -19,22 +19,22 @@
import os
- # Load configuration from environment
- FASTMCP_SERVER_AUTH_OCI_CONFIG_URL = os.environ["FASTMCP_SERVER_AUTH_OCI_CONFIG_URL"]
- FASTMCP_SERVER_AUTH_OCI_CLIENT_ID = os.environ["FASTMCP_SERVER_AUTH_OCI_CLIENT_ID"]
- FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET = os.environ["FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET"]
- FASTMCP_SERVER_AUTH_OCI_IAM_GUID = os.environ["FASTMCP_SERVER_AUTH_OCI_IAM_GUID"]
-
import oci
from oci.auth.signers import TokenExchangeSigner
logger = get_logger(__name__)
+ # Load configuration from environment
+ config_url = os.environ.get("OCI_CONFIG_URL") # OCI IAM Domain OIDC discovery URL
+ client_id = os.environ.get("OCI_CLIENT_ID") # Client ID configured for the OCI IAM Domain Integrated Application
+ client_secret = os.environ.get("OCI_CLIENT_SECRET") # Client secret configured for the OCI IAM Domain Integrated Application
+ iam_guid = os.environ.get("OCI_IAM_GUID") # IAM GUID configured for the OCI IAM Domain
+
# Simple OCI OIDC protection
auth = OCIProvider(
- config_url=FASTMCP_SERVER_AUTH_OCI_CONFIG_URL, #config URL is the OCI IAM Domain OIDC discovery URL.
- client_id=FASTMCP_SERVER_AUTH_OCI_CLIENT_ID, #This is same as the client ID configured for the OCI IAM Domain Integrated Application
- client_secret=FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET, #This is same as the client secret configured for the OCI IAM Domain Integrated Application
+ config_url=config_url, # config URL is the OCI IAM Domain OIDC discovery URL
+ client_id=client_id, # This is same as the client ID configured for the OCI IAM Domain Integrated Application
+ client_secret=client_secret, # This is same as the client secret configured for the OCI IAM Domain Integrated Application
required_scopes=["openid", "profile", "email"],
redirect_path="/auth/callback",
base_url="http://localhost:8000",
@@ -42,7 +42,7 @@
# NOTE: For production use, replace this with a thread-safe cache implementation
# such as threading.Lock-protected dict or a proper caching library
- _global_token_cache = {} #In memory cache for OCI session token signer
+ _global_token_cache = {} # In memory cache for OCI session token signer
def get_oci_signer() -> TokenExchangeSigner:
@@ -50,20 +50,20 @@ def get_oci_signer() -> TokenExchangeSigner:
tokenID = authntoken.claims.get("jti")
token = authntoken.token
- #Check if the signer exists for the token ID in memory cache
+ # Check if the signer exists for the token ID in memory cache
cached_signer = _global_token_cache.get(tokenID)
logger.debug(f"Global cached signer: {cached_signer}")
if cached_signer:
logger.debug(f"Using globally cached signer for token ID: {tokenID}")
return cached_signer
- #If the signer is not yet created for the token then create new OCI signer object
+ # If the signer is not yet created for the token then create new OCI signer object
logger.debug(f"Creating new signer for token ID: {tokenID}")
signer = TokenExchangeSigner(
jwt_or_func=token,
- oci_domain_id=FASTMCP_SERVER_AUTH_OCI_IAM_GUID.split(".")[0], #This is same as IAM GUID configured for the OCI IAM Domain
- client_id=FASTMCP_SERVER_AUTH_OCI_CLIENT_ID, #This is same as the client ID configured for the OCI IAM Domain Integrated Application
- client_secret=FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET #This is same as the client secret configured for the OCI IAM Domain Integrated Application
+ oci_domain_id=iam_guid.split(".")[0] if iam_guid else None, # This is same as IAM GUID configured for the OCI IAM Domain
+ client_id=client_id, # This is same as the client ID configured for the OCI IAM Domain Integrated Application
+ client_secret=client_secret, # This is same as the client secret configured for the OCI IAM Domain Integrated Application
)
logger.debug(f"Signer {signer} created for token ID: {tokenID}")
@@ -98,11 +98,13 @@ class OCIProvider(OIDCProxy):
from fastmcp import FastMCP
from fastmcp.server.auth.providers.oci import OCIProvider
- # Simple OCI OIDC protection
+ import os
+
+ # Load configuration from environment
auth = OCIProvider(
- config_url=FASTMCP_SERVER_AUTH_OCI_CONFIG_URL, #config URL is the OCI IAM Domain OIDC discovery URL.
- client_id=FASTMCP_SERVER_AUTH_OCI_CLIENT_ID, #This is same as the client ID configured for the OCI IAM Domain Integrated Application
- client_secret=FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET, #This is same as the client secret configured for the OCI IAM Domain Integrated Application
+ config_url=os.environ.get("OCI_CONFIG_URL"), # OCI IAM Domain OIDC discovery URL
+ client_id=os.environ.get("OCI_CLIENT_ID"), # Client ID configured for the OCI IAM Domain Integrated Application
+ client_secret=os.environ.get("OCI_CLIENT_SECRET"), # Client secret configured for the OCI IAM Domain Integrated Application
base_url="http://localhost:8000",
required_scopes=["openid", "profile", "email"],
redirect_path="/auth/callback",
diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py
index e4b3fde2dc..7aaa3e1717 100644
--- a/src/fastmcp/server/server.py
+++ b/src/fastmcp/server/server.py
@@ -199,7 +199,7 @@ def __init__(
version: str | None = None,
website_url: str | None = None,
icons: list[mcp.types.Icon] | None = None,
- auth: AuthProvider | NotSetT | None = NotSet,
+ auth: AuthProvider | None = None,
middleware: Sequence[Middleware] | None = None,
providers: Sequence[Provider] | None = None,
lifespan: LifespanCallable | None = None,
@@ -289,16 +289,6 @@ def __init__(
lifespan=_lifespan_proxy(fastmcp_server=self),
)
- # if auth is `NotSet`, try to create a provider from the environment
- if auth is NotSet:
- if fastmcp.settings.server_auth is not None:
- # server_auth_class returns the class itself, not an instance
- auth_class = cast(
- type[AuthProvider], fastmcp.settings.server_auth_class
- )
- auth = auth_class()
- else:
- auth = None
self.auth: AuthProvider | None = auth
if tools:
diff --git a/src/fastmcp/settings.py b/src/fastmcp/settings.py
index fc19403211..0d8af81397 100644
--- a/src/fastmcp/settings.py
+++ b/src/fastmcp/settings.py
@@ -5,10 +5,10 @@
import warnings
from datetime import timedelta
from pathlib import Path
-from typing import TYPE_CHECKING, Annotated, Any, Literal
+from typing import Annotated, Any, Literal
from platformdirs import user_data_dir
-from pydantic import Field, ImportString, field_validator
+from pydantic import Field, field_validator
from pydantic_settings import (
BaseSettings,
SettingsConfigDict,
@@ -26,9 +26,6 @@
TEN_MB_IN_BYTES = 1024 * 1024 * 10
-if TYPE_CHECKING:
- from fastmcp.server.auth.auth import AuthProvider
-
class DocketSettings(BaseSettings):
"""Docket worker configuration."""
@@ -296,37 +293,6 @@ def normalize_log_level(cls, v):
False # If True, uses true stateless mode (new transport per request)
)
- # Auth settings
- server_auth: Annotated[
- str | None,
- Field(
- description=inspect.cleandoc(
- """
- Configure the authentication provider for the server by specifying
- the full module path to an AuthProvider class (e.g.,
- 'fastmcp.server.auth.providers.google.GoogleProvider').
-
- The specified class will be imported and instantiated automatically
- during FastMCP server creation. Any class that inherits from AuthProvider
- can be used, including custom implementations.
-
- If None, no automatic configuration will take place.
-
- This setting is *always* overridden by any auth provider passed to the
- FastMCP constructor.
-
- Note that most auth providers require additional configuration
- that must be provided via env vars.
-
- Examples:
- - fastmcp.server.auth.providers.google.GoogleProvider
- - fastmcp.server.auth.providers.jwt.JWTVerifier
- - mycompany.auth.CustomAuthProvider
- """
- ),
- ),
- ] = None
-
include_tags: Annotated[
set[str] | None,
Field(
@@ -391,22 +357,3 @@ def normalize_log_level(cls, v):
),
),
] = True
-
- @property
- def server_auth_class(self) -> AuthProvider | None:
- from fastmcp.utilities.types import get_cached_typeadapter
-
- if not self.server_auth:
- return None
-
- # https://github.com/jlowin/fastmcp/issues/1749
- # Pydantic imports the module in an ImportString during model validation, but we don't want the server
- # auth module imported during settings creation as it imports dependencies we aren't ready for yet.
- # To fix this while limiting breaking changes, we delay the import by only creating the ImportString
- # when the class is actually needed
-
- type_adapter = get_cached_typeadapter(ImportString)
-
- auth_class = type_adapter.validate_python(self.server_auth)
-
- return auth_class
diff --git a/tests/deprecated/test_settings.py b/tests/deprecated/test_settings.py
index 3766c878d5..301abf4102 100644
--- a/tests/deprecated/test_settings.py
+++ b/tests/deprecated/test_settings.py
@@ -248,7 +248,6 @@ def test_deprecated_settings_inheritance_from_global(self):
"json_response": True,
"stateless_http": True,
}
- mock_settings.server_auth = None # Add server_auth attribute
server = FastMCP("TestServer")
@@ -278,7 +277,6 @@ def test_deprecated_settings_override_global(self):
"json_response": True,
"stateless_http": True,
}
- mock_settings.server_auth = None # Add server_auth attribute
with warnings.catch_warnings():
warnings.simplefilter("ignore") # Ignore warnings for this test
diff --git a/tests/server/auth/providers/test_introspection.py b/tests/server/auth/providers/test_introspection.py
index c60bef9d1f..c0c2c94932 100644
--- a/tests/server/auth/providers/test_introspection.py
+++ b/tests/server/auth/providers/test_introspection.py
@@ -5,57 +5,13 @@
from typing import Any
import pytest
-from pydantic import SecretStr
from pytest_httpx import HTTPXMock
from fastmcp.server.auth.providers.introspection import (
IntrospectionTokenVerifier,
- IntrospectionTokenVerifierSettings,
)
-class TestIntrospectionTokenVerifierSettings:
- """Test settings loading and validation."""
-
- def test_settings_from_parameters(self):
- """Test creating settings from parameters."""
- settings = IntrospectionTokenVerifierSettings(
- introspection_url="https://auth.example.com/introspect",
- client_id="test-client",
- client_secret=SecretStr("test-secret"),
- timeout_seconds=5,
- required_scopes=["read", "write"],
- )
-
- assert settings.introspection_url == "https://auth.example.com/introspect"
- assert settings.client_id == "test-client"
- assert settings.client_secret
- assert settings.client_secret.get_secret_value() == "test-secret"
- assert settings.timeout_seconds == 5
- assert settings.required_scopes == ["read", "write"]
-
- def test_settings_default_timeout(self):
- """Test default timeout value."""
- settings = IntrospectionTokenVerifierSettings(
- introspection_url="https://auth.example.com/introspect",
- client_id="test-client",
- client_secret=SecretStr("test-secret"),
- )
-
- assert settings.timeout_seconds == 10
-
- def test_settings_parse_scopes_from_string(self):
- """Test scope parsing from comma-separated string."""
- settings = IntrospectionTokenVerifierSettings(
- introspection_url="https://auth.example.com/introspect",
- client_id="test-client",
- client_secret=SecretStr("test-secret"),
- required_scopes="read,write,admin", # type: ignore
- )
-
- assert settings.required_scopes == ["read", "write", "admin"]
-
-
class TestIntrospectionTokenVerifier:
"""Test core token verification logic."""
@@ -94,24 +50,24 @@ def test_initialization(self):
def test_initialization_requires_introspection_url(self):
"""Test that introspection_url is required."""
- with pytest.raises(ValueError, match="introspection_url is required"):
- IntrospectionTokenVerifier(
+ with pytest.raises(TypeError):
+ IntrospectionTokenVerifier( # ty: ignore[missing-argument]
client_id="test-client",
client_secret="test-secret",
)
def test_initialization_requires_client_id(self):
"""Test that client_id is required."""
- with pytest.raises(ValueError, match="client_id is required"):
- IntrospectionTokenVerifier(
+ with pytest.raises(TypeError):
+ IntrospectionTokenVerifier( # ty: ignore[missing-argument]
introspection_url="https://auth.example.com/oauth/introspect",
client_secret="test-secret",
)
def test_initialization_requires_client_secret(self):
"""Test that client_secret is required."""
- with pytest.raises(ValueError, match="client_secret is required"):
- IntrospectionTokenVerifier(
+ with pytest.raises(TypeError):
+ IntrospectionTokenVerifier( # ty: ignore[missing-argument]
introspection_url="https://auth.example.com/oauth/introspect",
client_id="test-client",
)
diff --git a/tests/server/test_server.py b/tests/server/test_server.py
index 725e2a0419..2ba050b994 100644
--- a/tests/server/test_server.py
+++ b/tests/server/test_server.py
@@ -169,25 +169,11 @@ def get_template_resource(param: str):
class TestSettingsFromEnvironment:
- async def test_settings_from_environment_issue_1749(self):
- """Test that when auth is enabled, the server starts."""
+ async def test_server_starts_without_auth(self):
+ """Test that server starts without auth configured."""
from fastmcp.client.transports import PythonStdioTransport
- from fastmcp.server.auth.providers.azure import AzureProvider
- from fastmcp.settings import Settings
script = dedent("""
- import os
-
- os.environ["FASTMCP_SERVER_AUTH"] = "fastmcp.server.auth.providers.azure.AzureProvider"
-
- os.environ["FASTMCP_SERVER_AUTH_AZURE_TENANT_ID"] = "A_Valid_Value"
- os.environ["FASTMCP_SERVER_AUTH_AZURE_CLIENT_ID"] = "A_Valid_Value"
- os.environ["FASTMCP_SERVER_AUTH_AZURE_CLIENT_SECRET"] = "A_Valid_Value"
- os.environ["FASTMCP_SERVER_AUTH_AZURE_REDIRECT_PATH"] = "/auth/callback"
- os.environ["FASTMCP_SERVER_AUTH_AZURE_BASE_URL"] = "http://localhost:8000"
- os.environ["FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES"] = "User.Read,email,profile"
- os.environ["FASTMCP_SERVER_AUTH_AZURE_JWT_SIGNING_KEY"] = "test-secret"
-
import fastmcp
mcp = fastmcp.FastMCP("TestServer")
@@ -208,14 +194,6 @@ async def test_settings_from_environment_issue_1749(self):
assert tools == []
- settings = Settings(
- server_auth="fastmcp.server.auth.providers.azure.AzureProvider"
- )
-
- auth_class = settings.server_auth_class
-
- assert auth_class is AzureProvider
-
class TestAbstractCollectionTypes:
"""Test that FastMCP accepts abstract collection types from collections.abc."""
From ebe1b870503e21cd3dbf5f544e95ff1084ec7391 Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Fri, 26 Dec 2025 13:58:34 -0500
Subject: [PATCH 3/7] Document auth provider env var removal in upgrade guide
---
docs/development/upgrade-guide.mdx | 25 +++++++
.../v3-notes/auth-provider-env-vars.mdx | 71 +++++++++++++++++++
2 files changed, 96 insertions(+)
create mode 100644 docs/development/v3-notes/auth-provider-env-vars.mdx
diff --git a/docs/development/upgrade-guide.mdx b/docs/development/upgrade-guide.mdx
index cd4a672e18..8bfcfbbdd2 100644
--- a/docs/development/upgrade-guide.mdx
+++ b/docs/development/upgrade-guide.mdx
@@ -150,6 +150,31 @@ def my_prompt() -> Message:
See [Prompts documentation](/servers/prompts#return-values) for full details.
+### Auth Provider Environment Variables Removed
+
+Auth providers no longer automatically read configuration from environment variables. All required parameters must be passed explicitly:
+
+
+```python Before
+# Relied on FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID, etc.
+auth = GitHubProvider()
+```
+
+```python After
+import os
+
+auth = GitHubProvider(
+ client_id=os.environ["GITHUB_CLIENT_ID"],
+ client_secret=os.environ["GITHUB_CLIENT_SECRET"],
+ base_url=os.environ["GITHUB_BASE_URL"],
+)
+```
+
+
+This applies to all auth providers: `GitHubProvider`, `GoogleProvider`, `AzureProvider`, `Auth0Provider`, `AWSProvider`, `WorkOSProvider`, `DescopeProvider`, `DiscordProvider`, `ScalekitProvider`, `SupabaseProvider`, `OCIProvider`, `JWTVerifier`, and `IntrospectionVerifier`.
+
+The `FastMCPSettings` class has also been simplified - it no longer includes auth-related settings that were previously loaded from environment variables.
+
## v2.14.0
### OpenAPI Parser Promotion
diff --git a/docs/development/v3-notes/auth-provider-env-vars.mdx b/docs/development/v3-notes/auth-provider-env-vars.mdx
new file mode 100644
index 0000000000..f62164ba4a
--- /dev/null
+++ b/docs/development/v3-notes/auth-provider-env-vars.mdx
@@ -0,0 +1,71 @@
+---
+title: Auth Provider Environment Variables
+---
+
+## Decision: Remove automatic environment variable loading from auth providers
+
+**Status:** Implemented in v3.0.0
+
+### Background
+
+Auth providers in v2.x used `pydantic-settings` to automatically load configuration from environment variables with a `FASTMCP_SERVER_AUTH__` prefix. For example, `GitHubProvider` would read from:
+
+- `FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID`
+- `FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET`
+- `FASTMCP_SERVER_AUTH_GITHUB_BASE_URL`
+- etc.
+
+This was implemented via a `*ProviderSettings(BaseSettings)` class in each provider, combined with a `NotSet` sentinel pattern to distinguish between "not provided" and `None`.
+
+### Why remove it
+
+1. **Maintenance burden**: Every new provider needed to implement the settings class, validators, and the `NotSet` merging logic. This was ~50-100 lines of boilerplate per provider.
+
+2. **Documentation complexity**: Each provider needed documentation explaining both the parameter and the corresponding environment variable. This doubled the surface area to document and maintain.
+
+3. **Contributor friction**: New contributors adding providers had to understand and replicate this pattern, which was a source of inconsistency and bugs.
+
+4. **Marginal user value**: Python developers are comfortable with `os.environ["VAR"]` or `os.environ.get("VAR", default)`. The automatic loading saved a single line of code per parameter while adding significant complexity.
+
+5. **Implicit behavior**: Magic environment variable loading makes it harder to understand where values come from. Explicit `os.environ` calls are more traceable.
+
+### Migration path
+
+The migration is trivial - users add explicit environment variable reads:
+
+```python
+# Before (v2.x)
+auth = GitHubProvider() # Relied on env vars
+
+# After (v3.0)
+import os
+
+auth = GitHubProvider(
+ client_id=os.environ["GITHUB_CLIENT_ID"],
+ client_secret=os.environ["GITHUB_CLIENT_SECRET"],
+ base_url=os.environ["MY_BASE_URL"],
+)
+```
+
+Users can also use `os.environ.get()` with defaults, or any other configuration library they prefer (dotenv, dynaconf, etc.).
+
+### Backwards compatibility
+
+We chose not to provide backwards compatibility because:
+
+1. This is a major version bump (v3.0), which is the appropriate time for breaking changes
+2. The migration is straightforward (add `os.environ` calls)
+3. Maintaining compatibility would require keeping all the boilerplate we're trying to remove
+4. The pattern was likely not heavily used - most production deployments pass secrets explicitly rather than relying on magic prefixes
+
+### What was removed
+
+- `*ProviderSettings(BaseSettings)` classes from all auth providers
+- `NotSet` sentinel usage in provider constructors
+- `pydantic-settings` dependency for auth providers
+- Environment variable documentation from provider docs
+- Related test cases for env var loading
+
+### Result
+
+Provider constructors are now simple and explicit. Required parameters are actually required (Python raises `TypeError` if missing), and optional parameters have clear defaults. The code is more readable and easier to maintain.
From 793165317970c6cfc062153ad1c8776b441e3320 Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Fri, 26 Dec 2025 13:59:03 -0500
Subject: [PATCH 4/7] Clarify env vars can still be used manually
---
docs/development/upgrade-guide.mdx | 4 ++--
docs/development/v3-notes/auth-provider-env-vars.mdx | 2 ++
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/docs/development/upgrade-guide.mdx b/docs/development/upgrade-guide.mdx
index 8bfcfbbdd2..c83b59909a 100644
--- a/docs/development/upgrade-guide.mdx
+++ b/docs/development/upgrade-guide.mdx
@@ -150,9 +150,9 @@ def my_prompt() -> Message:
See [Prompts documentation](/servers/prompts#return-values) for full details.
-### Auth Provider Environment Variables Removed
+### Auth Provider Automatic Environment Variable Loading Removed
-Auth providers no longer automatically read configuration from environment variables. All required parameters must be passed explicitly:
+Auth providers no longer automatically read configuration from environment variables. You can still use environment variables, but you must read them yourself:
```python Before
diff --git a/docs/development/v3-notes/auth-provider-env-vars.mdx b/docs/development/v3-notes/auth-provider-env-vars.mdx
index f62164ba4a..c61f61cbea 100644
--- a/docs/development/v3-notes/auth-provider-env-vars.mdx
+++ b/docs/development/v3-notes/auth-provider-env-vars.mdx
@@ -4,6 +4,8 @@ title: Auth Provider Environment Variables
## Decision: Remove automatic environment variable loading from auth providers
+You can still use environment variables for configuration - you just read them yourself with `os.environ` instead of relying on FastMCP's automatic loading.
+
**Status:** Implemented in v3.0.0
### Background
From 441b1f0a701a84a323974b0002425eea6cc08a00 Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Fri, 26 Dec 2025 14:13:37 -0500
Subject: [PATCH 5/7] Remove duplicate Production Configuration sections from
integration docs
---
docs/integrations/auth0.mdx | 33 ---------------------------
docs/integrations/aws-cognito.mdx | 38 -------------------------------
docs/integrations/azure.mdx | 28 -----------------------
docs/integrations/discord.mdx | 29 -----------------------
docs/integrations/github.mdx | 25 --------------------
docs/integrations/google.mdx | 27 +---------------------
docs/integrations/oci.mdx | 38 +------------------------------
docs/integrations/workos.mdx | 19 ----------------
8 files changed, 2 insertions(+), 235 deletions(-)
diff --git a/docs/integrations/auth0.mdx b/docs/integrations/auth0.mdx
index 77c00acb02..cb8b22cfb7 100644
--- a/docs/integrations/auth0.mdx
+++ b/docs/integrations/auth0.mdx
@@ -194,36 +194,3 @@ For complete details on these parameters, see the [OAuth Proxy documentation](/s
The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache.
-
-## Production Configuration
-
-For production deployments, load sensitive credentials from environment variables:
-
-```python server.py
-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://your-server.com")
-)
-
-mcp = FastMCP(name="Auth0 Secured App", auth=auth)
-
-```python server.py
-from fastmcp import FastMCP
-
-# Authentication is automatically configured from environment
-mcp = FastMCP(name="Auth0 Secured App")
-
-@mcp.tool
-async def search_logs() -> list[str]:
- """Search the service logs."""
- # Your tool implementation here
- pass
-```
diff --git a/docs/integrations/aws-cognito.mdx b/docs/integrations/aws-cognito.mdx
index 58c6c8fdc6..4c906995a5 100644
--- a/docs/integrations/aws-cognito.mdx
+++ b/docs/integrations/aws-cognito.mdx
@@ -238,44 +238,6 @@ Parameters (`jwt_signing_key` and `client_storage`) work together to ensure toke
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
-## Production Configuration
-
-For production deployments, load sensitive credentials from environment variables:
-
-```python server.py
-import os
-from fastmcp import FastMCP
-from fastmcp.server.auth.providers.aws import AWSCognitoProvider
-
-# Load secrets from environment variables
-auth = AWSCognitoProvider(
- user_pool_id=os.environ.get("AWS_COGNITO_USER_POOL_ID"),
- client_id=os.environ.get("AWS_COGNITO_CLIENT_ID"),
- client_secret=os.environ.get("AWS_COGNITO_CLIENT_SECRET"),
- base_url=os.environ.get("BASE_URL", "https://your-server.com"),
- aws_region=os.environ.get("AWS_REGION", "eu-central-1")
-)
-
-mcp = FastMCP(name="AWS Cognito Secured App", auth=auth)
-
-```python server.py
-from fastmcp import FastMCP
-from fastmcp.server.dependencies import get_access_token
-
-# Authentication is automatically configured from environment
-mcp = FastMCP(name="AWS Cognito Secured App")
-
-@mcp.tool
-async def get_access_token_claims() -> dict:
- """Get the authenticated user's access token claims."""
- token = get_access_token()
- return {
- "sub": token.claims.get("sub"),
- "username": token.claims.get("username"),
- "cognito:groups": token.claims.get("cognito:groups", []),
- }
-```
-
## Features
### JWT Token Validation
diff --git a/docs/integrations/azure.mdx b/docs/integrations/azure.mdx
index c27c8ba2c6..68d389e44e 100644
--- a/docs/integrations/azure.mdx
+++ b/docs/integrations/azure.mdx
@@ -279,31 +279,3 @@ Parameters (`jwt_signing_key` and `client_storage`) work together to ensure toke
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
-
-## Production Configuration
-
-For production deployments, load sensitive credentials from environment variables:
-
-```python server.py
-import os
-from fastmcp import FastMCP
-from fastmcp.server.auth.providers.azure import AzureProvider
-
-# Load secrets from environment variables
-auth = AzureProvider(
- client_id=os.environ.get("AZURE_CLIENT_ID"),
- client_secret=os.environ.get("AZURE_CLIENT_SECRET"),
- tenant_id=os.environ.get("AZURE_TENANT_ID"),
- required_scopes=["read", "write"], # At least one scope required
- base_url=os.environ.get("BASE_URL", "https://your-server.com")
-)
-
-mcp = FastMCP(name="Azure Secured App", auth=auth)
-
-@mcp.tool
-async def protected_tool(query: str) -> str:
- """A tool that requires Azure authentication to access."""
- # Your tool implementation here
- return f"Processing authenticated request: {query}"
-```
-
diff --git a/docs/integrations/discord.mdx b/docs/integrations/discord.mdx
index bffb628641..6864339a8e 100644
--- a/docs/integrations/discord.mdx
+++ b/docs/integrations/discord.mdx
@@ -182,32 +182,3 @@ Parameters (`jwt_signing_key` and `client_storage`) work together to ensure toke
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
-
-## Production Configuration
-
-For production deployments, load sensitive credentials from environment variables:
-
-```python server.py
-import os
-from fastmcp import FastMCP
-from fastmcp.server.auth.providers.discord import DiscordProvider
-
-# Load secrets from environment variables
-auth = DiscordProvider(
- client_id=os.environ.get("DISCORD_CLIENT_ID"),
- client_secret=os.environ.get("DISCORD_CLIENT_SECRET"),
- base_url=os.environ.get("BASE_URL", "https://your-server.com")
-)
-
-mcp = FastMCP(name="Discord Secured App", auth=auth)
-
-```python server.py
-from fastmcp import FastMCP
-
-mcp = FastMCP(name="Discord Secured App")
-
-@mcp.tool
-async def protected_tool(query: str) -> str:
- """A tool that requires Discord authentication to access."""
- return f"Processing authenticated request: {query}"
-```
diff --git a/docs/integrations/github.mdx b/docs/integrations/github.mdx
index 627db1173d..a49862cb0c 100644
--- a/docs/integrations/github.mdx
+++ b/docs/integrations/github.mdx
@@ -174,28 +174,3 @@ Parameters (`jwt_signing_key` and `client_storage`) work together to ensure toke
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
-
-## Production Configuration
-
-For production deployments, load sensitive credentials from environment variables:
-
-```python server.py
-import os
-from fastmcp import FastMCP
-from fastmcp.server.auth.providers.github import GitHubProvider
-
-# Load secrets from environment variables
-auth = GitHubProvider(
- client_id=os.environ.get("GITHUB_CLIENT_ID"),
- client_secret=os.environ.get("GITHUB_CLIENT_SECRET"),
- base_url=os.environ.get("BASE_URL", "https://your-server.com")
-)
-
-mcp = FastMCP(name="GitHub Secured App", auth=auth)
-
-@mcp.tool
-async def list_repos() -> list[str]:
- """List the authenticated user's repositories."""
- # Your tool implementation here
- pass
-```
diff --git a/docs/integrations/google.mdx b/docs/integrations/google.mdx
index f478b63dba..fcaf6dc126 100644
--- a/docs/integrations/google.mdx
+++ b/docs/integrations/google.mdx
@@ -187,29 +187,4 @@ mcp = FastMCP(name="Production Google App", auth=auth_provider)
Parameters (`jwt_signing_key` and `client_storage`) work together to ensure tokens and client registrations survive server restarts. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without it, tokens are stored in plaintext. Store secrets in environment variables and use a persistent storage backend like Redis for distributed deployments.
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
-
-
-## Production Configuration
-
-For production deployments, load sensitive credentials from environment variables:
-
-```python server.py
-import os
-from fastmcp import FastMCP
-from fastmcp.server.auth.providers.google import GoogleProvider
-
-# Load secrets from environment variables
-auth = GoogleProvider(
- client_id=os.environ.get("GOOGLE_CLIENT_ID"),
- client_secret=os.environ.get("GOOGLE_CLIENT_SECRET"),
- base_url=os.environ.get("BASE_URL", "https://your-server.com")
-)
-
-mcp = FastMCP(name="Google Secured App", auth=auth)
-
-@mcp.tool
-async def protected_tool(query: str) -> str:
- """A tool that requires Google authentication to access."""
- # Your tool implementation here
- return f"Processing authenticated request: {query}"
-```
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/integrations/oci.mdx b/docs/integrations/oci.mdx
index 924e1ace42..fec9cd38f5 100644
--- a/docs/integrations/oci.mdx
+++ b/docs/integrations/oci.mdx
@@ -246,40 +246,4 @@ For complete details on these parameters, see the [OAuth Proxy documentation](/s
The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache.
-
-
-## Production Configuration
-
-For production deployments, load sensitive credentials from environment variables:
-
-```python server.py
-import os
-from fastmcp import FastMCP
-from fastmcp.server.auth.providers.oci import OCIProvider
-
-# Load secrets from environment variables
-auth = OCIProvider(
- config_url=os.environ.get("OCI_CONFIG_URL"),
- client_id=os.environ.get("OCI_CLIENT_ID"),
- client_secret=os.environ.get("OCI_CLIENT_SECRET"),
- base_url=os.environ.get("BASE_URL", "https://your-server.com")
-)
-
-mcp = FastMCP(name="OCI Secured App", auth=auth)
-
-```python server.py
-from fastmcp import FastMCP
-from fastmcp.server.dependencies import get_access_token
-
-# Authentication is automatically configured from environment
-mcp = FastMCP(name="OCI Secured App")
-
-@mcp.tool
-def whoami() -> str:
- """The whoami function is to test MCP server without requiring token exchange.
- This tool can be used to test successful authentication against OCI IAM.
- It will return logged in user's subject (username from IAM domain)."""
- token = get_access_token()
- user = token.claims.get("sub")
- return f"You are User: {user}"
-```
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/integrations/workos.mdx b/docs/integrations/workos.mdx
index 3005e6934e..3e795515ce 100644
--- a/docs/integrations/workos.mdx
+++ b/docs/integrations/workos.mdx
@@ -168,25 +168,6 @@ Parameters (`jwt_signing_key` and `client_storage`) work together to ensure toke
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
-## Production Configuration
-
-For production deployments, load sensitive credentials from environment variables:
-
-```python server.py
-import os
-from fastmcp import FastMCP
-from fastmcp.server.auth.providers.workos import WorkOSProvider
-
-# Load secrets from environment variables
-auth = WorkOSProvider(
- client_id=os.environ.get("WORKOS_CLIENT_ID"),
- client_secret=os.environ.get("WORKOS_CLIENT_SECRET"),
- authkit_domain=os.environ.get("WORKOS_AUTHKIT_DOMAIN"),
- base_url=os.environ.get("BASE_URL", "https://your-server.com")
-)
-
-mcp = FastMCP(name="WorkOS Protected Server", auth=auth)
-
## Configuration Options
From e6d965566901b6191774c1e6fc9791533ba8277d Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Fri, 26 Dec 2025 14:21:20 -0500
Subject: [PATCH 6/7] Fix duplicate import os in OCI provider docs
---
docs/python-sdk/fastmcp-server-auth-providers-oci.mdx | 1 -
1 file changed, 1 deletion(-)
diff --git a/docs/python-sdk/fastmcp-server-auth-providers-oci.mdx b/docs/python-sdk/fastmcp-server-auth-providers-oci.mdx
index 133f836a63..9d767dae3a 100644
--- a/docs/python-sdk/fastmcp-server-auth-providers-oci.mdx
+++ b/docs/python-sdk/fastmcp-server-auth-providers-oci.mdx
@@ -27,7 +27,6 @@ Example:
import os
- import os
import oci
from oci.auth.signers import TokenExchangeSigner
From b96a00984a4fd4dbe0f249c209059c1f408c23ab Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Fri, 26 Dec 2025 15:17:18 -0500
Subject: [PATCH 7/7] Simplify scopes parsing in docs example
---
docs/servers/auth/token-verification.mdx | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/docs/servers/auth/token-verification.mdx b/docs/servers/auth/token-verification.mdx
index 7e93c129d3..89c48b07ac 100644
--- a/docs/servers/auth/token-verification.mdx
+++ b/docs/servers/auth/token-verification.mdx
@@ -313,11 +313,15 @@ from fastmcp import FastMCP
from fastmcp.server.auth.providers.jwt import JWTVerifier
# Load configuration from environment variables
+# Parse comma-separated scopes if provided
+scopes_env = os.environ.get("JWT_REQUIRED_SCOPES")
+required_scopes = scopes_env.split(",") if scopes_env else None
+
verifier = JWTVerifier(
jwks_uri=os.environ.get("JWT_JWKS_URI"),
issuer=os.environ.get("JWT_ISSUER"),
audience=os.environ.get("JWT_AUDIENCE"),
- required_scopes=os.environ.get("JWT_REQUIRED_SCOPES", "").split(",") if os.environ.get("JWT_REQUIRED_SCOPES") else None
+ required_scopes=required_scopes,
)
mcp = FastMCP(name="Production API", auth=verifier)