fastmcp 2.13.0rc3__py3-none-any.whl → 2.13.0.2__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 +2 -2
- fastmcp/client/__init__.py +9 -9
- fastmcp/client/auth/oauth.py +7 -6
- fastmcp/client/client.py +10 -10
- fastmcp/client/sampling.py +1 -1
- fastmcp/client/transports.py +34 -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/server/__init__.py +1 -1
- fastmcp/server/auth/__init__.py +5 -5
- fastmcp/server/auth/auth.py +2 -2
- fastmcp/server/auth/oidc_proxy.py +2 -2
- fastmcp/server/auth/providers/azure.py +48 -25
- fastmcp/server/auth/providers/bearer.py +1 -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 +2 -2
- fastmcp/server/context.py +8 -10
- fastmcp/server/dependencies.py +5 -6
- fastmcp/server/elicitation.py +1 -1
- fastmcp/server/http.py +2 -3
- 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/openapi.py +10 -6
- fastmcp/server/proxy.py +5 -4
- fastmcp/server/server.py +27 -29
- fastmcp/tools/__init__.py +1 -1
- fastmcp/tools/tool.py +12 -12
- fastmcp/tools/tool_transform.py +6 -6
- fastmcp/utilities/cli.py +5 -6
- 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-2.13.0rc3.dist-info → fastmcp-2.13.0.2.dist-info}/METADATA +3 -3
- {fastmcp-2.13.0rc3.dist-info → fastmcp-2.13.0.2.dist-info}/RECORD +65 -65
- {fastmcp-2.13.0rc3.dist-info → fastmcp-2.13.0.2.dist-info}/WHEEL +0 -0
- {fastmcp-2.13.0rc3.dist-info → fastmcp-2.13.0.2.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.13.0rc3.dist-info → fastmcp-2.13.0.2.dist-info}/licenses/LICENSE +0 -0
fastmcp/server/server.py
CHANGED
|
@@ -15,7 +15,11 @@ from collections.abc import (
|
|
|
15
15
|
Mapping,
|
|
16
16
|
Sequence,
|
|
17
17
|
)
|
|
18
|
-
from contextlib import
|
|
18
|
+
from contextlib import (
|
|
19
|
+
AbstractAsyncContextManager,
|
|
20
|
+
AsyncExitStack,
|
|
21
|
+
asynccontextmanager,
|
|
22
|
+
)
|
|
19
23
|
from dataclasses import dataclass
|
|
20
24
|
from functools import partial
|
|
21
25
|
from pathlib import Path
|
|
@@ -150,7 +154,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
150
154
|
version: str | None = None,
|
|
151
155
|
website_url: str | None = None,
|
|
152
156
|
icons: list[mcp.types.Icon] | None = None,
|
|
153
|
-
auth: AuthProvider |
|
|
157
|
+
auth: AuthProvider | NotSetT | None = NotSet,
|
|
154
158
|
middleware: Sequence[Middleware] | None = None,
|
|
155
159
|
lifespan: LifespanCallable | None = None,
|
|
156
160
|
dependencies: list[str] | None = None,
|
|
@@ -1062,10 +1066,10 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1062
1066
|
try:
|
|
1063
1067
|
result = await self._call_tool_middleware(key, arguments)
|
|
1064
1068
|
return result.to_mcp_result()
|
|
1065
|
-
except DisabledError:
|
|
1066
|
-
raise NotFoundError(f"Unknown tool: {key}")
|
|
1067
|
-
except NotFoundError:
|
|
1068
|
-
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
|
|
1069
1073
|
|
|
1070
1074
|
async def _call_tool_middleware(
|
|
1071
1075
|
self,
|
|
@@ -1142,12 +1146,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1142
1146
|
return list[ReadResourceContents](
|
|
1143
1147
|
await self._read_resource_middleware(uri)
|
|
1144
1148
|
)
|
|
1145
|
-
except DisabledError:
|
|
1149
|
+
except DisabledError as e:
|
|
1146
1150
|
# convert to NotFoundError to avoid leaking resource presence
|
|
1147
|
-
raise NotFoundError(f"Unknown resource: {str(uri)!r}")
|
|
1148
|
-
except NotFoundError:
|
|
1151
|
+
raise NotFoundError(f"Unknown resource: {str(uri)!r}") from e
|
|
1152
|
+
except NotFoundError as e:
|
|
1149
1153
|
# standardize NotFound message
|
|
1150
|
-
raise NotFoundError(f"Unknown resource: {str(uri)!r}")
|
|
1154
|
+
raise NotFoundError(f"Unknown resource: {str(uri)!r}") from e
|
|
1151
1155
|
|
|
1152
1156
|
async def _read_resource_middleware(
|
|
1153
1157
|
self,
|
|
@@ -1158,10 +1162,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1158
1162
|
"""
|
|
1159
1163
|
|
|
1160
1164
|
# Convert string URI to AnyUrl if needed
|
|
1161
|
-
if isinstance(uri, str)
|
|
1162
|
-
uri_param = AnyUrl(uri)
|
|
1163
|
-
else:
|
|
1164
|
-
uri_param = uri
|
|
1165
|
+
uri_param = AnyUrl(uri) if isinstance(uri, str) else uri
|
|
1165
1166
|
|
|
1166
1167
|
mw_context = MiddlewareContext(
|
|
1167
1168
|
message=mcp.types.ReadResourceRequestParams(uri=uri_param),
|
|
@@ -1241,12 +1242,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1241
1242
|
async with fastmcp.server.context.Context(fastmcp=self):
|
|
1242
1243
|
try:
|
|
1243
1244
|
return await self._get_prompt_middleware(name, arguments)
|
|
1244
|
-
except DisabledError:
|
|
1245
|
+
except DisabledError as e:
|
|
1245
1246
|
# convert to NotFoundError to avoid leaking prompt presence
|
|
1246
|
-
raise NotFoundError(f"Unknown prompt: {name}")
|
|
1247
|
-
except NotFoundError:
|
|
1247
|
+
raise NotFoundError(f"Unknown prompt: {name}") from e
|
|
1248
|
+
except NotFoundError as e:
|
|
1248
1249
|
# standardize NotFound message
|
|
1249
|
-
raise NotFoundError(f"Unknown prompt: {name}")
|
|
1250
|
+
raise NotFoundError(f"Unknown prompt: {name}") from e
|
|
1250
1251
|
|
|
1251
1252
|
async def _get_prompt_middleware(
|
|
1252
1253
|
self, name: str, arguments: dict[str, Any] | None = None
|
|
@@ -1369,7 +1370,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1369
1370
|
description: str | None = None,
|
|
1370
1371
|
icons: list[mcp.types.Icon] | None = None,
|
|
1371
1372
|
tags: set[str] | None = None,
|
|
1372
|
-
output_schema: dict[str, Any] |
|
|
1373
|
+
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
|
1373
1374
|
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
1374
1375
|
exclude_args: list[str] | None = None,
|
|
1375
1376
|
meta: dict[str, Any] | None = None,
|
|
@@ -1386,7 +1387,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1386
1387
|
description: str | None = None,
|
|
1387
1388
|
icons: list[mcp.types.Icon] | None = None,
|
|
1388
1389
|
tags: set[str] | None = None,
|
|
1389
|
-
output_schema: dict[str, Any] |
|
|
1390
|
+
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
|
1390
1391
|
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
1391
1392
|
exclude_args: list[str] | None = None,
|
|
1392
1393
|
meta: dict[str, Any] | None = None,
|
|
@@ -1402,7 +1403,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1402
1403
|
description: str | None = None,
|
|
1403
1404
|
icons: list[mcp.types.Icon] | None = None,
|
|
1404
1405
|
tags: set[str] | None = None,
|
|
1405
|
-
output_schema: dict[str, Any] |
|
|
1406
|
+
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
|
1406
1407
|
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
1407
1408
|
exclude_args: list[str] | None = None,
|
|
1408
1409
|
meta: dict[str, Any] | None = None,
|
|
@@ -2029,14 +2030,14 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
2029
2030
|
port=port,
|
|
2030
2031
|
path=server_path,
|
|
2031
2032
|
)
|
|
2032
|
-
|
|
2033
|
+
uvicorn_config_from_user = uvicorn_config or {}
|
|
2033
2034
|
|
|
2034
2035
|
config_kwargs: dict[str, Any] = {
|
|
2035
2036
|
"timeout_graceful_shutdown": 0,
|
|
2036
2037
|
"lifespan": "on",
|
|
2037
2038
|
"ws": "websockets-sansio",
|
|
2038
2039
|
}
|
|
2039
|
-
config_kwargs.update(
|
|
2040
|
+
config_kwargs.update(uvicorn_config_from_user)
|
|
2040
2041
|
|
|
2041
2042
|
if "log_config" not in config_kwargs and "log_level" not in config_kwargs:
|
|
2042
2043
|
config_kwargs["log_level"] = default_log_level_to_use
|
|
@@ -2605,8 +2606,8 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
2605
2606
|
# - Connected clients: reuse existing session for all requests
|
|
2606
2607
|
# - Disconnected clients: create fresh sessions per request for isolation
|
|
2607
2608
|
if client.is_connected():
|
|
2608
|
-
|
|
2609
|
-
|
|
2609
|
+
proxy_logger = get_logger(__name__)
|
|
2610
|
+
proxy_logger.info(
|
|
2610
2611
|
"Proxy detected connected client - reusing existing session for all requests. "
|
|
2611
2612
|
"This may cause context mixing in concurrent scenarios."
|
|
2612
2613
|
)
|
|
@@ -2678,10 +2679,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
2678
2679
|
return False
|
|
2679
2680
|
|
|
2680
2681
|
if self.include_tags is not None:
|
|
2681
|
-
|
|
2682
|
-
return True
|
|
2683
|
-
else:
|
|
2684
|
-
return False
|
|
2682
|
+
return bool(any(itag in component.tags for itag in self.include_tags))
|
|
2685
2683
|
|
|
2686
2684
|
return True
|
|
2687
2685
|
|
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_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
|
@@ -240,12 +240,11 @@ def log_server_banner(
|
|
|
240
240
|
info_table.add_row("📦", "Transport:", display_transport)
|
|
241
241
|
|
|
242
242
|
# Show connection info based on transport
|
|
243
|
-
if transport in ("http", "streamable-http", "sse"):
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
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)
|
|
249
248
|
|
|
250
249
|
# Add documentation link
|
|
251
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
|
]
|
|
@@ -19,7 +19,6 @@ class Environment(BaseModel, ABC):
|
|
|
19
19
|
Returns:
|
|
20
20
|
Full command ready for subprocess execution
|
|
21
21
|
"""
|
|
22
|
-
pass
|
|
23
22
|
|
|
24
23
|
async def prepare(self, output_dir: Path | None = None) -> None:
|
|
25
24
|
"""Prepare the environment (optional, can be no-op).
|
|
@@ -27,4 +26,4 @@ class Environment(BaseModel, ABC):
|
|
|
27
26
|
Args:
|
|
28
27
|
output_dir: Directory for persistent environment setup
|
|
29
28
|
"""
|
|
30
|
-
|
|
29
|
+
# Default no-op implementation
|
fastmcp/utilities/openapi.py
CHANGED
|
@@ -175,16 +175,16 @@ class HTTPRoute(FastMCPBaseModel):
|
|
|
175
175
|
# Export public symbols
|
|
176
176
|
__all__ = [
|
|
177
177
|
"HTTPRoute",
|
|
178
|
+
"HttpMethod",
|
|
179
|
+
"JsonSchema",
|
|
178
180
|
"ParameterInfo",
|
|
181
|
+
"ParameterLocation",
|
|
179
182
|
"RequestBodyInfo",
|
|
180
183
|
"ResponseInfo",
|
|
181
|
-
"
|
|
182
|
-
"ParameterLocation",
|
|
183
|
-
"JsonSchema",
|
|
184
|
-
"parse_openapi_to_http_routes",
|
|
184
|
+
"_handle_nullable_fields",
|
|
185
185
|
"extract_output_schema_from_responses",
|
|
186
186
|
"format_deep_object_parameter",
|
|
187
|
-
"
|
|
187
|
+
"parse_openapi_to_http_routes",
|
|
188
188
|
]
|
|
189
189
|
|
|
190
190
|
# Type variables for generic parser
|
|
@@ -321,7 +321,7 @@ class OpenAPIParser(
|
|
|
321
321
|
else:
|
|
322
322
|
# Special handling for components
|
|
323
323
|
if part == "components" and hasattr(target, "components"):
|
|
324
|
-
target =
|
|
324
|
+
target = target.components
|
|
325
325
|
elif hasattr(target, part): # Fallback check
|
|
326
326
|
target = getattr(target, part, None)
|
|
327
327
|
else:
|
|
@@ -1178,10 +1178,10 @@ def _add_null_to_type(schema: dict[str, Any]) -> None:
|
|
|
1178
1178
|
elif isinstance(current_type, list):
|
|
1179
1179
|
# Add null to array if not already present
|
|
1180
1180
|
if "null" not in current_type:
|
|
1181
|
-
schema["type"] = current_type
|
|
1181
|
+
schema["type"] = [*current_type, "null"]
|
|
1182
1182
|
elif "oneOf" in schema:
|
|
1183
1183
|
# Convert oneOf to anyOf with null type
|
|
1184
|
-
schema["anyOf"] = schema.pop("oneOf")
|
|
1184
|
+
schema["anyOf"] = [*schema.pop("oneOf"), {"type": "null"}]
|
|
1185
1185
|
elif "anyOf" in schema:
|
|
1186
1186
|
# Add null type to anyOf if not already present
|
|
1187
1187
|
if not any(item.get("type") == "null" for item in schema["anyOf"]):
|
|
@@ -1233,7 +1233,7 @@ def _handle_nullable_fields(schema: dict[str, Any] | Any) -> dict[str, Any] | An
|
|
|
1233
1233
|
|
|
1234
1234
|
# Handle properties nullable fields
|
|
1235
1235
|
if has_property_nullable_field and "properties" in result:
|
|
1236
|
-
for
|
|
1236
|
+
for _prop_name, prop_schema in result["properties"].items():
|
|
1237
1237
|
if isinstance(prop_schema, dict) and "nullable" in prop_schema:
|
|
1238
1238
|
nullable_value = prop_schema.pop("nullable")
|
|
1239
1239
|
if nullable_value and (
|
fastmcp/utilities/tests.py
CHANGED
|
@@ -6,7 +6,7 @@ import multiprocessing
|
|
|
6
6
|
import socket
|
|
7
7
|
import time
|
|
8
8
|
from collections.abc import AsyncGenerator, Callable, Generator
|
|
9
|
-
from contextlib import asynccontextmanager, contextmanager
|
|
9
|
+
from contextlib import asynccontextmanager, contextmanager, suppress
|
|
10
10
|
from typing import TYPE_CHECKING, Any, Literal
|
|
11
11
|
from urllib.parse import parse_qs, urlparse
|
|
12
12
|
|
|
@@ -216,10 +216,8 @@ async def run_server_async(
|
|
|
216
216
|
finally:
|
|
217
217
|
# Cleanup: cancel the task
|
|
218
218
|
server_task.cancel()
|
|
219
|
-
|
|
219
|
+
with suppress(asyncio.CancelledError):
|
|
220
220
|
await server_task
|
|
221
|
-
except asyncio.CancelledError:
|
|
222
|
-
pass
|
|
223
221
|
|
|
224
222
|
|
|
225
223
|
@contextmanager
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmcp
|
|
3
|
-
Version: 2.13.
|
|
3
|
+
Version: 2.13.0.2
|
|
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
|
|
@@ -22,11 +22,11 @@ Requires-Dist: authlib>=1.5.2
|
|
|
22
22
|
Requires-Dist: cyclopts>=3.0.0
|
|
23
23
|
Requires-Dist: exceptiongroup>=1.2.2
|
|
24
24
|
Requires-Dist: httpx>=0.28.1
|
|
25
|
+
Requires-Dist: jsonschema-path>=0.3.4
|
|
25
26
|
Requires-Dist: mcp<2.0.0,>=1.17.0
|
|
26
|
-
Requires-Dist: openapi-core>=0.19.5
|
|
27
27
|
Requires-Dist: openapi-pydantic>=0.5.1
|
|
28
28
|
Requires-Dist: platformdirs>=4.0.0
|
|
29
|
-
Requires-Dist: py-key-value-aio[disk,keyring,memory]<0.3.0,>=0.2.
|
|
29
|
+
Requires-Dist: py-key-value-aio[disk,keyring,memory]<0.3.0,>=0.2.8
|
|
30
30
|
Requires-Dist: pydantic[email]>=2.11.7
|
|
31
31
|
Requires-Dist: pyperclip>=1.9.0
|
|
32
32
|
Requires-Dist: python-dotenv>=1.1.0
|