1616import anyio
1717import pydantic_core
1818import uvicorn
19- from pydantic import AnyHttpUrl , BaseModel , Field
19+ from pydantic import BaseModel , Field
2020from pydantic .networks import AnyUrl
2121from pydantic_settings import BaseSettings , SettingsConfigDict
2222from sse_starlette import EventSourceResponse
3030 RequireAuthMiddleware ,
3131)
3232from 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+ )
3436from mcp .server .fastmcp .exceptions import ResourceError
3537from mcp .server .fastmcp .prompts import Prompt , PromptManager
3638from 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
116110def 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
0 commit comments