fastmcp 2.12.5__py3-none-any.whl → 2.14.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/__init__.py +2 -23
- fastmcp/cli/__init__.py +0 -3
- fastmcp/cli/__main__.py +5 -0
- fastmcp/cli/cli.py +19 -33
- fastmcp/cli/install/claude_code.py +6 -6
- fastmcp/cli/install/claude_desktop.py +3 -3
- fastmcp/cli/install/cursor.py +18 -12
- fastmcp/cli/install/gemini_cli.py +3 -3
- fastmcp/cli/install/mcp_json.py +3 -3
- fastmcp/cli/install/shared.py +0 -15
- fastmcp/cli/run.py +13 -8
- fastmcp/cli/tasks.py +110 -0
- fastmcp/client/__init__.py +9 -9
- fastmcp/client/auth/oauth.py +123 -225
- fastmcp/client/client.py +697 -95
- fastmcp/client/elicitation.py +11 -5
- fastmcp/client/logging.py +18 -14
- fastmcp/client/messages.py +7 -5
- fastmcp/client/oauth_callback.py +85 -171
- fastmcp/client/roots.py +2 -1
- fastmcp/client/sampling.py +1 -1
- fastmcp/client/tasks.py +614 -0
- fastmcp/client/transports.py +117 -30
- fastmcp/contrib/component_manager/__init__.py +1 -1
- fastmcp/contrib/component_manager/component_manager.py +2 -2
- fastmcp/contrib/component_manager/component_service.py +10 -26
- fastmcp/contrib/mcp_mixin/README.md +32 -1
- fastmcp/contrib/mcp_mixin/__init__.py +2 -2
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +14 -2
- fastmcp/dependencies.py +25 -0
- fastmcp/experimental/sampling/handlers/openai.py +3 -3
- fastmcp/experimental/server/openapi/__init__.py +20 -21
- fastmcp/experimental/utilities/openapi/__init__.py +16 -47
- fastmcp/mcp_config.py +3 -4
- fastmcp/prompts/__init__.py +1 -1
- fastmcp/prompts/prompt.py +54 -51
- fastmcp/prompts/prompt_manager.py +16 -101
- fastmcp/resources/__init__.py +5 -5
- fastmcp/resources/resource.py +43 -21
- fastmcp/resources/resource_manager.py +9 -168
- fastmcp/resources/template.py +161 -61
- fastmcp/resources/types.py +30 -24
- fastmcp/server/__init__.py +1 -1
- fastmcp/server/auth/__init__.py +9 -14
- fastmcp/server/auth/auth.py +197 -46
- fastmcp/server/auth/handlers/authorize.py +326 -0
- fastmcp/server/auth/jwt_issuer.py +236 -0
- fastmcp/server/auth/middleware.py +96 -0
- fastmcp/server/auth/oauth_proxy.py +1469 -298
- fastmcp/server/auth/oidc_proxy.py +91 -20
- fastmcp/server/auth/providers/auth0.py +40 -21
- fastmcp/server/auth/providers/aws.py +29 -3
- fastmcp/server/auth/providers/azure.py +312 -131
- fastmcp/server/auth/providers/debug.py +114 -0
- fastmcp/server/auth/providers/descope.py +86 -29
- fastmcp/server/auth/providers/discord.py +308 -0
- fastmcp/server/auth/providers/github.py +29 -8
- fastmcp/server/auth/providers/google.py +48 -9
- fastmcp/server/auth/providers/in_memory.py +29 -5
- fastmcp/server/auth/providers/introspection.py +281 -0
- fastmcp/server/auth/providers/jwt.py +48 -31
- fastmcp/server/auth/providers/oci.py +233 -0
- fastmcp/server/auth/providers/scalekit.py +238 -0
- fastmcp/server/auth/providers/supabase.py +188 -0
- fastmcp/server/auth/providers/workos.py +35 -17
- fastmcp/server/context.py +236 -116
- fastmcp/server/dependencies.py +503 -18
- fastmcp/server/elicitation.py +286 -48
- fastmcp/server/event_store.py +177 -0
- fastmcp/server/http.py +71 -20
- fastmcp/server/low_level.py +165 -2
- fastmcp/server/middleware/__init__.py +1 -1
- fastmcp/server/middleware/caching.py +476 -0
- fastmcp/server/middleware/error_handling.py +14 -10
- fastmcp/server/middleware/logging.py +50 -39
- fastmcp/server/middleware/middleware.py +29 -16
- fastmcp/server/middleware/rate_limiting.py +3 -3
- fastmcp/server/middleware/tool_injection.py +116 -0
- fastmcp/server/openapi/__init__.py +35 -0
- fastmcp/{experimental/server → server}/openapi/components.py +15 -10
- fastmcp/{experimental/server → server}/openapi/routing.py +3 -3
- fastmcp/{experimental/server → server}/openapi/server.py +6 -5
- fastmcp/server/proxy.py +72 -48
- fastmcp/server/server.py +1415 -733
- fastmcp/server/tasks/__init__.py +21 -0
- fastmcp/server/tasks/capabilities.py +22 -0
- fastmcp/server/tasks/config.py +89 -0
- fastmcp/server/tasks/converters.py +205 -0
- fastmcp/server/tasks/handlers.py +356 -0
- fastmcp/server/tasks/keys.py +93 -0
- fastmcp/server/tasks/protocol.py +355 -0
- fastmcp/server/tasks/subscriptions.py +205 -0
- fastmcp/settings.py +125 -113
- fastmcp/tools/__init__.py +1 -1
- fastmcp/tools/tool.py +138 -55
- fastmcp/tools/tool_manager.py +30 -112
- fastmcp/tools/tool_transform.py +12 -21
- fastmcp/utilities/cli.py +67 -28
- fastmcp/utilities/components.py +10 -5
- fastmcp/utilities/inspect.py +79 -23
- fastmcp/utilities/json_schema.py +4 -4
- fastmcp/utilities/json_schema_type.py +8 -8
- fastmcp/utilities/logging.py +118 -8
- fastmcp/utilities/mcp_config.py +1 -2
- fastmcp/utilities/mcp_server_config/__init__.py +3 -3
- fastmcp/utilities/mcp_server_config/v1/environments/base.py +1 -2
- fastmcp/utilities/mcp_server_config/v1/environments/uv.py +6 -6
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +5 -5
- fastmcp/utilities/mcp_server_config/v1/schema.json +3 -0
- fastmcp/utilities/mcp_server_config/v1/sources/base.py +0 -1
- fastmcp/{experimental/utilities → utilities}/openapi/README.md +7 -35
- fastmcp/utilities/openapi/__init__.py +63 -0
- fastmcp/{experimental/utilities → utilities}/openapi/director.py +14 -15
- fastmcp/{experimental/utilities → utilities}/openapi/formatters.py +5 -5
- fastmcp/{experimental/utilities → utilities}/openapi/json_schema_converter.py +7 -3
- fastmcp/{experimental/utilities → utilities}/openapi/parser.py +37 -16
- fastmcp/utilities/tests.py +92 -5
- fastmcp/utilities/types.py +86 -16
- fastmcp/utilities/ui.py +626 -0
- {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/METADATA +24 -15
- fastmcp-2.14.0.dist-info/RECORD +156 -0
- {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/WHEEL +1 -1
- fastmcp/cli/claude.py +0 -135
- fastmcp/server/auth/providers/bearer.py +0 -25
- fastmcp/server/openapi.py +0 -1083
- fastmcp/utilities/openapi.py +0 -1568
- fastmcp/utilities/storage.py +0 -204
- fastmcp-2.12.5.dist-info/RECORD +0 -134
- fastmcp/{experimental/server → server}/openapi/README.md +0 -0
- fastmcp/{experimental/utilities → utilities}/openapi/models.py +3 -3
- fastmcp/{experimental/utilities → utilities}/openapi/schemas.py +2 -2
- {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/licenses/LICENSE +0 -0
fastmcp/utilities/cli.py
CHANGED
|
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
-
from importlib.metadata import version
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
from typing import TYPE_CHECKING, Any, Literal
|
|
8
7
|
|
|
@@ -138,7 +137,7 @@ def load_and_merge_config(
|
|
|
138
137
|
return new_config, resolved_spec
|
|
139
138
|
|
|
140
139
|
|
|
141
|
-
|
|
140
|
+
LOGO_ASCII_1 = r"""
|
|
142
141
|
_ __ ___ _____ __ __ _____________ ____ ____
|
|
143
142
|
_ __ ___ .'____/___ ______/ /_/ |/ / ____/ __ \ |___ \ / __ \
|
|
144
143
|
_ __ ___ / /_ / __ `/ ___/ __/ /|_/ / / / /_/ / ___/ / / / / /
|
|
@@ -147,6 +146,56 @@ _ __ ___ /_/ \____/____/\__/_/ /_/\____/_/ /_____(*)____/
|
|
|
147
146
|
|
|
148
147
|
""".lstrip("\n")
|
|
149
148
|
|
|
149
|
+
# This prints the below in a blue gradient
|
|
150
|
+
# █▀▀ ▄▀█ █▀▀ ▀█▀ █▀▄▀█ █▀▀ █▀█
|
|
151
|
+
# █▀ █▀█ ▄▄█ █ █ ▀ █ █▄▄ █▀▀
|
|
152
|
+
LOGO_ASCII_2 = (
|
|
153
|
+
"\x1b[38;2;0;198;255m \x1b[38;2;0;195;255m█\x1b[38;2;0;192;255m▀\x1b[38;2;0;189;255m▀\x1b[38;2;0;186;255m "
|
|
154
|
+
"\x1b[38;2;0;184;255m▄\x1b[38;2;0;181;255m▀\x1b[38;2;0;178;255m█\x1b[38;2;0;175;255m "
|
|
155
|
+
"\x1b[38;2;0;172;255m█\x1b[38;2;0;169;255m▀\x1b[38;2;0;166;255m▀\x1b[38;2;0;163;255m "
|
|
156
|
+
"\x1b[38;2;0;160;255m▀\x1b[38;2;0;157;255m█\x1b[38;2;0;155;255m▀\x1b[38;2;0;152;255m "
|
|
157
|
+
"\x1b[38;2;0;149;255m█\x1b[38;2;0;146;255m▀\x1b[38;2;0;143;255m▄\x1b[38;2;0;140;255m▀\x1b[38;2;0;137;255m█\x1b[38;2;0;134;255m "
|
|
158
|
+
"\x1b[38;2;0;131;255m█\x1b[38;2;0;128;255m▀\x1b[38;2;0;126;255m▀\x1b[38;2;0;123;255m "
|
|
159
|
+
"\x1b[38;2;0;120;255m█\x1b[38;2;0;117;255m▀\x1b[38;2;0;114;255m█\x1b[39m\n"
|
|
160
|
+
"\x1b[38;2;0;198;255m \x1b[38;2;0;195;255m█\x1b[38;2;0;192;255m▀\x1b[38;2;0;189;255m \x1b[38;2;0;186;255m "
|
|
161
|
+
"\x1b[38;2;0;184;255m█\x1b[38;2;0;181;255m▀\x1b[38;2;0;178;255m█\x1b[38;2;0;175;255m "
|
|
162
|
+
"\x1b[38;2;0;172;255m▄\x1b[38;2;0;169;255m▄\x1b[38;2;0;166;255m█\x1b[38;2;0;163;255m "
|
|
163
|
+
"\x1b[38;2;0;160;255m \x1b[38;2;0;157;255m█\x1b[38;2;0;155;255m \x1b[38;2;0;152;255m "
|
|
164
|
+
"\x1b[38;2;0;149;255m█\x1b[38;2;0;146;255m \x1b[38;2;0;143;255m▀\x1b[38;2;0;140;255m \x1b[38;2;0;137;255m█\x1b[38;2;0;134;255m "
|
|
165
|
+
"\x1b[38;2;0;131;255m█\x1b[38;2;0;128;255m▄\x1b[38;2;0;126;255m▄\x1b[38;2;0;123;255m "
|
|
166
|
+
"\x1b[38;2;0;120;255m█\x1b[38;2;0;117;255m▀\x1b[38;2;0;114;255m▀\x1b[39m"
|
|
167
|
+
).strip()
|
|
168
|
+
|
|
169
|
+
# Prints the below in a blue gradient - sylized F
|
|
170
|
+
# ▄▀▀▀
|
|
171
|
+
# █▀▀
|
|
172
|
+
# ▀
|
|
173
|
+
LOGO_ASCII_3 = (
|
|
174
|
+
" \x1b[38;2;0;170;255m▄\x1b[38;2;0;142;255m▀\x1b[38;2;0;114;255m▀\x1b[38;2;0;86;255m▀\x1b[39m\n"
|
|
175
|
+
" \x1b[38;2;0;170;255m█\x1b[38;2;0;142;255m▀\x1b[38;2;0;114;255m▀\x1b[39m\n"
|
|
176
|
+
"\x1b[38;2;0;170;255m▀\x1b[39m\n"
|
|
177
|
+
"\x1b[0m"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Prints the below in a blue gradient - block logo with slightly stylized F
|
|
181
|
+
# ▄▀▀ ▄▀█ █▀▀ ▀█▀ █▀▄▀█ █▀▀ █▀█
|
|
182
|
+
# █▀ █▀█ ▄▄█ █ █ ▀ █ █▄▄ █▀▀
|
|
183
|
+
|
|
184
|
+
LOGO_ASCII_4 = (
|
|
185
|
+
"\x1b[38;2;0;198;255m \x1b[38;2;0;195;255m▄\x1b[38;2;0;192;255m▀\x1b[38;2;0;189;255m▀\x1b[38;2;0;186;255m \x1b[38;2;0;184;255m▄\x1b[38;2;0;181;255m▀\x1b[38;2;0;178;255m█\x1b[38;2;0;175;255m "
|
|
186
|
+
"\x1b[38;2;0;172;255m█\x1b[38;2;0;169;255m▀\x1b[38;2;0;166;255m▀\x1b[38;2;0;163;255m "
|
|
187
|
+
"\x1b[38;2;0;160;255m▀\x1b[38;2;0;157;255m█\x1b[38;2;0;155;255m▀\x1b[38;2;0;152;255m "
|
|
188
|
+
"\x1b[38;2;0;149;255m█\x1b[38;2;0;146;255m▀\x1b[38;2;0;143;255m▄\x1b[38;2;0;140;255m▀\x1b[38;2;0;137;255m█\x1b[38;2;0;134;255m "
|
|
189
|
+
"\x1b[38;2;0;131;255m█\x1b[38;2;0;128;255m▀\x1b[38;2;0;126;255m▀\x1b[38;2;0;123;255m "
|
|
190
|
+
"\x1b[38;2;0;120;255m█\x1b[38;2;0;117;255m▀\x1b[38;2;0;114;255m█\x1b[39m\n"
|
|
191
|
+
"\x1b[38;2;0;198;255m \x1b[38;2;0;195;255m█\x1b[38;2;0;192;255m▀\x1b[38;2;0;189;255m \x1b[38;2;0;186;255m \x1b[38;2;0;184;255m█\x1b[38;2;0;181;255m▀\x1b[38;2;0;178;255m█\x1b[38;2;0;175;255m "
|
|
192
|
+
"\x1b[38;2;0;172;255m▄\x1b[38;2;0;169;255m▄\x1b[38;2;0;166;255m█\x1b[38;2;0;163;255m "
|
|
193
|
+
"\x1b[38;2;0;160;255m \x1b[38;2;0;157;255m█\x1b[38;2;0;155;255m \x1b[38;2;0;152;255m "
|
|
194
|
+
"\x1b[38;2;0;149;255m█\x1b[38;2;0;146;255m \x1b[38;2;0;143;255m▀\x1b[38;2;0;140;255m \x1b[38;2;0;137;255m█\x1b[38;2;0;134;255m "
|
|
195
|
+
"\x1b[38;2;0;131;255m█\x1b[38;2;0;128;255m▄\x1b[38;2;0;126;255m▄\x1b[38;2;0;123;255m "
|
|
196
|
+
"\x1b[38;2;0;120;255m█\x1b[38;2;0;117;255m▀\x1b[38;2;0;114;255m▀\x1b[39m\n"
|
|
197
|
+
)
|
|
198
|
+
|
|
150
199
|
|
|
151
200
|
def log_server_banner(
|
|
152
201
|
server: FastMCP[Any],
|
|
@@ -167,10 +216,11 @@ def log_server_banner(
|
|
|
167
216
|
"""
|
|
168
217
|
|
|
169
218
|
# Create the logo text
|
|
170
|
-
|
|
219
|
+
# Use Text with no_wrap and markup disabled to preserve ANSI escape codes
|
|
220
|
+
logo_text = Text.from_ansi(LOGO_ASCII_4, no_wrap=True)
|
|
171
221
|
|
|
172
222
|
# Create the main title
|
|
173
|
-
title_text = Text("FastMCP
|
|
223
|
+
title_text = Text(f"FastMCP {fastmcp.__version__}", style="bold blue")
|
|
174
224
|
|
|
175
225
|
# Create the information table
|
|
176
226
|
info_table = Table.grid(padding=(0, 1))
|
|
@@ -180,44 +230,31 @@ def log_server_banner(
|
|
|
180
230
|
|
|
181
231
|
match transport:
|
|
182
232
|
case "http" | "streamable-http":
|
|
183
|
-
display_transport = "
|
|
233
|
+
display_transport = "HTTP"
|
|
184
234
|
case "sse":
|
|
185
235
|
display_transport = "SSE"
|
|
186
236
|
case "stdio":
|
|
187
237
|
display_transport = "STDIO"
|
|
188
238
|
|
|
189
|
-
info_table.add_row("
|
|
239
|
+
info_table.add_row("🖥", "Server name:", Text(server.name + "\n", style="bold blue"))
|
|
190
240
|
info_table.add_row("📦", "Transport:", display_transport)
|
|
191
241
|
|
|
192
242
|
# Show connection info based on transport
|
|
193
|
-
if transport in ("http", "streamable-http", "sse"):
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
info_table.add_row("🔗", "Server URL:", server_url)
|
|
199
|
-
|
|
200
|
-
# Add version information with explicit style overrides
|
|
201
|
-
info_table.add_row("", "", "")
|
|
202
|
-
info_table.add_row(
|
|
203
|
-
"🏎️",
|
|
204
|
-
"FastMCP version:",
|
|
205
|
-
Text(fastmcp.__version__, style="dim white", no_wrap=True),
|
|
206
|
-
)
|
|
207
|
-
info_table.add_row(
|
|
208
|
-
"🤝",
|
|
209
|
-
"MCP SDK version:",
|
|
210
|
-
Text(version("mcp"), style="dim white", no_wrap=True),
|
|
211
|
-
)
|
|
243
|
+
if transport in ("http", "streamable-http", "sse") and host and port:
|
|
244
|
+
server_url = f"http://{host}:{port}"
|
|
245
|
+
if path:
|
|
246
|
+
server_url += f"/{path.lstrip('/')}"
|
|
247
|
+
info_table.add_row("🔗", "Server URL:", server_url)
|
|
212
248
|
|
|
213
249
|
# Add documentation link
|
|
214
250
|
info_table.add_row("", "", "")
|
|
215
251
|
info_table.add_row("📚", "Docs:", "https://gofastmcp.com")
|
|
216
|
-
info_table.add_row("🚀", "
|
|
252
|
+
info_table.add_row("🚀", "Hosting:", "https://fastmcp.cloud")
|
|
217
253
|
|
|
218
254
|
# Create panel with logo, title, and information using Group
|
|
219
255
|
panel_content = Group(
|
|
220
256
|
Align.center(logo_text),
|
|
257
|
+
"",
|
|
221
258
|
Align.center(title_text),
|
|
222
259
|
"",
|
|
223
260
|
"",
|
|
@@ -228,8 +265,10 @@ def log_server_banner(
|
|
|
228
265
|
panel_content,
|
|
229
266
|
border_style="dim",
|
|
230
267
|
padding=(1, 4),
|
|
231
|
-
expand=False,
|
|
268
|
+
# expand=False,
|
|
269
|
+
width=80, # Set max width for the panel
|
|
232
270
|
)
|
|
233
271
|
|
|
234
272
|
console = Console(stderr=True)
|
|
235
|
-
|
|
273
|
+
# Center the panel itself
|
|
274
|
+
console.print(Group("\n", Align.center(panel), "\n"))
|
fastmcp/utilities/components.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Sequence
|
|
4
|
-
from typing import Annotated, Any, TypedDict
|
|
4
|
+
from typing import Annotated, Any, TypedDict, cast
|
|
5
5
|
|
|
6
|
+
from mcp.types import Icon
|
|
6
7
|
from pydantic import BeforeValidator, Field, PrivateAttr
|
|
7
8
|
from typing_extensions import Self, TypeVar
|
|
8
9
|
|
|
@@ -39,6 +40,10 @@ class FastMCPComponent(FastMCPBaseModel):
|
|
|
39
40
|
default=None,
|
|
40
41
|
description="The description of the component.",
|
|
41
42
|
)
|
|
43
|
+
icons: list[Icon] | None = Field(
|
|
44
|
+
default=None,
|
|
45
|
+
description="Optional list of icons for this component to display in user interfaces.",
|
|
46
|
+
)
|
|
42
47
|
tags: Annotated[set[str], BeforeValidator(_convert_set_default_none)] = Field(
|
|
43
48
|
default_factory=set,
|
|
44
49
|
description="Tags for the component.",
|
|
@@ -91,7 +96,7 @@ class FastMCPComponent(FastMCPBaseModel):
|
|
|
91
96
|
|
|
92
97
|
return meta or None
|
|
93
98
|
|
|
94
|
-
def model_copy(
|
|
99
|
+
def model_copy( # type: ignore[override]
|
|
95
100
|
self,
|
|
96
101
|
*,
|
|
97
102
|
update: dict[str, Any] | None = None,
|
|
@@ -112,7 +117,7 @@ class FastMCPComponent(FastMCPBaseModel):
|
|
|
112
117
|
copy = super().model_copy(update=update, deep=deep)
|
|
113
118
|
if key is not None:
|
|
114
119
|
copy._key = key
|
|
115
|
-
return copy
|
|
120
|
+
return cast(Self, copy)
|
|
116
121
|
|
|
117
122
|
def __eq__(self, other: object) -> bool:
|
|
118
123
|
if type(self) is not type(other):
|
|
@@ -132,7 +137,7 @@ class FastMCPComponent(FastMCPBaseModel):
|
|
|
132
137
|
"""Disable the component."""
|
|
133
138
|
self.enabled = False
|
|
134
139
|
|
|
135
|
-
def copy(self) -> Self:
|
|
140
|
+
def copy(self) -> Self: # type: ignore[override]
|
|
136
141
|
"""Create a copy of the component."""
|
|
137
142
|
return self.model_copy()
|
|
138
143
|
|
|
@@ -168,7 +173,7 @@ class MirroredComponent(FastMCPComponent):
|
|
|
168
173
|
)
|
|
169
174
|
super().disable()
|
|
170
175
|
|
|
171
|
-
def copy(self) -> Self:
|
|
176
|
+
def copy(self) -> Self: # type: ignore[override]
|
|
172
177
|
"""Create a copy of the component that can be modified."""
|
|
173
178
|
# Create a copy and mark it as not mirrored
|
|
174
179
|
copied = self.model_copy()
|
fastmcp/utilities/inspect.py
CHANGED
|
@@ -28,6 +28,7 @@ class ToolInfo:
|
|
|
28
28
|
tags: list[str] | None = None
|
|
29
29
|
enabled: bool | None = None
|
|
30
30
|
title: str | None = None
|
|
31
|
+
icons: list[dict[str, Any]] | None = None
|
|
31
32
|
meta: dict[str, Any] | None = None
|
|
32
33
|
|
|
33
34
|
|
|
@@ -42,6 +43,7 @@ class PromptInfo:
|
|
|
42
43
|
tags: list[str] | None = None
|
|
43
44
|
enabled: bool | None = None
|
|
44
45
|
title: str | None = None
|
|
46
|
+
icons: list[dict[str, Any]] | None = None
|
|
45
47
|
meta: dict[str, Any] | None = None
|
|
46
48
|
|
|
47
49
|
|
|
@@ -58,6 +60,7 @@ class ResourceInfo:
|
|
|
58
60
|
tags: list[str] | None = None
|
|
59
61
|
enabled: bool | None = None
|
|
60
62
|
title: str | None = None
|
|
63
|
+
icons: list[dict[str, Any]] | None = None
|
|
61
64
|
meta: dict[str, Any] | None = None
|
|
62
65
|
|
|
63
66
|
|
|
@@ -75,6 +78,7 @@ class TemplateInfo:
|
|
|
75
78
|
tags: list[str] | None = None
|
|
76
79
|
enabled: bool | None = None
|
|
77
80
|
title: str | None = None
|
|
81
|
+
icons: list[dict[str, Any]] | None = None
|
|
78
82
|
meta: dict[str, Any] | None = None
|
|
79
83
|
|
|
80
84
|
|
|
@@ -85,6 +89,8 @@ class FastMCPInfo:
|
|
|
85
89
|
name: str
|
|
86
90
|
instructions: str | None
|
|
87
91
|
version: str | None # The server's own version string (if specified)
|
|
92
|
+
website_url: str | None
|
|
93
|
+
icons: list[dict[str, Any]] | None
|
|
88
94
|
fastmcp_version: str # Version of FastMCP generating this manifest
|
|
89
95
|
mcp_version: str # Version of MCP protocol library
|
|
90
96
|
server_generation: int # Server generation: 1 (mcp package) or 2 (fastmcp)
|
|
@@ -104,21 +110,20 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
|
|
|
104
110
|
Returns:
|
|
105
111
|
FastMCPInfo dataclass containing the extracted information
|
|
106
112
|
"""
|
|
107
|
-
# Get all
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
113
|
+
# Get all components via middleware to respect filtering and preserve metadata
|
|
114
|
+
tools_list = await mcp._list_tools_middleware()
|
|
115
|
+
prompts_list = await mcp._list_prompts_middleware()
|
|
116
|
+
resources_list = await mcp._list_resources_middleware()
|
|
117
|
+
templates_list = await mcp._list_resource_templates_middleware()
|
|
112
118
|
|
|
113
119
|
# Extract detailed tool information
|
|
114
120
|
tool_infos = []
|
|
115
|
-
for
|
|
116
|
-
|
|
117
|
-
mcp_tool = tool.to_mcp_tool(name=key)
|
|
121
|
+
for tool in tools_list:
|
|
122
|
+
mcp_tool = tool.to_mcp_tool(name=tool.key)
|
|
118
123
|
tool_infos.append(
|
|
119
124
|
ToolInfo(
|
|
120
|
-
key=key,
|
|
121
|
-
name=tool.name or key,
|
|
125
|
+
key=tool.key,
|
|
126
|
+
name=tool.name or tool.key,
|
|
122
127
|
description=tool.description,
|
|
123
128
|
input_schema=mcp_tool.inputSchema if mcp_tool.inputSchema else {},
|
|
124
129
|
output_schema=tool.output_schema,
|
|
@@ -126,17 +131,20 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
|
|
|
126
131
|
tags=list(tool.tags) if tool.tags else None,
|
|
127
132
|
enabled=tool.enabled,
|
|
128
133
|
title=tool.title,
|
|
134
|
+
icons=[icon.model_dump() for icon in tool.icons]
|
|
135
|
+
if tool.icons
|
|
136
|
+
else None,
|
|
129
137
|
meta=tool.meta,
|
|
130
138
|
)
|
|
131
139
|
)
|
|
132
140
|
|
|
133
141
|
# Extract detailed prompt information
|
|
134
142
|
prompt_infos = []
|
|
135
|
-
for
|
|
143
|
+
for prompt in prompts_list:
|
|
136
144
|
prompt_infos.append(
|
|
137
145
|
PromptInfo(
|
|
138
|
-
key=key,
|
|
139
|
-
name=prompt.name or key,
|
|
146
|
+
key=prompt.key,
|
|
147
|
+
name=prompt.name or prompt.key,
|
|
140
148
|
description=prompt.description,
|
|
141
149
|
arguments=[arg.model_dump() for arg in prompt.arguments]
|
|
142
150
|
if prompt.arguments
|
|
@@ -144,17 +152,20 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
|
|
|
144
152
|
tags=list(prompt.tags) if prompt.tags else None,
|
|
145
153
|
enabled=prompt.enabled,
|
|
146
154
|
title=prompt.title,
|
|
155
|
+
icons=[icon.model_dump() for icon in prompt.icons]
|
|
156
|
+
if prompt.icons
|
|
157
|
+
else None,
|
|
147
158
|
meta=prompt.meta,
|
|
148
159
|
)
|
|
149
160
|
)
|
|
150
161
|
|
|
151
162
|
# Extract detailed resource information
|
|
152
163
|
resource_infos = []
|
|
153
|
-
for
|
|
164
|
+
for resource in resources_list:
|
|
154
165
|
resource_infos.append(
|
|
155
166
|
ResourceInfo(
|
|
156
|
-
key=key,
|
|
157
|
-
uri=key,
|
|
167
|
+
key=resource.key,
|
|
168
|
+
uri=resource.key,
|
|
158
169
|
name=resource.name,
|
|
159
170
|
description=resource.description,
|
|
160
171
|
mime_type=resource.mime_type,
|
|
@@ -164,17 +175,20 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
|
|
|
164
175
|
tags=list(resource.tags) if resource.tags else None,
|
|
165
176
|
enabled=resource.enabled,
|
|
166
177
|
title=resource.title,
|
|
178
|
+
icons=[icon.model_dump() for icon in resource.icons]
|
|
179
|
+
if resource.icons
|
|
180
|
+
else None,
|
|
167
181
|
meta=resource.meta,
|
|
168
182
|
)
|
|
169
183
|
)
|
|
170
184
|
|
|
171
185
|
# Extract detailed template information
|
|
172
186
|
template_infos = []
|
|
173
|
-
for
|
|
187
|
+
for template in templates_list:
|
|
174
188
|
template_infos.append(
|
|
175
189
|
TemplateInfo(
|
|
176
|
-
key=key,
|
|
177
|
-
uri_template=key,
|
|
190
|
+
key=template.key,
|
|
191
|
+
uri_template=template.key,
|
|
178
192
|
name=template.name,
|
|
179
193
|
description=template.description,
|
|
180
194
|
mime_type=template.mime_type,
|
|
@@ -185,6 +199,9 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
|
|
|
185
199
|
tags=list(template.tags) if template.tags else None,
|
|
186
200
|
enabled=template.enabled,
|
|
187
201
|
title=template.title,
|
|
202
|
+
icons=[icon.model_dump() for icon in template.icons]
|
|
203
|
+
if template.icons
|
|
204
|
+
else None,
|
|
188
205
|
meta=template.meta,
|
|
189
206
|
)
|
|
190
207
|
)
|
|
@@ -197,13 +214,25 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
|
|
|
197
214
|
"logging": {},
|
|
198
215
|
}
|
|
199
216
|
|
|
217
|
+
# Extract server-level icons and website_url
|
|
218
|
+
server_icons = (
|
|
219
|
+
[icon.model_dump() for icon in mcp._mcp_server.icons]
|
|
220
|
+
if hasattr(mcp._mcp_server, "icons") and mcp._mcp_server.icons
|
|
221
|
+
else None
|
|
222
|
+
)
|
|
223
|
+
server_website_url = (
|
|
224
|
+
mcp._mcp_server.website_url if hasattr(mcp._mcp_server, "website_url") else None
|
|
225
|
+
)
|
|
226
|
+
|
|
200
227
|
return FastMCPInfo(
|
|
201
228
|
name=mcp.name,
|
|
202
229
|
instructions=mcp.instructions,
|
|
230
|
+
version=(mcp.version if hasattr(mcp, "version") else mcp._mcp_server.version),
|
|
231
|
+
website_url=server_website_url,
|
|
232
|
+
icons=server_icons,
|
|
203
233
|
fastmcp_version=fastmcp.__version__,
|
|
204
234
|
mcp_version=importlib.metadata.version("mcp"),
|
|
205
235
|
server_generation=2, # FastMCP v2
|
|
206
|
-
version=(mcp.version if hasattr(mcp, "version") else mcp._mcp_server.version),
|
|
207
236
|
tools=tool_infos,
|
|
208
237
|
prompts=prompt_infos,
|
|
209
238
|
resources=resource_infos,
|
|
@@ -248,6 +277,9 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
|
|
|
248
277
|
tags=None, # v1 doesn't have tags
|
|
249
278
|
enabled=None, # v1 doesn't have enabled field
|
|
250
279
|
title=None, # v1 doesn't have title
|
|
280
|
+
icons=[icon.model_dump() for icon in mcp_tool.icons]
|
|
281
|
+
if hasattr(mcp_tool, "icons") and mcp_tool.icons
|
|
282
|
+
else None,
|
|
251
283
|
meta=None, # v1 doesn't have meta field
|
|
252
284
|
)
|
|
253
285
|
)
|
|
@@ -269,6 +301,9 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
|
|
|
269
301
|
tags=None, # v1 doesn't have tags
|
|
270
302
|
enabled=None, # v1 doesn't have enabled field
|
|
271
303
|
title=None, # v1 doesn't have title
|
|
304
|
+
icons=[icon.model_dump() for icon in mcp_prompt.icons]
|
|
305
|
+
if hasattr(mcp_prompt, "icons") and mcp_prompt.icons
|
|
306
|
+
else None,
|
|
272
307
|
meta=None, # v1 doesn't have meta field
|
|
273
308
|
)
|
|
274
309
|
)
|
|
@@ -287,6 +322,9 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
|
|
|
287
322
|
tags=None, # v1 doesn't have tags
|
|
288
323
|
enabled=None, # v1 doesn't have enabled field
|
|
289
324
|
title=None, # v1 doesn't have title
|
|
325
|
+
icons=[icon.model_dump() for icon in mcp_resource.icons]
|
|
326
|
+
if hasattr(mcp_resource, "icons") and mcp_resource.icons
|
|
327
|
+
else None,
|
|
290
328
|
meta=None, # v1 doesn't have meta field
|
|
291
329
|
)
|
|
292
330
|
)
|
|
@@ -306,6 +344,9 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
|
|
|
306
344
|
tags=None, # v1 doesn't have tags
|
|
307
345
|
enabled=None, # v1 doesn't have enabled field
|
|
308
346
|
title=None, # v1 doesn't have title
|
|
347
|
+
icons=[icon.model_dump() for icon in mcp_template.icons]
|
|
348
|
+
if hasattr(mcp_template, "icons") and mcp_template.icons
|
|
349
|
+
else None,
|
|
309
350
|
meta=None, # v1 doesn't have meta field
|
|
310
351
|
)
|
|
311
352
|
)
|
|
@@ -318,13 +359,26 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
|
|
|
318
359
|
"logging": {},
|
|
319
360
|
}
|
|
320
361
|
|
|
362
|
+
# Extract server-level icons and website_url from serverInfo
|
|
363
|
+
server_info = client.initialize_result.serverInfo
|
|
364
|
+
server_icons = (
|
|
365
|
+
[icon.model_dump() for icon in server_info.icons]
|
|
366
|
+
if hasattr(server_info, "icons") and server_info.icons
|
|
367
|
+
else None
|
|
368
|
+
)
|
|
369
|
+
server_website_url = (
|
|
370
|
+
server_info.websiteUrl if hasattr(server_info, "websiteUrl") else None
|
|
371
|
+
)
|
|
372
|
+
|
|
321
373
|
return FastMCPInfo(
|
|
322
374
|
name=mcp._mcp_server.name,
|
|
323
375
|
instructions=mcp._mcp_server.instructions,
|
|
376
|
+
version=mcp._mcp_server.version,
|
|
377
|
+
website_url=server_website_url,
|
|
378
|
+
icons=server_icons,
|
|
324
379
|
fastmcp_version=fastmcp.__version__, # Version generating this manifest
|
|
325
380
|
mcp_version=importlib.metadata.version("mcp"),
|
|
326
381
|
server_generation=1, # MCP v1
|
|
327
|
-
version=mcp._mcp_server.version,
|
|
328
382
|
tools=tool_infos,
|
|
329
383
|
prompts=prompt_infos,
|
|
330
384
|
resources=resource_infos,
|
|
@@ -358,7 +412,7 @@ class InspectFormat(str, Enum):
|
|
|
358
412
|
MCP = "mcp"
|
|
359
413
|
|
|
360
414
|
|
|
361
|
-
|
|
415
|
+
def format_fastmcp_info(info: FastMCPInfo) -> bytes:
|
|
362
416
|
"""Format FastMCPInfo as FastMCP-specific JSON.
|
|
363
417
|
|
|
364
418
|
This includes FastMCP-specific fields like tags, enabled, annotations, etc.
|
|
@@ -369,6 +423,8 @@ async def format_fastmcp_info(info: FastMCPInfo) -> bytes:
|
|
|
369
423
|
"name": info.name,
|
|
370
424
|
"instructions": info.instructions,
|
|
371
425
|
"version": info.version,
|
|
426
|
+
"website_url": info.website_url,
|
|
427
|
+
"icons": info.icons,
|
|
372
428
|
"generation": info.server_generation,
|
|
373
429
|
"capabilities": info.capabilities,
|
|
374
430
|
},
|
|
@@ -445,6 +501,6 @@ async def format_info(
|
|
|
445
501
|
# This works for both v1 and v2 servers
|
|
446
502
|
if info is None:
|
|
447
503
|
info = await inspect_fastmcp(mcp)
|
|
448
|
-
return
|
|
504
|
+
return format_fastmcp_info(info)
|
|
449
505
|
else:
|
|
450
506
|
raise ValueError(f"Unknown format: {format}")
|
fastmcp/utilities/json_schema.py
CHANGED
|
@@ -98,7 +98,7 @@ def _single_pass_optimize(
|
|
|
98
98
|
if isinstance(node, dict):
|
|
99
99
|
# Collect $ref references for unused definition removal
|
|
100
100
|
if prune_defs:
|
|
101
|
-
ref = node.get("$ref")
|
|
101
|
+
ref = node.get("$ref") # type: ignore
|
|
102
102
|
if isinstance(ref, str) and ref.startswith("#/$defs/"):
|
|
103
103
|
referenced_def = ref.split("/")[-1]
|
|
104
104
|
if current_def_name:
|
|
@@ -127,13 +127,13 @@ def _single_pass_optimize(
|
|
|
127
127
|
"required",
|
|
128
128
|
]
|
|
129
129
|
):
|
|
130
|
-
node.pop("title")
|
|
130
|
+
node.pop("title") # type: ignore
|
|
131
131
|
|
|
132
132
|
if (
|
|
133
133
|
prune_additional_properties
|
|
134
|
-
and node.get("additionalProperties") is False
|
|
134
|
+
and node.get("additionalProperties") is False # type: ignore
|
|
135
135
|
):
|
|
136
|
-
node.pop("additionalProperties")
|
|
136
|
+
node.pop("additionalProperties") # type: ignore
|
|
137
137
|
|
|
138
138
|
# Recursive traversal
|
|
139
139
|
for key, value in node.items():
|
|
@@ -61,7 +61,7 @@ from pydantic import (
|
|
|
61
61
|
)
|
|
62
62
|
from typing_extensions import NotRequired, TypedDict
|
|
63
63
|
|
|
64
|
-
__all__ = ["
|
|
64
|
+
__all__ = ["JSONSchema", "json_schema_to_type"]
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
FORMAT_TYPES: dict[str, Any] = {
|
|
@@ -248,7 +248,7 @@ def _create_numeric_type(
|
|
|
248
248
|
if v is not None
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
return Annotated[base, Field(**constraints)] if constraints else base
|
|
251
|
+
return Annotated[base, Field(**constraints)] if constraints else base # type: ignore[return-value]
|
|
252
252
|
|
|
253
253
|
|
|
254
254
|
def _create_enum(name: str, values: list[Any]) -> type:
|
|
@@ -265,8 +265,8 @@ def _create_array_type(
|
|
|
265
265
|
if isinstance(items, list):
|
|
266
266
|
# Handle positional item schemas
|
|
267
267
|
item_types = [_schema_to_type(s, schemas) for s in items]
|
|
268
|
-
combined = Union[tuple(item_types)] # type: ignore # noqa: UP007
|
|
269
|
-
base = list[combined]
|
|
268
|
+
combined = Union[tuple(item_types)] # type: ignore[arg-type] # noqa: UP007
|
|
269
|
+
base = list[combined] # type: ignore[valid-type]
|
|
270
270
|
else:
|
|
271
271
|
# Handle single item schema
|
|
272
272
|
item_type = _schema_to_type(items, schemas)
|
|
@@ -282,7 +282,7 @@ def _create_array_type(
|
|
|
282
282
|
if v is not None
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
-
return Annotated[base, Field(**constraints)] if constraints else base
|
|
285
|
+
return Annotated[base, Field(**constraints)] if constraints else base # type: ignore[return-value]
|
|
286
286
|
|
|
287
287
|
|
|
288
288
|
def _return_Any() -> Any:
|
|
@@ -368,7 +368,7 @@ def _schema_to_type(
|
|
|
368
368
|
return types[0]
|
|
369
369
|
else:
|
|
370
370
|
if has_null:
|
|
371
|
-
return Union[
|
|
371
|
+
return Union[(*types, type(None))] # type: ignore
|
|
372
372
|
else:
|
|
373
373
|
return Union[tuple(types)] # type: ignore # noqa: UP007
|
|
374
374
|
|
|
@@ -389,7 +389,7 @@ def _schema_to_type(
|
|
|
389
389
|
if len(types) == 1:
|
|
390
390
|
return types[0] | None # type: ignore
|
|
391
391
|
else:
|
|
392
|
-
return Union[
|
|
392
|
+
return Union[(*types, type(None))] # type: ignore
|
|
393
393
|
return Union[tuple(types)] # type: ignore # noqa: UP007
|
|
394
394
|
|
|
395
395
|
return _get_from_type_handler(schema, schemas)(schema)
|
|
@@ -578,7 +578,7 @@ def _create_dataclass(
|
|
|
578
578
|
return _merge_defaults(data, original_schema)
|
|
579
579
|
return data
|
|
580
580
|
|
|
581
|
-
|
|
581
|
+
cls._apply_defaults = _apply_defaults # type: ignore[attr-defined]
|
|
582
582
|
|
|
583
583
|
# Store completed class
|
|
584
584
|
_classes[cache_key] = cls
|