docs/development/v3-notes/auth-provider-env-vars.mdx
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
Auth providers in v2.x used pydantic-settings to automatically load configuration from environment variables with a FASTMCP_SERVER_AUTH_<PROVIDER>_ prefix. For example, GitHubProvider would read from:
FASTMCP_SERVER_AUTH_GITHUB_CLIENT_IDFASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRETFASTMCP_SERVER_AUTH_GITHUB_BASE_URLThis was implemented via a *ProviderSettings(BaseSettings) class in each provider, combined with a NotSet sentinel pattern to distinguish between "not provided" and None.
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.
Documentation complexity: Each provider needed documentation explaining both the parameter and the corresponding environment variable. This doubled the surface area to document and maintain.
Contributor friction: New contributors adding providers had to understand and replicate this pattern, which was a source of inconsistency and bugs.
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.
Implicit behavior: Magic environment variable loading makes it harder to understand where values come from. Explicit os.environ calls are more traceable.
The migration is trivial - users add explicit environment variable reads:
# 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.).
We chose not to provide backwards compatibility because:
os.environ calls)*ProviderSettings(BaseSettings) classes from all auth providersNotSet sentinel usage in provider constructorspydantic-settings dependency for auth providersProvider 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.