fastmcp 2.13.0rc1__py3-none-any.whl → 2.13.0rc3__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/cli.py +1 -0
- fastmcp/cli/install/claude_code.py +3 -3
- fastmcp/client/oauth_callback.py +6 -2
- fastmcp/client/transports.py +1 -0
- fastmcp/resources/types.py +30 -24
- fastmcp/server/auth/handlers/authorize.py +324 -0
- fastmcp/server/auth/jwt_issuer.py +39 -92
- fastmcp/server/auth/middleware.py +96 -0
- fastmcp/server/auth/oauth_proxy.py +238 -228
- fastmcp/server/auth/oidc_proxy.py +16 -1
- fastmcp/server/auth/providers/auth0.py +30 -17
- fastmcp/server/auth/providers/aws.py +17 -2
- fastmcp/server/auth/providers/azure.py +69 -31
- fastmcp/server/auth/providers/github.py +17 -2
- fastmcp/server/auth/providers/google.py +18 -3
- fastmcp/server/auth/providers/workos.py +17 -2
- fastmcp/server/context.py +33 -2
- fastmcp/server/http.py +6 -3
- fastmcp/server/middleware/tool_injection.py +116 -0
- fastmcp/server/server.py +47 -26
- fastmcp/settings.py +2 -1
- fastmcp/tools/tool_manager.py +8 -4
- fastmcp/utilities/cli.py +62 -22
- fastmcp/utilities/ui.py +126 -6
- {fastmcp-2.13.0rc1.dist-info → fastmcp-2.13.0rc3.dist-info}/METADATA +5 -5
- {fastmcp-2.13.0rc1.dist-info → fastmcp-2.13.0rc3.dist-info}/RECORD +29 -26
- {fastmcp-2.13.0rc1.dist-info → fastmcp-2.13.0rc3.dist-info}/WHEEL +0 -0
- {fastmcp-2.13.0rc1.dist-info → fastmcp-2.13.0rc3.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.13.0rc1.dist-info → fastmcp-2.13.0rc3.dist-info}/licenses/LICENSE +0 -0
|
@@ -215,8 +215,12 @@ class OIDCProxy(OAuthProxy):
|
|
|
215
215
|
# Client configuration
|
|
216
216
|
allowed_client_redirect_uris: list[str] | None = None,
|
|
217
217
|
client_storage: AsyncKeyValue | None = None,
|
|
218
|
+
# JWT and encryption keys
|
|
219
|
+
jwt_signing_key: str | bytes | None = None,
|
|
218
220
|
# Token validation configuration
|
|
219
221
|
token_endpoint_auth_method: str | None = None,
|
|
222
|
+
# Consent screen configuration
|
|
223
|
+
require_authorization_consent: bool = True,
|
|
220
224
|
) -> None:
|
|
221
225
|
"""Initialize the OIDC proxy provider.
|
|
222
226
|
|
|
@@ -238,10 +242,19 @@ class OIDCProxy(OAuthProxy):
|
|
|
238
242
|
If None (default), only localhost redirect URIs are allowed.
|
|
239
243
|
If empty list, all redirect URIs are allowed (not recommended for production).
|
|
240
244
|
These are for MCP clients performing loopback redirects, NOT for the upstream OAuth app.
|
|
241
|
-
client_storage:
|
|
245
|
+
client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
|
|
246
|
+
If None, a DiskStore will be created in the data directory (derived from `platformdirs`). The
|
|
247
|
+
disk store will be encrypted using a key derived from the JWT Signing Key.
|
|
248
|
+
jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes). If bytes are provided,
|
|
249
|
+
they will be used as is. If a string is provided, it will be derived into a 32-byte key. If not
|
|
250
|
+
provided, the upstream client secret will be used to derive a 32-byte key using PBKDF2.
|
|
242
251
|
token_endpoint_auth_method: Token endpoint authentication method for upstream server.
|
|
243
252
|
Common values: "client_secret_basic", "client_secret_post", "none".
|
|
244
253
|
If None, authlib will use its default (typically "client_secret_basic").
|
|
254
|
+
require_authorization_consent: Whether to require user consent before authorizing clients (default True).
|
|
255
|
+
When True, users see a consent screen before being redirected to the upstream IdP.
|
|
256
|
+
When False, authorization proceeds directly without user confirmation.
|
|
257
|
+
SECURITY WARNING: Only disable for local development or testing environments.
|
|
245
258
|
"""
|
|
246
259
|
if not config_url:
|
|
247
260
|
raise ValueError("Missing required config URL")
|
|
@@ -295,7 +308,9 @@ class OIDCProxy(OAuthProxy):
|
|
|
295
308
|
"service_documentation_url": self.oidc_config.service_documentation,
|
|
296
309
|
"allowed_client_redirect_uris": allowed_client_redirect_uris,
|
|
297
310
|
"client_storage": client_storage,
|
|
311
|
+
"jwt_signing_key": jwt_signing_key,
|
|
298
312
|
"token_endpoint_auth_method": token_endpoint_auth_method,
|
|
313
|
+
"require_authorization_consent": require_authorization_consent,
|
|
299
314
|
}
|
|
300
315
|
|
|
301
316
|
if redirect_path:
|
|
@@ -52,6 +52,7 @@ class Auth0ProviderSettings(BaseSettings):
|
|
|
52
52
|
redirect_path: str | None = None
|
|
53
53
|
required_scopes: list[str] | None = None
|
|
54
54
|
allowed_client_redirect_uris: list[str] | None = None
|
|
55
|
+
jwt_signing_key: str | None = None
|
|
55
56
|
|
|
56
57
|
@field_validator("required_scopes", mode="before")
|
|
57
58
|
@classmethod
|
|
@@ -96,6 +97,8 @@ class Auth0Provider(OIDCProxy):
|
|
|
96
97
|
redirect_path: str | NotSetT = NotSet,
|
|
97
98
|
allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
|
|
98
99
|
client_storage: AsyncKeyValue | None = None,
|
|
100
|
+
jwt_signing_key: str | bytes | NotSetT = NotSet,
|
|
101
|
+
require_authorization_consent: bool = True,
|
|
99
102
|
) -> None:
|
|
100
103
|
"""Initialize Auth0 OAuth provider.
|
|
101
104
|
|
|
@@ -111,7 +114,16 @@ class Auth0Provider(OIDCProxy):
|
|
|
111
114
|
redirect_path: Redirect path configured in Auth0 application
|
|
112
115
|
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
|
|
113
116
|
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
|
|
114
|
-
client_storage:
|
|
117
|
+
client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
|
|
118
|
+
If None, a DiskStore will be created in the data directory (derived from `platformdirs`). The
|
|
119
|
+
disk store will be encrypted using a key derived from the JWT Signing Key.
|
|
120
|
+
jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes). If bytes are provided,
|
|
121
|
+
they will be used as is. If a string is provided, it will be derived into a 32-byte key. If not
|
|
122
|
+
provided, the upstream client secret will be used to derive a 32-byte key using PBKDF2.
|
|
123
|
+
require_authorization_consent: Whether to require user consent before authorizing clients (default True).
|
|
124
|
+
When True, users see a consent screen before being redirected to Auth0.
|
|
125
|
+
When False, authorization proceeds directly without user confirmation.
|
|
126
|
+
SECURITY WARNING: Only disable for local development or testing environments.
|
|
115
127
|
"""
|
|
116
128
|
settings = Auth0ProviderSettings.model_validate(
|
|
117
129
|
{
|
|
@@ -126,6 +138,7 @@ class Auth0Provider(OIDCProxy):
|
|
|
126
138
|
"required_scopes": required_scopes,
|
|
127
139
|
"redirect_path": redirect_path,
|
|
128
140
|
"allowed_client_redirect_uris": allowed_client_redirect_uris,
|
|
141
|
+
"jwt_signing_key": jwt_signing_key,
|
|
129
142
|
}.items()
|
|
130
143
|
if v is not NotSet
|
|
131
144
|
}
|
|
@@ -158,22 +171,22 @@ class Auth0Provider(OIDCProxy):
|
|
|
158
171
|
|
|
159
172
|
auth0_required_scopes = settings.required_scopes or ["openid"]
|
|
160
173
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
logger.
|
|
174
|
+
super().__init__(
|
|
175
|
+
config_url=settings.config_url,
|
|
176
|
+
client_id=settings.client_id,
|
|
177
|
+
client_secret=settings.client_secret.get_secret_value(),
|
|
178
|
+
audience=settings.audience,
|
|
179
|
+
base_url=settings.base_url,
|
|
180
|
+
issuer_url=settings.issuer_url,
|
|
181
|
+
redirect_path=settings.redirect_path,
|
|
182
|
+
required_scopes=auth0_required_scopes,
|
|
183
|
+
allowed_client_redirect_uris=settings.allowed_client_redirect_uris,
|
|
184
|
+
client_storage=client_storage,
|
|
185
|
+
jwt_signing_key=settings.jwt_signing_key,
|
|
186
|
+
require_authorization_consent=require_authorization_consent,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
logger.debug(
|
|
177
190
|
"Initialized Auth0 OAuth provider for client %s with scopes: %s",
|
|
178
191
|
settings.client_id,
|
|
179
192
|
auth0_required_scopes,
|
|
@@ -57,6 +57,7 @@ class AWSCognitoProviderSettings(BaseSettings):
|
|
|
57
57
|
redirect_path: str | None = None
|
|
58
58
|
required_scopes: list[str] | None = None
|
|
59
59
|
allowed_client_redirect_uris: list[str] | None = None
|
|
60
|
+
jwt_signing_key: str | None = None
|
|
60
61
|
|
|
61
62
|
@field_validator("required_scopes", mode="before")
|
|
62
63
|
@classmethod
|
|
@@ -135,6 +136,8 @@ class AWSCognitoProvider(OIDCProxy):
|
|
|
135
136
|
required_scopes: list[str] | NotSetT = NotSet,
|
|
136
137
|
allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
|
|
137
138
|
client_storage: AsyncKeyValue | None = None,
|
|
139
|
+
jwt_signing_key: str | bytes | NotSetT = NotSet,
|
|
140
|
+
require_authorization_consent: bool = True,
|
|
138
141
|
):
|
|
139
142
|
"""Initialize AWS Cognito OAuth provider.
|
|
140
143
|
|
|
@@ -150,7 +153,16 @@ class AWSCognitoProvider(OIDCProxy):
|
|
|
150
153
|
required_scopes: Required Cognito scopes (defaults to ["openid"])
|
|
151
154
|
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
|
|
152
155
|
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
|
|
153
|
-
client_storage:
|
|
156
|
+
client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
|
|
157
|
+
If None, a DiskStore will be created in the data directory (derived from `platformdirs`). The
|
|
158
|
+
disk store will be encrypted using a key derived from the JWT Signing Key.
|
|
159
|
+
jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes). If bytes are provided,
|
|
160
|
+
they will be used as is. If a string is provided, it will be derived into a 32-byte key. If not
|
|
161
|
+
provided, the upstream client secret will be used to derive a 32-byte key using PBKDF2.
|
|
162
|
+
require_authorization_consent: Whether to require user consent before authorizing clients (default True).
|
|
163
|
+
When True, users see a consent screen before being redirected to AWS Cognito.
|
|
164
|
+
When False, authorization proceeds directly without user confirmation.
|
|
165
|
+
SECURITY WARNING: Only disable for local development or testing environments.
|
|
154
166
|
"""
|
|
155
167
|
|
|
156
168
|
settings = AWSCognitoProviderSettings.model_validate(
|
|
@@ -166,6 +178,7 @@ class AWSCognitoProvider(OIDCProxy):
|
|
|
166
178
|
"redirect_path": redirect_path,
|
|
167
179
|
"required_scopes": required_scopes,
|
|
168
180
|
"allowed_client_redirect_uris": allowed_client_redirect_uris,
|
|
181
|
+
"jwt_signing_key": jwt_signing_key,
|
|
169
182
|
}.items()
|
|
170
183
|
if v is not NotSet
|
|
171
184
|
}
|
|
@@ -215,9 +228,11 @@ class AWSCognitoProvider(OIDCProxy):
|
|
|
215
228
|
redirect_path=redirect_path_final,
|
|
216
229
|
allowed_client_redirect_uris=allowed_client_redirect_uris_final,
|
|
217
230
|
client_storage=client_storage,
|
|
231
|
+
jwt_signing_key=settings.jwt_signing_key,
|
|
232
|
+
require_authorization_consent=require_authorization_consent,
|
|
218
233
|
)
|
|
219
234
|
|
|
220
|
-
logger.
|
|
235
|
+
logger.debug(
|
|
221
236
|
"Initialized AWS Cognito OAuth provider for client %s with scopes: %s",
|
|
222
237
|
settings.client_id,
|
|
223
238
|
required_scopes_final,
|
|
@@ -45,6 +45,7 @@ class AzureProviderSettings(BaseSettings):
|
|
|
45
45
|
required_scopes: list[str] | None = None
|
|
46
46
|
additional_authorize_scopes: list[str] | None = None
|
|
47
47
|
allowed_client_redirect_uris: list[str] | None = None
|
|
48
|
+
jwt_signing_key: str | None = None
|
|
48
49
|
|
|
49
50
|
@field_validator("required_scopes", mode="before")
|
|
50
51
|
@classmethod
|
|
@@ -64,18 +65,28 @@ class AzureProvider(OAuthProxy):
|
|
|
64
65
|
OAuth Proxy pattern. It supports both organizational accounts and personal
|
|
65
66
|
Microsoft accounts depending on the tenant configuration.
|
|
66
67
|
|
|
68
|
+
Scope Handling:
|
|
69
|
+
- required_scopes: Provide unprefixed scope names (e.g., ["read", "write"])
|
|
70
|
+
→ Automatically prefixed with identifier_uri during initialization
|
|
71
|
+
→ Validated on all tokens and advertised to MCP clients
|
|
72
|
+
- additional_authorize_scopes: Provide full format (e.g., ["User.Read"])
|
|
73
|
+
→ NOT prefixed, NOT validated, NOT advertised to clients
|
|
74
|
+
→ Used to request Microsoft Graph or other upstream API permissions
|
|
75
|
+
|
|
67
76
|
Features:
|
|
68
77
|
- OAuth proxy to Azure/Microsoft identity platform
|
|
69
78
|
- JWT validation using tenant issuer and JWKS
|
|
70
79
|
- Supports tenant configurations: specific tenant ID, "organizations", or "consumers"
|
|
80
|
+
- Custom API scopes and Microsoft Graph scopes in a single provider
|
|
71
81
|
|
|
72
82
|
Setup:
|
|
73
83
|
1. Create an App registration in Azure Portal
|
|
74
84
|
2. Configure Web platform redirect URI: http://localhost:8000/auth/callback (or your custom path)
|
|
75
|
-
3. Add an Application ID URI
|
|
76
|
-
4. Add
|
|
77
|
-
5.
|
|
78
|
-
6.
|
|
85
|
+
3. Add an Application ID URI under "Expose an API" (defaults to api://{client_id})
|
|
86
|
+
4. Add custom scopes (e.g., "read", "write") under "Expose an API"
|
|
87
|
+
5. Set access token version to 2 in the App manifest: "requestedAccessTokenVersion": 2
|
|
88
|
+
6. Create a client secret
|
|
89
|
+
7. Get Application (client) ID, Directory (tenant) ID, and client secret
|
|
79
90
|
|
|
80
91
|
Example:
|
|
81
92
|
```python
|
|
@@ -86,7 +97,8 @@ class AzureProvider(OAuthProxy):
|
|
|
86
97
|
client_id="your-client-id",
|
|
87
98
|
client_secret="your-client-secret",
|
|
88
99
|
tenant_id="your-tenant-id",
|
|
89
|
-
required_scopes=["
|
|
100
|
+
required_scopes=["read", "write"], # Unprefixed scope names
|
|
101
|
+
additional_authorize_scopes=["User.Read", "Mail.Read"], # Optional Graph scopes
|
|
90
102
|
base_url="http://localhost:8000",
|
|
91
103
|
# identifier_uri defaults to api://{client_id}
|
|
92
104
|
)
|
|
@@ -109,28 +121,49 @@ class AzureProvider(OAuthProxy):
|
|
|
109
121
|
additional_authorize_scopes: list[str] | None | NotSetT = NotSet,
|
|
110
122
|
allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
|
|
111
123
|
client_storage: AsyncKeyValue | None = None,
|
|
124
|
+
jwt_signing_key: str | bytes | NotSetT = NotSet,
|
|
125
|
+
require_authorization_consent: bool = True,
|
|
112
126
|
) -> None:
|
|
113
127
|
"""Initialize Azure OAuth provider.
|
|
114
128
|
|
|
115
129
|
Args:
|
|
116
|
-
client_id: Azure application (client) ID
|
|
117
|
-
client_secret: Azure client secret
|
|
118
|
-
tenant_id: Azure tenant ID (
|
|
119
|
-
identifier_uri: Optional Application ID URI for your API
|
|
120
|
-
|
|
121
|
-
|
|
130
|
+
client_id: Azure application (client) ID from your App registration
|
|
131
|
+
client_secret: Azure client secret from your App registration
|
|
132
|
+
tenant_id: Azure tenant ID (specific tenant GUID, "organizations", or "consumers")
|
|
133
|
+
identifier_uri: Optional Application ID URI for your custom API (defaults to api://{client_id}).
|
|
134
|
+
This URI is automatically prefixed to all required_scopes during initialization.
|
|
135
|
+
Example: identifier_uri="api://my-api" + required_scopes=["read"]
|
|
136
|
+
→ tokens validated for "api://my-api/read"
|
|
122
137
|
base_url: Public URL where OAuth endpoints will be accessible (includes any mount path)
|
|
123
138
|
issuer_url: Issuer URL for OAuth metadata (defaults to base_url). Use root-level URL
|
|
124
139
|
to avoid 404s during discovery when mounting under a path.
|
|
125
|
-
redirect_path: Redirect path configured in Azure (defaults to "/auth/callback")
|
|
126
|
-
required_scopes:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
140
|
+
redirect_path: Redirect path configured in Azure App registration (defaults to "/auth/callback")
|
|
141
|
+
required_scopes: Custom API scope names WITHOUT prefix (e.g., ["read", "write"]).
|
|
142
|
+
- Automatically prefixed with identifier_uri during initialization
|
|
143
|
+
- Validated on all tokens
|
|
144
|
+
- Advertised in Protected Resource Metadata
|
|
145
|
+
- Must match scope names defined in Azure Portal under "Expose an API"
|
|
146
|
+
Example: ["read", "write"] → validates tokens containing ["api://xxx/read", "api://xxx/write"]
|
|
147
|
+
additional_authorize_scopes: Microsoft Graph or other upstream scopes in full format.
|
|
148
|
+
- NOT prefixed with identifier_uri
|
|
149
|
+
- NOT validated on tokens
|
|
150
|
+
- NOT advertised to MCP clients
|
|
151
|
+
- Used to request additional permissions from Azure (e.g., Graph API access)
|
|
152
|
+
Example: ["User.Read", "Mail.Read", "offline_access"]
|
|
153
|
+
These scopes allow your FastMCP server to call Microsoft Graph APIs using the
|
|
154
|
+
upstream Azure token, but MCP clients are unaware of them.
|
|
131
155
|
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
|
|
132
156
|
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
|
|
133
|
-
client_storage:
|
|
157
|
+
client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
|
|
158
|
+
If None, a DiskStore will be created in the data directory (derived from `platformdirs`). The
|
|
159
|
+
disk store will be encrypted using a key derived from the JWT Signing Key.
|
|
160
|
+
jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes). If bytes are provided,
|
|
161
|
+
they will be used as is. If a string is provided, it will be derived into a 32-byte key. If not
|
|
162
|
+
provided, the upstream client secret will be used to derive a 32-byte key using PBKDF2.
|
|
163
|
+
require_authorization_consent: Whether to require user consent before authorizing clients (default True).
|
|
164
|
+
When True, users see a consent screen before being redirected to Azure.
|
|
165
|
+
When False, authorization proceeds directly without user confirmation.
|
|
166
|
+
SECURITY WARNING: Only disable for local development or testing environments.
|
|
134
167
|
"""
|
|
135
168
|
settings = AzureProviderSettings.model_validate(
|
|
136
169
|
{
|
|
@@ -146,6 +179,7 @@ class AzureProvider(OAuthProxy):
|
|
|
146
179
|
"required_scopes": required_scopes,
|
|
147
180
|
"additional_authorize_scopes": additional_authorize_scopes,
|
|
148
181
|
"allowed_client_redirect_uris": allowed_client_redirect_uris,
|
|
182
|
+
"jwt_signing_key": jwt_signing_key,
|
|
149
183
|
}.items()
|
|
150
184
|
if v is not NotSet
|
|
151
185
|
}
|
|
@@ -176,6 +210,12 @@ class AzureProvider(OAuthProxy):
|
|
|
176
210
|
self.additional_authorize_scopes = settings.additional_authorize_scopes or []
|
|
177
211
|
tenant_id_final = settings.tenant_id
|
|
178
212
|
|
|
213
|
+
# Prefix required scopes with identifier_uri for Azure
|
|
214
|
+
# Azure returns scopes as full URIs (e.g., "api://xxx/read") in tokens
|
|
215
|
+
prefixed_required_scopes = [
|
|
216
|
+
f"{self.identifier_uri}/{scope}" for scope in settings.required_scopes
|
|
217
|
+
]
|
|
218
|
+
|
|
179
219
|
# Always validate tokens against the app's API client ID using JWT
|
|
180
220
|
issuer = f"https://login.microsoftonline.com/{tenant_id_final}/v2.0"
|
|
181
221
|
jwks_uri = (
|
|
@@ -187,7 +227,7 @@ class AzureProvider(OAuthProxy):
|
|
|
187
227
|
issuer=issuer,
|
|
188
228
|
audience=settings.client_id,
|
|
189
229
|
algorithm="RS256",
|
|
190
|
-
required_scopes=
|
|
230
|
+
required_scopes=prefixed_required_scopes,
|
|
191
231
|
)
|
|
192
232
|
|
|
193
233
|
# Extract secret string from SecretStr
|
|
@@ -216,6 +256,8 @@ class AzureProvider(OAuthProxy):
|
|
|
216
256
|
or settings.base_url, # Default to base_url if not specified
|
|
217
257
|
allowed_client_redirect_uris=settings.allowed_client_redirect_uris,
|
|
218
258
|
client_storage=client_storage,
|
|
259
|
+
jwt_signing_key=settings.jwt_signing_key,
|
|
260
|
+
require_authorization_consent=require_authorization_consent,
|
|
219
261
|
)
|
|
220
262
|
|
|
221
263
|
logger.info(
|
|
@@ -256,14 +298,14 @@ class AzureProvider(OAuthProxy):
|
|
|
256
298
|
"Filtering out 'resource' parameter '%s' for Azure AD v2.0 (use scopes instead)",
|
|
257
299
|
original_resource,
|
|
258
300
|
)
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
|
|
301
|
+
# Scopes are already prefixed:
|
|
302
|
+
# - self.required_scopes was prefixed during __init__
|
|
303
|
+
# - Client scopes come from PRM which advertises prefixed scopes
|
|
304
|
+
scopes = params_to_use.scopes or self.required_scopes
|
|
305
|
+
|
|
306
|
+
final_scopes = list(scopes)
|
|
307
|
+
# Add Microsoft Graph scopes separately - these use shorthand format (e.g., "User.Read")
|
|
308
|
+
# and should not be prefixed with identifier_uri. Azure returns them as-is in tokens.
|
|
267
309
|
if self.additional_authorize_scopes:
|
|
268
310
|
final_scopes.extend(self.additional_authorize_scopes)
|
|
269
311
|
|
|
@@ -272,7 +314,3 @@ class AzureProvider(OAuthProxy):
|
|
|
272
314
|
auth_url = await super().authorize(client, modified_params)
|
|
273
315
|
separator = "&" if "?" in auth_url else "?"
|
|
274
316
|
return f"{auth_url}{separator}prompt=select_account"
|
|
275
|
-
|
|
276
|
-
def _add_prefix_to_scopes(self, scopes: list[str]) -> list[str]:
|
|
277
|
-
"""Add Application ID URI prefix for authorization request."""
|
|
278
|
-
return [f"{self.identifier_uri}/{scope}" for scope in scopes]
|
|
@@ -54,6 +54,7 @@ class GitHubProviderSettings(BaseSettings):
|
|
|
54
54
|
required_scopes: list[str] | None = None
|
|
55
55
|
timeout_seconds: int | None = None
|
|
56
56
|
allowed_client_redirect_uris: list[str] | None = None
|
|
57
|
+
jwt_signing_key: str | None = None
|
|
57
58
|
|
|
58
59
|
@field_validator("required_scopes", mode="before")
|
|
59
60
|
@classmethod
|
|
@@ -206,6 +207,8 @@ class GitHubProvider(OAuthProxy):
|
|
|
206
207
|
timeout_seconds: int | NotSetT = NotSet,
|
|
207
208
|
allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
|
|
208
209
|
client_storage: AsyncKeyValue | None = None,
|
|
210
|
+
jwt_signing_key: str | bytes | NotSetT = NotSet,
|
|
211
|
+
require_authorization_consent: bool = True,
|
|
209
212
|
):
|
|
210
213
|
"""Initialize GitHub OAuth provider.
|
|
211
214
|
|
|
@@ -220,7 +223,16 @@ class GitHubProvider(OAuthProxy):
|
|
|
220
223
|
timeout_seconds: HTTP request timeout for GitHub API calls
|
|
221
224
|
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
|
|
222
225
|
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
|
|
223
|
-
client_storage:
|
|
226
|
+
client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
|
|
227
|
+
If None, a DiskStore will be created in the data directory (derived from `platformdirs`). The
|
|
228
|
+
disk store will be encrypted using a key derived from the JWT Signing Key.
|
|
229
|
+
jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes). If bytes are provided,
|
|
230
|
+
they will be used as is. If a string is provided, it will be derived into a 32-byte key. If not
|
|
231
|
+
provided, the upstream client secret will be used to derive a 32-byte key using PBKDF2.
|
|
232
|
+
require_authorization_consent: Whether to require user consent before authorizing clients (default True).
|
|
233
|
+
When True, users see a consent screen before being redirected to GitHub.
|
|
234
|
+
When False, authorization proceeds directly without user confirmation.
|
|
235
|
+
SECURITY WARNING: Only disable for local development or testing environments.
|
|
224
236
|
"""
|
|
225
237
|
|
|
226
238
|
settings = GitHubProviderSettings.model_validate(
|
|
@@ -235,6 +247,7 @@ class GitHubProvider(OAuthProxy):
|
|
|
235
247
|
"required_scopes": required_scopes,
|
|
236
248
|
"timeout_seconds": timeout_seconds,
|
|
237
249
|
"allowed_client_redirect_uris": allowed_client_redirect_uris,
|
|
250
|
+
"jwt_signing_key": jwt_signing_key,
|
|
238
251
|
}.items()
|
|
239
252
|
if v is not NotSet
|
|
240
253
|
}
|
|
@@ -280,9 +293,11 @@ class GitHubProvider(OAuthProxy):
|
|
|
280
293
|
or settings.base_url, # Default to base_url if not specified
|
|
281
294
|
allowed_client_redirect_uris=allowed_client_redirect_uris_final,
|
|
282
295
|
client_storage=client_storage,
|
|
296
|
+
jwt_signing_key=settings.jwt_signing_key,
|
|
297
|
+
require_authorization_consent=require_authorization_consent,
|
|
283
298
|
)
|
|
284
299
|
|
|
285
|
-
logger.
|
|
300
|
+
logger.debug(
|
|
286
301
|
"Initialized GitHub OAuth provider for client %s with scopes: %s",
|
|
287
302
|
settings.client_id,
|
|
288
303
|
required_scopes_final,
|
|
@@ -56,6 +56,7 @@ class GoogleProviderSettings(BaseSettings):
|
|
|
56
56
|
required_scopes: list[str] | None = None
|
|
57
57
|
timeout_seconds: int | None = None
|
|
58
58
|
allowed_client_redirect_uris: list[str] | None = None
|
|
59
|
+
jwt_signing_key: str | None = None
|
|
59
60
|
|
|
60
61
|
@field_validator("required_scopes", mode="before")
|
|
61
62
|
@classmethod
|
|
@@ -79,7 +80,7 @@ class GoogleTokenVerifier(TokenVerifier):
|
|
|
79
80
|
"""Initialize the Google token verifier.
|
|
80
81
|
|
|
81
82
|
Args:
|
|
82
|
-
required_scopes: Required OAuth scopes (e.g., ['openid', 'email'])
|
|
83
|
+
required_scopes: Required OAuth scopes (e.g., ['openid', 'https://www.googleapis.com/auth/userinfo.email'])
|
|
83
84
|
timeout_seconds: HTTP request timeout
|
|
84
85
|
"""
|
|
85
86
|
super().__init__(required_scopes=required_scopes)
|
|
@@ -222,6 +223,8 @@ class GoogleProvider(OAuthProxy):
|
|
|
222
223
|
timeout_seconds: int | NotSetT = NotSet,
|
|
223
224
|
allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
|
|
224
225
|
client_storage: AsyncKeyValue | None = None,
|
|
226
|
+
jwt_signing_key: str | bytes | NotSetT = NotSet,
|
|
227
|
+
require_authorization_consent: bool = True,
|
|
225
228
|
):
|
|
226
229
|
"""Initialize Google OAuth provider.
|
|
227
230
|
|
|
@@ -239,7 +242,16 @@ class GoogleProvider(OAuthProxy):
|
|
|
239
242
|
timeout_seconds: HTTP request timeout for Google API calls
|
|
240
243
|
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
|
|
241
244
|
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
|
|
242
|
-
client_storage:
|
|
245
|
+
client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
|
|
246
|
+
If None, a DiskStore will be created in the data directory (derived from `platformdirs`). The
|
|
247
|
+
disk store will be encrypted using a key derived from the JWT Signing Key.
|
|
248
|
+
jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes). If bytes are provided,
|
|
249
|
+
they will be used as is. If a string is provided, it will be derived into a 32-byte key. If not
|
|
250
|
+
provided, the upstream client secret will be used to derive a 32-byte key using PBKDF2.
|
|
251
|
+
require_authorization_consent: Whether to require user consent before authorizing clients (default True).
|
|
252
|
+
When True, users see a consent screen before being redirected to Google.
|
|
253
|
+
When False, authorization proceeds directly without user confirmation.
|
|
254
|
+
SECURITY WARNING: Only disable for local development or testing environments.
|
|
243
255
|
"""
|
|
244
256
|
|
|
245
257
|
settings = GoogleProviderSettings.model_validate(
|
|
@@ -254,6 +266,7 @@ class GoogleProvider(OAuthProxy):
|
|
|
254
266
|
"required_scopes": required_scopes,
|
|
255
267
|
"timeout_seconds": timeout_seconds,
|
|
256
268
|
"allowed_client_redirect_uris": allowed_client_redirect_uris,
|
|
269
|
+
"jwt_signing_key": jwt_signing_key,
|
|
257
270
|
}.items()
|
|
258
271
|
if v is not NotSet
|
|
259
272
|
}
|
|
@@ -299,9 +312,11 @@ class GoogleProvider(OAuthProxy):
|
|
|
299
312
|
or settings.base_url, # Default to base_url if not specified
|
|
300
313
|
allowed_client_redirect_uris=allowed_client_redirect_uris_final,
|
|
301
314
|
client_storage=client_storage,
|
|
315
|
+
jwt_signing_key=settings.jwt_signing_key,
|
|
316
|
+
require_authorization_consent=require_authorization_consent,
|
|
302
317
|
)
|
|
303
318
|
|
|
304
|
-
logger.
|
|
319
|
+
logger.debug(
|
|
305
320
|
"Initialized Google OAuth provider for client %s with scopes: %s",
|
|
306
321
|
settings.client_id,
|
|
307
322
|
required_scopes_final,
|
|
@@ -46,6 +46,7 @@ class WorkOSProviderSettings(BaseSettings):
|
|
|
46
46
|
required_scopes: list[str] | None = None
|
|
47
47
|
timeout_seconds: int | None = None
|
|
48
48
|
allowed_client_redirect_uris: list[str] | None = None
|
|
49
|
+
jwt_signing_key: str | None = None
|
|
49
50
|
|
|
50
51
|
@field_validator("required_scopes", mode="before")
|
|
51
52
|
@classmethod
|
|
@@ -172,6 +173,8 @@ class WorkOSProvider(OAuthProxy):
|
|
|
172
173
|
timeout_seconds: int | NotSetT = NotSet,
|
|
173
174
|
allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
|
|
174
175
|
client_storage: AsyncKeyValue | None = None,
|
|
176
|
+
jwt_signing_key: str | bytes | NotSetT = NotSet,
|
|
177
|
+
require_authorization_consent: bool = True,
|
|
175
178
|
):
|
|
176
179
|
"""Initialize WorkOS OAuth provider.
|
|
177
180
|
|
|
@@ -187,7 +190,16 @@ class WorkOSProvider(OAuthProxy):
|
|
|
187
190
|
timeout_seconds: HTTP request timeout for WorkOS API calls
|
|
188
191
|
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
|
|
189
192
|
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
|
|
190
|
-
client_storage:
|
|
193
|
+
client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
|
|
194
|
+
If None, a DiskStore will be created in the data directory (derived from `platformdirs`). The
|
|
195
|
+
disk store will be encrypted using a key derived from the JWT Signing Key.
|
|
196
|
+
jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes). If bytes are provided,
|
|
197
|
+
they will be used as is. If a string is provided, it will be derived into a 32-byte key. If not
|
|
198
|
+
provided, the upstream client secret will be used to derive a 32-byte key using PBKDF2.
|
|
199
|
+
require_authorization_consent: Whether to require user consent before authorizing clients (default True).
|
|
200
|
+
When True, users see a consent screen before being redirected to WorkOS.
|
|
201
|
+
When False, authorization proceeds directly without user confirmation.
|
|
202
|
+
SECURITY WARNING: Only disable for local development or testing environments.
|
|
191
203
|
"""
|
|
192
204
|
|
|
193
205
|
settings = WorkOSProviderSettings.model_validate(
|
|
@@ -203,6 +215,7 @@ class WorkOSProvider(OAuthProxy):
|
|
|
203
215
|
"required_scopes": required_scopes,
|
|
204
216
|
"timeout_seconds": timeout_seconds,
|
|
205
217
|
"allowed_client_redirect_uris": allowed_client_redirect_uris,
|
|
218
|
+
"jwt_signing_key": jwt_signing_key,
|
|
206
219
|
}.items()
|
|
207
220
|
if v is not NotSet
|
|
208
221
|
}
|
|
@@ -256,9 +269,11 @@ class WorkOSProvider(OAuthProxy):
|
|
|
256
269
|
or settings.base_url, # Default to base_url if not specified
|
|
257
270
|
allowed_client_redirect_uris=allowed_client_redirect_uris_final,
|
|
258
271
|
client_storage=client_storage,
|
|
272
|
+
jwt_signing_key=settings.jwt_signing_key,
|
|
273
|
+
require_authorization_consent=require_authorization_consent,
|
|
259
274
|
)
|
|
260
275
|
|
|
261
|
-
logger.
|
|
276
|
+
logger.debug(
|
|
262
277
|
"Initialized WorkOS OAuth provider for client %s with AuthKit domain %s",
|
|
263
278
|
settings.client_id,
|
|
264
279
|
authkit_domain_final,
|
fastmcp/server/context.py
CHANGED
|
@@ -22,6 +22,7 @@ from mcp.types import (
|
|
|
22
22
|
AudioContent,
|
|
23
23
|
ClientCapabilities,
|
|
24
24
|
CreateMessageResult,
|
|
25
|
+
GetPromptResult,
|
|
25
26
|
ImageContent,
|
|
26
27
|
IncludeContext,
|
|
27
28
|
ModelHint,
|
|
@@ -32,6 +33,8 @@ from mcp.types import (
|
|
|
32
33
|
TextContent,
|
|
33
34
|
)
|
|
34
35
|
from mcp.types import CreateMessageRequestParams as SamplingParams
|
|
36
|
+
from mcp.types import Prompt as MCPPrompt
|
|
37
|
+
from mcp.types import Resource as MCPResource
|
|
35
38
|
from pydantic.networks import AnyUrl
|
|
36
39
|
from starlette.requests import Request
|
|
37
40
|
from typing_extensions import TypeVar
|
|
@@ -215,6 +218,36 @@ class Context:
|
|
|
215
218
|
related_request_id=self.request_id,
|
|
216
219
|
)
|
|
217
220
|
|
|
221
|
+
async def list_resources(self) -> list[MCPResource]:
|
|
222
|
+
"""List all available resources from the server.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
List of Resource objects available on the server
|
|
226
|
+
"""
|
|
227
|
+
return await self.fastmcp._list_resources_mcp()
|
|
228
|
+
|
|
229
|
+
async def list_prompts(self) -> list[MCPPrompt]:
|
|
230
|
+
"""List all available prompts from the server.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
List of Prompt objects available on the server
|
|
234
|
+
"""
|
|
235
|
+
return await self.fastmcp._list_prompts_mcp()
|
|
236
|
+
|
|
237
|
+
async def get_prompt(
|
|
238
|
+
self, name: str, arguments: dict[str, Any] | None = None
|
|
239
|
+
) -> GetPromptResult:
|
|
240
|
+
"""Get a prompt by name with optional arguments.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
name: The name of the prompt to get
|
|
244
|
+
arguments: Optional arguments to pass to the prompt
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
The prompt result
|
|
248
|
+
"""
|
|
249
|
+
return await self.fastmcp._get_prompt_mcp(name, arguments)
|
|
250
|
+
|
|
218
251
|
async def read_resource(self, uri: str | AnyUrl) -> list[ReadResourceContents]:
|
|
219
252
|
"""Read a resource by URI.
|
|
220
253
|
|
|
@@ -224,8 +257,6 @@ class Context:
|
|
|
224
257
|
Returns:
|
|
225
258
|
The resource content as either text or bytes
|
|
226
259
|
"""
|
|
227
|
-
if self.fastmcp is None:
|
|
228
|
-
raise ValueError("Context is not available outside of a request")
|
|
229
260
|
return await self.fastmcp._read_resource_mcp(uri)
|
|
230
261
|
|
|
231
262
|
async def log(
|
fastmcp/server/http.py
CHANGED
|
@@ -5,11 +5,12 @@ from contextlib import asynccontextmanager, contextmanager
|
|
|
5
5
|
from contextvars import ContextVar
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
-
from mcp.server.auth.middleware.bearer_auth import RequireAuthMiddleware
|
|
9
8
|
from mcp.server.auth.routes import build_resource_metadata_url
|
|
10
9
|
from mcp.server.lowlevel.server import LifespanResultT
|
|
11
10
|
from mcp.server.sse import SseServerTransport
|
|
12
|
-
from mcp.server.streamable_http import
|
|
11
|
+
from mcp.server.streamable_http import (
|
|
12
|
+
EventStore,
|
|
13
|
+
)
|
|
13
14
|
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
|
|
14
15
|
from starlette.applications import Starlette
|
|
15
16
|
from starlette.middleware import Middleware
|
|
@@ -19,6 +20,7 @@ from starlette.routing import BaseRoute, Mount, Route
|
|
|
19
20
|
from starlette.types import Lifespan, Receive, Scope, Send
|
|
20
21
|
|
|
21
22
|
from fastmcp.server.auth import AuthProvider
|
|
23
|
+
from fastmcp.server.auth.middleware import RequireAuthMiddleware
|
|
22
24
|
from fastmcp.utilities.logging import get_logger
|
|
23
25
|
|
|
24
26
|
if TYPE_CHECKING:
|
|
@@ -179,7 +181,7 @@ def create_sse_app(
|
|
|
179
181
|
build_resource_metadata_url(resource_url) if resource_url else None
|
|
180
182
|
)
|
|
181
183
|
|
|
182
|
-
# Create protected SSE endpoint route
|
|
184
|
+
# Create protected SSE endpoint route
|
|
183
185
|
server_routes.append(
|
|
184
186
|
Route(
|
|
185
187
|
sse_path,
|
|
@@ -316,6 +318,7 @@ def create_streamable_http_app(
|
|
|
316
318
|
auth.required_scopes,
|
|
317
319
|
resource_metadata_url,
|
|
318
320
|
),
|
|
321
|
+
methods=["GET", "POST", "DELETE"],
|
|
319
322
|
)
|
|
320
323
|
)
|
|
321
324
|
else:
|