fastmcp 2.14.4__py3-none-any.whl → 3.0.0b1__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/_vendor/__init__.py +1 -0
- fastmcp/_vendor/docket_di/README.md +7 -0
- fastmcp/_vendor/docket_di/__init__.py +163 -0
- fastmcp/cli/cli.py +112 -28
- fastmcp/cli/install/claude_code.py +1 -5
- fastmcp/cli/install/claude_desktop.py +1 -5
- fastmcp/cli/install/cursor.py +1 -5
- fastmcp/cli/install/gemini_cli.py +1 -5
- fastmcp/cli/install/mcp_json.py +1 -6
- fastmcp/cli/run.py +146 -5
- fastmcp/client/__init__.py +7 -9
- fastmcp/client/auth/oauth.py +18 -17
- fastmcp/client/client.py +100 -870
- fastmcp/client/elicitation.py +1 -1
- fastmcp/client/mixins/__init__.py +13 -0
- fastmcp/client/mixins/prompts.py +295 -0
- fastmcp/client/mixins/resources.py +325 -0
- fastmcp/client/mixins/task_management.py +157 -0
- fastmcp/client/mixins/tools.py +397 -0
- fastmcp/client/sampling/handlers/anthropic.py +2 -2
- fastmcp/client/sampling/handlers/openai.py +1 -1
- fastmcp/client/tasks.py +3 -3
- fastmcp/client/telemetry.py +47 -0
- fastmcp/client/transports/__init__.py +38 -0
- fastmcp/client/transports/base.py +82 -0
- fastmcp/client/transports/config.py +170 -0
- fastmcp/client/transports/http.py +145 -0
- fastmcp/client/transports/inference.py +154 -0
- fastmcp/client/transports/memory.py +90 -0
- fastmcp/client/transports/sse.py +89 -0
- fastmcp/client/transports/stdio.py +543 -0
- fastmcp/contrib/component_manager/README.md +4 -10
- fastmcp/contrib/component_manager/__init__.py +1 -2
- fastmcp/contrib/component_manager/component_manager.py +95 -160
- fastmcp/contrib/component_manager/example.py +1 -1
- fastmcp/contrib/mcp_mixin/example.py +4 -4
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +11 -4
- fastmcp/decorators.py +41 -0
- fastmcp/dependencies.py +12 -1
- fastmcp/exceptions.py +4 -0
- fastmcp/experimental/server/openapi/__init__.py +18 -15
- fastmcp/mcp_config.py +13 -4
- fastmcp/prompts/__init__.py +6 -3
- fastmcp/prompts/function_prompt.py +465 -0
- fastmcp/prompts/prompt.py +321 -271
- fastmcp/resources/__init__.py +5 -3
- fastmcp/resources/function_resource.py +335 -0
- fastmcp/resources/resource.py +325 -115
- fastmcp/resources/template.py +215 -43
- fastmcp/resources/types.py +27 -12
- fastmcp/server/__init__.py +2 -2
- fastmcp/server/auth/__init__.py +14 -0
- fastmcp/server/auth/auth.py +30 -10
- fastmcp/server/auth/authorization.py +190 -0
- fastmcp/server/auth/oauth_proxy/__init__.py +14 -0
- fastmcp/server/auth/oauth_proxy/consent.py +361 -0
- fastmcp/server/auth/oauth_proxy/models.py +178 -0
- fastmcp/server/auth/{oauth_proxy.py → oauth_proxy/proxy.py} +24 -778
- fastmcp/server/auth/oauth_proxy/ui.py +277 -0
- fastmcp/server/auth/oidc_proxy.py +2 -2
- fastmcp/server/auth/providers/auth0.py +24 -94
- fastmcp/server/auth/providers/aws.py +26 -95
- fastmcp/server/auth/providers/azure.py +41 -129
- fastmcp/server/auth/providers/descope.py +18 -49
- fastmcp/server/auth/providers/discord.py +25 -86
- fastmcp/server/auth/providers/github.py +23 -87
- fastmcp/server/auth/providers/google.py +24 -87
- fastmcp/server/auth/providers/introspection.py +60 -79
- fastmcp/server/auth/providers/jwt.py +30 -67
- fastmcp/server/auth/providers/oci.py +47 -110
- fastmcp/server/auth/providers/scalekit.py +23 -61
- fastmcp/server/auth/providers/supabase.py +18 -47
- fastmcp/server/auth/providers/workos.py +34 -127
- fastmcp/server/context.py +372 -419
- fastmcp/server/dependencies.py +541 -251
- fastmcp/server/elicitation.py +20 -18
- fastmcp/server/event_store.py +3 -3
- fastmcp/server/http.py +16 -6
- fastmcp/server/lifespan.py +198 -0
- fastmcp/server/low_level.py +92 -2
- fastmcp/server/middleware/__init__.py +5 -1
- fastmcp/server/middleware/authorization.py +312 -0
- fastmcp/server/middleware/caching.py +101 -54
- fastmcp/server/middleware/middleware.py +6 -9
- fastmcp/server/middleware/ping.py +70 -0
- fastmcp/server/middleware/tool_injection.py +2 -2
- fastmcp/server/mixins/__init__.py +7 -0
- fastmcp/server/mixins/lifespan.py +217 -0
- fastmcp/server/mixins/mcp_operations.py +392 -0
- fastmcp/server/mixins/transport.py +342 -0
- fastmcp/server/openapi/__init__.py +41 -21
- fastmcp/server/openapi/components.py +16 -339
- fastmcp/server/openapi/routing.py +34 -118
- fastmcp/server/openapi/server.py +67 -392
- fastmcp/server/providers/__init__.py +71 -0
- fastmcp/server/providers/aggregate.py +261 -0
- fastmcp/server/providers/base.py +578 -0
- fastmcp/server/providers/fastmcp_provider.py +674 -0
- fastmcp/server/providers/filesystem.py +226 -0
- fastmcp/server/providers/filesystem_discovery.py +327 -0
- fastmcp/server/providers/local_provider/__init__.py +11 -0
- fastmcp/server/providers/local_provider/decorators/__init__.py +15 -0
- fastmcp/server/providers/local_provider/decorators/prompts.py +256 -0
- fastmcp/server/providers/local_provider/decorators/resources.py +240 -0
- fastmcp/server/providers/local_provider/decorators/tools.py +315 -0
- fastmcp/server/providers/local_provider/local_provider.py +465 -0
- fastmcp/server/providers/openapi/__init__.py +39 -0
- fastmcp/server/providers/openapi/components.py +332 -0
- fastmcp/server/providers/openapi/provider.py +405 -0
- fastmcp/server/providers/openapi/routing.py +109 -0
- fastmcp/server/providers/proxy.py +867 -0
- fastmcp/server/providers/skills/__init__.py +59 -0
- fastmcp/server/providers/skills/_common.py +101 -0
- fastmcp/server/providers/skills/claude_provider.py +44 -0
- fastmcp/server/providers/skills/directory_provider.py +153 -0
- fastmcp/server/providers/skills/skill_provider.py +432 -0
- fastmcp/server/providers/skills/vendor_providers.py +142 -0
- fastmcp/server/providers/wrapped_provider.py +140 -0
- fastmcp/server/proxy.py +34 -700
- fastmcp/server/sampling/run.py +341 -2
- fastmcp/server/sampling/sampling_tool.py +4 -3
- fastmcp/server/server.py +1214 -2171
- fastmcp/server/tasks/__init__.py +2 -1
- fastmcp/server/tasks/capabilities.py +13 -1
- fastmcp/server/tasks/config.py +66 -3
- fastmcp/server/tasks/handlers.py +65 -273
- fastmcp/server/tasks/keys.py +4 -6
- fastmcp/server/tasks/requests.py +474 -0
- fastmcp/server/tasks/routing.py +76 -0
- fastmcp/server/tasks/subscriptions.py +20 -11
- fastmcp/server/telemetry.py +131 -0
- fastmcp/server/transforms/__init__.py +244 -0
- fastmcp/server/transforms/namespace.py +193 -0
- fastmcp/server/transforms/prompts_as_tools.py +175 -0
- fastmcp/server/transforms/resources_as_tools.py +190 -0
- fastmcp/server/transforms/tool_transform.py +96 -0
- fastmcp/server/transforms/version_filter.py +124 -0
- fastmcp/server/transforms/visibility.py +526 -0
- fastmcp/settings.py +34 -96
- fastmcp/telemetry.py +122 -0
- fastmcp/tools/__init__.py +10 -3
- fastmcp/tools/function_parsing.py +201 -0
- fastmcp/tools/function_tool.py +467 -0
- fastmcp/tools/tool.py +215 -362
- fastmcp/tools/tool_transform.py +38 -21
- fastmcp/utilities/async_utils.py +69 -0
- fastmcp/utilities/components.py +152 -91
- fastmcp/utilities/inspect.py +8 -20
- fastmcp/utilities/json_schema.py +12 -5
- fastmcp/utilities/json_schema_type.py +17 -15
- fastmcp/utilities/lifespan.py +56 -0
- fastmcp/utilities/logging.py +12 -4
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -3
- fastmcp/utilities/openapi/parser.py +3 -3
- fastmcp/utilities/pagination.py +80 -0
- fastmcp/utilities/skills.py +253 -0
- fastmcp/utilities/tests.py +0 -16
- fastmcp/utilities/timeout.py +47 -0
- fastmcp/utilities/types.py +1 -1
- fastmcp/utilities/versions.py +285 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/METADATA +8 -5
- fastmcp-3.0.0b1.dist-info/RECORD +228 -0
- fastmcp/client/transports.py +0 -1170
- fastmcp/contrib/component_manager/component_service.py +0 -209
- fastmcp/prompts/prompt_manager.py +0 -117
- fastmcp/resources/resource_manager.py +0 -338
- fastmcp/server/tasks/converters.py +0 -206
- fastmcp/server/tasks/protocol.py +0 -359
- fastmcp/tools/tool_manager.py +0 -170
- fastmcp/utilities/mcp_config.py +0 -56
- fastmcp-2.14.4.dist-info/RECORD +0 -161
- /fastmcp/server/{openapi → providers/openapi}/README.md +0 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/WHEEL +0 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
|
@@ -11,15 +11,12 @@ from authlib.jose import JsonWebKey, JsonWebToken
|
|
|
11
11
|
from authlib.jose.errors import JoseError
|
|
12
12
|
from cryptography.hazmat.primitives import serialization
|
|
13
13
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
14
|
-
from pydantic import AnyHttpUrl, SecretStr
|
|
15
|
-
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
14
|
+
from pydantic import AnyHttpUrl, SecretStr
|
|
16
15
|
from typing_extensions import TypedDict
|
|
17
16
|
|
|
18
17
|
from fastmcp.server.auth import AccessToken, TokenVerifier
|
|
19
|
-
from fastmcp.settings import ENV_FILE
|
|
20
18
|
from fastmcp.utilities.auth import parse_scopes
|
|
21
19
|
from fastmcp.utilities.logging import get_logger
|
|
22
|
-
from fastmcp.utilities.types import NotSet, NotSetT
|
|
23
20
|
|
|
24
21
|
logger = get_logger(__name__)
|
|
25
22
|
|
|
@@ -139,29 +136,6 @@ class RSAKeyPair:
|
|
|
139
136
|
return token_bytes.decode("utf-8")
|
|
140
137
|
|
|
141
138
|
|
|
142
|
-
class JWTVerifierSettings(BaseSettings):
|
|
143
|
-
"""Settings for JWT token verification."""
|
|
144
|
-
|
|
145
|
-
model_config = SettingsConfigDict(
|
|
146
|
-
env_prefix="FASTMCP_SERVER_AUTH_JWT_",
|
|
147
|
-
env_file=ENV_FILE,
|
|
148
|
-
extra="ignore",
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
public_key: str | None = None
|
|
152
|
-
jwks_uri: str | None = None
|
|
153
|
-
issuer: str | list[str] | None = None
|
|
154
|
-
algorithm: str | None = None
|
|
155
|
-
audience: str | list[str] | None = None
|
|
156
|
-
required_scopes: list[str] | None = None
|
|
157
|
-
base_url: AnyHttpUrl | str | None = None
|
|
158
|
-
|
|
159
|
-
@field_validator("required_scopes", mode="before")
|
|
160
|
-
@classmethod
|
|
161
|
-
def _parse_scopes(cls, v):
|
|
162
|
-
return parse_scopes(v)
|
|
163
|
-
|
|
164
|
-
|
|
165
139
|
class JWTVerifier(TokenVerifier):
|
|
166
140
|
"""
|
|
167
141
|
JWT token verifier supporting both asymmetric (RSA/ECDSA) and symmetric (HMAC) algorithms.
|
|
@@ -184,52 +158,36 @@ class JWTVerifier(TokenVerifier):
|
|
|
184
158
|
def __init__(
|
|
185
159
|
self,
|
|
186
160
|
*,
|
|
187
|
-
public_key: str |
|
|
188
|
-
jwks_uri: str |
|
|
189
|
-
issuer: str | list[str] |
|
|
190
|
-
audience: str | list[str] |
|
|
191
|
-
algorithm: str |
|
|
192
|
-
required_scopes: list[str] |
|
|
193
|
-
base_url: AnyHttpUrl | str |
|
|
161
|
+
public_key: str | None = None,
|
|
162
|
+
jwks_uri: str | None = None,
|
|
163
|
+
issuer: str | list[str] | None = None,
|
|
164
|
+
audience: str | list[str] | None = None,
|
|
165
|
+
algorithm: str | None = None,
|
|
166
|
+
required_scopes: list[str] | None = None,
|
|
167
|
+
base_url: AnyHttpUrl | str | None = None,
|
|
194
168
|
):
|
|
195
169
|
"""
|
|
196
170
|
Initialize a JWTVerifier configured to validate JWTs using either a static key or a JWKS endpoint.
|
|
197
171
|
|
|
198
172
|
Parameters:
|
|
199
|
-
public_key
|
|
200
|
-
jwks_uri
|
|
201
|
-
issuer
|
|
202
|
-
audience
|
|
203
|
-
algorithm
|
|
204
|
-
required_scopes
|
|
205
|
-
base_url
|
|
173
|
+
public_key: PEM-encoded public key for asymmetric algorithms or shared secret for symmetric algorithms.
|
|
174
|
+
jwks_uri: URI to fetch a JSON Web Key Set; used when verifying tokens with remote JWKS.
|
|
175
|
+
issuer: Expected issuer claim value or list of allowed issuer values.
|
|
176
|
+
audience: Expected audience claim value or list of allowed audience values.
|
|
177
|
+
algorithm: JWT signing algorithm to accept (default: "RS256"). Supported: HS256/384/512, RS256/384/512, ES256/384/512, PS256/384/512.
|
|
178
|
+
required_scopes: Scopes that must be present in validated tokens.
|
|
179
|
+
base_url: Base URL passed to the parent TokenVerifier.
|
|
206
180
|
|
|
207
181
|
Raises:
|
|
208
182
|
ValueError: If neither or both of `public_key` and `jwks_uri` are provided, or if `algorithm` is unsupported.
|
|
209
183
|
"""
|
|
210
|
-
|
|
211
|
-
{
|
|
212
|
-
k: v
|
|
213
|
-
for k, v in {
|
|
214
|
-
"public_key": public_key,
|
|
215
|
-
"jwks_uri": jwks_uri,
|
|
216
|
-
"issuer": issuer,
|
|
217
|
-
"audience": audience,
|
|
218
|
-
"algorithm": algorithm,
|
|
219
|
-
"required_scopes": required_scopes,
|
|
220
|
-
"base_url": base_url,
|
|
221
|
-
}.items()
|
|
222
|
-
if v is not NotSet
|
|
223
|
-
}
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
if not settings.public_key and not settings.jwks_uri:
|
|
184
|
+
if not public_key and not jwks_uri:
|
|
227
185
|
raise ValueError("Either public_key or jwks_uri must be provided")
|
|
228
186
|
|
|
229
|
-
if
|
|
187
|
+
if public_key and jwks_uri:
|
|
230
188
|
raise ValueError("Provide either public_key or jwks_uri, not both")
|
|
231
189
|
|
|
232
|
-
algorithm =
|
|
190
|
+
algorithm = algorithm or "RS256"
|
|
233
191
|
if algorithm not in {
|
|
234
192
|
"HS256",
|
|
235
193
|
"HS384",
|
|
@@ -246,17 +204,22 @@ class JWTVerifier(TokenVerifier):
|
|
|
246
204
|
}:
|
|
247
205
|
raise ValueError(f"Unsupported algorithm: {algorithm}.")
|
|
248
206
|
|
|
207
|
+
# Parse scopes if provided as string
|
|
208
|
+
parsed_required_scopes = (
|
|
209
|
+
parse_scopes(required_scopes) if required_scopes is not None else None
|
|
210
|
+
)
|
|
211
|
+
|
|
249
212
|
# Initialize parent TokenVerifier
|
|
250
213
|
super().__init__(
|
|
251
|
-
base_url=
|
|
252
|
-
required_scopes=
|
|
214
|
+
base_url=base_url,
|
|
215
|
+
required_scopes=parsed_required_scopes,
|
|
253
216
|
)
|
|
254
217
|
|
|
255
218
|
self.algorithm = algorithm
|
|
256
|
-
self.issuer =
|
|
257
|
-
self.audience =
|
|
258
|
-
self.public_key =
|
|
259
|
-
self.jwks_uri =
|
|
219
|
+
self.issuer = issuer
|
|
220
|
+
self.audience = audience
|
|
221
|
+
self.public_key = public_key
|
|
222
|
+
self.jwks_uri = jwks_uri
|
|
260
223
|
self.jwt = JsonWebToken([self.algorithm])
|
|
261
224
|
self.logger = get_logger(__name__)
|
|
262
225
|
|
|
@@ -312,7 +275,7 @@ class JWTVerifier(TokenVerifier):
|
|
|
312
275
|
for key_data in jwks_data.get("keys", []):
|
|
313
276
|
key_kid = key_data.get("kid")
|
|
314
277
|
jwk = JsonWebKey.import_key(key_data)
|
|
315
|
-
public_key = jwk.get_public_key()
|
|
278
|
+
public_key = jwk.get_public_key()
|
|
316
279
|
|
|
317
280
|
if key_kid:
|
|
318
281
|
self._jwks_cache[key_kid] = public_key
|
|
@@ -19,22 +19,22 @@ Example:
|
|
|
19
19
|
|
|
20
20
|
import os
|
|
21
21
|
|
|
22
|
-
# Load configuration from environment
|
|
23
|
-
FASTMCP_SERVER_AUTH_OCI_CONFIG_URL = os.environ["FASTMCP_SERVER_AUTH_OCI_CONFIG_URL"]
|
|
24
|
-
FASTMCP_SERVER_AUTH_OCI_CLIENT_ID = os.environ["FASTMCP_SERVER_AUTH_OCI_CLIENT_ID"]
|
|
25
|
-
FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET = os.environ["FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET"]
|
|
26
|
-
FASTMCP_SERVER_AUTH_OCI_IAM_GUID = os.environ["FASTMCP_SERVER_AUTH_OCI_IAM_GUID"]
|
|
27
|
-
|
|
28
22
|
import oci
|
|
29
23
|
from oci.auth.signers import TokenExchangeSigner
|
|
30
24
|
|
|
31
25
|
logger = get_logger(__name__)
|
|
32
26
|
|
|
27
|
+
# Load configuration from environment
|
|
28
|
+
config_url = os.environ.get("OCI_CONFIG_URL") # OCI IAM Domain OIDC discovery URL
|
|
29
|
+
client_id = os.environ.get("OCI_CLIENT_ID") # Client ID configured for the OCI IAM Domain Integrated Application
|
|
30
|
+
client_secret = os.environ.get("OCI_CLIENT_SECRET") # Client secret configured for the OCI IAM Domain Integrated Application
|
|
31
|
+
iam_guid = os.environ.get("OCI_IAM_GUID") # IAM GUID configured for the OCI IAM Domain
|
|
32
|
+
|
|
33
33
|
# Simple OCI OIDC protection
|
|
34
34
|
auth = OCIProvider(
|
|
35
|
-
config_url=
|
|
36
|
-
client_id=
|
|
37
|
-
client_secret=
|
|
35
|
+
config_url=config_url, # config URL is the OCI IAM Domain OIDC discovery URL
|
|
36
|
+
client_id=client_id, # This is same as the client ID configured for the OCI IAM Domain Integrated Application
|
|
37
|
+
client_secret=client_secret, # This is same as the client secret configured for the OCI IAM Domain Integrated Application
|
|
38
38
|
required_scopes=["openid", "profile", "email"],
|
|
39
39
|
redirect_path="/auth/callback",
|
|
40
40
|
base_url="http://localhost:8000",
|
|
@@ -42,7 +42,7 @@ Example:
|
|
|
42
42
|
|
|
43
43
|
# NOTE: For production use, replace this with a thread-safe cache implementation
|
|
44
44
|
# such as threading.Lock-protected dict or a proper caching library
|
|
45
|
-
_global_token_cache = {}
|
|
45
|
+
_global_token_cache = {} # In memory cache for OCI session token signer
|
|
46
46
|
|
|
47
47
|
def get_oci_signer() -> TokenExchangeSigner:
|
|
48
48
|
|
|
@@ -50,20 +50,20 @@ Example:
|
|
|
50
50
|
tokenID = authntoken.claims.get("jti")
|
|
51
51
|
token = authntoken.token
|
|
52
52
|
|
|
53
|
-
#Check if the signer exists for the token ID in memory cache
|
|
53
|
+
# Check if the signer exists for the token ID in memory cache
|
|
54
54
|
cached_signer = _global_token_cache.get(tokenID)
|
|
55
55
|
logger.debug(f"Global cached signer: {cached_signer}")
|
|
56
56
|
if cached_signer:
|
|
57
57
|
logger.debug(f"Using globally cached signer for token ID: {tokenID}")
|
|
58
58
|
return cached_signer
|
|
59
59
|
|
|
60
|
-
#If the signer is not yet created for the token then create new OCI signer object
|
|
60
|
+
# If the signer is not yet created for the token then create new OCI signer object
|
|
61
61
|
logger.debug(f"Creating new signer for token ID: {tokenID}")
|
|
62
62
|
signer = TokenExchangeSigner(
|
|
63
63
|
jwt_or_func=token,
|
|
64
|
-
oci_domain_id=
|
|
65
|
-
client_id=
|
|
66
|
-
client_secret=
|
|
64
|
+
oci_domain_id=iam_guid.split(".")[0] if iam_guid else None, # This is same as IAM GUID configured for the OCI IAM Domain
|
|
65
|
+
client_id=client_id, # This is same as the client ID configured for the OCI IAM Domain Integrated Application
|
|
66
|
+
client_secret=client_secret, # This is same as the client secret configured for the OCI IAM Domain Integrated Application
|
|
67
67
|
)
|
|
68
68
|
logger.debug(f"Signer {signer} created for token ID: {tokenID}")
|
|
69
69
|
|
|
@@ -78,44 +78,15 @@ Example:
|
|
|
78
78
|
"""
|
|
79
79
|
|
|
80
80
|
from key_value.aio.protocols import AsyncKeyValue
|
|
81
|
-
from pydantic import AnyHttpUrl
|
|
82
|
-
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
81
|
+
from pydantic import AnyHttpUrl
|
|
83
82
|
|
|
84
83
|
from fastmcp.server.auth.oidc_proxy import OIDCProxy
|
|
85
|
-
from fastmcp.settings import ENV_FILE
|
|
86
84
|
from fastmcp.utilities.auth import parse_scopes
|
|
87
85
|
from fastmcp.utilities.logging import get_logger
|
|
88
|
-
from fastmcp.utilities.types import NotSet, NotSetT
|
|
89
86
|
|
|
90
87
|
logger = get_logger(__name__)
|
|
91
88
|
|
|
92
89
|
|
|
93
|
-
class OCIProviderSettings(BaseSettings):
|
|
94
|
-
"""Settings for OCI IAM domain OIDC provider."""
|
|
95
|
-
|
|
96
|
-
model_config = SettingsConfigDict(
|
|
97
|
-
env_prefix="FASTMCP_SERVER_AUTH_OCI_",
|
|
98
|
-
env_file=ENV_FILE,
|
|
99
|
-
extra="ignore",
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
config_url: AnyHttpUrl | None = None
|
|
103
|
-
client_id: str | None = None
|
|
104
|
-
client_secret: SecretStr | None = None
|
|
105
|
-
audience: str | None = None
|
|
106
|
-
base_url: AnyHttpUrl | None = None
|
|
107
|
-
issuer_url: AnyHttpUrl | None = None
|
|
108
|
-
redirect_path: str | None = None
|
|
109
|
-
required_scopes: list[str] | None = None
|
|
110
|
-
allowed_client_redirect_uris: list[str] | None = None
|
|
111
|
-
jwt_signing_key: str | bytes | None = None
|
|
112
|
-
|
|
113
|
-
@field_validator("required_scopes", mode="before")
|
|
114
|
-
@classmethod
|
|
115
|
-
def _parse_scopes(cls, v):
|
|
116
|
-
return parse_scopes(v)
|
|
117
|
-
|
|
118
|
-
|
|
119
90
|
class OCIProvider(OIDCProxy):
|
|
120
91
|
"""An OCI IAM Domain provider implementation for FastMCP.
|
|
121
92
|
|
|
@@ -127,11 +98,13 @@ class OCIProvider(OIDCProxy):
|
|
|
127
98
|
from fastmcp import FastMCP
|
|
128
99
|
from fastmcp.server.auth.providers.oci import OCIProvider
|
|
129
100
|
|
|
130
|
-
|
|
101
|
+
import os
|
|
102
|
+
|
|
103
|
+
# Load configuration from environment
|
|
131
104
|
auth = OCIProvider(
|
|
132
|
-
config_url=
|
|
133
|
-
client_id=
|
|
134
|
-
client_secret=
|
|
105
|
+
config_url=os.environ.get("OCI_CONFIG_URL"), # OCI IAM Domain OIDC discovery URL
|
|
106
|
+
client_id=os.environ.get("OCI_CLIENT_ID"), # Client ID configured for the OCI IAM Domain Integrated Application
|
|
107
|
+
client_secret=os.environ.get("OCI_CLIENT_SECRET"), # Client secret configured for the OCI IAM Domain Integrated Application
|
|
135
108
|
base_url="http://localhost:8000",
|
|
136
109
|
required_scopes=["openid", "profile", "email"],
|
|
137
110
|
redirect_path="/auth/callback",
|
|
@@ -144,17 +117,17 @@ class OCIProvider(OIDCProxy):
|
|
|
144
117
|
def __init__(
|
|
145
118
|
self,
|
|
146
119
|
*,
|
|
147
|
-
config_url: AnyHttpUrl | str
|
|
148
|
-
client_id: str
|
|
149
|
-
client_secret: str
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
issuer_url: AnyHttpUrl | str |
|
|
153
|
-
required_scopes: list[str] |
|
|
154
|
-
redirect_path: str |
|
|
155
|
-
allowed_client_redirect_uris: list[str] |
|
|
120
|
+
config_url: AnyHttpUrl | str,
|
|
121
|
+
client_id: str,
|
|
122
|
+
client_secret: str,
|
|
123
|
+
base_url: AnyHttpUrl | str,
|
|
124
|
+
audience: str | None = None,
|
|
125
|
+
issuer_url: AnyHttpUrl | str | None = None,
|
|
126
|
+
required_scopes: list[str] | None = None,
|
|
127
|
+
redirect_path: str | None = None,
|
|
128
|
+
allowed_client_redirect_uris: list[str] | None = None,
|
|
156
129
|
client_storage: AsyncKeyValue | None = None,
|
|
157
|
-
jwt_signing_key: str | bytes |
|
|
130
|
+
jwt_signing_key: str | bytes | None = None,
|
|
158
131
|
require_authorization_consent: bool = True,
|
|
159
132
|
) -> None:
|
|
160
133
|
"""Initialize OCI OIDC provider.
|
|
@@ -163,71 +136,35 @@ class OCIProvider(OIDCProxy):
|
|
|
163
136
|
config_url: OCI OIDC Discovery URL
|
|
164
137
|
client_id: OCI IAM Domain Integrated Application client id
|
|
165
138
|
client_secret: OCI Integrated Application client secret
|
|
166
|
-
audience: OCI API audience (optional)
|
|
167
139
|
base_url: Public URL where OIDC endpoints will be accessible (includes any mount path)
|
|
140
|
+
audience: OCI API audience (optional)
|
|
168
141
|
issuer_url: Issuer URL for OCI IAM Domain metadata. This will override issuer URL from the discovery URL.
|
|
169
142
|
required_scopes: Required OCI scopes (defaults to ["openid"])
|
|
170
143
|
redirect_path: Redirect path configured in OCI IAM Domain Integrated Application. The default is "/auth/callback".
|
|
171
144
|
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
|
|
172
145
|
"""
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
"config_url": config_url,
|
|
178
|
-
"client_id": client_id,
|
|
179
|
-
"client_secret": client_secret,
|
|
180
|
-
"audience": audience,
|
|
181
|
-
"base_url": base_url,
|
|
182
|
-
"issuer_url": issuer_url,
|
|
183
|
-
"required_scopes": required_scopes,
|
|
184
|
-
"redirect_path": redirect_path,
|
|
185
|
-
"allowed_client_redirect_uris": allowed_client_redirect_uris,
|
|
186
|
-
"jwt_signing_key": jwt_signing_key,
|
|
187
|
-
}.items()
|
|
188
|
-
if v is not NotSet
|
|
189
|
-
}
|
|
190
|
-
settings = OCIProviderSettings(**overrides) # type: ignore[arg-type]
|
|
191
|
-
|
|
192
|
-
if not settings.config_url:
|
|
193
|
-
raise ValueError(
|
|
194
|
-
"config_url is required - set via parameter or FASTMCP_SERVER_AUTH_OCI_CONFIG_URL"
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
if not settings.client_id:
|
|
198
|
-
raise ValueError(
|
|
199
|
-
"client_id is required - set via parameter or FASTMCP_SERVER_AUTH_OCI_CLIENT_ID"
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
if not settings.client_secret:
|
|
203
|
-
raise ValueError(
|
|
204
|
-
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET"
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
if not settings.base_url:
|
|
208
|
-
raise ValueError(
|
|
209
|
-
"base_url is required - set via parameter or FASTMCP_SERVER_AUTH_OCI_BASE_URL"
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
oci_required_scopes = settings.required_scopes or ["openid"]
|
|
146
|
+
# Parse scopes if provided as string
|
|
147
|
+
oci_required_scopes = (
|
|
148
|
+
parse_scopes(required_scopes) if required_scopes is not None else ["openid"]
|
|
149
|
+
)
|
|
213
150
|
|
|
214
151
|
super().__init__(
|
|
215
|
-
config_url=
|
|
216
|
-
client_id=
|
|
217
|
-
client_secret=
|
|
218
|
-
audience=
|
|
219
|
-
base_url=
|
|
220
|
-
issuer_url=
|
|
221
|
-
redirect_path=
|
|
152
|
+
config_url=config_url,
|
|
153
|
+
client_id=client_id,
|
|
154
|
+
client_secret=client_secret,
|
|
155
|
+
audience=audience,
|
|
156
|
+
base_url=base_url,
|
|
157
|
+
issuer_url=issuer_url,
|
|
158
|
+
redirect_path=redirect_path,
|
|
222
159
|
required_scopes=oci_required_scopes,
|
|
223
|
-
allowed_client_redirect_uris=
|
|
160
|
+
allowed_client_redirect_uris=allowed_client_redirect_uris,
|
|
224
161
|
client_storage=client_storage,
|
|
225
|
-
jwt_signing_key=
|
|
162
|
+
jwt_signing_key=jwt_signing_key,
|
|
226
163
|
require_authorization_consent=require_authorization_consent,
|
|
227
164
|
)
|
|
228
165
|
|
|
229
166
|
logger.debug(
|
|
230
167
|
"Initialized OCI OAuth provider for client %s with scopes: %s",
|
|
231
|
-
|
|
168
|
+
client_id,
|
|
232
169
|
oci_required_scopes,
|
|
233
170
|
)
|
|
@@ -8,50 +8,18 @@ authentication for seamless MCP client authentication.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import httpx
|
|
11
|
-
from pydantic import AnyHttpUrl
|
|
12
|
-
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
11
|
+
from pydantic import AnyHttpUrl
|
|
13
12
|
from starlette.responses import JSONResponse
|
|
14
13
|
from starlette.routing import Route
|
|
15
14
|
|
|
16
15
|
from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier
|
|
17
16
|
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
|
18
|
-
from fastmcp.settings import ENV_FILE
|
|
19
17
|
from fastmcp.utilities.auth import parse_scopes
|
|
20
18
|
from fastmcp.utilities.logging import get_logger
|
|
21
|
-
from fastmcp.utilities.types import NotSet, NotSetT
|
|
22
19
|
|
|
23
20
|
logger = get_logger(__name__)
|
|
24
21
|
|
|
25
22
|
|
|
26
|
-
class ScalekitProviderSettings(BaseSettings):
|
|
27
|
-
model_config = SettingsConfigDict(
|
|
28
|
-
env_prefix="FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_",
|
|
29
|
-
env_file=ENV_FILE,
|
|
30
|
-
extra="ignore",
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
environment_url: AnyHttpUrl
|
|
34
|
-
resource_id: str
|
|
35
|
-
base_url: AnyHttpUrl | None = None
|
|
36
|
-
mcp_url: AnyHttpUrl | None = None
|
|
37
|
-
required_scopes: list[str] | None = None
|
|
38
|
-
|
|
39
|
-
@field_validator("required_scopes", mode="before")
|
|
40
|
-
@classmethod
|
|
41
|
-
def _parse_scopes(cls, value: object):
|
|
42
|
-
return parse_scopes(value)
|
|
43
|
-
|
|
44
|
-
@model_validator(mode="after")
|
|
45
|
-
def _resolve_base_url(self):
|
|
46
|
-
resolved = self.base_url or self.mcp_url
|
|
47
|
-
if resolved is None:
|
|
48
|
-
msg = "Either base_url or mcp_url must be provided for ScalekitProvider"
|
|
49
|
-
raise ValueError(msg)
|
|
50
|
-
|
|
51
|
-
object.__setattr__(self, "base_url", resolved)
|
|
52
|
-
return self
|
|
53
|
-
|
|
54
|
-
|
|
55
23
|
class ScalekitProvider(RemoteAuthProvider):
|
|
56
24
|
"""Scalekit resource server provider for OAuth 2.1 authentication.
|
|
57
25
|
|
|
@@ -95,12 +63,12 @@ class ScalekitProvider(RemoteAuthProvider):
|
|
|
95
63
|
def __init__(
|
|
96
64
|
self,
|
|
97
65
|
*,
|
|
98
|
-
environment_url: AnyHttpUrl | str
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
required_scopes: list[str] |
|
|
66
|
+
environment_url: AnyHttpUrl | str,
|
|
67
|
+
resource_id: str,
|
|
68
|
+
base_url: AnyHttpUrl | str | None = None,
|
|
69
|
+
mcp_url: AnyHttpUrl | str | None = None,
|
|
70
|
+
client_id: str | None = None,
|
|
71
|
+
required_scopes: list[str] | None = None,
|
|
104
72
|
token_verifier: TokenVerifier | None = None,
|
|
105
73
|
):
|
|
106
74
|
"""Initialize Scalekit resource server provider.
|
|
@@ -108,42 +76,36 @@ class ScalekitProvider(RemoteAuthProvider):
|
|
|
108
76
|
Args:
|
|
109
77
|
environment_url: Your Scalekit environment URL (e.g., "https://your-env.scalekit.com")
|
|
110
78
|
resource_id: Your Scalekit resource ID
|
|
111
|
-
base_url: Public URL of this FastMCP server
|
|
79
|
+
base_url: Public URL of this FastMCP server (or use mcp_url for backwards compatibility)
|
|
80
|
+
mcp_url: Deprecated alias for base_url. Will be removed in a future release.
|
|
81
|
+
client_id: Deprecated parameter, no longer required. Will be removed in a future release.
|
|
112
82
|
required_scopes: Optional list of scopes that must be present in tokens
|
|
113
83
|
token_verifier: Optional token verifier. If None, creates JWT verifier for Scalekit
|
|
114
84
|
"""
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
k: v
|
|
120
|
-
for k, v in {
|
|
121
|
-
"environment_url": environment_url,
|
|
122
|
-
"resource_id": resource_id,
|
|
123
|
-
"base_url": base_url,
|
|
124
|
-
"mcp_url": mcp_url,
|
|
125
|
-
"required_scopes": required_scopes,
|
|
126
|
-
}.items()
|
|
127
|
-
if v is not NotSet
|
|
128
|
-
}
|
|
129
|
-
)
|
|
85
|
+
# Resolve base_url from mcp_url if needed (backwards compatibility)
|
|
86
|
+
resolved_base_url = base_url or mcp_url
|
|
87
|
+
if not resolved_base_url:
|
|
88
|
+
raise ValueError("Either base_url or mcp_url must be provided")
|
|
130
89
|
|
|
131
|
-
if
|
|
90
|
+
if mcp_url is not None:
|
|
132
91
|
logger.warning(
|
|
133
92
|
"ScalekitProvider parameter 'mcp_url' is deprecated and will be removed in a future release. "
|
|
134
93
|
"Rename it to 'base_url'."
|
|
135
94
|
)
|
|
136
95
|
|
|
137
|
-
if
|
|
96
|
+
if client_id is not None:
|
|
138
97
|
logger.warning(
|
|
139
98
|
"ScalekitProvider no longer requires 'client_id'. The parameter is accepted only for backward "
|
|
140
99
|
"compatibility and will be removed in a future release."
|
|
141
100
|
)
|
|
142
101
|
|
|
143
|
-
self.environment_url = str(
|
|
144
|
-
self.resource_id =
|
|
145
|
-
|
|
146
|
-
|
|
102
|
+
self.environment_url = str(environment_url).rstrip("/")
|
|
103
|
+
self.resource_id = resource_id
|
|
104
|
+
parsed_scopes = (
|
|
105
|
+
parse_scopes(required_scopes) if required_scopes is not None else []
|
|
106
|
+
)
|
|
107
|
+
self.required_scopes = parsed_scopes
|
|
108
|
+
base_url_value = str(resolved_base_url)
|
|
147
109
|
|
|
148
110
|
logger.debug(
|
|
149
111
|
"Initializing ScalekitProvider: environment_url=%s resource_id=%s base_url=%s required_scopes=%s",
|
|
@@ -10,40 +10,18 @@ from __future__ import annotations
|
|
|
10
10
|
from typing import Literal
|
|
11
11
|
|
|
12
12
|
import httpx
|
|
13
|
-
from pydantic import AnyHttpUrl
|
|
14
|
-
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
13
|
+
from pydantic import AnyHttpUrl
|
|
15
14
|
from starlette.responses import JSONResponse
|
|
16
15
|
from starlette.routing import Route
|
|
17
16
|
|
|
18
17
|
from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier
|
|
19
18
|
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
|
20
|
-
from fastmcp.settings import ENV_FILE
|
|
21
19
|
from fastmcp.utilities.auth import parse_scopes
|
|
22
20
|
from fastmcp.utilities.logging import get_logger
|
|
23
|
-
from fastmcp.utilities.types import NotSet, NotSetT
|
|
24
21
|
|
|
25
22
|
logger = get_logger(__name__)
|
|
26
23
|
|
|
27
24
|
|
|
28
|
-
class SupabaseProviderSettings(BaseSettings):
|
|
29
|
-
model_config = SettingsConfigDict(
|
|
30
|
-
env_prefix="FASTMCP_SERVER_AUTH_SUPABASE_",
|
|
31
|
-
env_file=ENV_FILE,
|
|
32
|
-
extra="ignore",
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
project_url: AnyHttpUrl
|
|
36
|
-
base_url: AnyHttpUrl
|
|
37
|
-
auth_route: str = "/auth/v1"
|
|
38
|
-
algorithm: Literal["HS256", "RS256", "ES256"] = "ES256"
|
|
39
|
-
required_scopes: list[str] | None = None
|
|
40
|
-
|
|
41
|
-
@field_validator("required_scopes", mode="before")
|
|
42
|
-
@classmethod
|
|
43
|
-
def _parse_scopes(cls, v):
|
|
44
|
-
return parse_scopes(v)
|
|
45
|
-
|
|
46
|
-
|
|
47
25
|
class SupabaseProvider(RemoteAuthProvider):
|
|
48
26
|
"""Supabase metadata provider for DCR (Dynamic Client Registration).
|
|
49
27
|
|
|
@@ -62,6 +40,7 @@ class SupabaseProvider(RemoteAuthProvider):
|
|
|
62
40
|
2. JWT Verification:
|
|
63
41
|
- FastMCP verifies JWTs using the JWKS endpoint at {project_url}{auth_route}/.well-known/jwks.json
|
|
64
42
|
- JWTs are issued by {project_url}{auth_route}
|
|
43
|
+
- Default auth_route is "/auth/v1" (can be customized for self-hosted setups)
|
|
65
44
|
- Tokens are cached for up to 10 minutes by Supabase's edge servers
|
|
66
45
|
- Algorithm must match your Supabase Auth configuration
|
|
67
46
|
|
|
@@ -92,11 +71,11 @@ class SupabaseProvider(RemoteAuthProvider):
|
|
|
92
71
|
def __init__(
|
|
93
72
|
self,
|
|
94
73
|
*,
|
|
95
|
-
project_url: AnyHttpUrl | str
|
|
96
|
-
base_url: AnyHttpUrl | str
|
|
97
|
-
auth_route: str
|
|
98
|
-
algorithm: Literal["HS256", "RS256", "ES256"]
|
|
99
|
-
required_scopes: list[str] |
|
|
74
|
+
project_url: AnyHttpUrl | str,
|
|
75
|
+
base_url: AnyHttpUrl | str,
|
|
76
|
+
auth_route: str = "/auth/v1",
|
|
77
|
+
algorithm: Literal["HS256", "RS256", "ES256"] = "ES256",
|
|
78
|
+
required_scopes: list[str] | None = None,
|
|
100
79
|
token_verifier: TokenVerifier | None = None,
|
|
101
80
|
):
|
|
102
81
|
"""Initialize Supabase metadata provider.
|
|
@@ -104,7 +83,8 @@ class SupabaseProvider(RemoteAuthProvider):
|
|
|
104
83
|
Args:
|
|
105
84
|
project_url: Your Supabase project URL (e.g., "https://abc123.supabase.co")
|
|
106
85
|
base_url: Public URL of this FastMCP server
|
|
107
|
-
auth_route: Supabase Auth route. Defaults to "/auth/v1".
|
|
86
|
+
auth_route: Supabase Auth route. Defaults to "/auth/v1". Can be customized
|
|
87
|
+
for self-hosted Supabase Auth setups using custom routes.
|
|
108
88
|
algorithm: JWT signing algorithm (HS256, RS256, or ES256). Must match your
|
|
109
89
|
Supabase Auth configuration. Defaults to ES256.
|
|
110
90
|
required_scopes: Optional list of scopes to require for all requests.
|
|
@@ -112,31 +92,22 @@ class SupabaseProvider(RemoteAuthProvider):
|
|
|
112
92
|
scopes are an upcoming feature.
|
|
113
93
|
token_verifier: Optional token verifier. If None, creates JWT verifier for Supabase
|
|
114
94
|
"""
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
for k, v in {
|
|
119
|
-
"project_url": project_url,
|
|
120
|
-
"base_url": base_url,
|
|
121
|
-
"auth_route": auth_route,
|
|
122
|
-
"algorithm": algorithm,
|
|
123
|
-
"required_scopes": required_scopes,
|
|
124
|
-
}.items()
|
|
125
|
-
if v is not NotSet
|
|
126
|
-
}
|
|
127
|
-
)
|
|
95
|
+
self.project_url = str(project_url).rstrip("/")
|
|
96
|
+
self.base_url = AnyHttpUrl(str(base_url).rstrip("/"))
|
|
97
|
+
self.auth_route = auth_route.strip("/")
|
|
128
98
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
99
|
+
# Parse scopes if provided as string
|
|
100
|
+
parsed_scopes = (
|
|
101
|
+
parse_scopes(required_scopes) if required_scopes is not None else None
|
|
102
|
+
)
|
|
132
103
|
|
|
133
104
|
# Create default JWT verifier if none provided
|
|
134
105
|
if token_verifier is None:
|
|
135
106
|
token_verifier = JWTVerifier(
|
|
136
107
|
jwks_uri=f"{self.project_url}/{self.auth_route}/.well-known/jwks.json",
|
|
137
108
|
issuer=f"{self.project_url}/{self.auth_route}",
|
|
138
|
-
algorithm=
|
|
139
|
-
required_scopes=
|
|
109
|
+
algorithm=algorithm,
|
|
110
|
+
required_scopes=parsed_scopes,
|
|
140
111
|
)
|
|
141
112
|
|
|
142
113
|
# Initialize RemoteAuthProvider with Supabase as the authorization server
|