fastmcp 2.3.5__py3-none-any.whl → 2.5.0__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/client/client.py +44 -6
- fastmcp/client/logging.py +14 -8
- fastmcp/client/transports.py +202 -57
- fastmcp/prompts/prompt.py +11 -4
- fastmcp/prompts/prompt_manager.py +25 -5
- fastmcp/resources/resource_manager.py +31 -5
- fastmcp/resources/template.py +10 -5
- fastmcp/server/context.py +46 -0
- fastmcp/server/http.py +25 -1
- fastmcp/server/openapi.py +436 -73
- fastmcp/server/server.py +412 -127
- fastmcp/settings.py +46 -1
- fastmcp/tools/tool.py +5 -1
- fastmcp/tools/tool_manager.py +9 -2
- fastmcp/utilities/logging.py +6 -1
- fastmcp/utilities/mcp_config.py +77 -0
- fastmcp/utilities/openapi.py +233 -602
- fastmcp/utilities/tests.py +8 -4
- {fastmcp-2.3.5.dist-info → fastmcp-2.5.0.dist-info}/METADATA +27 -4
- {fastmcp-2.3.5.dist-info → fastmcp-2.5.0.dist-info}/RECORD +23 -22
- {fastmcp-2.3.5.dist-info → fastmcp-2.5.0.dist-info}/WHEEL +0 -0
- {fastmcp-2.3.5.dist-info → fastmcp-2.5.0.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.3.5.dist-info → fastmcp-2.5.0.dist-info}/licenses/LICENSE +0 -0
fastmcp/settings.py
CHANGED
|
@@ -29,6 +29,17 @@ class Settings(BaseSettings):
|
|
|
29
29
|
|
|
30
30
|
test_mode: bool = False
|
|
31
31
|
log_level: LOG_LEVEL = "INFO"
|
|
32
|
+
enable_rich_tracebacks: Annotated[
|
|
33
|
+
bool,
|
|
34
|
+
Field(
|
|
35
|
+
description=inspect.cleandoc(
|
|
36
|
+
"""
|
|
37
|
+
If True, will use rich tracebacks for logging.
|
|
38
|
+
"""
|
|
39
|
+
)
|
|
40
|
+
),
|
|
41
|
+
] = True
|
|
42
|
+
|
|
32
43
|
client_raise_first_exceptiongroup_error: Annotated[
|
|
33
44
|
bool,
|
|
34
45
|
Field(
|
|
@@ -44,6 +55,21 @@ class Settings(BaseSettings):
|
|
|
44
55
|
),
|
|
45
56
|
),
|
|
46
57
|
] = True
|
|
58
|
+
|
|
59
|
+
resource_prefix_format: Annotated[
|
|
60
|
+
Literal["protocol", "path"],
|
|
61
|
+
Field(
|
|
62
|
+
default="path",
|
|
63
|
+
description=inspect.cleandoc(
|
|
64
|
+
"""
|
|
65
|
+
When perfixing a resource URI, either use path formatting (resource://prefix/path)
|
|
66
|
+
or protocol formatting (prefix+resource://path). Protocol formatting was the default in FastMCP < 2.4;
|
|
67
|
+
path formatting is current default.
|
|
68
|
+
"""
|
|
69
|
+
),
|
|
70
|
+
),
|
|
71
|
+
] = "path"
|
|
72
|
+
|
|
47
73
|
tool_attempt_parse_json_args: Annotated[
|
|
48
74
|
bool,
|
|
49
75
|
Field(
|
|
@@ -66,7 +92,9 @@ class Settings(BaseSettings):
|
|
|
66
92
|
"""Finalize the settings."""
|
|
67
93
|
from fastmcp.utilities.logging import configure_logging
|
|
68
94
|
|
|
69
|
-
configure_logging(
|
|
95
|
+
configure_logging(
|
|
96
|
+
self.log_level, enable_rich_tracebacks=self.enable_rich_tracebacks
|
|
97
|
+
)
|
|
70
98
|
|
|
71
99
|
return self
|
|
72
100
|
|
|
@@ -108,6 +136,23 @@ class ServerSettings(BaseSettings):
|
|
|
108
136
|
# prompt settings
|
|
109
137
|
on_duplicate_prompts: DuplicateBehavior = "warn"
|
|
110
138
|
|
|
139
|
+
# error handling
|
|
140
|
+
mask_error_details: Annotated[
|
|
141
|
+
bool,
|
|
142
|
+
Field(
|
|
143
|
+
default=False,
|
|
144
|
+
description=inspect.cleandoc(
|
|
145
|
+
"""
|
|
146
|
+
If True, error details from user-supplied functions (tool, resource, prompt)
|
|
147
|
+
will be masked before being sent to clients. Only error messages from explicitly
|
|
148
|
+
raised ToolError, ResourceError, or PromptError will be included in responses.
|
|
149
|
+
If False (default), all error details will be included in responses, but prefixed
|
|
150
|
+
with appropriate context.
|
|
151
|
+
"""
|
|
152
|
+
),
|
|
153
|
+
),
|
|
154
|
+
] = False
|
|
155
|
+
|
|
111
156
|
dependencies: Annotated[
|
|
112
157
|
list[str],
|
|
113
158
|
Field(
|
fastmcp/tools/tool.py
CHANGED
|
@@ -69,13 +69,17 @@ class Tool(BaseModel):
|
|
|
69
69
|
if param.kind == inspect.Parameter.VAR_KEYWORD:
|
|
70
70
|
raise ValueError("Functions with **kwargs are not supported as tools")
|
|
71
71
|
|
|
72
|
-
func_name = name or fn.__name__
|
|
72
|
+
func_name = name or getattr(fn, "__name__", None) or fn.__class__.__name__
|
|
73
73
|
|
|
74
74
|
if func_name == "<lambda>":
|
|
75
75
|
raise ValueError("You must provide a name for lambda functions")
|
|
76
76
|
|
|
77
77
|
func_doc = description or fn.__doc__ or ""
|
|
78
78
|
|
|
79
|
+
# if the fn is a callable class, we need to get the __call__ method from here out
|
|
80
|
+
if not inspect.isroutine(fn):
|
|
81
|
+
fn = fn.__call__
|
|
82
|
+
|
|
79
83
|
type_adapter = get_cached_typeadapter(fn)
|
|
80
84
|
schema = type_adapter.json_schema()
|
|
81
85
|
|
fastmcp/tools/tool_manager.py
CHANGED
|
@@ -23,9 +23,11 @@ class ToolManager:
|
|
|
23
23
|
self,
|
|
24
24
|
duplicate_behavior: DuplicateBehavior | None = None,
|
|
25
25
|
serializer: Callable[[Any], str] | None = None,
|
|
26
|
+
mask_error_details: bool = False,
|
|
26
27
|
):
|
|
27
28
|
self._tools: dict[str, Tool] = {}
|
|
28
29
|
self._serializer = serializer
|
|
30
|
+
self.mask_error_details = mask_error_details
|
|
29
31
|
|
|
30
32
|
# Default to "warn" if None is provided
|
|
31
33
|
if duplicate_behavior is None:
|
|
@@ -124,7 +126,12 @@ class ToolManager:
|
|
|
124
126
|
logger.exception(f"Error calling tool {key!r}: {e}")
|
|
125
127
|
raise e
|
|
126
128
|
|
|
127
|
-
#
|
|
129
|
+
# Handle other exceptions
|
|
128
130
|
except Exception as e:
|
|
129
131
|
logger.exception(f"Error calling tool {key!r}: {e}")
|
|
130
|
-
|
|
132
|
+
if self.mask_error_details:
|
|
133
|
+
# Mask internal details
|
|
134
|
+
raise ToolError(f"Error calling tool {key!r}") from e
|
|
135
|
+
else:
|
|
136
|
+
# Include original error details
|
|
137
|
+
raise ToolError(f"Error calling tool {key!r}: {e}") from e
|
fastmcp/utilities/logging.py
CHANGED
|
@@ -22,6 +22,7 @@ def get_logger(name: str) -> logging.Logger:
|
|
|
22
22
|
def configure_logging(
|
|
23
23
|
level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | int = "INFO",
|
|
24
24
|
logger: logging.Logger | None = None,
|
|
25
|
+
enable_rich_tracebacks: bool = True,
|
|
25
26
|
) -> None:
|
|
26
27
|
"""
|
|
27
28
|
Configure logging for FastMCP.
|
|
@@ -30,11 +31,15 @@ def configure_logging(
|
|
|
30
31
|
logger: the logger to configure
|
|
31
32
|
level: the log level to use
|
|
32
33
|
"""
|
|
34
|
+
|
|
33
35
|
if logger is None:
|
|
34
36
|
logger = logging.getLogger("FastMCP")
|
|
35
37
|
|
|
36
38
|
# Only configure the FastMCP logger namespace
|
|
37
|
-
handler = RichHandler(
|
|
39
|
+
handler = RichHandler(
|
|
40
|
+
console=Console(stderr=True),
|
|
41
|
+
rich_tracebacks=enable_rich_tracebacks,
|
|
42
|
+
)
|
|
38
43
|
formatter = logging.Formatter("%(message)s")
|
|
39
44
|
handler.setFormatter(formatter)
|
|
40
45
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
4
|
+
from urllib.parse import urlparse
|
|
5
|
+
|
|
6
|
+
from pydantic import AnyUrl, BaseModel, Field
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from fastmcp.client.transports import (
|
|
10
|
+
SSETransport,
|
|
11
|
+
StdioTransport,
|
|
12
|
+
StreamableHttpTransport,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def infer_transport_type_from_url(
|
|
17
|
+
url: str | AnyUrl,
|
|
18
|
+
) -> Literal["streamable-http", "sse"]:
|
|
19
|
+
"""
|
|
20
|
+
Infer the appropriate transport type from the given URL.
|
|
21
|
+
"""
|
|
22
|
+
url = str(url)
|
|
23
|
+
if not url.startswith("http"):
|
|
24
|
+
raise ValueError(f"Invalid URL: {url}")
|
|
25
|
+
|
|
26
|
+
parsed_url = urlparse(url)
|
|
27
|
+
path = parsed_url.path
|
|
28
|
+
|
|
29
|
+
if "/sse/" in path or path.rstrip("/").endswith("/sse"):
|
|
30
|
+
return "sse"
|
|
31
|
+
else:
|
|
32
|
+
return "streamable-http"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class StdioMCPServer(BaseModel):
|
|
36
|
+
command: str
|
|
37
|
+
args: list[str] = Field(default_factory=list)
|
|
38
|
+
env: dict[str, Any] = Field(default_factory=dict)
|
|
39
|
+
cwd: str | None = None
|
|
40
|
+
transport: Literal["stdio"] = "stdio"
|
|
41
|
+
|
|
42
|
+
def to_transport(self) -> StdioTransport:
|
|
43
|
+
from fastmcp.client.transports import StdioTransport
|
|
44
|
+
|
|
45
|
+
return StdioTransport(
|
|
46
|
+
command=self.command,
|
|
47
|
+
args=self.args,
|
|
48
|
+
env=self.env,
|
|
49
|
+
cwd=self.cwd,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RemoteMCPServer(BaseModel):
|
|
54
|
+
url: str
|
|
55
|
+
headers: dict[str, str] = Field(default_factory=dict)
|
|
56
|
+
transport: Literal["streamable-http", "sse", "http"] | None = None
|
|
57
|
+
|
|
58
|
+
def to_transport(self) -> StreamableHttpTransport | SSETransport:
|
|
59
|
+
from fastmcp.client.transports import SSETransport, StreamableHttpTransport
|
|
60
|
+
|
|
61
|
+
if self.transport is None:
|
|
62
|
+
transport = infer_transport_type_from_url(self.url)
|
|
63
|
+
else:
|
|
64
|
+
transport = self.transport
|
|
65
|
+
|
|
66
|
+
if transport == "sse":
|
|
67
|
+
return SSETransport(self.url, headers=self.headers)
|
|
68
|
+
else:
|
|
69
|
+
return StreamableHttpTransport(self.url, headers=self.headers)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class MCPConfig(BaseModel):
|
|
73
|
+
mcpServers: dict[str, StdioMCPServer | RemoteMCPServer]
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def from_dict(cls, config: dict[str, Any]) -> MCPConfig:
|
|
77
|
+
return cls(mcpServers=config.get("mcpServers", config))
|