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.
- fastmcp/_vendor/__init__.py +1 -0
- fastmcp/_vendor/docket_di/README.md +7 -0
- fastmcp/_vendor/docket_di/__init__.py +163 -0
- fastmcp/cli/cli.py +112 -28
- fastmcp/cli/install/claude_code.py +1 -5
- fastmcp/cli/install/claude_desktop.py +1 -5
- fastmcp/cli/install/cursor.py +1 -5
- fastmcp/cli/install/gemini_cli.py +1 -5
- fastmcp/cli/install/mcp_json.py +1 -6
- fastmcp/cli/run.py +146 -5
- fastmcp/client/__init__.py +7 -9
- fastmcp/client/auth/oauth.py +18 -17
- fastmcp/client/client.py +100 -870
- fastmcp/client/elicitation.py +1 -1
- fastmcp/client/mixins/__init__.py +13 -0
- fastmcp/client/mixins/prompts.py +295 -0
- fastmcp/client/mixins/resources.py +325 -0
- fastmcp/client/mixins/task_management.py +157 -0
- fastmcp/client/mixins/tools.py +397 -0
- fastmcp/client/sampling/handlers/anthropic.py +2 -2
- fastmcp/client/sampling/handlers/openai.py +1 -1
- fastmcp/client/tasks.py +3 -3
- fastmcp/client/telemetry.py +47 -0
- fastmcp/client/transports/__init__.py +38 -0
- fastmcp/client/transports/base.py +82 -0
- fastmcp/client/transports/config.py +170 -0
- fastmcp/client/transports/http.py +145 -0
- fastmcp/client/transports/inference.py +154 -0
- fastmcp/client/transports/memory.py +90 -0
- fastmcp/client/transports/sse.py +89 -0
- fastmcp/client/transports/stdio.py +543 -0
- fastmcp/contrib/component_manager/README.md +4 -10
- fastmcp/contrib/component_manager/__init__.py +1 -2
- fastmcp/contrib/component_manager/component_manager.py +95 -160
- fastmcp/contrib/component_manager/example.py +1 -1
- fastmcp/contrib/mcp_mixin/example.py +4 -4
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +11 -4
- fastmcp/decorators.py +41 -0
- fastmcp/dependencies.py +12 -1
- fastmcp/exceptions.py +4 -0
- fastmcp/experimental/server/openapi/__init__.py +18 -15
- fastmcp/mcp_config.py +13 -4
- fastmcp/prompts/__init__.py +6 -3
- fastmcp/prompts/function_prompt.py +465 -0
- fastmcp/prompts/prompt.py +321 -271
- fastmcp/resources/__init__.py +5 -3
- fastmcp/resources/function_resource.py +335 -0
- fastmcp/resources/resource.py +325 -115
- fastmcp/resources/template.py +215 -43
- fastmcp/resources/types.py +27 -12
- fastmcp/server/__init__.py +2 -2
- fastmcp/server/auth/__init__.py +14 -0
- fastmcp/server/auth/auth.py +30 -10
- fastmcp/server/auth/authorization.py +190 -0
- fastmcp/server/auth/oauth_proxy/__init__.py +14 -0
- fastmcp/server/auth/oauth_proxy/consent.py +361 -0
- fastmcp/server/auth/oauth_proxy/models.py +178 -0
- fastmcp/server/auth/{oauth_proxy.py → oauth_proxy/proxy.py} +24 -778
- fastmcp/server/auth/oauth_proxy/ui.py +277 -0
- fastmcp/server/auth/oidc_proxy.py +2 -2
- fastmcp/server/auth/providers/auth0.py +24 -94
- fastmcp/server/auth/providers/aws.py +26 -95
- fastmcp/server/auth/providers/azure.py +41 -129
- fastmcp/server/auth/providers/descope.py +18 -49
- fastmcp/server/auth/providers/discord.py +25 -86
- fastmcp/server/auth/providers/github.py +23 -87
- fastmcp/server/auth/providers/google.py +24 -87
- fastmcp/server/auth/providers/introspection.py +60 -79
- fastmcp/server/auth/providers/jwt.py +30 -67
- fastmcp/server/auth/providers/oci.py +47 -110
- fastmcp/server/auth/providers/scalekit.py +23 -61
- fastmcp/server/auth/providers/supabase.py +18 -47
- fastmcp/server/auth/providers/workos.py +34 -127
- fastmcp/server/context.py +372 -419
- fastmcp/server/dependencies.py +541 -251
- fastmcp/server/elicitation.py +20 -18
- fastmcp/server/event_store.py +3 -3
- fastmcp/server/http.py +16 -6
- fastmcp/server/lifespan.py +198 -0
- fastmcp/server/low_level.py +92 -2
- fastmcp/server/middleware/__init__.py +5 -1
- fastmcp/server/middleware/authorization.py +312 -0
- fastmcp/server/middleware/caching.py +101 -54
- fastmcp/server/middleware/middleware.py +6 -9
- fastmcp/server/middleware/ping.py +70 -0
- fastmcp/server/middleware/tool_injection.py +2 -2
- fastmcp/server/mixins/__init__.py +7 -0
- fastmcp/server/mixins/lifespan.py +217 -0
- fastmcp/server/mixins/mcp_operations.py +392 -0
- fastmcp/server/mixins/transport.py +342 -0
- fastmcp/server/openapi/__init__.py +41 -21
- fastmcp/server/openapi/components.py +16 -339
- fastmcp/server/openapi/routing.py +34 -118
- fastmcp/server/openapi/server.py +67 -392
- fastmcp/server/providers/__init__.py +71 -0
- fastmcp/server/providers/aggregate.py +261 -0
- fastmcp/server/providers/base.py +578 -0
- fastmcp/server/providers/fastmcp_provider.py +674 -0
- fastmcp/server/providers/filesystem.py +226 -0
- fastmcp/server/providers/filesystem_discovery.py +327 -0
- fastmcp/server/providers/local_provider/__init__.py +11 -0
- fastmcp/server/providers/local_provider/decorators/__init__.py +15 -0
- fastmcp/server/providers/local_provider/decorators/prompts.py +256 -0
- fastmcp/server/providers/local_provider/decorators/resources.py +240 -0
- fastmcp/server/providers/local_provider/decorators/tools.py +315 -0
- fastmcp/server/providers/local_provider/local_provider.py +465 -0
- fastmcp/server/providers/openapi/__init__.py +39 -0
- fastmcp/server/providers/openapi/components.py +332 -0
- fastmcp/server/providers/openapi/provider.py +405 -0
- fastmcp/server/providers/openapi/routing.py +109 -0
- fastmcp/server/providers/proxy.py +867 -0
- fastmcp/server/providers/skills/__init__.py +59 -0
- fastmcp/server/providers/skills/_common.py +101 -0
- fastmcp/server/providers/skills/claude_provider.py +44 -0
- fastmcp/server/providers/skills/directory_provider.py +153 -0
- fastmcp/server/providers/skills/skill_provider.py +432 -0
- fastmcp/server/providers/skills/vendor_providers.py +142 -0
- fastmcp/server/providers/wrapped_provider.py +140 -0
- fastmcp/server/proxy.py +34 -700
- fastmcp/server/sampling/run.py +341 -2
- fastmcp/server/sampling/sampling_tool.py +4 -3
- fastmcp/server/server.py +1214 -2171
- fastmcp/server/tasks/__init__.py +2 -1
- fastmcp/server/tasks/capabilities.py +13 -1
- fastmcp/server/tasks/config.py +66 -3
- fastmcp/server/tasks/handlers.py +65 -273
- fastmcp/server/tasks/keys.py +4 -6
- fastmcp/server/tasks/requests.py +474 -0
- fastmcp/server/tasks/routing.py +76 -0
- fastmcp/server/tasks/subscriptions.py +20 -11
- fastmcp/server/telemetry.py +131 -0
- fastmcp/server/transforms/__init__.py +244 -0
- fastmcp/server/transforms/namespace.py +193 -0
- fastmcp/server/transforms/prompts_as_tools.py +175 -0
- fastmcp/server/transforms/resources_as_tools.py +190 -0
- fastmcp/server/transforms/tool_transform.py +96 -0
- fastmcp/server/transforms/version_filter.py +124 -0
- fastmcp/server/transforms/visibility.py +526 -0
- fastmcp/settings.py +34 -96
- fastmcp/telemetry.py +122 -0
- fastmcp/tools/__init__.py +10 -3
- fastmcp/tools/function_parsing.py +201 -0
- fastmcp/tools/function_tool.py +467 -0
- fastmcp/tools/tool.py +215 -362
- fastmcp/tools/tool_transform.py +38 -21
- fastmcp/utilities/async_utils.py +69 -0
- fastmcp/utilities/components.py +152 -91
- fastmcp/utilities/inspect.py +8 -20
- fastmcp/utilities/json_schema.py +12 -5
- fastmcp/utilities/json_schema_type.py +17 -15
- fastmcp/utilities/lifespan.py +56 -0
- fastmcp/utilities/logging.py +12 -4
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -3
- fastmcp/utilities/openapi/parser.py +3 -3
- fastmcp/utilities/pagination.py +80 -0
- fastmcp/utilities/skills.py +253 -0
- fastmcp/utilities/tests.py +0 -16
- fastmcp/utilities/timeout.py +47 -0
- fastmcp/utilities/types.py +1 -1
- fastmcp/utilities/versions.py +285 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/METADATA +8 -5
- fastmcp-3.0.0b1.dist-info/RECORD +228 -0
- fastmcp/client/transports.py +0 -1170
- fastmcp/contrib/component_manager/component_service.py +0 -209
- fastmcp/prompts/prompt_manager.py +0 -117
- fastmcp/resources/resource_manager.py +0 -338
- fastmcp/server/tasks/converters.py +0 -206
- fastmcp/server/tasks/protocol.py +0 -359
- fastmcp/tools/tool_manager.py +0 -170
- fastmcp/utilities/mcp_config.py +0 -56
- fastmcp-2.14.4.dist-info/RECORD +0 -161
- /fastmcp/server/{openapi → providers/openapi}/README.md +0 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/WHEEL +0 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""OAuth Proxy UI Generation Functions.
|
|
2
|
+
|
|
3
|
+
This module contains HTML generation functions for consent and error pages.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from urllib.parse import urlparse
|
|
9
|
+
|
|
10
|
+
from fastmcp.utilities.ui import (
|
|
11
|
+
BUTTON_STYLES,
|
|
12
|
+
DETAIL_BOX_STYLES,
|
|
13
|
+
DETAILS_STYLES,
|
|
14
|
+
INFO_BOX_STYLES,
|
|
15
|
+
REDIRECT_SECTION_STYLES,
|
|
16
|
+
TOOLTIP_STYLES,
|
|
17
|
+
create_logo,
|
|
18
|
+
create_page,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def create_consent_html(
|
|
23
|
+
client_id: str,
|
|
24
|
+
redirect_uri: str,
|
|
25
|
+
scopes: list[str],
|
|
26
|
+
txn_id: str,
|
|
27
|
+
csrf_token: str,
|
|
28
|
+
client_name: str | None = None,
|
|
29
|
+
title: str = "Application Access Request",
|
|
30
|
+
server_name: str | None = None,
|
|
31
|
+
server_icon_url: str | None = None,
|
|
32
|
+
server_website_url: str | None = None,
|
|
33
|
+
client_website_url: str | None = None,
|
|
34
|
+
csp_policy: str | None = None,
|
|
35
|
+
) -> str:
|
|
36
|
+
"""Create a styled HTML consent page for OAuth authorization requests.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
csp_policy: Content Security Policy override.
|
|
40
|
+
If None, uses the built-in CSP policy with appropriate directives.
|
|
41
|
+
If empty string "", disables CSP entirely (no meta tag is rendered).
|
|
42
|
+
If a non-empty string, uses that as the CSP policy value.
|
|
43
|
+
"""
|
|
44
|
+
import html as html_module
|
|
45
|
+
|
|
46
|
+
client_display = html_module.escape(client_name or client_id)
|
|
47
|
+
server_name_escaped = html_module.escape(server_name or "FastMCP")
|
|
48
|
+
|
|
49
|
+
# Make server name a hyperlink if website URL is available
|
|
50
|
+
if server_website_url:
|
|
51
|
+
website_url_escaped = html_module.escape(server_website_url)
|
|
52
|
+
server_display = f'<a href="{website_url_escaped}" target="_blank" rel="noopener noreferrer" class="server-name-link">{server_name_escaped}</a>'
|
|
53
|
+
else:
|
|
54
|
+
server_display = server_name_escaped
|
|
55
|
+
|
|
56
|
+
# Build intro box with call-to-action
|
|
57
|
+
intro_box = f"""
|
|
58
|
+
<div class="info-box">
|
|
59
|
+
<p>The application <strong>{client_display}</strong> wants to access the MCP server <strong>{server_display}</strong>. Please ensure you recognize the callback address below.</p>
|
|
60
|
+
</div>
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
# Build redirect URI section (yellow box, centered)
|
|
64
|
+
redirect_uri_escaped = html_module.escape(redirect_uri)
|
|
65
|
+
redirect_section = f"""
|
|
66
|
+
<div class="redirect-section">
|
|
67
|
+
<span class="label">Credentials will be sent to:</span>
|
|
68
|
+
<div class="value">{redirect_uri_escaped}</div>
|
|
69
|
+
</div>
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
# Build advanced details with collapsible section
|
|
73
|
+
detail_rows = [
|
|
74
|
+
("Application Name", html_module.escape(client_name or client_id)),
|
|
75
|
+
("Application Website", html_module.escape(client_website_url or "N/A")),
|
|
76
|
+
("Application ID", client_id),
|
|
77
|
+
("Redirect URI", redirect_uri_escaped),
|
|
78
|
+
(
|
|
79
|
+
"Requested Scopes",
|
|
80
|
+
", ".join(html_module.escape(s) for s in scopes) if scopes else "None",
|
|
81
|
+
),
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
detail_rows_html = "\n".join(
|
|
85
|
+
[
|
|
86
|
+
f"""
|
|
87
|
+
<div class="detail-row">
|
|
88
|
+
<div class="detail-label">{label}:</div>
|
|
89
|
+
<div class="detail-value">{value}</div>
|
|
90
|
+
</div>
|
|
91
|
+
"""
|
|
92
|
+
for label, value in detail_rows
|
|
93
|
+
]
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
advanced_details = f"""
|
|
97
|
+
<details>
|
|
98
|
+
<summary>Advanced Details</summary>
|
|
99
|
+
<div class="detail-box">
|
|
100
|
+
{detail_rows_html}
|
|
101
|
+
</div>
|
|
102
|
+
</details>
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
# Build form with buttons
|
|
106
|
+
# Use empty action to submit to current URL (/consent or /mcp/consent)
|
|
107
|
+
# The POST handler is registered at the same path as GET
|
|
108
|
+
form = f"""
|
|
109
|
+
<form id="consentForm" method="POST" action="">
|
|
110
|
+
<input type="hidden" name="txn_id" value="{txn_id}" />
|
|
111
|
+
<input type="hidden" name="csrf_token" value="{csrf_token}" />
|
|
112
|
+
<input type="hidden" name="submit" value="true" />
|
|
113
|
+
<div class="button-group">
|
|
114
|
+
<button type="submit" name="action" value="approve" class="btn-approve">Allow Access</button>
|
|
115
|
+
<button type="submit" name="action" value="deny" class="btn-deny">Deny</button>
|
|
116
|
+
</div>
|
|
117
|
+
</form>
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
# Build help link with tooltip (identical to current implementation)
|
|
121
|
+
help_link = """
|
|
122
|
+
<div class="help-link-container">
|
|
123
|
+
<span class="help-link">
|
|
124
|
+
Why am I seeing this?
|
|
125
|
+
<span class="tooltip">
|
|
126
|
+
This FastMCP server requires your consent to allow a new client
|
|
127
|
+
to connect. This protects you from <a
|
|
128
|
+
href="https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices#confused-deputy-problem"
|
|
129
|
+
target="_blank" class="tooltip-link">confused deputy
|
|
130
|
+
attacks</a>, where malicious clients could impersonate you
|
|
131
|
+
and steal access.<br><br>
|
|
132
|
+
<a
|
|
133
|
+
href="https://gofastmcp.com/servers/auth/oauth-proxy#confused-deputy-attacks"
|
|
134
|
+
target="_blank" class="tooltip-link">Learn more about
|
|
135
|
+
FastMCP security →</a>
|
|
136
|
+
</span>
|
|
137
|
+
</span>
|
|
138
|
+
</div>
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
# Build the page content
|
|
142
|
+
content = f"""
|
|
143
|
+
<div class="container">
|
|
144
|
+
{create_logo(icon_url=server_icon_url, alt_text=server_name or "FastMCP")}
|
|
145
|
+
<h1>Application Access Request</h1>
|
|
146
|
+
{intro_box}
|
|
147
|
+
{redirect_section}
|
|
148
|
+
{advanced_details}
|
|
149
|
+
{form}
|
|
150
|
+
</div>
|
|
151
|
+
{help_link}
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
# Additional styles needed for this page
|
|
155
|
+
additional_styles = (
|
|
156
|
+
INFO_BOX_STYLES
|
|
157
|
+
+ REDIRECT_SECTION_STYLES
|
|
158
|
+
+ DETAILS_STYLES
|
|
159
|
+
+ DETAIL_BOX_STYLES
|
|
160
|
+
+ BUTTON_STYLES
|
|
161
|
+
+ TOOLTIP_STYLES
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Determine CSP policy to use
|
|
165
|
+
# If csp_policy is None, build the default CSP policy
|
|
166
|
+
# If csp_policy is empty string, CSP will be disabled entirely in create_page
|
|
167
|
+
# If csp_policy is a non-empty string, use it as-is
|
|
168
|
+
if csp_policy is None:
|
|
169
|
+
# Need to allow form-action for form submission
|
|
170
|
+
# Chrome requires explicit scheme declarations in CSP form-action when redirect chains
|
|
171
|
+
# end in custom protocol schemes (e.g., cursor://). Parse redirect_uri to include its scheme.
|
|
172
|
+
parsed_redirect = urlparse(redirect_uri)
|
|
173
|
+
redirect_scheme = parsed_redirect.scheme.lower()
|
|
174
|
+
|
|
175
|
+
# Build form-action directive with standard schemes plus custom protocol if present
|
|
176
|
+
form_action_schemes = ["https:", "http:"]
|
|
177
|
+
if redirect_scheme and redirect_scheme not in ("http", "https"):
|
|
178
|
+
# Custom protocol scheme (e.g., cursor:, vscode:, etc.)
|
|
179
|
+
form_action_schemes.append(f"{redirect_scheme}:")
|
|
180
|
+
|
|
181
|
+
form_action_directive = " ".join(form_action_schemes)
|
|
182
|
+
csp_policy = f"default-src 'none'; style-src 'unsafe-inline'; img-src https: data:; base-uri 'none'; form-action {form_action_directive}"
|
|
183
|
+
|
|
184
|
+
return create_page(
|
|
185
|
+
content=content,
|
|
186
|
+
title=title,
|
|
187
|
+
additional_styles=additional_styles,
|
|
188
|
+
csp_policy=csp_policy,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def create_error_html(
|
|
193
|
+
error_title: str,
|
|
194
|
+
error_message: str,
|
|
195
|
+
error_details: dict[str, str] | None = None,
|
|
196
|
+
server_name: str | None = None,
|
|
197
|
+
server_icon_url: str | None = None,
|
|
198
|
+
) -> str:
|
|
199
|
+
"""Create a styled HTML error page for OAuth errors.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
error_title: The error title (e.g., "OAuth Error", "Authorization Failed")
|
|
203
|
+
error_message: The main error message to display
|
|
204
|
+
error_details: Optional dictionary of error details to show (e.g., `{"Error Code": "invalid_client"}`)
|
|
205
|
+
server_name: Optional server name to display
|
|
206
|
+
server_icon_url: Optional URL to server icon/logo
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Complete HTML page as a string
|
|
210
|
+
"""
|
|
211
|
+
import html as html_module
|
|
212
|
+
|
|
213
|
+
error_message_escaped = html_module.escape(error_message)
|
|
214
|
+
|
|
215
|
+
# Build error message box
|
|
216
|
+
error_box = f"""
|
|
217
|
+
<div class="info-box error">
|
|
218
|
+
<p>{error_message_escaped}</p>
|
|
219
|
+
</div>
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
# Build error details section if provided
|
|
223
|
+
details_section = ""
|
|
224
|
+
if error_details:
|
|
225
|
+
detail_rows_html = "\n".join(
|
|
226
|
+
[
|
|
227
|
+
f"""
|
|
228
|
+
<div class="detail-row">
|
|
229
|
+
<div class="detail-label">{html_module.escape(label)}:</div>
|
|
230
|
+
<div class="detail-value">{html_module.escape(value)}</div>
|
|
231
|
+
</div>
|
|
232
|
+
"""
|
|
233
|
+
for label, value in error_details.items()
|
|
234
|
+
]
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
details_section = f"""
|
|
238
|
+
<details>
|
|
239
|
+
<summary>Error Details</summary>
|
|
240
|
+
<div class="detail-box">
|
|
241
|
+
{detail_rows_html}
|
|
242
|
+
</div>
|
|
243
|
+
</details>
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
# Build the page content
|
|
247
|
+
content = f"""
|
|
248
|
+
<div class="container">
|
|
249
|
+
{create_logo(icon_url=server_icon_url, alt_text=server_name or "FastMCP")}
|
|
250
|
+
<h1>{html_module.escape(error_title)}</h1>
|
|
251
|
+
{error_box}
|
|
252
|
+
{details_section}
|
|
253
|
+
</div>
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
# Additional styles needed for this page
|
|
257
|
+
# Override .info-box.error to use normal text color instead of red
|
|
258
|
+
additional_styles = (
|
|
259
|
+
INFO_BOX_STYLES
|
|
260
|
+
+ DETAILS_STYLES
|
|
261
|
+
+ DETAIL_BOX_STYLES
|
|
262
|
+
+ """
|
|
263
|
+
.info-box.error {
|
|
264
|
+
color: #111827;
|
|
265
|
+
}
|
|
266
|
+
"""
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Simple CSP policy for error pages (no forms needed)
|
|
270
|
+
csp_policy = "default-src 'none'; style-src 'unsafe-inline'; img-src https: data:; base-uri 'none'"
|
|
271
|
+
|
|
272
|
+
return create_page(
|
|
273
|
+
content=content,
|
|
274
|
+
title=error_title,
|
|
275
|
+
additional_styles=additional_styles,
|
|
276
|
+
csp_policy=csp_policy,
|
|
277
|
+
)
|
|
@@ -249,8 +249,8 @@ class OIDCProxy(OAuthProxy):
|
|
|
249
249
|
redirect_path: Redirect path configured in upstream OAuth app (defaults to "/auth/callback")
|
|
250
250
|
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
|
|
251
251
|
Patterns support wildcards (e.g., "http://localhost:*", "https://*.example.com/*").
|
|
252
|
-
If None (default),
|
|
253
|
-
If empty list,
|
|
252
|
+
If None (default), all redirect URIs are allowed (for DCR compatibility).
|
|
253
|
+
If empty list, no redirect URIs are allowed.
|
|
254
254
|
These are for MCP clients performing loopback redirects, NOT for the upstream OAuth app.
|
|
255
255
|
client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
|
|
256
256
|
If None, a DiskStore will be created in the data directory (derived from `platformdirs`). The
|
|
@@ -22,44 +22,15 @@ Example:
|
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
24
|
from key_value.aio.protocols import AsyncKeyValue
|
|
25
|
-
from pydantic import AnyHttpUrl
|
|
26
|
-
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
25
|
+
from pydantic import AnyHttpUrl
|
|
27
26
|
|
|
28
27
|
from fastmcp.server.auth.oidc_proxy import OIDCProxy
|
|
29
|
-
from fastmcp.settings import ENV_FILE
|
|
30
28
|
from fastmcp.utilities.auth import parse_scopes
|
|
31
29
|
from fastmcp.utilities.logging import get_logger
|
|
32
|
-
from fastmcp.utilities.types import NotSet, NotSetT
|
|
33
30
|
|
|
34
31
|
logger = get_logger(__name__)
|
|
35
32
|
|
|
36
33
|
|
|
37
|
-
class Auth0ProviderSettings(BaseSettings):
|
|
38
|
-
"""Settings for Auth0 OIDC provider."""
|
|
39
|
-
|
|
40
|
-
model_config = SettingsConfigDict(
|
|
41
|
-
env_prefix="FASTMCP_SERVER_AUTH_AUTH0_",
|
|
42
|
-
env_file=ENV_FILE,
|
|
43
|
-
extra="ignore",
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
config_url: AnyHttpUrl | None = None
|
|
47
|
-
client_id: str | None = None
|
|
48
|
-
client_secret: SecretStr | None = None
|
|
49
|
-
audience: str | None = None
|
|
50
|
-
base_url: AnyHttpUrl | None = None
|
|
51
|
-
issuer_url: AnyHttpUrl | None = None
|
|
52
|
-
redirect_path: str | None = None
|
|
53
|
-
required_scopes: list[str] | None = None
|
|
54
|
-
allowed_client_redirect_uris: list[str] | None = None
|
|
55
|
-
jwt_signing_key: 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
34
|
class Auth0Provider(OIDCProxy):
|
|
64
35
|
"""An Auth0 provider implementation for FastMCP.
|
|
65
36
|
|
|
@@ -87,17 +58,17 @@ class Auth0Provider(OIDCProxy):
|
|
|
87
58
|
def __init__(
|
|
88
59
|
self,
|
|
89
60
|
*,
|
|
90
|
-
config_url: AnyHttpUrl | str
|
|
91
|
-
client_id: str
|
|
92
|
-
client_secret: str
|
|
93
|
-
audience: str
|
|
94
|
-
base_url: AnyHttpUrl | str
|
|
95
|
-
issuer_url: AnyHttpUrl | str |
|
|
96
|
-
required_scopes: list[str] |
|
|
97
|
-
redirect_path: str |
|
|
98
|
-
allowed_client_redirect_uris: list[str] |
|
|
61
|
+
config_url: AnyHttpUrl | str,
|
|
62
|
+
client_id: str,
|
|
63
|
+
client_secret: str,
|
|
64
|
+
audience: str,
|
|
65
|
+
base_url: AnyHttpUrl | str,
|
|
66
|
+
issuer_url: AnyHttpUrl | str | None = None,
|
|
67
|
+
required_scopes: list[str] | None = None,
|
|
68
|
+
redirect_path: str | None = None,
|
|
69
|
+
allowed_client_redirect_uris: list[str] | None = None,
|
|
99
70
|
client_storage: AsyncKeyValue | None = None,
|
|
100
|
-
jwt_signing_key: str | bytes |
|
|
71
|
+
jwt_signing_key: str | bytes | None = None,
|
|
101
72
|
require_authorization_consent: bool = True,
|
|
102
73
|
) -> None:
|
|
103
74
|
"""Initialize Auth0 OAuth provider.
|
|
@@ -125,69 +96,28 @@ class Auth0Provider(OIDCProxy):
|
|
|
125
96
|
When False, authorization proceeds directly without user confirmation.
|
|
126
97
|
SECURITY WARNING: Only disable for local development or testing environments.
|
|
127
98
|
"""
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
for k, v in {
|
|
132
|
-
"config_url": config_url,
|
|
133
|
-
"client_id": client_id,
|
|
134
|
-
"client_secret": client_secret,
|
|
135
|
-
"audience": audience,
|
|
136
|
-
"base_url": base_url,
|
|
137
|
-
"issuer_url": issuer_url,
|
|
138
|
-
"required_scopes": required_scopes,
|
|
139
|
-
"redirect_path": redirect_path,
|
|
140
|
-
"allowed_client_redirect_uris": allowed_client_redirect_uris,
|
|
141
|
-
"jwt_signing_key": jwt_signing_key,
|
|
142
|
-
}.items()
|
|
143
|
-
if v is not NotSet
|
|
144
|
-
}
|
|
99
|
+
# Parse scopes if provided as string
|
|
100
|
+
auth0_required_scopes = (
|
|
101
|
+
parse_scopes(required_scopes) if required_scopes is not None else ["openid"]
|
|
145
102
|
)
|
|
146
103
|
|
|
147
|
-
if not settings.config_url:
|
|
148
|
-
raise ValueError(
|
|
149
|
-
"config_url is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL"
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
if not settings.client_id:
|
|
153
|
-
raise ValueError(
|
|
154
|
-
"client_id is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID"
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
if not settings.client_secret:
|
|
158
|
-
raise ValueError(
|
|
159
|
-
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET"
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
if not settings.audience:
|
|
163
|
-
raise ValueError(
|
|
164
|
-
"audience is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE"
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
if not settings.base_url:
|
|
168
|
-
raise ValueError(
|
|
169
|
-
"base_url is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_BASE_URL"
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
auth0_required_scopes = settings.required_scopes or ["openid"]
|
|
173
|
-
|
|
174
104
|
super().__init__(
|
|
175
|
-
config_url=
|
|
176
|
-
client_id=
|
|
177
|
-
client_secret=
|
|
178
|
-
audience=
|
|
179
|
-
base_url=
|
|
180
|
-
issuer_url=
|
|
181
|
-
redirect_path=
|
|
105
|
+
config_url=config_url,
|
|
106
|
+
client_id=client_id,
|
|
107
|
+
client_secret=client_secret,
|
|
108
|
+
audience=audience,
|
|
109
|
+
base_url=base_url,
|
|
110
|
+
issuer_url=issuer_url,
|
|
111
|
+
redirect_path=redirect_path,
|
|
182
112
|
required_scopes=auth0_required_scopes,
|
|
183
|
-
allowed_client_redirect_uris=
|
|
113
|
+
allowed_client_redirect_uris=allowed_client_redirect_uris,
|
|
184
114
|
client_storage=client_storage,
|
|
185
|
-
jwt_signing_key=
|
|
115
|
+
jwt_signing_key=jwt_signing_key,
|
|
186
116
|
require_authorization_consent=require_authorization_consent,
|
|
187
117
|
)
|
|
188
118
|
|
|
189
119
|
logger.debug(
|
|
190
120
|
"Initialized Auth0 OAuth provider for client %s with scopes: %s",
|
|
191
|
-
|
|
121
|
+
client_id,
|
|
192
122
|
auth0_required_scopes,
|
|
193
123
|
)
|
|
@@ -24,47 +24,18 @@ Example:
|
|
|
24
24
|
from __future__ import annotations
|
|
25
25
|
|
|
26
26
|
from key_value.aio.protocols import AsyncKeyValue
|
|
27
|
-
from pydantic import AnyHttpUrl
|
|
28
|
-
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
27
|
+
from pydantic import AnyHttpUrl
|
|
29
28
|
|
|
30
29
|
from fastmcp.server.auth import TokenVerifier
|
|
31
30
|
from fastmcp.server.auth.auth import AccessToken
|
|
32
31
|
from fastmcp.server.auth.oidc_proxy import OIDCProxy
|
|
33
32
|
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
|
34
|
-
from fastmcp.settings import ENV_FILE
|
|
35
33
|
from fastmcp.utilities.auth import parse_scopes
|
|
36
34
|
from fastmcp.utilities.logging import get_logger
|
|
37
|
-
from fastmcp.utilities.types import NotSet, NotSetT
|
|
38
35
|
|
|
39
36
|
logger = get_logger(__name__)
|
|
40
37
|
|
|
41
38
|
|
|
42
|
-
class AWSCognitoProviderSettings(BaseSettings):
|
|
43
|
-
"""Settings for AWS Cognito OAuth provider."""
|
|
44
|
-
|
|
45
|
-
model_config = SettingsConfigDict(
|
|
46
|
-
env_prefix="FASTMCP_SERVER_AUTH_AWS_COGNITO_",
|
|
47
|
-
env_file=ENV_FILE,
|
|
48
|
-
extra="ignore",
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
user_pool_id: str | None = None
|
|
52
|
-
aws_region: str | None = None
|
|
53
|
-
client_id: str | None = None
|
|
54
|
-
client_secret: SecretStr | None = None
|
|
55
|
-
base_url: AnyHttpUrl | str | None = None
|
|
56
|
-
issuer_url: AnyHttpUrl | str | None = None
|
|
57
|
-
redirect_path: str | None = None
|
|
58
|
-
required_scopes: list[str] | 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
39
|
class AWSCognitoTokenVerifier(JWTVerifier):
|
|
69
40
|
"""Token verifier that filters claims to Cognito-specific subset."""
|
|
70
41
|
|
|
@@ -126,27 +97,27 @@ class AWSCognitoProvider(OIDCProxy):
|
|
|
126
97
|
def __init__(
|
|
127
98
|
self,
|
|
128
99
|
*,
|
|
129
|
-
user_pool_id: str
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
issuer_url: AnyHttpUrl | str |
|
|
135
|
-
redirect_path: str
|
|
136
|
-
required_scopes: list[str] |
|
|
137
|
-
allowed_client_redirect_uris: list[str] |
|
|
100
|
+
user_pool_id: str,
|
|
101
|
+
client_id: str,
|
|
102
|
+
client_secret: str,
|
|
103
|
+
base_url: AnyHttpUrl | str,
|
|
104
|
+
aws_region: str = "eu-central-1",
|
|
105
|
+
issuer_url: AnyHttpUrl | str | None = None,
|
|
106
|
+
redirect_path: str = "/auth/callback",
|
|
107
|
+
required_scopes: list[str] | None = None,
|
|
108
|
+
allowed_client_redirect_uris: list[str] | None = None,
|
|
138
109
|
client_storage: AsyncKeyValue | None = None,
|
|
139
|
-
jwt_signing_key: str | bytes |
|
|
110
|
+
jwt_signing_key: str | bytes | None = None,
|
|
140
111
|
require_authorization_consent: bool = True,
|
|
141
112
|
):
|
|
142
113
|
"""Initialize AWS Cognito OAuth provider.
|
|
143
114
|
|
|
144
115
|
Args:
|
|
145
116
|
user_pool_id: Your Cognito User Pool ID (e.g., "eu-central-1_XXXXXXXXX")
|
|
146
|
-
aws_region: AWS region where your User Pool is located (defaults to "eu-central-1")
|
|
147
117
|
client_id: Cognito app client ID
|
|
148
118
|
client_secret: Cognito app client secret
|
|
149
119
|
base_url: Public URL where OAuth endpoints will be accessible (includes any mount path)
|
|
120
|
+
aws_region: AWS region where your User Pool is located (defaults to "eu-central-1")
|
|
150
121
|
issuer_url: Issuer URL for OAuth metadata (defaults to base_url). Use root-level URL
|
|
151
122
|
to avoid 404s during discovery when mounting under a path.
|
|
152
123
|
redirect_path: Redirect path configured in Cognito app (defaults to "/auth/callback")
|
|
@@ -164,77 +135,37 @@ class AWSCognitoProvider(OIDCProxy):
|
|
|
164
135
|
When False, authorization proceeds directly without user confirmation.
|
|
165
136
|
SECURITY WARNING: Only disable for local development or testing environments.
|
|
166
137
|
"""
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
k: v
|
|
171
|
-
for k, v in {
|
|
172
|
-
"user_pool_id": user_pool_id,
|
|
173
|
-
"aws_region": aws_region,
|
|
174
|
-
"client_id": client_id,
|
|
175
|
-
"client_secret": client_secret,
|
|
176
|
-
"base_url": base_url,
|
|
177
|
-
"issuer_url": issuer_url,
|
|
178
|
-
"redirect_path": redirect_path,
|
|
179
|
-
"required_scopes": required_scopes,
|
|
180
|
-
"allowed_client_redirect_uris": allowed_client_redirect_uris,
|
|
181
|
-
"jwt_signing_key": jwt_signing_key,
|
|
182
|
-
}.items()
|
|
183
|
-
if v is not NotSet
|
|
184
|
-
}
|
|
138
|
+
# Parse scopes if provided as string
|
|
139
|
+
required_scopes_final = (
|
|
140
|
+
parse_scopes(required_scopes) if required_scopes is not None else ["openid"]
|
|
185
141
|
)
|
|
186
142
|
|
|
187
|
-
# Validate required settings
|
|
188
|
-
if not settings.user_pool_id:
|
|
189
|
-
raise ValueError(
|
|
190
|
-
"user_pool_id is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID"
|
|
191
|
-
)
|
|
192
|
-
if not settings.client_id:
|
|
193
|
-
raise ValueError(
|
|
194
|
-
"client_id is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID"
|
|
195
|
-
)
|
|
196
|
-
if not settings.client_secret:
|
|
197
|
-
raise ValueError(
|
|
198
|
-
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET"
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
# Apply defaults
|
|
202
|
-
required_scopes_final = settings.required_scopes or ["openid"]
|
|
203
|
-
allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
|
|
204
|
-
aws_region_final = settings.aws_region or "eu-central-1"
|
|
205
|
-
redirect_path_final = settings.redirect_path or "/auth/callback"
|
|
206
|
-
|
|
207
143
|
# Construct OIDC discovery URL
|
|
208
|
-
config_url = f"https://cognito-idp.{
|
|
209
|
-
|
|
210
|
-
# Extract secret string from SecretStr
|
|
211
|
-
client_secret_str = (
|
|
212
|
-
settings.client_secret.get_secret_value() if settings.client_secret else ""
|
|
213
|
-
)
|
|
144
|
+
config_url = f"https://cognito-idp.{aws_region}.amazonaws.com/{user_pool_id}/.well-known/openid-configuration"
|
|
214
145
|
|
|
215
146
|
# Store Cognito-specific info for claim filtering
|
|
216
|
-
self.user_pool_id =
|
|
217
|
-
self.aws_region =
|
|
147
|
+
self.user_pool_id = user_pool_id
|
|
148
|
+
self.aws_region = aws_region
|
|
218
149
|
|
|
219
150
|
# Initialize OIDC proxy with Cognito discovery
|
|
220
151
|
super().__init__(
|
|
221
152
|
config_url=config_url,
|
|
222
|
-
client_id=
|
|
223
|
-
client_secret=
|
|
153
|
+
client_id=client_id,
|
|
154
|
+
client_secret=client_secret,
|
|
224
155
|
algorithm="RS256",
|
|
225
156
|
required_scopes=required_scopes_final,
|
|
226
|
-
base_url=
|
|
227
|
-
issuer_url=
|
|
228
|
-
redirect_path=
|
|
229
|
-
allowed_client_redirect_uris=
|
|
157
|
+
base_url=base_url,
|
|
158
|
+
issuer_url=issuer_url,
|
|
159
|
+
redirect_path=redirect_path,
|
|
160
|
+
allowed_client_redirect_uris=allowed_client_redirect_uris,
|
|
230
161
|
client_storage=client_storage,
|
|
231
|
-
jwt_signing_key=
|
|
162
|
+
jwt_signing_key=jwt_signing_key,
|
|
232
163
|
require_authorization_consent=require_authorization_consent,
|
|
233
164
|
)
|
|
234
165
|
|
|
235
166
|
logger.debug(
|
|
236
167
|
"Initialized AWS Cognito OAuth provider for client %s with scopes: %s",
|
|
237
|
-
|
|
168
|
+
client_id,
|
|
238
169
|
required_scopes_final,
|
|
239
170
|
)
|
|
240
171
|
|