fastmcp 2.12.0__py3-none-any.whl → 2.12.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,7 +30,6 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
30
30
  from fastmcp.server.auth import TokenVerifier
31
31
  from fastmcp.server.auth.auth import AccessToken
32
32
  from fastmcp.server.auth.oauth_proxy import OAuthProxy
33
- from fastmcp.server.auth.registry import register_provider
34
33
  from fastmcp.utilities.auth import parse_scopes
35
34
  from fastmcp.utilities.logging import get_logger
36
35
  from fastmcp.utilities.types import NotSet, NotSetT
@@ -53,7 +52,6 @@ class GoogleProviderSettings(BaseSettings):
53
52
  redirect_path: str | None = None
54
53
  required_scopes: list[str] | None = None
55
54
  timeout_seconds: int | None = None
56
- resource_server_url: AnyHttpUrl | str | None = None
57
55
  allowed_client_redirect_uris: list[str] | None = None
58
56
 
59
57
  @field_validator("required_scopes", mode="before")
@@ -181,7 +179,6 @@ class GoogleTokenVerifier(TokenVerifier):
181
179
  return None
182
180
 
183
181
 
184
- @register_provider("Google")
185
182
  class GoogleProvider(OAuthProxy):
186
183
  """Complete Google OAuth provider for FastMCP.
187
184
 
@@ -203,7 +200,7 @@ class GoogleProvider(OAuthProxy):
203
200
  auth = GoogleProvider(
204
201
  client_id="123456789.apps.googleusercontent.com",
205
202
  client_secret="GOCSPX-abc123...",
206
- base_url="https://my-server.com" # Optional, defaults to http://localhost:8000
203
+ base_url="https://my-server.com"
207
204
  )
208
205
 
209
206
  mcp = FastMCP("My App", auth=auth)
@@ -219,7 +216,6 @@ class GoogleProvider(OAuthProxy):
219
216
  redirect_path: str | NotSetT = NotSet,
220
217
  required_scopes: list[str] | NotSetT = NotSet,
221
218
  timeout_seconds: int | NotSetT = NotSet,
222
- resource_server_url: AnyHttpUrl | str | NotSetT = NotSet,
223
219
  allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
224
220
  ):
225
221
  """Initialize Google OAuth provider.
@@ -237,6 +233,7 @@ class GoogleProvider(OAuthProxy):
237
233
  allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
238
234
  If None (default), all URIs are allowed. If empty list, no URIs are allowed.
239
235
  """
236
+
240
237
  settings = GoogleProviderSettings.model_validate(
241
238
  {
242
239
  k: v
@@ -247,7 +244,6 @@ class GoogleProvider(OAuthProxy):
247
244
  "redirect_path": redirect_path,
248
245
  "required_scopes": required_scopes,
249
246
  "timeout_seconds": timeout_seconds,
250
- "resource_server_url": resource_server_url,
251
247
  "allowed_client_redirect_uris": allowed_client_redirect_uris,
252
248
  }.items()
253
249
  if v is not NotSet
@@ -265,12 +261,10 @@ class GoogleProvider(OAuthProxy):
265
261
  )
266
262
 
267
263
  # Apply defaults
268
- base_url_final = settings.base_url or "http://localhost:8000"
269
264
  redirect_path_final = settings.redirect_path or "/auth/callback"
270
265
  timeout_seconds_final = settings.timeout_seconds or 10
271
266
  # Google requires at least one scope - openid is the minimal OIDC scope
272
267
  required_scopes_final = settings.required_scopes or ["openid"]
273
- resource_server_url_final = settings.resource_server_url or base_url_final
274
268
  allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
275
269
 
276
270
  # Create Google token verifier
@@ -291,11 +285,10 @@ class GoogleProvider(OAuthProxy):
291
285
  upstream_client_id=settings.client_id,
292
286
  upstream_client_secret=client_secret_str,
293
287
  token_verifier=token_verifier,
294
- base_url=base_url_final,
288
+ base_url=settings.base_url,
295
289
  redirect_path=redirect_path_final,
296
- issuer_url=base_url_final, # We act as the issuer for client registration
290
+ issuer_url=settings.base_url, # We act as the issuer for client registration
297
291
  allowed_client_redirect_uris=allowed_client_redirect_uris_final,
298
- resource_server_url=resource_server_url_final,
299
292
  )
300
293
 
301
294
  logger.info(
@@ -41,7 +41,6 @@ class InMemoryOAuthProvider(OAuthProvider):
41
41
  client_registration_options: ClientRegistrationOptions | None = None,
42
42
  revocation_options: RevocationOptions | None = None,
43
43
  required_scopes: list[str] | None = None,
44
- resource_server_url: AnyHttpUrl | str | None = None,
45
44
  ):
46
45
  super().__init__(
47
46
  base_url=base_url or "http://fastmcp.example.com",
@@ -49,7 +48,6 @@ class InMemoryOAuthProvider(OAuthProvider):
49
48
  client_registration_options=client_registration_options,
50
49
  revocation_options=revocation_options,
51
50
  required_scopes=required_scopes,
52
- resource_server_url=resource_server_url,
53
51
  )
54
52
  self.clients: dict[str, OAuthClientInformationFull] = {}
55
53
  self.auth_codes: dict[str, AuthorizationCode] = {}
@@ -16,7 +16,6 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
16
16
  from typing_extensions import TypedDict
17
17
 
18
18
  from fastmcp.server.auth import AccessToken, TokenVerifier
19
- from fastmcp.server.auth.registry import register_provider
20
19
  from fastmcp.utilities.auth import parse_scopes
21
20
  from fastmcp.utilities.logging import get_logger
22
21
  from fastmcp.utilities.types import NotSet, NotSetT
@@ -154,7 +153,7 @@ class JWTVerifierSettings(BaseSettings):
154
153
  algorithm: str | None = None
155
154
  audience: str | list[str] | None = None
156
155
  required_scopes: list[str] | None = None
157
- resource_server_url: AnyHttpUrl | str | None = None
156
+ base_url: AnyHttpUrl | str | None = None
158
157
 
159
158
  @field_validator("required_scopes", mode="before")
160
159
  @classmethod
@@ -162,7 +161,6 @@ class JWTVerifierSettings(BaseSettings):
162
161
  return parse_scopes(v)
163
162
 
164
163
 
165
- @register_provider("JWT")
166
164
  class JWTVerifier(TokenVerifier):
167
165
  """
168
166
  JWT token verifier supporting both asymmetric (RSA/ECDSA) and symmetric (HMAC) algorithms.
@@ -191,7 +189,7 @@ class JWTVerifier(TokenVerifier):
191
189
  audience: str | list[str] | None | NotSetT = NotSet,
192
190
  algorithm: str | None | NotSetT = NotSet,
193
191
  required_scopes: list[str] | None | NotSetT = NotSet,
194
- resource_server_url: AnyHttpUrl | str | None | NotSetT = NotSet,
192
+ base_url: AnyHttpUrl | str | None | NotSetT = NotSet,
195
193
  ):
196
194
  """
197
195
  Initialize the JWT token verifier.
@@ -206,7 +204,7 @@ class JWTVerifier(TokenVerifier):
206
204
  - Asymmetric: RS256/384/512, ES256/384/512, PS256/384/512 (default: RS256)
207
205
  - Symmetric: HS256, HS384, HS512
208
206
  required_scopes: Required scopes for all tokens
209
- resource_server_url: Resource server URL for TokenVerifier protocol
207
+ base_url: Base URL for TokenVerifier protocol
210
208
  """
211
209
  settings = JWTVerifierSettings.model_validate(
212
210
  {
@@ -218,7 +216,7 @@ class JWTVerifier(TokenVerifier):
218
216
  "audience": audience,
219
217
  "algorithm": algorithm,
220
218
  "required_scopes": required_scopes,
221
- "resource_server_url": resource_server_url,
219
+ "base_url": base_url,
222
220
  }.items()
223
221
  if v is not NotSet
224
222
  }
@@ -249,7 +247,7 @@ class JWTVerifier(TokenVerifier):
249
247
 
250
248
  # Initialize parent TokenVerifier
251
249
  super().__init__(
252
- resource_server_url=settings.resource_server_url,
250
+ base_url=settings.base_url,
253
251
  required_scopes=settings.required_scopes,
254
252
  )
255
253
 
@@ -10,6 +10,8 @@ Choose based on your WorkOS setup and authentication requirements.
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
+ from typing import Any
14
+
13
15
  import httpx
14
16
  from pydantic import AnyHttpUrl, SecretStr, field_validator
15
17
  from pydantic_settings import BaseSettings, SettingsConfigDict
@@ -19,7 +21,6 @@ from starlette.routing import Route
19
21
  from fastmcp.server.auth import AccessToken, RemoteAuthProvider, TokenVerifier
20
22
  from fastmcp.server.auth.oauth_proxy import OAuthProxy
21
23
  from fastmcp.server.auth.providers.jwt import JWTVerifier
22
- from fastmcp.server.auth.registry import register_provider
23
24
  from fastmcp.utilities.auth import parse_scopes
24
25
  from fastmcp.utilities.logging import get_logger
25
26
  from fastmcp.utilities.types import NotSet, NotSetT
@@ -43,7 +44,6 @@ class WorkOSProviderSettings(BaseSettings):
43
44
  redirect_path: str | None = None
44
45
  required_scopes: list[str] | None = None
45
46
  timeout_seconds: int | None = None
46
- resource_server_url: AnyHttpUrl | str | None = None
47
47
  allowed_client_redirect_uris: list[str] | None = None
48
48
 
49
49
  @field_validator("required_scopes", mode="before")
@@ -124,7 +124,6 @@ class WorkOSTokenVerifier(TokenVerifier):
124
124
  return None
125
125
 
126
126
 
127
- @register_provider("WORKOS")
128
127
  class WorkOSProvider(OAuthProxy):
129
128
  """Complete WorkOS OAuth provider for FastMCP.
130
129
 
@@ -169,7 +168,6 @@ class WorkOSProvider(OAuthProxy):
169
168
  redirect_path: str | NotSetT = NotSet,
170
169
  required_scopes: list[str] | None | NotSetT = NotSet,
171
170
  timeout_seconds: int | NotSetT = NotSet,
172
- resource_server_url: AnyHttpUrl | str | NotSetT = NotSet,
173
171
  allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
174
172
  ):
175
173
  """Initialize WorkOS OAuth provider.
@@ -182,11 +180,10 @@ class WorkOSProvider(OAuthProxy):
182
180
  redirect_path: Redirect path configured in WorkOS (defaults to "/auth/callback")
183
181
  required_scopes: Required OAuth scopes (no default)
184
182
  timeout_seconds: HTTP request timeout for WorkOS API calls
185
- resource_server_url: Path of the FastMCP server (defaults to base_url). If your MCP endpoint is at
186
- a different path like {base_url}/mcp, specify it here for RFC 8707 compliance.
187
183
  allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
188
184
  If None (default), all URIs are allowed. If empty list, no URIs are allowed.
189
185
  """
186
+
190
187
  settings = WorkOSProviderSettings.model_validate(
191
188
  {
192
189
  k: v
@@ -198,7 +195,6 @@ class WorkOSProvider(OAuthProxy):
198
195
  "redirect_path": redirect_path,
199
196
  "required_scopes": required_scopes,
200
197
  "timeout_seconds": timeout_seconds,
201
- "resource_server_url": resource_server_url,
202
198
  "allowed_client_redirect_uris": allowed_client_redirect_uris,
203
199
  }.items()
204
200
  if v is not NotSet
@@ -224,11 +220,9 @@ class WorkOSProvider(OAuthProxy):
224
220
  if not authkit_domain_str.startswith(("http://", "https://")):
225
221
  authkit_domain_str = f"https://{authkit_domain_str}"
226
222
  authkit_domain_final = authkit_domain_str.rstrip("/")
227
- base_url_final = settings.base_url or "http://localhost:8000"
228
223
  redirect_path_final = settings.redirect_path or "/auth/callback"
229
224
  timeout_seconds_final = settings.timeout_seconds or 10
230
225
  scopes_final = settings.required_scopes or []
231
- resource_server_url_final = settings.resource_server_url or base_url_final
232
226
  allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
233
227
 
234
228
  # Extract secret string from SecretStr
@@ -250,11 +244,10 @@ class WorkOSProvider(OAuthProxy):
250
244
  upstream_client_id=settings.client_id,
251
245
  upstream_client_secret=client_secret_str,
252
246
  token_verifier=token_verifier,
253
- base_url=base_url_final,
247
+ base_url=settings.base_url,
254
248
  redirect_path=redirect_path_final,
255
- issuer_url=base_url_final,
249
+ issuer_url=settings.base_url,
256
250
  allowed_client_redirect_uris=allowed_client_redirect_uris_final,
257
- resource_server_url=resource_server_url_final,
258
251
  )
259
252
 
260
253
  logger.info(
@@ -281,7 +274,6 @@ class AuthKitProviderSettings(BaseSettings):
281
274
  return parse_scopes(v)
282
275
 
283
276
 
284
- @register_provider("AUTHKIT")
285
277
  class AuthKitProvider(RemoteAuthProvider):
286
278
  """AuthKit metadata provider for DCR (Dynamic Client Registration).
287
279
 
@@ -362,17 +354,25 @@ class AuthKitProvider(RemoteAuthProvider):
362
354
  super().__init__(
363
355
  token_verifier=token_verifier,
364
356
  authorization_servers=[AnyHttpUrl(self.authkit_domain)],
365
- resource_server_url=self.base_url,
357
+ base_url=self.base_url,
366
358
  )
367
359
 
368
- def get_routes(self) -> list[Route]:
360
+ def get_routes(
361
+ self,
362
+ mcp_path: str | None = None,
363
+ mcp_endpoint: Any | None = None,
364
+ ) -> list[Route]:
369
365
  """Get OAuth routes including AuthKit authorization server metadata forwarding.
370
366
 
371
367
  This returns the standard protected resource routes plus an authorization server
372
368
  metadata endpoint that forwards AuthKit's OAuth metadata to clients.
369
+
370
+ Args:
371
+ mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
372
+ mcp_endpoint: The MCP endpoint handler to protect with auth
373
373
  """
374
374
  # Get the standard protected resource routes from RemoteAuthProvider
375
- routes = super().get_routes()
375
+ routes = super().get_routes(mcp_path, mcp_endpoint)
376
376
 
377
377
  async def oauth_authorization_server_metadata(request):
378
378
  """Forward AuthKit OAuth authorization server metadata with FastMCP customizations."""
fastmcp/server/context.py CHANGED
@@ -10,7 +10,7 @@ from contextlib import contextmanager
10
10
  from contextvars import ContextVar, Token
11
11
  from dataclasses import dataclass
12
12
  from enum import Enum
13
- from typing import Any, Literal, TypeVar, cast, get_origin, overload
13
+ from typing import Any, Literal, cast, get_origin, overload
14
14
 
15
15
  from mcp import LoggingLevel, ServerSession
16
16
  from mcp.server.lowlevel.helper_types import ReadResourceContents
@@ -31,6 +31,7 @@ from mcp.types import (
31
31
  from mcp.types import CreateMessageRequestParams as SamplingParams
32
32
  from pydantic.networks import AnyUrl
33
33
  from starlette.requests import Request
34
+ from typing_extensions import TypeVar
34
35
 
35
36
  import fastmcp.server.dependencies
36
37
  from fastmcp import settings
@@ -47,7 +48,7 @@ from fastmcp.utilities.types import get_cached_typeadapter
47
48
 
48
49
  logger = get_logger(__name__)
49
50
 
50
- T = TypeVar("T")
51
+ T = TypeVar("T", default=Any)
51
52
  _current_context: ContextVar[Context | None] = ContextVar("context", default=None) # type: ignore[assignment]
52
53
  _flush_lock = asyncio.Lock()
53
54
 
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, ParamSpec, TypeVar
3
+ from typing import TYPE_CHECKING
4
4
 
5
5
  from mcp.server.auth.middleware.auth_context import (
6
6
  get_access_token as _sdk_get_access_token,
@@ -12,9 +12,6 @@ from fastmcp.server.auth import AccessToken
12
12
  if TYPE_CHECKING:
13
13
  from fastmcp.server.context import Context
14
14
 
15
- P = ParamSpec("P")
16
- R = TypeVar("R")
17
-
18
15
  __all__ = [
19
16
  "get_context",
20
17
  "get_http_request",
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
- from typing import Any, Generic, Literal, TypeVar
4
+ from typing import Any, Generic, Literal
5
5
 
6
6
  from mcp.server.elicitation import (
7
7
  CancelledElicitation,
@@ -10,6 +10,7 @@ from mcp.server.elicitation import (
10
10
  from pydantic import BaseModel
11
11
  from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue
12
12
  from pydantic_core import core_schema
13
+ from typing_extensions import TypeVar
13
14
 
14
15
  from fastmcp.utilities.json_schema import compress_schema
15
16
  from fastmcp.utilities.logging import get_logger
@@ -25,7 +26,7 @@ __all__ = [
25
26
 
26
27
  logger = get_logger(__name__)
27
28
 
28
- T = TypeVar("T")
29
+ T = TypeVar("T", default=Any)
29
30
 
30
31
 
31
32
  class ElicitationJsonSchema(GenerateJsonSchema):
fastmcp/server/http.py CHANGED
@@ -3,21 +3,15 @@ from __future__ import annotations
3
3
  from collections.abc import AsyncGenerator, Callable, Generator
4
4
  from contextlib import asynccontextmanager, contextmanager
5
5
  from contextvars import ContextVar
6
- from typing import TYPE_CHECKING, cast
6
+ from typing import TYPE_CHECKING
7
7
 
8
- from mcp.server.auth.middleware.auth_context import AuthContextMiddleware
9
- from mcp.server.auth.middleware.bearer_auth import (
10
- BearerAuthBackend,
11
- RequireAuthMiddleware,
12
- )
13
- from mcp.server.auth.provider import TokenVerifier as TokenVerifierProtocol
8
+ from mcp.server.auth.middleware.bearer_auth import RequireAuthMiddleware
14
9
  from mcp.server.lowlevel.server import LifespanResultT
15
10
  from mcp.server.sse import SseServerTransport
16
11
  from mcp.server.streamable_http import EventStore
17
12
  from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
18
13
  from starlette.applications import Starlette
19
14
  from starlette.middleware import Middleware
20
- from starlette.middleware.authentication import AuthenticationMiddleware
21
15
  from starlette.requests import Request
22
16
  from starlette.responses import Response
23
17
  from starlette.routing import BaseRoute, Mount, Route
@@ -170,40 +164,26 @@ def create_sse_app(
170
164
 
171
165
  # Set up auth if enabled
172
166
  if auth:
173
- # Create auth middleware
174
- auth_middleware = [
175
- Middleware(
176
- AuthenticationMiddleware,
177
- backend=BearerAuthBackend(auth),
178
- ),
179
- Middleware(AuthContextMiddleware),
180
- ]
181
-
182
- # Get auth routes and scopes
183
- auth_routes = auth.get_routes()
184
- required_scopes = getattr(auth, "required_scopes", None) or []
185
-
186
- # Get resource metadata URL for WWW-Authenticate header
187
- resource_metadata_url = auth.get_resource_metadata_url()
167
+ # Get auth middleware from the provider
168
+ auth_middleware = auth.get_middleware()
169
+
170
+ # Get auth routes including protected MCP endpoint
171
+ auth_routes = auth.get_routes(
172
+ mcp_path=sse_path,
173
+ mcp_endpoint=handle_sse,
174
+ )
188
175
 
189
176
  server_routes.extend(auth_routes)
190
177
  server_middleware.extend(auth_middleware)
191
178
 
192
- # Auth is enabled, wrap endpoints with RequireAuthMiddleware
193
- server_routes.append(
194
- Route(
195
- sse_path,
196
- endpoint=RequireAuthMiddleware(
197
- handle_sse, required_scopes, resource_metadata_url
198
- ),
199
- methods=["GET"],
200
- )
201
- )
179
+ # Manually wrap the SSE message endpoint with RequireAuthMiddleware
202
180
  server_routes.append(
203
181
  Mount(
204
182
  message_path,
205
183
  app=RequireAuthMiddleware(
206
- sse.handle_post_message, required_scopes, resource_metadata_url
184
+ sse.handle_post_message,
185
+ auth.required_scopes,
186
+ auth._get_resource_url("/.well-known/oauth-protected-resource"),
207
187
  ),
208
188
  )
209
189
  )
@@ -291,34 +271,17 @@ def create_streamable_http_app(
291
271
 
292
272
  # Add StreamableHTTP routes with or without auth
293
273
  if auth:
294
- # Create auth middleware
295
- auth_middleware = [
296
- Middleware(
297
- AuthenticationMiddleware,
298
- backend=BearerAuthBackend(cast(TokenVerifierProtocol, auth)),
299
- ),
300
- Middleware(AuthContextMiddleware),
301
- ]
302
-
303
- # Get auth routes and scopes
304
- auth_routes = auth.get_routes()
305
- required_scopes = getattr(auth, "required_scopes", None) or []
306
-
307
- # Get resource metadata URL for WWW-Authenticate header
308
- resource_metadata_url = auth.get_resource_metadata_url()
274
+ # Get auth middleware from the provider
275
+ auth_middleware = auth.get_middleware()
276
+
277
+ # Get auth routes including protected MCP endpoint
278
+ auth_routes = auth.get_routes(
279
+ mcp_path=streamable_http_path,
280
+ mcp_endpoint=streamable_http_app,
281
+ )
309
282
 
310
283
  server_routes.extend(auth_routes)
311
284
  server_middleware.extend(auth_middleware)
312
-
313
- # Auth is enabled, wrap endpoint with RequireAuthMiddleware
314
- server_routes.append(
315
- Route(
316
- streamable_http_path,
317
- endpoint=RequireAuthMiddleware(
318
- streamable_http_app, required_scopes, resource_metadata_url
319
- ),
320
- )
321
- )
322
285
  else:
323
286
  # No auth required
324
287
  server_routes.append(
@@ -11,11 +11,11 @@ from typing import (
11
11
  Generic,
12
12
  Literal,
13
13
  Protocol,
14
- TypeVar,
15
14
  runtime_checkable,
16
15
  )
17
16
 
18
17
  import mcp.types as mt
18
+ from typing_extensions import TypeVar
19
19
 
20
20
  from fastmcp.prompts.prompt import Prompt
21
21
  from fastmcp.resources.resource import Resource
@@ -34,8 +34,8 @@ __all__ = [
34
34
  logger = logging.getLogger(__name__)
35
35
 
36
36
 
37
- T = TypeVar("T")
38
- R = TypeVar("R", covariant=True)
37
+ T = TypeVar("T", default=Any)
38
+ R = TypeVar("R", covariant=True, default=Any)
39
39
 
40
40
 
41
41
  @runtime_checkable
fastmcp/server/server.py CHANGED
@@ -52,7 +52,6 @@ from fastmcp.prompts.prompt import FunctionPrompt
52
52
  from fastmcp.resources import Resource, ResourceManager
53
53
  from fastmcp.resources.template import ResourceTemplate
54
54
  from fastmcp.server.auth import AuthProvider
55
- from fastmcp.server.auth.registry import get_registered_provider
56
55
  from fastmcp.server.http import (
57
56
  StarletteWithLifespan,
58
57
  create_sse_app,
@@ -209,8 +208,8 @@ class FastMCP(Generic[LifespanResultT]):
209
208
  # if auth is `NotSet`, try to create a provider from the environment
210
209
  if auth is NotSet:
211
210
  if fastmcp.settings.server_auth is not None:
212
- provider_cls = get_registered_provider(fastmcp.settings.server_auth)
213
- auth = provider_cls()
211
+ # ImportString returns the class itself
212
+ auth = fastmcp.settings.server_auth()
214
213
  else:
215
214
  auth = None
216
215
  self.auth = cast(AuthProvider | None, auth)
fastmcp/settings.py CHANGED
@@ -5,7 +5,7 @@ import warnings
5
5
  from pathlib import Path
6
6
  from typing import Annotated, Any, Literal
7
7
 
8
- from pydantic import Field, field_validator
8
+ from pydantic import Field, ImportString, field_validator
9
9
  from pydantic.fields import FieldInfo
10
10
  from pydantic_settings import (
11
11
  BaseSettings,
@@ -258,14 +258,17 @@ class Settings(BaseSettings):
258
258
 
259
259
  # Auth settings
260
260
  server_auth: Annotated[
261
- str | None,
261
+ ImportString | None,
262
262
  Field(
263
263
  description=inspect.cleandoc(
264
264
  """
265
- Configure the authentication provider for the server. Auth
266
- providers are registered with a specific key, and providing that
267
- key here will cause the server to automatically configure the
268
- provider from the environment.
265
+ Configure the authentication provider for the server by specifying
266
+ the full module path to an AuthProvider class (e.g.,
267
+ 'fastmcp.server.auth.providers.google.GoogleProvider').
268
+
269
+ The specified class will be imported and instantiated automatically.
270
+ Any class that inherits from AuthProvider can be used, including
271
+ custom implementations.
269
272
 
270
273
  If None, no automatic configuration will take place.
271
274
 
@@ -274,6 +277,11 @@ class Settings(BaseSettings):
274
277
 
275
278
  Note that most auth providers require additional configuration
276
279
  that must be provided via env vars.
280
+
281
+ Examples:
282
+ - fastmcp.server.auth.providers.google.GoogleProvider
283
+ - fastmcp.server.auth.providers.jwt.JWTVerifier
284
+ - mycompany.auth.CustomAuthProvider
277
285
  """
278
286
  ),
279
287
  ),
fastmcp/tools/tool.py CHANGED
@@ -10,7 +10,6 @@ from typing import (
10
10
  Any,
11
11
  Generic,
12
12
  Literal,
13
- TypeVar,
14
13
  get_type_hints,
15
14
  )
16
15
 
@@ -19,6 +18,7 @@ import pydantic_core
19
18
  from mcp.types import ContentBlock, TextContent, ToolAnnotations
20
19
  from mcp.types import Tool as MCPTool
21
20
  from pydantic import Field, PydanticSchemaGenerationError
21
+ from typing_extensions import TypeVar
22
22
 
23
23
  import fastmcp
24
24
  from fastmcp.server.dependencies import get_context
@@ -41,7 +41,7 @@ if TYPE_CHECKING:
41
41
 
42
42
  logger = get_logger(__name__)
43
43
 
44
- T = TypeVar("T")
44
+ T = TypeVar("T", default=Any)
45
45
 
46
46
 
47
47
  @dataclass
@@ -1,15 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Sequence
4
- from typing import Annotated, Any, TypedDict, TypeVar
4
+ from typing import Annotated, Any, TypedDict
5
5
 
6
6
  from pydantic import BeforeValidator, Field, PrivateAttr
7
- from typing_extensions import Self
7
+ from typing_extensions import Self, TypeVar
8
8
 
9
9
  import fastmcp
10
10
  from fastmcp.utilities.types import FastMCPBaseModel
11
11
 
12
- T = TypeVar("T")
12
+ T = TypeVar("T", default=Any)
13
13
 
14
14
 
15
15
  class FastMCPMeta(TypedDict, total=False):
@@ -184,7 +184,7 @@ class MCPServerConfig(BaseModel):
184
184
  """Validate and convert source to proper format.
185
185
 
186
186
  Supports:
187
- - Dict format: {"path": "server.py", "entrypoint": "app"}
187
+ - Dict format: `{"path": "server.py", "entrypoint": "app"}`
188
188
  - FileSystemSource instance (passed through)
189
189
 
190
190
  No string parsing happens here - that's only at CLI boundaries.
@@ -13,7 +13,6 @@ from typing import (
13
13
  Any,
14
14
  Protocol,
15
15
  TypeAlias,
16
- TypeVar,
17
16
  Union,
18
17
  get_args,
19
18
  get_origin,
@@ -23,8 +22,9 @@ from typing import (
23
22
  import mcp.types
24
23
  from mcp.types import Annotations, ContentBlock, ModelPreferences, SamplingMessage
25
24
  from pydantic import AnyUrl, BaseModel, ConfigDict, Field, TypeAdapter, UrlConstraints
25
+ from typing_extensions import TypeVar
26
26
 
27
- T = TypeVar("T")
27
+ T = TypeVar("T", default=Any)
28
28
 
29
29
  # sentinel values for optional arguments
30
30
  NotSet = ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastmcp
3
- Version: 2.12.0
3
+ Version: 2.12.1
4
4
  Summary: The fast, Pythonic way to build MCP servers and clients.
5
5
  Project-URL: Homepage, https://gofastmcp.com
6
6
  Project-URL: Repository, https://github.com/jlowin/fastmcp
@@ -22,13 +22,14 @@ Requires-Dist: cyclopts>=3.0.0
22
22
  Requires-Dist: exceptiongroup>=1.2.2
23
23
  Requires-Dist: httpx>=0.28.1
24
24
  Requires-Dist: mcp<2.0.0,>=1.12.4
25
- Requires-Dist: openai>=1.95.1
26
25
  Requires-Dist: openapi-core>=0.19.5
27
26
  Requires-Dist: openapi-pydantic>=0.5.1
28
27
  Requires-Dist: pydantic[email]>=2.11.7
29
28
  Requires-Dist: pyperclip>=1.9.0
30
29
  Requires-Dist: python-dotenv>=1.1.0
31
30
  Requires-Dist: rich>=13.9.4
31
+ Provides-Extra: openai
32
+ Requires-Dist: openai>=1.102.0; extra == 'openai'
32
33
  Provides-Extra: websockets
33
34
  Requires-Dist: websockets>=15.0.1; extra == 'websockets'
34
35
  Description-Content-Type: text/markdown