fastmcp 2.12.3__py3-none-any.whl → 2.12.4__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.
Files changed (34) hide show
  1. fastmcp/cli/install/gemini_cli.py +0 -1
  2. fastmcp/cli/run.py +2 -2
  3. fastmcp/client/auth/oauth.py +49 -36
  4. fastmcp/client/client.py +12 -2
  5. fastmcp/contrib/mcp_mixin/README.md +2 -2
  6. fastmcp/experimental/utilities/openapi/schemas.py +31 -5
  7. fastmcp/server/auth/auth.py +3 -3
  8. fastmcp/server/auth/oauth_proxy.py +42 -12
  9. fastmcp/server/auth/oidc_proxy.py +348 -0
  10. fastmcp/server/auth/providers/auth0.py +174 -0
  11. fastmcp/server/auth/providers/aws.py +237 -0
  12. fastmcp/server/auth/providers/azure.py +6 -2
  13. fastmcp/server/auth/providers/descope.py +172 -0
  14. fastmcp/server/auth/providers/github.py +6 -2
  15. fastmcp/server/auth/providers/google.py +6 -2
  16. fastmcp/server/auth/providers/workos.py +6 -2
  17. fastmcp/server/context.py +7 -6
  18. fastmcp/server/http.py +1 -1
  19. fastmcp/server/middleware/logging.py +147 -116
  20. fastmcp/server/middleware/middleware.py +3 -2
  21. fastmcp/server/openapi.py +5 -1
  22. fastmcp/server/server.py +36 -31
  23. fastmcp/settings.py +27 -5
  24. fastmcp/tools/tool.py +4 -2
  25. fastmcp/utilities/json_schema.py +18 -1
  26. fastmcp/utilities/logging.py +66 -4
  27. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +2 -1
  28. fastmcp/utilities/storage.py +204 -0
  29. fastmcp/utilities/tests.py +8 -6
  30. {fastmcp-2.12.3.dist-info → fastmcp-2.12.4.dist-info}/METADATA +120 -47
  31. {fastmcp-2.12.3.dist-info → fastmcp-2.12.4.dist-info}/RECORD +34 -29
  32. {fastmcp-2.12.3.dist-info → fastmcp-2.12.4.dist-info}/WHEEL +0 -0
  33. {fastmcp-2.12.3.dist-info → fastmcp-2.12.4.dist-info}/entry_points.txt +0 -0
  34. {fastmcp-2.12.3.dist-info → fastmcp-2.12.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,237 @@
1
+ """AWS Cognito OAuth provider for FastMCP.
2
+
3
+ This module provides a complete AWS Cognito OAuth integration that's ready to use
4
+ with a user pool ID, domain prefix, client ID and client secret. It handles all
5
+ the complexity of AWS Cognito's OAuth flow, token validation, and user management.
6
+
7
+ Example:
8
+ ```python
9
+ from fastmcp import FastMCP
10
+ from fastmcp.server.auth.providers.aws_cognito import AWSCognitoProvider
11
+
12
+ # Simple AWS Cognito OAuth protection
13
+ auth = AWSCognitoProvider(
14
+ user_pool_id="your-user-pool-id",
15
+ aws_region="eu-central-1",
16
+ client_id="your-cognito-client-id",
17
+ client_secret="your-cognito-client-secret"
18
+ )
19
+
20
+ mcp = FastMCP("My Protected Server", auth=auth)
21
+ ```
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from pydantic import AnyHttpUrl, SecretStr, field_validator
27
+ from pydantic_settings import BaseSettings, SettingsConfigDict
28
+
29
+ from fastmcp.server.auth import TokenVerifier
30
+ from fastmcp.server.auth.auth import AccessToken
31
+ from fastmcp.server.auth.oidc_proxy import OIDCProxy
32
+ from fastmcp.server.auth.providers.jwt import JWTVerifier
33
+ from fastmcp.utilities.auth import parse_scopes
34
+ from fastmcp.utilities.logging import get_logger
35
+ from fastmcp.utilities.types import NotSet, NotSetT
36
+
37
+ logger = get_logger(__name__)
38
+
39
+
40
+ class AWSCognitoProviderSettings(BaseSettings):
41
+ """Settings for AWS Cognito OAuth provider."""
42
+
43
+ model_config = SettingsConfigDict(
44
+ env_prefix="FASTMCP_SERVER_AUTH_AWS_COGNITO_",
45
+ env_file=".env",
46
+ extra="ignore",
47
+ )
48
+
49
+ user_pool_id: str | None = None
50
+ aws_region: str | None = None
51
+ client_id: str | None = None
52
+ client_secret: SecretStr | None = None
53
+ base_url: AnyHttpUrl | str | None = None
54
+ redirect_path: str | None = None
55
+ required_scopes: list[str] | None = None
56
+ allowed_client_redirect_uris: list[str] | None = None
57
+
58
+ @field_validator("required_scopes", mode="before")
59
+ @classmethod
60
+ def _parse_scopes(cls, v):
61
+ return parse_scopes(v)
62
+
63
+
64
+ class AWSCognitoTokenVerifier(JWTVerifier):
65
+ """Token verifier that filters claims to Cognito-specific subset."""
66
+
67
+ async def verify_token(self, token: str) -> AccessToken | None:
68
+ """Verify token and filter claims to Cognito-specific subset."""
69
+ # Use base JWT verification
70
+ access_token = await super().verify_token(token)
71
+ if not access_token:
72
+ return None
73
+
74
+ # Filter claims to Cognito-specific subset
75
+ cognito_claims = {
76
+ "sub": access_token.claims.get("sub"),
77
+ "username": access_token.claims.get("username"),
78
+ "cognito:groups": access_token.claims.get("cognito:groups", []),
79
+ }
80
+
81
+ # Return new AccessToken with filtered claims
82
+ return AccessToken(
83
+ token=access_token.token,
84
+ client_id=access_token.client_id,
85
+ scopes=access_token.scopes,
86
+ expires_at=access_token.expires_at,
87
+ claims=cognito_claims,
88
+ )
89
+
90
+
91
+ class AWSCognitoProvider(OIDCProxy):
92
+ """Complete AWS Cognito OAuth provider for FastMCP.
93
+
94
+ This provider makes it trivial to add AWS Cognito OAuth protection to any
95
+ FastMCP server using OIDC Discovery. Just provide your Cognito User Pool details,
96
+ client credentials, and a base URL, and you're ready to go.
97
+
98
+ Features:
99
+ - Automatic OIDC Discovery from AWS Cognito User Pool
100
+ - Automatic JWT token validation via Cognito's public keys
101
+ - Cognito-specific claim filtering (sub, username, cognito:groups)
102
+ - Support for Cognito User Pools
103
+
104
+ Example:
105
+ ```python
106
+ from fastmcp import FastMCP
107
+ from fastmcp.server.auth.providers.aws_cognito import AWSCognitoProvider
108
+
109
+ auth = AWSCognitoProvider(
110
+ user_pool_id="eu-central-1_XXXXXXXXX",
111
+ aws_region="eu-central-1",
112
+ client_id="your-cognito-client-id",
113
+ client_secret="your-cognito-client-secret",
114
+ base_url="https://my-server.com",
115
+ redirect_path="/custom/callback",
116
+ )
117
+
118
+ mcp = FastMCP("My App", auth=auth)
119
+ ```
120
+ """
121
+
122
+ def __init__(
123
+ self,
124
+ *,
125
+ user_pool_id: str | NotSetT = NotSet,
126
+ aws_region: str | NotSetT = NotSet,
127
+ client_id: str | NotSetT = NotSet,
128
+ client_secret: str | NotSetT = NotSet,
129
+ base_url: AnyHttpUrl | str | NotSetT = NotSet,
130
+ redirect_path: str | NotSetT = NotSet,
131
+ required_scopes: list[str] | NotSetT = NotSet,
132
+ allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
133
+ ):
134
+ """Initialize AWS Cognito OAuth provider.
135
+
136
+ Args:
137
+ user_pool_id: Your Cognito User Pool ID (e.g., "eu-central-1_XXXXXXXXX")
138
+ aws_region: AWS region where your User Pool is located (defaults to "eu-central-1")
139
+ client_id: Cognito app client ID
140
+ client_secret: Cognito app client secret
141
+ base_url: Public URL of your FastMCP server (for OAuth callbacks)
142
+ redirect_path: Redirect path configured in Cognito app (defaults to "/auth/callback")
143
+ required_scopes: Required Cognito scopes (defaults to ["openid"])
144
+ allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
145
+ If None (default), all URIs are allowed. If empty list, no URIs are allowed.
146
+ """
147
+
148
+ settings = AWSCognitoProviderSettings.model_validate(
149
+ {
150
+ k: v
151
+ for k, v in {
152
+ "user_pool_id": user_pool_id,
153
+ "aws_region": aws_region,
154
+ "client_id": client_id,
155
+ "client_secret": client_secret,
156
+ "base_url": base_url,
157
+ "redirect_path": redirect_path,
158
+ "required_scopes": required_scopes,
159
+ "allowed_client_redirect_uris": allowed_client_redirect_uris,
160
+ }.items()
161
+ if v is not NotSet
162
+ }
163
+ )
164
+
165
+ # Validate required settings
166
+ if not settings.user_pool_id:
167
+ raise ValueError(
168
+ "user_pool_id is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID"
169
+ )
170
+ if not settings.client_id:
171
+ raise ValueError(
172
+ "client_id is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID"
173
+ )
174
+ if not settings.client_secret:
175
+ raise ValueError(
176
+ "client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET"
177
+ )
178
+
179
+ # Apply defaults
180
+ required_scopes_final = settings.required_scopes or ["openid"]
181
+ allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
182
+ aws_region_final = settings.aws_region or "eu-central-1"
183
+ redirect_path_final = settings.redirect_path or "/auth/callback"
184
+
185
+ # Construct OIDC discovery URL
186
+ config_url = f"https://cognito-idp.{aws_region_final}.amazonaws.com/{settings.user_pool_id}/.well-known/openid-configuration"
187
+
188
+ # Extract secret string from SecretStr
189
+ client_secret_str = (
190
+ settings.client_secret.get_secret_value() if settings.client_secret else ""
191
+ )
192
+
193
+ # Store Cognito-specific info for claim filtering
194
+ self.user_pool_id = settings.user_pool_id
195
+ self.aws_region = aws_region_final
196
+
197
+ # Initialize OIDC proxy with Cognito discovery
198
+ super().__init__(
199
+ config_url=config_url,
200
+ client_id=settings.client_id,
201
+ client_secret=client_secret_str,
202
+ algorithm="RS256",
203
+ required_scopes=required_scopes_final,
204
+ base_url=settings.base_url,
205
+ redirect_path=redirect_path_final,
206
+ allowed_client_redirect_uris=allowed_client_redirect_uris_final,
207
+ )
208
+
209
+ logger.info(
210
+ "Initialized AWS Cognito OAuth provider for client %s with scopes: %s",
211
+ settings.client_id,
212
+ required_scopes_final,
213
+ )
214
+
215
+ def get_token_verifier(
216
+ self,
217
+ *,
218
+ algorithm: str | None = None,
219
+ audience: str | None = None,
220
+ required_scopes: list[str] | None = None,
221
+ timeout_seconds: int | None = None,
222
+ ) -> TokenVerifier:
223
+ """Creates a Cognito-specific token verifier with claim filtering.
224
+
225
+ Args:
226
+ algorithm: Optional token verifier algorithm
227
+ audience: Optional token verifier audience
228
+ required_scopes: Optional token verifier required_scopes
229
+ timeout_seconds: HTTP request timeout in seconds
230
+ """
231
+ return AWSCognitoTokenVerifier(
232
+ issuer=str(self.oidc_config.issuer),
233
+ audience=audience,
234
+ algorithm=algorithm,
235
+ jwks_uri=str(self.oidc_config.jwks_uri),
236
+ required_scopes=required_scopes,
237
+ )
@@ -14,6 +14,7 @@ from fastmcp.server.auth import AccessToken, TokenVerifier
14
14
  from fastmcp.server.auth.oauth_proxy import OAuthProxy
15
15
  from fastmcp.utilities.auth import parse_scopes
16
16
  from fastmcp.utilities.logging import get_logger
17
+ from fastmcp.utilities.storage import KVStorage
17
18
  from fastmcp.utilities.types import NotSet, NotSetT
18
19
 
19
20
  logger = get_logger(__name__)
@@ -160,6 +161,7 @@ class AzureProvider(OAuthProxy):
160
161
  required_scopes: list[str] | None | NotSetT = NotSet,
161
162
  timeout_seconds: int | NotSetT = NotSet,
162
163
  allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
164
+ client_storage: KVStorage | None = None,
163
165
  ):
164
166
  """Initialize Azure OAuth provider.
165
167
 
@@ -173,6 +175,8 @@ class AzureProvider(OAuthProxy):
173
175
  timeout_seconds: HTTP request timeout for Azure API calls
174
176
  allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
175
177
  If None (default), all URIs are allowed. If empty list, no URIs are allowed.
178
+ client_storage: Storage implementation for OAuth client registrations.
179
+ Defaults to file-based storage if not specified.
176
180
  """
177
181
  settings = AzureProviderSettings.model_validate(
178
182
  {
@@ -211,7 +215,6 @@ class AzureProvider(OAuthProxy):
211
215
  # Apply defaults
212
216
  tenant_id_final = settings.tenant_id
213
217
 
214
- redirect_path_final = settings.redirect_path or "/auth/callback"
215
218
  timeout_seconds_final = settings.timeout_seconds or 10
216
219
  # Default scopes for Azure - User.Read gives us access to user info via Graph API
217
220
  scopes_final = settings.required_scopes or [
@@ -249,9 +252,10 @@ class AzureProvider(OAuthProxy):
249
252
  upstream_client_secret=client_secret_str,
250
253
  token_verifier=token_verifier,
251
254
  base_url=settings.base_url,
252
- redirect_path=redirect_path_final,
255
+ redirect_path=settings.redirect_path,
253
256
  issuer_url=settings.base_url,
254
257
  allowed_client_redirect_uris=allowed_client_redirect_uris_final,
258
+ client_storage=client_storage,
255
259
  )
256
260
 
257
261
  logger.info(
@@ -0,0 +1,172 @@
1
+ """Descope authentication provider for FastMCP.
2
+
3
+ This module provides DescopeProvider - a complete authentication solution that integrates
4
+ with Descope's OAuth 2.1 and OpenID Connect services, supporting Dynamic Client Registration (DCR)
5
+ for seamless MCP client authentication.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ import httpx
13
+ from pydantic import AnyHttpUrl
14
+ from pydantic_settings import BaseSettings, SettingsConfigDict
15
+ from starlette.responses import JSONResponse
16
+ from starlette.routing import Route
17
+
18
+ from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier
19
+ from fastmcp.server.auth.providers.jwt import JWTVerifier
20
+ from fastmcp.utilities.logging import get_logger
21
+ from fastmcp.utilities.types import NotSet, NotSetT
22
+
23
+ logger = get_logger(__name__)
24
+
25
+
26
+ class DescopeProviderSettings(BaseSettings):
27
+ model_config = SettingsConfigDict(
28
+ env_prefix="FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_",
29
+ env_file=".env",
30
+ extra="ignore",
31
+ )
32
+
33
+ project_id: str
34
+ base_url: AnyHttpUrl
35
+ descope_base_url: AnyHttpUrl = AnyHttpUrl("https://api.descope.com")
36
+
37
+
38
+ class DescopeProvider(RemoteAuthProvider):
39
+ """Descope metadata provider for DCR (Dynamic Client Registration).
40
+
41
+ This provider implements Descope integration using metadata forwarding.
42
+ This is the recommended approach for Descope DCR
43
+ as it allows Descope to handle the OAuth flow directly while FastMCP acts
44
+ as a resource server.
45
+
46
+ IMPORTANT SETUP REQUIREMENTS:
47
+
48
+ 1. Enable Dynamic Client Registration in Descope Console:
49
+ - Go to the [Inbound Apps page](https://app.descope.com/apps/inbound) of the Descope Console
50
+ - Click **DCR Settings**
51
+ - Enable **Dynamic Client Registration (DCR)**
52
+ - Define allowed scopes
53
+
54
+ 2. Note your Project ID:
55
+ - Save your Project ID from [Project Settings](https://app.descope.com/settings/project)
56
+ - Example: P2abc...123
57
+
58
+ For detailed setup instructions, see:
59
+ https://docs.descope.com/identity-federation/inbound-apps/creating-inbound-apps#method-2-dynamic-client-registration-dcr
60
+
61
+ Example:
62
+ ```python
63
+ from fastmcp.server.auth.providers.descope import DescopeProvider
64
+
65
+ # Create Descope metadata provider (JWT verifier created automatically)
66
+ descope_auth = DescopeProvider(
67
+ project_id="P2abc...123",
68
+ base_url="https://your-fastmcp-server.com",
69
+ descope_base_url="https://api.descope.com",
70
+ )
71
+
72
+ # Use with FastMCP
73
+ mcp = FastMCP("My App", auth=descope_auth)
74
+ ```
75
+ """
76
+
77
+ def __init__(
78
+ self,
79
+ *,
80
+ project_id: str | NotSetT = NotSet,
81
+ base_url: AnyHttpUrl | str | NotSetT = NotSet,
82
+ descope_base_url: AnyHttpUrl | str | NotSetT = NotSet,
83
+ token_verifier: TokenVerifier | None = None,
84
+ ):
85
+ """Initialize Descope metadata provider.
86
+
87
+ Args:
88
+ project_id: Your Descope Project ID (e.g., "P2abc...123")
89
+ base_url: Public URL of this FastMCP server
90
+ descope_base_url: Descope API base URL (defaults to https://api.descope.com)
91
+ token_verifier: Optional token verifier. If None, creates JWT verifier for Descope
92
+ """
93
+ settings = DescopeProviderSettings.model_validate(
94
+ {
95
+ k: v
96
+ for k, v in {
97
+ "project_id": project_id,
98
+ "base_url": base_url,
99
+ "descope_base_url": descope_base_url,
100
+ }.items()
101
+ if v is not NotSet
102
+ }
103
+ )
104
+
105
+ self.project_id = settings.project_id
106
+ self.base_url = str(settings.base_url).rstrip("/")
107
+ self.descope_base_url = str(settings.descope_base_url).rstrip("/")
108
+
109
+ # Create default JWT verifier if none provided
110
+ if token_verifier is None:
111
+ token_verifier = JWTVerifier(
112
+ jwks_uri=f"{self.descope_base_url}/{self.project_id}/.well-known/jwks.json",
113
+ issuer=f"{self.descope_base_url}/v1/apps/{self.project_id}",
114
+ algorithm="RS256",
115
+ audience=self.project_id,
116
+ )
117
+
118
+ # Initialize RemoteAuthProvider with Descope as the authorization server
119
+ super().__init__(
120
+ token_verifier=token_verifier,
121
+ authorization_servers=[
122
+ AnyHttpUrl(f"{self.descope_base_url}/v1/apps/{self.project_id}")
123
+ ],
124
+ base_url=self.base_url,
125
+ )
126
+
127
+ def get_routes(
128
+ self,
129
+ mcp_path: str | None = None,
130
+ mcp_endpoint: Any | None = None,
131
+ ) -> list[Route]:
132
+ """Get OAuth routes including Descope authorization server metadata forwarding.
133
+
134
+ This returns the standard protected resource routes plus an authorization server
135
+ metadata endpoint that forwards Descope's OAuth metadata to clients.
136
+
137
+ Args:
138
+ mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
139
+ mcp_endpoint: The MCP endpoint handler to protect with auth
140
+ """
141
+ # Get the standard protected resource routes from RemoteAuthProvider
142
+ routes = super().get_routes(mcp_path, mcp_endpoint)
143
+
144
+ async def oauth_authorization_server_metadata(request):
145
+ """Forward Descope OAuth authorization server metadata with FastMCP customizations."""
146
+ try:
147
+ async with httpx.AsyncClient() as client:
148
+ response = await client.get(
149
+ f"{self.descope_base_url}/v1/apps/{self.project_id}/.well-known/oauth-authorization-server"
150
+ )
151
+ response.raise_for_status()
152
+ metadata = response.json()
153
+ return JSONResponse(metadata)
154
+ except Exception as e:
155
+ return JSONResponse(
156
+ {
157
+ "error": "server_error",
158
+ "error_description": f"Failed to fetch Descope metadata: {e}",
159
+ },
160
+ status_code=500,
161
+ )
162
+
163
+ # Add Descope authorization server metadata forwarding
164
+ routes.append(
165
+ Route(
166
+ "/.well-known/oauth-authorization-server",
167
+ endpoint=oauth_authorization_server_metadata,
168
+ methods=["GET"],
169
+ )
170
+ )
171
+
172
+ return routes
@@ -30,6 +30,7 @@ from fastmcp.server.auth.auth import AccessToken
30
30
  from fastmcp.server.auth.oauth_proxy import OAuthProxy
31
31
  from fastmcp.utilities.auth import parse_scopes
32
32
  from fastmcp.utilities.logging import get_logger
33
+ from fastmcp.utilities.storage import KVStorage
33
34
  from fastmcp.utilities.types import NotSet, NotSetT
34
35
 
35
36
  logger = get_logger(__name__)
@@ -201,6 +202,7 @@ class GitHubProvider(OAuthProxy):
201
202
  required_scopes: list[str] | NotSetT = NotSet,
202
203
  timeout_seconds: int | NotSetT = NotSet,
203
204
  allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
205
+ client_storage: KVStorage | None = None,
204
206
  ):
205
207
  """Initialize GitHub OAuth provider.
206
208
 
@@ -213,6 +215,8 @@ class GitHubProvider(OAuthProxy):
213
215
  timeout_seconds: HTTP request timeout for GitHub API calls
214
216
  allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
215
217
  If None (default), all URIs are allowed. If empty list, no URIs are allowed.
218
+ client_storage: Storage implementation for OAuth client registrations.
219
+ Defaults to file-based storage if not specified.
216
220
  """
217
221
 
218
222
  settings = GitHubProviderSettings.model_validate(
@@ -243,7 +247,6 @@ class GitHubProvider(OAuthProxy):
243
247
 
244
248
  # Apply defaults
245
249
 
246
- redirect_path_final = settings.redirect_path or "/auth/callback"
247
250
  timeout_seconds_final = settings.timeout_seconds or 10
248
251
  required_scopes_final = settings.required_scopes or ["user"]
249
252
  allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
@@ -267,9 +270,10 @@ class GitHubProvider(OAuthProxy):
267
270
  upstream_client_secret=client_secret_str,
268
271
  token_verifier=token_verifier,
269
272
  base_url=settings.base_url,
270
- redirect_path=redirect_path_final,
273
+ redirect_path=settings.redirect_path,
271
274
  issuer_url=settings.base_url, # We act as the issuer for client registration
272
275
  allowed_client_redirect_uris=allowed_client_redirect_uris_final,
276
+ client_storage=client_storage,
273
277
  )
274
278
 
275
279
  logger.info(
@@ -32,6 +32,7 @@ from fastmcp.server.auth.auth import AccessToken
32
32
  from fastmcp.server.auth.oauth_proxy import OAuthProxy
33
33
  from fastmcp.utilities.auth import parse_scopes
34
34
  from fastmcp.utilities.logging import get_logger
35
+ from fastmcp.utilities.storage import KVStorage
35
36
  from fastmcp.utilities.types import NotSet, NotSetT
36
37
 
37
38
  logger = get_logger(__name__)
@@ -217,6 +218,7 @@ class GoogleProvider(OAuthProxy):
217
218
  required_scopes: list[str] | NotSetT = NotSet,
218
219
  timeout_seconds: int | NotSetT = NotSet,
219
220
  allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
221
+ client_storage: KVStorage | None = None,
220
222
  ):
221
223
  """Initialize Google OAuth provider.
222
224
 
@@ -232,6 +234,8 @@ class GoogleProvider(OAuthProxy):
232
234
  timeout_seconds: HTTP request timeout for Google API calls
233
235
  allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
234
236
  If None (default), all URIs are allowed. If empty list, no URIs are allowed.
237
+ client_storage: Storage implementation for OAuth client registrations.
238
+ Defaults to file-based storage if not specified.
235
239
  """
236
240
 
237
241
  settings = GoogleProviderSettings.model_validate(
@@ -261,7 +265,6 @@ class GoogleProvider(OAuthProxy):
261
265
  )
262
266
 
263
267
  # Apply defaults
264
- redirect_path_final = settings.redirect_path or "/auth/callback"
265
268
  timeout_seconds_final = settings.timeout_seconds or 10
266
269
  # Google requires at least one scope - openid is the minimal OIDC scope
267
270
  required_scopes_final = settings.required_scopes or ["openid"]
@@ -286,9 +289,10 @@ class GoogleProvider(OAuthProxy):
286
289
  upstream_client_secret=client_secret_str,
287
290
  token_verifier=token_verifier,
288
291
  base_url=settings.base_url,
289
- redirect_path=redirect_path_final,
292
+ redirect_path=settings.redirect_path,
290
293
  issuer_url=settings.base_url, # We act as the issuer for client registration
291
294
  allowed_client_redirect_uris=allowed_client_redirect_uris_final,
295
+ client_storage=client_storage,
292
296
  )
293
297
 
294
298
  logger.info(
@@ -23,6 +23,7 @@ from fastmcp.server.auth.oauth_proxy import OAuthProxy
23
23
  from fastmcp.server.auth.providers.jwt import JWTVerifier
24
24
  from fastmcp.utilities.auth import parse_scopes
25
25
  from fastmcp.utilities.logging import get_logger
26
+ from fastmcp.utilities.storage import KVStorage
26
27
  from fastmcp.utilities.types import NotSet, NotSetT
27
28
 
28
29
  logger = get_logger(__name__)
@@ -169,6 +170,7 @@ class WorkOSProvider(OAuthProxy):
169
170
  required_scopes: list[str] | None | NotSetT = NotSet,
170
171
  timeout_seconds: int | NotSetT = NotSet,
171
172
  allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
173
+ client_storage: KVStorage | None = None,
172
174
  ):
173
175
  """Initialize WorkOS OAuth provider.
174
176
 
@@ -182,6 +184,8 @@ class WorkOSProvider(OAuthProxy):
182
184
  timeout_seconds: HTTP request timeout for WorkOS API calls
183
185
  allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
184
186
  If None (default), all URIs are allowed. If empty list, no URIs are allowed.
187
+ client_storage: Storage implementation for OAuth client registrations.
188
+ Defaults to file-based storage if not specified.
185
189
  """
186
190
 
187
191
  settings = WorkOSProviderSettings.model_validate(
@@ -220,7 +224,6 @@ class WorkOSProvider(OAuthProxy):
220
224
  if not authkit_domain_str.startswith(("http://", "https://")):
221
225
  authkit_domain_str = f"https://{authkit_domain_str}"
222
226
  authkit_domain_final = authkit_domain_str.rstrip("/")
223
- redirect_path_final = settings.redirect_path or "/auth/callback"
224
227
  timeout_seconds_final = settings.timeout_seconds or 10
225
228
  scopes_final = settings.required_scopes or []
226
229
  allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
@@ -245,9 +248,10 @@ class WorkOSProvider(OAuthProxy):
245
248
  upstream_client_secret=client_secret_str,
246
249
  token_verifier=token_verifier,
247
250
  base_url=settings.base_url,
248
- redirect_path=redirect_path_final,
251
+ redirect_path=settings.redirect_path,
249
252
  issuer_url=settings.base_url,
250
253
  allowed_client_redirect_uris=allowed_client_redirect_uris_final,
254
+ client_storage=client_storage,
251
255
  )
252
256
 
253
257
  logger.info(
fastmcp/server/context.py CHANGED
@@ -5,7 +5,7 @@ import copy
5
5
  import inspect
6
6
  import warnings
7
7
  import weakref
8
- from collections.abc import Generator, Mapping
8
+ from collections.abc import Generator, Mapping, Sequence
9
9
  from contextlib import contextmanager
10
10
  from contextvars import ContextVar, Token
11
11
  from dataclasses import dataclass
@@ -17,9 +17,10 @@ from mcp.server.lowlevel.helper_types import ReadResourceContents
17
17
  from mcp.server.lowlevel.server import request_ctx
18
18
  from mcp.shared.context import RequestContext
19
19
  from mcp.types import (
20
+ AudioContent,
20
21
  ClientCapabilities,
21
- ContentBlock,
22
22
  CreateMessageResult,
23
+ ImageContent,
23
24
  IncludeContext,
24
25
  ModelHint,
25
26
  ModelPreferences,
@@ -359,13 +360,13 @@ class Context:
359
360
 
360
361
  async def sample(
361
362
  self,
362
- messages: str | list[str | SamplingMessage],
363
+ messages: str | Sequence[str | SamplingMessage],
363
364
  system_prompt: str | None = None,
364
365
  include_context: IncludeContext | None = None,
365
366
  temperature: float | None = None,
366
367
  max_tokens: int | None = None,
367
368
  model_preferences: ModelPreferences | str | list[str] | None = None,
368
- ) -> ContentBlock:
369
+ ) -> TextContent | ImageContent | AudioContent:
369
370
  """
370
371
  Send a sampling request to the client and await the response.
371
372
 
@@ -383,7 +384,7 @@ class Context:
383
384
  content=TextContent(text=messages, type="text"), role="user"
384
385
  )
385
386
  ]
386
- elif isinstance(messages, list):
387
+ elif isinstance(messages, Sequence):
387
388
  sampling_messages = [
388
389
  SamplingMessage(content=TextContent(text=m, type="text"), role="user")
389
390
  if isinstance(m, str)
@@ -573,7 +574,7 @@ class Context:
573
574
  warnings.warn(
574
575
  "Context.get_http_request() is deprecated and will be removed in a future version. "
575
576
  "Use get_http_request() from fastmcp.server.dependencies instead. "
576
- "See https://gofastmcp.com/patterns/http-requests for more details.",
577
+ "See https://gofastmcp.com/servers/context#http-requests for more details.",
577
578
  DeprecationWarning,
578
579
  stacklevel=2,
579
580
  )
fastmcp/server/http.py CHANGED
@@ -113,7 +113,7 @@ def create_base_app(
113
113
  A Starlette application
114
114
  """
115
115
  # Always add RequestContextMiddleware as the outermost middleware
116
- middleware.append(Middleware(RequestContextMiddleware))
116
+ middleware.insert(0, Middleware(RequestContextMiddleware))
117
117
 
118
118
  return StarletteWithLifespan(
119
119
  routes=routes,