fastmcp 2.12.2__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 (54) hide show
  1. fastmcp/cli/claude.py +1 -10
  2. fastmcp/cli/cli.py +45 -25
  3. fastmcp/cli/install/__init__.py +2 -0
  4. fastmcp/cli/install/claude_code.py +1 -10
  5. fastmcp/cli/install/claude_desktop.py +1 -9
  6. fastmcp/cli/install/cursor.py +2 -18
  7. fastmcp/cli/install/gemini_cli.py +241 -0
  8. fastmcp/cli/install/mcp_json.py +1 -9
  9. fastmcp/cli/run.py +2 -86
  10. fastmcp/client/auth/oauth.py +50 -37
  11. fastmcp/client/client.py +18 -8
  12. fastmcp/client/elicitation.py +6 -1
  13. fastmcp/client/transports.py +1 -1
  14. fastmcp/contrib/component_manager/component_service.py +1 -1
  15. fastmcp/contrib/mcp_mixin/README.md +3 -3
  16. fastmcp/contrib/mcp_mixin/mcp_mixin.py +41 -6
  17. fastmcp/experimental/utilities/openapi/director.py +8 -1
  18. fastmcp/experimental/utilities/openapi/schemas.py +31 -5
  19. fastmcp/prompts/prompt.py +10 -8
  20. fastmcp/resources/resource.py +14 -11
  21. fastmcp/resources/template.py +12 -10
  22. fastmcp/server/auth/auth.py +10 -4
  23. fastmcp/server/auth/oauth_proxy.py +93 -23
  24. fastmcp/server/auth/oidc_proxy.py +348 -0
  25. fastmcp/server/auth/providers/auth0.py +174 -0
  26. fastmcp/server/auth/providers/aws.py +237 -0
  27. fastmcp/server/auth/providers/azure.py +6 -2
  28. fastmcp/server/auth/providers/descope.py +172 -0
  29. fastmcp/server/auth/providers/github.py +6 -2
  30. fastmcp/server/auth/providers/google.py +6 -2
  31. fastmcp/server/auth/providers/workos.py +6 -2
  32. fastmcp/server/context.py +17 -16
  33. fastmcp/server/dependencies.py +18 -5
  34. fastmcp/server/http.py +1 -1
  35. fastmcp/server/middleware/logging.py +147 -116
  36. fastmcp/server/middleware/middleware.py +3 -2
  37. fastmcp/server/openapi.py +5 -1
  38. fastmcp/server/server.py +43 -36
  39. fastmcp/settings.py +42 -6
  40. fastmcp/tools/tool.py +105 -87
  41. fastmcp/tools/tool_transform.py +1 -1
  42. fastmcp/utilities/json_schema.py +18 -1
  43. fastmcp/utilities/logging.py +66 -4
  44. fastmcp/utilities/mcp_server_config/v1/environments/uv.py +4 -39
  45. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -2
  46. fastmcp/utilities/mcp_server_config/v1/schema.json +2 -1
  47. fastmcp/utilities/storage.py +204 -0
  48. fastmcp/utilities/tests.py +8 -6
  49. fastmcp/utilities/types.py +9 -5
  50. {fastmcp-2.12.2.dist-info → fastmcp-2.12.4.dist-info}/METADATA +121 -48
  51. {fastmcp-2.12.2.dist-info → fastmcp-2.12.4.dist-info}/RECORD +54 -48
  52. {fastmcp-2.12.2.dist-info → fastmcp-2.12.4.dist-info}/WHEEL +0 -0
  53. {fastmcp-2.12.2.dist-info → fastmcp-2.12.4.dist-info}/entry_points.txt +0 -0
  54. {fastmcp-2.12.2.dist-info → fastmcp-2.12.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,174 @@
1
+ """Auth0 OAuth provider for FastMCP.
2
+
3
+ This module provides a complete Auth0 integration that's ready to use with
4
+ just the configuration URL, client ID, client secret, audience, and base URL.
5
+
6
+ Example:
7
+ ```python
8
+ from fastmcp import FastMCP
9
+ from fastmcp.server.auth.providers.auth0 import Auth0Provider
10
+
11
+ # Simple Auth0 OAuth protection
12
+ auth = Auth0Provider(
13
+ config_url="https://auth0.config.url",
14
+ client_id="your-auth0-client-id",
15
+ client_secret="your-auth0-client-secret",
16
+ audience="your-auth0-api-audience",
17
+ base_url="http://localhost:8000",
18
+ )
19
+
20
+ mcp = FastMCP("My Protected Server", auth=auth)
21
+ ```
22
+ """
23
+
24
+ from pydantic import AnyHttpUrl, SecretStr, field_validator
25
+ from pydantic_settings import BaseSettings, SettingsConfigDict
26
+
27
+ from fastmcp.server.auth.oidc_proxy import OIDCProxy
28
+ from fastmcp.utilities.auth import parse_scopes
29
+ from fastmcp.utilities.logging import get_logger
30
+ from fastmcp.utilities.storage import KVStorage
31
+ from fastmcp.utilities.types import NotSet, NotSetT
32
+
33
+ logger = get_logger(__name__)
34
+
35
+
36
+ class Auth0ProviderSettings(BaseSettings):
37
+ """Settings for Auth0 OIDC provider."""
38
+
39
+ model_config = SettingsConfigDict(
40
+ env_prefix="FASTMCP_SERVER_AUTH_AUTH0_",
41
+ env_file=".env",
42
+ extra="ignore",
43
+ )
44
+
45
+ config_url: AnyHttpUrl | None = None
46
+ client_id: str | None = None
47
+ client_secret: SecretStr | None = None
48
+ audience: str | None = None
49
+ base_url: AnyHttpUrl | None = None
50
+ redirect_path: str | None = None
51
+ required_scopes: list[str] | None = None
52
+ allowed_client_redirect_uris: list[str] | None = None
53
+
54
+ @field_validator("required_scopes", mode="before")
55
+ @classmethod
56
+ def _parse_scopes(cls, v):
57
+ return parse_scopes(v)
58
+
59
+
60
+ class Auth0Provider(OIDCProxy):
61
+ """An Auth0 provider implementation for FastMCP.
62
+
63
+ This provider is a complete Auth0 integration that's ready to use with
64
+ just the configuration URL, client ID, client secret, audience, and base URL.
65
+
66
+ Example:
67
+ ```python
68
+ from fastmcp import FastMCP
69
+ from fastmcp.server.auth.providers.auth0 import Auth0Provider
70
+
71
+ # Simple Auth0 OAuth protection
72
+ auth = Auth0Provider(
73
+ config_url="https://auth0.config.url",
74
+ client_id="your-auth0-client-id",
75
+ client_secret="your-auth0-client-secret",
76
+ audience="your-auth0-api-audience",
77
+ base_url="http://localhost:8000",
78
+ )
79
+
80
+ mcp = FastMCP("My Protected Server", auth=auth)
81
+ ```
82
+ """
83
+
84
+ def __init__(
85
+ self,
86
+ *,
87
+ config_url: AnyHttpUrl | str | NotSetT = NotSet,
88
+ client_id: str | NotSetT = NotSet,
89
+ client_secret: str | NotSetT = NotSet,
90
+ audience: str | NotSetT = NotSet,
91
+ base_url: AnyHttpUrl | str | NotSetT = NotSet,
92
+ required_scopes: list[str] | NotSetT = NotSet,
93
+ redirect_path: str | NotSetT = NotSet,
94
+ allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
95
+ client_storage: KVStorage | None = None,
96
+ ) -> None:
97
+ """Initialize Auth0 OAuth provider.
98
+
99
+ Args:
100
+ config_url: Auth0 config URL
101
+ client_id: Auth0 application client id
102
+ client_secret: Auth0 application client secret
103
+ audience: Auth0 API audience
104
+ base_url: Public URL of your FastMCP server (for OAuth callbacks)
105
+ required_scopes: Required Auth0 scopes (defaults to ["openid"])
106
+ redirect_path: Redirect path configured in Auth0 application
107
+ allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
108
+ If None (default), all URIs are allowed. If empty list, no URIs are allowed.
109
+ client_storage: Storage implementation for OAuth client registrations.
110
+ Defaults to file-based storage if not specified.
111
+ """
112
+ settings = Auth0ProviderSettings.model_validate(
113
+ {
114
+ k: v
115
+ for k, v in {
116
+ "config_url": config_url,
117
+ "client_id": client_id,
118
+ "client_secret": client_secret,
119
+ "audience": audience,
120
+ "base_url": base_url,
121
+ "required_scopes": required_scopes,
122
+ "redirect_path": redirect_path,
123
+ "allowed_client_redirect_uris": allowed_client_redirect_uris,
124
+ }.items()
125
+ if v is not NotSet
126
+ }
127
+ )
128
+
129
+ if not settings.config_url:
130
+ raise ValueError(
131
+ "config_url is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL"
132
+ )
133
+
134
+ if not settings.client_id:
135
+ raise ValueError(
136
+ "client_id is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID"
137
+ )
138
+
139
+ if not settings.client_secret:
140
+ raise ValueError(
141
+ "client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET"
142
+ )
143
+
144
+ if not settings.audience:
145
+ raise ValueError(
146
+ "audience is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE"
147
+ )
148
+
149
+ if not settings.base_url:
150
+ raise ValueError(
151
+ "base_url is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_BASE_URL"
152
+ )
153
+
154
+ auth0_required_scopes = settings.required_scopes or ["openid"]
155
+
156
+ init_kwargs = {
157
+ "config_url": settings.config_url,
158
+ "client_id": settings.client_id,
159
+ "client_secret": settings.client_secret.get_secret_value(),
160
+ "audience": settings.audience,
161
+ "base_url": settings.base_url,
162
+ "redirect_path": settings.redirect_path,
163
+ "required_scopes": auth0_required_scopes,
164
+ "allowed_client_redirect_uris": settings.allowed_client_redirect_uris,
165
+ "client_storage": client_storage,
166
+ }
167
+
168
+ super().__init__(**init_kwargs)
169
+
170
+ logger.info(
171
+ "Initialized Auth0 OAuth provider for client %s with scopes: %s",
172
+ settings.client_id,
173
+ auth0_required_scopes,
174
+ )
@@ -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(