fastmcp 2.10.5__py3-none-any.whl → 2.10.6__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 +7 -2
- fastmcp/cli/install/__init__.py +2 -2
- fastmcp/cli/install/{mcp_config.py → mcp_json.py} +10 -7
- fastmcp/prompts/prompt_manager.py +1 -1
- fastmcp/resources/resource_manager.py +2 -2
- fastmcp/server/context.py +3 -0
- fastmcp/server/openapi.py +25 -12
- fastmcp/server/proxy.py +42 -0
- fastmcp/server/server.py +2 -3
- fastmcp/settings.py +10 -12
- fastmcp/tools/tool.py +5 -3
- fastmcp/tools/tool_manager.py +1 -1
- fastmcp/tools/tool_transform.py +10 -3
- fastmcp/utilities/openapi.py +96 -10
- {fastmcp-2.10.5.dist-info → fastmcp-2.10.6.dist-info}/METADATA +2 -2
- {fastmcp-2.10.5.dist-info → fastmcp-2.10.6.dist-info}/RECORD +19 -19
- {fastmcp-2.10.5.dist-info → fastmcp-2.10.6.dist-info}/WHEEL +0 -0
- {fastmcp-2.10.5.dist-info → fastmcp-2.10.6.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.10.5.dist-info → fastmcp-2.10.6.dist-info}/licenses/LICENSE +0 -0
fastmcp/__init__.py
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"""FastMCP - An ergonomic MCP interface."""
|
|
2
2
|
|
|
3
3
|
import warnings
|
|
4
|
-
from importlib.metadata import version
|
|
4
|
+
from importlib.metadata import version as _version
|
|
5
5
|
from fastmcp.settings import Settings
|
|
6
|
+
from fastmcp.utilities.logging import configure_logging as _configure_logging
|
|
6
7
|
|
|
7
8
|
settings = Settings()
|
|
9
|
+
_configure_logging(
|
|
10
|
+
level=settings.log_level,
|
|
11
|
+
enable_rich_tracebacks=settings.enable_rich_tracebacks,
|
|
12
|
+
)
|
|
8
13
|
|
|
9
14
|
from fastmcp.server.server import FastMCP
|
|
10
15
|
from fastmcp.server.context import Context
|
|
@@ -13,7 +18,7 @@ import fastmcp.server
|
|
|
13
18
|
from fastmcp.client import Client
|
|
14
19
|
from . import client
|
|
15
20
|
|
|
16
|
-
__version__ =
|
|
21
|
+
__version__ = _version("fastmcp")
|
|
17
22
|
|
|
18
23
|
|
|
19
24
|
# ensure deprecation warnings are displayed by default
|
fastmcp/cli/install/__init__.py
CHANGED
|
@@ -5,7 +5,7 @@ import cyclopts
|
|
|
5
5
|
from .claude_code import claude_code_command
|
|
6
6
|
from .claude_desktop import claude_desktop_command
|
|
7
7
|
from .cursor import cursor_command
|
|
8
|
-
from .
|
|
8
|
+
from .mcp_json import mcp_json_command
|
|
9
9
|
|
|
10
10
|
# Create a cyclopts app for install subcommands
|
|
11
11
|
install_app = cyclopts.App(
|
|
@@ -17,4 +17,4 @@ install_app = cyclopts.App(
|
|
|
17
17
|
install_app.command(claude_code_command, name="claude-code")
|
|
18
18
|
install_app.command(claude_desktop_command, name="claude-desktop")
|
|
19
19
|
install_app.command(cursor_command, name="cursor")
|
|
20
|
-
install_app.command(
|
|
20
|
+
install_app.command(mcp_json_command, name="mcp-json")
|
|
@@ -16,7 +16,7 @@ from .shared import process_common_args
|
|
|
16
16
|
logger = get_logger(__name__)
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def
|
|
19
|
+
def install_mcp_json(
|
|
20
20
|
file: Path,
|
|
21
21
|
server_object: str | None,
|
|
22
22
|
name: str,
|
|
@@ -65,15 +65,18 @@ def install_mcp_config(
|
|
|
65
65
|
# Add fastmcp run command
|
|
66
66
|
args.extend(["fastmcp", "run", server_spec])
|
|
67
67
|
|
|
68
|
-
# Build MCP server configuration
|
|
69
|
-
|
|
68
|
+
# Build MCP server configuration
|
|
69
|
+
server_config = {
|
|
70
70
|
"command": "uv",
|
|
71
71
|
"args": args,
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
# Add environment variables if provided
|
|
75
75
|
if env_vars:
|
|
76
|
-
|
|
76
|
+
server_config["env"] = env_vars
|
|
77
|
+
|
|
78
|
+
# Wrap with server name as root key
|
|
79
|
+
config = {name: server_config}
|
|
77
80
|
|
|
78
81
|
# Convert to JSON
|
|
79
82
|
json_output = json.dumps(config, indent=2)
|
|
@@ -93,13 +96,13 @@ def install_mcp_config(
|
|
|
93
96
|
return False
|
|
94
97
|
|
|
95
98
|
|
|
96
|
-
def
|
|
99
|
+
def mcp_json_command(
|
|
97
100
|
server_spec: str,
|
|
98
101
|
*,
|
|
99
102
|
server_name: Annotated[
|
|
100
103
|
str | None,
|
|
101
104
|
cyclopts.Parameter(
|
|
102
|
-
name=["--
|
|
105
|
+
name=["--name", "-n"],
|
|
103
106
|
help="Custom name for the server in MCP config",
|
|
104
107
|
),
|
|
105
108
|
] = None,
|
|
@@ -151,7 +154,7 @@ def mcp_config_command(
|
|
|
151
154
|
server_spec, server_name, with_packages, env_vars, env_file
|
|
152
155
|
)
|
|
153
156
|
|
|
154
|
-
success =
|
|
157
|
+
success = install_mcp_json(
|
|
155
158
|
file=file,
|
|
156
159
|
server_object=server_object,
|
|
157
160
|
name=name,
|
|
@@ -78,7 +78,7 @@ class PromptManager:
|
|
|
78
78
|
except Exception as e:
|
|
79
79
|
# Skip failed mounts silently, matches existing behavior
|
|
80
80
|
logger.warning(
|
|
81
|
-
f"Failed to get prompts from mounted
|
|
81
|
+
f"Failed to get prompts from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
|
|
82
82
|
)
|
|
83
83
|
continue
|
|
84
84
|
|
|
@@ -109,7 +109,7 @@ class ResourceManager:
|
|
|
109
109
|
except Exception as e:
|
|
110
110
|
# Skip failed mounts silently, matches existing behavior
|
|
111
111
|
logger.warning(
|
|
112
|
-
f"Failed to get resources from mounted
|
|
112
|
+
f"Failed to get resources from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
|
|
113
113
|
)
|
|
114
114
|
continue
|
|
115
115
|
|
|
@@ -157,7 +157,7 @@ class ResourceManager:
|
|
|
157
157
|
except Exception as e:
|
|
158
158
|
# Skip failed mounts silently, matches existing behavior
|
|
159
159
|
logger.warning(
|
|
160
|
-
f"Failed to get templates from mounted
|
|
160
|
+
f"Failed to get templates from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
|
|
161
161
|
)
|
|
162
162
|
continue
|
|
163
163
|
|
fastmcp/server/context.py
CHANGED
|
@@ -16,6 +16,7 @@ from mcp.shared.context import RequestContext
|
|
|
16
16
|
from mcp.types import (
|
|
17
17
|
ContentBlock,
|
|
18
18
|
CreateMessageResult,
|
|
19
|
+
IncludeContext,
|
|
19
20
|
ModelHint,
|
|
20
21
|
ModelPreferences,
|
|
21
22
|
Root,
|
|
@@ -272,6 +273,7 @@ class Context:
|
|
|
272
273
|
self,
|
|
273
274
|
messages: str | list[str | SamplingMessage],
|
|
274
275
|
system_prompt: str | None = None,
|
|
276
|
+
include_context: IncludeContext | None = None,
|
|
275
277
|
temperature: float | None = None,
|
|
276
278
|
max_tokens: int | None = None,
|
|
277
279
|
model_preferences: ModelPreferences | str | list[str] | None = None,
|
|
@@ -304,6 +306,7 @@ class Context:
|
|
|
304
306
|
result: CreateMessageResult = await self.session.create_message(
|
|
305
307
|
messages=sampling_messages,
|
|
306
308
|
system_prompt=system_prompt,
|
|
309
|
+
include_context=include_context,
|
|
307
310
|
temperature=temperature,
|
|
308
311
|
max_tokens=max_tokens,
|
|
309
312
|
model_preferences=self._parse_model_preferences(model_preferences),
|
fastmcp/server/openapi.py
CHANGED
|
@@ -344,18 +344,28 @@ class OpenAPITool(Tool):
|
|
|
344
344
|
suffixed_name = f"{p.name}__{p.location}"
|
|
345
345
|
param_value = None
|
|
346
346
|
|
|
347
|
+
suffixed_value = arguments.get(suffixed_name)
|
|
347
348
|
if (
|
|
348
349
|
suffixed_name in arguments
|
|
349
|
-
and
|
|
350
|
-
and
|
|
350
|
+
and suffixed_value is not None
|
|
351
|
+
and suffixed_value != ""
|
|
352
|
+
and not (
|
|
353
|
+
isinstance(suffixed_value, list | dict)
|
|
354
|
+
and len(suffixed_value) == 0
|
|
355
|
+
)
|
|
351
356
|
):
|
|
352
357
|
param_value = arguments[suffixed_name]
|
|
353
|
-
|
|
354
|
-
p.name
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
358
|
+
else:
|
|
359
|
+
name_value = arguments.get(p.name)
|
|
360
|
+
if (
|
|
361
|
+
p.name in arguments
|
|
362
|
+
and name_value is not None
|
|
363
|
+
and name_value != ""
|
|
364
|
+
and not (
|
|
365
|
+
isinstance(name_value, list | dict) and len(name_value) == 0
|
|
366
|
+
)
|
|
367
|
+
):
|
|
368
|
+
param_value = arguments[p.name]
|
|
359
369
|
|
|
360
370
|
if param_value is not None:
|
|
361
371
|
# Handle different parameter styles and types
|
|
@@ -367,7 +377,11 @@ class OpenAPITool(Tool):
|
|
|
367
377
|
) # Default explode for query is True
|
|
368
378
|
|
|
369
379
|
# Handle deepObject style for object parameters
|
|
370
|
-
if
|
|
380
|
+
if (
|
|
381
|
+
param_style == "deepObject"
|
|
382
|
+
and isinstance(param_value, dict)
|
|
383
|
+
and len(param_value) > 0
|
|
384
|
+
):
|
|
371
385
|
if param_explode:
|
|
372
386
|
# deepObject with explode=true: object properties become separate parameters
|
|
373
387
|
# e.g., target[id]=123&target[type]=user
|
|
@@ -386,6 +400,7 @@ class OpenAPITool(Tool):
|
|
|
386
400
|
elif (
|
|
387
401
|
isinstance(param_value, list)
|
|
388
402
|
and p.schema_.get("type") == "array"
|
|
403
|
+
and len(param_value) > 0
|
|
389
404
|
):
|
|
390
405
|
if param_explode:
|
|
391
406
|
# When explode=True, we pass the array directly, which HTTPX will serialize
|
|
@@ -444,9 +459,7 @@ class OpenAPITool(Tool):
|
|
|
444
459
|
params_to_exclude.add(p.name)
|
|
445
460
|
|
|
446
461
|
body_params = {
|
|
447
|
-
k: v
|
|
448
|
-
for k, v in arguments.items()
|
|
449
|
-
if k not in params_to_exclude and k != "context"
|
|
462
|
+
k: v for k, v in arguments.items() if k not in params_to_exclude
|
|
450
463
|
}
|
|
451
464
|
|
|
452
465
|
if body_params:
|
fastmcp/server/proxy.py
CHANGED
|
@@ -580,3 +580,45 @@ class ProxyClient(Client[ClientTransportT]):
|
|
|
580
580
|
"""
|
|
581
581
|
ctx = get_context()
|
|
582
582
|
await ctx.report_progress(progress, total, message)
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
class StatefulProxyClient(ProxyClient[ClientTransportT]):
|
|
586
|
+
"""
|
|
587
|
+
A proxy client that provides a stateful client factory for the proxy server.
|
|
588
|
+
|
|
589
|
+
The stateful proxy client bound its copy to the server session.
|
|
590
|
+
And it will be disconnected when the session is exited.
|
|
591
|
+
|
|
592
|
+
This is useful to proxy a stateful mcp server such as the Playwright MCP server.
|
|
593
|
+
Note that it is essential to ensure that the proxy server itself is also stateful.
|
|
594
|
+
"""
|
|
595
|
+
|
|
596
|
+
async def __aexit__(self, exc_type, exc_value, traceback) -> None:
|
|
597
|
+
"""
|
|
598
|
+
The stateful proxy client will be forced disconnected when the session is exited.
|
|
599
|
+
So we do nothing here.
|
|
600
|
+
"""
|
|
601
|
+
pass
|
|
602
|
+
|
|
603
|
+
def new_stateful(self) -> Client[ClientTransportT]:
|
|
604
|
+
"""
|
|
605
|
+
Create a new stateful proxy client instance with the same configuration.
|
|
606
|
+
|
|
607
|
+
Use this method as the client factory for stateful proxy server.
|
|
608
|
+
"""
|
|
609
|
+
session = get_context().session
|
|
610
|
+
proxy_client = session.__dict__.get("_proxy_client", None)
|
|
611
|
+
|
|
612
|
+
if proxy_client is None:
|
|
613
|
+
proxy_client = self.new()
|
|
614
|
+
logger.debug(f"{proxy_client} created for {session}")
|
|
615
|
+
session.__dict__["_proxy_client"] = proxy_client
|
|
616
|
+
|
|
617
|
+
async def _on_session_exit():
|
|
618
|
+
proxy_client: Client = session.__dict__.pop("_proxy_client")
|
|
619
|
+
logger.debug(f"{proxy_client} will be disconnect")
|
|
620
|
+
await proxy_client._disconnect(force=True)
|
|
621
|
+
|
|
622
|
+
session._exit_stack.push_async_callback(_on_session_exit)
|
|
623
|
+
|
|
624
|
+
return proxy_client
|
fastmcp/server/server.py
CHANGED
|
@@ -1662,8 +1662,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1662
1662
|
resource_separator: Deprecated. Separator character for resource URIs.
|
|
1663
1663
|
prompt_separator: Deprecated. Separator character for prompt names.
|
|
1664
1664
|
"""
|
|
1665
|
-
from fastmcp.
|
|
1666
|
-
from fastmcp.server.proxy import FastMCPProxy, ProxyClient
|
|
1665
|
+
from fastmcp.server.proxy import FastMCPProxy
|
|
1667
1666
|
|
|
1668
1667
|
# Deprecated since 2.9.0
|
|
1669
1668
|
# Prior to 2.9.0, the first positional argument was the prefix and the
|
|
@@ -1715,7 +1714,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1715
1714
|
as_proxy = server._has_lifespan
|
|
1716
1715
|
|
|
1717
1716
|
if as_proxy and not isinstance(server, FastMCPProxy):
|
|
1718
|
-
server =
|
|
1717
|
+
server = FastMCP.as_proxy(server)
|
|
1719
1718
|
|
|
1720
1719
|
# Delegate mounting to all three managers
|
|
1721
1720
|
mounted_server = MountedServer(
|
fastmcp/settings.py
CHANGED
|
@@ -5,7 +5,7 @@ import warnings
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Annotated, Any, Literal
|
|
7
7
|
|
|
8
|
-
from pydantic import Field,
|
|
8
|
+
from pydantic import Field, field_validator
|
|
9
9
|
from pydantic.fields import FieldInfo
|
|
10
10
|
from pydantic_settings import (
|
|
11
11
|
BaseSettings,
|
|
@@ -99,7 +99,16 @@ class Settings(BaseSettings):
|
|
|
99
99
|
home: Path = Path.home() / ".fastmcp"
|
|
100
100
|
|
|
101
101
|
test_mode: bool = False
|
|
102
|
+
|
|
102
103
|
log_level: LOG_LEVEL = "INFO"
|
|
104
|
+
|
|
105
|
+
@field_validator("log_level", mode="before")
|
|
106
|
+
@classmethod
|
|
107
|
+
def normalize_log_level(cls, v):
|
|
108
|
+
if isinstance(v, str):
|
|
109
|
+
return v.upper()
|
|
110
|
+
return v
|
|
111
|
+
|
|
103
112
|
enable_rich_tracebacks: Annotated[
|
|
104
113
|
bool,
|
|
105
114
|
Field(
|
|
@@ -162,17 +171,6 @@ class Settings(BaseSettings):
|
|
|
162
171
|
),
|
|
163
172
|
] = None
|
|
164
173
|
|
|
165
|
-
@model_validator(mode="after")
|
|
166
|
-
def setup_logging(self) -> Self:
|
|
167
|
-
"""Finalize the settings."""
|
|
168
|
-
from fastmcp.utilities.logging import configure_logging
|
|
169
|
-
|
|
170
|
-
configure_logging(
|
|
171
|
-
self.log_level, enable_rich_tracebacks=self.enable_rich_tracebacks
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
return self
|
|
175
|
-
|
|
176
174
|
# HTTP settings
|
|
177
175
|
host: str = "127.0.0.1"
|
|
178
176
|
port: int = 8000
|
fastmcp/tools/tool.py
CHANGED
|
@@ -185,8 +185,9 @@ class Tool(FastMCPComponent):
|
|
|
185
185
|
tool: Tool,
|
|
186
186
|
transform_fn: Callable[..., Any] | None = None,
|
|
187
187
|
name: str | None = None,
|
|
188
|
+
title: str | None | NotSetT = NotSet,
|
|
188
189
|
transform_args: dict[str, ArgTransform] | None = None,
|
|
189
|
-
description: str | None =
|
|
190
|
+
description: str | None | NotSetT = NotSet,
|
|
190
191
|
tags: set[str] | None = None,
|
|
191
192
|
annotations: ToolAnnotations | None = None,
|
|
192
193
|
output_schema: dict[str, Any] | None | Literal[False] = None,
|
|
@@ -199,6 +200,7 @@ class Tool(FastMCPComponent):
|
|
|
199
200
|
tool=tool,
|
|
200
201
|
transform_fn=transform_fn,
|
|
201
202
|
name=name,
|
|
203
|
+
title=title,
|
|
202
204
|
transform_args=transform_args,
|
|
203
205
|
description=description,
|
|
204
206
|
tags=tags,
|
|
@@ -397,7 +399,7 @@ class ParsedFunction:
|
|
|
397
399
|
|
|
398
400
|
try:
|
|
399
401
|
type_adapter = get_cached_typeadapter(clean_output_type)
|
|
400
|
-
base_schema = type_adapter.json_schema()
|
|
402
|
+
base_schema = type_adapter.json_schema(mode="serialization")
|
|
401
403
|
|
|
402
404
|
# Generate schema for wrapped type if it's non-object
|
|
403
405
|
# because MCP requires that output schemas are objects
|
|
@@ -408,7 +410,7 @@ class ParsedFunction:
|
|
|
408
410
|
# Use the wrapped result schema directly
|
|
409
411
|
wrapped_type = _WrappedResult[clean_output_type]
|
|
410
412
|
wrapped_adapter = get_cached_typeadapter(wrapped_type)
|
|
411
|
-
output_schema = wrapped_adapter.json_schema()
|
|
413
|
+
output_schema = wrapped_adapter.json_schema(mode="serialization")
|
|
412
414
|
output_schema["x-fastmcp-wrap-result"] = True
|
|
413
415
|
else:
|
|
414
416
|
output_schema = base_schema
|
fastmcp/tools/tool_manager.py
CHANGED
|
@@ -76,7 +76,7 @@ class ToolManager:
|
|
|
76
76
|
except Exception as e:
|
|
77
77
|
# Skip failed mounts silently, matches existing behavior
|
|
78
78
|
logger.warning(
|
|
79
|
-
f"Failed to get tools from mounted
|
|
79
|
+
f"Failed to get tools from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
|
|
80
80
|
)
|
|
81
81
|
continue
|
|
82
82
|
|
fastmcp/tools/tool_transform.py
CHANGED
|
@@ -325,7 +325,8 @@ class TransformedTool(Tool):
|
|
|
325
325
|
cls,
|
|
326
326
|
tool: Tool,
|
|
327
327
|
name: str | None = None,
|
|
328
|
-
|
|
328
|
+
title: str | None | NotSetT = NotSet,
|
|
329
|
+
description: str | None | NotSetT = NotSet,
|
|
329
330
|
tags: set[str] | None = None,
|
|
330
331
|
transform_fn: Callable[..., Any] | None = None,
|
|
331
332
|
transform_args: dict[str, ArgTransform] | None = None,
|
|
@@ -342,6 +343,7 @@ class TransformedTool(Tool):
|
|
|
342
343
|
to call the parent tool. Functions with **kwargs receive transformed
|
|
343
344
|
argument names.
|
|
344
345
|
name: New name for the tool. Defaults to parent tool's name.
|
|
346
|
+
title: New title for the tool. Defaults to parent tool's title.
|
|
345
347
|
transform_args: Optional transformations for parent tool arguments.
|
|
346
348
|
Only specified arguments are transformed, others pass through unchanged:
|
|
347
349
|
- Simple rename (str)
|
|
@@ -506,13 +508,18 @@ class TransformedTool(Tool):
|
|
|
506
508
|
f"{', '.join(sorted(duplicates))}"
|
|
507
509
|
)
|
|
508
510
|
|
|
509
|
-
|
|
511
|
+
final_name = name or tool.name
|
|
512
|
+
final_description = (
|
|
513
|
+
description if not isinstance(description, NotSetT) else tool.description
|
|
514
|
+
)
|
|
515
|
+
final_title = title if not isinstance(title, NotSetT) else tool.title
|
|
510
516
|
|
|
511
517
|
transformed_tool = cls(
|
|
512
518
|
fn=final_fn,
|
|
513
519
|
forwarding_fn=forwarding_fn,
|
|
514
520
|
parent_tool=tool,
|
|
515
|
-
name=
|
|
521
|
+
name=final_name,
|
|
522
|
+
title=final_title,
|
|
516
523
|
description=final_description,
|
|
517
524
|
parameters=final_schema,
|
|
518
525
|
output_schema=final_output_schema,
|
fastmcp/utilities/openapi.py
CHANGED
|
@@ -1068,15 +1068,16 @@ def _replace_ref_with_defs(
|
|
|
1068
1068
|
"""
|
|
1069
1069
|
schema = info.copy()
|
|
1070
1070
|
if ref_path := schema.get("$ref"):
|
|
1071
|
-
if ref_path
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1071
|
+
if isinstance(ref_path, str):
|
|
1072
|
+
if ref_path.startswith("#/components/schemas/"):
|
|
1073
|
+
schema_name = ref_path.split("/")[-1]
|
|
1074
|
+
schema["$ref"] = f"#/$defs/{schema_name}"
|
|
1075
|
+
elif not ref_path.startswith("#/"):
|
|
1076
|
+
raise ValueError(
|
|
1077
|
+
f"External or non-local reference not supported: {ref_path}. "
|
|
1078
|
+
f"FastMCP only supports local schema references starting with '#/'. "
|
|
1079
|
+
f"Please include all schema definitions within the OpenAPI document."
|
|
1080
|
+
)
|
|
1080
1081
|
elif properties := schema.get("properties"):
|
|
1081
1082
|
if "$ref" in properties:
|
|
1082
1083
|
schema["properties"] = _replace_ref_with_defs(properties)
|
|
@@ -1095,6 +1096,81 @@ def _replace_ref_with_defs(
|
|
|
1095
1096
|
return schema
|
|
1096
1097
|
|
|
1097
1098
|
|
|
1099
|
+
def _make_optional_parameter_nullable(schema: dict[str, Any]) -> dict[str, Any]:
|
|
1100
|
+
"""
|
|
1101
|
+
Make an optional parameter schema nullable to allow None values.
|
|
1102
|
+
|
|
1103
|
+
For optional parameters, we need to allow null values in addition to the
|
|
1104
|
+
specified type to handle cases where None is passed for optional parameters.
|
|
1105
|
+
"""
|
|
1106
|
+
# If schema already has multiple types or is already nullable, don't modify
|
|
1107
|
+
if "anyOf" in schema or "oneOf" in schema or "allOf" in schema:
|
|
1108
|
+
return schema
|
|
1109
|
+
|
|
1110
|
+
# If it's already nullable (type includes null), don't modify
|
|
1111
|
+
if isinstance(schema.get("type"), list) and "null" in schema["type"]:
|
|
1112
|
+
return schema
|
|
1113
|
+
|
|
1114
|
+
# Create a new schema that allows null in addition to the original type
|
|
1115
|
+
if "type" in schema:
|
|
1116
|
+
original_type = schema["type"]
|
|
1117
|
+
|
|
1118
|
+
if isinstance(original_type, str):
|
|
1119
|
+
# Single type - make it a union with null
|
|
1120
|
+
nullable_schema = schema.copy()
|
|
1121
|
+
|
|
1122
|
+
nested_non_nullable_schema = {
|
|
1123
|
+
"type": original_type,
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
# If the original type is an array, move the array-specific properties into the now-nested schema
|
|
1127
|
+
# https://json-schema.org/understanding-json-schema/reference/array
|
|
1128
|
+
if original_type == "array":
|
|
1129
|
+
for array_property in [
|
|
1130
|
+
"items",
|
|
1131
|
+
"prefixItems",
|
|
1132
|
+
"unevaluatedItems",
|
|
1133
|
+
"contains",
|
|
1134
|
+
"minContains",
|
|
1135
|
+
"maxContains",
|
|
1136
|
+
"minItems",
|
|
1137
|
+
"maxItems",
|
|
1138
|
+
"uniqueItems",
|
|
1139
|
+
]:
|
|
1140
|
+
if array_property in nullable_schema:
|
|
1141
|
+
nested_non_nullable_schema[array_property] = nullable_schema[
|
|
1142
|
+
array_property
|
|
1143
|
+
]
|
|
1144
|
+
del nullable_schema[array_property]
|
|
1145
|
+
|
|
1146
|
+
# If the original type is an object, move the object-specific properties into the now-nested schema
|
|
1147
|
+
# https://json-schema.org/understanding-json-schema/reference/object
|
|
1148
|
+
elif original_type == "object":
|
|
1149
|
+
for object_property in [
|
|
1150
|
+
"properties",
|
|
1151
|
+
"patternProperties",
|
|
1152
|
+
"additionalProperties",
|
|
1153
|
+
"unevaluatedProperties",
|
|
1154
|
+
"required",
|
|
1155
|
+
"propertyNames",
|
|
1156
|
+
"minProperties",
|
|
1157
|
+
"maxProperties",
|
|
1158
|
+
]:
|
|
1159
|
+
if object_property in nullable_schema:
|
|
1160
|
+
nested_non_nullable_schema[object_property] = nullable_schema[
|
|
1161
|
+
object_property
|
|
1162
|
+
]
|
|
1163
|
+
del nullable_schema[object_property]
|
|
1164
|
+
|
|
1165
|
+
nullable_schema["anyOf"] = [nested_non_nullable_schema, {"type": "null"}]
|
|
1166
|
+
|
|
1167
|
+
# Remove the original type since we're using anyOf
|
|
1168
|
+
del nullable_schema["type"]
|
|
1169
|
+
return nullable_schema
|
|
1170
|
+
|
|
1171
|
+
return schema
|
|
1172
|
+
|
|
1173
|
+
|
|
1098
1174
|
def _combine_schemas(route: HTTPRoute) -> dict[str, Any]:
|
|
1099
1175
|
"""
|
|
1100
1176
|
Combines parameter and request body schemas into a single schema.
|
|
@@ -1156,15 +1232,25 @@ def _combine_schemas(route: HTTPRoute) -> dict[str, Any]:
|
|
|
1156
1232
|
else:
|
|
1157
1233
|
param_schema["description"] = location_desc
|
|
1158
1234
|
|
|
1235
|
+
# Make optional parameters nullable to allow None values
|
|
1236
|
+
if not param.required:
|
|
1237
|
+
param_schema = _make_optional_parameter_nullable(param_schema)
|
|
1238
|
+
|
|
1159
1239
|
properties[suffixed_name] = param_schema
|
|
1160
1240
|
else:
|
|
1161
1241
|
# No collision, use original name
|
|
1162
1242
|
if param.required:
|
|
1163
1243
|
required.append(param.name)
|
|
1164
|
-
|
|
1244
|
+
param_schema = _replace_ref_with_defs(
|
|
1165
1245
|
param.schema_.copy(), param.description
|
|
1166
1246
|
)
|
|
1167
1247
|
|
|
1248
|
+
# Make optional parameters nullable to allow None values
|
|
1249
|
+
if not param.required:
|
|
1250
|
+
param_schema = _make_optional_parameter_nullable(param_schema)
|
|
1251
|
+
|
|
1252
|
+
properties[param.name] = param_schema
|
|
1253
|
+
|
|
1168
1254
|
# Add request body properties (no suffixes for body parameters)
|
|
1169
1255
|
if route.request_body and route.request_body.content_schema:
|
|
1170
1256
|
for prop_name, prop_schema in body_props.items():
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmcp
|
|
3
|
-
Version: 2.10.
|
|
3
|
+
Version: 2.10.6
|
|
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
|
|
@@ -38,7 +38,7 @@ Description-Content-Type: text/markdown
|
|
|
38
38
|
|
|
39
39
|
<strong>The fast, Pythonic way to build MCP servers and clients.</strong>
|
|
40
40
|
|
|
41
|
-
*FastMCP is made with
|
|
41
|
+
*FastMCP is made with ☕️ by [Prefect](https://www.prefect.io/)*
|
|
42
42
|
|
|
43
43
|
[](https://gofastmcp.com)
|
|
44
44
|
[](https://pypi.org/project/fastmcp)
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
fastmcp/__init__.py,sha256=
|
|
1
|
+
fastmcp/__init__.py,sha256=B_FAqsxbTJmwvJKyIDMOZWpUUdmO806bKo8RR32oZL0,1503
|
|
2
2
|
fastmcp/exceptions.py,sha256=-krEavxwddQau6T7MESCR4VjKNLfP9KHJrU1p3y72FU,744
|
|
3
3
|
fastmcp/mcp_config.py,sha256=hOF_NO7F9LbrbmRTj99TVSqHIul4sExNpf1T1HRLpzo,9606
|
|
4
4
|
fastmcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
fastmcp/settings.py,sha256=
|
|
5
|
+
fastmcp/settings.py,sha256=nGbuVgqpZbMHI4PjMzRoU3gJcC2rdiPhP2kWmsptWNU,8817
|
|
6
6
|
fastmcp/cli/__init__.py,sha256=Ii284TNoG5lxTP40ETMGhHEq3lQZWxu9m9JuU57kUpQ,87
|
|
7
7
|
fastmcp/cli/claude.py,sha256=IAlcZ4qZKBBj09jZUMEx7EANZE_IR3vcu7zOBJmMOuU,4567
|
|
8
8
|
fastmcp/cli/cli.py,sha256=Q4HDVDSty1Gx6qN_M4FDx8RYat34vhWT25338X-quNs,12672
|
|
9
9
|
fastmcp/cli/run.py,sha256=V268Lf7LXdeMZ4_D4fKdFST7cOs8pGgLXZTxtcEJRWg,6715
|
|
10
|
-
fastmcp/cli/install/__init__.py,sha256=
|
|
10
|
+
fastmcp/cli/install/__init__.py,sha256=cDEc0hhuf_xwGpI6ghpqlvlMdBMJUGHq_rs-tgmOJZ8,695
|
|
11
11
|
fastmcp/cli/install/claude_code.py,sha256=VlFVGKKRppkmp42io6VPTQrQHgNww4H2ppa6mAWM-Ao,6430
|
|
12
12
|
fastmcp/cli/install/claude_desktop.py,sha256=o3p3KiW0QzsrzOEa3OU_gkvzTuFSkabDwEqwwgp1mYU,5590
|
|
13
13
|
fastmcp/cli/install/cursor.py,sha256=296a22T3fdaC8deCwh3TreVFoJ0QpjLxRF1gNsp3Wjg,5555
|
|
14
|
-
fastmcp/cli/install/
|
|
14
|
+
fastmcp/cli/install/mcp_json.py,sha256=FfLm6OdinHunrau0GbZY6P1LUWTKYZvHvaEoyvAqguc,4602
|
|
15
15
|
fastmcp/cli/install/shared.py,sha256=Y0YZei1YemVCkg0ieUgfRse-lqSlIn5Ho8t6pB9nDa4,2683
|
|
16
16
|
fastmcp/client/__init__.py,sha256=kd2hhSuD8rZuF87c9zlPJP_icJ-Rx3exyNoK0EzfOtE,617
|
|
17
17
|
fastmcp/client/client.py,sha256=GniETS28L8B-ahzztU2ZLI_XSCcEib-miGzE2ZnG4Xc,34054
|
|
@@ -42,21 +42,21 @@ fastmcp/contrib/mcp_mixin/example.py,sha256=GnunkXmtG5hLLTUsM8aW5ZURU52Z8vI4tNLl
|
|
|
42
42
|
fastmcp/contrib/mcp_mixin/mcp_mixin.py,sha256=sUSJ2o0sTsb061MyPN2xuYP0oI4W6YVQXupY3nnjD50,8687
|
|
43
43
|
fastmcp/prompts/__init__.py,sha256=An8uMBUh9Hrb7qqcn_5_Hent7IOeSh7EA2IUVsIrtHc,179
|
|
44
44
|
fastmcp/prompts/prompt.py,sha256=Ux_FT8GTnejvjDWEgf5nSAIwbhV-9otgCJm-EqnzxvY,13861
|
|
45
|
-
fastmcp/prompts/prompt_manager.py,sha256=
|
|
45
|
+
fastmcp/prompts/prompt_manager.py,sha256=lErodAnqVHRu_1FttfHTh-arWf9rkCOCGTxZzZP_3SE,7707
|
|
46
46
|
fastmcp/resources/__init__.py,sha256=y1iAuqx-GIrS1NqIYzKezIDiYyjNEzzHD35epHpMnXE,463
|
|
47
47
|
fastmcp/resources/resource.py,sha256=GGCUHUQe4ALrVJc-Ng1uz8-mtQSg_vuVA71H_qJkko0,6031
|
|
48
|
-
fastmcp/resources/resource_manager.py,sha256=
|
|
48
|
+
fastmcp/resources/resource_manager.py,sha256=jmnKAkA7jevvKeccAVDsAAeXvI0IkrQeIXHCC36tf_0,19837
|
|
49
49
|
fastmcp/resources/template.py,sha256=mYsw3xPWRq1AgOVFkG_A1otY3NXYw5oUJnVzMC1Qy80,10346
|
|
50
50
|
fastmcp/resources/types.py,sha256=SiYNLnpXT-mHgNUrzqKUvXYUsY-V3gwJIrYdJfFwDDo,4868
|
|
51
51
|
fastmcp/server/__init__.py,sha256=bMD4aQD4yJqLz7-mudoNsyeV8UgQfRAg3PRwPvwTEds,119
|
|
52
|
-
fastmcp/server/context.py,sha256=
|
|
52
|
+
fastmcp/server/context.py,sha256=hYDoc0O72SnyOloQvGRuYELOSkdAqQkSOBczVehokX0,20378
|
|
53
53
|
fastmcp/server/dependencies.py,sha256=iKJdz1XsVJcrfHo_reXj9ZSldw-HeAwsp9S6lAgfGA8,2358
|
|
54
54
|
fastmcp/server/elicitation.py,sha256=jZIHjV4NjhYbT-w8pBArwd0vNzP8OYwzmsnWDdk6Bd0,6136
|
|
55
55
|
fastmcp/server/http.py,sha256=d0Jij4HVTaAohluRXArSniXLb1HcHP3ytbe-mMHg6nE,11678
|
|
56
56
|
fastmcp/server/low_level.py,sha256=LNmc_nU_wx-fRG8OEHdLPKopZpovcrWlyAxJzKss3TA,1239
|
|
57
|
-
fastmcp/server/openapi.py,sha256=
|
|
58
|
-
fastmcp/server/proxy.py,sha256=
|
|
59
|
-
fastmcp/server/server.py,sha256=
|
|
57
|
+
fastmcp/server/openapi.py,sha256=L6lZy3kXknc1vzm3rJi3fe2Q-aMRluktp1EFBJq8Cs0,42079
|
|
58
|
+
fastmcp/server/proxy.py,sha256=7d92XLWCGgiTTIW0LWgdqacNc9JtHGj4BIP72aKrHwI,23513
|
|
59
|
+
fastmcp/server/server.py,sha256=2RfMq7W4jeenSsz54wjN6Yl0-HWTl9HkBBHMUV6rejg,82703
|
|
60
60
|
fastmcp/server/auth/__init__.py,sha256=doHCLwOIElvH1NrTdpeP9JKfnNf3MDYPSpQfdsQ-uI0,84
|
|
61
61
|
fastmcp/server/auth/auth.py,sha256=A00OKxglEMrGMMIiMbc6UmpGc2VoWDkEVU5g2pIzDIg,2119
|
|
62
62
|
fastmcp/server/auth/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -70,9 +70,9 @@ fastmcp/server/middleware/middleware.py,sha256=jFy7NmLHvGmrGkKViS1PvR_7oGM1EklxN
|
|
|
70
70
|
fastmcp/server/middleware/rate_limiting.py,sha256=VTrCoQFmWCm0BxwOrNfG21CBFDDOKJT7IiSEjpJgmPA,7921
|
|
71
71
|
fastmcp/server/middleware/timing.py,sha256=lL_xc-ErLD5lplfvd5-HIyWEbZhgNBYkcQ74KFXAMkA,5591
|
|
72
72
|
fastmcp/tools/__init__.py,sha256=vzqb-Y7Kf0d5T0aOsld-O-FA8kD7-4uFExChewFHEzY,201
|
|
73
|
-
fastmcp/tools/tool.py,sha256
|
|
74
|
-
fastmcp/tools/tool_manager.py,sha256=
|
|
75
|
-
fastmcp/tools/tool_transform.py,sha256=
|
|
73
|
+
fastmcp/tools/tool.py,sha256=-Cia4f7iMmWJJkUAze5AggGu61wGAtipMJno8CoOjko,17589
|
|
74
|
+
fastmcp/tools/tool_manager.py,sha256=xksy5A81YL1mGIJ9m6aBwTnFoiuNieI50tpmZ5ONChE,7769
|
|
75
|
+
fastmcp/tools/tool_transform.py,sha256=Fgf_hKXUVC2_UKa551GGyrS5JiChCvK8j0msh-iupA4,32870
|
|
76
76
|
fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
|
|
77
77
|
fastmcp/utilities/cache.py,sha256=aV3oZ-ZhMgLSM9iAotlUlEy5jFvGXrVo0Y5Bj4PBtqY,707
|
|
78
78
|
fastmcp/utilities/cli.py,sha256=TXuSyALFAGJwi7tWEBwBmaGhYZBdF1aG6dLgl3zjM1w,3272
|
|
@@ -83,11 +83,11 @@ fastmcp/utilities/inspect.py,sha256=XNA0dfYM5G-FVbJaVJO8loSUUCNypyLA-QjqTOneJyU,
|
|
|
83
83
|
fastmcp/utilities/json_schema.py,sha256=8qyb3qOvk1gc3p63uP6LGyKdOANkNdD9YA32OiBxyNw,5495
|
|
84
84
|
fastmcp/utilities/json_schema_type.py,sha256=Sml03nJGOnUfxCGrHWRMwZMultV0X5JThMepUnHIUiA,22377
|
|
85
85
|
fastmcp/utilities/logging.py,sha256=1y7oNmy8WrR0NsfNVw1LPoKu92OFdmzIO65syOKi_BI,1388
|
|
86
|
-
fastmcp/utilities/openapi.py,sha256=
|
|
86
|
+
fastmcp/utilities/openapi.py,sha256=h9eB85w0lWiTx0NAS5WxwzKpNBdxHMAQ1zPv0lrjxvo,55566
|
|
87
87
|
fastmcp/utilities/tests.py,sha256=kZH8HQAC702a5vNJb4K0tO1ll9CZADWQ_P-5ERWSvSA,4242
|
|
88
88
|
fastmcp/utilities/types.py,sha256=c6HPvHCpkq8EXh0hWjaUlj9aCZklmxzAQHCXZy7llNo,10636
|
|
89
|
-
fastmcp-2.10.
|
|
90
|
-
fastmcp-2.10.
|
|
91
|
-
fastmcp-2.10.
|
|
92
|
-
fastmcp-2.10.
|
|
93
|
-
fastmcp-2.10.
|
|
89
|
+
fastmcp-2.10.6.dist-info/METADATA,sha256=lQDaHuao4LS0HuJBYOv0nvhHs2ACFNUmNqWknT6Zmxk,17844
|
|
90
|
+
fastmcp-2.10.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
91
|
+
fastmcp-2.10.6.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
|
|
92
|
+
fastmcp-2.10.6.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
93
|
+
fastmcp-2.10.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|