Skip to content

Commit 8d637b4

Browse files
committed
Pull all auth settings out into a separate config
1 parent d79be8f commit 8d637b4

File tree

3 files changed

+45
-30
lines changed

3 files changed

+45
-30
lines changed

src/mcp/server/auth/router.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
from dataclasses import dataclass
21
from typing import Callable
32

4-
from pydantic import AnyHttpUrl
3+
from pydantic import AnyHttpUrl, BaseModel, Field
54
from starlette.routing import Route
65

76
from mcp.server.auth.handlers.authorize import AuthorizationHandler
@@ -14,17 +13,29 @@
1413
from mcp.shared.auth import OAuthMetadata
1514

1615

17-
@dataclass
18-
class ClientRegistrationOptions:
16+
class ClientRegistrationOptions(BaseModel):
1917
enabled: bool = False
2018
client_secret_expiry_seconds: int | None = None
2119

2220

23-
@dataclass
24-
class RevocationOptions:
21+
class RevocationOptions(BaseModel):
2522
enabled: bool = False
2623

2724

25+
class AuthSettings(BaseModel):
26+
issuer_url: AnyHttpUrl = Field(
27+
...,
28+
description="URL advertised as OAuth issuer; this should be the URL the server "
29+
"is reachable at",
30+
)
31+
service_documentation_url: AnyHttpUrl | None = Field(
32+
None, description="Service documentation URL advertised by OAuth"
33+
)
34+
client_registration_options: ClientRegistrationOptions | None = None
35+
revocation_options: RevocationOptions | None = None
36+
required_scopes: list[str] | None = None
37+
38+
2839
def validate_issuer_url(url: AnyHttpUrl):
2940
"""
3041
Validate that the issuer URL meets OAuth 2.0 requirements.

src/mcp/server/fastmcp/server.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import anyio
1717
import pydantic_core
1818
import uvicorn
19-
from pydantic import AnyHttpUrl, BaseModel, Field
19+
from pydantic import BaseModel, Field
2020
from pydantic.networks import AnyUrl
2121
from pydantic_settings import BaseSettings, SettingsConfigDict
2222
from sse_starlette import EventSourceResponse
@@ -30,7 +30,9 @@
3030
RequireAuthMiddleware,
3131
)
3232
from mcp.server.auth.provider import OAuthServerProvider
33-
from mcp.server.auth.router import ClientRegistrationOptions, RevocationOptions
33+
from mcp.server.auth.router import (
34+
AuthSettings,
35+
)
3436
from mcp.server.fastmcp.exceptions import ResourceError
3537
from mcp.server.fastmcp.prompts import Prompt, PromptManager
3638
from mcp.server.fastmcp.resources import FunctionResource, Resource, ResourceManager
@@ -71,6 +73,8 @@ class Settings(BaseSettings, Generic[LifespanResultT]):
7173
model_config = SettingsConfigDict(
7274
env_prefix="FASTMCP_",
7375
env_file=".env",
76+
env_nested_delimiter="__",
77+
nested_model_default_partial_update=True,
7478
extra="ignore",
7579
)
7680

@@ -100,17 +104,7 @@ class Settings(BaseSettings, Generic[LifespanResultT]):
100104
Callable[["FastMCP"], AbstractAsyncContextManager[LifespanResultT]] | None
101105
) = Field(None, description="Lifespan context manager")
102106

103-
auth_issuer_url: AnyHttpUrl | None = Field(
104-
None,
105-
description="URL advertised as OAuth issuer; this should be the URL the server "
106-
"is reachable at",
107-
)
108-
auth_service_documentation_url: AnyHttpUrl | None = Field(
109-
None, description="Service documentation URL advertised by OAuth"
110-
)
111-
auth_client_registration_options: ClientRegistrationOptions | None = None
112-
auth_revocation_options: RevocationOptions | None = None
113-
auth_required_scopes: list[str] | None = None
107+
auth: AuthSettings | None = None
114108

115109

116110
def lifespan_wrapper(
@@ -151,6 +145,11 @@ def __init__(
151145
self._prompt_manager = PromptManager(
152146
warn_on_duplicate_prompts=self.settings.warn_on_duplicate_prompts
153147
)
148+
if (self.settings.auth is not None) != (auth_provider is not None):
149+
raise ValueError(
150+
"settings.auth must be specified if and only if auth_provider "
151+
"is specified"
152+
)
154153
self._auth_provider = auth_provider
155154
self._custom_starlette_routes = []
156155
self.dependencies = self.settings.dependencies
@@ -541,12 +540,15 @@ async def handle_sse(request) -> EventSourceResponse:
541540
# Create routes
542541
routes = []
543542
middleware = []
544-
required_scopes = self.settings.auth_required_scopes or []
543+
required_scopes = []
545544

546545
# Add auth endpoints if auth provider is configured
547-
if self._auth_provider and self.settings.auth_issuer_url:
546+
if self._auth_provider:
547+
assert self.settings.auth
548548
from mcp.server.auth.router import create_auth_routes
549549

550+
required_scopes = self.settings.auth.required_scopes or []
551+
550552
middleware = [
551553
# extract auth info from request (but do not require it)
552554
Middleware(
@@ -562,10 +564,10 @@ async def handle_sse(request) -> EventSourceResponse:
562564
routes.extend(
563565
create_auth_routes(
564566
provider=self._auth_provider,
565-
issuer_url=self.settings.auth_issuer_url,
566-
service_documentation_url=self.settings.auth_service_documentation_url,
567-
client_registration_options=self.settings.auth_client_registration_options,
568-
revocation_options=self.settings.auth_revocation_options,
567+
issuer_url=self.settings.auth.issuer_url,
568+
service_documentation_url=self.settings.auth.service_documentation_url,
569+
client_registration_options=self.settings.auth.client_registration_options,
570+
revocation_options=self.settings.auth.revocation_options,
569571
)
570572
)
571573

tests/server/fastmcp/auth/test_auth_integration.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from httpx_sse import aconnect_sse
1717
from pydantic import AnyHttpUrl
1818
from starlette.applications import Starlette
19-
from starlette.routing import Mount
2019

2120
from mcp.server.auth.provider import (
2221
AuthInfo,
@@ -28,6 +27,7 @@
2827
construct_redirect_uri,
2928
)
3029
from mcp.server.auth.router import (
30+
AuthSettings,
3131
ClientRegistrationOptions,
3232
RevocationOptions,
3333
create_auth_routes,
@@ -958,11 +958,13 @@ async def test_fastmcp_with_auth(
958958
# Create FastMCP server with auth provider
959959
mcp = FastMCP(
960960
auth_provider=mock_oauth_provider,
961-
auth_issuer_url="https://auth.example.com",
962961
require_auth=True,
963-
auth_client_registration_options=ClientRegistrationOptions(enabled=True),
964-
auth_revocation_options=RevocationOptions(enabled=True),
965-
auth_required_scopes=["read"],
962+
auth=AuthSettings(
963+
issuer_url=AnyHttpUrl("https://auth.example.com"),
964+
client_registration_options=ClientRegistrationOptions(enabled=True),
965+
revocation_options=RevocationOptions(enabled=True),
966+
required_scopes=["read"],
967+
),
966968
)
967969

968970
# Add a test tool

0 commit comments

Comments
 (0)