fastmcp 2.12.3__py3-none-any.whl → 2.12.5__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.5.dist-info}/METADATA +121 -48
  31. {fastmcp-2.12.3.dist-info → fastmcp-2.12.5.dist-info}/RECORD +34 -29
  32. {fastmcp-2.12.3.dist-info → fastmcp-2.12.5.dist-info}/WHEEL +0 -0
  33. {fastmcp-2.12.3.dist-info → fastmcp-2.12.5.dist-info}/entry_points.txt +0 -0
  34. {fastmcp-2.12.3.dist-info → fastmcp-2.12.5.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,348 @@
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 pydantic import AnyHttpUrl, BaseModel, model_validator
16
+ from typing_extensions import Self
17
+
18
+ from fastmcp.server.auth import TokenVerifier
19
+ from fastmcp.server.auth.oauth_proxy import OAuthProxy
20
+ from fastmcp.server.auth.providers.jwt import JWTVerifier
21
+ from fastmcp.utilities.logging import get_logger
22
+ from fastmcp.utilities.storage import KVStorage
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:
127
+ message = f"Invalid URL for configuration metadata: {attr}"
128
+ logger.error(message)
129
+ raise ValueError(message)
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
+ algorithm: str | None = None,
210
+ required_scopes: list[str] | None = None,
211
+ # FastMCP server configuration
212
+ base_url: AnyHttpUrl | str,
213
+ redirect_path: str | None = None,
214
+ # Client configuration
215
+ allowed_client_redirect_uris: list[str] | None = None,
216
+ client_storage: KVStorage | None = None,
217
+ # Token validation configuration
218
+ token_endpoint_auth_method: str | None = None,
219
+ ) -> None:
220
+ """Initialize the OIDC proxy provider.
221
+
222
+ Args:
223
+ config_url: URL of upstream configuration
224
+ strict: Optional strict flag for the configuration
225
+ client_id: Client ID registered with upstream server
226
+ client_secret: Client secret for upstream server
227
+ audience: Audience for upstream server
228
+ timeout_seconds: HTTP request timeout in seconds
229
+ algorithm: Token verifier algorithm
230
+ required_scopes: Required OAuth scopes
231
+ base_url: Public URL of the server that exposes this FastMCP server; redirect path is
232
+ relative to this URL
233
+ redirect_path: Redirect path configured in upstream OAuth app (defaults to "/auth/callback")
234
+ allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
235
+ Patterns support wildcards (e.g., "http://localhost:*", "https://*.example.com/*").
236
+ If None (default), only localhost redirect URIs are allowed.
237
+ If empty list, all redirect URIs are allowed (not recommended for production).
238
+ These are for MCP clients performing loopback redirects, NOT for the upstream OAuth app.
239
+ client_storage: Storage implementation for OAuth client registrations.
240
+ Defaults to file-based storage if not specified.
241
+ token_endpoint_auth_method: Token endpoint authentication method for upstream server.
242
+ Common values: "client_secret_basic", "client_secret_post", "none".
243
+ If None, authlib will use its default (typically "client_secret_basic").
244
+ """
245
+ if not config_url:
246
+ raise ValueError("Missing required config URL")
247
+
248
+ if not client_id:
249
+ raise ValueError("Missing required client id")
250
+
251
+ if not client_secret:
252
+ raise ValueError("Missing required client secret")
253
+
254
+ if not base_url:
255
+ raise ValueError("Missing required base URL")
256
+
257
+ if isinstance(config_url, str):
258
+ config_url = AnyHttpUrl(config_url)
259
+
260
+ self.oidc_config = self.get_oidc_configuration(
261
+ config_url, strict, timeout_seconds
262
+ )
263
+ if (
264
+ not self.oidc_config.authorization_endpoint
265
+ or not self.oidc_config.token_endpoint
266
+ ):
267
+ logger.debug(f"Invalid OIDC Configuration: {self.oidc_config}")
268
+ raise ValueError("Missing required OIDC endpoints")
269
+
270
+ revocation_endpoint = (
271
+ str(self.oidc_config.revocation_endpoint)
272
+ if self.oidc_config.revocation_endpoint
273
+ else None
274
+ )
275
+
276
+ token_verifier = self.get_token_verifier(
277
+ algorithm=algorithm,
278
+ audience=audience,
279
+ required_scopes=required_scopes,
280
+ timeout_seconds=timeout_seconds,
281
+ )
282
+
283
+ init_kwargs = {
284
+ "upstream_authorization_endpoint": str(
285
+ self.oidc_config.authorization_endpoint
286
+ ),
287
+ "upstream_token_endpoint": str(self.oidc_config.token_endpoint),
288
+ "upstream_client_id": client_id,
289
+ "upstream_client_secret": client_secret,
290
+ "upstream_revocation_endpoint": revocation_endpoint,
291
+ "token_verifier": token_verifier,
292
+ "base_url": base_url,
293
+ "service_documentation_url": self.oidc_config.service_documentation,
294
+ "allowed_client_redirect_uris": allowed_client_redirect_uris,
295
+ "client_storage": client_storage,
296
+ "token_endpoint_auth_method": token_endpoint_auth_method,
297
+ }
298
+
299
+ if redirect_path:
300
+ init_kwargs["redirect_path"] = redirect_path
301
+
302
+ if audience:
303
+ extra_params = {"audience": audience}
304
+ init_kwargs["extra_authorize_params"] = extra_params
305
+ init_kwargs["extra_token_params"] = extra_params
306
+
307
+ super().__init__(**init_kwargs)
308
+
309
+ def get_oidc_configuration(
310
+ self,
311
+ config_url: AnyHttpUrl,
312
+ strict: bool | None,
313
+ timeout_seconds: int | None,
314
+ ) -> OIDCConfiguration:
315
+ """Gets the OIDC configuration for the specified configuration URL.
316
+
317
+ Args:
318
+ config_url: The OIDC configuration URL
319
+ strict: The strict flag for the configuration
320
+ timeout_seconds: HTTP request timeout in seconds
321
+ """
322
+ return OIDCConfiguration.get_oidc_configuration(
323
+ config_url, strict=strict, timeout_seconds=timeout_seconds
324
+ )
325
+
326
+ def get_token_verifier(
327
+ self,
328
+ *,
329
+ algorithm: str | None = None,
330
+ audience: str | None = None,
331
+ required_scopes: list[str] | None = None,
332
+ timeout_seconds: int | None = None,
333
+ ) -> TokenVerifier:
334
+ """Creates the token verifier for the specified OIDC configuration and arguments.
335
+
336
+ Args:
337
+ algorithm: Optional token verifier algorithm
338
+ audience: Optional token verifier audience
339
+ required_scopes: Optional token verifier required_scopes
340
+ timeout_seconds: HTTP request timeout in seconds
341
+ """
342
+ return JWTVerifier(
343
+ jwks_uri=str(self.oidc_config.jwks_uri),
344
+ issuer=str(self.oidc_config.issuer),
345
+ algorithm=algorithm,
346
+ audience=audience,
347
+ required_scopes=required_scopes,
348
+ )
@@ -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
+ )