fastmcp 2.12.1__py3-none-any.whl → 2.13.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.
Files changed (109) hide show
  1. fastmcp/__init__.py +2 -2
  2. fastmcp/cli/cli.py +56 -36
  3. fastmcp/cli/install/__init__.py +2 -0
  4. fastmcp/cli/install/claude_code.py +7 -16
  5. fastmcp/cli/install/claude_desktop.py +4 -12
  6. fastmcp/cli/install/cursor.py +20 -30
  7. fastmcp/cli/install/gemini_cli.py +241 -0
  8. fastmcp/cli/install/mcp_json.py +4 -12
  9. fastmcp/cli/run.py +15 -94
  10. fastmcp/client/__init__.py +9 -9
  11. fastmcp/client/auth/oauth.py +117 -206
  12. fastmcp/client/client.py +123 -47
  13. fastmcp/client/elicitation.py +6 -1
  14. fastmcp/client/logging.py +18 -14
  15. fastmcp/client/oauth_callback.py +85 -171
  16. fastmcp/client/sampling.py +1 -1
  17. fastmcp/client/transports.py +81 -26
  18. fastmcp/contrib/component_manager/__init__.py +1 -1
  19. fastmcp/contrib/component_manager/component_manager.py +2 -2
  20. fastmcp/contrib/component_manager/component_service.py +7 -7
  21. fastmcp/contrib/mcp_mixin/README.md +35 -4
  22. fastmcp/contrib/mcp_mixin/__init__.py +2 -2
  23. fastmcp/contrib/mcp_mixin/mcp_mixin.py +54 -7
  24. fastmcp/experimental/sampling/handlers/openai.py +2 -2
  25. fastmcp/experimental/server/openapi/__init__.py +5 -8
  26. fastmcp/experimental/server/openapi/components.py +11 -7
  27. fastmcp/experimental/server/openapi/routing.py +2 -2
  28. fastmcp/experimental/utilities/openapi/__init__.py +10 -15
  29. fastmcp/experimental/utilities/openapi/director.py +16 -10
  30. fastmcp/experimental/utilities/openapi/json_schema_converter.py +6 -2
  31. fastmcp/experimental/utilities/openapi/models.py +3 -3
  32. fastmcp/experimental/utilities/openapi/parser.py +37 -16
  33. fastmcp/experimental/utilities/openapi/schemas.py +33 -7
  34. fastmcp/mcp_config.py +3 -4
  35. fastmcp/prompts/__init__.py +1 -1
  36. fastmcp/prompts/prompt.py +32 -27
  37. fastmcp/prompts/prompt_manager.py +16 -101
  38. fastmcp/resources/__init__.py +5 -5
  39. fastmcp/resources/resource.py +28 -20
  40. fastmcp/resources/resource_manager.py +9 -168
  41. fastmcp/resources/template.py +119 -27
  42. fastmcp/resources/types.py +30 -24
  43. fastmcp/server/__init__.py +1 -1
  44. fastmcp/server/auth/__init__.py +9 -5
  45. fastmcp/server/auth/auth.py +80 -47
  46. fastmcp/server/auth/handlers/authorize.py +326 -0
  47. fastmcp/server/auth/jwt_issuer.py +236 -0
  48. fastmcp/server/auth/middleware.py +96 -0
  49. fastmcp/server/auth/oauth_proxy.py +1556 -265
  50. fastmcp/server/auth/oidc_proxy.py +412 -0
  51. fastmcp/server/auth/providers/auth0.py +193 -0
  52. fastmcp/server/auth/providers/aws.py +263 -0
  53. fastmcp/server/auth/providers/azure.py +314 -129
  54. fastmcp/server/auth/providers/bearer.py +1 -1
  55. fastmcp/server/auth/providers/debug.py +114 -0
  56. fastmcp/server/auth/providers/descope.py +229 -0
  57. fastmcp/server/auth/providers/discord.py +308 -0
  58. fastmcp/server/auth/providers/github.py +31 -6
  59. fastmcp/server/auth/providers/google.py +50 -7
  60. fastmcp/server/auth/providers/in_memory.py +27 -3
  61. fastmcp/server/auth/providers/introspection.py +281 -0
  62. fastmcp/server/auth/providers/jwt.py +48 -31
  63. fastmcp/server/auth/providers/oci.py +233 -0
  64. fastmcp/server/auth/providers/scalekit.py +238 -0
  65. fastmcp/server/auth/providers/supabase.py +188 -0
  66. fastmcp/server/auth/providers/workos.py +37 -15
  67. fastmcp/server/context.py +194 -67
  68. fastmcp/server/dependencies.py +56 -16
  69. fastmcp/server/elicitation.py +1 -1
  70. fastmcp/server/http.py +57 -18
  71. fastmcp/server/low_level.py +121 -2
  72. fastmcp/server/middleware/__init__.py +1 -1
  73. fastmcp/server/middleware/caching.py +476 -0
  74. fastmcp/server/middleware/error_handling.py +14 -10
  75. fastmcp/server/middleware/logging.py +158 -116
  76. fastmcp/server/middleware/middleware.py +30 -16
  77. fastmcp/server/middleware/rate_limiting.py +3 -3
  78. fastmcp/server/middleware/tool_injection.py +116 -0
  79. fastmcp/server/openapi.py +15 -7
  80. fastmcp/server/proxy.py +22 -11
  81. fastmcp/server/server.py +744 -254
  82. fastmcp/settings.py +65 -15
  83. fastmcp/tools/__init__.py +1 -1
  84. fastmcp/tools/tool.py +173 -108
  85. fastmcp/tools/tool_manager.py +30 -112
  86. fastmcp/tools/tool_transform.py +13 -11
  87. fastmcp/utilities/cli.py +67 -28
  88. fastmcp/utilities/components.py +7 -2
  89. fastmcp/utilities/inspect.py +79 -23
  90. fastmcp/utilities/json_schema.py +21 -4
  91. fastmcp/utilities/json_schema_type.py +4 -4
  92. fastmcp/utilities/logging.py +182 -10
  93. fastmcp/utilities/mcp_server_config/__init__.py +3 -3
  94. fastmcp/utilities/mcp_server_config/v1/environments/base.py +1 -2
  95. fastmcp/utilities/mcp_server_config/v1/environments/uv.py +10 -45
  96. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +8 -7
  97. fastmcp/utilities/mcp_server_config/v1/schema.json +5 -1
  98. fastmcp/utilities/mcp_server_config/v1/sources/base.py +0 -1
  99. fastmcp/utilities/openapi.py +11 -11
  100. fastmcp/utilities/tests.py +93 -10
  101. fastmcp/utilities/types.py +87 -21
  102. fastmcp/utilities/ui.py +626 -0
  103. {fastmcp-2.12.1.dist-info → fastmcp-2.13.2.dist-info}/METADATA +141 -60
  104. fastmcp-2.13.2.dist-info/RECORD +144 -0
  105. {fastmcp-2.12.1.dist-info → fastmcp-2.13.2.dist-info}/WHEEL +1 -1
  106. fastmcp/cli/claude.py +0 -144
  107. fastmcp-2.12.1.dist-info/RECORD +0 -128
  108. {fastmcp-2.12.1.dist-info → fastmcp-2.13.2.dist-info}/entry_points.txt +0 -0
  109. {fastmcp-2.12.1.dist-info → fastmcp-2.13.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,412 @@
1
+ """OIDC Proxy Provider for FastMCP.
2
+
3
+ This provider acts as a transparent proxy to an upstream OIDC compliant Authorization
4
+ Server. It leverages the OAuthProxy class to handle Dynamic Client Registration and
5
+ forwarding of all OAuth flows.
6
+
7
+ This implementation is based on:
8
+ OpenID Connect Discovery 1.0 - https://openid.net/specs/openid-connect-discovery-1_0.html
9
+ OAuth 2.0 Authorization Server Metadata - https://datatracker.ietf.org/doc/html/rfc8414
10
+ """
11
+
12
+ from collections.abc import Sequence
13
+
14
+ import httpx
15
+ from key_value.aio.protocols import AsyncKeyValue
16
+ from pydantic import AnyHttpUrl, BaseModel, model_validator
17
+ from typing_extensions import Self
18
+
19
+ from fastmcp.server.auth import TokenVerifier
20
+ from fastmcp.server.auth.oauth_proxy import OAuthProxy
21
+ from fastmcp.server.auth.providers.jwt import JWTVerifier
22
+ from fastmcp.utilities.logging import get_logger
23
+
24
+ logger = get_logger(__name__)
25
+
26
+
27
+ class OIDCConfiguration(BaseModel):
28
+ """OIDC Configuration.
29
+
30
+ See:
31
+ https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
32
+ https://datatracker.ietf.org/doc/html/rfc8414#section-2
33
+ """
34
+
35
+ strict: bool = True
36
+
37
+ # OpenID Connect Discovery 1.0
38
+ issuer: AnyHttpUrl | str | None = None # Strict
39
+
40
+ authorization_endpoint: AnyHttpUrl | str | None = None # Strict
41
+ token_endpoint: AnyHttpUrl | str | None = None # Strict
42
+ userinfo_endpoint: AnyHttpUrl | str | None = None
43
+
44
+ jwks_uri: AnyHttpUrl | str | None = None # Strict
45
+
46
+ registration_endpoint: AnyHttpUrl | str | None = None
47
+
48
+ scopes_supported: Sequence[str] | None = None
49
+
50
+ response_types_supported: Sequence[str] | None = None # Strict
51
+ response_modes_supported: Sequence[str] | None = None
52
+
53
+ grant_types_supported: Sequence[str] | None = None
54
+
55
+ acr_values_supported: Sequence[str] | None = None
56
+
57
+ subject_types_supported: Sequence[str] | None = None # Strict
58
+
59
+ id_token_signing_alg_values_supported: Sequence[str] | None = None # Strict
60
+ id_token_encryption_alg_values_supported: Sequence[str] | None = None
61
+ id_token_encryption_enc_values_supported: Sequence[str] | None = None
62
+
63
+ userinfo_signing_alg_values_supported: Sequence[str] | None = None
64
+ userinfo_encryption_alg_values_supported: Sequence[str] | None = None
65
+ userinfo_encryption_enc_values_supported: Sequence[str] | None = None
66
+
67
+ request_object_signing_alg_values_supported: Sequence[str] | None = None
68
+ request_object_encryption_alg_values_supported: Sequence[str] | None = None
69
+ request_object_encryption_enc_values_supported: Sequence[str] | None = None
70
+
71
+ token_endpoint_auth_methods_supported: Sequence[str] | None = None
72
+ token_endpoint_auth_signing_alg_values_supported: Sequence[str] | None = None
73
+
74
+ display_values_supported: Sequence[str] | None = None
75
+
76
+ claim_types_supported: Sequence[str] | None = None
77
+ claims_supported: Sequence[str] | None = None
78
+
79
+ service_documentation: AnyHttpUrl | str | None = None
80
+
81
+ claims_locales_supported: Sequence[str] | None = None
82
+ ui_locales_supported: Sequence[str] | None = None
83
+
84
+ claims_parameter_supported: bool | None = None
85
+ request_parameter_supported: bool | None = None
86
+ request_uri_parameter_supported: bool | None = None
87
+
88
+ require_request_uri_registration: bool | None = None
89
+
90
+ op_policy_uri: AnyHttpUrl | str | None = None
91
+ op_tos_uri: AnyHttpUrl | str | None = None
92
+
93
+ # OAuth 2.0 Authorization Server Metadata
94
+ revocation_endpoint: AnyHttpUrl | str | None = None
95
+ revocation_endpoint_auth_methods_supported: Sequence[str] | None = None
96
+ revocation_endpoint_auth_signing_alg_values_supported: Sequence[str] | None = None
97
+
98
+ introspection_endpoint: AnyHttpUrl | str | None = None
99
+ introspection_endpoint_auth_methods_supported: Sequence[str] | None = None
100
+ introspection_endpoint_auth_signing_alg_values_supported: Sequence[str] | None = (
101
+ None
102
+ )
103
+
104
+ code_challenge_methods_supported: Sequence[str] | None = None
105
+
106
+ signed_metadata: str | None = None
107
+
108
+ @model_validator(mode="after")
109
+ def _enforce_strict(self) -> Self:
110
+ """Enforce strict rules."""
111
+ if not self.strict:
112
+ return self
113
+
114
+ def enforce(attr: str, is_url: bool = False) -> None:
115
+ value = getattr(self, attr, None)
116
+ if not value:
117
+ message = f"Missing required configuration metadata: {attr}"
118
+ logger.error(message)
119
+ raise ValueError(message)
120
+
121
+ if not is_url or isinstance(value, AnyHttpUrl):
122
+ return
123
+
124
+ try:
125
+ AnyHttpUrl(value)
126
+ except Exception as e:
127
+ message = f"Invalid URL for configuration metadata: {attr}"
128
+ logger.error(message)
129
+ raise ValueError(message) from e
130
+
131
+ enforce("issuer", True)
132
+ enforce("authorization_endpoint", True)
133
+ enforce("token_endpoint", True)
134
+ enforce("jwks_uri", True)
135
+ enforce("response_types_supported")
136
+ enforce("subject_types_supported")
137
+ enforce("id_token_signing_alg_values_supported")
138
+
139
+ return self
140
+
141
+ @classmethod
142
+ def get_oidc_configuration(
143
+ cls, config_url: AnyHttpUrl, *, strict: bool | None, timeout_seconds: int | None
144
+ ) -> Self:
145
+ """Get the OIDC configuration for the specified config URL.
146
+
147
+ Args:
148
+ config_url: The OIDC config URL
149
+ strict: The strict flag for the configuration
150
+ timeout_seconds: HTTP request timeout in seconds
151
+ """
152
+ get_kwargs = {}
153
+ if timeout_seconds is not None:
154
+ get_kwargs["timeout"] = timeout_seconds
155
+
156
+ try:
157
+ response = httpx.get(str(config_url), **get_kwargs)
158
+ response.raise_for_status()
159
+
160
+ config_data = response.json()
161
+ if strict is not None:
162
+ config_data["strict"] = strict
163
+
164
+ return cls.model_validate(config_data)
165
+ except Exception:
166
+ logger.exception(
167
+ f"Unable to get OIDC configuration for config url: {config_url}"
168
+ )
169
+ raise
170
+
171
+
172
+ class OIDCProxy(OAuthProxy):
173
+ """OAuth provider that wraps OAuthProxy to provide configuration via an OIDC configuration URL.
174
+
175
+ This provider makes it easier to add OAuth protection for any upstream provider
176
+ that is OIDC compliant.
177
+
178
+ Example:
179
+ ```python
180
+ from fastmcp import FastMCP
181
+ from fastmcp.server.auth.oidc_proxy import OIDCProxy
182
+
183
+ # Simple OIDC based protection
184
+ auth = OIDCProxy(
185
+ config_url="https://oidc.config.url",
186
+ client_id="your-oidc-client-id",
187
+ client_secret="your-oidc-client-secret",
188
+ base_url="https://your.server.url",
189
+ )
190
+
191
+ mcp = FastMCP("My Protected Server", auth=auth)
192
+ ```
193
+ """
194
+
195
+ oidc_config: OIDCConfiguration
196
+
197
+ def __init__(
198
+ self,
199
+ *,
200
+ # OIDC configuration
201
+ config_url: AnyHttpUrl | str,
202
+ strict: bool | None = None,
203
+ # Upstream server configuration
204
+ client_id: str,
205
+ client_secret: str,
206
+ audience: str | None = None,
207
+ timeout_seconds: int | None = None,
208
+ # Token verifier
209
+ token_verifier: TokenVerifier | None = None,
210
+ algorithm: str | None = None,
211
+ required_scopes: list[str] | None = None,
212
+ # FastMCP server configuration
213
+ base_url: AnyHttpUrl | str,
214
+ issuer_url: AnyHttpUrl | str | None = None,
215
+ redirect_path: str | None = None,
216
+ # Client configuration
217
+ allowed_client_redirect_uris: list[str] | None = None,
218
+ client_storage: AsyncKeyValue | None = None,
219
+ # JWT and encryption keys
220
+ jwt_signing_key: str | bytes | None = None,
221
+ # Token validation configuration
222
+ token_endpoint_auth_method: str | None = None,
223
+ # Consent screen configuration
224
+ require_authorization_consent: bool = True,
225
+ consent_csp_policy: str | None = None,
226
+ # Extra parameters
227
+ extra_authorize_params: dict[str, str] | None = None,
228
+ extra_token_params: dict[str, str] | None = None,
229
+ ) -> None:
230
+ """Initialize the OIDC proxy provider.
231
+
232
+ Args:
233
+ config_url: URL of upstream configuration
234
+ strict: Optional strict flag for the configuration
235
+ client_id: Client ID registered with upstream server
236
+ client_secret: Client secret for upstream server
237
+ audience: Audience for upstream server
238
+ timeout_seconds: HTTP request timeout in seconds
239
+ token_verifier: Optional custom token verifier (e.g., IntrospectionTokenVerifier for opaque tokens).
240
+ If not provided, a JWTVerifier will be created using the OIDC configuration.
241
+ Cannot be used with algorithm or required_scopes parameters (configure these on your verifier instead).
242
+ algorithm: Token verifier algorithm (only used if token_verifier is not provided)
243
+ required_scopes: Required scopes for token validation (only used if token_verifier is not provided)
244
+ base_url: Public URL where OAuth endpoints will be accessible (includes any mount path)
245
+ issuer_url: Issuer URL for OAuth metadata (defaults to base_url). Use root-level URL
246
+ to avoid 404s during discovery when mounting under a path.
247
+ redirect_path: Redirect path configured in upstream OAuth app (defaults to "/auth/callback")
248
+ allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
249
+ Patterns support wildcards (e.g., "http://localhost:*", "https://*.example.com/*").
250
+ If None (default), only localhost redirect URIs are allowed.
251
+ If empty list, all redirect URIs are allowed (not recommended for production).
252
+ These are for MCP clients performing loopback redirects, NOT for the upstream OAuth app.
253
+ client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
254
+ If None, a DiskStore will be created in the data directory (derived from `platformdirs`). The
255
+ disk store will be encrypted using a key derived from the JWT Signing Key.
256
+ jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes). If bytes are provided,
257
+ they will be used as is. If a string is provided, it will be derived into a 32-byte key. If not
258
+ provided, the upstream client secret will be used to derive a 32-byte key using PBKDF2.
259
+ token_endpoint_auth_method: Token endpoint authentication method for upstream server.
260
+ Common values: "client_secret_basic", "client_secret_post", "none".
261
+ If None, authlib will use its default (typically "client_secret_basic").
262
+ require_authorization_consent: Whether to require user consent before authorizing clients (default True).
263
+ When True, users see a consent screen before being redirected to the upstream IdP.
264
+ When False, authorization proceeds directly without user confirmation.
265
+ SECURITY WARNING: Only disable for local development or testing environments.
266
+ consent_csp_policy: Content Security Policy for the consent page.
267
+ If None (default), uses the built-in CSP policy with appropriate directives.
268
+ If empty string "", disables CSP entirely (no meta tag is rendered).
269
+ If a non-empty string, uses that as the CSP policy value.
270
+ extra_authorize_params: Additional parameters to forward to the upstream authorization endpoint.
271
+ Useful for provider-specific parameters like prompt=consent or access_type=offline.
272
+ Example: {"prompt": "consent", "access_type": "offline"}
273
+ extra_token_params: Additional parameters to forward to the upstream token endpoint.
274
+ Useful for provider-specific parameters during token exchange.
275
+ """
276
+ if not config_url:
277
+ raise ValueError("Missing required config URL")
278
+
279
+ if not client_id:
280
+ raise ValueError("Missing required client id")
281
+
282
+ if not client_secret:
283
+ raise ValueError("Missing required client secret")
284
+
285
+ if not base_url:
286
+ raise ValueError("Missing required base URL")
287
+
288
+ # Validate that verifier-specific parameters are not used with custom verifier
289
+ if token_verifier is not None:
290
+ if algorithm is not None:
291
+ raise ValueError(
292
+ "Cannot specify 'algorithm' when providing a custom token_verifier. "
293
+ "Configure the algorithm on your token verifier instead."
294
+ )
295
+ if required_scopes is not None:
296
+ raise ValueError(
297
+ "Cannot specify 'required_scopes' when providing a custom token_verifier. "
298
+ "Configure required scopes on your token verifier instead."
299
+ )
300
+
301
+ if isinstance(config_url, str):
302
+ config_url = AnyHttpUrl(config_url)
303
+
304
+ self.oidc_config = self.get_oidc_configuration(
305
+ config_url, strict, timeout_seconds
306
+ )
307
+ if (
308
+ not self.oidc_config.authorization_endpoint
309
+ or not self.oidc_config.token_endpoint
310
+ ):
311
+ logger.debug(f"Invalid OIDC Configuration: {self.oidc_config}")
312
+ raise ValueError("Missing required OIDC endpoints")
313
+
314
+ revocation_endpoint = (
315
+ str(self.oidc_config.revocation_endpoint)
316
+ if self.oidc_config.revocation_endpoint
317
+ else None
318
+ )
319
+
320
+ # Use custom verifier if provided, otherwise create default JWTVerifier
321
+ if token_verifier is None:
322
+ token_verifier = self.get_token_verifier(
323
+ algorithm=algorithm,
324
+ audience=audience,
325
+ required_scopes=required_scopes,
326
+ timeout_seconds=timeout_seconds,
327
+ )
328
+
329
+ init_kwargs = {
330
+ "upstream_authorization_endpoint": str(
331
+ self.oidc_config.authorization_endpoint
332
+ ),
333
+ "upstream_token_endpoint": str(self.oidc_config.token_endpoint),
334
+ "upstream_client_id": client_id,
335
+ "upstream_client_secret": client_secret,
336
+ "upstream_revocation_endpoint": revocation_endpoint,
337
+ "token_verifier": token_verifier,
338
+ "base_url": base_url,
339
+ "issuer_url": issuer_url or base_url,
340
+ "service_documentation_url": self.oidc_config.service_documentation,
341
+ "allowed_client_redirect_uris": allowed_client_redirect_uris,
342
+ "client_storage": client_storage,
343
+ "jwt_signing_key": jwt_signing_key,
344
+ "token_endpoint_auth_method": token_endpoint_auth_method,
345
+ "require_authorization_consent": require_authorization_consent,
346
+ "consent_csp_policy": consent_csp_policy,
347
+ }
348
+
349
+ if redirect_path:
350
+ init_kwargs["redirect_path"] = redirect_path
351
+
352
+ # Build extra params, merging audience with user-provided params
353
+ # User params override audience if there's a conflict
354
+ final_authorize_params: dict[str, str] = {}
355
+ final_token_params: dict[str, str] = {}
356
+
357
+ if audience:
358
+ final_authorize_params["audience"] = audience
359
+ final_token_params["audience"] = audience
360
+
361
+ if extra_authorize_params:
362
+ final_authorize_params.update(extra_authorize_params)
363
+ if extra_token_params:
364
+ final_token_params.update(extra_token_params)
365
+
366
+ if final_authorize_params:
367
+ init_kwargs["extra_authorize_params"] = final_authorize_params
368
+ if final_token_params:
369
+ init_kwargs["extra_token_params"] = final_token_params
370
+
371
+ super().__init__(**init_kwargs) # ty: ignore[invalid-argument-type]
372
+
373
+ def get_oidc_configuration(
374
+ self,
375
+ config_url: AnyHttpUrl,
376
+ strict: bool | None,
377
+ timeout_seconds: int | None,
378
+ ) -> OIDCConfiguration:
379
+ """Gets the OIDC configuration for the specified configuration URL.
380
+
381
+ Args:
382
+ config_url: The OIDC configuration URL
383
+ strict: The strict flag for the configuration
384
+ timeout_seconds: HTTP request timeout in seconds
385
+ """
386
+ return OIDCConfiguration.get_oidc_configuration(
387
+ config_url, strict=strict, timeout_seconds=timeout_seconds
388
+ )
389
+
390
+ def get_token_verifier(
391
+ self,
392
+ *,
393
+ algorithm: str | None = None,
394
+ audience: str | None = None,
395
+ required_scopes: list[str] | None = None,
396
+ timeout_seconds: int | None = None,
397
+ ) -> TokenVerifier:
398
+ """Creates the token verifier for the specified OIDC configuration and arguments.
399
+
400
+ Args:
401
+ algorithm: Optional token verifier algorithm
402
+ audience: Optional token verifier audience
403
+ required_scopes: Optional token verifier required_scopes
404
+ timeout_seconds: HTTP request timeout in seconds
405
+ """
406
+ return JWTVerifier(
407
+ jwks_uri=str(self.oidc_config.jwks_uri),
408
+ issuer=str(self.oidc_config.issuer),
409
+ algorithm=algorithm,
410
+ audience=audience,
411
+ required_scopes=required_scopes,
412
+ )
@@ -0,0 +1,193 @@
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 key_value.aio.protocols import AsyncKeyValue
25
+ from pydantic import AnyHttpUrl, SecretStr, field_validator
26
+ from pydantic_settings import BaseSettings, SettingsConfigDict
27
+
28
+ from fastmcp.server.auth.oidc_proxy import OIDCProxy
29
+ from fastmcp.settings import ENV_FILE
30
+ from fastmcp.utilities.auth import parse_scopes
31
+ from fastmcp.utilities.logging import get_logger
32
+ from fastmcp.utilities.types import NotSet, NotSetT
33
+
34
+ logger = get_logger(__name__)
35
+
36
+
37
+ class Auth0ProviderSettings(BaseSettings):
38
+ """Settings for Auth0 OIDC provider."""
39
+
40
+ model_config = SettingsConfigDict(
41
+ env_prefix="FASTMCP_SERVER_AUTH_AUTH0_",
42
+ env_file=ENV_FILE,
43
+ extra="ignore",
44
+ )
45
+
46
+ config_url: AnyHttpUrl | None = None
47
+ client_id: str | None = None
48
+ client_secret: SecretStr | None = None
49
+ audience: str | None = None
50
+ base_url: AnyHttpUrl | None = None
51
+ issuer_url: AnyHttpUrl | None = None
52
+ redirect_path: str | None = None
53
+ required_scopes: list[str] | None = None
54
+ allowed_client_redirect_uris: list[str] | None = None
55
+ jwt_signing_key: str | None = None
56
+
57
+ @field_validator("required_scopes", mode="before")
58
+ @classmethod
59
+ def _parse_scopes(cls, v):
60
+ return parse_scopes(v)
61
+
62
+
63
+ class Auth0Provider(OIDCProxy):
64
+ """An Auth0 provider implementation for FastMCP.
65
+
66
+ This provider is a complete Auth0 integration that's ready to use with
67
+ just the configuration URL, client ID, client secret, audience, and base URL.
68
+
69
+ Example:
70
+ ```python
71
+ from fastmcp import FastMCP
72
+ from fastmcp.server.auth.providers.auth0 import Auth0Provider
73
+
74
+ # Simple Auth0 OAuth protection
75
+ auth = Auth0Provider(
76
+ config_url="https://auth0.config.url",
77
+ client_id="your-auth0-client-id",
78
+ client_secret="your-auth0-client-secret",
79
+ audience="your-auth0-api-audience",
80
+ base_url="http://localhost:8000",
81
+ )
82
+
83
+ mcp = FastMCP("My Protected Server", auth=auth)
84
+ ```
85
+ """
86
+
87
+ def __init__(
88
+ self,
89
+ *,
90
+ config_url: AnyHttpUrl | str | NotSetT = NotSet,
91
+ client_id: str | NotSetT = NotSet,
92
+ client_secret: str | NotSetT = NotSet,
93
+ audience: str | NotSetT = NotSet,
94
+ base_url: AnyHttpUrl | str | NotSetT = NotSet,
95
+ issuer_url: AnyHttpUrl | str | NotSetT = NotSet,
96
+ required_scopes: list[str] | NotSetT = NotSet,
97
+ redirect_path: str | NotSetT = NotSet,
98
+ allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
99
+ client_storage: AsyncKeyValue | None = None,
100
+ jwt_signing_key: str | bytes | NotSetT = NotSet,
101
+ require_authorization_consent: bool = True,
102
+ ) -> None:
103
+ """Initialize Auth0 OAuth provider.
104
+
105
+ Args:
106
+ config_url: Auth0 config URL
107
+ client_id: Auth0 application client id
108
+ client_secret: Auth0 application client secret
109
+ audience: Auth0 API audience
110
+ base_url: Public URL where OAuth endpoints will be accessible (includes any mount path)
111
+ issuer_url: Issuer URL for OAuth metadata (defaults to base_url). Use root-level URL
112
+ to avoid 404s during discovery when mounting under a path.
113
+ required_scopes: Required Auth0 scopes (defaults to ["openid"])
114
+ redirect_path: Redirect path configured in Auth0 application
115
+ allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
116
+ If None (default), all URIs are allowed. If empty list, no URIs are allowed.
117
+ client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
118
+ If None, a DiskStore will be created in the data directory (derived from `platformdirs`). The
119
+ disk store will be encrypted using a key derived from the JWT Signing Key.
120
+ jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes). If bytes are provided,
121
+ they will be used as is. If a string is provided, it will be derived into a 32-byte key. If not
122
+ provided, the upstream client secret will be used to derive a 32-byte key using PBKDF2.
123
+ require_authorization_consent: Whether to require user consent before authorizing clients (default True).
124
+ When True, users see a consent screen before being redirected to Auth0.
125
+ When False, authorization proceeds directly without user confirmation.
126
+ SECURITY WARNING: Only disable for local development or testing environments.
127
+ """
128
+ settings = Auth0ProviderSettings.model_validate(
129
+ {
130
+ k: v
131
+ for k, v in {
132
+ "config_url": config_url,
133
+ "client_id": client_id,
134
+ "client_secret": client_secret,
135
+ "audience": audience,
136
+ "base_url": base_url,
137
+ "issuer_url": issuer_url,
138
+ "required_scopes": required_scopes,
139
+ "redirect_path": redirect_path,
140
+ "allowed_client_redirect_uris": allowed_client_redirect_uris,
141
+ "jwt_signing_key": jwt_signing_key,
142
+ }.items()
143
+ if v is not NotSet
144
+ }
145
+ )
146
+
147
+ if not settings.config_url:
148
+ raise ValueError(
149
+ "config_url is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL"
150
+ )
151
+
152
+ if not settings.client_id:
153
+ raise ValueError(
154
+ "client_id is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID"
155
+ )
156
+
157
+ if not settings.client_secret:
158
+ raise ValueError(
159
+ "client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET"
160
+ )
161
+
162
+ if not settings.audience:
163
+ raise ValueError(
164
+ "audience is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE"
165
+ )
166
+
167
+ if not settings.base_url:
168
+ raise ValueError(
169
+ "base_url is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_BASE_URL"
170
+ )
171
+
172
+ auth0_required_scopes = settings.required_scopes or ["openid"]
173
+
174
+ super().__init__(
175
+ config_url=settings.config_url,
176
+ client_id=settings.client_id,
177
+ client_secret=settings.client_secret.get_secret_value(),
178
+ audience=settings.audience,
179
+ base_url=settings.base_url,
180
+ issuer_url=settings.issuer_url,
181
+ redirect_path=settings.redirect_path,
182
+ required_scopes=auth0_required_scopes,
183
+ allowed_client_redirect_uris=settings.allowed_client_redirect_uris,
184
+ client_storage=client_storage,
185
+ jwt_signing_key=settings.jwt_signing_key,
186
+ require_authorization_consent=require_authorization_consent,
187
+ )
188
+
189
+ logger.debug(
190
+ "Initialized Auth0 OAuth provider for client %s with scopes: %s",
191
+ settings.client_id,
192
+ auth0_required_scopes,
193
+ )