fastmcp 2.12.4__py3-none-any.whl → 2.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastmcp/cli/cli.py +7 -6
- fastmcp/cli/install/claude_code.py +6 -6
- fastmcp/cli/install/claude_desktop.py +3 -3
- fastmcp/cli/install/cursor.py +7 -7
- fastmcp/cli/install/gemini_cli.py +3 -3
- fastmcp/cli/install/mcp_json.py +3 -3
- fastmcp/cli/run.py +13 -8
- fastmcp/client/auth/oauth.py +100 -208
- fastmcp/client/client.py +11 -11
- fastmcp/client/logging.py +18 -14
- fastmcp/client/oauth_callback.py +85 -171
- fastmcp/client/transports.py +77 -22
- fastmcp/contrib/component_manager/component_service.py +6 -6
- fastmcp/contrib/mcp_mixin/README.md +32 -1
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +14 -2
- fastmcp/experimental/utilities/openapi/json_schema_converter.py +4 -0
- fastmcp/experimental/utilities/openapi/parser.py +23 -3
- fastmcp/prompts/prompt.py +13 -6
- fastmcp/prompts/prompt_manager.py +16 -101
- fastmcp/resources/resource.py +13 -6
- fastmcp/resources/resource_manager.py +5 -164
- fastmcp/resources/template.py +107 -17
- fastmcp/resources/types.py +30 -24
- fastmcp/server/auth/auth.py +40 -32
- fastmcp/server/auth/handlers/authorize.py +324 -0
- fastmcp/server/auth/jwt_issuer.py +236 -0
- fastmcp/server/auth/middleware.py +96 -0
- fastmcp/server/auth/oauth_proxy.py +1256 -242
- fastmcp/server/auth/oidc_proxy.py +23 -6
- fastmcp/server/auth/providers/auth0.py +40 -21
- fastmcp/server/auth/providers/aws.py +29 -3
- fastmcp/server/auth/providers/azure.py +178 -127
- fastmcp/server/auth/providers/descope.py +4 -6
- fastmcp/server/auth/providers/github.py +29 -8
- fastmcp/server/auth/providers/google.py +30 -9
- fastmcp/server/auth/providers/introspection.py +281 -0
- fastmcp/server/auth/providers/jwt.py +8 -2
- fastmcp/server/auth/providers/scalekit.py +179 -0
- fastmcp/server/auth/providers/supabase.py +172 -0
- fastmcp/server/auth/providers/workos.py +32 -14
- fastmcp/server/context.py +122 -36
- fastmcp/server/http.py +58 -18
- fastmcp/server/low_level.py +121 -2
- fastmcp/server/middleware/caching.py +469 -0
- fastmcp/server/middleware/error_handling.py +6 -2
- fastmcp/server/middleware/logging.py +48 -37
- fastmcp/server/middleware/middleware.py +28 -15
- fastmcp/server/middleware/rate_limiting.py +3 -3
- fastmcp/server/middleware/tool_injection.py +116 -0
- fastmcp/server/proxy.py +6 -6
- fastmcp/server/server.py +683 -207
- fastmcp/settings.py +24 -10
- fastmcp/tools/tool.py +7 -3
- fastmcp/tools/tool_manager.py +30 -112
- fastmcp/tools/tool_transform.py +3 -3
- fastmcp/utilities/cli.py +62 -22
- fastmcp/utilities/components.py +5 -0
- fastmcp/utilities/inspect.py +77 -21
- fastmcp/utilities/logging.py +118 -8
- fastmcp/utilities/mcp_server_config/v1/environments/uv.py +6 -6
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -3
- fastmcp/utilities/mcp_server_config/v1/schema.json +3 -0
- fastmcp/utilities/tests.py +87 -4
- fastmcp/utilities/types.py +1 -1
- fastmcp/utilities/ui.py +617 -0
- {fastmcp-2.12.4.dist-info → fastmcp-2.13.0.dist-info}/METADATA +10 -6
- {fastmcp-2.12.4.dist-info → fastmcp-2.13.0.dist-info}/RECORD +70 -63
- fastmcp/cli/claude.py +0 -135
- fastmcp/utilities/storage.py +0 -204
- {fastmcp-2.12.4.dist-info → fastmcp-2.13.0.dist-info}/WHEEL +0 -0
- {fastmcp-2.12.4.dist-info → fastmcp-2.13.0.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.12.4.dist-info → fastmcp-2.13.0.dist-info}/licenses/LICENSE +0 -0
fastmcp/utilities/ui.py
ADDED
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared UI utilities for FastMCP HTML pages.
|
|
3
|
+
|
|
4
|
+
This module provides reusable HTML/CSS components for OAuth callbacks,
|
|
5
|
+
consent pages, and other user-facing interfaces.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import html
|
|
11
|
+
|
|
12
|
+
from starlette.responses import HTMLResponse
|
|
13
|
+
|
|
14
|
+
# FastMCP branding
|
|
15
|
+
FASTMCP_LOGO_URL = "https://gofastmcp.com/assets/brand/blue-logo.png"
|
|
16
|
+
|
|
17
|
+
# Base CSS styles shared across all FastMCP pages
|
|
18
|
+
BASE_STYLES = """
|
|
19
|
+
* {
|
|
20
|
+
margin: 0;
|
|
21
|
+
padding: 0;
|
|
22
|
+
box-sizing: border-box;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
body {
|
|
26
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
27
|
+
margin: 0;
|
|
28
|
+
padding: 0;
|
|
29
|
+
min-height: 100vh;
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
justify-content: center;
|
|
33
|
+
background: #f9fafb;
|
|
34
|
+
color: #0a0a0a;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.container {
|
|
38
|
+
background: #ffffff;
|
|
39
|
+
border: 1px solid #e5e7eb;
|
|
40
|
+
padding: 3rem 2.5rem;
|
|
41
|
+
border-radius: 1rem;
|
|
42
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
43
|
+
text-align: center;
|
|
44
|
+
max-width: 36rem;
|
|
45
|
+
margin: 1rem;
|
|
46
|
+
width: 100%;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@media (max-width: 640px) {
|
|
50
|
+
.container {
|
|
51
|
+
padding: 2rem 1.5rem;
|
|
52
|
+
margin: 0.5rem;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.logo {
|
|
57
|
+
width: 64px;
|
|
58
|
+
height: auto;
|
|
59
|
+
margin-bottom: 1.5rem;
|
|
60
|
+
display: block;
|
|
61
|
+
margin-left: auto;
|
|
62
|
+
margin-right: auto;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
h1 {
|
|
66
|
+
font-size: 1.5rem;
|
|
67
|
+
font-weight: 600;
|
|
68
|
+
margin-bottom: 1.5rem;
|
|
69
|
+
color: #111827;
|
|
70
|
+
}
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
# Button styles
|
|
74
|
+
BUTTON_STYLES = """
|
|
75
|
+
.button-group {
|
|
76
|
+
display: flex;
|
|
77
|
+
gap: 0.75rem;
|
|
78
|
+
margin-top: 1.5rem;
|
|
79
|
+
justify-content: center;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
button {
|
|
83
|
+
padding: 0.75rem 2rem;
|
|
84
|
+
font-size: 0.9375rem;
|
|
85
|
+
font-weight: 500;
|
|
86
|
+
border-radius: 0.5rem;
|
|
87
|
+
border: none;
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
transition: all 0.15s;
|
|
90
|
+
font-family: inherit;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
button:hover {
|
|
94
|
+
transform: translateY(-1px);
|
|
95
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.btn-approve, .btn-primary {
|
|
99
|
+
background: #10b981;
|
|
100
|
+
color: #ffffff;
|
|
101
|
+
min-width: 120px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.btn-deny, .btn-secondary {
|
|
105
|
+
background: #6b7280;
|
|
106
|
+
color: #ffffff;
|
|
107
|
+
min-width: 120px;
|
|
108
|
+
}
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
# Info box / message box styles
|
|
112
|
+
INFO_BOX_STYLES = """
|
|
113
|
+
.info-box {
|
|
114
|
+
background: #f0f9ff;
|
|
115
|
+
border: 1px solid #bae6fd;
|
|
116
|
+
border-radius: 0.5rem;
|
|
117
|
+
padding: 1rem;
|
|
118
|
+
margin-bottom: 1.5rem;
|
|
119
|
+
text-align: left;
|
|
120
|
+
font-size: 0.9375rem;
|
|
121
|
+
line-height: 1.5;
|
|
122
|
+
color: #374151;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.info-box p {
|
|
126
|
+
margin-bottom: 0.5rem;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.info-box p:last-child {
|
|
130
|
+
margin-bottom: 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.info-box.centered {
|
|
134
|
+
text-align: center;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.info-box.error {
|
|
138
|
+
background: #fef2f2;
|
|
139
|
+
border-color: #fecaca;
|
|
140
|
+
color: #991b1b;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.info-box strong {
|
|
144
|
+
color: #0ea5e9;
|
|
145
|
+
font-weight: 600;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.info-box .server-name-link {
|
|
149
|
+
color: #0ea5e9;
|
|
150
|
+
text-decoration: underline;
|
|
151
|
+
font-weight: 600;
|
|
152
|
+
cursor: pointer;
|
|
153
|
+
transition: opacity 0.15s;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.info-box .server-name-link:hover {
|
|
157
|
+
opacity: 0.8;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Monospace info box - gray styling with code font */
|
|
161
|
+
.info-box-mono {
|
|
162
|
+
background: #f9fafb;
|
|
163
|
+
border: 1px solid #e5e7eb;
|
|
164
|
+
border-radius: 0.5rem;
|
|
165
|
+
padding: 0.875rem;
|
|
166
|
+
margin: 1.25rem 0;
|
|
167
|
+
font-size: 0.875rem;
|
|
168
|
+
color: #6b7280;
|
|
169
|
+
font-family: 'SF Mono', 'Monaco', 'Consolas', 'Courier New', monospace;
|
|
170
|
+
text-align: left;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.info-box-mono.centered {
|
|
174
|
+
text-align: center;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.info-box-mono.error {
|
|
178
|
+
background: #fef2f2;
|
|
179
|
+
border-color: #fecaca;
|
|
180
|
+
color: #991b1b;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.info-box-mono strong {
|
|
184
|
+
color: #111827;
|
|
185
|
+
font-weight: 600;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.warning-box {
|
|
189
|
+
background: #f0f9ff;
|
|
190
|
+
border: 1px solid #bae6fd;
|
|
191
|
+
border-radius: 0.5rem;
|
|
192
|
+
padding: 1rem;
|
|
193
|
+
margin-bottom: 1.5rem;
|
|
194
|
+
text-align: center;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.warning-box p {
|
|
198
|
+
margin-bottom: 0.5rem;
|
|
199
|
+
line-height: 1.5;
|
|
200
|
+
color: #6b7280;
|
|
201
|
+
font-size: 0.9375rem;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.warning-box p:last-child {
|
|
205
|
+
margin-bottom: 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.warning-box strong {
|
|
209
|
+
color: #0ea5e9;
|
|
210
|
+
font-weight: 600;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.warning-box a {
|
|
214
|
+
color: #0ea5e9;
|
|
215
|
+
text-decoration: underline;
|
|
216
|
+
font-weight: 600;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.warning-box a:hover {
|
|
220
|
+
color: #0284c7;
|
|
221
|
+
text-decoration: underline;
|
|
222
|
+
}
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
# Status message styles (for success/error indicators)
|
|
226
|
+
STATUS_MESSAGE_STYLES = """
|
|
227
|
+
.status-message {
|
|
228
|
+
display: flex;
|
|
229
|
+
align-items: center;
|
|
230
|
+
justify-content: center;
|
|
231
|
+
gap: 0.75rem;
|
|
232
|
+
margin-bottom: 1.5rem;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.status-icon {
|
|
236
|
+
font-size: 1.5rem;
|
|
237
|
+
line-height: 1;
|
|
238
|
+
display: inline-flex;
|
|
239
|
+
align-items: center;
|
|
240
|
+
justify-content: center;
|
|
241
|
+
width: 2rem;
|
|
242
|
+
height: 2rem;
|
|
243
|
+
border-radius: 0.5rem;
|
|
244
|
+
flex-shrink: 0;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.status-icon.success {
|
|
248
|
+
background: #10b98120;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.status-icon.error {
|
|
252
|
+
background: #ef444420;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.message {
|
|
256
|
+
font-size: 1.125rem;
|
|
257
|
+
line-height: 1.75;
|
|
258
|
+
color: #111827;
|
|
259
|
+
font-weight: 600;
|
|
260
|
+
text-align: left;
|
|
261
|
+
}
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
# Detail box styles (for key-value pairs)
|
|
265
|
+
DETAIL_BOX_STYLES = """
|
|
266
|
+
.detail-box {
|
|
267
|
+
background: #f9fafb;
|
|
268
|
+
border: 1px solid #e5e7eb;
|
|
269
|
+
border-radius: 0.5rem;
|
|
270
|
+
padding: 1rem;
|
|
271
|
+
margin-bottom: 1.5rem;
|
|
272
|
+
text-align: left;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.detail-row {
|
|
276
|
+
display: flex;
|
|
277
|
+
padding: 0.5rem 0;
|
|
278
|
+
border-bottom: 1px solid #e5e7eb;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.detail-row:last-child {
|
|
282
|
+
border-bottom: none;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.detail-label {
|
|
286
|
+
font-weight: 600;
|
|
287
|
+
min-width: 160px;
|
|
288
|
+
color: #6b7280;
|
|
289
|
+
font-size: 0.875rem;
|
|
290
|
+
flex-shrink: 0;
|
|
291
|
+
padding-right: 1rem;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.detail-value {
|
|
295
|
+
flex: 1;
|
|
296
|
+
font-family: 'SF Mono', 'Monaco', 'Consolas', 'Courier New', monospace;
|
|
297
|
+
font-size: 0.75rem;
|
|
298
|
+
color: #111827;
|
|
299
|
+
word-break: break-all;
|
|
300
|
+
overflow-wrap: break-word;
|
|
301
|
+
}
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
# Redirect section styles (for OAuth redirect URI box)
|
|
305
|
+
REDIRECT_SECTION_STYLES = """
|
|
306
|
+
.redirect-section {
|
|
307
|
+
background: #fffbeb;
|
|
308
|
+
border: 1px solid #fcd34d;
|
|
309
|
+
border-radius: 0.5rem;
|
|
310
|
+
padding: 1rem;
|
|
311
|
+
margin-bottom: 1.5rem;
|
|
312
|
+
text-align: left;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.redirect-section .label {
|
|
316
|
+
font-size: 0.875rem;
|
|
317
|
+
color: #6b7280;
|
|
318
|
+
font-weight: 600;
|
|
319
|
+
margin-bottom: 0.5rem;
|
|
320
|
+
display: block;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.redirect-section .value {
|
|
324
|
+
font-family: 'SF Mono', 'Monaco', 'Consolas', 'Courier New', monospace;
|
|
325
|
+
font-size: 0.875rem;
|
|
326
|
+
color: #111827;
|
|
327
|
+
word-break: break-all;
|
|
328
|
+
margin-top: 0.25rem;
|
|
329
|
+
}
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
# Collapsible details styles
|
|
333
|
+
DETAILS_STYLES = """
|
|
334
|
+
details {
|
|
335
|
+
margin-bottom: 1.5rem;
|
|
336
|
+
text-align: left;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
summary {
|
|
340
|
+
cursor: pointer;
|
|
341
|
+
font-size: 0.875rem;
|
|
342
|
+
color: #6b7280;
|
|
343
|
+
font-weight: 600;
|
|
344
|
+
list-style: none;
|
|
345
|
+
padding: 0.5rem;
|
|
346
|
+
border-radius: 0.25rem;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
summary:hover {
|
|
350
|
+
background: #f9fafb;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
summary::marker {
|
|
354
|
+
display: none;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
summary::before {
|
|
358
|
+
content: "▶";
|
|
359
|
+
display: inline-block;
|
|
360
|
+
margin-right: 0.5rem;
|
|
361
|
+
transition: transform 0.2s;
|
|
362
|
+
font-size: 0.75rem;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
details[open] summary::before {
|
|
366
|
+
transform: rotate(90deg);
|
|
367
|
+
}
|
|
368
|
+
"""
|
|
369
|
+
|
|
370
|
+
# Helper text styles
|
|
371
|
+
HELPER_TEXT_STYLES = """
|
|
372
|
+
.close-instruction, .help-text {
|
|
373
|
+
font-size: 0.875rem;
|
|
374
|
+
color: #6b7280;
|
|
375
|
+
margin-top: 1.5rem;
|
|
376
|
+
}
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
# Tooltip styles for hover help
|
|
380
|
+
TOOLTIP_STYLES = """
|
|
381
|
+
.help-link-container {
|
|
382
|
+
position: fixed;
|
|
383
|
+
bottom: 1.5rem;
|
|
384
|
+
right: 1.5rem;
|
|
385
|
+
font-size: 0.875rem;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.help-link {
|
|
389
|
+
color: #6b7280;
|
|
390
|
+
text-decoration: none;
|
|
391
|
+
cursor: help;
|
|
392
|
+
position: relative;
|
|
393
|
+
display: inline-block;
|
|
394
|
+
border-bottom: 1px dotted #9ca3af;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
@media (max-width: 640px) {
|
|
398
|
+
.help-link {
|
|
399
|
+
background: #ffffff;
|
|
400
|
+
padding: 0.25rem 0.5rem;
|
|
401
|
+
border-radius: 0.25rem;
|
|
402
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.help-link:hover {
|
|
407
|
+
color: #111827;
|
|
408
|
+
border-bottom-color: #111827;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.help-link:hover .tooltip {
|
|
412
|
+
opacity: 1;
|
|
413
|
+
visibility: visible;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.tooltip {
|
|
417
|
+
position: absolute;
|
|
418
|
+
bottom: 100%;
|
|
419
|
+
right: 0;
|
|
420
|
+
left: auto;
|
|
421
|
+
margin-bottom: 0.5rem;
|
|
422
|
+
background: #1f2937;
|
|
423
|
+
color: #ffffff;
|
|
424
|
+
padding: 0.75rem 1rem;
|
|
425
|
+
border-radius: 0.5rem;
|
|
426
|
+
font-size: 0.8125rem;
|
|
427
|
+
line-height: 1.5;
|
|
428
|
+
width: 280px;
|
|
429
|
+
max-width: calc(100vw - 3rem);
|
|
430
|
+
opacity: 0;
|
|
431
|
+
visibility: hidden;
|
|
432
|
+
transition: opacity 0.2s, visibility 0.2s;
|
|
433
|
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
434
|
+
text-align: left;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.tooltip::after {
|
|
438
|
+
content: '';
|
|
439
|
+
position: absolute;
|
|
440
|
+
top: 100%;
|
|
441
|
+
right: 1rem;
|
|
442
|
+
border: 6px solid transparent;
|
|
443
|
+
border-top-color: #1f2937;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.tooltip-link {
|
|
447
|
+
color: #60a5fa;
|
|
448
|
+
text-decoration: underline;
|
|
449
|
+
}
|
|
450
|
+
"""
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def create_page(
|
|
454
|
+
content: str,
|
|
455
|
+
title: str = "FastMCP",
|
|
456
|
+
additional_styles: str = "",
|
|
457
|
+
csp_policy: str = "default-src 'none'; style-src 'unsafe-inline'; img-src https:; base-uri 'none'",
|
|
458
|
+
) -> str:
|
|
459
|
+
"""
|
|
460
|
+
Create a complete HTML page with FastMCP styling.
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
content: HTML content to place inside the page
|
|
464
|
+
title: Page title
|
|
465
|
+
additional_styles: Extra CSS to include
|
|
466
|
+
csp_policy: Content Security Policy header value
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
Complete HTML page as string
|
|
470
|
+
"""
|
|
471
|
+
title = html.escape(title)
|
|
472
|
+
return f"""
|
|
473
|
+
<!DOCTYPE html>
|
|
474
|
+
<html lang="en">
|
|
475
|
+
<head>
|
|
476
|
+
<meta charset="UTF-8">
|
|
477
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
478
|
+
<title>{title}</title>
|
|
479
|
+
<style>
|
|
480
|
+
{BASE_STYLES}
|
|
481
|
+
{additional_styles}
|
|
482
|
+
</style>
|
|
483
|
+
<meta http-equiv="Content-Security-Policy" content="{csp_policy}" />
|
|
484
|
+
</head>
|
|
485
|
+
<body>
|
|
486
|
+
{content}
|
|
487
|
+
</body>
|
|
488
|
+
</html>
|
|
489
|
+
"""
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def create_logo(icon_url: str | None = None, alt_text: str = "FastMCP") -> str:
|
|
493
|
+
"""Create logo HTML.
|
|
494
|
+
|
|
495
|
+
Args:
|
|
496
|
+
icon_url: Optional custom icon URL. If not provided, uses the FastMCP logo.
|
|
497
|
+
alt_text: Alt text for the logo image.
|
|
498
|
+
|
|
499
|
+
Returns:
|
|
500
|
+
HTML for logo image tag.
|
|
501
|
+
"""
|
|
502
|
+
url = icon_url or FASTMCP_LOGO_URL
|
|
503
|
+
alt = html.escape(alt_text)
|
|
504
|
+
return f'<img src="{html.escape(url)}" alt="{alt}" class="logo" />'
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def create_status_message(message: str, is_success: bool = True) -> str:
|
|
508
|
+
"""
|
|
509
|
+
Create a status message with icon.
|
|
510
|
+
|
|
511
|
+
Args:
|
|
512
|
+
message: Status message text
|
|
513
|
+
is_success: True for success (✓), False for error (✕)
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
HTML for status message
|
|
517
|
+
"""
|
|
518
|
+
message = html.escape(message)
|
|
519
|
+
icon = "✓" if is_success else "✕"
|
|
520
|
+
icon_class = "success" if is_success else "error"
|
|
521
|
+
|
|
522
|
+
return f"""
|
|
523
|
+
<div class="status-message">
|
|
524
|
+
<span class="status-icon {icon_class}">{icon}</span>
|
|
525
|
+
<div class="message">{message}</div>
|
|
526
|
+
</div>
|
|
527
|
+
"""
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def create_info_box(
|
|
531
|
+
content: str,
|
|
532
|
+
is_error: bool = False,
|
|
533
|
+
centered: bool = False,
|
|
534
|
+
monospace: bool = False,
|
|
535
|
+
) -> str:
|
|
536
|
+
"""
|
|
537
|
+
Create an info box.
|
|
538
|
+
|
|
539
|
+
Args:
|
|
540
|
+
content: HTML content for the info box
|
|
541
|
+
is_error: True for error styling, False for normal
|
|
542
|
+
centered: True to center the text, False for left-aligned
|
|
543
|
+
monospace: True to use gray monospace font styling instead of blue
|
|
544
|
+
|
|
545
|
+
Returns:
|
|
546
|
+
HTML for info box
|
|
547
|
+
"""
|
|
548
|
+
content = html.escape(content)
|
|
549
|
+
base_class = "info-box-mono" if monospace else "info-box"
|
|
550
|
+
classes = [base_class]
|
|
551
|
+
if is_error:
|
|
552
|
+
classes.append("error")
|
|
553
|
+
if centered:
|
|
554
|
+
classes.append("centered")
|
|
555
|
+
class_str = " ".join(classes)
|
|
556
|
+
return f'<div class="{class_str}">{content}</div>'
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def create_detail_box(rows: list[tuple[str, str]]) -> str:
|
|
560
|
+
"""
|
|
561
|
+
Create a detail box with key-value pairs.
|
|
562
|
+
|
|
563
|
+
Args:
|
|
564
|
+
rows: List of (label, value) tuples
|
|
565
|
+
|
|
566
|
+
Returns:
|
|
567
|
+
HTML for detail box
|
|
568
|
+
"""
|
|
569
|
+
rows_html = "\n".join(
|
|
570
|
+
f"""
|
|
571
|
+
<div class="detail-row">
|
|
572
|
+
<div class="detail-label">{html.escape(label)}:</div>
|
|
573
|
+
<div class="detail-value">{html.escape(value)}</div>
|
|
574
|
+
</div>
|
|
575
|
+
"""
|
|
576
|
+
for label, value in rows
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
return f'<div class="detail-box">{rows_html}</div>'
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def create_button_group(buttons: list[tuple[str, str, str]]) -> str:
|
|
583
|
+
"""
|
|
584
|
+
Create a group of buttons.
|
|
585
|
+
|
|
586
|
+
Args:
|
|
587
|
+
buttons: List of (text, value, css_class) tuples
|
|
588
|
+
|
|
589
|
+
Returns:
|
|
590
|
+
HTML for button group
|
|
591
|
+
"""
|
|
592
|
+
buttons_html = "\n".join(
|
|
593
|
+
f'<button type="submit" name="action" value="{value}" class="{css_class}">{text}</button>'
|
|
594
|
+
for text, value, css_class in buttons
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
return f'<div class="button-group">{buttons_html}</div>'
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def create_secure_html_response(html: str, status_code: int = 200) -> HTMLResponse:
|
|
601
|
+
"""
|
|
602
|
+
Create an HTMLResponse with security headers.
|
|
603
|
+
|
|
604
|
+
Adds X-Frame-Options: DENY to prevent clickjacking attacks per MCP security best practices.
|
|
605
|
+
|
|
606
|
+
Args:
|
|
607
|
+
html: HTML content to return
|
|
608
|
+
status_code: HTTP status code
|
|
609
|
+
|
|
610
|
+
Returns:
|
|
611
|
+
HTMLResponse with security headers
|
|
612
|
+
"""
|
|
613
|
+
return HTMLResponse(
|
|
614
|
+
content=html,
|
|
615
|
+
status_code=status_code,
|
|
616
|
+
headers={"X-Frame-Options": "DENY"},
|
|
617
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmcp
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.13.0
|
|
4
4
|
Summary: The fast, Pythonic way to build MCP servers and clients.
|
|
5
5
|
Project-URL: Homepage, https://gofastmcp.com
|
|
6
6
|
Project-URL: Repository, https://github.com/jlowin/fastmcp
|
|
@@ -14,6 +14,7 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
18
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
19
|
Classifier: Typing :: Typed
|
|
19
20
|
Requires-Python: >=3.10
|
|
@@ -21,17 +22,18 @@ Requires-Dist: authlib>=1.5.2
|
|
|
21
22
|
Requires-Dist: cyclopts>=3.0.0
|
|
22
23
|
Requires-Dist: exceptiongroup>=1.2.2
|
|
23
24
|
Requires-Dist: httpx>=0.28.1
|
|
24
|
-
Requires-Dist: mcp<2.0.0,>=1.
|
|
25
|
+
Requires-Dist: mcp<2.0.0,>=1.17.0
|
|
25
26
|
Requires-Dist: openapi-core>=0.19.5
|
|
26
27
|
Requires-Dist: openapi-pydantic>=0.5.1
|
|
28
|
+
Requires-Dist: platformdirs>=4.0.0
|
|
29
|
+
Requires-Dist: py-key-value-aio[disk,keyring,memory]<0.3.0,>=0.2.6
|
|
27
30
|
Requires-Dist: pydantic[email]>=2.11.7
|
|
28
31
|
Requires-Dist: pyperclip>=1.9.0
|
|
29
32
|
Requires-Dist: python-dotenv>=1.1.0
|
|
30
33
|
Requires-Dist: rich>=13.9.4
|
|
34
|
+
Requires-Dist: websockets>=15.0.1
|
|
31
35
|
Provides-Extra: openai
|
|
32
36
|
Requires-Dist: openai>=1.102.0; extra == 'openai'
|
|
33
|
-
Provides-Extra: websockets
|
|
34
|
-
Requires-Dist: websockets>=15.0.1; extra == 'websockets'
|
|
35
37
|
Description-Content-Type: text/markdown
|
|
36
38
|
|
|
37
39
|
<div align="center">
|
|
@@ -51,6 +53,7 @@ Description-Content-Type: text/markdown
|
|
|
51
53
|
*Made with ☕️ by [Prefect](https://www.prefect.io/)*
|
|
52
54
|
|
|
53
55
|
[](https://gofastmcp.com)
|
|
56
|
+
[](https://discord.gg/uu8dJCgttd)
|
|
54
57
|
[](https://pypi.org/project/fastmcp)
|
|
55
58
|
[](https://github.com/jlowin/fastmcp/actions/workflows/run-tests.yml)
|
|
56
59
|
[](https://github.com/jlowin/fastmcp/blob/main/LICENSE)
|
|
@@ -106,6 +109,8 @@ There are two ways to access the LLM-friendly documentation:
|
|
|
106
109
|
- [`llms.txt`](https://gofastmcp.com/llms.txt) is essentially a sitemap, listing all the pages in the documentation.
|
|
107
110
|
- [`llms-full.txt`](https://gofastmcp.com/llms-full.txt) contains the entire documentation. Note this may exceed the context window of your LLM.
|
|
108
111
|
|
|
112
|
+
**Community:** Join our [Discord server](https://discord.gg/uu8dJCgttd) to connect with other FastMCP developers and share what you're building.
|
|
113
|
+
|
|
109
114
|
---
|
|
110
115
|
|
|
111
116
|
<!-- omit in toc -->
|
|
@@ -244,7 +249,6 @@ Access MCP session capabilities within your tools, resources, or prompts by addi
|
|
|
244
249
|
|
|
245
250
|
- **Logging:** Log messages to MCP clients with `ctx.info()`, `ctx.error()`, etc.
|
|
246
251
|
- **LLM Sampling:** Use `ctx.sample()` to request completions from the client's LLM.
|
|
247
|
-
- **HTTP Request:** Use `ctx.http_request()` to make HTTP requests to other servers.
|
|
248
252
|
- **Resource Access:** Use `ctx.read_resource()` to access resources on the server
|
|
249
253
|
- **Progress Reporting:** Use `ctx.report_progress()` to report progress to the client.
|
|
250
254
|
- and more...
|
|
@@ -354,7 +358,7 @@ FastMCP provides comprehensive authentication support that sets it apart from ba
|
|
|
354
358
|
Protecting a server takes just two lines:
|
|
355
359
|
|
|
356
360
|
```python
|
|
357
|
-
from fastmcp.server.auth import GoogleProvider
|
|
361
|
+
from fastmcp.server.auth.providers.google import GoogleProvider
|
|
358
362
|
|
|
359
363
|
auth = GoogleProvider(client_id="...", client_secret="...", base_url="https://myserver.com")
|
|
360
364
|
mcp = FastMCP("Protected Server", auth=auth)
|