fastmcp 2.2.8__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/server/server.py +15 -1
- fastmcp/settings.py +13 -0
- fastmcp/tools/tool.py +27 -9
- fastmcp/utilities/tests.py +41 -0
- {fastmcp-2.2.8.dist-info → fastmcp-2.2.10.dist-info}/METADATA +5 -1
- {fastmcp-2.2.8.dist-info → fastmcp-2.2.10.dist-info}/RECORD +9 -8
- {fastmcp-2.2.8.dist-info → fastmcp-2.2.10.dist-info}/WHEEL +0 -0
- {fastmcp-2.2.8.dist-info → fastmcp-2.2.10.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.2.8.dist-info → fastmcp-2.2.10.dist-info}/licenses/LICENSE +0 -0
fastmcp/server/server.py
CHANGED
|
@@ -14,6 +14,7 @@ from typing import TYPE_CHECKING, Any, Generic, Literal
|
|
|
14
14
|
|
|
15
15
|
import anyio
|
|
16
16
|
import httpx
|
|
17
|
+
import pydantic
|
|
17
18
|
import uvicorn
|
|
18
19
|
from mcp.server.auth.middleware.auth_context import AuthContextMiddleware
|
|
19
20
|
from mcp.server.auth.middleware.bearer_auth import (
|
|
@@ -39,7 +40,7 @@ from mcp.types import Prompt as MCPPrompt
|
|
|
39
40
|
from mcp.types import Resource as MCPResource
|
|
40
41
|
from mcp.types import ResourceTemplate as MCPResourceTemplate
|
|
41
42
|
from mcp.types import Tool as MCPTool
|
|
42
|
-
from pydantic
|
|
43
|
+
from pydantic import AnyUrl
|
|
43
44
|
from starlette.applications import Starlette
|
|
44
45
|
from starlette.middleware import Middleware
|
|
45
46
|
from starlette.middleware.authentication import AuthenticationMiddleware
|
|
@@ -88,6 +89,8 @@ class MountedServer:
|
|
|
88
89
|
if prompt_separator is None:
|
|
89
90
|
prompt_separator = "_"
|
|
90
91
|
|
|
92
|
+
_validate_resource_prefix(f"{prefix}{resource_separator}")
|
|
93
|
+
|
|
91
94
|
self.server = server
|
|
92
95
|
self.prefix = prefix
|
|
93
96
|
self.tool_separator = tool_separator
|
|
@@ -1074,6 +1077,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1074
1077
|
|
|
1075
1078
|
# Import resources and templates from the mounted server
|
|
1076
1079
|
resource_prefix = f"{prefix}{resource_separator}"
|
|
1080
|
+
_validate_resource_prefix(resource_prefix)
|
|
1077
1081
|
for key, resource in (await server.get_resources()).items():
|
|
1078
1082
|
self._resource_manager.add_resource(resource, key=f"{resource_prefix}{key}")
|
|
1079
1083
|
for key, template in (await server.get_resource_templates()).items():
|
|
@@ -1131,3 +1135,13 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1131
1135
|
from fastmcp.server.proxy import FastMCPProxy
|
|
1132
1136
|
|
|
1133
1137
|
return FastMCPProxy(client=client, **settings)
|
|
1138
|
+
|
|
1139
|
+
|
|
1140
|
+
def _validate_resource_prefix(prefix: str) -> None:
|
|
1141
|
+
valid_resource = "resource://path/to/resource"
|
|
1142
|
+
try:
|
|
1143
|
+
AnyUrl(f"{prefix}{valid_resource}")
|
|
1144
|
+
except pydantic.ValidationError as e:
|
|
1145
|
+
raise ValueError(
|
|
1146
|
+
f"Resource prefix or separator would result in an invalid resource URI: {e}"
|
|
1147
|
+
)
|
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,16 +116,32 @@ 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
|
|
|
@@ -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])
|
|
@@ -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
|
|
@@ -33,9 +33,9 @@ fastmcp/server/__init__.py,sha256=pdkghG11VLMZiluQ-4_rl2JK1LMWmV003m9dDRUN8W4,92
|
|
|
33
33
|
fastmcp/server/context.py,sha256=X8mBdF7SPwH_x8qG4oDoZBPZO9ibYFLjZu_pZoQvafg,7858
|
|
34
34
|
fastmcp/server/openapi.py,sha256=1Yhep4zwHhZPlfNIo2Y3sIxfwHNJRdzw3Kc4DZrEW3Q,23950
|
|
35
35
|
fastmcp/server/proxy.py,sha256=f7V1X9lDUanHS_iTzEf7Nt0WwkDOXDiJ3-XrMWdq3tw,9969
|
|
36
|
-
fastmcp/server/server.py,sha256=
|
|
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/tests.py,sha256=2QoGdD9l5mfXauUNn-tu5WC2sGWvDHZrATXenetBrZc,1162
|
|
46
47
|
fastmcp/utilities/types.py,sha256=6CcqAQ1QqCO2HGSFlPS6FO5JRWnacjCcO2-EhyEnZV0,4400
|
|
47
|
-
fastmcp-2.2.
|
|
48
|
-
fastmcp-2.2.
|
|
49
|
-
fastmcp-2.2.
|
|
50
|
-
fastmcp-2.2.
|
|
51
|
-
fastmcp-2.2.
|
|
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
|