fastmcp 2.11.2__py3-none-any.whl → 2.12.0rc1__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 (77) hide show
  1. fastmcp/__init__.py +5 -4
  2. fastmcp/cli/claude.py +22 -18
  3. fastmcp/cli/cli.py +472 -136
  4. fastmcp/cli/install/claude_code.py +37 -40
  5. fastmcp/cli/install/claude_desktop.py +37 -42
  6. fastmcp/cli/install/cursor.py +148 -38
  7. fastmcp/cli/install/mcp_json.py +38 -43
  8. fastmcp/cli/install/shared.py +64 -7
  9. fastmcp/cli/run.py +122 -215
  10. fastmcp/client/auth/oauth.py +69 -13
  11. fastmcp/client/client.py +46 -9
  12. fastmcp/client/logging.py +25 -1
  13. fastmcp/client/oauth_callback.py +91 -91
  14. fastmcp/client/sampling.py +12 -4
  15. fastmcp/client/transports.py +143 -67
  16. fastmcp/experimental/sampling/__init__.py +0 -0
  17. fastmcp/experimental/sampling/handlers/__init__.py +3 -0
  18. fastmcp/experimental/sampling/handlers/base.py +21 -0
  19. fastmcp/experimental/sampling/handlers/openai.py +163 -0
  20. fastmcp/experimental/server/openapi/routing.py +1 -3
  21. fastmcp/experimental/server/openapi/server.py +10 -25
  22. fastmcp/experimental/utilities/openapi/__init__.py +2 -2
  23. fastmcp/experimental/utilities/openapi/formatters.py +34 -0
  24. fastmcp/experimental/utilities/openapi/models.py +5 -2
  25. fastmcp/experimental/utilities/openapi/parser.py +252 -70
  26. fastmcp/experimental/utilities/openapi/schemas.py +135 -106
  27. fastmcp/mcp_config.py +40 -20
  28. fastmcp/prompts/prompt_manager.py +4 -2
  29. fastmcp/resources/resource_manager.py +16 -6
  30. fastmcp/server/auth/__init__.py +11 -1
  31. fastmcp/server/auth/auth.py +19 -2
  32. fastmcp/server/auth/oauth_proxy.py +1047 -0
  33. fastmcp/server/auth/providers/azure.py +270 -0
  34. fastmcp/server/auth/providers/github.py +287 -0
  35. fastmcp/server/auth/providers/google.py +305 -0
  36. fastmcp/server/auth/providers/jwt.py +27 -16
  37. fastmcp/server/auth/providers/workos.py +256 -2
  38. fastmcp/server/auth/redirect_validation.py +65 -0
  39. fastmcp/server/auth/registry.py +1 -1
  40. fastmcp/server/context.py +91 -41
  41. fastmcp/server/dependencies.py +32 -2
  42. fastmcp/server/elicitation.py +60 -1
  43. fastmcp/server/http.py +44 -37
  44. fastmcp/server/middleware/logging.py +66 -28
  45. fastmcp/server/proxy.py +2 -0
  46. fastmcp/server/sampling/handler.py +19 -0
  47. fastmcp/server/server.py +85 -20
  48. fastmcp/settings.py +18 -3
  49. fastmcp/tools/tool.py +23 -10
  50. fastmcp/tools/tool_manager.py +5 -1
  51. fastmcp/tools/tool_transform.py +75 -32
  52. fastmcp/utilities/auth.py +34 -0
  53. fastmcp/utilities/cli.py +148 -15
  54. fastmcp/utilities/components.py +21 -5
  55. fastmcp/utilities/inspect.py +166 -37
  56. fastmcp/utilities/json_schema_type.py +4 -2
  57. fastmcp/utilities/logging.py +4 -1
  58. fastmcp/utilities/mcp_config.py +47 -18
  59. fastmcp/utilities/mcp_server_config/__init__.py +25 -0
  60. fastmcp/utilities/mcp_server_config/v1/__init__.py +0 -0
  61. fastmcp/utilities/mcp_server_config/v1/environments/__init__.py +6 -0
  62. fastmcp/utilities/mcp_server_config/v1/environments/base.py +30 -0
  63. fastmcp/utilities/mcp_server_config/v1/environments/uv.py +306 -0
  64. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +446 -0
  65. fastmcp/utilities/mcp_server_config/v1/schema.json +361 -0
  66. fastmcp/utilities/mcp_server_config/v1/sources/__init__.py +0 -0
  67. fastmcp/utilities/mcp_server_config/v1/sources/base.py +30 -0
  68. fastmcp/utilities/mcp_server_config/v1/sources/filesystem.py +216 -0
  69. fastmcp/utilities/openapi.py +4 -4
  70. fastmcp/utilities/tests.py +7 -2
  71. fastmcp/utilities/types.py +15 -2
  72. {fastmcp-2.11.2.dist-info → fastmcp-2.12.0rc1.dist-info}/METADATA +3 -2
  73. fastmcp-2.12.0rc1.dist-info/RECORD +129 -0
  74. fastmcp-2.11.2.dist-info/RECORD +0 -108
  75. {fastmcp-2.11.2.dist-info → fastmcp-2.12.0rc1.dist-info}/WHEEL +0 -0
  76. {fastmcp-2.11.2.dist-info → fastmcp-2.12.0rc1.dist-info}/entry_points.txt +0 -0
  77. {fastmcp-2.11.2.dist-info → fastmcp-2.12.0rc1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,270 @@
1
+ """Azure (Microsoft Entra) OAuth provider for FastMCP.
2
+
3
+ This provider implements Azure/Microsoft Entra ID OAuth authentication
4
+ using the OAuth Proxy pattern for non-DCR OAuth flows.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import httpx
10
+ from pydantic import SecretStr, field_validator
11
+ from pydantic_settings import BaseSettings, SettingsConfigDict
12
+
13
+ from fastmcp.server.auth import AccessToken, TokenVerifier
14
+ from fastmcp.server.auth.oauth_proxy import OAuthProxy
15
+ from fastmcp.server.auth.registry import register_provider
16
+ from fastmcp.utilities.auth import parse_scopes
17
+ from fastmcp.utilities.logging import get_logger
18
+ from fastmcp.utilities.types import NotSet, NotSetT
19
+
20
+ logger = get_logger(__name__)
21
+
22
+
23
+ class AzureProviderSettings(BaseSettings):
24
+ """Settings for Azure OAuth provider."""
25
+
26
+ model_config = SettingsConfigDict(
27
+ env_prefix="FASTMCP_SERVER_AUTH_AZURE_",
28
+ env_file=".env",
29
+ extra="ignore",
30
+ )
31
+
32
+ client_id: str | None = None
33
+ client_secret: SecretStr | None = None
34
+ tenant_id: str | None = None
35
+ base_url: str | None = None
36
+ redirect_path: str | None = None
37
+ required_scopes: list[str] | None = None
38
+ timeout_seconds: int | None = None
39
+ resource_server_url: str | None = None
40
+ allowed_client_redirect_uris: list[str] | None = None
41
+
42
+ @field_validator("required_scopes", mode="before")
43
+ @classmethod
44
+ def _parse_scopes(cls, v):
45
+ return parse_scopes(v)
46
+
47
+
48
+ class AzureTokenVerifier(TokenVerifier):
49
+ """Token verifier for Azure OAuth tokens.
50
+
51
+ Azure tokens are JWTs, but we verify them by calling the Microsoft Graph API
52
+ to get user information and validate the token.
53
+ """
54
+
55
+ def __init__(
56
+ self,
57
+ *,
58
+ required_scopes: list[str] | None = None,
59
+ timeout_seconds: int = 10,
60
+ ):
61
+ """Initialize the Azure token verifier.
62
+
63
+ Args:
64
+ required_scopes: Required OAuth scopes
65
+ timeout_seconds: HTTP request timeout
66
+ """
67
+ super().__init__(required_scopes=required_scopes)
68
+ self.timeout_seconds = timeout_seconds
69
+
70
+ async def verify_token(self, token: str) -> AccessToken | None:
71
+ """Verify Azure OAuth token by calling Microsoft Graph API."""
72
+ try:
73
+ async with httpx.AsyncClient(timeout=self.timeout_seconds) as client:
74
+ # Use Microsoft Graph API to validate token and get user info
75
+ response = await client.get(
76
+ "https://graph.microsoft.com/v1.0/me",
77
+ headers={
78
+ "Authorization": f"Bearer {token}",
79
+ "User-Agent": "FastMCP-Azure-OAuth",
80
+ },
81
+ )
82
+
83
+ if response.status_code != 200:
84
+ logger.debug(
85
+ "Azure token verification failed: %d - %s",
86
+ response.status_code,
87
+ response.text[:200],
88
+ )
89
+ return None
90
+
91
+ user_data = response.json()
92
+
93
+ # Create AccessToken with Azure user info
94
+ return AccessToken(
95
+ token=token,
96
+ client_id=str(user_data.get("id", "unknown")),
97
+ scopes=self.required_scopes or [],
98
+ expires_at=None,
99
+ claims={
100
+ "sub": user_data.get("id"),
101
+ "email": user_data.get("mail")
102
+ or user_data.get("userPrincipalName"),
103
+ "name": user_data.get("displayName"),
104
+ "given_name": user_data.get("givenName"),
105
+ "family_name": user_data.get("surname"),
106
+ "job_title": user_data.get("jobTitle"),
107
+ "office_location": user_data.get("officeLocation"),
108
+ },
109
+ )
110
+
111
+ except httpx.RequestError as e:
112
+ logger.debug("Failed to verify Azure token: %s", e)
113
+ return None
114
+ except Exception as e:
115
+ logger.debug("Azure token verification error: %s", e)
116
+ return None
117
+
118
+
119
+ @register_provider("AZURE")
120
+ class AzureProvider(OAuthProxy):
121
+ """Azure (Microsoft Entra) OAuth provider for FastMCP.
122
+
123
+ This provider implements Azure/Microsoft Entra ID authentication using the
124
+ OAuth Proxy pattern. It supports both organizational accounts and personal
125
+ Microsoft accounts depending on the tenant configuration.
126
+
127
+ Features:
128
+ - Transparent OAuth proxy to Azure/Microsoft identity platform
129
+ - Automatic token validation via Microsoft Graph API
130
+ - User information extraction
131
+ - Support for different tenant configurations (common, organizations, consumers)
132
+
133
+ Setup Requirements:
134
+ 1. Register an application in Azure Portal (portal.azure.com)
135
+ 2. Configure redirect URI as: http://localhost:8000/auth/callback
136
+ 3. Note your Application (client) ID and create a client secret
137
+ 4. Optionally note your Directory (tenant) ID for single-tenant apps
138
+
139
+ Example:
140
+ ```python
141
+ from fastmcp import FastMCP
142
+ from fastmcp.server.auth.providers.azure import AzureProvider
143
+
144
+ auth = AzureProvider(
145
+ client_id="your-client-id",
146
+ client_secret="your-client-secret",
147
+ tenant_id="your-tenant-id", # Required: your Azure tenant ID from Azure Portal
148
+ base_url="http://localhost:8000"
149
+ )
150
+
151
+ mcp = FastMCP("My App", auth=auth)
152
+ ```
153
+ """
154
+
155
+ def __init__(
156
+ self,
157
+ *,
158
+ client_id: str | NotSetT = NotSet,
159
+ client_secret: str | NotSetT = NotSet,
160
+ tenant_id: str | NotSetT = NotSet,
161
+ base_url: str | NotSetT = NotSet,
162
+ redirect_path: str | NotSetT = NotSet,
163
+ required_scopes: list[str] | None | NotSetT = NotSet,
164
+ timeout_seconds: int | NotSetT = NotSet,
165
+ resource_server_url: str | NotSetT = NotSet,
166
+ allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
167
+ ):
168
+ """Initialize Azure OAuth provider.
169
+
170
+ Args:
171
+ client_id: Azure application (client) ID
172
+ client_secret: Azure client secret
173
+ tenant_id: Azure tenant ID (your specific tenant ID, "organizations", or "consumers")
174
+ base_url: Public URL of your FastMCP server (for OAuth callbacks)
175
+ redirect_path: Redirect path configured in Azure (defaults to "/auth/callback")
176
+ required_scopes: Required scopes (defaults to ["User.Read", "email", "openid", "profile"])
177
+ timeout_seconds: HTTP request timeout for Azure API calls
178
+ resource_server_url: Path of the FastMCP server (defaults to base_url). If your MCP endpoint is at
179
+ a different path like {base_url}/mcp, specify it here for RFC 8707 compliance.
180
+ allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
181
+ If None (default), all URIs are allowed. If empty list, no URIs are allowed.
182
+ """
183
+ settings = AzureProviderSettings.model_validate(
184
+ {
185
+ k: v
186
+ for k, v in {
187
+ "client_id": client_id,
188
+ "client_secret": client_secret,
189
+ "tenant_id": tenant_id,
190
+ "base_url": base_url,
191
+ "redirect_path": redirect_path,
192
+ "required_scopes": required_scopes,
193
+ "timeout_seconds": timeout_seconds,
194
+ "resource_server_url": resource_server_url,
195
+ "allowed_client_redirect_uris": allowed_client_redirect_uris,
196
+ }.items()
197
+ if v is not NotSet
198
+ }
199
+ )
200
+
201
+ # Validate required settings
202
+ if not settings.client_id:
203
+ raise ValueError(
204
+ "client_id is required - set via parameter or FASTMCP_SERVER_AUTH_AZURE_CLIENT_ID"
205
+ )
206
+ if not settings.client_secret:
207
+ raise ValueError(
208
+ "client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AZURE_CLIENT_SECRET"
209
+ )
210
+
211
+ # Validate tenant_id is provided
212
+ if not settings.tenant_id:
213
+ raise ValueError(
214
+ "tenant_id is required - set via parameter or FASTMCP_SERVER_AUTH_AZURE_TENANT_ID. "
215
+ "Use your Azure tenant ID (found in Azure Portal), 'organizations', or 'consumers'"
216
+ )
217
+
218
+ # Apply defaults
219
+ tenant_id_final = settings.tenant_id
220
+ base_url_final = settings.base_url or "http://localhost:8000"
221
+ redirect_path_final = settings.redirect_path or "/auth/callback"
222
+ timeout_seconds_final = settings.timeout_seconds or 10
223
+ # Default scopes for Azure - User.Read gives us access to user info via Graph API
224
+ scopes_final = settings.required_scopes or [
225
+ "User.Read",
226
+ "email",
227
+ "openid",
228
+ "profile",
229
+ ]
230
+ resource_server_url_final = settings.resource_server_url or base_url_final
231
+ allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
232
+
233
+ # Extract secret string from SecretStr
234
+ client_secret_str = (
235
+ settings.client_secret.get_secret_value() if settings.client_secret else ""
236
+ )
237
+
238
+ # Create Azure token verifier
239
+ token_verifier = AzureTokenVerifier(
240
+ required_scopes=scopes_final,
241
+ timeout_seconds=timeout_seconds_final,
242
+ )
243
+
244
+ # Build Azure OAuth endpoints with tenant
245
+ authorization_endpoint = (
246
+ f"https://login.microsoftonline.com/{tenant_id_final}/oauth2/v2.0/authorize"
247
+ )
248
+ token_endpoint = (
249
+ f"https://login.microsoftonline.com/{tenant_id_final}/oauth2/v2.0/token"
250
+ )
251
+
252
+ # Initialize OAuth proxy with Azure endpoints
253
+ super().__init__(
254
+ upstream_authorization_endpoint=authorization_endpoint,
255
+ upstream_token_endpoint=token_endpoint,
256
+ upstream_client_id=settings.client_id,
257
+ upstream_client_secret=client_secret_str,
258
+ token_verifier=token_verifier,
259
+ base_url=base_url_final,
260
+ redirect_path=redirect_path_final,
261
+ issuer_url=base_url_final,
262
+ allowed_client_redirect_uris=allowed_client_redirect_uris_final,
263
+ resource_server_url=resource_server_url_final,
264
+ )
265
+
266
+ logger.info(
267
+ "Initialized Azure OAuth provider for client %s with tenant %s",
268
+ settings.client_id,
269
+ tenant_id_final,
270
+ )
@@ -0,0 +1,287 @@
1
+ """GitHub OAuth provider for FastMCP.
2
+
3
+ This module provides a complete GitHub OAuth integration that's ready to use
4
+ with just a client ID and client secret. It handles all the complexity of
5
+ GitHub's OAuth flow, token validation, and user management.
6
+
7
+ Example:
8
+ ```python
9
+ from fastmcp import FastMCP
10
+ from fastmcp.server.auth.providers.github import GitHubProvider
11
+
12
+ # Simple GitHub OAuth protection
13
+ auth = GitHubProvider(
14
+ client_id="your-github-client-id",
15
+ client_secret="your-github-client-secret"
16
+ )
17
+
18
+ mcp = FastMCP("My Protected Server", auth=auth)
19
+ ```
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import httpx
25
+ from pydantic import AnyHttpUrl, SecretStr, field_validator
26
+ from pydantic_settings import BaseSettings, SettingsConfigDict
27
+
28
+ from fastmcp.server.auth import TokenVerifier
29
+ from fastmcp.server.auth.auth import AccessToken
30
+ from fastmcp.server.auth.oauth_proxy import OAuthProxy
31
+ from fastmcp.server.auth.registry import register_provider
32
+ from fastmcp.utilities.auth import parse_scopes
33
+ from fastmcp.utilities.logging import get_logger
34
+ from fastmcp.utilities.types import NotSet, NotSetT
35
+
36
+ logger = get_logger(__name__)
37
+
38
+
39
+ class GitHubProviderSettings(BaseSettings):
40
+ """Settings for GitHub OAuth provider."""
41
+
42
+ model_config = SettingsConfigDict(
43
+ env_prefix="FASTMCP_SERVER_AUTH_GITHUB_",
44
+ env_file=".env",
45
+ extra="ignore",
46
+ )
47
+
48
+ client_id: str | None = None
49
+ client_secret: SecretStr | None = None
50
+ base_url: AnyHttpUrl | str | None = None
51
+ redirect_path: str | None = None
52
+ required_scopes: list[str] | None = None
53
+ timeout_seconds: int | None = None
54
+ resource_server_url: AnyHttpUrl | str | None = None
55
+ allowed_client_redirect_uris: list[str] | None = None
56
+
57
+ @field_validator("required_scopes", mode="before")
58
+ @classmethod
59
+ def _parse_scopes(cls, v):
60
+ return parse_scopes(v)
61
+
62
+
63
+ class GitHubTokenVerifier(TokenVerifier):
64
+ """Token verifier for GitHub OAuth tokens.
65
+
66
+ GitHub OAuth tokens are opaque (not JWTs), so we verify them
67
+ by calling GitHub's API to check if they're valid and get user info.
68
+ """
69
+
70
+ def __init__(
71
+ self,
72
+ *,
73
+ required_scopes: list[str] | None = None,
74
+ timeout_seconds: int = 10,
75
+ ):
76
+ """Initialize the GitHub token verifier.
77
+
78
+ Args:
79
+ required_scopes: Required OAuth scopes (e.g., ['user:email'])
80
+ timeout_seconds: HTTP request timeout
81
+ """
82
+ super().__init__(required_scopes=required_scopes)
83
+ self.timeout_seconds = timeout_seconds
84
+
85
+ async def verify_token(self, token: str) -> AccessToken | None:
86
+ """Verify GitHub OAuth token by calling GitHub API."""
87
+ try:
88
+ async with httpx.AsyncClient(timeout=self.timeout_seconds) as client:
89
+ # Get token info from GitHub API
90
+ response = await client.get(
91
+ "https://api.github.com/user",
92
+ headers={
93
+ "Authorization": f"Bearer {token}",
94
+ "Accept": "application/vnd.github.v3+json",
95
+ "User-Agent": "FastMCP-GitHub-OAuth",
96
+ },
97
+ )
98
+
99
+ if response.status_code != 200:
100
+ logger.debug(
101
+ "GitHub token verification failed: %d - %s",
102
+ response.status_code,
103
+ response.text[:200],
104
+ )
105
+ return None
106
+
107
+ user_data = response.json()
108
+
109
+ # Get token scopes from GitHub API
110
+ # GitHub includes scopes in the X-OAuth-Scopes header
111
+ scopes_response = await client.get(
112
+ "https://api.github.com/user/repos", # Any authenticated endpoint
113
+ headers={
114
+ "Authorization": f"Bearer {token}",
115
+ "Accept": "application/vnd.github.v3+json",
116
+ "User-Agent": "FastMCP-GitHub-OAuth",
117
+ },
118
+ )
119
+
120
+ # Extract scopes from X-OAuth-Scopes header if available
121
+ oauth_scopes_header = scopes_response.headers.get("x-oauth-scopes", "")
122
+ token_scopes = [
123
+ scope.strip()
124
+ for scope in oauth_scopes_header.split(",")
125
+ if scope.strip()
126
+ ]
127
+
128
+ # If no scopes in header, assume basic scopes based on successful user API call
129
+ if not token_scopes:
130
+ token_scopes = ["user"] # Basic scope if we can access user info
131
+
132
+ # Check required scopes
133
+ if self.required_scopes:
134
+ token_scopes_set = set(token_scopes)
135
+ required_scopes_set = set(self.required_scopes)
136
+ if not required_scopes_set.issubset(token_scopes_set):
137
+ logger.debug(
138
+ "GitHub token missing required scopes. Has %d, needs %d",
139
+ len(token_scopes_set),
140
+ len(required_scopes_set),
141
+ )
142
+ return None
143
+
144
+ # Create AccessToken with GitHub user info
145
+ return AccessToken(
146
+ token=token,
147
+ client_id=str(user_data.get("id", "unknown")), # Use GitHub user ID
148
+ scopes=token_scopes,
149
+ expires_at=None, # GitHub tokens don't typically expire
150
+ claims={
151
+ "sub": str(user_data["id"]),
152
+ "login": user_data.get("login"),
153
+ "name": user_data.get("name"),
154
+ "email": user_data.get("email"),
155
+ "avatar_url": user_data.get("avatar_url"),
156
+ "github_user_data": user_data,
157
+ },
158
+ )
159
+
160
+ except httpx.RequestError as e:
161
+ logger.debug("Failed to verify GitHub token: %s", e)
162
+ return None
163
+ except Exception as e:
164
+ logger.debug("GitHub token verification error: %s", e)
165
+ return None
166
+
167
+
168
+ @register_provider("GitHub")
169
+ class GitHubProvider(OAuthProxy):
170
+ """Complete GitHub OAuth provider for FastMCP.
171
+
172
+ This provider makes it trivial to add GitHub OAuth protection to any
173
+ FastMCP server. Just provide your GitHub OAuth app credentials and
174
+ a base URL, and you're ready to go.
175
+
176
+ Features:
177
+ - Transparent OAuth proxy to GitHub
178
+ - Automatic token validation via GitHub API
179
+ - User information extraction
180
+ - Minimal configuration required
181
+
182
+ Example:
183
+ ```python
184
+ from fastmcp import FastMCP
185
+ from fastmcp.server.auth.providers.github import GitHubProvider
186
+
187
+ auth = GitHubProvider(
188
+ client_id="Ov23li...",
189
+ client_secret="abc123...",
190
+ base_url="https://my-server.com" # Optional, defaults to http://localhost:8000
191
+ )
192
+
193
+ mcp = FastMCP("My App", auth=auth)
194
+ ```
195
+ """
196
+
197
+ def __init__(
198
+ self,
199
+ *,
200
+ client_id: str | NotSetT = NotSet,
201
+ client_secret: str | NotSetT = NotSet,
202
+ base_url: AnyHttpUrl | str | NotSetT = NotSet,
203
+ redirect_path: str | NotSetT = NotSet,
204
+ required_scopes: list[str] | NotSetT = NotSet,
205
+ timeout_seconds: int | NotSetT = NotSet,
206
+ resource_server_url: AnyHttpUrl | str | NotSetT = NotSet,
207
+ allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
208
+ ):
209
+ """Initialize GitHub OAuth provider.
210
+
211
+ Args:
212
+ client_id: GitHub OAuth app client ID (e.g., "Ov23li...")
213
+ client_secret: GitHub OAuth app client secret
214
+ base_url: Public URL of your FastMCP server (for OAuth callbacks)
215
+ redirect_path: Redirect path configured in GitHub OAuth app (defaults to "/auth/callback")
216
+ required_scopes: Required GitHub scopes (defaults to ["user"])
217
+ timeout_seconds: HTTP request timeout for GitHub API calls
218
+ resource_server_url: Path of the FastMCP server (defaults to base_url). If your MCP endpoint is at
219
+ a different path like {base_url}/mcp, specify it here for RFC 8707 compliance.
220
+ allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
221
+ If None (default), all URIs are allowed. If empty list, no URIs are allowed.
222
+ """
223
+ settings = GitHubProviderSettings.model_validate(
224
+ {
225
+ k: v
226
+ for k, v in {
227
+ "client_id": client_id,
228
+ "client_secret": client_secret,
229
+ "base_url": base_url,
230
+ "redirect_path": redirect_path,
231
+ "required_scopes": required_scopes,
232
+ "timeout_seconds": timeout_seconds,
233
+ "resource_server_url": resource_server_url,
234
+ "allowed_client_redirect_uris": allowed_client_redirect_uris,
235
+ }.items()
236
+ if v is not NotSet
237
+ }
238
+ )
239
+
240
+ # Validate required settings
241
+ if not settings.client_id:
242
+ raise ValueError(
243
+ "client_id is required - set via parameter or FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID"
244
+ )
245
+ if not settings.client_secret:
246
+ raise ValueError(
247
+ "client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET"
248
+ )
249
+
250
+ # Apply defaults
251
+ base_url_final = settings.base_url or "http://localhost:8000"
252
+ redirect_path_final = settings.redirect_path or "/auth/callback"
253
+ timeout_seconds_final = settings.timeout_seconds or 10
254
+ required_scopes_final = settings.required_scopes or ["user"]
255
+ resource_server_url_final = settings.resource_server_url or base_url_final
256
+ allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
257
+
258
+ # Create GitHub token verifier
259
+ token_verifier = GitHubTokenVerifier(
260
+ required_scopes=required_scopes_final,
261
+ timeout_seconds=timeout_seconds_final,
262
+ )
263
+
264
+ # Extract secret string from SecretStr
265
+ client_secret_str = (
266
+ settings.client_secret.get_secret_value() if settings.client_secret else ""
267
+ )
268
+
269
+ # Initialize OAuth proxy with GitHub endpoints
270
+ super().__init__(
271
+ upstream_authorization_endpoint="https://github.com/login/oauth/authorize",
272
+ upstream_token_endpoint="https://github.com/login/oauth/access_token",
273
+ upstream_client_id=settings.client_id,
274
+ upstream_client_secret=client_secret_str,
275
+ token_verifier=token_verifier,
276
+ base_url=base_url_final,
277
+ redirect_path=redirect_path_final,
278
+ issuer_url=base_url_final, # We act as the issuer for client registration
279
+ allowed_client_redirect_uris=allowed_client_redirect_uris_final,
280
+ resource_server_url=resource_server_url_final,
281
+ )
282
+
283
+ logger.info(
284
+ "Initialized GitHub OAuth provider for client %s with scopes: %s",
285
+ settings.client_id,
286
+ required_scopes_final,
287
+ )