fastmcp 2.2.9__py3-none-any.whl → 2.2.10__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/settings.py +13 -0
- fastmcp/tools/tool.py +28 -12
- fastmcp/utilities/tests.py +41 -0
- fastmcp/utilities/types.py +4 -7
- {fastmcp-2.2.9.dist-info → fastmcp-2.2.10.dist-info}/METADATA +5 -1
- {fastmcp-2.2.9.dist-info → fastmcp-2.2.10.dist-info}/RECORD +9 -8
- {fastmcp-2.2.9.dist-info → fastmcp-2.2.10.dist-info}/WHEEL +0 -0
- {fastmcp-2.2.9.dist-info → fastmcp-2.2.10.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.2.9.dist-info → fastmcp-2.2.10.dist-info}/licenses/LICENSE +0 -0
fastmcp/settings.py
CHANGED
|
@@ -27,6 +27,16 @@ class Settings(BaseSettings):
|
|
|
27
27
|
|
|
28
28
|
test_mode: bool = False
|
|
29
29
|
log_level: LOG_LEVEL = "INFO"
|
|
30
|
+
tool_attempt_parse_json_args: bool = Field(
|
|
31
|
+
default=False,
|
|
32
|
+
description="""
|
|
33
|
+
Note: this enables a legacy behavior. If True, will attempt to parse
|
|
34
|
+
stringified JSON lists and objects strings in tool arguments before
|
|
35
|
+
passing them to the tool. This is an old behavior that can create
|
|
36
|
+
unexpected type coercion issues, but may be helpful for less powerful
|
|
37
|
+
LLMs that stringify JSON instead of passing actual lists and objects.
|
|
38
|
+
Defaults to False.""",
|
|
39
|
+
)
|
|
30
40
|
|
|
31
41
|
|
|
32
42
|
class ServerSettings(BaseSettings):
|
|
@@ -83,3 +93,6 @@ class ClientSettings(BaseSettings):
|
|
|
83
93
|
)
|
|
84
94
|
|
|
85
95
|
log_level: LOG_LEVEL = Field(default_factory=lambda: Settings().log_level)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
settings = Settings()
|
fastmcp/tools/tool.py
CHANGED
|
@@ -10,6 +10,7 @@ from mcp.types import EmbeddedResource, ImageContent, TextContent, ToolAnnotatio
|
|
|
10
10
|
from mcp.types import Tool as MCPTool
|
|
11
11
|
from pydantic import BaseModel, BeforeValidator, Field
|
|
12
12
|
|
|
13
|
+
import fastmcp
|
|
13
14
|
from fastmcp.exceptions import ToolError
|
|
14
15
|
from fastmcp.utilities.json_schema import prune_params
|
|
15
16
|
from fastmcp.utilities.logging import get_logger
|
|
@@ -107,6 +108,7 @@ class Tool(BaseModel):
|
|
|
107
108
|
context: Context[ServerSessionT, LifespanContextT] | None = None,
|
|
108
109
|
) -> list[TextContent | ImageContent | EmbeddedResource]:
|
|
109
110
|
"""Run the tool with arguments."""
|
|
111
|
+
|
|
110
112
|
try:
|
|
111
113
|
injected_args = (
|
|
112
114
|
{self.context_kwarg: context} if self.context_kwarg is not None else {}
|
|
@@ -114,22 +116,36 @@ class Tool(BaseModel):
|
|
|
114
116
|
|
|
115
117
|
parsed_args = arguments.copy()
|
|
116
118
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
if fastmcp.settings.settings.tool_attempt_parse_json_args:
|
|
120
|
+
# Pre-parse data from JSON in order to handle cases like `["a", "b", "c"]`
|
|
121
|
+
# being passed in as JSON inside a string rather than an actual list.
|
|
122
|
+
#
|
|
123
|
+
# Claude desktop is prone to this - in fact it seems incapable of NOT doing
|
|
124
|
+
# this. For sub-models, it tends to pass dicts (JSON objects) as JSON strings,
|
|
125
|
+
# which can be pre-parsed here.
|
|
126
|
+
signature = inspect.signature(self.fn)
|
|
127
|
+
for param_name in self.parameters["properties"]:
|
|
128
|
+
arg = parsed_args.get(param_name, None)
|
|
129
|
+
# if not in signature, we won't have annotations, so skip logic
|
|
130
|
+
if param_name not in signature.parameters:
|
|
131
|
+
continue
|
|
132
|
+
# if not a string, we won't have a JSON to parse, so skip logic
|
|
133
|
+
if not isinstance(arg, str):
|
|
134
|
+
continue
|
|
135
|
+
# skip if the type is a simple type (int, float, bool)
|
|
136
|
+
if signature.parameters[param_name].annotation in (
|
|
137
|
+
int,
|
|
138
|
+
float,
|
|
139
|
+
bool,
|
|
140
|
+
):
|
|
141
|
+
continue
|
|
125
142
|
try:
|
|
126
|
-
parsed_args[param_name] = json.loads(
|
|
143
|
+
parsed_args[param_name] = json.loads(arg)
|
|
144
|
+
|
|
127
145
|
except json.JSONDecodeError:
|
|
128
146
|
pass
|
|
129
147
|
|
|
130
|
-
type_adapter = get_cached_typeadapter(
|
|
131
|
-
self.fn, config=frozenset([("coerce_numbers_to_str", True)])
|
|
132
|
-
)
|
|
148
|
+
type_adapter = get_cached_typeadapter(self.fn)
|
|
133
149
|
result = type_adapter.validate_python(parsed_args | injected_args)
|
|
134
150
|
if inspect.isawaitable(result):
|
|
135
151
|
result = await result
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from fastmcp.settings import settings
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@contextmanager
|
|
9
|
+
def temporary_settings(**kwargs: Any):
|
|
10
|
+
"""
|
|
11
|
+
Temporarily override ControlFlow setting values.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
**kwargs: The settings to override, including nested settings.
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
Temporarily override a setting:
|
|
18
|
+
```python
|
|
19
|
+
import fastmcp
|
|
20
|
+
from fastmcp.utilities.tests import temporary_settings
|
|
21
|
+
|
|
22
|
+
with temporary_settings(log_level='DEBUG'):
|
|
23
|
+
assert fastmcp.settings.settings.log_level == 'DEBUG'
|
|
24
|
+
assert fastmcp.settings.settings.log_level == 'INFO'
|
|
25
|
+
```
|
|
26
|
+
"""
|
|
27
|
+
old_settings = copy.deepcopy(settings.model_dump())
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
# apply the new settings
|
|
31
|
+
for attr, value in kwargs.items():
|
|
32
|
+
if not hasattr(settings, attr):
|
|
33
|
+
raise AttributeError(f"Setting {attr} does not exist.")
|
|
34
|
+
setattr(settings, attr, value)
|
|
35
|
+
yield
|
|
36
|
+
|
|
37
|
+
finally:
|
|
38
|
+
# restore the old settings
|
|
39
|
+
for attr in kwargs:
|
|
40
|
+
if hasattr(settings, attr):
|
|
41
|
+
setattr(settings, attr, old_settings[attr])
|
fastmcp/utilities/types.py
CHANGED
|
@@ -6,26 +6,23 @@ from collections.abc import Callable
|
|
|
6
6
|
from functools import lru_cache
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from types import UnionType
|
|
9
|
-
from typing import Annotated,
|
|
9
|
+
from typing import Annotated, TypeVar, Union, get_args, get_origin
|
|
10
10
|
|
|
11
11
|
from mcp.types import ImageContent
|
|
12
|
-
from pydantic import
|
|
12
|
+
from pydantic import TypeAdapter
|
|
13
13
|
|
|
14
14
|
T = TypeVar("T")
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
@lru_cache(maxsize=5000)
|
|
18
|
-
def get_cached_typeadapter(
|
|
19
|
-
cls: T, config: frozenset[tuple[str, Any]] | None = None
|
|
20
|
-
) -> TypeAdapter[T]:
|
|
18
|
+
def get_cached_typeadapter(cls: T) -> TypeAdapter[T]:
|
|
21
19
|
"""
|
|
22
20
|
TypeAdapters are heavy objects, and in an application context we'd typically
|
|
23
21
|
create them once in a global scope and reuse them as often as possible.
|
|
24
22
|
However, this isn't feasible for user-generated functions. Instead, we use a
|
|
25
23
|
cache to minimize the cost of creating them as much as possible.
|
|
26
24
|
"""
|
|
27
|
-
|
|
28
|
-
return TypeAdapter(cls, config=ConfigDict(**config_dict))
|
|
25
|
+
return TypeAdapter(cls)
|
|
29
26
|
|
|
30
27
|
|
|
31
28
|
def issubclass_safe(cls: type, base: type) -> bool:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmcp
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.10
|
|
4
4
|
Summary: The fast, Pythonic way to build MCP servers.
|
|
5
5
|
Project-URL: Homepage, https://gofastmcp.com
|
|
6
6
|
Project-URL: Repository, https://github.com/jlowin/fastmcp
|
|
@@ -379,6 +379,10 @@ Run tests using pytest:
|
|
|
379
379
|
```bash
|
|
380
380
|
pytest
|
|
381
381
|
```
|
|
382
|
+
or if you want an overview of the code coverage
|
|
383
|
+
```bash
|
|
384
|
+
uv run pytest --cov=src --cov=examples --cov-report=html
|
|
385
|
+
```
|
|
382
386
|
|
|
383
387
|
### Static Checks
|
|
384
388
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
fastmcp/__init__.py,sha256=e-wu5UQpgduOauB-H8lzbnxv_H9K90fCJVnc1qgaAhM,413
|
|
2
2
|
fastmcp/exceptions.py,sha256=QKVHbftoZp4YZQ2NxA-t1SjztqspFdX95YTFOAmr5EE,640
|
|
3
3
|
fastmcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
fastmcp/settings.py,sha256=
|
|
4
|
+
fastmcp/settings.py,sha256=VS04TK0vVROcXy8tmj1n4ABx3f5XoR8IPXc6i1gK-fg,2752
|
|
5
5
|
fastmcp/cli/__init__.py,sha256=Ii284TNoG5lxTP40ETMGhHEq3lQZWxu9m9JuU57kUpQ,87
|
|
6
6
|
fastmcp/cli/claude.py,sha256=IAlcZ4qZKBBj09jZUMEx7EANZE_IR3vcu7zOBJmMOuU,4567
|
|
7
7
|
fastmcp/cli/cli.py,sha256=wsFIYTv48_nr0mcW8vjI1l7_thr4cOrRxzo9g-qr2l8,15726
|
|
@@ -35,7 +35,7 @@ fastmcp/server/openapi.py,sha256=1Yhep4zwHhZPlfNIo2Y3sIxfwHNJRdzw3Kc4DZrEW3Q,239
|
|
|
35
35
|
fastmcp/server/proxy.py,sha256=f7V1X9lDUanHS_iTzEf7Nt0WwkDOXDiJ3-XrMWdq3tw,9969
|
|
36
36
|
fastmcp/server/server.py,sha256=kXcnO_TzJuoColFjSBER8M5IaA4k9Cvwkrd5LUYKgRQ,43588
|
|
37
37
|
fastmcp/tools/__init__.py,sha256=ocw-SFTtN6vQ8fgnlF8iNAOflRmh79xS1xdO0Bc3QPE,96
|
|
38
|
-
fastmcp/tools/tool.py,sha256=
|
|
38
|
+
fastmcp/tools/tool.py,sha256=8TLSzP3PKzcYQg-S-3DLNqhwj2zKzSdWv98kTndgQ4U,8086
|
|
39
39
|
fastmcp/tools/tool_manager.py,sha256=whi3oYfTqUXC0vXWvSvGBaUqwo1YKZxSI-clRAKKnWQ,3606
|
|
40
40
|
fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
|
|
41
41
|
fastmcp/utilities/decorators.py,sha256=AjhjsetQZF4YOPV5MTZmIxO21iFp_4fDIS3O2_KNCEg,2990
|
|
@@ -43,9 +43,10 @@ fastmcp/utilities/http.py,sha256=EzOe38e2oC0SjAnLmcf__1AjEtF19px8ZIVxPLt3Cr0,991
|
|
|
43
43
|
fastmcp/utilities/json_schema.py,sha256=mSakhP8bENxhLFMwHJSxJAFllNeByIBDjVohwlpac6w,2026
|
|
44
44
|
fastmcp/utilities/logging.py,sha256=zav8pnFxG_fvGJHUV2XpobmT9WVrmv1mlQBSCz-CPx4,1159
|
|
45
45
|
fastmcp/utilities/openapi.py,sha256=Er3G1MyFwiWVxZXicXtD2j-BvttHEDTi1dgkq1KiBQc,51073
|
|
46
|
-
fastmcp/utilities/
|
|
47
|
-
fastmcp
|
|
48
|
-
fastmcp-2.2.
|
|
49
|
-
fastmcp-2.2.
|
|
50
|
-
fastmcp-2.2.
|
|
51
|
-
fastmcp-2.2.
|
|
46
|
+
fastmcp/utilities/tests.py,sha256=2QoGdD9l5mfXauUNn-tu5WC2sGWvDHZrATXenetBrZc,1162
|
|
47
|
+
fastmcp/utilities/types.py,sha256=6CcqAQ1QqCO2HGSFlPS6FO5JRWnacjCcO2-EhyEnZV0,4400
|
|
48
|
+
fastmcp-2.2.10.dist-info/METADATA,sha256=7tUjAAgeXdrrioAbuis8c79HbHmw07uLiPm6nlHtYDQ,16397
|
|
49
|
+
fastmcp-2.2.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
50
|
+
fastmcp-2.2.10.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
|
|
51
|
+
fastmcp-2.2.10.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
52
|
+
fastmcp-2.2.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|