fastmcp 2.1.1__py3-none-any.whl → 2.2.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/cli/cli.py +32 -0
- fastmcp/client/client.py +16 -15
- fastmcp/client/transports.py +28 -7
- fastmcp/exceptions.py +8 -0
- fastmcp/prompts/prompt.py +20 -9
- fastmcp/prompts/prompt_manager.py +37 -45
- fastmcp/resources/resource.py +19 -8
- fastmcp/resources/resource_manager.py +83 -115
- fastmcp/resources/template.py +82 -17
- fastmcp/server/openapi.py +10 -16
- fastmcp/server/proxy.py +102 -76
- fastmcp/server/server.py +319 -256
- fastmcp/settings.py +8 -11
- fastmcp/tools/tool.py +66 -14
- fastmcp/tools/tool_manager.py +46 -44
- fastmcp/utilities/logging.py +14 -6
- fastmcp/utilities/openapi.py +0 -87
- {fastmcp-2.1.1.dist-info → fastmcp-2.2.0.dist-info}/METADATA +20 -7
- fastmcp-2.2.0.dist-info/RECORD +40 -0
- fastmcp-2.1.1.dist-info/RECORD +0 -40
- {fastmcp-2.1.1.dist-info → fastmcp-2.2.0.dist-info}/WHEEL +0 -0
- {fastmcp-2.1.1.dist-info → fastmcp-2.2.0.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.1.1.dist-info → fastmcp-2.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
"""Resource manager functionality."""
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
-
import re
|
|
5
4
|
from collections.abc import Callable
|
|
6
5
|
from typing import Any
|
|
7
6
|
|
|
8
7
|
from pydantic import AnyUrl
|
|
9
8
|
|
|
10
|
-
from fastmcp.exceptions import
|
|
11
|
-
from fastmcp.resources import FunctionResource
|
|
12
|
-
from fastmcp.resources.
|
|
9
|
+
from fastmcp.exceptions import NotFoundError
|
|
10
|
+
from fastmcp.resources import FunctionResource
|
|
11
|
+
from fastmcp.resources.resource import Resource
|
|
12
|
+
from fastmcp.resources.template import (
|
|
13
|
+
ResourceTemplate,
|
|
14
|
+
match_uri_template,
|
|
15
|
+
)
|
|
13
16
|
from fastmcp.settings import DuplicateBehavior
|
|
14
17
|
from fastmcp.utilities.logging import get_logger
|
|
15
18
|
|
|
@@ -19,9 +22,20 @@ logger = get_logger(__name__)
|
|
|
19
22
|
class ResourceManager:
|
|
20
23
|
"""Manages FastMCP resources."""
|
|
21
24
|
|
|
22
|
-
def __init__(self, duplicate_behavior: DuplicateBehavior =
|
|
25
|
+
def __init__(self, duplicate_behavior: DuplicateBehavior | None = None):
|
|
23
26
|
self._resources: dict[str, Resource] = {}
|
|
24
27
|
self._templates: dict[str, ResourceTemplate] = {}
|
|
28
|
+
|
|
29
|
+
# Default to "warn" if None is provided
|
|
30
|
+
if duplicate_behavior is None:
|
|
31
|
+
duplicate_behavior = "warn"
|
|
32
|
+
|
|
33
|
+
if duplicate_behavior not in DuplicateBehavior.__args__:
|
|
34
|
+
raise ValueError(
|
|
35
|
+
f"Invalid duplicate_behavior: {duplicate_behavior}. "
|
|
36
|
+
f"Must be one of: {', '.join(DuplicateBehavior.__args__)}"
|
|
37
|
+
)
|
|
38
|
+
|
|
25
39
|
self.duplicate_behavior = duplicate_behavior
|
|
26
40
|
|
|
27
41
|
def add_resource_or_template_from_fn(
|
|
@@ -51,7 +65,7 @@ class ResourceManager:
|
|
|
51
65
|
has_uri_params = "{" in uri and "}" in uri
|
|
52
66
|
has_func_params = bool(inspect.signature(fn).parameters)
|
|
53
67
|
|
|
54
|
-
if has_uri_params
|
|
68
|
+
if has_uri_params or has_func_params:
|
|
55
69
|
return self.add_template_from_fn(
|
|
56
70
|
fn, uri, name, description, mime_type, tags
|
|
57
71
|
)
|
|
@@ -98,32 +112,35 @@ class ResourceManager:
|
|
|
98
112
|
)
|
|
99
113
|
return self.add_resource(resource)
|
|
100
114
|
|
|
101
|
-
def add_resource(self, resource: Resource) -> Resource:
|
|
115
|
+
def add_resource(self, resource: Resource, key: str | None = None) -> Resource:
|
|
102
116
|
"""Add a resource to the manager.
|
|
103
117
|
|
|
104
118
|
Args:
|
|
105
119
|
resource: A Resource instance to add
|
|
120
|
+
key: Optional URI to use as the storage key (if different from resource.uri)
|
|
106
121
|
"""
|
|
122
|
+
storage_key = key or str(resource.uri)
|
|
107
123
|
logger.debug(
|
|
108
124
|
"Adding resource",
|
|
109
125
|
extra={
|
|
110
126
|
"uri": resource.uri,
|
|
127
|
+
"storage_key": storage_key,
|
|
111
128
|
"type": type(resource).__name__,
|
|
112
129
|
"resource_name": resource.name,
|
|
113
130
|
},
|
|
114
131
|
)
|
|
115
|
-
existing = self._resources.get(
|
|
132
|
+
existing = self._resources.get(storage_key)
|
|
116
133
|
if existing:
|
|
117
|
-
if self.duplicate_behavior ==
|
|
118
|
-
logger.warning(f"Resource already exists: {
|
|
119
|
-
self._resources[
|
|
120
|
-
elif self.duplicate_behavior ==
|
|
121
|
-
self._resources[
|
|
122
|
-
elif self.duplicate_behavior ==
|
|
123
|
-
raise ValueError(f"Resource already exists: {
|
|
124
|
-
elif self.duplicate_behavior ==
|
|
125
|
-
|
|
126
|
-
self._resources[
|
|
134
|
+
if self.duplicate_behavior == "warn":
|
|
135
|
+
logger.warning(f"Resource already exists: {storage_key}")
|
|
136
|
+
self._resources[storage_key] = resource
|
|
137
|
+
elif self.duplicate_behavior == "replace":
|
|
138
|
+
self._resources[storage_key] = resource
|
|
139
|
+
elif self.duplicate_behavior == "error":
|
|
140
|
+
raise ValueError(f"Resource already exists: {storage_key}")
|
|
141
|
+
elif self.duplicate_behavior == "ignore":
|
|
142
|
+
return existing
|
|
143
|
+
self._resources[storage_key] = resource
|
|
127
144
|
return resource
|
|
128
145
|
|
|
129
146
|
def add_template_from_fn(
|
|
@@ -137,16 +154,6 @@ class ResourceManager:
|
|
|
137
154
|
) -> ResourceTemplate:
|
|
138
155
|
"""Create a template from a function."""
|
|
139
156
|
|
|
140
|
-
# Validate that URI params match function params
|
|
141
|
-
uri_params = set(re.findall(r"{(\w+)}", uri_template))
|
|
142
|
-
func_params = set(inspect.signature(fn).parameters.keys())
|
|
143
|
-
|
|
144
|
-
if uri_params != func_params:
|
|
145
|
-
raise ValueError(
|
|
146
|
-
f"Mismatch between URI parameters {uri_params} "
|
|
147
|
-
f"and function parameters {func_params}"
|
|
148
|
-
)
|
|
149
|
-
|
|
150
157
|
template = ResourceTemplate.from_function(
|
|
151
158
|
fn,
|
|
152
159
|
uri_template=uri_template,
|
|
@@ -157,40 +164,60 @@ class ResourceManager:
|
|
|
157
164
|
)
|
|
158
165
|
return self.add_template(template)
|
|
159
166
|
|
|
160
|
-
def add_template(
|
|
167
|
+
def add_template(
|
|
168
|
+
self, template: ResourceTemplate, key: str | None = None
|
|
169
|
+
) -> ResourceTemplate:
|
|
161
170
|
"""Add a template to the manager.
|
|
162
171
|
|
|
163
172
|
Args:
|
|
164
173
|
template: A ResourceTemplate instance to add
|
|
174
|
+
key: Optional URI template to use as the storage key (if different from template.uri_template)
|
|
165
175
|
|
|
166
176
|
Returns:
|
|
167
177
|
The added template. If a template with the same URI already exists,
|
|
168
178
|
returns the existing template.
|
|
169
179
|
"""
|
|
180
|
+
uri_template_str = str(template.uri_template)
|
|
181
|
+
storage_key = key or uri_template_str
|
|
170
182
|
logger.debug(
|
|
171
|
-
"Adding
|
|
183
|
+
"Adding template",
|
|
172
184
|
extra={
|
|
173
|
-
"
|
|
185
|
+
"uri_template": uri_template_str,
|
|
186
|
+
"storage_key": storage_key,
|
|
174
187
|
"type": type(template).__name__,
|
|
175
|
-
"
|
|
188
|
+
"template_name": template.name,
|
|
176
189
|
},
|
|
177
190
|
)
|
|
178
|
-
existing = self._templates.get(
|
|
191
|
+
existing = self._templates.get(storage_key)
|
|
179
192
|
if existing:
|
|
180
|
-
if self.duplicate_behavior ==
|
|
181
|
-
logger.warning(f"
|
|
182
|
-
self._templates[
|
|
183
|
-
elif self.duplicate_behavior ==
|
|
184
|
-
self._templates[
|
|
185
|
-
elif self.duplicate_behavior ==
|
|
186
|
-
raise ValueError(f"
|
|
187
|
-
elif self.duplicate_behavior ==
|
|
188
|
-
|
|
189
|
-
self._templates[
|
|
193
|
+
if self.duplicate_behavior == "warn":
|
|
194
|
+
logger.warning(f"Template already exists: {storage_key}")
|
|
195
|
+
self._templates[storage_key] = template
|
|
196
|
+
elif self.duplicate_behavior == "replace":
|
|
197
|
+
self._templates[storage_key] = template
|
|
198
|
+
elif self.duplicate_behavior == "error":
|
|
199
|
+
raise ValueError(f"Template already exists: {storage_key}")
|
|
200
|
+
elif self.duplicate_behavior == "ignore":
|
|
201
|
+
return existing
|
|
202
|
+
self._templates[storage_key] = template
|
|
190
203
|
return template
|
|
191
204
|
|
|
192
|
-
|
|
193
|
-
"""
|
|
205
|
+
def has_resource(self, uri: AnyUrl | str) -> bool:
|
|
206
|
+
"""Check if a resource exists."""
|
|
207
|
+
uri_str = str(uri)
|
|
208
|
+
if uri_str in self._resources:
|
|
209
|
+
return True
|
|
210
|
+
for template_key in self._templates.keys():
|
|
211
|
+
if match_uri_template(uri_str, template_key):
|
|
212
|
+
return True
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
async def get_resource(self, uri: AnyUrl | str) -> Resource:
|
|
216
|
+
"""Get resource by URI, checking concrete resources first, then templates.
|
|
217
|
+
|
|
218
|
+
Raises:
|
|
219
|
+
NotFoundError: If no resource or template matching the URI is found.
|
|
220
|
+
"""
|
|
194
221
|
uri_str = str(uri)
|
|
195
222
|
logger.debug("Getting resource", extra={"uri": uri_str})
|
|
196
223
|
|
|
@@ -198,80 +225,21 @@ class ResourceManager:
|
|
|
198
225
|
if resource := self._resources.get(uri_str):
|
|
199
226
|
return resource
|
|
200
227
|
|
|
201
|
-
# Then check templates
|
|
202
|
-
for template in self._templates.
|
|
203
|
-
|
|
228
|
+
# Then check templates - use the utility function to match against storage keys
|
|
229
|
+
for storage_key, template in self._templates.items():
|
|
230
|
+
# Try to match against the storage key (which might be a custom key)
|
|
231
|
+
if params := match_uri_template(uri_str, storage_key):
|
|
204
232
|
try:
|
|
205
233
|
return await template.create_resource(uri_str, params)
|
|
206
234
|
except Exception as e:
|
|
207
235
|
raise ValueError(f"Error creating resource from template: {e}")
|
|
208
236
|
|
|
209
|
-
raise
|
|
210
|
-
|
|
211
|
-
def list_resources(self) -> list[Resource]:
|
|
212
|
-
"""List all registered resources."""
|
|
213
|
-
logger.debug("Listing resources", extra={"count": len(self._resources)})
|
|
214
|
-
return list(self._resources.values())
|
|
215
|
-
|
|
216
|
-
def list_templates(self) -> list[ResourceTemplate]:
|
|
217
|
-
"""List all registered templates."""
|
|
218
|
-
logger.debug("Listing templates", extra={"count": len(self._templates)})
|
|
219
|
-
return list(self._templates.values())
|
|
237
|
+
raise NotFoundError(f"Unknown resource: {uri_str}")
|
|
220
238
|
|
|
221
|
-
def
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
"""Import resources from another resource manager.
|
|
239
|
+
def get_resources(self) -> dict[str, Resource]:
|
|
240
|
+
"""Get all registered resources, keyed by URI."""
|
|
241
|
+
return self._resources
|
|
225
242
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
the original URI is used.
|
|
230
|
-
|
|
231
|
-
Args:
|
|
232
|
-
manager: The ResourceManager to import from
|
|
233
|
-
prefix: A prefix to apply to the resource URIs, including the delimiter.
|
|
234
|
-
For example, "app+" would result in URIs like "app+data://users".
|
|
235
|
-
If None, the original URI is used.
|
|
236
|
-
"""
|
|
237
|
-
for uri, resource in manager._resources.items():
|
|
238
|
-
# Create prefixed URI and copy the resource with the new URI
|
|
239
|
-
prefixed_uri = f"{prefix}{uri}" if prefix else uri
|
|
240
|
-
|
|
241
|
-
new_resource = resource.copy(updates=dict(uri=prefixed_uri))
|
|
242
|
-
|
|
243
|
-
# Store directly in resources dictionary
|
|
244
|
-
self.add_resource(new_resource)
|
|
245
|
-
logger.debug(f'Imported resource "{uri}" as "{prefixed_uri}"')
|
|
246
|
-
|
|
247
|
-
def import_templates(
|
|
248
|
-
self, manager: "ResourceManager", prefix: str | None = None
|
|
249
|
-
) -> None:
|
|
250
|
-
"""Import resource templates from another resource manager.
|
|
251
|
-
|
|
252
|
-
Templates are imported with a prefixed URI template if a prefix is provided.
|
|
253
|
-
For example, if a template has URI template "data://users/{id}" and you import
|
|
254
|
-
it with prefix "app+", the imported template will have URI template
|
|
255
|
-
"app+data://users/{id}". If no prefix is provided, the original URI template is used.
|
|
256
|
-
|
|
257
|
-
Args:
|
|
258
|
-
manager: The ResourceManager to import templates from
|
|
259
|
-
prefix: A prefix to apply to the template URIs, including the delimiter.
|
|
260
|
-
For example, "app+" would result in URI templates like "app+data://users/{id}".
|
|
261
|
-
If None, the original URI template is used.
|
|
262
|
-
"""
|
|
263
|
-
for uri_template, template in manager._templates.items():
|
|
264
|
-
# Create prefixed URI template and copy the template with the new URI template
|
|
265
|
-
prefixed_uri_template = (
|
|
266
|
-
f"{prefix}{uri_template}" if prefix else uri_template
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
new_template = template.copy(
|
|
270
|
-
updates=dict(uri_template=prefixed_uri_template)
|
|
271
|
-
)
|
|
272
|
-
|
|
273
|
-
# Store directly in templates dictionary
|
|
274
|
-
self.add_template(new_template)
|
|
275
|
-
logger.debug(
|
|
276
|
-
f'Imported template "{uri_template}" as "{prefixed_uri_template}"'
|
|
277
|
-
)
|
|
243
|
+
def get_templates(self) -> dict[str, ResourceTemplate]:
|
|
244
|
+
"""Get all registered templates, keyed by URI template."""
|
|
245
|
+
return self._templates
|
fastmcp/resources/template.py
CHANGED
|
@@ -6,14 +6,49 @@ import inspect
|
|
|
6
6
|
import re
|
|
7
7
|
from collections.abc import Callable
|
|
8
8
|
from typing import Annotated, Any
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from
|
|
9
|
+
from urllib.parse import unquote
|
|
10
|
+
|
|
11
|
+
from mcp.types import ResourceTemplate as MCPResourceTemplate
|
|
12
|
+
from pydantic import (
|
|
13
|
+
AnyUrl,
|
|
14
|
+
BaseModel,
|
|
15
|
+
BeforeValidator,
|
|
16
|
+
Field,
|
|
17
|
+
TypeAdapter,
|
|
18
|
+
field_validator,
|
|
19
|
+
validate_call,
|
|
20
|
+
)
|
|
12
21
|
|
|
13
22
|
from fastmcp.resources.types import FunctionResource, Resource
|
|
14
23
|
from fastmcp.utilities.types import _convert_set_defaults
|
|
15
24
|
|
|
16
25
|
|
|
26
|
+
def build_regex(template: str) -> re.Pattern:
|
|
27
|
+
# Escape all non-brace characters, then restore {var} placeholders
|
|
28
|
+
parts = re.split(r"(\{[^}]+\})", template)
|
|
29
|
+
pattern = ""
|
|
30
|
+
for part in parts:
|
|
31
|
+
if part.startswith("{") and part.endswith("}"):
|
|
32
|
+
name = part[1:-1]
|
|
33
|
+
pattern += f"(?P<{name}>[^/]+)"
|
|
34
|
+
else:
|
|
35
|
+
pattern += re.escape(part)
|
|
36
|
+
return re.compile(f"^{pattern}$")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def match_uri_template(uri: str, uri_template: str) -> dict[str, str] | None:
|
|
40
|
+
regex = build_regex(uri_template)
|
|
41
|
+
match = regex.match(uri)
|
|
42
|
+
if match:
|
|
43
|
+
return {k: unquote(v) for k, v in match.groupdict().items()}
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MyModel(BaseModel):
|
|
48
|
+
key: str
|
|
49
|
+
value: int
|
|
50
|
+
|
|
51
|
+
|
|
17
52
|
class ResourceTemplate(BaseModel):
|
|
18
53
|
"""A template for dynamically creating resources."""
|
|
19
54
|
|
|
@@ -33,6 +68,14 @@ class ResourceTemplate(BaseModel):
|
|
|
33
68
|
description="JSON schema for function parameters"
|
|
34
69
|
)
|
|
35
70
|
|
|
71
|
+
@field_validator("mime_type", mode="before")
|
|
72
|
+
@classmethod
|
|
73
|
+
def set_default_mime_type(cls, mime_type: str | None) -> str:
|
|
74
|
+
"""Set default MIME type if not provided."""
|
|
75
|
+
if mime_type:
|
|
76
|
+
return mime_type
|
|
77
|
+
return "text/plain"
|
|
78
|
+
|
|
36
79
|
@classmethod
|
|
37
80
|
def from_function(
|
|
38
81
|
cls,
|
|
@@ -48,6 +91,30 @@ class ResourceTemplate(BaseModel):
|
|
|
48
91
|
if func_name == "<lambda>":
|
|
49
92
|
raise ValueError("You must provide a name for lambda functions")
|
|
50
93
|
|
|
94
|
+
# Validate that URI params match function params
|
|
95
|
+
uri_params = set(re.findall(r"{(\w+)}", uri_template))
|
|
96
|
+
if not uri_params:
|
|
97
|
+
raise ValueError("URI template must contain at least one parameter")
|
|
98
|
+
|
|
99
|
+
func_params = set(inspect.signature(fn).parameters.keys())
|
|
100
|
+
|
|
101
|
+
# get the parameters that are required
|
|
102
|
+
required_params = {
|
|
103
|
+
p
|
|
104
|
+
for p in func_params
|
|
105
|
+
if inspect.signature(fn).parameters[p].default is inspect.Parameter.empty
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if not required_params.issubset(uri_params):
|
|
109
|
+
raise ValueError(
|
|
110
|
+
f"URI parameters {uri_params} must be a subset of the required function arguments: {required_params}"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if not uri_params.issubset(func_params):
|
|
114
|
+
raise ValueError(
|
|
115
|
+
f"URI parameters {uri_params} must be a subset of the function arguments: {func_params}"
|
|
116
|
+
)
|
|
117
|
+
|
|
51
118
|
# Get schema from TypeAdapter - will fail if function isn't properly typed
|
|
52
119
|
parameters = TypeAdapter(fn).json_schema()
|
|
53
120
|
|
|
@@ -66,12 +133,7 @@ class ResourceTemplate(BaseModel):
|
|
|
66
133
|
|
|
67
134
|
def matches(self, uri: str) -> dict[str, Any] | None:
|
|
68
135
|
"""Check if URI matches template and extract parameters."""
|
|
69
|
-
|
|
70
|
-
pattern = self.uri_template.replace("{", "(?P<").replace("}", ">[^/]+)")
|
|
71
|
-
match = re.match(f"^{pattern}$", uri)
|
|
72
|
-
if match:
|
|
73
|
-
return match.groupdict()
|
|
74
|
-
return None
|
|
136
|
+
return match_uri_template(uri, self.uri_template)
|
|
75
137
|
|
|
76
138
|
async def create_resource(self, uri: str, params: dict[str, Any]) -> Resource:
|
|
77
139
|
"""Create a resource from the template with the given parameters."""
|
|
@@ -82,7 +144,7 @@ class ResourceTemplate(BaseModel):
|
|
|
82
144
|
result = await result
|
|
83
145
|
|
|
84
146
|
return FunctionResource(
|
|
85
|
-
uri=uri, #
|
|
147
|
+
uri=AnyUrl(uri), # Explicitly convert to AnyUrl
|
|
86
148
|
name=self.name,
|
|
87
149
|
description=self.description,
|
|
88
150
|
mime_type=self.mime_type,
|
|
@@ -92,14 +154,17 @@ class ResourceTemplate(BaseModel):
|
|
|
92
154
|
except Exception as e:
|
|
93
155
|
raise ValueError(f"Error creating resource from template: {e}")
|
|
94
156
|
|
|
95
|
-
def copy(self, updates: dict[str, Any] | None = None) -> Self:
|
|
96
|
-
"""Copy the resource template with optional updates."""
|
|
97
|
-
data = self.model_dump()
|
|
98
|
-
if updates:
|
|
99
|
-
data.update(updates)
|
|
100
|
-
return type(self)(**data)
|
|
101
|
-
|
|
102
157
|
def __eq__(self, other: object) -> bool:
|
|
103
158
|
if not isinstance(other, ResourceTemplate):
|
|
104
159
|
return False
|
|
105
160
|
return self.model_dump() == other.model_dump()
|
|
161
|
+
|
|
162
|
+
def to_mcp_template(self, **overrides: Any) -> MCPResourceTemplate:
|
|
163
|
+
"""Convert the resource template to an MCPResourceTemplate."""
|
|
164
|
+
kwargs = {
|
|
165
|
+
"uriTemplate": self.uri_template,
|
|
166
|
+
"name": self.name,
|
|
167
|
+
"description": self.description,
|
|
168
|
+
"mimeType": self.mime_type,
|
|
169
|
+
}
|
|
170
|
+
return MCPResourceTemplate(**kwargs | overrides)
|
fastmcp/server/openapi.py
CHANGED
|
@@ -8,6 +8,7 @@ from re import Pattern
|
|
|
8
8
|
from typing import Any, Literal
|
|
9
9
|
|
|
10
10
|
import httpx
|
|
11
|
+
from mcp.types import TextContent
|
|
11
12
|
from pydantic.networks import AnyUrl
|
|
12
13
|
|
|
13
14
|
from fastmcp.resources import Resource, ResourceTemplate
|
|
@@ -613,25 +614,18 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
613
614
|
f"Registered TEMPLATE: {uri_template_str} ({route.method} {route.path}) with tags: {route.tags}"
|
|
614
615
|
)
|
|
615
616
|
|
|
616
|
-
async def
|
|
617
|
-
"""Override the call_tool method to return the raw result without converting to content.
|
|
617
|
+
async def _mcp_call_tool(self, name: str, arguments: dict[str, Any]) -> Any:
|
|
618
|
+
"""Override the call_tool method to return the raw result without converting to content."""
|
|
618
619
|
|
|
619
|
-
For testing purposes, if specific tools are called, we convert the result to the expected object.
|
|
620
|
-
"""
|
|
621
620
|
context = self.get_context()
|
|
622
621
|
result = await self._tool_manager.call_tool(name, arguments, context=context)
|
|
623
622
|
|
|
624
|
-
# For
|
|
625
|
-
if
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
if isinstance(result, dict):
|
|
632
|
-
return User(**result)
|
|
633
|
-
except ImportError:
|
|
634
|
-
# If User class not found, just return the raw result
|
|
635
|
-
pass
|
|
623
|
+
# For other tools, ensure the response is wrapped in TextContent
|
|
624
|
+
if isinstance(result, dict | str):
|
|
625
|
+
if isinstance(result, dict):
|
|
626
|
+
result_text = json.dumps(result)
|
|
627
|
+
else:
|
|
628
|
+
result_text = result
|
|
629
|
+
return [TextContent(text=result_text, type="text")]
|
|
636
630
|
|
|
637
631
|
return result
|