fastmcp 2.13.0rc2__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.
@@ -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: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided
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: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided
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,20 +171,20 @@ class Auth0Provider(OIDCProxy):
158
171
 
159
172
  auth0_required_scopes = settings.required_scopes or ["openid"]
160
173
 
161
- init_kwargs = {
162
- "config_url": settings.config_url,
163
- "client_id": settings.client_id,
164
- "client_secret": settings.client_secret.get_secret_value(),
165
- "audience": settings.audience,
166
- "base_url": settings.base_url,
167
- "issuer_url": settings.issuer_url,
168
- "redirect_path": settings.redirect_path,
169
- "required_scopes": auth0_required_scopes,
170
- "allowed_client_redirect_uris": settings.allowed_client_redirect_uris,
171
- "client_storage": client_storage,
172
- }
173
-
174
- super().__init__(**init_kwargs)
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
+ )
175
188
 
176
189
  logger.debug(
177
190
  "Initialized Auth0 OAuth provider for client %s with scopes: %s",
@@ -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: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided
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,6 +228,8 @@ 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
235
  logger.debug(
@@ -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. Either use the default (api://{client_id}) or set a custom one.
76
- 4. Add a custom scope.
77
- 5. Create a client secret.
78
- 6. Get Application (client) ID, Directory (tenant) ID, and client secret
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=["your-scope"],
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 (your specific tenant ID, "organizations", or "consumers")
119
- identifier_uri: Optional Application ID URI for your API. (defaults to api://{client_id})
120
- Used only to prefix scopes in authorization requests. Tokens are always validated
121
- against your app's client ID.
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: Required scopes. These are validated on tokens and used as defaults
127
- when the client does not request specific scopes.
128
- additional_authorize_scopes: Additional scopes to include in the authorization request
129
- without prefixing. Use this to request upstream scopes such as Microsoft Graph
130
- permissions. These are not used for token validation.
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: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided
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=settings.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
- original_scopes = params_to_use.scopes or self.required_scopes
260
- prefixed_scopes = (
261
- self._add_prefix_to_scopes(original_scopes)
262
- if self.identifier_uri
263
- else original_scopes
264
- )
265
-
266
- final_scopes = list(prefixed_scopes)
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: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided
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,6 +293,8 @@ 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
300
  logger.debug(
@@ -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
@@ -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: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided
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,6 +312,8 @@ 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
319
  logger.debug(
@@ -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: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided
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,6 +269,8 @@ 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
276
  logger.debug(
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,7 +5,6 @@ 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
@@ -21,6 +20,7 @@ from starlette.routing import BaseRoute, Mount, Route
21
20
  from starlette.types import Lifespan, Receive, Scope, Send
22
21
 
23
22
  from fastmcp.server.auth import AuthProvider
23
+ from fastmcp.server.auth.middleware import RequireAuthMiddleware
24
24
  from fastmcp.utilities.logging import get_logger
25
25
 
26
26
  if TYPE_CHECKING: