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 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.networks import AnyUrl
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
- # Pre-parse data from JSON in order to handle cases like `["a", "b", "c"]`
118
- # being passed in as JSON inside a string rather than an actual list.
119
- #
120
- # Claude desktop is prone to this - in fact it seems incapable of NOT doing
121
- # this. For sub-models, it tends to pass dicts (JSON objects) as JSON strings,
122
- # which can be pre-parsed here.
123
- for param_name in self.parameters["properties"]:
124
- if isinstance(parsed_args.get(param_name, None), str):
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(parsed_args[param_name])
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.8
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=Ix_VrAegM27wpPViCJvC04dmPm2QsBgtbxcZXHGQOhM,2213
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=OWyq_TrCDJkcZ1p-g-SoFziHQGSQY97tArUkjNCiCRQ,43137
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=yvXdhEqhEy1bZAMrx51HiXoUJjo5DdM69APf3ScW7qg,7307
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.8.dist-info/METADATA,sha256=vbozZFoyWFVmXvAl4Gx3LS7ipBdrjEqvPKOmunOjCww,16279
48
- fastmcp-2.2.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
49
- fastmcp-2.2.8.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
50
- fastmcp-2.2.8.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
51
- fastmcp-2.2.8.dist-info/RECORD,,
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,,