fastmcp 2.14.4__py3-none-any.whl → 3.0.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. fastmcp/_vendor/__init__.py +1 -0
  2. fastmcp/_vendor/docket_di/README.md +7 -0
  3. fastmcp/_vendor/docket_di/__init__.py +163 -0
  4. fastmcp/cli/cli.py +112 -28
  5. fastmcp/cli/install/claude_code.py +1 -5
  6. fastmcp/cli/install/claude_desktop.py +1 -5
  7. fastmcp/cli/install/cursor.py +1 -5
  8. fastmcp/cli/install/gemini_cli.py +1 -5
  9. fastmcp/cli/install/mcp_json.py +1 -6
  10. fastmcp/cli/run.py +146 -5
  11. fastmcp/client/__init__.py +7 -9
  12. fastmcp/client/auth/oauth.py +18 -17
  13. fastmcp/client/client.py +100 -870
  14. fastmcp/client/elicitation.py +1 -1
  15. fastmcp/client/mixins/__init__.py +13 -0
  16. fastmcp/client/mixins/prompts.py +295 -0
  17. fastmcp/client/mixins/resources.py +325 -0
  18. fastmcp/client/mixins/task_management.py +157 -0
  19. fastmcp/client/mixins/tools.py +397 -0
  20. fastmcp/client/sampling/handlers/anthropic.py +2 -2
  21. fastmcp/client/sampling/handlers/openai.py +1 -1
  22. fastmcp/client/tasks.py +3 -3
  23. fastmcp/client/telemetry.py +47 -0
  24. fastmcp/client/transports/__init__.py +38 -0
  25. fastmcp/client/transports/base.py +82 -0
  26. fastmcp/client/transports/config.py +170 -0
  27. fastmcp/client/transports/http.py +145 -0
  28. fastmcp/client/transports/inference.py +154 -0
  29. fastmcp/client/transports/memory.py +90 -0
  30. fastmcp/client/transports/sse.py +89 -0
  31. fastmcp/client/transports/stdio.py +543 -0
  32. fastmcp/contrib/component_manager/README.md +4 -10
  33. fastmcp/contrib/component_manager/__init__.py +1 -2
  34. fastmcp/contrib/component_manager/component_manager.py +95 -160
  35. fastmcp/contrib/component_manager/example.py +1 -1
  36. fastmcp/contrib/mcp_mixin/example.py +4 -4
  37. fastmcp/contrib/mcp_mixin/mcp_mixin.py +11 -4
  38. fastmcp/decorators.py +41 -0
  39. fastmcp/dependencies.py +12 -1
  40. fastmcp/exceptions.py +4 -0
  41. fastmcp/experimental/server/openapi/__init__.py +18 -15
  42. fastmcp/mcp_config.py +13 -4
  43. fastmcp/prompts/__init__.py +6 -3
  44. fastmcp/prompts/function_prompt.py +465 -0
  45. fastmcp/prompts/prompt.py +321 -271
  46. fastmcp/resources/__init__.py +5 -3
  47. fastmcp/resources/function_resource.py +335 -0
  48. fastmcp/resources/resource.py +325 -115
  49. fastmcp/resources/template.py +215 -43
  50. fastmcp/resources/types.py +27 -12
  51. fastmcp/server/__init__.py +2 -2
  52. fastmcp/server/auth/__init__.py +14 -0
  53. fastmcp/server/auth/auth.py +30 -10
  54. fastmcp/server/auth/authorization.py +190 -0
  55. fastmcp/server/auth/oauth_proxy/__init__.py +14 -0
  56. fastmcp/server/auth/oauth_proxy/consent.py +361 -0
  57. fastmcp/server/auth/oauth_proxy/models.py +178 -0
  58. fastmcp/server/auth/{oauth_proxy.py → oauth_proxy/proxy.py} +24 -778
  59. fastmcp/server/auth/oauth_proxy/ui.py +277 -0
  60. fastmcp/server/auth/oidc_proxy.py +2 -2
  61. fastmcp/server/auth/providers/auth0.py +24 -94
  62. fastmcp/server/auth/providers/aws.py +26 -95
  63. fastmcp/server/auth/providers/azure.py +41 -129
  64. fastmcp/server/auth/providers/descope.py +18 -49
  65. fastmcp/server/auth/providers/discord.py +25 -86
  66. fastmcp/server/auth/providers/github.py +23 -87
  67. fastmcp/server/auth/providers/google.py +24 -87
  68. fastmcp/server/auth/providers/introspection.py +60 -79
  69. fastmcp/server/auth/providers/jwt.py +30 -67
  70. fastmcp/server/auth/providers/oci.py +47 -110
  71. fastmcp/server/auth/providers/scalekit.py +23 -61
  72. fastmcp/server/auth/providers/supabase.py +18 -47
  73. fastmcp/server/auth/providers/workos.py +34 -127
  74. fastmcp/server/context.py +372 -419
  75. fastmcp/server/dependencies.py +541 -251
  76. fastmcp/server/elicitation.py +20 -18
  77. fastmcp/server/event_store.py +3 -3
  78. fastmcp/server/http.py +16 -6
  79. fastmcp/server/lifespan.py +198 -0
  80. fastmcp/server/low_level.py +92 -2
  81. fastmcp/server/middleware/__init__.py +5 -1
  82. fastmcp/server/middleware/authorization.py +312 -0
  83. fastmcp/server/middleware/caching.py +101 -54
  84. fastmcp/server/middleware/middleware.py +6 -9
  85. fastmcp/server/middleware/ping.py +70 -0
  86. fastmcp/server/middleware/tool_injection.py +2 -2
  87. fastmcp/server/mixins/__init__.py +7 -0
  88. fastmcp/server/mixins/lifespan.py +217 -0
  89. fastmcp/server/mixins/mcp_operations.py +392 -0
  90. fastmcp/server/mixins/transport.py +342 -0
  91. fastmcp/server/openapi/__init__.py +41 -21
  92. fastmcp/server/openapi/components.py +16 -339
  93. fastmcp/server/openapi/routing.py +34 -118
  94. fastmcp/server/openapi/server.py +67 -392
  95. fastmcp/server/providers/__init__.py +71 -0
  96. fastmcp/server/providers/aggregate.py +261 -0
  97. fastmcp/server/providers/base.py +578 -0
  98. fastmcp/server/providers/fastmcp_provider.py +674 -0
  99. fastmcp/server/providers/filesystem.py +226 -0
  100. fastmcp/server/providers/filesystem_discovery.py +327 -0
  101. fastmcp/server/providers/local_provider/__init__.py +11 -0
  102. fastmcp/server/providers/local_provider/decorators/__init__.py +15 -0
  103. fastmcp/server/providers/local_provider/decorators/prompts.py +256 -0
  104. fastmcp/server/providers/local_provider/decorators/resources.py +240 -0
  105. fastmcp/server/providers/local_provider/decorators/tools.py +315 -0
  106. fastmcp/server/providers/local_provider/local_provider.py +465 -0
  107. fastmcp/server/providers/openapi/__init__.py +39 -0
  108. fastmcp/server/providers/openapi/components.py +332 -0
  109. fastmcp/server/providers/openapi/provider.py +405 -0
  110. fastmcp/server/providers/openapi/routing.py +109 -0
  111. fastmcp/server/providers/proxy.py +867 -0
  112. fastmcp/server/providers/skills/__init__.py +59 -0
  113. fastmcp/server/providers/skills/_common.py +101 -0
  114. fastmcp/server/providers/skills/claude_provider.py +44 -0
  115. fastmcp/server/providers/skills/directory_provider.py +153 -0
  116. fastmcp/server/providers/skills/skill_provider.py +432 -0
  117. fastmcp/server/providers/skills/vendor_providers.py +142 -0
  118. fastmcp/server/providers/wrapped_provider.py +140 -0
  119. fastmcp/server/proxy.py +34 -700
  120. fastmcp/server/sampling/run.py +341 -2
  121. fastmcp/server/sampling/sampling_tool.py +4 -3
  122. fastmcp/server/server.py +1214 -2171
  123. fastmcp/server/tasks/__init__.py +2 -1
  124. fastmcp/server/tasks/capabilities.py +13 -1
  125. fastmcp/server/tasks/config.py +66 -3
  126. fastmcp/server/tasks/handlers.py +65 -273
  127. fastmcp/server/tasks/keys.py +4 -6
  128. fastmcp/server/tasks/requests.py +474 -0
  129. fastmcp/server/tasks/routing.py +76 -0
  130. fastmcp/server/tasks/subscriptions.py +20 -11
  131. fastmcp/server/telemetry.py +131 -0
  132. fastmcp/server/transforms/__init__.py +244 -0
  133. fastmcp/server/transforms/namespace.py +193 -0
  134. fastmcp/server/transforms/prompts_as_tools.py +175 -0
  135. fastmcp/server/transforms/resources_as_tools.py +190 -0
  136. fastmcp/server/transforms/tool_transform.py +96 -0
  137. fastmcp/server/transforms/version_filter.py +124 -0
  138. fastmcp/server/transforms/visibility.py +526 -0
  139. fastmcp/settings.py +34 -96
  140. fastmcp/telemetry.py +122 -0
  141. fastmcp/tools/__init__.py +10 -3
  142. fastmcp/tools/function_parsing.py +201 -0
  143. fastmcp/tools/function_tool.py +467 -0
  144. fastmcp/tools/tool.py +215 -362
  145. fastmcp/tools/tool_transform.py +38 -21
  146. fastmcp/utilities/async_utils.py +69 -0
  147. fastmcp/utilities/components.py +152 -91
  148. fastmcp/utilities/inspect.py +8 -20
  149. fastmcp/utilities/json_schema.py +12 -5
  150. fastmcp/utilities/json_schema_type.py +17 -15
  151. fastmcp/utilities/lifespan.py +56 -0
  152. fastmcp/utilities/logging.py +12 -4
  153. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -3
  154. fastmcp/utilities/openapi/parser.py +3 -3
  155. fastmcp/utilities/pagination.py +80 -0
  156. fastmcp/utilities/skills.py +253 -0
  157. fastmcp/utilities/tests.py +0 -16
  158. fastmcp/utilities/timeout.py +47 -0
  159. fastmcp/utilities/types.py +1 -1
  160. fastmcp/utilities/versions.py +285 -0
  161. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/METADATA +8 -5
  162. fastmcp-3.0.0b1.dist-info/RECORD +228 -0
  163. fastmcp/client/transports.py +0 -1170
  164. fastmcp/contrib/component_manager/component_service.py +0 -209
  165. fastmcp/prompts/prompt_manager.py +0 -117
  166. fastmcp/resources/resource_manager.py +0 -338
  167. fastmcp/server/tasks/converters.py +0 -206
  168. fastmcp/server/tasks/protocol.py +0 -359
  169. fastmcp/tools/tool_manager.py +0 -170
  170. fastmcp/utilities/mcp_config.py +0 -56
  171. fastmcp-2.14.4.dist-info/RECORD +0 -161
  172. /fastmcp/server/{openapi → providers/openapi}/README.md +0 -0
  173. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/WHEEL +0 -0
  174. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/entry_points.txt +0 -0
  175. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -9,15 +9,11 @@ from __future__ import annotations
9
9
  from typing import TYPE_CHECKING, Any
10
10
 
11
11
  from key_value.aio.protocols import AsyncKeyValue
12
- from pydantic import SecretStr, field_validator
13
- from pydantic_settings import BaseSettings, SettingsConfigDict
14
12
 
15
13
  from fastmcp.server.auth.oauth_proxy import OAuthProxy
16
14
  from fastmcp.server.auth.providers.jwt import JWTVerifier
17
- from fastmcp.settings import ENV_FILE
18
15
  from fastmcp.utilities.auth import parse_scopes
19
16
  from fastmcp.utilities.logging import get_logger
20
- from fastmcp.utilities.types import NotSet, NotSetT
21
17
 
22
18
  if TYPE_CHECKING:
23
19
  from mcp.server.auth.provider import AuthorizationParams
@@ -31,39 +27,6 @@ logger = get_logger(__name__)
31
27
  OIDC_SCOPES = frozenset({"openid", "profile", "email", "offline_access"})
32
28
 
33
29
 
34
- class AzureProviderSettings(BaseSettings):
35
- """Settings for Azure OAuth provider."""
36
-
37
- model_config = SettingsConfigDict(
38
- env_prefix="FASTMCP_SERVER_AUTH_AZURE_",
39
- env_file=ENV_FILE,
40
- extra="ignore",
41
- )
42
-
43
- client_id: str | None = None
44
- client_secret: SecretStr | None = None
45
- tenant_id: str | None = None
46
- identifier_uri: str | None = None
47
- base_url: str | None = None
48
- issuer_url: str | None = None
49
- redirect_path: str | None = None
50
- required_scopes: list[str] | None = None
51
- additional_authorize_scopes: list[str] | None = None
52
- allowed_client_redirect_uris: list[str] | None = None
53
- jwt_signing_key: str | None = None
54
- base_authority: str = "login.microsoftonline.com"
55
-
56
- @field_validator("required_scopes", mode="before")
57
- @classmethod
58
- def _parse_scopes(cls, v: object) -> list[str] | None:
59
- return parse_scopes(v)
60
-
61
- @field_validator("additional_authorize_scopes", mode="before")
62
- @classmethod
63
- def _parse_additional_authorize_scopes(cls, v: object) -> list[str] | None:
64
- return parse_scopes(v)
65
-
66
-
67
30
  class AzureProvider(OAuthProxy):
68
31
  """Azure (Microsoft Entra) OAuth provider for FastMCP.
69
32
 
@@ -127,20 +90,20 @@ class AzureProvider(OAuthProxy):
127
90
  def __init__(
128
91
  self,
129
92
  *,
130
- client_id: str | NotSetT = NotSet,
131
- client_secret: str | NotSetT = NotSet,
132
- tenant_id: str | NotSetT = NotSet,
133
- identifier_uri: str | NotSetT | None = NotSet,
134
- base_url: str | NotSetT = NotSet,
135
- issuer_url: str | NotSetT = NotSet,
136
- redirect_path: str | NotSetT = NotSet,
137
- required_scopes: list[str] | NotSetT | None = NotSet,
138
- additional_authorize_scopes: list[str] | NotSetT | None = NotSet,
139
- allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
93
+ client_id: str,
94
+ client_secret: str,
95
+ tenant_id: str,
96
+ required_scopes: list[str],
97
+ base_url: str,
98
+ identifier_uri: str | None = None,
99
+ issuer_url: str | None = None,
100
+ redirect_path: str | None = None,
101
+ additional_authorize_scopes: list[str] | None = None,
102
+ allowed_client_redirect_uris: list[str] | None = None,
140
103
  client_storage: AsyncKeyValue | None = None,
141
- jwt_signing_key: str | bytes | NotSetT = NotSet,
104
+ jwt_signing_key: str | bytes | None = None,
142
105
  require_authorization_consent: bool = True,
143
- base_authority: str | NotSetT = NotSet,
106
+ base_authority: str = "login.microsoftonline.com",
144
107
  ) -> None:
145
108
  """Initialize Azure OAuth provider.
146
109
 
@@ -185,126 +148,75 @@ class AzureProvider(OAuthProxy):
185
148
  When False, authorization proceeds directly without user confirmation.
186
149
  SECURITY WARNING: Only disable for local development or testing environments.
187
150
  """
188
- settings = AzureProviderSettings.model_validate(
189
- {
190
- k: v
191
- for k, v in {
192
- "client_id": client_id,
193
- "client_secret": client_secret,
194
- "tenant_id": tenant_id,
195
- "identifier_uri": identifier_uri,
196
- "base_url": base_url,
197
- "issuer_url": issuer_url,
198
- "redirect_path": redirect_path,
199
- "required_scopes": required_scopes,
200
- "additional_authorize_scopes": additional_authorize_scopes,
201
- "allowed_client_redirect_uris": allowed_client_redirect_uris,
202
- "jwt_signing_key": jwt_signing_key,
203
- "base_authority": base_authority,
204
- }.items()
205
- if v is not NotSet
206
- }
151
+ # Parse scopes if provided as string
152
+ parsed_required_scopes = parse_scopes(required_scopes)
153
+ parsed_additional_scopes = (
154
+ parse_scopes(additional_authorize_scopes)
155
+ if additional_authorize_scopes
156
+ else []
207
157
  )
208
158
 
209
- # Validate required settings
210
- if not settings.client_id:
211
- msg = "client_id is required - set via parameter or FASTMCP_SERVER_AUTH_AZURE_CLIENT_ID"
212
- raise ValueError(msg)
213
- if not settings.client_secret:
214
- msg = "client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AZURE_CLIENT_SECRET"
215
- raise ValueError(msg)
216
-
217
- # Validate tenant_id is provided
218
- if not settings.tenant_id:
219
- msg = (
220
- "tenant_id is required - set via parameter or "
221
- "FASTMCP_SERVER_AUTH_AZURE_TENANT_ID. Use your Azure tenant ID "
222
- "(found in Azure Portal), 'organizations', or 'consumers'"
223
- )
224
- raise ValueError(msg)
225
-
226
- # Validate required_scopes has at least one scope
227
- if not settings.required_scopes:
228
- msg = (
229
- "required_scopes must include at least one scope - set via parameter or "
230
- "FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES. Azure's OAuth API requires "
231
- "the 'scope' parameter in authorization requests. Use the unprefixed scope "
232
- "names from your Azure App registration (e.g., ['read', 'write'])"
233
- )
234
- raise ValueError(msg)
235
-
236
159
  # Apply defaults
237
- self.identifier_uri = settings.identifier_uri or f"api://{settings.client_id}"
238
- self.additional_authorize_scopes = settings.additional_authorize_scopes or []
239
- tenant_id_final = settings.tenant_id
160
+ self.identifier_uri = identifier_uri or f"api://{client_id}"
161
+ self.additional_authorize_scopes = parsed_additional_scopes
240
162
 
241
163
  # Always validate tokens against the app's API client ID using JWT
242
- base_authority_final = settings.base_authority
243
- issuer = f"https://{base_authority_final}/{tenant_id_final}/v2.0"
244
- jwks_uri = (
245
- f"https://{base_authority_final}/{tenant_id_final}/discovery/v2.0/keys"
246
- )
164
+ issuer = f"https://{base_authority}/{tenant_id}/v2.0"
165
+ jwks_uri = f"https://{base_authority}/{tenant_id}/discovery/v2.0/keys"
247
166
 
248
167
  # Azure access tokens only include custom API scopes in the `scp` claim,
249
168
  # NOT standard OIDC scopes (openid, profile, email, offline_access).
250
169
  # Filter out OIDC scopes from validation - they'll still be sent to Azure
251
170
  # during authorization (handled by _prefix_scopes_for_azure).
252
- validation_scopes = None
253
- if settings.required_scopes:
171
+ if parsed_required_scopes:
254
172
  validation_scopes = [
255
- s for s in settings.required_scopes if s not in OIDC_SCOPES
173
+ s for s in parsed_required_scopes if s not in OIDC_SCOPES
256
174
  ]
257
175
  # If all scopes were OIDC scopes, use None (no scope validation)
258
176
  if not validation_scopes:
259
177
  validation_scopes = None
178
+ else:
179
+ validation_scopes = None
260
180
 
261
181
  token_verifier = JWTVerifier(
262
182
  jwks_uri=jwks_uri,
263
183
  issuer=issuer,
264
- audience=settings.client_id,
184
+ audience=client_id,
265
185
  algorithm="RS256",
266
186
  required_scopes=validation_scopes, # Only validate non-OIDC scopes
267
187
  )
268
188
 
269
- # Extract secret string from SecretStr
270
- client_secret_str = (
271
- settings.client_secret.get_secret_value() if settings.client_secret else ""
272
- )
273
-
274
189
  # Build Azure OAuth endpoints with tenant
275
190
  authorization_endpoint = (
276
- f"https://{base_authority_final}/{tenant_id_final}/oauth2/v2.0/authorize"
277
- )
278
- token_endpoint = (
279
- f"https://{base_authority_final}/{tenant_id_final}/oauth2/v2.0/token"
191
+ f"https://{base_authority}/{tenant_id}/oauth2/v2.0/authorize"
280
192
  )
193
+ token_endpoint = f"https://{base_authority}/{tenant_id}/oauth2/v2.0/token"
281
194
 
282
195
  # Initialize OAuth proxy with Azure endpoints
283
196
  super().__init__(
284
197
  upstream_authorization_endpoint=authorization_endpoint,
285
198
  upstream_token_endpoint=token_endpoint,
286
- upstream_client_id=settings.client_id,
287
- upstream_client_secret=client_secret_str,
199
+ upstream_client_id=client_id,
200
+ upstream_client_secret=client_secret,
288
201
  token_verifier=token_verifier,
289
- base_url=settings.base_url,
290
- redirect_path=settings.redirect_path,
291
- issuer_url=settings.issuer_url
292
- or settings.base_url, # Default to base_url if not specified
293
- allowed_client_redirect_uris=settings.allowed_client_redirect_uris,
202
+ base_url=base_url,
203
+ redirect_path=redirect_path,
204
+ issuer_url=issuer_url or base_url, # Default to base_url if not specified
205
+ allowed_client_redirect_uris=allowed_client_redirect_uris,
294
206
  client_storage=client_storage,
295
- jwt_signing_key=settings.jwt_signing_key,
207
+ jwt_signing_key=jwt_signing_key,
296
208
  require_authorization_consent=require_authorization_consent,
297
209
  # Advertise full scopes including OIDC (even though we only validate non-OIDC)
298
- valid_scopes=settings.required_scopes,
210
+ valid_scopes=parsed_required_scopes,
299
211
  )
300
212
 
301
213
  authority_info = ""
302
- if base_authority_final != "login.microsoftonline.com":
303
- authority_info = f" using authority {base_authority_final}"
214
+ if base_authority != "login.microsoftonline.com":
215
+ authority_info = f" using authority {base_authority}"
304
216
  logger.info(
305
217
  "Initialized Azure OAuth provider for client %s with tenant %s%s%s",
306
- settings.client_id,
307
- tenant_id_final,
218
+ client_id,
219
+ tenant_id,
308
220
  f" and identifier_uri {self.identifier_uri}" if self.identifier_uri else "",
309
221
  authority_info,
310
222
  )
@@ -10,40 +10,18 @@ from __future__ import annotations
10
10
  from urllib.parse import urlparse
11
11
 
12
12
  import httpx
13
- from pydantic import AnyHttpUrl, field_validator
14
- from pydantic_settings import BaseSettings, SettingsConfigDict
13
+ from pydantic import AnyHttpUrl
15
14
  from starlette.responses import JSONResponse
16
15
  from starlette.routing import Route
17
16
 
18
17
  from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier
19
18
  from fastmcp.server.auth.providers.jwt import JWTVerifier
20
- from fastmcp.settings import ENV_FILE
21
19
  from fastmcp.utilities.auth import parse_scopes
22
20
  from fastmcp.utilities.logging import get_logger
23
- from fastmcp.utilities.types import NotSet, NotSetT
24
21
 
25
22
  logger = get_logger(__name__)
26
23
 
27
24
 
28
- class DescopeProviderSettings(BaseSettings):
29
- model_config = SettingsConfigDict(
30
- env_prefix="FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_",
31
- env_file=ENV_FILE,
32
- extra="ignore",
33
- )
34
-
35
- config_url: AnyHttpUrl | None = None
36
- project_id: str | None = None
37
- descope_base_url: AnyHttpUrl | str | None = None
38
- base_url: AnyHttpUrl
39
- required_scopes: list[str] | None = None
40
-
41
- @field_validator("required_scopes", mode="before")
42
- @classmethod
43
- def _parse_scopes(cls, v):
44
- return parse_scopes(v)
45
-
46
-
47
25
  class DescopeProvider(RemoteAuthProvider):
48
26
  """Descope metadata provider for DCR (Dynamic Client Registration).
49
27
 
@@ -85,46 +63,37 @@ class DescopeProvider(RemoteAuthProvider):
85
63
  def __init__(
86
64
  self,
87
65
  *,
88
- config_url: AnyHttpUrl | str | NotSetT = NotSet,
89
- project_id: str | NotSetT = NotSet,
90
- descope_base_url: AnyHttpUrl | str | NotSetT = NotSet,
91
- base_url: AnyHttpUrl | str | NotSetT = NotSet,
92
- required_scopes: list[str] | NotSetT | None = NotSet,
66
+ base_url: AnyHttpUrl | str,
67
+ config_url: AnyHttpUrl | str | None = None,
68
+ project_id: str | None = None,
69
+ descope_base_url: AnyHttpUrl | str | None = None,
70
+ required_scopes: list[str] | None = None,
93
71
  token_verifier: TokenVerifier | None = None,
94
72
  ):
95
73
  """Initialize Descope metadata provider.
96
74
 
97
75
  Args:
76
+ base_url: Public URL of this FastMCP server
98
77
  config_url: Your Descope Well-Known URL (e.g., "https://.../v1/apps/agentic/P.../M.../.well-known/openid-configuration")
99
78
  This is the new recommended way. If provided, project_id and descope_base_url are ignored.
100
79
  project_id: Your Descope Project ID (e.g., "P2abc123"). Used with descope_base_url for backwards compatibility.
101
80
  descope_base_url: Your Descope base URL (e.g., "https://api.descope.com"). Used with project_id for backwards compatibility.
102
- base_url: Public URL of this FastMCP server
103
81
  required_scopes: Optional list of scopes that must be present in validated tokens.
104
82
  These scopes will be included in the protected resource metadata.
105
83
  token_verifier: Optional token verifier. If None, creates JWT verifier for Descope
106
84
  """
107
- settings = DescopeProviderSettings.model_validate(
108
- {
109
- k: v
110
- for k, v in {
111
- "config_url": config_url,
112
- "project_id": project_id,
113
- "descope_base_url": descope_base_url,
114
- "base_url": base_url,
115
- "required_scopes": required_scopes,
116
- }.items()
117
- if v is not NotSet
118
- }
119
- )
85
+ self.base_url = AnyHttpUrl(str(base_url).rstrip("/"))
120
86
 
121
- self.base_url = AnyHttpUrl(str(settings.base_url).rstrip("/"))
87
+ # Parse scopes if provided as string
88
+ parsed_scopes = (
89
+ parse_scopes(required_scopes) if required_scopes is not None else None
90
+ )
122
91
 
123
92
  # Determine which API is being used
124
- if settings.config_url is not None:
93
+ if config_url is not None:
125
94
  # New API: use config_url
126
95
  # Strip /.well-known/openid-configuration from config_url if present
127
- issuer_url = str(settings.config_url)
96
+ issuer_url = str(config_url)
128
97
  if issuer_url.endswith("/.well-known/openid-configuration"):
129
98
  issuer_url = issuer_url[: -len("/.well-known/openid-configuration")]
130
99
 
@@ -150,10 +119,10 @@ class DescopeProvider(RemoteAuthProvider):
150
119
  self.descope_base_url = f"{parsed_url.scheme}://{parsed_url.netloc}".rstrip(
151
120
  "/"
152
121
  )
153
- elif settings.project_id is not None and settings.descope_base_url is not None:
122
+ elif project_id is not None and descope_base_url is not None:
154
123
  # Old API: use project_id and descope_base_url
155
- self.project_id = settings.project_id
156
- descope_base_url_str = str(settings.descope_base_url).rstrip("/")
124
+ self.project_id = project_id
125
+ descope_base_url_str = str(descope_base_url).rstrip("/")
157
126
  # Ensure descope_base_url has a scheme
158
127
  if not descope_base_url_str.startswith(("http://", "https://")):
159
128
  descope_base_url_str = f"https://{descope_base_url_str}"
@@ -172,7 +141,7 @@ class DescopeProvider(RemoteAuthProvider):
172
141
  issuer=issuer_url,
173
142
  algorithm="RS256",
174
143
  audience=self.project_id,
175
- required_scopes=settings.required_scopes,
144
+ required_scopes=parsed_scopes,
176
145
  )
177
146
 
178
147
  # Initialize RemoteAuthProvider with Descope as the authorization server
@@ -26,45 +26,17 @@ from datetime import datetime
26
26
 
27
27
  import httpx
28
28
  from key_value.aio.protocols import AsyncKeyValue
29
- from pydantic import AnyHttpUrl, SecretStr, field_validator
30
- from pydantic_settings import BaseSettings, SettingsConfigDict
29
+ from pydantic import AnyHttpUrl
31
30
 
32
31
  from fastmcp.server.auth import TokenVerifier
33
32
  from fastmcp.server.auth.auth import AccessToken
34
33
  from fastmcp.server.auth.oauth_proxy import OAuthProxy
35
- from fastmcp.settings import ENV_FILE
36
34
  from fastmcp.utilities.auth import parse_scopes
37
35
  from fastmcp.utilities.logging import get_logger
38
- from fastmcp.utilities.types import NotSet, NotSetT
39
36
 
40
37
  logger = get_logger(__name__)
41
38
 
42
39
 
43
- class DiscordProviderSettings(BaseSettings):
44
- """Settings for Discord OAuth provider."""
45
-
46
- model_config = SettingsConfigDict(
47
- env_prefix="FASTMCP_SERVER_AUTH_DISCORD_",
48
- env_file=ENV_FILE,
49
- extra="ignore",
50
- )
51
-
52
- client_id: str | None = None
53
- client_secret: SecretStr | None = None
54
- base_url: AnyHttpUrl | str | None = None
55
- issuer_url: AnyHttpUrl | str | None = None
56
- redirect_path: str | None = None
57
- required_scopes: list[str] | None = None
58
- timeout_seconds: int | None = None
59
- allowed_client_redirect_uris: list[str] | None = None
60
- jwt_signing_key: str | None = None
61
-
62
- @field_validator("required_scopes", mode="before")
63
- @classmethod
64
- def _parse_scopes(cls, v):
65
- return parse_scopes(v)
66
-
67
-
68
40
  class DiscordTokenVerifier(TokenVerifier):
69
41
  """Token verifier for Discord OAuth tokens.
70
42
 
@@ -200,16 +172,16 @@ class DiscordProvider(OAuthProxy):
200
172
  def __init__(
201
173
  self,
202
174
  *,
203
- client_id: str | NotSetT = NotSet,
204
- client_secret: str | NotSetT = NotSet,
205
- base_url: AnyHttpUrl | str | NotSetT = NotSet,
206
- issuer_url: AnyHttpUrl | str | NotSetT = NotSet,
207
- redirect_path: str | NotSetT = NotSet,
208
- required_scopes: list[str] | NotSetT = NotSet,
209
- timeout_seconds: int | NotSetT = NotSet,
210
- allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
175
+ client_id: str,
176
+ client_secret: str,
177
+ base_url: AnyHttpUrl | str,
178
+ issuer_url: AnyHttpUrl | str | None = None,
179
+ redirect_path: str | None = None,
180
+ required_scopes: list[str] | None = None,
181
+ timeout_seconds: int = 10,
182
+ allowed_client_redirect_uris: list[str] | None = None,
211
183
  client_storage: AsyncKeyValue | None = None,
212
- jwt_signing_key: str | bytes | NotSetT = NotSet,
184
+ jwt_signing_key: str | bytes | None = None,
213
185
  require_authorization_consent: bool = True,
214
186
  ):
215
187
  """Initialize Discord OAuth provider.
@@ -225,7 +197,7 @@ class DiscordProvider(OAuthProxy):
225
197
  - "identify" for profile info (default)
226
198
  - "email" for email access
227
199
  - "guilds" for server membership info
228
- timeout_seconds: HTTP request timeout for Discord API calls
200
+ timeout_seconds: HTTP request timeout for Discord API calls (defaults to 10)
229
201
  allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
230
202
  If None (default), all URIs are allowed. If empty list, no URIs are allowed.
231
203
  client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
@@ -239,70 +211,37 @@ class DiscordProvider(OAuthProxy):
239
211
  When False, authorization proceeds directly without user confirmation.
240
212
  SECURITY WARNING: Only disable for local development or testing environments.
241
213
  """
242
-
243
- settings = DiscordProviderSettings.model_validate(
244
- {
245
- k: v
246
- for k, v in {
247
- "client_id": client_id,
248
- "client_secret": client_secret,
249
- "base_url": base_url,
250
- "issuer_url": issuer_url,
251
- "redirect_path": redirect_path,
252
- "required_scopes": required_scopes,
253
- "timeout_seconds": timeout_seconds,
254
- "allowed_client_redirect_uris": allowed_client_redirect_uris,
255
- "jwt_signing_key": jwt_signing_key,
256
- }.items()
257
- if v is not NotSet
258
- }
214
+ # Parse scopes if provided as string
215
+ required_scopes_final = (
216
+ parse_scopes(required_scopes)
217
+ if required_scopes is not None
218
+ else ["identify"]
259
219
  )
260
220
 
261
- # Validate required settings
262
- if not settings.client_id:
263
- raise ValueError(
264
- "client_id is required - set via parameter or FASTMCP_SERVER_AUTH_DISCORD_CLIENT_ID"
265
- )
266
- if not settings.client_secret:
267
- raise ValueError(
268
- "client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_DISCORD_CLIENT_SECRET"
269
- )
270
-
271
- # Apply defaults
272
- timeout_seconds_final = settings.timeout_seconds or 10
273
- required_scopes_final = settings.required_scopes or ["identify"]
274
- allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
275
-
276
221
  # Create Discord token verifier
277
222
  token_verifier = DiscordTokenVerifier(
278
223
  required_scopes=required_scopes_final,
279
- timeout_seconds=timeout_seconds_final,
280
- )
281
-
282
- # Extract secret string from SecretStr
283
- client_secret_str = (
284
- settings.client_secret.get_secret_value() if settings.client_secret else ""
224
+ timeout_seconds=timeout_seconds,
285
225
  )
286
226
 
287
227
  # Initialize OAuth proxy with Discord endpoints
288
228
  super().__init__(
289
229
  upstream_authorization_endpoint="https://discord.com/oauth2/authorize",
290
230
  upstream_token_endpoint="https://discord.com/api/oauth2/token",
291
- upstream_client_id=settings.client_id,
292
- upstream_client_secret=client_secret_str,
231
+ upstream_client_id=client_id,
232
+ upstream_client_secret=client_secret,
293
233
  token_verifier=token_verifier,
294
- base_url=settings.base_url,
295
- redirect_path=settings.redirect_path,
296
- issuer_url=settings.issuer_url
297
- or settings.base_url, # Default to base_url if not specified
298
- allowed_client_redirect_uris=allowed_client_redirect_uris_final,
234
+ base_url=base_url,
235
+ redirect_path=redirect_path,
236
+ issuer_url=issuer_url or base_url, # Default to base_url if not specified
237
+ allowed_client_redirect_uris=allowed_client_redirect_uris,
299
238
  client_storage=client_storage,
300
- jwt_signing_key=settings.jwt_signing_key,
239
+ jwt_signing_key=jwt_signing_key,
301
240
  require_authorization_consent=require_authorization_consent,
302
241
  )
303
242
 
304
243
  logger.debug(
305
244
  "Initialized Discord OAuth provider for client %s with scopes: %s",
306
- settings.client_id,
245
+ client_id,
307
246
  required_scopes_final,
308
247
  )