fastmcp 2.12.0rc1__py3-none-any.whl → 2.12.2__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.
- fastmcp/client/auth/oauth.py +78 -2
- fastmcp/client/elicitation.py +3 -2
- fastmcp/experimental/sampling/handlers/__init__.py +0 -3
- fastmcp/experimental/sampling/handlers/openai.py +16 -9
- fastmcp/server/auth/auth.py +130 -59
- fastmcp/server/auth/oauth_proxy.py +122 -221
- fastmcp/server/auth/providers/azure.py +3 -12
- fastmcp/server/auth/providers/github.py +5 -13
- fastmcp/server/auth/providers/google.py +4 -11
- fastmcp/server/auth/providers/in_memory.py +0 -2
- fastmcp/server/auth/providers/jwt.py +5 -7
- fastmcp/server/auth/providers/workos.py +16 -16
- fastmcp/server/context.py +3 -2
- fastmcp/server/dependencies.py +1 -4
- fastmcp/server/elicitation.py +3 -2
- fastmcp/server/http.py +22 -59
- fastmcp/server/middleware/middleware.py +3 -3
- fastmcp/server/server.py +2 -3
- fastmcp/settings.py +14 -6
- fastmcp/tools/tool.py +2 -2
- fastmcp/utilities/components.py +3 -3
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +2 -2
- fastmcp/utilities/types.py +2 -2
- {fastmcp-2.12.0rc1.dist-info → fastmcp-2.12.2.dist-info}/METADATA +3 -2
- {fastmcp-2.12.0rc1.dist-info → fastmcp-2.12.2.dist-info}/RECORD +28 -29
- fastmcp/server/auth/registry.py +0 -52
- {fastmcp-2.12.0rc1.dist-info → fastmcp-2.12.2.dist-info}/WHEEL +0 -0
- {fastmcp-2.12.0rc1.dist-info → fastmcp-2.12.2.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.12.0rc1.dist-info → fastmcp-2.12.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -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"
|
|
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=
|
|
288
|
+
base_url=settings.base_url,
|
|
295
289
|
redirect_path=redirect_path_final,
|
|
296
|
-
issuer_url=
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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=
|
|
247
|
+
base_url=settings.base_url,
|
|
254
248
|
redirect_path=redirect_path_final,
|
|
255
|
-
issuer_url=
|
|
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
|
-
|
|
357
|
+
base_url=self.base_url,
|
|
366
358
|
)
|
|
367
359
|
|
|
368
|
-
def get_routes(
|
|
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,
|
|
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
|
|
fastmcp/server/dependencies.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
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",
|
fastmcp/server/elicitation.py
CHANGED
|
@@ -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
|
|
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
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
-
from mcp.server.auth.middleware.
|
|
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
|
-
#
|
|
174
|
-
auth_middleware =
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
#
|
|
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,
|
|
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
|
-
#
|
|
295
|
-
auth_middleware =
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
213
|
-
auth =
|
|
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
|
-
|
|
261
|
+
ImportString | None,
|
|
262
262
|
Field(
|
|
263
263
|
description=inspect.cleandoc(
|
|
264
264
|
"""
|
|
265
|
-
Configure the authentication provider for the server
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
fastmcp/utilities/components.py
CHANGED
|
@@ -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
|
|
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):
|
|
@@ -36,7 +36,7 @@ EnvironmentType: TypeAlias = UVEnvironment
|
|
|
36
36
|
class Deployment(BaseModel):
|
|
37
37
|
"""Configuration for server deployment and runtime settings."""
|
|
38
38
|
|
|
39
|
-
transport: Literal["stdio", "http", "sse"] | None = Field(
|
|
39
|
+
transport: Literal["stdio", "http", "sse", "streamable-http"] | None = Field(
|
|
40
40
|
default=None,
|
|
41
41
|
description="Transport protocol to use",
|
|
42
42
|
)
|
|
@@ -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.
|
fastmcp/utilities/types.py
CHANGED
|
@@ -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.
|
|
3
|
+
Version: 2.12.2
|
|
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
|