fastmcp 2.13.0rc2__py3-none-any.whl → 2.13.0.1__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 -2
- fastmcp/cli/cli.py +3 -2
- fastmcp/cli/install/claude_code.py +3 -3
- fastmcp/client/__init__.py +9 -9
- fastmcp/client/auth/oauth.py +7 -6
- fastmcp/client/client.py +10 -10
- fastmcp/client/oauth_callback.py +6 -2
- fastmcp/client/sampling.py +1 -1
- fastmcp/client/transports.py +35 -34
- fastmcp/contrib/component_manager/__init__.py +1 -1
- fastmcp/contrib/component_manager/component_manager.py +2 -2
- fastmcp/contrib/mcp_mixin/__init__.py +2 -2
- fastmcp/experimental/sampling/handlers/openai.py +2 -2
- fastmcp/experimental/server/openapi/__init__.py +5 -8
- fastmcp/experimental/server/openapi/components.py +11 -7
- fastmcp/experimental/server/openapi/routing.py +2 -2
- fastmcp/experimental/utilities/openapi/__init__.py +10 -15
- fastmcp/experimental/utilities/openapi/director.py +1 -1
- fastmcp/experimental/utilities/openapi/json_schema_converter.py +2 -2
- fastmcp/experimental/utilities/openapi/models.py +3 -3
- fastmcp/experimental/utilities/openapi/parser.py +3 -5
- fastmcp/experimental/utilities/openapi/schemas.py +2 -2
- fastmcp/mcp_config.py +2 -3
- fastmcp/prompts/__init__.py +1 -1
- fastmcp/prompts/prompt.py +9 -13
- fastmcp/resources/__init__.py +5 -5
- fastmcp/resources/resource.py +1 -3
- fastmcp/resources/resource_manager.py +1 -1
- fastmcp/resources/types.py +30 -24
- fastmcp/server/__init__.py +1 -1
- fastmcp/server/auth/__init__.py +5 -5
- fastmcp/server/auth/auth.py +2 -2
- fastmcp/server/auth/handlers/authorize.py +324 -0
- fastmcp/server/auth/jwt_issuer.py +39 -92
- fastmcp/server/auth/middleware.py +96 -0
- fastmcp/server/auth/oauth_proxy.py +236 -217
- fastmcp/server/auth/oidc_proxy.py +18 -3
- fastmcp/server/auth/providers/auth0.py +28 -15
- fastmcp/server/auth/providers/aws.py +16 -1
- fastmcp/server/auth/providers/azure.py +101 -40
- fastmcp/server/auth/providers/bearer.py +1 -1
- fastmcp/server/auth/providers/github.py +16 -1
- fastmcp/server/auth/providers/google.py +16 -1
- fastmcp/server/auth/providers/in_memory.py +2 -2
- fastmcp/server/auth/providers/introspection.py +2 -2
- fastmcp/server/auth/providers/jwt.py +17 -18
- fastmcp/server/auth/providers/supabase.py +1 -1
- fastmcp/server/auth/providers/workos.py +18 -3
- fastmcp/server/context.py +41 -12
- fastmcp/server/dependencies.py +5 -6
- fastmcp/server/elicitation.py +1 -1
- fastmcp/server/http.py +3 -4
- fastmcp/server/middleware/__init__.py +1 -1
- fastmcp/server/middleware/caching.py +1 -1
- fastmcp/server/middleware/error_handling.py +8 -8
- fastmcp/server/middleware/middleware.py +1 -1
- fastmcp/server/middleware/tool_injection.py +116 -0
- fastmcp/server/openapi.py +10 -6
- fastmcp/server/proxy.py +5 -4
- fastmcp/server/server.py +74 -55
- fastmcp/settings.py +2 -1
- fastmcp/tools/__init__.py +1 -1
- fastmcp/tools/tool.py +12 -12
- fastmcp/tools/tool_manager.py +8 -4
- fastmcp/tools/tool_transform.py +6 -6
- fastmcp/utilities/cli.py +50 -21
- fastmcp/utilities/inspect.py +2 -2
- fastmcp/utilities/json_schema_type.py +4 -4
- fastmcp/utilities/logging.py +14 -18
- 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/sources/base.py +0 -1
- fastmcp/utilities/openapi.py +9 -9
- fastmcp/utilities/tests.py +2 -4
- fastmcp/utilities/ui.py +126 -6
- {fastmcp-2.13.0rc2.dist-info → fastmcp-2.13.0.1.dist-info}/METADATA +5 -5
- fastmcp-2.13.0.1.dist-info/RECORD +141 -0
- fastmcp-2.13.0rc2.dist-info/RECORD +0 -138
- {fastmcp-2.13.0rc2.dist-info → fastmcp-2.13.0.1.dist-info}/WHEEL +0 -0
- {fastmcp-2.13.0rc2.dist-info → fastmcp-2.13.0.1.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.13.0rc2.dist-info → fastmcp-2.13.0.1.dist-info}/licenses/LICENSE +0 -0
fastmcp/server/server.py
CHANGED
|
@@ -7,8 +7,19 @@ import json
|
|
|
7
7
|
import re
|
|
8
8
|
import secrets
|
|
9
9
|
import warnings
|
|
10
|
-
from collections.abc import
|
|
11
|
-
|
|
10
|
+
from collections.abc import (
|
|
11
|
+
AsyncIterator,
|
|
12
|
+
Awaitable,
|
|
13
|
+
Callable,
|
|
14
|
+
Collection,
|
|
15
|
+
Mapping,
|
|
16
|
+
Sequence,
|
|
17
|
+
)
|
|
18
|
+
from contextlib import (
|
|
19
|
+
AbstractAsyncContextManager,
|
|
20
|
+
AsyncExitStack,
|
|
21
|
+
asynccontextmanager,
|
|
22
|
+
)
|
|
12
23
|
from dataclasses import dataclass
|
|
13
24
|
from functools import partial
|
|
14
25
|
from pathlib import Path
|
|
@@ -43,9 +54,11 @@ import fastmcp
|
|
|
43
54
|
import fastmcp.server
|
|
44
55
|
from fastmcp.exceptions import DisabledError, NotFoundError
|
|
45
56
|
from fastmcp.mcp_config import MCPConfig
|
|
46
|
-
from fastmcp.prompts import Prompt
|
|
57
|
+
from fastmcp.prompts import Prompt
|
|
47
58
|
from fastmcp.prompts.prompt import FunctionPrompt
|
|
48
|
-
from fastmcp.
|
|
59
|
+
from fastmcp.prompts.prompt_manager import PromptManager
|
|
60
|
+
from fastmcp.resources.resource import Resource
|
|
61
|
+
from fastmcp.resources.resource_manager import ResourceManager
|
|
49
62
|
from fastmcp.resources.template import ResourceTemplate
|
|
50
63
|
from fastmcp.server.auth import AuthProvider
|
|
51
64
|
from fastmcp.server.http import (
|
|
@@ -56,8 +69,8 @@ from fastmcp.server.http import (
|
|
|
56
69
|
from fastmcp.server.low_level import LowLevelServer
|
|
57
70
|
from fastmcp.server.middleware import Middleware, MiddlewareContext
|
|
58
71
|
from fastmcp.settings import Settings
|
|
59
|
-
from fastmcp.tools import ToolManager
|
|
60
72
|
from fastmcp.tools.tool import FunctionTool, Tool, ToolResult
|
|
73
|
+
from fastmcp.tools.tool_manager import ToolManager
|
|
61
74
|
from fastmcp.tools.tool_transform import ToolTransformConfig
|
|
62
75
|
from fastmcp.utilities.cli import log_server_banner
|
|
63
76
|
from fastmcp.utilities.components import FastMCPComponent
|
|
@@ -66,7 +79,6 @@ from fastmcp.utilities.types import NotSet, NotSetT
|
|
|
66
79
|
|
|
67
80
|
if TYPE_CHECKING:
|
|
68
81
|
from fastmcp.client import Client
|
|
69
|
-
from fastmcp.client.sampling import ServerSamplingHandler
|
|
70
82
|
from fastmcp.client.transports import ClientTransport, ClientTransportT
|
|
71
83
|
from fastmcp.experimental.server.openapi import FastMCPOpenAPI as FastMCPOpenAPINew
|
|
72
84
|
from fastmcp.experimental.server.openapi.routing import (
|
|
@@ -80,6 +92,8 @@ if TYPE_CHECKING:
|
|
|
80
92
|
from fastmcp.server.openapi import FastMCPOpenAPI, RouteMap
|
|
81
93
|
from fastmcp.server.openapi import RouteMapFn as OpenAPIRouteMapFn
|
|
82
94
|
from fastmcp.server.proxy import FastMCPProxy
|
|
95
|
+
from fastmcp.server.sampling.handler import ServerSamplingHandler
|
|
96
|
+
from fastmcp.tools.tool import ToolResultSerializerType
|
|
83
97
|
|
|
84
98
|
logger = get_logger(__name__)
|
|
85
99
|
|
|
@@ -140,17 +154,17 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
140
154
|
version: str | None = None,
|
|
141
155
|
website_url: str | None = None,
|
|
142
156
|
icons: list[mcp.types.Icon] | None = None,
|
|
143
|
-
auth: AuthProvider |
|
|
144
|
-
middleware:
|
|
157
|
+
auth: AuthProvider | NotSetT | None = NotSet,
|
|
158
|
+
middleware: Sequence[Middleware] | None = None,
|
|
145
159
|
lifespan: LifespanCallable | None = None,
|
|
146
160
|
dependencies: list[str] | None = None,
|
|
147
161
|
resource_prefix_format: Literal["protocol", "path"] | None = None,
|
|
148
162
|
mask_error_details: bool | None = None,
|
|
149
|
-
tools:
|
|
150
|
-
tool_transformations:
|
|
151
|
-
tool_serializer:
|
|
152
|
-
include_tags:
|
|
153
|
-
exclude_tags:
|
|
163
|
+
tools: Sequence[Tool | Callable[..., Any]] | None = None,
|
|
164
|
+
tool_transformations: Mapping[str, ToolTransformConfig] | None = None,
|
|
165
|
+
tool_serializer: ToolResultSerializerType | None = None,
|
|
166
|
+
include_tags: Collection[str] | None = None,
|
|
167
|
+
exclude_tags: Collection[str] | None = None,
|
|
154
168
|
include_fastmcp_meta: bool | None = None,
|
|
155
169
|
on_duplicate_tools: DuplicateBehavior | None = None,
|
|
156
170
|
on_duplicate_resources: DuplicateBehavior | None = None,
|
|
@@ -179,27 +193,29 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
179
193
|
|
|
180
194
|
self._additional_http_routes: list[BaseRoute] = []
|
|
181
195
|
self._mounted_servers: list[MountedServer] = []
|
|
182
|
-
self._tool_manager = ToolManager(
|
|
196
|
+
self._tool_manager: ToolManager = ToolManager(
|
|
183
197
|
duplicate_behavior=on_duplicate_tools,
|
|
184
198
|
mask_error_details=mask_error_details,
|
|
185
199
|
transformations=tool_transformations,
|
|
186
200
|
)
|
|
187
|
-
self._resource_manager = ResourceManager(
|
|
201
|
+
self._resource_manager: ResourceManager = ResourceManager(
|
|
188
202
|
duplicate_behavior=on_duplicate_resources,
|
|
189
203
|
mask_error_details=mask_error_details,
|
|
190
204
|
)
|
|
191
|
-
self._prompt_manager = PromptManager(
|
|
205
|
+
self._prompt_manager: PromptManager = PromptManager(
|
|
192
206
|
duplicate_behavior=on_duplicate_prompts,
|
|
193
207
|
mask_error_details=mask_error_details,
|
|
194
208
|
)
|
|
195
|
-
self._tool_serializer = tool_serializer
|
|
209
|
+
self._tool_serializer: Callable[[Any], str] | None = tool_serializer
|
|
196
210
|
|
|
197
211
|
self._lifespan: LifespanCallable[LifespanResultT] = lifespan or default_lifespan
|
|
198
212
|
self._lifespan_result: LifespanResultT | None = None
|
|
199
|
-
self._lifespan_result_set = False
|
|
213
|
+
self._lifespan_result_set: bool = False
|
|
200
214
|
|
|
201
215
|
# Generate random ID if no name provided
|
|
202
|
-
self._mcp_server = LowLevelServer[
|
|
216
|
+
self._mcp_server: LowLevelServer[LifespanResultT, Any] = LowLevelServer[
|
|
217
|
+
LifespanResultT
|
|
218
|
+
](
|
|
203
219
|
fastmcp=self,
|
|
204
220
|
name=name or self.generate_name(),
|
|
205
221
|
version=version or fastmcp.__version__,
|
|
@@ -216,7 +232,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
216
232
|
auth = fastmcp.settings.server_auth_class()
|
|
217
233
|
else:
|
|
218
234
|
auth = None
|
|
219
|
-
self.auth = cast(AuthProvider | None, auth)
|
|
235
|
+
self.auth: AuthProvider | None = cast(AuthProvider | None, auth)
|
|
220
236
|
|
|
221
237
|
if tools:
|
|
222
238
|
for tool in tools:
|
|
@@ -224,15 +240,20 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
224
240
|
tool = Tool.from_function(tool, serializer=self._tool_serializer)
|
|
225
241
|
self.add_tool(tool)
|
|
226
242
|
|
|
227
|
-
self.include_tags =
|
|
228
|
-
|
|
229
|
-
|
|
243
|
+
self.include_tags: set[str] | None = (
|
|
244
|
+
set(include_tags) if include_tags is not None else None
|
|
245
|
+
)
|
|
246
|
+
self.exclude_tags: set[str] | None = (
|
|
247
|
+
set(exclude_tags) if exclude_tags is not None else None
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
self.strict_input_validation: bool = (
|
|
230
251
|
strict_input_validation
|
|
231
252
|
if strict_input_validation is not None
|
|
232
253
|
else fastmcp.settings.strict_input_validation
|
|
233
254
|
)
|
|
234
255
|
|
|
235
|
-
self.middleware = middleware or []
|
|
256
|
+
self.middleware: list[Middleware] = list(middleware or [])
|
|
236
257
|
|
|
237
258
|
# Set up MCP protocol handlers
|
|
238
259
|
self._setup_handlers()
|
|
@@ -251,14 +272,18 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
251
272
|
DeprecationWarning,
|
|
252
273
|
stacklevel=2,
|
|
253
274
|
)
|
|
254
|
-
self.dependencies = (
|
|
275
|
+
self.dependencies: list[str] = (
|
|
255
276
|
dependencies or fastmcp.settings.server_dependencies
|
|
256
277
|
) # TODO: Remove (deprecated in v2.11.4)
|
|
257
278
|
|
|
258
|
-
self.sampling_handler =
|
|
259
|
-
|
|
279
|
+
self.sampling_handler: ServerSamplingHandler[LifespanResultT] | None = (
|
|
280
|
+
sampling_handler
|
|
281
|
+
)
|
|
282
|
+
self.sampling_handler_behavior: Literal["always", "fallback"] = (
|
|
283
|
+
sampling_handler_behavior or "fallback"
|
|
284
|
+
)
|
|
260
285
|
|
|
261
|
-
self.include_fastmcp_meta = (
|
|
286
|
+
self.include_fastmcp_meta: bool = (
|
|
262
287
|
include_fastmcp_meta
|
|
263
288
|
if include_fastmcp_meta is not None
|
|
264
289
|
else fastmcp.settings.include_fastmcp_meta
|
|
@@ -1041,10 +1066,10 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1041
1066
|
try:
|
|
1042
1067
|
result = await self._call_tool_middleware(key, arguments)
|
|
1043
1068
|
return result.to_mcp_result()
|
|
1044
|
-
except DisabledError:
|
|
1045
|
-
raise NotFoundError(f"Unknown tool: {key}")
|
|
1046
|
-
except NotFoundError:
|
|
1047
|
-
raise NotFoundError(f"Unknown tool: {key}")
|
|
1069
|
+
except DisabledError as e:
|
|
1070
|
+
raise NotFoundError(f"Unknown tool: {key}") from e
|
|
1071
|
+
except NotFoundError as e:
|
|
1072
|
+
raise NotFoundError(f"Unknown tool: {key}") from e
|
|
1048
1073
|
|
|
1049
1074
|
async def _call_tool_middleware(
|
|
1050
1075
|
self,
|
|
@@ -1121,12 +1146,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1121
1146
|
return list[ReadResourceContents](
|
|
1122
1147
|
await self._read_resource_middleware(uri)
|
|
1123
1148
|
)
|
|
1124
|
-
except DisabledError:
|
|
1149
|
+
except DisabledError as e:
|
|
1125
1150
|
# convert to NotFoundError to avoid leaking resource presence
|
|
1126
|
-
raise NotFoundError(f"Unknown resource: {str(uri)!r}")
|
|
1127
|
-
except NotFoundError:
|
|
1151
|
+
raise NotFoundError(f"Unknown resource: {str(uri)!r}") from e
|
|
1152
|
+
except NotFoundError as e:
|
|
1128
1153
|
# standardize NotFound message
|
|
1129
|
-
raise NotFoundError(f"Unknown resource: {str(uri)!r}")
|
|
1154
|
+
raise NotFoundError(f"Unknown resource: {str(uri)!r}") from e
|
|
1130
1155
|
|
|
1131
1156
|
async def _read_resource_middleware(
|
|
1132
1157
|
self,
|
|
@@ -1137,10 +1162,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1137
1162
|
"""
|
|
1138
1163
|
|
|
1139
1164
|
# Convert string URI to AnyUrl if needed
|
|
1140
|
-
if isinstance(uri, str)
|
|
1141
|
-
uri_param = AnyUrl(uri)
|
|
1142
|
-
else:
|
|
1143
|
-
uri_param = uri
|
|
1165
|
+
uri_param = AnyUrl(uri) if isinstance(uri, str) else uri
|
|
1144
1166
|
|
|
1145
1167
|
mw_context = MiddlewareContext(
|
|
1146
1168
|
message=mcp.types.ReadResourceRequestParams(uri=uri_param),
|
|
@@ -1220,12 +1242,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1220
1242
|
async with fastmcp.server.context.Context(fastmcp=self):
|
|
1221
1243
|
try:
|
|
1222
1244
|
return await self._get_prompt_middleware(name, arguments)
|
|
1223
|
-
except DisabledError:
|
|
1245
|
+
except DisabledError as e:
|
|
1224
1246
|
# convert to NotFoundError to avoid leaking prompt presence
|
|
1225
|
-
raise NotFoundError(f"Unknown prompt: {name}")
|
|
1226
|
-
except NotFoundError:
|
|
1247
|
+
raise NotFoundError(f"Unknown prompt: {name}") from e
|
|
1248
|
+
except NotFoundError as e:
|
|
1227
1249
|
# standardize NotFound message
|
|
1228
|
-
raise NotFoundError(f"Unknown prompt: {name}")
|
|
1250
|
+
raise NotFoundError(f"Unknown prompt: {name}") from e
|
|
1229
1251
|
|
|
1230
1252
|
async def _get_prompt_middleware(
|
|
1231
1253
|
self, name: str, arguments: dict[str, Any] | None = None
|
|
@@ -1348,7 +1370,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1348
1370
|
description: str | None = None,
|
|
1349
1371
|
icons: list[mcp.types.Icon] | None = None,
|
|
1350
1372
|
tags: set[str] | None = None,
|
|
1351
|
-
output_schema: dict[str, Any] |
|
|
1373
|
+
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
|
1352
1374
|
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
1353
1375
|
exclude_args: list[str] | None = None,
|
|
1354
1376
|
meta: dict[str, Any] | None = None,
|
|
@@ -1365,7 +1387,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1365
1387
|
description: str | None = None,
|
|
1366
1388
|
icons: list[mcp.types.Icon] | None = None,
|
|
1367
1389
|
tags: set[str] | None = None,
|
|
1368
|
-
output_schema: dict[str, Any] |
|
|
1390
|
+
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
|
1369
1391
|
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
1370
1392
|
exclude_args: list[str] | None = None,
|
|
1371
1393
|
meta: dict[str, Any] | None = None,
|
|
@@ -1381,7 +1403,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1381
1403
|
description: str | None = None,
|
|
1382
1404
|
icons: list[mcp.types.Icon] | None = None,
|
|
1383
1405
|
tags: set[str] | None = None,
|
|
1384
|
-
output_schema: dict[str, Any] |
|
|
1406
|
+
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
|
1385
1407
|
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
1386
1408
|
exclude_args: list[str] | None = None,
|
|
1387
1409
|
meta: dict[str, Any] | None = None,
|
|
@@ -2008,14 +2030,14 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
2008
2030
|
port=port,
|
|
2009
2031
|
path=server_path,
|
|
2010
2032
|
)
|
|
2011
|
-
|
|
2033
|
+
uvicorn_config_from_user = uvicorn_config or {}
|
|
2012
2034
|
|
|
2013
2035
|
config_kwargs: dict[str, Any] = {
|
|
2014
2036
|
"timeout_graceful_shutdown": 0,
|
|
2015
2037
|
"lifespan": "on",
|
|
2016
2038
|
"ws": "websockets-sansio",
|
|
2017
2039
|
}
|
|
2018
|
-
config_kwargs.update(
|
|
2040
|
+
config_kwargs.update(uvicorn_config_from_user)
|
|
2019
2041
|
|
|
2020
2042
|
if "log_config" not in config_kwargs and "log_level" not in config_kwargs:
|
|
2021
2043
|
config_kwargs["log_level"] = default_log_level_to_use
|
|
@@ -2584,8 +2606,8 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
2584
2606
|
# - Connected clients: reuse existing session for all requests
|
|
2585
2607
|
# - Disconnected clients: create fresh sessions per request for isolation
|
|
2586
2608
|
if client.is_connected():
|
|
2587
|
-
|
|
2588
|
-
|
|
2609
|
+
proxy_logger = get_logger(__name__)
|
|
2610
|
+
proxy_logger.info(
|
|
2589
2611
|
"Proxy detected connected client - reusing existing session for all requests. "
|
|
2590
2612
|
"This may cause context mixing in concurrent scenarios."
|
|
2591
2613
|
)
|
|
@@ -2657,10 +2679,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
2657
2679
|
return False
|
|
2658
2680
|
|
|
2659
2681
|
if self.include_tags is not None:
|
|
2660
|
-
|
|
2661
|
-
return True
|
|
2662
|
-
else:
|
|
2663
|
-
return False
|
|
2682
|
+
return bool(any(itag in component.tags for itag in self.include_tags))
|
|
2664
2683
|
|
|
2665
2684
|
return True
|
|
2666
2685
|
|
fastmcp/settings.py
CHANGED
|
@@ -6,6 +6,7 @@ import warnings
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import TYPE_CHECKING, Annotated, Any, Literal
|
|
8
8
|
|
|
9
|
+
from platformdirs import user_data_dir
|
|
9
10
|
from pydantic import Field, ImportString, field_validator
|
|
10
11
|
from pydantic.fields import FieldInfo
|
|
11
12
|
from pydantic_settings import (
|
|
@@ -150,7 +151,7 @@ class Settings(BaseSettings):
|
|
|
150
151
|
)
|
|
151
152
|
return self
|
|
152
153
|
|
|
153
|
-
home: Path = Path
|
|
154
|
+
home: Path = Path(user_data_dir("fastmcp", appauthor=False))
|
|
154
155
|
|
|
155
156
|
test_mode: bool = False
|
|
156
157
|
|
fastmcp/tools/__init__.py
CHANGED
|
@@ -2,4 +2,4 @@ from .tool import Tool, FunctionTool
|
|
|
2
2
|
from .tool_manager import ToolManager
|
|
3
3
|
from .tool_transform import forward, forward_raw
|
|
4
4
|
|
|
5
|
-
__all__ = ["
|
|
5
|
+
__all__ = ["FunctionTool", "Tool", "ToolManager", "forward", "forward_raw"]
|
fastmcp/tools/tool.py
CHANGED
|
@@ -173,7 +173,7 @@ class Tool(FastMCPComponent):
|
|
|
173
173
|
tags: set[str] | None = None,
|
|
174
174
|
annotations: ToolAnnotations | None = None,
|
|
175
175
|
exclude_args: list[str] | None = None,
|
|
176
|
-
output_schema: dict[str, Any] |
|
|
176
|
+
output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
|
|
177
177
|
serializer: ToolResultSerializerType | None = None,
|
|
178
178
|
meta: dict[str, Any] | None = None,
|
|
179
179
|
enabled: bool | None = None,
|
|
@@ -212,13 +212,13 @@ class Tool(FastMCPComponent):
|
|
|
212
212
|
tool: Tool,
|
|
213
213
|
*,
|
|
214
214
|
name: str | None = None,
|
|
215
|
-
title: str |
|
|
216
|
-
description: str |
|
|
215
|
+
title: str | NotSetT | None = NotSet,
|
|
216
|
+
description: str | NotSetT | None = NotSet,
|
|
217
217
|
tags: set[str] | None = None,
|
|
218
|
-
annotations: ToolAnnotations |
|
|
219
|
-
output_schema: dict[str, Any] |
|
|
218
|
+
annotations: ToolAnnotations | NotSetT | None = NotSet,
|
|
219
|
+
output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
|
|
220
220
|
serializer: ToolResultSerializerType | None = None,
|
|
221
|
-
meta: dict[str, Any] |
|
|
221
|
+
meta: dict[str, Any] | NotSetT | None = NotSet,
|
|
222
222
|
transform_args: dict[str, ArgTransform] | None = None,
|
|
223
223
|
enabled: bool | None = None,
|
|
224
224
|
transform_fn: Callable[..., Any] | None = None,
|
|
@@ -255,7 +255,7 @@ class FunctionTool(Tool):
|
|
|
255
255
|
tags: set[str] | None = None,
|
|
256
256
|
annotations: ToolAnnotations | None = None,
|
|
257
257
|
exclude_args: list[str] | None = None,
|
|
258
|
-
output_schema: dict[str, Any] |
|
|
258
|
+
output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
|
|
259
259
|
serializer: ToolResultSerializerType | None = None,
|
|
260
260
|
meta: dict[str, Any] | None = None,
|
|
261
261
|
enabled: bool | None = None,
|
|
@@ -446,9 +446,8 @@ class ParsedFunction:
|
|
|
446
446
|
# we ensure that no output schema is automatically generated.
|
|
447
447
|
clean_output_type = replace_type(
|
|
448
448
|
output_type,
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
for t in (
|
|
449
|
+
dict.fromkeys( # type: ignore[arg-type]
|
|
450
|
+
(
|
|
452
451
|
Image,
|
|
453
452
|
Audio,
|
|
454
453
|
File,
|
|
@@ -458,8 +457,9 @@ class ParsedFunction:
|
|
|
458
457
|
mcp.types.AudioContent,
|
|
459
458
|
mcp.types.ResourceLink,
|
|
460
459
|
mcp.types.EmbeddedResource,
|
|
461
|
-
)
|
|
462
|
-
|
|
460
|
+
),
|
|
461
|
+
_UnserializableType,
|
|
462
|
+
),
|
|
463
463
|
)
|
|
464
464
|
|
|
465
465
|
try:
|
fastmcp/tools/tool_manager.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import warnings
|
|
4
|
-
from collections.abc import Callable
|
|
4
|
+
from collections.abc import Callable, Mapping
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
from mcp.types import ToolAnnotations
|
|
@@ -27,11 +27,15 @@ class ToolManager:
|
|
|
27
27
|
self,
|
|
28
28
|
duplicate_behavior: DuplicateBehavior | None = None,
|
|
29
29
|
mask_error_details: bool | None = None,
|
|
30
|
-
transformations:
|
|
30
|
+
transformations: Mapping[str, ToolTransformConfig] | None = None,
|
|
31
31
|
):
|
|
32
32
|
self._tools: dict[str, Tool] = {}
|
|
33
|
-
self.mask_error_details =
|
|
34
|
-
|
|
33
|
+
self.mask_error_details: bool = (
|
|
34
|
+
mask_error_details or settings.mask_error_details
|
|
35
|
+
)
|
|
36
|
+
self.transformations: dict[str, ToolTransformConfig] = dict(
|
|
37
|
+
transformations or {}
|
|
38
|
+
)
|
|
35
39
|
|
|
36
40
|
# Default to "warn" if None is provided
|
|
37
41
|
if duplicate_behavior is None:
|
fastmcp/tools/tool_transform.py
CHANGED
|
@@ -365,15 +365,15 @@ class TransformedTool(Tool):
|
|
|
365
365
|
cls,
|
|
366
366
|
tool: Tool,
|
|
367
367
|
name: str | None = None,
|
|
368
|
-
title: str |
|
|
369
|
-
description: str |
|
|
368
|
+
title: str | NotSetT | None = NotSet,
|
|
369
|
+
description: str | NotSetT | None = NotSet,
|
|
370
370
|
tags: set[str] | None = None,
|
|
371
371
|
transform_fn: Callable[..., Any] | None = None,
|
|
372
372
|
transform_args: dict[str, ArgTransform] | None = None,
|
|
373
|
-
annotations: ToolAnnotations |
|
|
374
|
-
output_schema: dict[str, Any] |
|
|
375
|
-
serializer: Callable[[Any], str] |
|
|
376
|
-
meta: dict[str, Any] |
|
|
373
|
+
annotations: ToolAnnotations | NotSetT | None = NotSet,
|
|
374
|
+
output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
|
|
375
|
+
serializer: Callable[[Any], str] | NotSetT | None = NotSet,
|
|
376
|
+
meta: dict[str, Any] | NotSetT | None = NotSet,
|
|
377
377
|
enabled: bool | None = None,
|
|
378
378
|
) -> TransformedTool:
|
|
379
379
|
"""Create a transformed tool from a parent tool.
|
fastmcp/utilities/cli.py
CHANGED
|
@@ -150,22 +150,52 @@ _ __ ___ /_/ \____/____/\__/_/ /_/\____/_/ /_____(*)____/
|
|
|
150
150
|
# █▀▀ ▄▀█ █▀▀ ▀█▀ █▀▄▀█ █▀▀ █▀█
|
|
151
151
|
# █▀ █▀█ ▄▄█ █ █ ▀ █ █▄▄ █▀▀
|
|
152
152
|
LOGO_ASCII_2 = (
|
|
153
|
-
"\
|
|
154
|
-
"\
|
|
155
|
-
"\
|
|
156
|
-
"\
|
|
157
|
-
"\
|
|
158
|
-
"\
|
|
159
|
-
"\
|
|
160
|
-
"\
|
|
161
|
-
"\
|
|
162
|
-
"\
|
|
163
|
-
"\
|
|
164
|
-
"\
|
|
165
|
-
"\
|
|
166
|
-
"\
|
|
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
167
|
).strip()
|
|
168
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
|
+
|
|
169
199
|
|
|
170
200
|
def log_server_banner(
|
|
171
201
|
server: FastMCP[Any],
|
|
@@ -187,7 +217,7 @@ def log_server_banner(
|
|
|
187
217
|
|
|
188
218
|
# Create the logo text
|
|
189
219
|
# Use Text with no_wrap and markup disabled to preserve ANSI escape codes
|
|
190
|
-
logo_text = Text.from_ansi(
|
|
220
|
+
logo_text = Text.from_ansi(LOGO_ASCII_4, no_wrap=True)
|
|
191
221
|
|
|
192
222
|
# Create the main title
|
|
193
223
|
title_text = Text(f"FastMCP {fastmcp.__version__}", style="bold blue")
|
|
@@ -210,12 +240,11 @@ def log_server_banner(
|
|
|
210
240
|
info_table.add_row("📦", "Transport:", display_transport)
|
|
211
241
|
|
|
212
242
|
# Show connection info based on transport
|
|
213
|
-
if transport in ("http", "streamable-http", "sse"):
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
info_table.add_row("🔗", "Server URL:", server_url)
|
|
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)
|
|
219
248
|
|
|
220
249
|
# Add documentation link
|
|
221
250
|
info_table.add_row("", "", "")
|
fastmcp/utilities/inspect.py
CHANGED
|
@@ -412,7 +412,7 @@ class InspectFormat(str, Enum):
|
|
|
412
412
|
MCP = "mcp"
|
|
413
413
|
|
|
414
414
|
|
|
415
|
-
|
|
415
|
+
def format_fastmcp_info(info: FastMCPInfo) -> bytes:
|
|
416
416
|
"""Format FastMCPInfo as FastMCP-specific JSON.
|
|
417
417
|
|
|
418
418
|
This includes FastMCP-specific fields like tags, enabled, annotations, etc.
|
|
@@ -501,6 +501,6 @@ async def format_info(
|
|
|
501
501
|
# This works for both v1 and v2 servers
|
|
502
502
|
if info is None:
|
|
503
503
|
info = await inspect_fastmcp(mcp)
|
|
504
|
-
return
|
|
504
|
+
return format_fastmcp_info(info)
|
|
505
505
|
else:
|
|
506
506
|
raise ValueError(f"Unknown format: {format}")
|
|
@@ -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] = {
|
|
@@ -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
|
fastmcp/utilities/logging.py
CHANGED
|
@@ -147,6 +147,18 @@ def temporary_log_level(
|
|
|
147
147
|
yield
|
|
148
148
|
|
|
149
149
|
|
|
150
|
+
_level_to_no: dict[
|
|
151
|
+
Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None, int | None
|
|
152
|
+
] = {
|
|
153
|
+
"DEBUG": logging.DEBUG,
|
|
154
|
+
"INFO": logging.INFO,
|
|
155
|
+
"WARNING": logging.WARNING,
|
|
156
|
+
"ERROR": logging.ERROR,
|
|
157
|
+
"CRITICAL": logging.CRITICAL,
|
|
158
|
+
None: None,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
150
162
|
class _ClampedLogFilter(logging.Filter):
|
|
151
163
|
min_level: tuple[int, str] | None
|
|
152
164
|
max_level: tuple[int, str] | None
|
|
@@ -161,29 +173,13 @@ class _ClampedLogFilter(logging.Filter):
|
|
|
161
173
|
self.min_level = None
|
|
162
174
|
self.max_level = None
|
|
163
175
|
|
|
164
|
-
if min_level_no :=
|
|
176
|
+
if min_level_no := _level_to_no.get(min_level):
|
|
165
177
|
self.min_level = (min_level_no, str(min_level))
|
|
166
|
-
if max_level_no :=
|
|
178
|
+
if max_level_no := _level_to_no.get(max_level):
|
|
167
179
|
self.max_level = (max_level_no, str(max_level))
|
|
168
180
|
|
|
169
181
|
super().__init__()
|
|
170
182
|
|
|
171
|
-
def _level_to_no(
|
|
172
|
-
self, level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None
|
|
173
|
-
) -> int | None:
|
|
174
|
-
if level == "DEBUG":
|
|
175
|
-
return logging.DEBUG
|
|
176
|
-
elif level == "INFO":
|
|
177
|
-
return logging.INFO
|
|
178
|
-
elif level == "WARNING":
|
|
179
|
-
return logging.WARNING
|
|
180
|
-
elif level == "ERROR":
|
|
181
|
-
return logging.ERROR
|
|
182
|
-
elif level == "CRITICAL":
|
|
183
|
-
return logging.CRITICAL
|
|
184
|
-
else:
|
|
185
|
-
return None
|
|
186
|
-
|
|
187
183
|
@override
|
|
188
184
|
def filter(self, record: logging.LogRecord) -> bool:
|
|
189
185
|
if self.max_level:
|
|
@@ -15,11 +15,11 @@ from fastmcp.utilities.mcp_server_config.v1.sources.base import Source
|
|
|
15
15
|
from fastmcp.utilities.mcp_server_config.v1.sources.filesystem import FileSystemSource
|
|
16
16
|
|
|
17
17
|
__all__ = [
|
|
18
|
-
"Source",
|
|
19
18
|
"Deployment",
|
|
20
19
|
"Environment",
|
|
21
|
-
"UVEnvironment",
|
|
22
|
-
"MCPServerConfig",
|
|
23
20
|
"FileSystemSource",
|
|
21
|
+
"MCPServerConfig",
|
|
22
|
+
"Source",
|
|
23
|
+
"UVEnvironment",
|
|
24
24
|
"generate_schema",
|
|
25
25
|
]
|