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.
- fastmcp/cli/install/gemini_cli.py +0 -1
- fastmcp/cli/run.py +2 -2
- fastmcp/client/auth/oauth.py +49 -36
- fastmcp/client/client.py +12 -2
- fastmcp/contrib/mcp_mixin/README.md +2 -2
- fastmcp/experimental/utilities/openapi/schemas.py +31 -5
- fastmcp/server/auth/auth.py +3 -3
- fastmcp/server/auth/oauth_proxy.py +42 -12
- fastmcp/server/auth/oidc_proxy.py +348 -0
- fastmcp/server/auth/providers/auth0.py +174 -0
- fastmcp/server/auth/providers/aws.py +237 -0
- fastmcp/server/auth/providers/azure.py +6 -2
- fastmcp/server/auth/providers/descope.py +172 -0
- fastmcp/server/auth/providers/github.py +6 -2
- fastmcp/server/auth/providers/google.py +6 -2
- fastmcp/server/auth/providers/workos.py +6 -2
- fastmcp/server/context.py +7 -6
- fastmcp/server/http.py +1 -1
- fastmcp/server/middleware/logging.py +147 -116
- fastmcp/server/middleware/middleware.py +3 -2
- fastmcp/server/openapi.py +5 -1
- fastmcp/server/server.py +36 -31
- fastmcp/settings.py +27 -5
- fastmcp/tools/tool.py +4 -2
- fastmcp/utilities/json_schema.py +18 -1
- fastmcp/utilities/logging.py +66 -4
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +2 -1
- fastmcp/utilities/storage.py +204 -0
- fastmcp/utilities/tests.py +8 -6
- {fastmcp-2.12.3.dist-info → fastmcp-2.12.5.dist-info}/METADATA +121 -48
- {fastmcp-2.12.3.dist-info → fastmcp-2.12.5.dist-info}/RECORD +34 -29
- {fastmcp-2.12.3.dist-info → fastmcp-2.12.5.dist-info}/WHEEL +0 -0
- {fastmcp-2.12.3.dist-info → fastmcp-2.12.5.dist-info}/entry_points.txt +0 -0
- {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
|
+
)
|