fastmcp 2.12.2__py3-none-any.whl → 2.12.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastmcp/cli/claude.py +1 -10
- fastmcp/cli/cli.py +45 -25
- fastmcp/cli/install/__init__.py +2 -0
- fastmcp/cli/install/claude_code.py +1 -10
- fastmcp/cli/install/claude_desktop.py +1 -9
- fastmcp/cli/install/cursor.py +2 -18
- fastmcp/cli/install/gemini_cli.py +241 -0
- fastmcp/cli/install/mcp_json.py +1 -9
- fastmcp/cli/run.py +2 -86
- fastmcp/client/auth/oauth.py +50 -37
- fastmcp/client/client.py +18 -8
- fastmcp/client/elicitation.py +6 -1
- fastmcp/client/transports.py +1 -1
- fastmcp/contrib/component_manager/component_service.py +1 -1
- fastmcp/contrib/mcp_mixin/README.md +3 -3
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +41 -6
- fastmcp/experimental/utilities/openapi/director.py +8 -1
- fastmcp/experimental/utilities/openapi/schemas.py +31 -5
- fastmcp/prompts/prompt.py +10 -8
- fastmcp/resources/resource.py +14 -11
- fastmcp/resources/template.py +12 -10
- fastmcp/server/auth/auth.py +10 -4
- fastmcp/server/auth/oauth_proxy.py +93 -23
- 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 +17 -16
- fastmcp/server/dependencies.py +18 -5
- 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 +43 -36
- fastmcp/settings.py +42 -6
- fastmcp/tools/tool.py +105 -87
- fastmcp/tools/tool_transform.py +1 -1
- fastmcp/utilities/json_schema.py +18 -1
- fastmcp/utilities/logging.py +66 -4
- fastmcp/utilities/mcp_server_config/v1/environments/uv.py +4 -39
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -2
- fastmcp/utilities/mcp_server_config/v1/schema.json +2 -1
- fastmcp/utilities/storage.py +204 -0
- fastmcp/utilities/tests.py +8 -6
- fastmcp/utilities/types.py +9 -5
- {fastmcp-2.12.2.dist-info → fastmcp-2.12.4.dist-info}/METADATA +121 -48
- {fastmcp-2.12.2.dist-info → fastmcp-2.12.4.dist-info}/RECORD +54 -48
- {fastmcp-2.12.2.dist-info → fastmcp-2.12.4.dist-info}/WHEEL +0 -0
- {fastmcp-2.12.2.dist-info → fastmcp-2.12.4.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.12.2.dist-info → fastmcp-2.12.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Auth0 OAuth provider for FastMCP.
|
|
2
|
+
|
|
3
|
+
This module provides a complete Auth0 integration that's ready to use with
|
|
4
|
+
just the configuration URL, client ID, client secret, audience, and base URL.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
```python
|
|
8
|
+
from fastmcp import FastMCP
|
|
9
|
+
from fastmcp.server.auth.providers.auth0 import Auth0Provider
|
|
10
|
+
|
|
11
|
+
# Simple Auth0 OAuth protection
|
|
12
|
+
auth = Auth0Provider(
|
|
13
|
+
config_url="https://auth0.config.url",
|
|
14
|
+
client_id="your-auth0-client-id",
|
|
15
|
+
client_secret="your-auth0-client-secret",
|
|
16
|
+
audience="your-auth0-api-audience",
|
|
17
|
+
base_url="http://localhost:8000",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
mcp = FastMCP("My Protected Server", auth=auth)
|
|
21
|
+
```
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from pydantic import AnyHttpUrl, SecretStr, field_validator
|
|
25
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
26
|
+
|
|
27
|
+
from fastmcp.server.auth.oidc_proxy import OIDCProxy
|
|
28
|
+
from fastmcp.utilities.auth import parse_scopes
|
|
29
|
+
from fastmcp.utilities.logging import get_logger
|
|
30
|
+
from fastmcp.utilities.storage import KVStorage
|
|
31
|
+
from fastmcp.utilities.types import NotSet, NotSetT
|
|
32
|
+
|
|
33
|
+
logger = get_logger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Auth0ProviderSettings(BaseSettings):
|
|
37
|
+
"""Settings for Auth0 OIDC provider."""
|
|
38
|
+
|
|
39
|
+
model_config = SettingsConfigDict(
|
|
40
|
+
env_prefix="FASTMCP_SERVER_AUTH_AUTH0_",
|
|
41
|
+
env_file=".env",
|
|
42
|
+
extra="ignore",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
config_url: AnyHttpUrl | None = None
|
|
46
|
+
client_id: str | None = None
|
|
47
|
+
client_secret: SecretStr | None = None
|
|
48
|
+
audience: str | None = None
|
|
49
|
+
base_url: AnyHttpUrl | None = None
|
|
50
|
+
redirect_path: str | None = None
|
|
51
|
+
required_scopes: list[str] | None = None
|
|
52
|
+
allowed_client_redirect_uris: list[str] | None = None
|
|
53
|
+
|
|
54
|
+
@field_validator("required_scopes", mode="before")
|
|
55
|
+
@classmethod
|
|
56
|
+
def _parse_scopes(cls, v):
|
|
57
|
+
return parse_scopes(v)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Auth0Provider(OIDCProxy):
|
|
61
|
+
"""An Auth0 provider implementation for FastMCP.
|
|
62
|
+
|
|
63
|
+
This provider is a complete Auth0 integration that's ready to use with
|
|
64
|
+
just the configuration URL, client ID, client secret, audience, and base URL.
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
```python
|
|
68
|
+
from fastmcp import FastMCP
|
|
69
|
+
from fastmcp.server.auth.providers.auth0 import Auth0Provider
|
|
70
|
+
|
|
71
|
+
# Simple Auth0 OAuth protection
|
|
72
|
+
auth = Auth0Provider(
|
|
73
|
+
config_url="https://auth0.config.url",
|
|
74
|
+
client_id="your-auth0-client-id",
|
|
75
|
+
client_secret="your-auth0-client-secret",
|
|
76
|
+
audience="your-auth0-api-audience",
|
|
77
|
+
base_url="http://localhost:8000",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
mcp = FastMCP("My Protected Server", auth=auth)
|
|
81
|
+
```
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
*,
|
|
87
|
+
config_url: AnyHttpUrl | str | NotSetT = NotSet,
|
|
88
|
+
client_id: str | NotSetT = NotSet,
|
|
89
|
+
client_secret: str | NotSetT = NotSet,
|
|
90
|
+
audience: str | NotSetT = NotSet,
|
|
91
|
+
base_url: AnyHttpUrl | str | NotSetT = NotSet,
|
|
92
|
+
required_scopes: list[str] | NotSetT = NotSet,
|
|
93
|
+
redirect_path: str | NotSetT = NotSet,
|
|
94
|
+
allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
|
|
95
|
+
client_storage: KVStorage | None = None,
|
|
96
|
+
) -> None:
|
|
97
|
+
"""Initialize Auth0 OAuth provider.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
config_url: Auth0 config URL
|
|
101
|
+
client_id: Auth0 application client id
|
|
102
|
+
client_secret: Auth0 application client secret
|
|
103
|
+
audience: Auth0 API audience
|
|
104
|
+
base_url: Public URL of your FastMCP server (for OAuth callbacks)
|
|
105
|
+
required_scopes: Required Auth0 scopes (defaults to ["openid"])
|
|
106
|
+
redirect_path: Redirect path configured in Auth0 application
|
|
107
|
+
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
|
|
108
|
+
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
|
|
109
|
+
client_storage: Storage implementation for OAuth client registrations.
|
|
110
|
+
Defaults to file-based storage if not specified.
|
|
111
|
+
"""
|
|
112
|
+
settings = Auth0ProviderSettings.model_validate(
|
|
113
|
+
{
|
|
114
|
+
k: v
|
|
115
|
+
for k, v in {
|
|
116
|
+
"config_url": config_url,
|
|
117
|
+
"client_id": client_id,
|
|
118
|
+
"client_secret": client_secret,
|
|
119
|
+
"audience": audience,
|
|
120
|
+
"base_url": base_url,
|
|
121
|
+
"required_scopes": required_scopes,
|
|
122
|
+
"redirect_path": redirect_path,
|
|
123
|
+
"allowed_client_redirect_uris": allowed_client_redirect_uris,
|
|
124
|
+
}.items()
|
|
125
|
+
if v is not NotSet
|
|
126
|
+
}
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if not settings.config_url:
|
|
130
|
+
raise ValueError(
|
|
131
|
+
"config_url is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if not settings.client_id:
|
|
135
|
+
raise ValueError(
|
|
136
|
+
"client_id is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if not settings.client_secret:
|
|
140
|
+
raise ValueError(
|
|
141
|
+
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if not settings.audience:
|
|
145
|
+
raise ValueError(
|
|
146
|
+
"audience is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
if not settings.base_url:
|
|
150
|
+
raise ValueError(
|
|
151
|
+
"base_url is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_BASE_URL"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
auth0_required_scopes = settings.required_scopes or ["openid"]
|
|
155
|
+
|
|
156
|
+
init_kwargs = {
|
|
157
|
+
"config_url": settings.config_url,
|
|
158
|
+
"client_id": settings.client_id,
|
|
159
|
+
"client_secret": settings.client_secret.get_secret_value(),
|
|
160
|
+
"audience": settings.audience,
|
|
161
|
+
"base_url": settings.base_url,
|
|
162
|
+
"redirect_path": settings.redirect_path,
|
|
163
|
+
"required_scopes": auth0_required_scopes,
|
|
164
|
+
"allowed_client_redirect_uris": settings.allowed_client_redirect_uris,
|
|
165
|
+
"client_storage": client_storage,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
super().__init__(**init_kwargs)
|
|
169
|
+
|
|
170
|
+
logger.info(
|
|
171
|
+
"Initialized Auth0 OAuth provider for client %s with scopes: %s",
|
|
172
|
+
settings.client_id,
|
|
173
|
+
auth0_required_scopes,
|
|
174
|
+
)
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""AWS Cognito OAuth provider for FastMCP.
|
|
2
|
+
|
|
3
|
+
This module provides a complete AWS Cognito OAuth integration that's ready to use
|
|
4
|
+
with a user pool ID, domain prefix, client ID and client secret. It handles all
|
|
5
|
+
the complexity of AWS Cognito's OAuth flow, token validation, and user management.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
```python
|
|
9
|
+
from fastmcp import FastMCP
|
|
10
|
+
from fastmcp.server.auth.providers.aws_cognito import AWSCognitoProvider
|
|
11
|
+
|
|
12
|
+
# Simple AWS Cognito OAuth protection
|
|
13
|
+
auth = AWSCognitoProvider(
|
|
14
|
+
user_pool_id="your-user-pool-id",
|
|
15
|
+
aws_region="eu-central-1",
|
|
16
|
+
client_id="your-cognito-client-id",
|
|
17
|
+
client_secret="your-cognito-client-secret"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
mcp = FastMCP("My Protected Server", auth=auth)
|
|
21
|
+
```
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from pydantic import AnyHttpUrl, SecretStr, field_validator
|
|
27
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
28
|
+
|
|
29
|
+
from fastmcp.server.auth import TokenVerifier
|
|
30
|
+
from fastmcp.server.auth.auth import AccessToken
|
|
31
|
+
from fastmcp.server.auth.oidc_proxy import OIDCProxy
|
|
32
|
+
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
|
33
|
+
from fastmcp.utilities.auth import parse_scopes
|
|
34
|
+
from fastmcp.utilities.logging import get_logger
|
|
35
|
+
from fastmcp.utilities.types import NotSet, NotSetT
|
|
36
|
+
|
|
37
|
+
logger = get_logger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AWSCognitoProviderSettings(BaseSettings):
|
|
41
|
+
"""Settings for AWS Cognito OAuth provider."""
|
|
42
|
+
|
|
43
|
+
model_config = SettingsConfigDict(
|
|
44
|
+
env_prefix="FASTMCP_SERVER_AUTH_AWS_COGNITO_",
|
|
45
|
+
env_file=".env",
|
|
46
|
+
extra="ignore",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
user_pool_id: str | None = None
|
|
50
|
+
aws_region: str | None = None
|
|
51
|
+
client_id: str | None = None
|
|
52
|
+
client_secret: SecretStr | None = None
|
|
53
|
+
base_url: AnyHttpUrl | str | None = None
|
|
54
|
+
redirect_path: str | None = None
|
|
55
|
+
required_scopes: list[str] | None = None
|
|
56
|
+
allowed_client_redirect_uris: list[str] | None = None
|
|
57
|
+
|
|
58
|
+
@field_validator("required_scopes", mode="before")
|
|
59
|
+
@classmethod
|
|
60
|
+
def _parse_scopes(cls, v):
|
|
61
|
+
return parse_scopes(v)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class AWSCognitoTokenVerifier(JWTVerifier):
|
|
65
|
+
"""Token verifier that filters claims to Cognito-specific subset."""
|
|
66
|
+
|
|
67
|
+
async def verify_token(self, token: str) -> AccessToken | None:
|
|
68
|
+
"""Verify token and filter claims to Cognito-specific subset."""
|
|
69
|
+
# Use base JWT verification
|
|
70
|
+
access_token = await super().verify_token(token)
|
|
71
|
+
if not access_token:
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
# Filter claims to Cognito-specific subset
|
|
75
|
+
cognito_claims = {
|
|
76
|
+
"sub": access_token.claims.get("sub"),
|
|
77
|
+
"username": access_token.claims.get("username"),
|
|
78
|
+
"cognito:groups": access_token.claims.get("cognito:groups", []),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Return new AccessToken with filtered claims
|
|
82
|
+
return AccessToken(
|
|
83
|
+
token=access_token.token,
|
|
84
|
+
client_id=access_token.client_id,
|
|
85
|
+
scopes=access_token.scopes,
|
|
86
|
+
expires_at=access_token.expires_at,
|
|
87
|
+
claims=cognito_claims,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class AWSCognitoProvider(OIDCProxy):
|
|
92
|
+
"""Complete AWS Cognito OAuth provider for FastMCP.
|
|
93
|
+
|
|
94
|
+
This provider makes it trivial to add AWS Cognito OAuth protection to any
|
|
95
|
+
FastMCP server using OIDC Discovery. Just provide your Cognito User Pool details,
|
|
96
|
+
client credentials, and a base URL, and you're ready to go.
|
|
97
|
+
|
|
98
|
+
Features:
|
|
99
|
+
- Automatic OIDC Discovery from AWS Cognito User Pool
|
|
100
|
+
- Automatic JWT token validation via Cognito's public keys
|
|
101
|
+
- Cognito-specific claim filtering (sub, username, cognito:groups)
|
|
102
|
+
- Support for Cognito User Pools
|
|
103
|
+
|
|
104
|
+
Example:
|
|
105
|
+
```python
|
|
106
|
+
from fastmcp import FastMCP
|
|
107
|
+
from fastmcp.server.auth.providers.aws_cognito import AWSCognitoProvider
|
|
108
|
+
|
|
109
|
+
auth = AWSCognitoProvider(
|
|
110
|
+
user_pool_id="eu-central-1_XXXXXXXXX",
|
|
111
|
+
aws_region="eu-central-1",
|
|
112
|
+
client_id="your-cognito-client-id",
|
|
113
|
+
client_secret="your-cognito-client-secret",
|
|
114
|
+
base_url="https://my-server.com",
|
|
115
|
+
redirect_path="/custom/callback",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
mcp = FastMCP("My App", auth=auth)
|
|
119
|
+
```
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def __init__(
|
|
123
|
+
self,
|
|
124
|
+
*,
|
|
125
|
+
user_pool_id: str | NotSetT = NotSet,
|
|
126
|
+
aws_region: str | NotSetT = NotSet,
|
|
127
|
+
client_id: str | NotSetT = NotSet,
|
|
128
|
+
client_secret: str | NotSetT = NotSet,
|
|
129
|
+
base_url: AnyHttpUrl | str | NotSetT = NotSet,
|
|
130
|
+
redirect_path: str | NotSetT = NotSet,
|
|
131
|
+
required_scopes: list[str] | NotSetT = NotSet,
|
|
132
|
+
allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
|
|
133
|
+
):
|
|
134
|
+
"""Initialize AWS Cognito OAuth provider.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
user_pool_id: Your Cognito User Pool ID (e.g., "eu-central-1_XXXXXXXXX")
|
|
138
|
+
aws_region: AWS region where your User Pool is located (defaults to "eu-central-1")
|
|
139
|
+
client_id: Cognito app client ID
|
|
140
|
+
client_secret: Cognito app client secret
|
|
141
|
+
base_url: Public URL of your FastMCP server (for OAuth callbacks)
|
|
142
|
+
redirect_path: Redirect path configured in Cognito app (defaults to "/auth/callback")
|
|
143
|
+
required_scopes: Required Cognito scopes (defaults to ["openid"])
|
|
144
|
+
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
|
|
145
|
+
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
settings = AWSCognitoProviderSettings.model_validate(
|
|
149
|
+
{
|
|
150
|
+
k: v
|
|
151
|
+
for k, v in {
|
|
152
|
+
"user_pool_id": user_pool_id,
|
|
153
|
+
"aws_region": aws_region,
|
|
154
|
+
"client_id": client_id,
|
|
155
|
+
"client_secret": client_secret,
|
|
156
|
+
"base_url": base_url,
|
|
157
|
+
"redirect_path": redirect_path,
|
|
158
|
+
"required_scopes": required_scopes,
|
|
159
|
+
"allowed_client_redirect_uris": allowed_client_redirect_uris,
|
|
160
|
+
}.items()
|
|
161
|
+
if v is not NotSet
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Validate required settings
|
|
166
|
+
if not settings.user_pool_id:
|
|
167
|
+
raise ValueError(
|
|
168
|
+
"user_pool_id is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID"
|
|
169
|
+
)
|
|
170
|
+
if not settings.client_id:
|
|
171
|
+
raise ValueError(
|
|
172
|
+
"client_id is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID"
|
|
173
|
+
)
|
|
174
|
+
if not settings.client_secret:
|
|
175
|
+
raise ValueError(
|
|
176
|
+
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Apply defaults
|
|
180
|
+
required_scopes_final = settings.required_scopes or ["openid"]
|
|
181
|
+
allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
|
|
182
|
+
aws_region_final = settings.aws_region or "eu-central-1"
|
|
183
|
+
redirect_path_final = settings.redirect_path or "/auth/callback"
|
|
184
|
+
|
|
185
|
+
# Construct OIDC discovery URL
|
|
186
|
+
config_url = f"https://cognito-idp.{aws_region_final}.amazonaws.com/{settings.user_pool_id}/.well-known/openid-configuration"
|
|
187
|
+
|
|
188
|
+
# Extract secret string from SecretStr
|
|
189
|
+
client_secret_str = (
|
|
190
|
+
settings.client_secret.get_secret_value() if settings.client_secret else ""
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Store Cognito-specific info for claim filtering
|
|
194
|
+
self.user_pool_id = settings.user_pool_id
|
|
195
|
+
self.aws_region = aws_region_final
|
|
196
|
+
|
|
197
|
+
# Initialize OIDC proxy with Cognito discovery
|
|
198
|
+
super().__init__(
|
|
199
|
+
config_url=config_url,
|
|
200
|
+
client_id=settings.client_id,
|
|
201
|
+
client_secret=client_secret_str,
|
|
202
|
+
algorithm="RS256",
|
|
203
|
+
required_scopes=required_scopes_final,
|
|
204
|
+
base_url=settings.base_url,
|
|
205
|
+
redirect_path=redirect_path_final,
|
|
206
|
+
allowed_client_redirect_uris=allowed_client_redirect_uris_final,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
logger.info(
|
|
210
|
+
"Initialized AWS Cognito OAuth provider for client %s with scopes: %s",
|
|
211
|
+
settings.client_id,
|
|
212
|
+
required_scopes_final,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def get_token_verifier(
|
|
216
|
+
self,
|
|
217
|
+
*,
|
|
218
|
+
algorithm: str | None = None,
|
|
219
|
+
audience: str | None = None,
|
|
220
|
+
required_scopes: list[str] | None = None,
|
|
221
|
+
timeout_seconds: int | None = None,
|
|
222
|
+
) -> TokenVerifier:
|
|
223
|
+
"""Creates a Cognito-specific token verifier with claim filtering.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
algorithm: Optional token verifier algorithm
|
|
227
|
+
audience: Optional token verifier audience
|
|
228
|
+
required_scopes: Optional token verifier required_scopes
|
|
229
|
+
timeout_seconds: HTTP request timeout in seconds
|
|
230
|
+
"""
|
|
231
|
+
return AWSCognitoTokenVerifier(
|
|
232
|
+
issuer=str(self.oidc_config.issuer),
|
|
233
|
+
audience=audience,
|
|
234
|
+
algorithm=algorithm,
|
|
235
|
+
jwks_uri=str(self.oidc_config.jwks_uri),
|
|
236
|
+
required_scopes=required_scopes,
|
|
237
|
+
)
|
|
@@ -14,6 +14,7 @@ from fastmcp.server.auth import AccessToken, TokenVerifier
|
|
|
14
14
|
from fastmcp.server.auth.oauth_proxy import OAuthProxy
|
|
15
15
|
from fastmcp.utilities.auth import parse_scopes
|
|
16
16
|
from fastmcp.utilities.logging import get_logger
|
|
17
|
+
from fastmcp.utilities.storage import KVStorage
|
|
17
18
|
from fastmcp.utilities.types import NotSet, NotSetT
|
|
18
19
|
|
|
19
20
|
logger = get_logger(__name__)
|
|
@@ -160,6 +161,7 @@ class AzureProvider(OAuthProxy):
|
|
|
160
161
|
required_scopes: list[str] | None | NotSetT = NotSet,
|
|
161
162
|
timeout_seconds: int | NotSetT = NotSet,
|
|
162
163
|
allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
|
|
164
|
+
client_storage: KVStorage | None = None,
|
|
163
165
|
):
|
|
164
166
|
"""Initialize Azure OAuth provider.
|
|
165
167
|
|
|
@@ -173,6 +175,8 @@ class AzureProvider(OAuthProxy):
|
|
|
173
175
|
timeout_seconds: HTTP request timeout for Azure API calls
|
|
174
176
|
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
|
|
175
177
|
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
|
|
178
|
+
client_storage: Storage implementation for OAuth client registrations.
|
|
179
|
+
Defaults to file-based storage if not specified.
|
|
176
180
|
"""
|
|
177
181
|
settings = AzureProviderSettings.model_validate(
|
|
178
182
|
{
|
|
@@ -211,7 +215,6 @@ class AzureProvider(OAuthProxy):
|
|
|
211
215
|
# Apply defaults
|
|
212
216
|
tenant_id_final = settings.tenant_id
|
|
213
217
|
|
|
214
|
-
redirect_path_final = settings.redirect_path or "/auth/callback"
|
|
215
218
|
timeout_seconds_final = settings.timeout_seconds or 10
|
|
216
219
|
# Default scopes for Azure - User.Read gives us access to user info via Graph API
|
|
217
220
|
scopes_final = settings.required_scopes or [
|
|
@@ -249,9 +252,10 @@ class AzureProvider(OAuthProxy):
|
|
|
249
252
|
upstream_client_secret=client_secret_str,
|
|
250
253
|
token_verifier=token_verifier,
|
|
251
254
|
base_url=settings.base_url,
|
|
252
|
-
redirect_path=
|
|
255
|
+
redirect_path=settings.redirect_path,
|
|
253
256
|
issuer_url=settings.base_url,
|
|
254
257
|
allowed_client_redirect_uris=allowed_client_redirect_uris_final,
|
|
258
|
+
client_storage=client_storage,
|
|
255
259
|
)
|
|
256
260
|
|
|
257
261
|
logger.info(
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Descope authentication provider for FastMCP.
|
|
2
|
+
|
|
3
|
+
This module provides DescopeProvider - a complete authentication solution that integrates
|
|
4
|
+
with Descope's OAuth 2.1 and OpenID Connect services, supporting Dynamic Client Registration (DCR)
|
|
5
|
+
for seamless MCP client authentication.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
from pydantic import AnyHttpUrl
|
|
14
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
15
|
+
from starlette.responses import JSONResponse
|
|
16
|
+
from starlette.routing import Route
|
|
17
|
+
|
|
18
|
+
from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier
|
|
19
|
+
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
|
20
|
+
from fastmcp.utilities.logging import get_logger
|
|
21
|
+
from fastmcp.utilities.types import NotSet, NotSetT
|
|
22
|
+
|
|
23
|
+
logger = get_logger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DescopeProviderSettings(BaseSettings):
|
|
27
|
+
model_config = SettingsConfigDict(
|
|
28
|
+
env_prefix="FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_",
|
|
29
|
+
env_file=".env",
|
|
30
|
+
extra="ignore",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
project_id: str
|
|
34
|
+
base_url: AnyHttpUrl
|
|
35
|
+
descope_base_url: AnyHttpUrl = AnyHttpUrl("https://api.descope.com")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DescopeProvider(RemoteAuthProvider):
|
|
39
|
+
"""Descope metadata provider for DCR (Dynamic Client Registration).
|
|
40
|
+
|
|
41
|
+
This provider implements Descope integration using metadata forwarding.
|
|
42
|
+
This is the recommended approach for Descope DCR
|
|
43
|
+
as it allows Descope to handle the OAuth flow directly while FastMCP acts
|
|
44
|
+
as a resource server.
|
|
45
|
+
|
|
46
|
+
IMPORTANT SETUP REQUIREMENTS:
|
|
47
|
+
|
|
48
|
+
1. Enable Dynamic Client Registration in Descope Console:
|
|
49
|
+
- Go to the [Inbound Apps page](https://app.descope.com/apps/inbound) of the Descope Console
|
|
50
|
+
- Click **DCR Settings**
|
|
51
|
+
- Enable **Dynamic Client Registration (DCR)**
|
|
52
|
+
- Define allowed scopes
|
|
53
|
+
|
|
54
|
+
2. Note your Project ID:
|
|
55
|
+
- Save your Project ID from [Project Settings](https://app.descope.com/settings/project)
|
|
56
|
+
- Example: P2abc...123
|
|
57
|
+
|
|
58
|
+
For detailed setup instructions, see:
|
|
59
|
+
https://docs.descope.com/identity-federation/inbound-apps/creating-inbound-apps#method-2-dynamic-client-registration-dcr
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
```python
|
|
63
|
+
from fastmcp.server.auth.providers.descope import DescopeProvider
|
|
64
|
+
|
|
65
|
+
# Create Descope metadata provider (JWT verifier created automatically)
|
|
66
|
+
descope_auth = DescopeProvider(
|
|
67
|
+
project_id="P2abc...123",
|
|
68
|
+
base_url="https://your-fastmcp-server.com",
|
|
69
|
+
descope_base_url="https://api.descope.com",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Use with FastMCP
|
|
73
|
+
mcp = FastMCP("My App", auth=descope_auth)
|
|
74
|
+
```
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
*,
|
|
80
|
+
project_id: str | NotSetT = NotSet,
|
|
81
|
+
base_url: AnyHttpUrl | str | NotSetT = NotSet,
|
|
82
|
+
descope_base_url: AnyHttpUrl | str | NotSetT = NotSet,
|
|
83
|
+
token_verifier: TokenVerifier | None = None,
|
|
84
|
+
):
|
|
85
|
+
"""Initialize Descope metadata provider.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
project_id: Your Descope Project ID (e.g., "P2abc...123")
|
|
89
|
+
base_url: Public URL of this FastMCP server
|
|
90
|
+
descope_base_url: Descope API base URL (defaults to https://api.descope.com)
|
|
91
|
+
token_verifier: Optional token verifier. If None, creates JWT verifier for Descope
|
|
92
|
+
"""
|
|
93
|
+
settings = DescopeProviderSettings.model_validate(
|
|
94
|
+
{
|
|
95
|
+
k: v
|
|
96
|
+
for k, v in {
|
|
97
|
+
"project_id": project_id,
|
|
98
|
+
"base_url": base_url,
|
|
99
|
+
"descope_base_url": descope_base_url,
|
|
100
|
+
}.items()
|
|
101
|
+
if v is not NotSet
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
self.project_id = settings.project_id
|
|
106
|
+
self.base_url = str(settings.base_url).rstrip("/")
|
|
107
|
+
self.descope_base_url = str(settings.descope_base_url).rstrip("/")
|
|
108
|
+
|
|
109
|
+
# Create default JWT verifier if none provided
|
|
110
|
+
if token_verifier is None:
|
|
111
|
+
token_verifier = JWTVerifier(
|
|
112
|
+
jwks_uri=f"{self.descope_base_url}/{self.project_id}/.well-known/jwks.json",
|
|
113
|
+
issuer=f"{self.descope_base_url}/v1/apps/{self.project_id}",
|
|
114
|
+
algorithm="RS256",
|
|
115
|
+
audience=self.project_id,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Initialize RemoteAuthProvider with Descope as the authorization server
|
|
119
|
+
super().__init__(
|
|
120
|
+
token_verifier=token_verifier,
|
|
121
|
+
authorization_servers=[
|
|
122
|
+
AnyHttpUrl(f"{self.descope_base_url}/v1/apps/{self.project_id}")
|
|
123
|
+
],
|
|
124
|
+
base_url=self.base_url,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def get_routes(
|
|
128
|
+
self,
|
|
129
|
+
mcp_path: str | None = None,
|
|
130
|
+
mcp_endpoint: Any | None = None,
|
|
131
|
+
) -> list[Route]:
|
|
132
|
+
"""Get OAuth routes including Descope authorization server metadata forwarding.
|
|
133
|
+
|
|
134
|
+
This returns the standard protected resource routes plus an authorization server
|
|
135
|
+
metadata endpoint that forwards Descope's OAuth metadata to clients.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
|
|
139
|
+
mcp_endpoint: The MCP endpoint handler to protect with auth
|
|
140
|
+
"""
|
|
141
|
+
# Get the standard protected resource routes from RemoteAuthProvider
|
|
142
|
+
routes = super().get_routes(mcp_path, mcp_endpoint)
|
|
143
|
+
|
|
144
|
+
async def oauth_authorization_server_metadata(request):
|
|
145
|
+
"""Forward Descope OAuth authorization server metadata with FastMCP customizations."""
|
|
146
|
+
try:
|
|
147
|
+
async with httpx.AsyncClient() as client:
|
|
148
|
+
response = await client.get(
|
|
149
|
+
f"{self.descope_base_url}/v1/apps/{self.project_id}/.well-known/oauth-authorization-server"
|
|
150
|
+
)
|
|
151
|
+
response.raise_for_status()
|
|
152
|
+
metadata = response.json()
|
|
153
|
+
return JSONResponse(metadata)
|
|
154
|
+
except Exception as e:
|
|
155
|
+
return JSONResponse(
|
|
156
|
+
{
|
|
157
|
+
"error": "server_error",
|
|
158
|
+
"error_description": f"Failed to fetch Descope metadata: {e}",
|
|
159
|
+
},
|
|
160
|
+
status_code=500,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Add Descope authorization server metadata forwarding
|
|
164
|
+
routes.append(
|
|
165
|
+
Route(
|
|
166
|
+
"/.well-known/oauth-authorization-server",
|
|
167
|
+
endpoint=oauth_authorization_server_metadata,
|
|
168
|
+
methods=["GET"],
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
return routes
|
|
@@ -30,6 +30,7 @@ from fastmcp.server.auth.auth import AccessToken
|
|
|
30
30
|
from fastmcp.server.auth.oauth_proxy import OAuthProxy
|
|
31
31
|
from fastmcp.utilities.auth import parse_scopes
|
|
32
32
|
from fastmcp.utilities.logging import get_logger
|
|
33
|
+
from fastmcp.utilities.storage import KVStorage
|
|
33
34
|
from fastmcp.utilities.types import NotSet, NotSetT
|
|
34
35
|
|
|
35
36
|
logger = get_logger(__name__)
|
|
@@ -201,6 +202,7 @@ class GitHubProvider(OAuthProxy):
|
|
|
201
202
|
required_scopes: list[str] | NotSetT = NotSet,
|
|
202
203
|
timeout_seconds: int | NotSetT = NotSet,
|
|
203
204
|
allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
|
|
205
|
+
client_storage: KVStorage | None = None,
|
|
204
206
|
):
|
|
205
207
|
"""Initialize GitHub OAuth provider.
|
|
206
208
|
|
|
@@ -213,6 +215,8 @@ class GitHubProvider(OAuthProxy):
|
|
|
213
215
|
timeout_seconds: HTTP request timeout for GitHub API calls
|
|
214
216
|
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
|
|
215
217
|
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
|
|
218
|
+
client_storage: Storage implementation for OAuth client registrations.
|
|
219
|
+
Defaults to file-based storage if not specified.
|
|
216
220
|
"""
|
|
217
221
|
|
|
218
222
|
settings = GitHubProviderSettings.model_validate(
|
|
@@ -243,7 +247,6 @@ class GitHubProvider(OAuthProxy):
|
|
|
243
247
|
|
|
244
248
|
# Apply defaults
|
|
245
249
|
|
|
246
|
-
redirect_path_final = settings.redirect_path or "/auth/callback"
|
|
247
250
|
timeout_seconds_final = settings.timeout_seconds or 10
|
|
248
251
|
required_scopes_final = settings.required_scopes or ["user"]
|
|
249
252
|
allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
|
|
@@ -267,9 +270,10 @@ class GitHubProvider(OAuthProxy):
|
|
|
267
270
|
upstream_client_secret=client_secret_str,
|
|
268
271
|
token_verifier=token_verifier,
|
|
269
272
|
base_url=settings.base_url,
|
|
270
|
-
redirect_path=
|
|
273
|
+
redirect_path=settings.redirect_path,
|
|
271
274
|
issuer_url=settings.base_url, # We act as the issuer for client registration
|
|
272
275
|
allowed_client_redirect_uris=allowed_client_redirect_uris_final,
|
|
276
|
+
client_storage=client_storage,
|
|
273
277
|
)
|
|
274
278
|
|
|
275
279
|
logger.info(
|