fastmcp 2.12.5__py3-none-any.whl → 2.13.0rc2__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 +6 -6
- fastmcp/cli/install/claude_code.py +3 -3
- 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 +81 -171
- fastmcp/client/transports.py +76 -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/server/auth/auth.py +40 -32
- fastmcp/server/auth/jwt_issuer.py +289 -0
- fastmcp/server/auth/oauth_proxy.py +1228 -233
- fastmcp/server/auth/oidc_proxy.py +8 -6
- fastmcp/server/auth/providers/auth0.py +13 -7
- fastmcp/server/auth/providers/aws.py +14 -3
- fastmcp/server/auth/providers/azure.py +137 -124
- fastmcp/server/auth/providers/descope.py +4 -6
- fastmcp/server/auth/providers/github.py +14 -8
- fastmcp/server/auth/providers/google.py +15 -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 +17 -14
- fastmcp/server/context.py +89 -34
- fastmcp/server/http.py +57 -17
- 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/proxy.py +6 -6
- fastmcp/server/server.py +638 -183
- fastmcp/settings.py +22 -9
- fastmcp/tools/tool.py +7 -3
- fastmcp/tools/tool_manager.py +22 -108
- fastmcp/tools/tool_transform.py +3 -3
- fastmcp/utilities/cli.py +32 -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 +497 -0
- {fastmcp-2.12.5.dist-info → fastmcp-2.13.0rc2.dist-info}/METADATA +8 -4
- {fastmcp-2.12.5.dist-info → fastmcp-2.13.0rc2.dist-info}/RECORD +66 -62
- fastmcp/cli/claude.py +0 -135
- fastmcp/utilities/storage.py +0 -204
- {fastmcp-2.12.5.dist-info → fastmcp-2.13.0rc2.dist-info}/WHEEL +0 -0
- {fastmcp-2.12.5.dist-info → fastmcp-2.13.0rc2.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.12.5.dist-info → fastmcp-2.13.0rc2.dist-info}/licenses/LICENSE +0 -0
fastmcp/settings.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations as _annotations
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
+
import os
|
|
4
5
|
import warnings
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import TYPE_CHECKING, Annotated, Any, Literal
|
|
@@ -19,10 +20,14 @@ from fastmcp.utilities.logging import get_logger
|
|
|
19
20
|
|
|
20
21
|
logger = get_logger(__name__)
|
|
21
22
|
|
|
23
|
+
ENV_FILE = os.getenv("FASTMCP_ENV_FILE", ".env")
|
|
24
|
+
|
|
22
25
|
LOG_LEVEL = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
23
26
|
|
|
24
27
|
DuplicateBehavior = Literal["warn", "error", "replace", "ignore"]
|
|
25
28
|
|
|
29
|
+
TEN_MB_IN_BYTES = 1024 * 1024 * 10
|
|
30
|
+
|
|
26
31
|
if TYPE_CHECKING:
|
|
27
32
|
from fastmcp.server.auth.auth import AuthProvider
|
|
28
33
|
|
|
@@ -82,7 +87,7 @@ class Settings(BaseSettings):
|
|
|
82
87
|
|
|
83
88
|
model_config = ExtendedSettingsConfigDict(
|
|
84
89
|
env_prefixes=["FASTMCP_", "FASTMCP_SERVER_"],
|
|
85
|
-
env_file=
|
|
90
|
+
env_file=ENV_FILE,
|
|
86
91
|
extra="ignore",
|
|
87
92
|
env_nested_delimiter="__",
|
|
88
93
|
nested_model_default_partial_update=True,
|
|
@@ -189,7 +194,6 @@ class Settings(BaseSettings):
|
|
|
189
194
|
client_raise_first_exceptiongroup_error: Annotated[
|
|
190
195
|
bool,
|
|
191
196
|
Field(
|
|
192
|
-
default=True,
|
|
193
197
|
description=inspect.cleandoc(
|
|
194
198
|
"""
|
|
195
199
|
Many MCP components operate in anyio taskgroups, and raise
|
|
@@ -205,7 +209,6 @@ class Settings(BaseSettings):
|
|
|
205
209
|
resource_prefix_format: Annotated[
|
|
206
210
|
Literal["protocol", "path"],
|
|
207
211
|
Field(
|
|
208
|
-
default="path",
|
|
209
212
|
description=inspect.cleandoc(
|
|
210
213
|
"""
|
|
211
214
|
When perfixing a resource URI, either use path formatting (resource://prefix/path)
|
|
@@ -235,7 +238,6 @@ class Settings(BaseSettings):
|
|
|
235
238
|
mask_error_details: Annotated[
|
|
236
239
|
bool,
|
|
237
240
|
Field(
|
|
238
|
-
default=False,
|
|
239
241
|
description=inspect.cleandoc(
|
|
240
242
|
"""
|
|
241
243
|
If True, error details from user-supplied functions (tool, resource, prompt)
|
|
@@ -248,6 +250,22 @@ class Settings(BaseSettings):
|
|
|
248
250
|
),
|
|
249
251
|
] = False
|
|
250
252
|
|
|
253
|
+
strict_input_validation: Annotated[
|
|
254
|
+
bool,
|
|
255
|
+
Field(
|
|
256
|
+
description=inspect.cleandoc(
|
|
257
|
+
"""
|
|
258
|
+
If True, tool inputs are strictly validated against the input
|
|
259
|
+
JSON schema. For example, providing the string \"10\" to an
|
|
260
|
+
integer field will raise an error. If False, compatible inputs
|
|
261
|
+
will be coerced to match the schema, which can increase
|
|
262
|
+
compatibility. For example, providing the string \"10\" to an
|
|
263
|
+
integer field will be coerced to 10. Defaults to False.
|
|
264
|
+
"""
|
|
265
|
+
),
|
|
266
|
+
),
|
|
267
|
+
] = False
|
|
268
|
+
|
|
251
269
|
server_dependencies: list[str] = Field(
|
|
252
270
|
default_factory=list,
|
|
253
271
|
description="List of dependencies to install in the server environment",
|
|
@@ -293,7 +311,6 @@ class Settings(BaseSettings):
|
|
|
293
311
|
include_tags: Annotated[
|
|
294
312
|
set[str] | None,
|
|
295
313
|
Field(
|
|
296
|
-
default=None,
|
|
297
314
|
description=inspect.cleandoc(
|
|
298
315
|
"""
|
|
299
316
|
If provided, only components that match these tags will be
|
|
@@ -306,7 +323,6 @@ class Settings(BaseSettings):
|
|
|
306
323
|
exclude_tags: Annotated[
|
|
307
324
|
set[str] | None,
|
|
308
325
|
Field(
|
|
309
|
-
default=None,
|
|
310
326
|
description=inspect.cleandoc(
|
|
311
327
|
"""
|
|
312
328
|
If provided, components that match these tags will be excluded
|
|
@@ -320,7 +336,6 @@ class Settings(BaseSettings):
|
|
|
320
336
|
include_fastmcp_meta: Annotated[
|
|
321
337
|
bool,
|
|
322
338
|
Field(
|
|
323
|
-
default=True,
|
|
324
339
|
description=inspect.cleandoc(
|
|
325
340
|
"""
|
|
326
341
|
Whether to include FastMCP meta in the server's MCP responses.
|
|
@@ -335,7 +350,6 @@ class Settings(BaseSettings):
|
|
|
335
350
|
mounted_components_raise_on_load_error: Annotated[
|
|
336
351
|
bool,
|
|
337
352
|
Field(
|
|
338
|
-
default=False,
|
|
339
353
|
description=inspect.cleandoc(
|
|
340
354
|
"""
|
|
341
355
|
If True, errors encountered when loading mounted components (tools, resources, prompts)
|
|
@@ -349,7 +363,6 @@ class Settings(BaseSettings):
|
|
|
349
363
|
show_cli_banner: Annotated[
|
|
350
364
|
bool,
|
|
351
365
|
Field(
|
|
352
|
-
default=True,
|
|
353
366
|
description=inspect.cleandoc(
|
|
354
367
|
"""
|
|
355
368
|
If True, the server banner will be displayed when running the server via CLI.
|
fastmcp/tools/tool.py
CHANGED
|
@@ -16,7 +16,7 @@ from typing import (
|
|
|
16
16
|
|
|
17
17
|
import mcp.types
|
|
18
18
|
import pydantic_core
|
|
19
|
-
from mcp.types import ContentBlock, TextContent, ToolAnnotations
|
|
19
|
+
from mcp.types import ContentBlock, Icon, TextContent, ToolAnnotations
|
|
20
20
|
from mcp.types import Tool as MCPTool
|
|
21
21
|
from pydantic import Field, PydanticSchemaGenerationError
|
|
22
22
|
from typing_extensions import TypeVar
|
|
@@ -156,6 +156,7 @@ class Tool(FastMCPComponent):
|
|
|
156
156
|
description=overrides.get("description", self.description),
|
|
157
157
|
inputSchema=overrides.get("inputSchema", self.parameters),
|
|
158
158
|
outputSchema=overrides.get("outputSchema", self.output_schema),
|
|
159
|
+
icons=overrides.get("icons", self.icons),
|
|
159
160
|
annotations=overrides.get("annotations", self.annotations),
|
|
160
161
|
_meta=overrides.get(
|
|
161
162
|
"_meta", self.get_meta(include_fastmcp_meta=include_fastmcp_meta)
|
|
@@ -168,6 +169,7 @@ class Tool(FastMCPComponent):
|
|
|
168
169
|
name: str | None = None,
|
|
169
170
|
title: str | None = None,
|
|
170
171
|
description: str | None = None,
|
|
172
|
+
icons: list[Icon] | None = None,
|
|
171
173
|
tags: set[str] | None = None,
|
|
172
174
|
annotations: ToolAnnotations | None = None,
|
|
173
175
|
exclude_args: list[str] | None = None,
|
|
@@ -182,6 +184,7 @@ class Tool(FastMCPComponent):
|
|
|
182
184
|
name=name,
|
|
183
185
|
title=title,
|
|
184
186
|
description=description,
|
|
187
|
+
icons=icons,
|
|
185
188
|
tags=tags,
|
|
186
189
|
annotations=annotations,
|
|
187
190
|
exclude_args=exclude_args,
|
|
@@ -248,6 +251,7 @@ class FunctionTool(Tool):
|
|
|
248
251
|
name: str | None = None,
|
|
249
252
|
title: str | None = None,
|
|
250
253
|
description: str | None = None,
|
|
254
|
+
icons: list[Icon] | None = None,
|
|
251
255
|
tags: set[str] | None = None,
|
|
252
256
|
annotations: ToolAnnotations | None = None,
|
|
253
257
|
exclude_args: list[str] | None = None,
|
|
@@ -291,6 +295,7 @@ class FunctionTool(Tool):
|
|
|
291
295
|
name=name or parsed_fn.name,
|
|
292
296
|
title=title,
|
|
293
297
|
description=description or parsed_fn.description,
|
|
298
|
+
icons=icons,
|
|
294
299
|
parameters=parsed_fn.input_schema,
|
|
295
300
|
output_schema=final_output_schema,
|
|
296
301
|
annotations=annotations,
|
|
@@ -546,13 +551,12 @@ def _convert_to_content(
|
|
|
546
551
|
|
|
547
552
|
# If any item is a ContentBlock, convert non-ContentBlock items to TextContent
|
|
548
553
|
# without aggregating them
|
|
549
|
-
if any(isinstance(item, ContentBlock) for item in result):
|
|
554
|
+
if any(isinstance(item, ContentBlock | Image | Audio | File) for item in result):
|
|
550
555
|
return [
|
|
551
556
|
_convert_to_single_content_block(item, serializer)
|
|
552
557
|
if not isinstance(item, ContentBlock)
|
|
553
558
|
else item
|
|
554
559
|
for item in result
|
|
555
560
|
]
|
|
556
|
-
|
|
557
561
|
# If none of the items are ContentBlocks, aggregate all items into a single TextContent
|
|
558
562
|
return [TextContent(type="text", text=_serialize_with_fallback(result, serializer))]
|
fastmcp/tools/tool_manager.py
CHANGED
|
@@ -2,9 +2,10 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import warnings
|
|
4
4
|
from collections.abc import Callable
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import Any
|
|
6
6
|
|
|
7
7
|
from mcp.types import ToolAnnotations
|
|
8
|
+
from pydantic import ValidationError
|
|
8
9
|
|
|
9
10
|
from fastmcp import settings
|
|
10
11
|
from fastmcp.exceptions import NotFoundError, ToolError
|
|
@@ -16,9 +17,6 @@ from fastmcp.tools.tool_transform import (
|
|
|
16
17
|
)
|
|
17
18
|
from fastmcp.utilities.logging import get_logger
|
|
18
19
|
|
|
19
|
-
if TYPE_CHECKING:
|
|
20
|
-
from fastmcp.server.server import MountedServer
|
|
21
|
-
|
|
22
20
|
logger = get_logger(__name__)
|
|
23
21
|
|
|
24
22
|
|
|
@@ -32,7 +30,6 @@ class ToolManager:
|
|
|
32
30
|
transformations: dict[str, ToolTransformConfig] | None = None,
|
|
33
31
|
):
|
|
34
32
|
self._tools: dict[str, Tool] = {}
|
|
35
|
-
self._mounted_servers: list[MountedServer] = []
|
|
36
33
|
self.mask_error_details = mask_error_details or settings.mask_error_details
|
|
37
34
|
self.transformations = transformations or {}
|
|
38
35
|
|
|
@@ -48,56 +45,12 @@ class ToolManager:
|
|
|
48
45
|
|
|
49
46
|
self.duplicate_behavior = duplicate_behavior
|
|
50
47
|
|
|
51
|
-
def
|
|
52
|
-
"""
|
|
53
|
-
self._mounted_servers.append(server)
|
|
54
|
-
|
|
55
|
-
async def _load_tools(self, *, via_server: bool = False) -> dict[str, Tool]:
|
|
56
|
-
"""
|
|
57
|
-
The single, consolidated recursive method for fetching tools. The 'via_server'
|
|
58
|
-
parameter determines the communication path.
|
|
59
|
-
|
|
60
|
-
- via_server=False: Manager-to-manager path for complete, unfiltered inventory
|
|
61
|
-
- via_server=True: Server-to-server path for filtered MCP requests
|
|
62
|
-
"""
|
|
63
|
-
all_tools: dict[str, Tool] = {}
|
|
64
|
-
|
|
65
|
-
for mounted in self._mounted_servers:
|
|
66
|
-
try:
|
|
67
|
-
if via_server:
|
|
68
|
-
# Use the server-to-server filtered path
|
|
69
|
-
child_results = await mounted.server._list_tools()
|
|
70
|
-
else:
|
|
71
|
-
# Use the manager-to-manager unfiltered path
|
|
72
|
-
child_results = await mounted.server._tool_manager.list_tools()
|
|
73
|
-
|
|
74
|
-
# The combination logic is the same for both paths
|
|
75
|
-
child_dict = {t.key: t for t in child_results}
|
|
76
|
-
if mounted.prefix:
|
|
77
|
-
for tool in child_dict.values():
|
|
78
|
-
prefixed_tool = tool.model_copy(
|
|
79
|
-
key=f"{mounted.prefix}_{tool.key}"
|
|
80
|
-
)
|
|
81
|
-
all_tools[prefixed_tool.key] = prefixed_tool
|
|
82
|
-
else:
|
|
83
|
-
all_tools.update(child_dict)
|
|
84
|
-
except Exception as e:
|
|
85
|
-
# Skip failed mounts silently, matches existing behavior
|
|
86
|
-
logger.warning(
|
|
87
|
-
f"Failed to get tools from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
|
|
88
|
-
)
|
|
89
|
-
if settings.mounted_components_raise_on_load_error:
|
|
90
|
-
raise
|
|
91
|
-
continue
|
|
92
|
-
|
|
93
|
-
# Finally, add local tools, which always take precedence
|
|
94
|
-
all_tools.update(self._tools)
|
|
95
|
-
|
|
48
|
+
async def _load_tools(self) -> dict[str, Tool]:
|
|
49
|
+
"""Return this manager's local tools with transformations applied."""
|
|
96
50
|
transformed_tools = apply_transformations_to_tools(
|
|
97
|
-
tools=
|
|
51
|
+
tools=self._tools,
|
|
98
52
|
transformations=self.transformations,
|
|
99
53
|
)
|
|
100
|
-
|
|
101
54
|
return transformed_tools
|
|
102
55
|
|
|
103
56
|
async def has_tool(self, key: str) -> bool:
|
|
@@ -114,25 +67,9 @@ class ToolManager:
|
|
|
114
67
|
|
|
115
68
|
async def get_tools(self) -> dict[str, Tool]:
|
|
116
69
|
"""
|
|
117
|
-
Gets the complete, unfiltered inventory of
|
|
118
|
-
"""
|
|
119
|
-
return await self._load_tools(via_server=False)
|
|
120
|
-
|
|
121
|
-
async def list_tools(self) -> list[Tool]:
|
|
122
|
-
"""
|
|
123
|
-
Lists all tools, applying protocol filtering.
|
|
70
|
+
Gets the complete, unfiltered inventory of local tools.
|
|
124
71
|
"""
|
|
125
|
-
|
|
126
|
-
return list(tools_dict.values())
|
|
127
|
-
|
|
128
|
-
@property
|
|
129
|
-
def _tools_transformed(self) -> list[str]:
|
|
130
|
-
"""Get the local tools."""
|
|
131
|
-
|
|
132
|
-
return [
|
|
133
|
-
transformation.name or tool_name
|
|
134
|
-
for tool_name, transformation in self.transformations.items()
|
|
135
|
-
]
|
|
72
|
+
return await self._load_tools()
|
|
136
73
|
|
|
137
74
|
def add_tool_from_fn(
|
|
138
75
|
self,
|
|
@@ -214,41 +151,18 @@ class ToolManager:
|
|
|
214
151
|
Internal API for servers: Finds and calls a tool, respecting the
|
|
215
152
|
filtered protocol path.
|
|
216
153
|
"""
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
raise e
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
except Exception as e:
|
|
233
|
-
logger.exception(f"Error calling tool {key!r}")
|
|
234
|
-
if self.mask_error_details:
|
|
235
|
-
# Mask internal details
|
|
236
|
-
raise ToolError(f"Error calling tool {key!r}") from e
|
|
237
|
-
else:
|
|
238
|
-
# Include original error details
|
|
239
|
-
raise ToolError(f"Error calling tool {key!r}: {e}") from e
|
|
240
|
-
|
|
241
|
-
# 2. Check mounted servers using the filtered protocol path.
|
|
242
|
-
for mounted in reversed(self._mounted_servers):
|
|
243
|
-
tool_key = key
|
|
244
|
-
if mounted.prefix:
|
|
245
|
-
if key.startswith(f"{mounted.prefix}_"):
|
|
246
|
-
tool_key = key.removeprefix(f"{mounted.prefix}_")
|
|
247
|
-
else:
|
|
248
|
-
continue
|
|
249
|
-
try:
|
|
250
|
-
return await mounted.server._call_tool(tool_key, arguments)
|
|
251
|
-
except NotFoundError:
|
|
252
|
-
continue
|
|
253
|
-
|
|
254
|
-
raise NotFoundError(f"Tool {key!r} not found.")
|
|
154
|
+
tool = await self.get_tool(key)
|
|
155
|
+
try:
|
|
156
|
+
return await tool.run(arguments)
|
|
157
|
+
except ValidationError as e:
|
|
158
|
+
logger.exception(f"Error validating tool {key!r}: {e}")
|
|
159
|
+
raise e
|
|
160
|
+
except ToolError as e:
|
|
161
|
+
logger.exception(f"Error calling tool {key!r}")
|
|
162
|
+
raise e
|
|
163
|
+
except Exception as e:
|
|
164
|
+
logger.exception(f"Error calling tool {key!r}")
|
|
165
|
+
if self.mask_error_details:
|
|
166
|
+
raise ToolError(f"Error calling tool {key!r}") from e
|
|
167
|
+
else:
|
|
168
|
+
raise ToolError(f"Error calling tool {key!r}: {e}") from e
|
fastmcp/tools/tool_transform.py
CHANGED
|
@@ -34,7 +34,7 @@ _current_tool: ContextVar[TransformedTool | None] = ContextVar( # type: ignore[
|
|
|
34
34
|
)
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
async def forward(**kwargs) -> ToolResult:
|
|
37
|
+
async def forward(**kwargs: Any) -> ToolResult:
|
|
38
38
|
"""Forward to parent tool with argument transformation applied.
|
|
39
39
|
|
|
40
40
|
This function can only be called from within a transformed tool's custom
|
|
@@ -64,7 +64,7 @@ async def forward(**kwargs) -> ToolResult:
|
|
|
64
64
|
return await tool.forwarding_fn(**kwargs)
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
async def forward_raw(**kwargs) -> ToolResult:
|
|
67
|
+
async def forward_raw(**kwargs: Any) -> ToolResult:
|
|
68
68
|
"""Forward directly to parent tool without transformation.
|
|
69
69
|
|
|
70
70
|
This function bypasses all argument transformation and validation, calling the parent
|
|
@@ -681,7 +681,7 @@ class TransformedTool(Tool):
|
|
|
681
681
|
schema = compress_schema(schema, prune_defs=True)
|
|
682
682
|
|
|
683
683
|
# Create forwarding function that closes over everything it needs
|
|
684
|
-
async def _forward(**kwargs):
|
|
684
|
+
async def _forward(**kwargs: Any):
|
|
685
685
|
# Validate arguments
|
|
686
686
|
valid_args = set(new_props.keys())
|
|
687
687
|
provided_args = set(kwargs.keys())
|
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,26 @@ _ __ ___ /_/ \____/____/\__/_/ /_/\____/_/ /_____(*)____/
|
|
|
147
146
|
|
|
148
147
|
""".lstrip("\n")
|
|
149
148
|
|
|
149
|
+
# This prints the below in a blue gradient
|
|
150
|
+
# █▀▀ ▄▀█ █▀▀ ▀█▀ █▀▄▀█ █▀▀ █▀█
|
|
151
|
+
# █▀ █▀█ ▄▄█ █ █ ▀ █ █▄▄ █▀▀
|
|
152
|
+
LOGO_ASCII_2 = (
|
|
153
|
+
"\033[38;2;0;198;255m \033[38;2;0;195;255m█\033[38;2;0;192;255m▀\033[38;2;0;189;255m▀\033[38;2;0;186;255m "
|
|
154
|
+
"\033[38;2;0;184;255m▄\033[38;2;0;181;255m▀\033[38;2;0;178;255m█\033[38;2;0;175;255m "
|
|
155
|
+
"\033[38;2;0;172;255m█\033[38;2;0;169;255m▀\033[38;2;0;166;255m▀\033[38;2;0;163;255m "
|
|
156
|
+
"\033[38;2;0;160;255m▀\033[38;2;0;157;255m█\033[38;2;0;155;255m▀\033[38;2;0;152;255m "
|
|
157
|
+
"\033[38;2;0;149;255m█\033[38;2;0;146;255m▀\033[38;2;0;143;255m▄\033[38;2;0;140;255m▀\033[38;2;0;137;255m█\033[38;2;0;134;255m "
|
|
158
|
+
"\033[38;2;0;131;255m█\033[38;2;0;128;255m▀\033[38;2;0;126;255m▀\033[38;2;0;123;255m "
|
|
159
|
+
"\033[38;2;0;120;255m█\033[38;2;0;117;255m▀\033[38;2;0;114;255m█\033[39m\n"
|
|
160
|
+
"\033[38;2;0;198;255m \033[38;2;0;195;255m█\033[38;2;0;192;255m▀\033[38;2;0;189;255m \033[38;2;0;186;255m "
|
|
161
|
+
"\033[38;2;0;184;255m█\033[38;2;0;181;255m▀\033[38;2;0;178;255m█\033[38;2;0;175;255m "
|
|
162
|
+
"\033[38;2;0;172;255m▄\033[38;2;0;169;255m▄\033[38;2;0;166;255m█\033[38;2;0;163;255m "
|
|
163
|
+
"\033[38;2;0;160;255m \033[38;2;0;157;255m█\033[38;2;0;155;255m \033[38;2;0;152;255m "
|
|
164
|
+
"\033[38;2;0;149;255m█\033[38;2;0;146;255m \033[38;2;0;143;255m▀\033[38;2;0;140;255m \033[38;2;0;137;255m█\033[38;2;0;134;255m "
|
|
165
|
+
"\033[38;2;0;131;255m█\033[38;2;0;128;255m▄\033[38;2;0;126;255m▄\033[38;2;0;123;255m "
|
|
166
|
+
"\033[38;2;0;120;255m█\033[38;2;0;117;255m▀\033[38;2;0;114;255m▀\033[39m"
|
|
167
|
+
).strip()
|
|
168
|
+
|
|
150
169
|
|
|
151
170
|
def log_server_banner(
|
|
152
171
|
server: FastMCP[Any],
|
|
@@ -167,10 +186,11 @@ def log_server_banner(
|
|
|
167
186
|
"""
|
|
168
187
|
|
|
169
188
|
# Create the logo text
|
|
170
|
-
|
|
189
|
+
# Use Text with no_wrap and markup disabled to preserve ANSI escape codes
|
|
190
|
+
logo_text = Text.from_ansi(LOGO_ASCII_2, no_wrap=True)
|
|
171
191
|
|
|
172
192
|
# Create the main title
|
|
173
|
-
title_text = Text("FastMCP
|
|
193
|
+
title_text = Text(f"FastMCP {fastmcp.__version__}", style="bold blue")
|
|
174
194
|
|
|
175
195
|
# Create the information table
|
|
176
196
|
info_table = Table.grid(padding=(0, 1))
|
|
@@ -180,13 +200,13 @@ def log_server_banner(
|
|
|
180
200
|
|
|
181
201
|
match transport:
|
|
182
202
|
case "http" | "streamable-http":
|
|
183
|
-
display_transport = "
|
|
203
|
+
display_transport = "HTTP"
|
|
184
204
|
case "sse":
|
|
185
205
|
display_transport = "SSE"
|
|
186
206
|
case "stdio":
|
|
187
207
|
display_transport = "STDIO"
|
|
188
208
|
|
|
189
|
-
info_table.add_row("
|
|
209
|
+
info_table.add_row("🖥", "Server name:", Text(server.name + "\n", style="bold blue"))
|
|
190
210
|
info_table.add_row("📦", "Transport:", display_transport)
|
|
191
211
|
|
|
192
212
|
# Show connection info based on transport
|
|
@@ -197,27 +217,15 @@ def log_server_banner(
|
|
|
197
217
|
server_url += f"/{path.lstrip('/')}"
|
|
198
218
|
info_table.add_row("🔗", "Server URL:", server_url)
|
|
199
219
|
|
|
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
|
-
)
|
|
212
|
-
|
|
213
220
|
# Add documentation link
|
|
214
221
|
info_table.add_row("", "", "")
|
|
215
222
|
info_table.add_row("📚", "Docs:", "https://gofastmcp.com")
|
|
216
|
-
info_table.add_row("🚀", "
|
|
223
|
+
info_table.add_row("🚀", "Hosting:", "https://fastmcp.cloud")
|
|
217
224
|
|
|
218
225
|
# Create panel with logo, title, and information using Group
|
|
219
226
|
panel_content = Group(
|
|
220
227
|
Align.center(logo_text),
|
|
228
|
+
"",
|
|
221
229
|
Align.center(title_text),
|
|
222
230
|
"",
|
|
223
231
|
"",
|
|
@@ -228,8 +236,10 @@ def log_server_banner(
|
|
|
228
236
|
panel_content,
|
|
229
237
|
border_style="dim",
|
|
230
238
|
padding=(1, 4),
|
|
231
|
-
expand=False,
|
|
239
|
+
# expand=False,
|
|
240
|
+
width=80, # Set max width for the panel
|
|
232
241
|
)
|
|
233
242
|
|
|
234
243
|
console = Console(stderr=True)
|
|
235
|
-
|
|
244
|
+
# Center the panel itself
|
|
245
|
+
console.print(Group("\n", Align.center(panel), "\n"))
|
fastmcp/utilities/components.py
CHANGED
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from collections.abc import Sequence
|
|
4
4
|
from typing import Annotated, Any, TypedDict
|
|
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.",
|