fastmcp 2.2.5__py3-none-any.whl → 2.2.6__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/__init__.py +1 -0
- fastmcp/client/base.py +0 -1
- fastmcp/client/client.py +12 -8
- fastmcp/client/logging.py +13 -0
- fastmcp/client/sampling.py +2 -0
- fastmcp/client/transports.py +37 -4
- fastmcp/server/context.py +15 -13
- fastmcp/server/openapi.py +67 -28
- fastmcp/server/proxy.py +1 -1
- fastmcp/server/server.py +18 -12
- {fastmcp-2.2.5.dist-info → fastmcp-2.2.6.dist-info}/METADATA +2 -2
- {fastmcp-2.2.5.dist-info → fastmcp-2.2.6.dist-info}/RECORD +15 -14
- {fastmcp-2.2.5.dist-info → fastmcp-2.2.6.dist-info}/WHEEL +0 -0
- {fastmcp-2.2.5.dist-info → fastmcp-2.2.6.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.2.5.dist-info → fastmcp-2.2.6.dist-info}/licenses/LICENSE +0 -0
fastmcp/__init__.py
CHANGED
fastmcp/client/base.py
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
fastmcp/client/client.py
CHANGED
|
@@ -5,12 +5,9 @@ from typing import Any, Literal, cast, overload
|
|
|
5
5
|
|
|
6
6
|
import mcp.types
|
|
7
7
|
from mcp import ClientSession
|
|
8
|
-
from mcp.client.session import (
|
|
9
|
-
LoggingFnT,
|
|
10
|
-
MessageHandlerFnT,
|
|
11
|
-
)
|
|
12
8
|
from pydantic import AnyUrl
|
|
13
9
|
|
|
10
|
+
from fastmcp.client.logging import LogHandler, MessageHandler
|
|
14
11
|
from fastmcp.client.roots import (
|
|
15
12
|
RootsHandler,
|
|
16
13
|
RootsList,
|
|
@@ -22,7 +19,14 @@ from fastmcp.server import FastMCP
|
|
|
22
19
|
|
|
23
20
|
from .transports import ClientTransport, SessionKwargs, infer_transport
|
|
24
21
|
|
|
25
|
-
__all__ = [
|
|
22
|
+
__all__ = [
|
|
23
|
+
"Client",
|
|
24
|
+
"RootsHandler",
|
|
25
|
+
"RootsList",
|
|
26
|
+
"LogHandler",
|
|
27
|
+
"MessageHandler",
|
|
28
|
+
"SamplingHandler",
|
|
29
|
+
]
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
class Client:
|
|
@@ -35,12 +39,12 @@ class Client:
|
|
|
35
39
|
|
|
36
40
|
def __init__(
|
|
37
41
|
self,
|
|
38
|
-
transport: ClientTransport | FastMCP | AnyUrl | Path | str,
|
|
42
|
+
transport: ClientTransport | FastMCP | AnyUrl | Path | dict[str, Any] | str,
|
|
39
43
|
# Common args
|
|
40
44
|
roots: RootsList | RootsHandler | None = None,
|
|
41
45
|
sampling_handler: SamplingHandler | None = None,
|
|
42
|
-
log_handler:
|
|
43
|
-
message_handler:
|
|
46
|
+
log_handler: LogHandler | None = None,
|
|
47
|
+
message_handler: MessageHandler | None = None,
|
|
44
48
|
read_timeout_seconds: datetime.timedelta | None = None,
|
|
45
49
|
):
|
|
46
50
|
self.transport = infer_transport(transport)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from typing import TypeAlias
|
|
2
|
+
|
|
3
|
+
from mcp.client.session import (
|
|
4
|
+
LoggingFnT,
|
|
5
|
+
MessageHandlerFnT,
|
|
6
|
+
)
|
|
7
|
+
from mcp.types import LoggingMessageNotificationParams
|
|
8
|
+
|
|
9
|
+
LogMessage: TypeAlias = LoggingMessageNotificationParams
|
|
10
|
+
LogHandler: TypeAlias = LoggingFnT
|
|
11
|
+
MessageHandler: TypeAlias = MessageHandlerFnT
|
|
12
|
+
|
|
13
|
+
__all__ = ["LogMessage", "LogHandler", "MessageHandler"]
|
fastmcp/client/sampling.py
CHANGED
|
@@ -9,6 +9,8 @@ from mcp.shared.context import LifespanContextT, RequestContext
|
|
|
9
9
|
from mcp.types import CreateMessageRequestParams as SamplingParams
|
|
10
10
|
from mcp.types import SamplingMessage
|
|
11
11
|
|
|
12
|
+
__all__ = ["SamplingMessage", "SamplingParams", "MessageResult", "SamplingHandler"]
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
class MessageResult(CreateMessageResult):
|
|
14
16
|
role: mcp.types.Role = "assistant"
|
fastmcp/client/transports.py
CHANGED
|
@@ -6,9 +6,7 @@ import shutil
|
|
|
6
6
|
import sys
|
|
7
7
|
from collections.abc import AsyncIterator
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import
|
|
10
|
-
TypedDict,
|
|
11
|
-
)
|
|
9
|
+
from typing import Any, TypedDict
|
|
12
10
|
|
|
13
11
|
from exceptiongroup import BaseExceptionGroup, catch
|
|
14
12
|
from mcp import ClientSession, McpError, StdioServerParameters
|
|
@@ -416,7 +414,7 @@ class FastMCPTransport(ClientTransport):
|
|
|
416
414
|
|
|
417
415
|
|
|
418
416
|
def infer_transport(
|
|
419
|
-
transport: ClientTransport | FastMCPServer | AnyUrl | Path | str,
|
|
417
|
+
transport: ClientTransport | FastMCPServer | AnyUrl | Path | dict[str, Any] | str,
|
|
420
418
|
) -> ClientTransport:
|
|
421
419
|
"""
|
|
422
420
|
Infer the appropriate transport type from the given transport argument.
|
|
@@ -450,6 +448,41 @@ def infer_transport(
|
|
|
450
448
|
elif isinstance(transport, AnyUrl | str) and str(transport).startswith("ws"):
|
|
451
449
|
return WSTransport(url=transport)
|
|
452
450
|
|
|
451
|
+
## if the transport is a config dict
|
|
452
|
+
elif isinstance(transport, dict):
|
|
453
|
+
if "mcpServers" not in transport:
|
|
454
|
+
raise ValueError("Invalid transport dictionary: missing 'mcpServers' key")
|
|
455
|
+
else:
|
|
456
|
+
server = transport["mcpServers"]
|
|
457
|
+
if len(list(server.keys())) > 1:
|
|
458
|
+
raise ValueError(
|
|
459
|
+
"Invalid transport dictionary: multiple servers found - only one expected"
|
|
460
|
+
)
|
|
461
|
+
server_name = list(server.keys())[0]
|
|
462
|
+
# Stdio transport
|
|
463
|
+
if "command" in server[server_name] and "args" in server[server_name]:
|
|
464
|
+
return StdioTransport(
|
|
465
|
+
command=server[server_name]["command"],
|
|
466
|
+
args=server[server_name]["args"],
|
|
467
|
+
env=server[server_name].get("env", None),
|
|
468
|
+
cwd=server[server_name].get("cwd", None),
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
# HTTP transport
|
|
472
|
+
elif "url" in server:
|
|
473
|
+
return SSETransport(
|
|
474
|
+
url=server["url"],
|
|
475
|
+
headers=server.get("headers", None),
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
# WebSocket transport
|
|
479
|
+
elif "ws_url" in server:
|
|
480
|
+
return WSTransport(
|
|
481
|
+
url=server["ws_url"],
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
raise ValueError("Cannot determine transport type from dictionary")
|
|
485
|
+
|
|
453
486
|
# the transport is an unknown type
|
|
454
487
|
else:
|
|
455
488
|
raise ValueError(f"Could not infer a valid transport from: {transport}")
|
fastmcp/server/context.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations as _annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any, Generic
|
|
3
|
+
from typing import Any, Generic
|
|
4
4
|
|
|
5
|
+
from mcp import LoggingLevel
|
|
5
6
|
from mcp.server.lowlevel.helper_types import ReadResourceContents
|
|
6
7
|
from mcp.server.session import ServerSessionT
|
|
7
8
|
from mcp.shared.context import LifespanContextT, RequestContext
|
|
@@ -122,19 +123,20 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT]):
|
|
|
122
123
|
|
|
123
124
|
async def log(
|
|
124
125
|
self,
|
|
125
|
-
level: Literal["debug", "info", "warning", "error"],
|
|
126
126
|
message: str,
|
|
127
|
-
|
|
127
|
+
level: LoggingLevel | None = None,
|
|
128
128
|
logger_name: str | None = None,
|
|
129
129
|
) -> None:
|
|
130
130
|
"""Send a log message to the client.
|
|
131
131
|
|
|
132
132
|
Args:
|
|
133
|
-
level: Log level (debug, info, warning, error)
|
|
134
133
|
message: Log message
|
|
134
|
+
level: Optional log level. One of "debug", "info", "notice", "warning", "error", "critical",
|
|
135
|
+
"alert", or "emergency". Default is "info".
|
|
135
136
|
logger_name: Optional logger name
|
|
136
|
-
**extra: Additional structured data to include
|
|
137
137
|
"""
|
|
138
|
+
if level is None:
|
|
139
|
+
level = "info"
|
|
138
140
|
await self.request_context.session.send_log_message(
|
|
139
141
|
level=level, data=message, logger=logger_name
|
|
140
142
|
)
|
|
@@ -159,21 +161,21 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT]):
|
|
|
159
161
|
return self.request_context.session
|
|
160
162
|
|
|
161
163
|
# Convenience methods for common log levels
|
|
162
|
-
async def debug(self, message: str,
|
|
164
|
+
async def debug(self, message: str, logger_name: str | None = None) -> None:
|
|
163
165
|
"""Send a debug log message."""
|
|
164
|
-
await self.log("debug", message,
|
|
166
|
+
await self.log(level="debug", message=message, logger_name=logger_name)
|
|
165
167
|
|
|
166
|
-
async def info(self, message: str,
|
|
168
|
+
async def info(self, message: str, logger_name: str | None = None) -> None:
|
|
167
169
|
"""Send an info log message."""
|
|
168
|
-
await self.log("info", message,
|
|
170
|
+
await self.log(level="info", message=message, logger_name=logger_name)
|
|
169
171
|
|
|
170
|
-
async def warning(self, message: str,
|
|
172
|
+
async def warning(self, message: str, logger_name: str | None = None) -> None:
|
|
171
173
|
"""Send a warning log message."""
|
|
172
|
-
await self.log("warning", message,
|
|
174
|
+
await self.log(level="warning", message=message, logger_name=logger_name)
|
|
173
175
|
|
|
174
|
-
async def error(self, message: str,
|
|
176
|
+
async def error(self, message: str, logger_name: str | None = None) -> None:
|
|
175
177
|
"""Send an error log message."""
|
|
176
|
-
await self.log("error", message,
|
|
178
|
+
await self.log(level="error", message=message, logger_name=logger_name)
|
|
177
179
|
|
|
178
180
|
async def list_roots(self) -> list[Root]:
|
|
179
181
|
"""List the roots available to the server, as indicated by the client."""
|
fastmcp/server/openapi.py
CHANGED
|
@@ -10,12 +10,12 @@ from re import Pattern
|
|
|
10
10
|
from typing import TYPE_CHECKING, Any, Literal
|
|
11
11
|
|
|
12
12
|
import httpx
|
|
13
|
-
from mcp.types import TextContent
|
|
13
|
+
from mcp.types import EmbeddedResource, ImageContent, TextContent
|
|
14
14
|
from pydantic.networks import AnyUrl
|
|
15
15
|
|
|
16
16
|
from fastmcp.resources import Resource, ResourceTemplate
|
|
17
17
|
from fastmcp.server.server import FastMCP
|
|
18
|
-
from fastmcp.tools.tool import Tool
|
|
18
|
+
from fastmcp.tools.tool import Tool, _convert_to_content
|
|
19
19
|
from fastmcp.utilities import openapi
|
|
20
20
|
from fastmcp.utilities.func_metadata import func_metadata
|
|
21
21
|
from fastmcp.utilities.logging import get_logger
|
|
@@ -125,6 +125,7 @@ class OpenAPITool(Tool):
|
|
|
125
125
|
fn_metadata: Any,
|
|
126
126
|
is_async: bool = True,
|
|
127
127
|
tags: set[str] = set(),
|
|
128
|
+
timeout: float | None = None,
|
|
128
129
|
):
|
|
129
130
|
super().__init__(
|
|
130
131
|
name=name,
|
|
@@ -138,6 +139,7 @@ class OpenAPITool(Tool):
|
|
|
138
139
|
)
|
|
139
140
|
self._client = client
|
|
140
141
|
self._route = route
|
|
142
|
+
self._timeout = timeout
|
|
141
143
|
|
|
142
144
|
async def _execute_request(self, *args, **kwargs):
|
|
143
145
|
"""Execute the HTTP request based on the route configuration."""
|
|
@@ -147,19 +149,37 @@ class OpenAPITool(Tool):
|
|
|
147
149
|
path = self._route.path
|
|
148
150
|
|
|
149
151
|
# Replace path parameters with values from kwargs
|
|
152
|
+
# Path parameters should never be None as they're typically required
|
|
153
|
+
# but we'll handle that case anyway
|
|
150
154
|
path_params = {
|
|
151
155
|
p.name: kwargs.get(p.name)
|
|
152
156
|
for p in self._route.parameters
|
|
153
157
|
if p.location == "path"
|
|
158
|
+
and p.name in kwargs
|
|
159
|
+
and kwargs.get(p.name) is not None
|
|
154
160
|
}
|
|
161
|
+
|
|
162
|
+
# Ensure all path parameters are provided
|
|
163
|
+
required_path_params = {
|
|
164
|
+
p.name
|
|
165
|
+
for p in self._route.parameters
|
|
166
|
+
if p.location == "path" and p.required
|
|
167
|
+
}
|
|
168
|
+
missing_params = required_path_params - path_params.keys()
|
|
169
|
+
if missing_params:
|
|
170
|
+
raise ValueError(f"Missing required path parameters: {missing_params}")
|
|
171
|
+
|
|
155
172
|
for param_name, param_value in path_params.items():
|
|
156
173
|
path = path.replace(f"{{{param_name}}}", str(param_value))
|
|
157
174
|
|
|
158
|
-
# Prepare query parameters
|
|
175
|
+
# Prepare query parameters - filter out None and empty strings
|
|
159
176
|
query_params = {
|
|
160
177
|
p.name: kwargs.get(p.name)
|
|
161
178
|
for p in self._route.parameters
|
|
162
|
-
if p.location == "query"
|
|
179
|
+
if p.location == "query"
|
|
180
|
+
and p.name in kwargs
|
|
181
|
+
and kwargs.get(p.name) is not None
|
|
182
|
+
and kwargs.get(p.name) != ""
|
|
163
183
|
}
|
|
164
184
|
|
|
165
185
|
# Prepare headers - fix typing by ensuring all values are strings
|
|
@@ -206,7 +226,7 @@ class OpenAPITool(Tool):
|
|
|
206
226
|
params=query_params,
|
|
207
227
|
headers=headers,
|
|
208
228
|
json=json_data,
|
|
209
|
-
timeout=
|
|
229
|
+
timeout=self._timeout,
|
|
210
230
|
)
|
|
211
231
|
|
|
212
232
|
# Raise for 4xx/5xx responses
|
|
@@ -237,9 +257,14 @@ class OpenAPITool(Tool):
|
|
|
237
257
|
# Handle request errors (connection, timeout, etc.)
|
|
238
258
|
raise ValueError(f"Request error: {str(e)}")
|
|
239
259
|
|
|
240
|
-
async def run(
|
|
260
|
+
async def run(
|
|
261
|
+
self,
|
|
262
|
+
arguments: dict[str, Any],
|
|
263
|
+
context: Context[ServerSessionT, LifespanContextT] | None = None,
|
|
264
|
+
) -> list[TextContent | ImageContent | EmbeddedResource]:
|
|
241
265
|
"""Run the tool with arguments and optional context."""
|
|
242
|
-
|
|
266
|
+
response = await self._execute_request(**arguments, context=context)
|
|
267
|
+
return _convert_to_content(response)
|
|
243
268
|
|
|
244
269
|
|
|
245
270
|
class OpenAPIResource(Resource):
|
|
@@ -254,6 +279,7 @@ class OpenAPIResource(Resource):
|
|
|
254
279
|
description: str,
|
|
255
280
|
mime_type: str = "application/json",
|
|
256
281
|
tags: set[str] = set(),
|
|
282
|
+
timeout: float | None = None,
|
|
257
283
|
):
|
|
258
284
|
super().__init__(
|
|
259
285
|
uri=AnyUrl(uri), # Convert string to AnyUrl
|
|
@@ -264,6 +290,7 @@ class OpenAPIResource(Resource):
|
|
|
264
290
|
)
|
|
265
291
|
self._client = client
|
|
266
292
|
self._route = route
|
|
293
|
+
self._timeout = timeout
|
|
267
294
|
|
|
268
295
|
async def read(
|
|
269
296
|
self, context: Context[ServerSessionT, LifespanContextT] | None = None
|
|
@@ -278,30 +305,44 @@ class OpenAPIResource(Resource):
|
|
|
278
305
|
if "{" in path and "}" in path:
|
|
279
306
|
# Extract the resource ID from the URI (the last part after the last slash)
|
|
280
307
|
parts = resource_uri.split("/")
|
|
308
|
+
|
|
281
309
|
if len(parts) > 1:
|
|
282
310
|
# Find all path parameters in the route path
|
|
283
311
|
path_params = {}
|
|
284
312
|
|
|
285
|
-
#
|
|
286
|
-
param_value = parts[
|
|
287
|
-
-1
|
|
288
|
-
] # The last part contains the parameter value
|
|
289
|
-
|
|
290
|
-
# Find the path parameter name from the route path
|
|
313
|
+
# Find the path parameter names from the route path
|
|
291
314
|
param_matches = re.findall(r"\{([^}]+)\}", path)
|
|
292
315
|
if param_matches:
|
|
293
|
-
#
|
|
294
|
-
|
|
295
|
-
|
|
316
|
+
# Reverse sorting from creation order (traversal is backwards)
|
|
317
|
+
param_matches.sort(reverse=True)
|
|
318
|
+
# Number of sent parameters is number of parts -1 (assuming first part is resource identifier)
|
|
319
|
+
expected_param_count = len(parts) - 1
|
|
320
|
+
# Map parameters from the end of the URI to the parameters in the path
|
|
321
|
+
# Last parameter in URI (parts[-1]) maps to last parameter in path, and so on
|
|
322
|
+
for i, param_name in enumerate(param_matches):
|
|
323
|
+
# Ensure we don't use resource identifier as parameter
|
|
324
|
+
if i < expected_param_count:
|
|
325
|
+
# Get values from the end of parts
|
|
326
|
+
param_value = parts[-1 - i]
|
|
327
|
+
path_params[param_name] = param_value
|
|
296
328
|
|
|
297
329
|
# Replace path parameters with their values
|
|
298
330
|
for param_name, param_value in path_params.items():
|
|
299
331
|
path = path.replace(f"{{{param_name}}}", str(param_value))
|
|
300
332
|
|
|
333
|
+
# Filter any query parameters - get query parameters and filter out None/empty values
|
|
334
|
+
query_params = {}
|
|
335
|
+
for param in self._route.parameters:
|
|
336
|
+
if param.location == "query" and hasattr(self, f"_{param.name}"):
|
|
337
|
+
value = getattr(self, f"_{param.name}")
|
|
338
|
+
if value is not None and value != "":
|
|
339
|
+
query_params[param.name] = value
|
|
340
|
+
|
|
301
341
|
response = await self._client.request(
|
|
302
342
|
method=self._route.method,
|
|
303
343
|
url=path,
|
|
304
|
-
|
|
344
|
+
params=query_params,
|
|
345
|
+
timeout=self._timeout,
|
|
305
346
|
)
|
|
306
347
|
|
|
307
348
|
# Raise for 4xx/5xx responses
|
|
@@ -349,6 +390,7 @@ class OpenAPIResourceTemplate(ResourceTemplate):
|
|
|
349
390
|
description: str,
|
|
350
391
|
parameters: dict[str, Any],
|
|
351
392
|
tags: set[str] = set(),
|
|
393
|
+
timeout: float | None = None,
|
|
352
394
|
):
|
|
353
395
|
super().__init__(
|
|
354
396
|
uri_template=uri_template,
|
|
@@ -361,6 +403,7 @@ class OpenAPIResourceTemplate(ResourceTemplate):
|
|
|
361
403
|
)
|
|
362
404
|
self._client = client
|
|
363
405
|
self._route = route
|
|
406
|
+
self._timeout = timeout
|
|
364
407
|
|
|
365
408
|
async def create_resource(
|
|
366
409
|
self,
|
|
@@ -383,6 +426,7 @@ class OpenAPIResourceTemplate(ResourceTemplate):
|
|
|
383
426
|
description=self.description or f"Resource for {self._route.path}",
|
|
384
427
|
mime_type="application/json",
|
|
385
428
|
tags=set(self._route.tags or []),
|
|
429
|
+
timeout=self._timeout,
|
|
386
430
|
)
|
|
387
431
|
|
|
388
432
|
|
|
@@ -430,6 +474,7 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
430
474
|
client: httpx.AsyncClient,
|
|
431
475
|
name: str | None = None,
|
|
432
476
|
route_maps: list[RouteMap] | None = None,
|
|
477
|
+
timeout: float | None = None,
|
|
433
478
|
**settings: Any,
|
|
434
479
|
):
|
|
435
480
|
"""
|
|
@@ -440,13 +485,13 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
440
485
|
client: httpx AsyncClient for making HTTP requests
|
|
441
486
|
name: Optional name for the server
|
|
442
487
|
route_maps: Optional list of RouteMap objects defining route mappings
|
|
443
|
-
|
|
488
|
+
timeout: Optional timeout (in seconds) for all requests
|
|
444
489
|
**settings: Additional settings for FastMCP
|
|
445
490
|
"""
|
|
446
491
|
super().__init__(name=name or "OpenAPI FastMCP", **settings)
|
|
447
492
|
|
|
448
493
|
self._client = client
|
|
449
|
-
|
|
494
|
+
self._timeout = timeout
|
|
450
495
|
http_routes = openapi.parse_openapi_to_http_routes(openapi_spec)
|
|
451
496
|
|
|
452
497
|
# Process routes
|
|
@@ -504,6 +549,7 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
504
549
|
fn_metadata=func_metadata(_openapi_passthrough),
|
|
505
550
|
is_async=True,
|
|
506
551
|
tags=set(route.tags or []),
|
|
552
|
+
timeout=self._timeout,
|
|
507
553
|
)
|
|
508
554
|
# Register the tool by directly assigning to the tools dictionary
|
|
509
555
|
self._tool_manager._tools[tool_name] = tool
|
|
@@ -532,6 +578,7 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
532
578
|
name=resource_name,
|
|
533
579
|
description=enhanced_description,
|
|
534
580
|
tags=set(route.tags or []),
|
|
581
|
+
timeout=self._timeout,
|
|
535
582
|
)
|
|
536
583
|
# Register the resource by directly assigning to the resources dictionary
|
|
537
584
|
self._resource_manager._resources[str(resource.uri)] = resource
|
|
@@ -577,6 +624,7 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
577
624
|
description=enhanced_description,
|
|
578
625
|
parameters=template_params_schema,
|
|
579
626
|
tags=set(route.tags or []),
|
|
627
|
+
timeout=self._timeout,
|
|
580
628
|
)
|
|
581
629
|
# Register the template by directly assigning to the templates dictionary
|
|
582
630
|
self._resource_manager._templates[uri_template_str] = template
|
|
@@ -589,13 +637,4 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
589
637
|
|
|
590
638
|
context = self.get_context()
|
|
591
639
|
result = await self._tool_manager.call_tool(name, arguments, context=context)
|
|
592
|
-
|
|
593
|
-
# For other tools, ensure the response is wrapped in TextContent
|
|
594
|
-
if isinstance(result, dict | str):
|
|
595
|
-
if isinstance(result, dict):
|
|
596
|
-
result_text = json.dumps(result)
|
|
597
|
-
else:
|
|
598
|
-
result_text = result
|
|
599
|
-
return [TextContent(text=result_text, type="text")]
|
|
600
|
-
|
|
601
640
|
return result
|
fastmcp/server/proxy.py
CHANGED
|
@@ -61,7 +61,7 @@ class ProxyTool(Tool):
|
|
|
61
61
|
self,
|
|
62
62
|
arguments: dict[str, Any],
|
|
63
63
|
context: Context[ServerSessionT, LifespanContextT] | None = None,
|
|
64
|
-
) ->
|
|
64
|
+
) -> list[TextContent | ImageContent | EmbeddedResource]:
|
|
65
65
|
# the client context manager will swallow any exceptions inside a TaskGroup
|
|
66
66
|
# so we return the raw result and raise an exception ourselves
|
|
67
67
|
async with self._client:
|
fastmcp/server/server.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""FastMCP - A more ergonomic interface for MCP servers."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
import datetime
|
|
4
6
|
from collections.abc import AsyncIterator, Awaitable, Callable
|
|
5
7
|
from contextlib import (
|
|
@@ -63,7 +65,7 @@ class MountedServer:
|
|
|
63
65
|
def __init__(
|
|
64
66
|
self,
|
|
65
67
|
prefix: str,
|
|
66
|
-
server:
|
|
68
|
+
server: FastMCP,
|
|
67
69
|
tool_separator: str | None = None,
|
|
68
70
|
resource_separator: str | None = None,
|
|
69
71
|
prompt_separator: str | None = None,
|
|
@@ -149,7 +151,7 @@ class TimedCache:
|
|
|
149
151
|
|
|
150
152
|
|
|
151
153
|
@asynccontextmanager
|
|
152
|
-
async def default_lifespan(server:
|
|
154
|
+
async def default_lifespan(server: FastMCP) -> AsyncIterator[Any]:
|
|
153
155
|
"""Default lifespan context manager that does nothing.
|
|
154
156
|
|
|
155
157
|
Args:
|
|
@@ -162,8 +164,8 @@ async def default_lifespan(server: "FastMCP") -> AsyncIterator[Any]:
|
|
|
162
164
|
|
|
163
165
|
|
|
164
166
|
def _lifespan_wrapper(
|
|
165
|
-
app:
|
|
166
|
-
lifespan: Callable[[
|
|
167
|
+
app: FastMCP,
|
|
168
|
+
lifespan: Callable[[FastMCP], AbstractAsyncContextManager[LifespanResultT]],
|
|
167
169
|
) -> Callable[
|
|
168
170
|
[MCPServer[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]
|
|
169
171
|
]:
|
|
@@ -182,7 +184,11 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
182
184
|
name: str | None = None,
|
|
183
185
|
instructions: str | None = None,
|
|
184
186
|
lifespan: (
|
|
185
|
-
Callable[
|
|
187
|
+
Callable[
|
|
188
|
+
[FastMCP[LifespanResultT]],
|
|
189
|
+
AbstractAsyncContextManager[LifespanResultT],
|
|
190
|
+
]
|
|
191
|
+
| None
|
|
186
192
|
) = None,
|
|
187
193
|
tags: set[str] | None = None,
|
|
188
194
|
**settings: Any,
|
|
@@ -273,7 +279,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
273
279
|
self._mcp_server.get_prompt()(self._mcp_get_prompt)
|
|
274
280
|
self._mcp_server.list_resource_templates()(self._mcp_list_resource_templates)
|
|
275
281
|
|
|
276
|
-
def get_context(self) ->
|
|
282
|
+
def get_context(self) -> Context[ServerSession, LifespanResultT]:
|
|
277
283
|
"""
|
|
278
284
|
Returns a Context object. Note that the context will only be valid
|
|
279
285
|
during a request; outside a request, most methods will error.
|
|
@@ -766,7 +772,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
766
772
|
def mount(
|
|
767
773
|
self,
|
|
768
774
|
prefix: str,
|
|
769
|
-
server:
|
|
775
|
+
server: FastMCP[LifespanResultT],
|
|
770
776
|
tool_separator: str | None = None,
|
|
771
777
|
resource_separator: str | None = None,
|
|
772
778
|
prompt_separator: str | None = None,
|
|
@@ -791,7 +797,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
791
797
|
async def import_server(
|
|
792
798
|
self,
|
|
793
799
|
prefix: str,
|
|
794
|
-
server:
|
|
800
|
+
server: FastMCP[LifespanResultT],
|
|
795
801
|
tool_separator: str | None = None,
|
|
796
802
|
resource_separator: str | None = None,
|
|
797
803
|
prompt_separator: str | None = None,
|
|
@@ -865,7 +871,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
865
871
|
@classmethod
|
|
866
872
|
def from_openapi(
|
|
867
873
|
cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, **settings: Any
|
|
868
|
-
) ->
|
|
874
|
+
) -> FastMCPOpenAPI:
|
|
869
875
|
"""
|
|
870
876
|
Create a FastMCP server from an OpenAPI specification.
|
|
871
877
|
"""
|
|
@@ -875,8 +881,8 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
875
881
|
|
|
876
882
|
@classmethod
|
|
877
883
|
def from_fastapi(
|
|
878
|
-
cls, app:
|
|
879
|
-
) ->
|
|
884
|
+
cls, app: Any, name: str | None = None, **settings: Any
|
|
885
|
+
) -> FastMCPOpenAPI:
|
|
880
886
|
"""
|
|
881
887
|
Create a FastMCP server from a FastAPI application.
|
|
882
888
|
"""
|
|
@@ -894,7 +900,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
894
900
|
)
|
|
895
901
|
|
|
896
902
|
@classmethod
|
|
897
|
-
def from_client(cls, client:
|
|
903
|
+
def from_client(cls, client: Client, **settings: Any) -> FastMCPProxy:
|
|
898
904
|
"""
|
|
899
905
|
Create a FastMCP proxy server from a FastMCP client.
|
|
900
906
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmcp
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.6
|
|
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
|
|
@@ -24,7 +24,7 @@ Requires-Dist: openapi-pydantic>=0.5.1
|
|
|
24
24
|
Requires-Dist: python-dotenv>=1.1.0
|
|
25
25
|
Requires-Dist: rich>=13.9.4
|
|
26
26
|
Requires-Dist: typer>=0.15.2
|
|
27
|
-
Requires-Dist: websockets>=
|
|
27
|
+
Requires-Dist: websockets>=14.0
|
|
28
28
|
Description-Content-Type: text/markdown
|
|
29
29
|
|
|
30
30
|
<div align="center">
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
fastmcp/__init__.py,sha256=
|
|
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
4
|
fastmcp/settings.py,sha256=VCjc-3011pKRYjt2h9rZ68XhVEekbpyLyVUREVBTSrg,1955
|
|
@@ -6,11 +6,12 @@ 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
|
|
8
8
|
fastmcp/client/__init__.py,sha256=BXO9NUhntZ5GnUACfaRCzDJ5IzxqFJs8qKG-CRMSco4,490
|
|
9
|
-
fastmcp/client/base.py,sha256=
|
|
10
|
-
fastmcp/client/client.py,sha256=
|
|
9
|
+
fastmcp/client/base.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
fastmcp/client/client.py,sha256=cGVy066XGBtwl4Bwa_Prx-Vy5NMZHrDH8jxiq1oYuBo,7812
|
|
11
|
+
fastmcp/client/logging.py,sha256=Q8jYcZj4KA15Yiz3RP8tBXj8sd9IxL3VThF_Y0O4Upc,356
|
|
11
12
|
fastmcp/client/roots.py,sha256=IxI_bHwHTmg6c2H-s1av1ZgrRnNDieHtYwdGFbzXT5c,2471
|
|
12
|
-
fastmcp/client/sampling.py,sha256=
|
|
13
|
-
fastmcp/client/transports.py,sha256=
|
|
13
|
+
fastmcp/client/sampling.py,sha256=UlDHxnd6k_HoU8RA3ob0g8-e6haJBc9u27N_v291QoI,1698
|
|
14
|
+
fastmcp/client/transports.py,sha256=FMMniyxnaaHyWeFHXXP_ayOpb4jtCoyjaiqLobgYb_M,16885
|
|
14
15
|
fastmcp/contrib/README.md,sha256=rKknYSI1T192UvSszqwwDlQ2eYQpxywrNTLoj177SYU,878
|
|
15
16
|
fastmcp/contrib/bulk_tool_caller/README.md,sha256=5aUUY1TSFKtz1pvTLSDqkUCkGkuqMfMZNsLeaNqEgAc,1960
|
|
16
17
|
fastmcp/contrib/bulk_tool_caller/__init__.py,sha256=xvGSSaUXTQrc31erBoi1Gh7BikgOliETDiYVTP3rLxY,75
|
|
@@ -29,10 +30,10 @@ fastmcp/resources/resource_manager.py,sha256=yfQNCEUooZiQ8LNslnAzjQp4Vh-y1YOlFyG
|
|
|
29
30
|
fastmcp/resources/template.py,sha256=oa85KiuTjh3C7aZvMwmO4fwbTi6IvwlQ3fxizJuv3dk,7261
|
|
30
31
|
fastmcp/resources/types.py,sha256=c1z6BQSosgrlPQ3v67DuXCvDjCJMq9Xl45npEpyk0ik,7710
|
|
31
32
|
fastmcp/server/__init__.py,sha256=pdkghG11VLMZiluQ-4_rl2JK1LMWmV003m9dDRUN8W4,92
|
|
32
|
-
fastmcp/server/context.py,sha256=
|
|
33
|
-
fastmcp/server/openapi.py,sha256=
|
|
34
|
-
fastmcp/server/proxy.py,sha256=
|
|
35
|
-
fastmcp/server/server.py,sha256=
|
|
33
|
+
fastmcp/server/context.py,sha256=_eaL_6QtlQC9yDvHYjntmMzG1aNQZSnli5bIIpCLIwc,7403
|
|
34
|
+
fastmcp/server/openapi.py,sha256=bMmBfgNGzkbweM5g_64NpqvJ6cPtzhSWrzgRvUqP3ec,23185
|
|
35
|
+
fastmcp/server/proxy.py,sha256=pOY6XRvU_GDVv6hd3BttG5W5D1bOtjs9u6iygUMHPsw,10158
|
|
36
|
+
fastmcp/server/server.py,sha256=vQDVo7M8JwltqbprsE7Bd_lTxlnXyrc5jQ4C1uQA1lA,33152
|
|
36
37
|
fastmcp/tools/__init__.py,sha256=ocw-SFTtN6vQ8fgnlF8iNAOflRmh79xS1xdO0Bc3QPE,96
|
|
37
38
|
fastmcp/tools/tool.py,sha256=k797XAeXdcUuBfeuvxkEy8xckXi7xSzQVgkzL876rBQ,6755
|
|
38
39
|
fastmcp/tools/tool_manager.py,sha256=hClv7fwj0cQSSwW0i-Swt7xiVqR4T9LVmr1Tp704nW4,3283
|
|
@@ -42,8 +43,8 @@ fastmcp/utilities/func_metadata.py,sha256=iYXnx7MILOSL8mUQ6Rtq_6U7qA08OkoEN2APY8
|
|
|
42
43
|
fastmcp/utilities/logging.py,sha256=zav8pnFxG_fvGJHUV2XpobmT9WVrmv1mlQBSCz-CPx4,1159
|
|
43
44
|
fastmcp/utilities/openapi.py,sha256=PrH3usbTblaVC6jIH1UGiPEfgB2sSCLj33zA5dH7o_s,45193
|
|
44
45
|
fastmcp/utilities/types.py,sha256=m2rPYMzO-ZFvvZ46N-1-Xqyw693K7yq9Z2xR4pVELyk,2091
|
|
45
|
-
fastmcp-2.2.
|
|
46
|
-
fastmcp-2.2.
|
|
47
|
-
fastmcp-2.2.
|
|
48
|
-
fastmcp-2.2.
|
|
49
|
-
fastmcp-2.2.
|
|
46
|
+
fastmcp-2.2.6.dist-info/METADATA,sha256=gjgxlV6gTth_za73JXr1kzU9LgD9ELMoqAMX1vaaUDY,27767
|
|
47
|
+
fastmcp-2.2.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
48
|
+
fastmcp-2.2.6.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
|
|
49
|
+
fastmcp-2.2.6.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
50
|
+
fastmcp-2.2.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|