fastmcp 2.5.0__py3-none-any.whl → 2.5.2__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 +1 -3
- fastmcp/client/client.py +83 -15
- fastmcp/client/transports.py +183 -48
- fastmcp/resources/template.py +1 -1
- fastmcp/server/dependencies.py +43 -0
- fastmcp/server/openapi.py +3 -22
- fastmcp/server/server.py +43 -16
- fastmcp/settings.py +7 -0
- fastmcp/tools/tool.py +4 -2
- {fastmcp-2.5.0.dist-info → fastmcp-2.5.2.dist-info}/METADATA +1 -1
- {fastmcp-2.5.0.dist-info → fastmcp-2.5.2.dist-info}/RECORD +14 -14
- {fastmcp-2.5.0.dist-info → fastmcp-2.5.2.dist-info}/WHEEL +0 -0
- {fastmcp-2.5.0.dist-info → fastmcp-2.5.2.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.5.0.dist-info → fastmcp-2.5.2.dist-info}/licenses/LICENSE +0 -0
fastmcp/cli/cli.py
CHANGED
|
@@ -50,9 +50,7 @@ def _get_npx_command():
|
|
|
50
50
|
def _parse_env_var(env_var: str) -> tuple[str, str]:
|
|
51
51
|
"""Parse environment variable string in format KEY=VALUE."""
|
|
52
52
|
if "=" not in env_var:
|
|
53
|
-
logger.error(
|
|
54
|
-
f"Invalid environment variable format: {env_var}. Must be KEY=VALUE"
|
|
55
|
-
)
|
|
53
|
+
logger.error("Invalid environment variable format. Must be KEY=VALUE")
|
|
56
54
|
sys.exit(1)
|
|
57
55
|
key, value = env_var.split("=", 1)
|
|
58
56
|
return key.strip(), value.strip()
|
fastmcp/client/client.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
from contextlib import AsyncExitStack, asynccontextmanager
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Any, cast
|
|
4
|
+
from typing import Any, Generic, cast, overload
|
|
5
5
|
|
|
6
6
|
import anyio
|
|
7
7
|
import mcp.types
|
|
@@ -9,6 +9,7 @@ from exceptiongroup import catch
|
|
|
9
9
|
from mcp import ClientSession
|
|
10
10
|
from pydantic import AnyUrl
|
|
11
11
|
|
|
12
|
+
import fastmcp
|
|
12
13
|
from fastmcp.client.logging import (
|
|
13
14
|
LogHandler,
|
|
14
15
|
MessageHandler,
|
|
@@ -27,7 +28,18 @@ from fastmcp.server import FastMCP
|
|
|
27
28
|
from fastmcp.utilities.exceptions import get_catch_handlers
|
|
28
29
|
from fastmcp.utilities.mcp_config import MCPConfig
|
|
29
30
|
|
|
30
|
-
from .transports import
|
|
31
|
+
from .transports import (
|
|
32
|
+
ClientTransportT,
|
|
33
|
+
FastMCP1Server,
|
|
34
|
+
FastMCPTransport,
|
|
35
|
+
MCPConfigTransport,
|
|
36
|
+
NodeStdioTransport,
|
|
37
|
+
PythonStdioTransport,
|
|
38
|
+
SessionKwargs,
|
|
39
|
+
SSETransport,
|
|
40
|
+
StreamableHttpTransport,
|
|
41
|
+
infer_transport,
|
|
42
|
+
)
|
|
31
43
|
|
|
32
44
|
__all__ = [
|
|
33
45
|
"Client",
|
|
@@ -40,13 +52,13 @@ __all__ = [
|
|
|
40
52
|
]
|
|
41
53
|
|
|
42
54
|
|
|
43
|
-
class Client:
|
|
55
|
+
class Client(Generic[ClientTransportT]):
|
|
44
56
|
"""
|
|
45
57
|
MCP client that delegates connection management to a Transport instance.
|
|
46
58
|
|
|
47
59
|
The Client class is responsible for MCP protocol logic, while the Transport
|
|
48
|
-
handles connection establishment and management. Client provides methods
|
|
49
|
-
|
|
60
|
+
handles connection establishment and management. Client provides methods for
|
|
61
|
+
working with resources, prompts, tools and other MCP capabilities.
|
|
50
62
|
|
|
51
63
|
Args:
|
|
52
64
|
transport: Connection source specification, which can be:
|
|
@@ -62,24 +74,60 @@ class Client:
|
|
|
62
74
|
message_handler: Optional handler for protocol messages
|
|
63
75
|
progress_handler: Optional handler for progress notifications
|
|
64
76
|
timeout: Optional timeout for requests (seconds or timedelta)
|
|
77
|
+
init_timeout: Optional timeout for initial connection (seconds or timedelta).
|
|
78
|
+
Set to 0 to disable. If None, uses the value in the FastMCP global settings.
|
|
65
79
|
|
|
66
80
|
Examples:
|
|
67
|
-
```python
|
|
68
|
-
|
|
69
|
-
client = Client("http://localhost:8080")
|
|
81
|
+
```python # Connect to FastMCP server client =
|
|
82
|
+
Client("http://localhost:8080")
|
|
70
83
|
|
|
71
84
|
async with client:
|
|
72
|
-
# List available resources
|
|
73
|
-
resources = await client.list_resources()
|
|
85
|
+
# List available resources resources = await client.list_resources()
|
|
74
86
|
|
|
75
|
-
# Call a tool
|
|
76
|
-
|
|
87
|
+
# Call a tool result = await client.call_tool("my_tool", {"param":
|
|
88
|
+
"value"})
|
|
77
89
|
```
|
|
78
90
|
"""
|
|
79
91
|
|
|
92
|
+
@overload
|
|
93
|
+
def __new__(
|
|
94
|
+
cls,
|
|
95
|
+
transport: ClientTransportT,
|
|
96
|
+
**kwargs: Any,
|
|
97
|
+
) -> "Client[ClientTransportT]": ...
|
|
98
|
+
|
|
99
|
+
@overload
|
|
100
|
+
def __new__(
|
|
101
|
+
cls, transport: AnyUrl, **kwargs
|
|
102
|
+
) -> "Client[SSETransport|StreamableHttpTransport]": ...
|
|
103
|
+
|
|
104
|
+
@overload
|
|
105
|
+
def __new__(
|
|
106
|
+
cls, transport: FastMCP | FastMCP1Server, **kwargs
|
|
107
|
+
) -> "Client[FastMCPTransport]": ...
|
|
108
|
+
|
|
109
|
+
@overload
|
|
110
|
+
def __new__(
|
|
111
|
+
cls, transport: Path, **kwargs
|
|
112
|
+
) -> "Client[PythonStdioTransport|NodeStdioTransport]": ...
|
|
113
|
+
|
|
114
|
+
@overload
|
|
115
|
+
def __new__(
|
|
116
|
+
cls, transport: MCPConfig | dict[str, Any], **kwargs
|
|
117
|
+
) -> "Client[MCPConfigTransport]": ...
|
|
118
|
+
|
|
119
|
+
@overload
|
|
120
|
+
def __new__(
|
|
121
|
+
cls, transport: str, **kwargs
|
|
122
|
+
) -> "Client[PythonStdioTransport|NodeStdioTransport|SSETransport|StreamableHttpTransport]": ...
|
|
123
|
+
|
|
124
|
+
def __new__(cls, transport, **kwargs) -> "Client":
|
|
125
|
+
instance = super().__new__(cls)
|
|
126
|
+
return instance
|
|
127
|
+
|
|
80
128
|
def __init__(
|
|
81
129
|
self,
|
|
82
|
-
transport:
|
|
130
|
+
transport: ClientTransportT
|
|
83
131
|
| FastMCP
|
|
84
132
|
| AnyUrl
|
|
85
133
|
| Path
|
|
@@ -93,8 +141,9 @@ class Client:
|
|
|
93
141
|
message_handler: MessageHandler | None = None,
|
|
94
142
|
progress_handler: ProgressHandler | None = None,
|
|
95
143
|
timeout: datetime.timedelta | float | int | None = None,
|
|
144
|
+
init_timeout: datetime.timedelta | float | int | None = None,
|
|
96
145
|
):
|
|
97
|
-
self.transport = infer_transport(transport)
|
|
146
|
+
self.transport = cast(ClientTransportT, infer_transport(transport))
|
|
98
147
|
self._session: ClientSession | None = None
|
|
99
148
|
self._exit_stack: AsyncExitStack | None = None
|
|
100
149
|
self._nesting_counter: int = 0
|
|
@@ -111,6 +160,17 @@ class Client:
|
|
|
111
160
|
if isinstance(timeout, int | float):
|
|
112
161
|
timeout = datetime.timedelta(seconds=timeout)
|
|
113
162
|
|
|
163
|
+
# handle init handshake timeout
|
|
164
|
+
if init_timeout is None:
|
|
165
|
+
init_timeout = fastmcp.settings.settings.client_init_timeout
|
|
166
|
+
if isinstance(init_timeout, datetime.timedelta):
|
|
167
|
+
init_timeout = init_timeout.total_seconds()
|
|
168
|
+
elif not init_timeout:
|
|
169
|
+
init_timeout = None
|
|
170
|
+
else:
|
|
171
|
+
init_timeout = float(init_timeout)
|
|
172
|
+
self._init_timeout = init_timeout
|
|
173
|
+
|
|
114
174
|
self._session_kwargs: SessionKwargs = {
|
|
115
175
|
"sampling_callback": None,
|
|
116
176
|
"list_roots_callback": None,
|
|
@@ -134,6 +194,7 @@ class Client:
|
|
|
134
194
|
raise RuntimeError(
|
|
135
195
|
"Client is not connected. Use the 'async with client:' context manager first."
|
|
136
196
|
)
|
|
197
|
+
|
|
137
198
|
return self._session
|
|
138
199
|
|
|
139
200
|
@property
|
|
@@ -168,9 +229,11 @@ class Client:
|
|
|
168
229
|
self._session = session
|
|
169
230
|
# Initialize the session
|
|
170
231
|
try:
|
|
171
|
-
with anyio.fail_after(
|
|
232
|
+
with anyio.fail_after(self._init_timeout):
|
|
172
233
|
self._initialize_result = await self._session.initialize()
|
|
173
234
|
yield
|
|
235
|
+
except anyio.ClosedResourceError:
|
|
236
|
+
raise RuntimeError("Server session was closed unexpectedly")
|
|
174
237
|
except TimeoutError:
|
|
175
238
|
raise RuntimeError("Failed to initialize server session")
|
|
176
239
|
finally:
|
|
@@ -203,6 +266,11 @@ class Client:
|
|
|
203
266
|
finally:
|
|
204
267
|
self._exit_stack = None
|
|
205
268
|
|
|
269
|
+
async def close(self):
|
|
270
|
+
await self.transport.close()
|
|
271
|
+
self._session = None
|
|
272
|
+
self._initialize_result = None
|
|
273
|
+
|
|
206
274
|
# --- MCP Client Methods ---
|
|
207
275
|
|
|
208
276
|
async def ping(self) -> bool:
|
fastmcp/client/transports.py
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import abc
|
|
2
|
+
import asyncio
|
|
2
3
|
import contextlib
|
|
3
4
|
import datetime
|
|
4
5
|
import os
|
|
5
6
|
import shutil
|
|
6
7
|
import sys
|
|
8
|
+
import warnings
|
|
7
9
|
from collections.abc import AsyncIterator
|
|
8
10
|
from pathlib import Path
|
|
9
|
-
from typing import TYPE_CHECKING, Any, TypedDict, cast
|
|
11
|
+
from typing import TYPE_CHECKING, Any, TypedDict, TypeVar, cast, overload
|
|
10
12
|
|
|
11
13
|
from mcp import ClientSession, StdioServerParameters
|
|
12
14
|
from mcp.client.session import (
|
|
@@ -25,7 +27,7 @@ from pydantic import AnyUrl
|
|
|
25
27
|
from typing_extensions import Unpack
|
|
26
28
|
|
|
27
29
|
from fastmcp.server import FastMCP as FastMCPServer
|
|
28
|
-
from fastmcp.server.dependencies import
|
|
30
|
+
from fastmcp.server.dependencies import get_http_headers
|
|
29
31
|
from fastmcp.server.server import FastMCP
|
|
30
32
|
from fastmcp.utilities.logging import get_logger
|
|
31
33
|
from fastmcp.utilities.mcp_config import MCPConfig, infer_transport_type_from_url
|
|
@@ -35,10 +37,8 @@ if TYPE_CHECKING:
|
|
|
35
37
|
|
|
36
38
|
logger = get_logger(__name__)
|
|
37
39
|
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
"content-length",
|
|
41
|
-
}
|
|
40
|
+
# TypeVar for preserving specific ClientTransport subclass types
|
|
41
|
+
ClientTransportT = TypeVar("ClientTransportT", bound="ClientTransport")
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
class SessionKwargs(TypedDict, total=False):
|
|
@@ -88,11 +88,21 @@ class ClientTransport(abc.ABC):
|
|
|
88
88
|
# Basic representation for subclasses
|
|
89
89
|
return f"<{self.__class__.__name__}>"
|
|
90
90
|
|
|
91
|
+
async def close(self):
|
|
92
|
+
"""Close the transport."""
|
|
93
|
+
pass
|
|
94
|
+
|
|
91
95
|
|
|
92
96
|
class WSTransport(ClientTransport):
|
|
93
97
|
"""Transport implementation that connects to an MCP server via WebSockets."""
|
|
94
98
|
|
|
95
99
|
def __init__(self, url: str | AnyUrl):
|
|
100
|
+
# we never really used this transport, so it can be removed at any time
|
|
101
|
+
warnings.warn(
|
|
102
|
+
"WSTransport is a deprecated MCP transport and will be removed in a future version. Use StreamableHttpTransport instead.",
|
|
103
|
+
DeprecationWarning,
|
|
104
|
+
stacklevel=2,
|
|
105
|
+
)
|
|
96
106
|
if isinstance(url, AnyUrl):
|
|
97
107
|
url = str(url)
|
|
98
108
|
if not isinstance(url, str) or not url.startswith("ws"):
|
|
@@ -138,23 +148,12 @@ class SSETransport(ClientTransport):
|
|
|
138
148
|
async def connect_session(
|
|
139
149
|
self, **session_kwargs: Unpack[SessionKwargs]
|
|
140
150
|
) -> AsyncIterator[ClientSession]:
|
|
141
|
-
client_kwargs: dict[str, Any] = {
|
|
142
|
-
"headers": self.headers,
|
|
143
|
-
}
|
|
151
|
+
client_kwargs: dict[str, Any] = {}
|
|
144
152
|
|
|
145
153
|
# load headers from an active HTTP request, if available. This will only be true
|
|
146
154
|
# if the client is used in a FastMCP Proxy, in which case the MCP client headers
|
|
147
155
|
# need to be forwarded to the remote server.
|
|
148
|
-
|
|
149
|
-
active_request = get_http_request()
|
|
150
|
-
for name, value in active_request.headers.items():
|
|
151
|
-
name = name.lower()
|
|
152
|
-
if name not in self.headers and name not in {
|
|
153
|
-
h.lower() for h in EXCLUDE_HEADERS
|
|
154
|
-
}:
|
|
155
|
-
client_kwargs["headers"][name] = str(value)
|
|
156
|
-
except RuntimeError:
|
|
157
|
-
client_kwargs["headers"] = self.headers
|
|
156
|
+
client_kwargs["headers"] = get_http_headers() | self.headers
|
|
158
157
|
|
|
159
158
|
# sse_read_timeout has a default value set, so we can't pass None without overriding it
|
|
160
159
|
# instead we simply leave the kwarg out if it's not provided
|
|
@@ -201,25 +200,12 @@ class StreamableHttpTransport(ClientTransport):
|
|
|
201
200
|
async def connect_session(
|
|
202
201
|
self, **session_kwargs: Unpack[SessionKwargs]
|
|
203
202
|
) -> AsyncIterator[ClientSession]:
|
|
204
|
-
client_kwargs: dict[str, Any] = {
|
|
205
|
-
"headers": self.headers,
|
|
206
|
-
}
|
|
203
|
+
client_kwargs: dict[str, Any] = {}
|
|
207
204
|
|
|
208
205
|
# load headers from an active HTTP request, if available. This will only be true
|
|
209
206
|
# if the client is used in a FastMCP Proxy, in which case the MCP client headers
|
|
210
207
|
# need to be forwarded to the remote server.
|
|
211
|
-
|
|
212
|
-
active_request = get_http_request()
|
|
213
|
-
for name, value in active_request.headers.items():
|
|
214
|
-
name = name.lower()
|
|
215
|
-
if name not in self.headers and name not in {
|
|
216
|
-
h.lower() for h in EXCLUDE_HEADERS
|
|
217
|
-
}:
|
|
218
|
-
client_kwargs["headers"][name] = str(value)
|
|
219
|
-
|
|
220
|
-
except RuntimeError:
|
|
221
|
-
client_kwargs["headers"] = self.headers
|
|
222
|
-
print(client_kwargs)
|
|
208
|
+
client_kwargs["headers"] = get_http_headers() | self.headers
|
|
223
209
|
|
|
224
210
|
# sse_read_timeout has a default value set, so we can't pass None without overriding it
|
|
225
211
|
# instead we simply leave the kwarg out if it's not provided
|
|
@@ -253,6 +239,7 @@ class StdioTransport(ClientTransport):
|
|
|
253
239
|
args: list[str],
|
|
254
240
|
env: dict[str, str] | None = None,
|
|
255
241
|
cwd: str | None = None,
|
|
242
|
+
keep_alive: bool | None = None,
|
|
256
243
|
):
|
|
257
244
|
"""
|
|
258
245
|
Initialize a Stdio transport.
|
|
@@ -262,25 +249,90 @@ class StdioTransport(ClientTransport):
|
|
|
262
249
|
args: The arguments to pass to the command
|
|
263
250
|
env: Environment variables to set for the subprocess
|
|
264
251
|
cwd: Current working directory for the subprocess
|
|
252
|
+
keep_alive: Whether to keep the subprocess alive between connections.
|
|
253
|
+
Defaults to True. When True, the subprocess remains active
|
|
254
|
+
after the connection context exits, allowing reuse in
|
|
255
|
+
subsequent connections.
|
|
265
256
|
"""
|
|
266
257
|
self.command = command
|
|
267
258
|
self.args = args
|
|
268
259
|
self.env = env
|
|
269
260
|
self.cwd = cwd
|
|
261
|
+
if keep_alive is None:
|
|
262
|
+
keep_alive = True
|
|
263
|
+
self.keep_alive = keep_alive
|
|
264
|
+
|
|
265
|
+
self._session: ClientSession | None = None
|
|
266
|
+
self._connect_task: asyncio.Task | None = None
|
|
267
|
+
self._ready_event = asyncio.Event()
|
|
268
|
+
self._stop_event = asyncio.Event()
|
|
270
269
|
|
|
271
270
|
@contextlib.asynccontextmanager
|
|
272
271
|
async def connect_session(
|
|
273
272
|
self, **session_kwargs: Unpack[SessionKwargs]
|
|
274
273
|
) -> AsyncIterator[ClientSession]:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
274
|
+
try:
|
|
275
|
+
await self.connect(**session_kwargs)
|
|
276
|
+
assert self._session is not None
|
|
277
|
+
yield self._session
|
|
278
|
+
finally:
|
|
279
|
+
if not self.keep_alive:
|
|
280
|
+
await self.disconnect()
|
|
281
|
+
else:
|
|
282
|
+
logger.debug("Stdio transport has keep_alive=True, not disconnecting")
|
|
283
|
+
|
|
284
|
+
async def connect(
|
|
285
|
+
self, **session_kwargs: Unpack[SessionKwargs]
|
|
286
|
+
) -> ClientSession | None:
|
|
287
|
+
if self._connect_task is not None:
|
|
288
|
+
return
|
|
289
|
+
|
|
290
|
+
async def _connect_task():
|
|
291
|
+
async with contextlib.AsyncExitStack() as stack:
|
|
292
|
+
try:
|
|
293
|
+
server_params = StdioServerParameters(
|
|
294
|
+
command=self.command, args=self.args, env=self.env, cwd=self.cwd
|
|
295
|
+
)
|
|
296
|
+
transport = await stack.enter_async_context(
|
|
297
|
+
stdio_client(server_params)
|
|
298
|
+
)
|
|
299
|
+
read_stream, write_stream = transport
|
|
300
|
+
self._session = await stack.enter_async_context(
|
|
301
|
+
ClientSession(read_stream, write_stream, **session_kwargs)
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
logger.debug("Stdio transport connected")
|
|
305
|
+
self._ready_event.set()
|
|
306
|
+
|
|
307
|
+
# Wait until disconnect is requested (stop_event is set)
|
|
308
|
+
await self._stop_event.wait()
|
|
309
|
+
finally:
|
|
310
|
+
# Clean up client on exit
|
|
311
|
+
self._session = None
|
|
312
|
+
logger.debug("Stdio transport disconnected")
|
|
313
|
+
|
|
314
|
+
# start the connection task
|
|
315
|
+
self._connect_task = asyncio.create_task(_connect_task())
|
|
316
|
+
# wait for the client to be ready before returning
|
|
317
|
+
await self._ready_event.wait()
|
|
318
|
+
|
|
319
|
+
async def disconnect(self):
|
|
320
|
+
if self._connect_task is None:
|
|
321
|
+
return
|
|
322
|
+
|
|
323
|
+
# signal the connection task to stop
|
|
324
|
+
self._stop_event.set()
|
|
325
|
+
|
|
326
|
+
# wait for the connection task to finish cleanly
|
|
327
|
+
await self._connect_task
|
|
328
|
+
|
|
329
|
+
# reset variables and events for potential future reconnects
|
|
330
|
+
self._connect_task = None
|
|
331
|
+
self._stop_event = asyncio.Event()
|
|
332
|
+
self._ready_event = asyncio.Event()
|
|
333
|
+
|
|
334
|
+
async def close(self):
|
|
335
|
+
await self.disconnect()
|
|
284
336
|
|
|
285
337
|
def __repr__(self) -> str:
|
|
286
338
|
return (
|
|
@@ -298,6 +350,7 @@ class PythonStdioTransport(StdioTransport):
|
|
|
298
350
|
env: dict[str, str] | None = None,
|
|
299
351
|
cwd: str | None = None,
|
|
300
352
|
python_cmd: str = sys.executable,
|
|
353
|
+
keep_alive: bool | None = None,
|
|
301
354
|
):
|
|
302
355
|
"""
|
|
303
356
|
Initialize a Python transport.
|
|
@@ -308,6 +361,10 @@ class PythonStdioTransport(StdioTransport):
|
|
|
308
361
|
env: Environment variables to set for the subprocess
|
|
309
362
|
cwd: Current working directory for the subprocess
|
|
310
363
|
python_cmd: Python command to use (default: "python")
|
|
364
|
+
keep_alive: Whether to keep the subprocess alive between connections.
|
|
365
|
+
Defaults to True. When True, the subprocess remains active
|
|
366
|
+
after the connection context exits, allowing reuse in
|
|
367
|
+
subsequent connections.
|
|
311
368
|
"""
|
|
312
369
|
script_path = Path(script_path).resolve()
|
|
313
370
|
if not script_path.is_file():
|
|
@@ -319,7 +376,13 @@ class PythonStdioTransport(StdioTransport):
|
|
|
319
376
|
if args:
|
|
320
377
|
full_args.extend(args)
|
|
321
378
|
|
|
322
|
-
super().__init__(
|
|
379
|
+
super().__init__(
|
|
380
|
+
command=python_cmd,
|
|
381
|
+
args=full_args,
|
|
382
|
+
env=env,
|
|
383
|
+
cwd=cwd,
|
|
384
|
+
keep_alive=keep_alive,
|
|
385
|
+
)
|
|
323
386
|
self.script_path = script_path
|
|
324
387
|
|
|
325
388
|
|
|
@@ -332,6 +395,7 @@ class FastMCPStdioTransport(StdioTransport):
|
|
|
332
395
|
args: list[str] | None = None,
|
|
333
396
|
env: dict[str, str] | None = None,
|
|
334
397
|
cwd: str | None = None,
|
|
398
|
+
keep_alive: bool | None = None,
|
|
335
399
|
):
|
|
336
400
|
script_path = Path(script_path).resolve()
|
|
337
401
|
if not script_path.is_file():
|
|
@@ -340,7 +404,11 @@ class FastMCPStdioTransport(StdioTransport):
|
|
|
340
404
|
raise ValueError(f"Not a Python script: {script_path}")
|
|
341
405
|
|
|
342
406
|
super().__init__(
|
|
343
|
-
command="fastmcp",
|
|
407
|
+
command="fastmcp",
|
|
408
|
+
args=["run", str(script_path)],
|
|
409
|
+
env=env,
|
|
410
|
+
cwd=cwd,
|
|
411
|
+
keep_alive=keep_alive,
|
|
344
412
|
)
|
|
345
413
|
self.script_path = script_path
|
|
346
414
|
|
|
@@ -355,6 +423,7 @@ class NodeStdioTransport(StdioTransport):
|
|
|
355
423
|
env: dict[str, str] | None = None,
|
|
356
424
|
cwd: str | None = None,
|
|
357
425
|
node_cmd: str = "node",
|
|
426
|
+
keep_alive: bool | None = None,
|
|
358
427
|
):
|
|
359
428
|
"""
|
|
360
429
|
Initialize a Node transport.
|
|
@@ -365,6 +434,10 @@ class NodeStdioTransport(StdioTransport):
|
|
|
365
434
|
env: Environment variables to set for the subprocess
|
|
366
435
|
cwd: Current working directory for the subprocess
|
|
367
436
|
node_cmd: Node.js command to use (default: "node")
|
|
437
|
+
keep_alive: Whether to keep the subprocess alive between connections.
|
|
438
|
+
Defaults to True. When True, the subprocess remains active
|
|
439
|
+
after the connection context exits, allowing reuse in
|
|
440
|
+
subsequent connections.
|
|
368
441
|
"""
|
|
369
442
|
script_path = Path(script_path).resolve()
|
|
370
443
|
if not script_path.is_file():
|
|
@@ -376,7 +449,9 @@ class NodeStdioTransport(StdioTransport):
|
|
|
376
449
|
if args:
|
|
377
450
|
full_args.extend(args)
|
|
378
451
|
|
|
379
|
-
super().__init__(
|
|
452
|
+
super().__init__(
|
|
453
|
+
command=node_cmd, args=full_args, env=env, cwd=cwd, keep_alive=keep_alive
|
|
454
|
+
)
|
|
380
455
|
self.script_path = script_path
|
|
381
456
|
|
|
382
457
|
|
|
@@ -392,6 +467,7 @@ class UvxStdioTransport(StdioTransport):
|
|
|
392
467
|
with_packages: list[str] | None = None,
|
|
393
468
|
from_package: str | None = None,
|
|
394
469
|
env_vars: dict[str, str] | None = None,
|
|
470
|
+
keep_alive: bool | None = None,
|
|
395
471
|
):
|
|
396
472
|
"""
|
|
397
473
|
Initialize a Uvx transport.
|
|
@@ -404,6 +480,10 @@ class UvxStdioTransport(StdioTransport):
|
|
|
404
480
|
with_packages: Additional packages to include
|
|
405
481
|
from_package: Package to install the tool from
|
|
406
482
|
env_vars: Additional environment variables
|
|
483
|
+
keep_alive: Whether to keep the subprocess alive between connections.
|
|
484
|
+
Defaults to True. When True, the subprocess remains active
|
|
485
|
+
after the connection context exits, allowing reuse in
|
|
486
|
+
subsequent connections.
|
|
407
487
|
"""
|
|
408
488
|
# Basic validation
|
|
409
489
|
if project_directory and not Path(project_directory).exists():
|
|
@@ -431,7 +511,13 @@ class UvxStdioTransport(StdioTransport):
|
|
|
431
511
|
env = os.environ.copy()
|
|
432
512
|
env.update(env_vars)
|
|
433
513
|
|
|
434
|
-
super().__init__(
|
|
514
|
+
super().__init__(
|
|
515
|
+
command="uvx",
|
|
516
|
+
args=uvx_args,
|
|
517
|
+
env=env,
|
|
518
|
+
cwd=project_directory,
|
|
519
|
+
keep_alive=keep_alive,
|
|
520
|
+
)
|
|
435
521
|
self.tool_name = tool_name
|
|
436
522
|
|
|
437
523
|
|
|
@@ -445,6 +531,7 @@ class NpxStdioTransport(StdioTransport):
|
|
|
445
531
|
project_directory: str | None = None,
|
|
446
532
|
env_vars: dict[str, str] | None = None,
|
|
447
533
|
use_package_lock: bool = True,
|
|
534
|
+
keep_alive: bool | None = None,
|
|
448
535
|
):
|
|
449
536
|
"""
|
|
450
537
|
Initialize an Npx transport.
|
|
@@ -455,6 +542,10 @@ class NpxStdioTransport(StdioTransport):
|
|
|
455
542
|
project_directory: Project directory with package.json
|
|
456
543
|
env_vars: Additional environment variables
|
|
457
544
|
use_package_lock: Whether to use package-lock.json (--prefer-offline)
|
|
545
|
+
keep_alive: Whether to keep the subprocess alive between connections.
|
|
546
|
+
Defaults to True. When True, the subprocess remains active
|
|
547
|
+
after the connection context exits, allowing reuse in
|
|
548
|
+
subsequent connections.
|
|
458
549
|
"""
|
|
459
550
|
# verify npx is installed
|
|
460
551
|
if shutil.which("npx") is None:
|
|
@@ -482,7 +573,13 @@ class NpxStdioTransport(StdioTransport):
|
|
|
482
573
|
env = os.environ.copy()
|
|
483
574
|
env.update(env_vars)
|
|
484
575
|
|
|
485
|
-
super().__init__(
|
|
576
|
+
super().__init__(
|
|
577
|
+
command="npx",
|
|
578
|
+
args=npx_args,
|
|
579
|
+
env=env,
|
|
580
|
+
cwd=project_directory,
|
|
581
|
+
keep_alive=keep_alive,
|
|
582
|
+
)
|
|
486
583
|
self.package = package
|
|
487
584
|
|
|
488
585
|
|
|
@@ -604,6 +701,44 @@ class MCPConfigTransport(ClientTransport):
|
|
|
604
701
|
return f"<MCPConfig(config='{self.config}')>"
|
|
605
702
|
|
|
606
703
|
|
|
704
|
+
@overload
|
|
705
|
+
def infer_transport(transport: ClientTransportT) -> ClientTransportT: ...
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
@overload
|
|
709
|
+
def infer_transport(transport: FastMCPServer) -> FastMCPTransport: ...
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
@overload
|
|
713
|
+
def infer_transport(transport: FastMCP1Server) -> FastMCPTransport: ...
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
@overload
|
|
717
|
+
def infer_transport(transport: MCPConfig) -> MCPConfigTransport: ...
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
@overload
|
|
721
|
+
def infer_transport(transport: dict[str, Any]) -> MCPConfigTransport: ...
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
@overload
|
|
725
|
+
def infer_transport(
|
|
726
|
+
transport: AnyUrl,
|
|
727
|
+
) -> SSETransport | StreamableHttpTransport: ...
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
@overload
|
|
731
|
+
def infer_transport(
|
|
732
|
+
transport: str,
|
|
733
|
+
) -> (
|
|
734
|
+
PythonStdioTransport | NodeStdioTransport | SSETransport | StreamableHttpTransport
|
|
735
|
+
): ...
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
@overload
|
|
739
|
+
def infer_transport(transport: Path) -> PythonStdioTransport | NodeStdioTransport: ...
|
|
740
|
+
|
|
741
|
+
|
|
607
742
|
def infer_transport(
|
|
608
743
|
transport: ClientTransport
|
|
609
744
|
| FastMCPServer
|
fastmcp/resources/template.py
CHANGED
|
@@ -148,7 +148,7 @@ class ResourceTemplate(BaseModel):
|
|
|
148
148
|
f"URI parameters {uri_params} must be a subset of the function arguments: {func_params}"
|
|
149
149
|
)
|
|
150
150
|
|
|
151
|
-
description = description or fn.__doc__
|
|
151
|
+
description = description or fn.__doc__
|
|
152
152
|
|
|
153
153
|
if not inspect.isroutine(fn):
|
|
154
154
|
fn = fn.__call__
|
fastmcp/server/dependencies.py
CHANGED
|
@@ -33,3 +33,46 @@ def get_http_request() -> Request:
|
|
|
33
33
|
if request is None:
|
|
34
34
|
raise RuntimeError("No active HTTP request found.")
|
|
35
35
|
return request
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_http_headers(include_all: bool = False) -> dict[str, str]:
|
|
39
|
+
"""
|
|
40
|
+
Extract headers from the current HTTP request if available.
|
|
41
|
+
|
|
42
|
+
Never raises an exception, even if there is no active HTTP request (in which case
|
|
43
|
+
an empty dict is returned).
|
|
44
|
+
|
|
45
|
+
By default, strips problematic headers like `content-length` that cause issues if forwarded to downstream clients.
|
|
46
|
+
If `include_all` is True, all headers are returned.
|
|
47
|
+
"""
|
|
48
|
+
if include_all:
|
|
49
|
+
exclude_headers = set()
|
|
50
|
+
else:
|
|
51
|
+
exclude_headers = {
|
|
52
|
+
"host",
|
|
53
|
+
"content-length",
|
|
54
|
+
"connection",
|
|
55
|
+
"transfer-encoding",
|
|
56
|
+
"upgrade",
|
|
57
|
+
"te",
|
|
58
|
+
"keep-alive",
|
|
59
|
+
"expect",
|
|
60
|
+
# Proxy-related headers
|
|
61
|
+
"proxy-authenticate",
|
|
62
|
+
"proxy-authorization",
|
|
63
|
+
"proxy-connection",
|
|
64
|
+
}
|
|
65
|
+
# (just in case)
|
|
66
|
+
if not all(h.lower() == h for h in exclude_headers):
|
|
67
|
+
raise ValueError("Excluded headers must be lowercase")
|
|
68
|
+
headers = {}
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
request = get_http_request()
|
|
72
|
+
for name, value in request.headers.items():
|
|
73
|
+
lower_name = name.lower()
|
|
74
|
+
if lower_name not in exclude_headers:
|
|
75
|
+
headers[lower_name] = str(value)
|
|
76
|
+
return headers
|
|
77
|
+
except RuntimeError:
|
|
78
|
+
return {}
|
fastmcp/server/openapi.py
CHANGED
|
@@ -18,7 +18,7 @@ from pydantic.networks import AnyUrl
|
|
|
18
18
|
|
|
19
19
|
from fastmcp.exceptions import ToolError
|
|
20
20
|
from fastmcp.resources import Resource, ResourceTemplate
|
|
21
|
-
from fastmcp.server.dependencies import
|
|
21
|
+
from fastmcp.server.dependencies import get_http_headers
|
|
22
22
|
from fastmcp.server.server import FastMCP
|
|
23
23
|
from fastmcp.tools.tool import Tool, _convert_to_content
|
|
24
24
|
from fastmcp.utilities import openapi
|
|
@@ -60,25 +60,6 @@ def _slugify(text: str) -> str:
|
|
|
60
60
|
return slug
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
def _get_mcp_client_headers() -> dict[str, str]:
|
|
64
|
-
"""
|
|
65
|
-
Extract headers from the current MCP client HTTP request if available.
|
|
66
|
-
|
|
67
|
-
These headers will take precedence over OpenAPI-defined headers when both are present.
|
|
68
|
-
|
|
69
|
-
Returns:
|
|
70
|
-
Dictionary of header name-value pairs (lowercased names), or empty dict if no HTTP request is active.
|
|
71
|
-
"""
|
|
72
|
-
try:
|
|
73
|
-
http_request = get_http_request()
|
|
74
|
-
return {
|
|
75
|
-
name.lower(): str(value) for name, value in http_request.headers.items()
|
|
76
|
-
}
|
|
77
|
-
except RuntimeError:
|
|
78
|
-
# No active HTTP request (e.g., STDIO transport), return empty dict
|
|
79
|
-
return {}
|
|
80
|
-
|
|
81
|
-
|
|
82
63
|
# Type definitions for the mapping functions
|
|
83
64
|
RouteMapFn = Callable[[HTTPRoute, "MCPType"], "MCPType | None"]
|
|
84
65
|
ComponentFn = Callable[
|
|
@@ -423,7 +404,7 @@ class OpenAPITool(Tool):
|
|
|
423
404
|
headers.update(openapi_headers)
|
|
424
405
|
|
|
425
406
|
# Add headers from the current MCP client HTTP request (these take precedence)
|
|
426
|
-
mcp_headers =
|
|
407
|
+
mcp_headers = get_http_headers()
|
|
427
408
|
headers.update(mcp_headers)
|
|
428
409
|
|
|
429
410
|
# Prepare request body
|
|
@@ -574,7 +555,7 @@ class OpenAPIResource(Resource):
|
|
|
574
555
|
|
|
575
556
|
# Prepare headers from MCP client request if available
|
|
576
557
|
headers = {}
|
|
577
|
-
mcp_headers =
|
|
558
|
+
mcp_headers = get_http_headers()
|
|
578
559
|
headers.update(mcp_headers)
|
|
579
560
|
|
|
580
561
|
response = await self._client.request(
|
fastmcp/server/server.py
CHANGED
|
@@ -62,7 +62,7 @@ from fastmcp.utilities.mcp_config import MCPConfig
|
|
|
62
62
|
|
|
63
63
|
if TYPE_CHECKING:
|
|
64
64
|
from fastmcp.client import Client
|
|
65
|
-
from fastmcp.client.transports import ClientTransport
|
|
65
|
+
from fastmcp.client.transports import ClientTransport, ClientTransportT
|
|
66
66
|
from fastmcp.server.openapi import ComponentFn as OpenAPIComponentFn
|
|
67
67
|
from fastmcp.server.openapi import FastMCPOpenAPI, RouteMap
|
|
68
68
|
from fastmcp.server.openapi import RouteMapFn as OpenAPIRouteMapFn
|
|
@@ -257,9 +257,15 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
257
257
|
"""Get all registered tools, indexed by registered key."""
|
|
258
258
|
if (tools := self._cache.get("tools")) is self._cache.NOT_FOUND:
|
|
259
259
|
tools: dict[str, Tool] = {}
|
|
260
|
-
for server in self._mounted_servers.
|
|
261
|
-
|
|
262
|
-
|
|
260
|
+
for prefix, server in self._mounted_servers.items():
|
|
261
|
+
try:
|
|
262
|
+
server_tools = await server.get_tools()
|
|
263
|
+
tools.update(server_tools)
|
|
264
|
+
except Exception as e:
|
|
265
|
+
logger.warning(
|
|
266
|
+
f"Failed to get tools from mounted server '{prefix}': {e}"
|
|
267
|
+
)
|
|
268
|
+
continue
|
|
263
269
|
tools.update(self._tool_manager.get_tools())
|
|
264
270
|
self._cache.set("tools", tools)
|
|
265
271
|
return tools
|
|
@@ -268,9 +274,15 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
268
274
|
"""Get all registered resources, indexed by registered key."""
|
|
269
275
|
if (resources := self._cache.get("resources")) is self._cache.NOT_FOUND:
|
|
270
276
|
resources: dict[str, Resource] = {}
|
|
271
|
-
for server in self._mounted_servers.
|
|
272
|
-
|
|
273
|
-
|
|
277
|
+
for prefix, server in self._mounted_servers.items():
|
|
278
|
+
try:
|
|
279
|
+
server_resources = await server.get_resources()
|
|
280
|
+
resources.update(server_resources)
|
|
281
|
+
except Exception as e:
|
|
282
|
+
logger.warning(
|
|
283
|
+
f"Failed to get resources from mounted server '{prefix}': {e}"
|
|
284
|
+
)
|
|
285
|
+
continue
|
|
274
286
|
resources.update(self._resource_manager.get_resources())
|
|
275
287
|
self._cache.set("resources", resources)
|
|
276
288
|
return resources
|
|
@@ -281,9 +293,16 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
281
293
|
templates := self._cache.get("resource_templates")
|
|
282
294
|
) is self._cache.NOT_FOUND:
|
|
283
295
|
templates: dict[str, ResourceTemplate] = {}
|
|
284
|
-
for server in self._mounted_servers.
|
|
285
|
-
|
|
286
|
-
|
|
296
|
+
for prefix, server in self._mounted_servers.items():
|
|
297
|
+
try:
|
|
298
|
+
server_templates = await server.get_resource_templates()
|
|
299
|
+
templates.update(server_templates)
|
|
300
|
+
except Exception as e:
|
|
301
|
+
logger.warning(
|
|
302
|
+
"Failed to get resource templates from mounted server "
|
|
303
|
+
f"'{prefix}': {e}"
|
|
304
|
+
)
|
|
305
|
+
continue
|
|
287
306
|
templates.update(self._resource_manager.get_templates())
|
|
288
307
|
self._cache.set("resource_templates", templates)
|
|
289
308
|
return templates
|
|
@@ -294,9 +313,15 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
294
313
|
"""
|
|
295
314
|
if (prompts := self._cache.get("prompts")) is self._cache.NOT_FOUND:
|
|
296
315
|
prompts: dict[str, Prompt] = {}
|
|
297
|
-
for server in self._mounted_servers.
|
|
298
|
-
|
|
299
|
-
|
|
316
|
+
for prefix, server in self._mounted_servers.items():
|
|
317
|
+
try:
|
|
318
|
+
server_prompts = await server.get_prompts()
|
|
319
|
+
prompts.update(server_prompts)
|
|
320
|
+
except Exception as e:
|
|
321
|
+
logger.warning(
|
|
322
|
+
f"Failed to get prompts from mounted server '{prefix}': {e}"
|
|
323
|
+
)
|
|
324
|
+
continue
|
|
300
325
|
prompts.update(self._prompt_manager.get_prompts())
|
|
301
326
|
self._cache.set("prompts", prompts)
|
|
302
327
|
return prompts
|
|
@@ -802,7 +827,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
802
827
|
"""
|
|
803
828
|
host = host or self.settings.host
|
|
804
829
|
port = port or self.settings.port
|
|
805
|
-
default_log_level_to_use = log_level or self.settings.log_level.lower()
|
|
830
|
+
default_log_level_to_use = (log_level or self.settings.log_level).lower()
|
|
806
831
|
|
|
807
832
|
app = self.http_app(path=path, transport=transport, middleware=middleware)
|
|
808
833
|
|
|
@@ -1263,7 +1288,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1263
1288
|
@classmethod
|
|
1264
1289
|
def as_proxy(
|
|
1265
1290
|
cls,
|
|
1266
|
-
backend: Client
|
|
1291
|
+
backend: Client[ClientTransportT]
|
|
1267
1292
|
| ClientTransport
|
|
1268
1293
|
| FastMCP[Any]
|
|
1269
1294
|
| AnyUrl
|
|
@@ -1291,7 +1316,9 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1291
1316
|
return FastMCPProxy(client=client, **settings)
|
|
1292
1317
|
|
|
1293
1318
|
@classmethod
|
|
1294
|
-
def from_client(
|
|
1319
|
+
def from_client(
|
|
1320
|
+
cls, client: Client[ClientTransportT], **settings: Any
|
|
1321
|
+
) -> FastMCPProxy:
|
|
1295
1322
|
"""
|
|
1296
1323
|
Create a FastMCP proxy server from a FastMCP client.
|
|
1297
1324
|
"""
|
fastmcp/settings.py
CHANGED
|
@@ -87,6 +87,13 @@ class Settings(BaseSettings):
|
|
|
87
87
|
),
|
|
88
88
|
] = False
|
|
89
89
|
|
|
90
|
+
client_init_timeout: Annotated[
|
|
91
|
+
float | None,
|
|
92
|
+
Field(
|
|
93
|
+
description="The timeout for the client's initialization handshake, in seconds. Set to None or 0 to disable.",
|
|
94
|
+
),
|
|
95
|
+
] = None
|
|
96
|
+
|
|
90
97
|
@model_validator(mode="after")
|
|
91
98
|
def setup_logging(self) -> Self:
|
|
92
99
|
"""Finalize the settings."""
|
fastmcp/tools/tool.py
CHANGED
|
@@ -36,7 +36,9 @@ class Tool(BaseModel):
|
|
|
36
36
|
|
|
37
37
|
fn: Callable[..., Any]
|
|
38
38
|
name: str = Field(description="Name of the tool")
|
|
39
|
-
description: str = Field(
|
|
39
|
+
description: str | None = Field(
|
|
40
|
+
default=None, description="Description of what the tool does"
|
|
41
|
+
)
|
|
40
42
|
parameters: dict[str, Any] = Field(description="JSON schema for tool parameters")
|
|
41
43
|
tags: Annotated[set[str], BeforeValidator(_convert_set_defaults)] = Field(
|
|
42
44
|
default_factory=set, description="Tags for the tool"
|
|
@@ -74,7 +76,7 @@ class Tool(BaseModel):
|
|
|
74
76
|
if func_name == "<lambda>":
|
|
75
77
|
raise ValueError("You must provide a name for lambda functions")
|
|
76
78
|
|
|
77
|
-
func_doc = description or fn.__doc__
|
|
79
|
+
func_doc = description or fn.__doc__
|
|
78
80
|
|
|
79
81
|
# if the fn is a callable class, we need to get the __call__ method from here out
|
|
80
82
|
if not inspect.isroutine(fn):
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
fastmcp/__init__.py,sha256=yTAqLZORsPqbr7AE0ayw6zIYBeMlxQlI-3HE2WqbvHk,435
|
|
2
2
|
fastmcp/exceptions.py,sha256=YvaKqOT3w0boXF9ylIoaSIzW9XiQ1qLFG1LZq6B60H8,680
|
|
3
3
|
fastmcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
fastmcp/settings.py,sha256=
|
|
4
|
+
fastmcp/settings.py,sha256=aVOLK-QfhGr_0mPLVzBmeUxyS9_w8gSHAjMmRqEoEow,5577
|
|
5
5
|
fastmcp/cli/__init__.py,sha256=Ii284TNoG5lxTP40ETMGhHEq3lQZWxu9m9JuU57kUpQ,87
|
|
6
6
|
fastmcp/cli/claude.py,sha256=IAlcZ4qZKBBj09jZUMEx7EANZE_IR3vcu7zOBJmMOuU,4567
|
|
7
|
-
fastmcp/cli/cli.py,sha256=
|
|
7
|
+
fastmcp/cli/cli.py,sha256=CQxpRTXgnQQynGJLEV5g1FnLMaiWoiUgefnMZ7VxS4o,12367
|
|
8
8
|
fastmcp/cli/run.py,sha256=o7Ge6JZKXYwlY2vYdMNoVX8agBchAaeU_73iPndojIM,5351
|
|
9
9
|
fastmcp/client/__init__.py,sha256=Ri8GFHolIKOZnXaMzIc3VpkLcEqAmOoYGCKgmSk6NnE,550
|
|
10
10
|
fastmcp/client/base.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
fastmcp/client/client.py,sha256=
|
|
11
|
+
fastmcp/client/client.py,sha256=JxFC_YUOrDPZwiDbD0JgCHQsOy1F8Rup7hnege96OIc,23021
|
|
12
12
|
fastmcp/client/logging.py,sha256=hOPRailZUp89RUck6V4HPaWVZinVrNY8HD4hD0dd-fE,822
|
|
13
13
|
fastmcp/client/progress.py,sha256=WjLLDbUKMsx8DK-fqO7AGsXb83ak-6BMrLvzzznGmcI,1043
|
|
14
14
|
fastmcp/client/roots.py,sha256=IxI_bHwHTmg6c2H-s1av1ZgrRnNDieHtYwdGFbzXT5c,2471
|
|
15
15
|
fastmcp/client/sampling.py,sha256=UlDHxnd6k_HoU8RA3ob0g8-e6haJBc9u27N_v291QoI,1698
|
|
16
|
-
fastmcp/client/transports.py,sha256=
|
|
16
|
+
fastmcp/client/transports.py,sha256=G1MQ7bHkmQbbni4ZWwMJs-opbPdqpyKYdg7TYkkjLbU,29986
|
|
17
17
|
fastmcp/contrib/README.md,sha256=rKknYSI1T192UvSszqwwDlQ2eYQpxywrNTLoj177SYU,878
|
|
18
18
|
fastmcp/contrib/bulk_tool_caller/README.md,sha256=5aUUY1TSFKtz1pvTLSDqkUCkGkuqMfMZNsLeaNqEgAc,1960
|
|
19
19
|
fastmcp/contrib/bulk_tool_caller/__init__.py,sha256=xvGSSaUXTQrc31erBoi1Gh7BikgOliETDiYVTP3rLxY,75
|
|
@@ -31,17 +31,17 @@ fastmcp/prompts/prompt_manager.py,sha256=qptEhZHMwc8XxQd5lTQg8iIb5MiTZVsNaux_XLv
|
|
|
31
31
|
fastmcp/resources/__init__.py,sha256=t0x1j8lc74rjUKtXe9H5Gs4fpQt82K4NgBK6Y7A0xTg,467
|
|
32
32
|
fastmcp/resources/resource.py,sha256=Rx1My_fi1f-oqnQ9R_v7ejopAk4BJDfbB75-s4d31dM,2492
|
|
33
33
|
fastmcp/resources/resource_manager.py,sha256=nsgCR3lo9t4Q0QR6txPfAas2upqIb8P8ZlqWAfV9Qc0,11344
|
|
34
|
-
fastmcp/resources/template.py,sha256=
|
|
34
|
+
fastmcp/resources/template.py,sha256=u0_-yNMmZfnl5DqtSRndGbGBrm7JgbzBU8IUd0hrEWE,7523
|
|
35
35
|
fastmcp/resources/types.py,sha256=5fUFvzRlekNjtfihtq8S-fT0alKoNfclzrugqeM5JRE,6366
|
|
36
36
|
fastmcp/server/__init__.py,sha256=bMD4aQD4yJqLz7-mudoNsyeV8UgQfRAg3PRwPvwTEds,119
|
|
37
37
|
fastmcp/server/context.py,sha256=yN1e0LsnCl7cEpr9WlbvFhSf8oE56kKb-20m8h2SsBY,10171
|
|
38
|
-
fastmcp/server/dependencies.py,sha256=
|
|
38
|
+
fastmcp/server/dependencies.py,sha256=4kdJLvWn-lMU7uPIJ-Np1RHBwvkbU7Dc31ZdsGTA9_I,2093
|
|
39
39
|
fastmcp/server/http.py,sha256=wZWUrLvKITlvkxQoggJ9RyvynCUMEJqqMMsvX7Hmb9o,12807
|
|
40
|
-
fastmcp/server/openapi.py,sha256=
|
|
40
|
+
fastmcp/server/openapi.py,sha256=9qXSuEl671sT1F7nSM3SiD5KANGqHUhiL1BBdCnuCcU,39153
|
|
41
41
|
fastmcp/server/proxy.py,sha256=mt3eM6TQWfnZD5XehmTXisskZ4CBbsWyjRPjprlTjBY,9653
|
|
42
|
-
fastmcp/server/server.py,sha256=
|
|
42
|
+
fastmcp/server/server.py,sha256=TGC8ysEtA-fpuFzaqfvCstfWYJks5ClrCQl6zyYfLZM,58237
|
|
43
43
|
fastmcp/tools/__init__.py,sha256=ocw-SFTtN6vQ8fgnlF8iNAOflRmh79xS1xdO0Bc3QPE,96
|
|
44
|
-
fastmcp/tools/tool.py,sha256=
|
|
44
|
+
fastmcp/tools/tool.py,sha256=Qx1sQ-D_llZIETPea8KoRn_vOjYgyriDqi0hpd_pRP8,7832
|
|
45
45
|
fastmcp/tools/tool_manager.py,sha256=785vKYlJ9B2B5ThXFhuXYB4VNY4h0283-_AAdy1hEfk,4430
|
|
46
46
|
fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
|
|
47
47
|
fastmcp/utilities/cache.py,sha256=aV3oZ-ZhMgLSM9iAotlUlEy5jFvGXrVo0Y5Bj4PBtqY,707
|
|
@@ -53,8 +53,8 @@ fastmcp/utilities/mcp_config.py,sha256=_wY3peaFDEgyOBkJ_Tb8sETk3mtdwtw1053q7ry0z
|
|
|
53
53
|
fastmcp/utilities/openapi.py,sha256=QQos4vP59HQ8vPDTKftWOIVv_zmW30mNxYSXVU7JUbY,38441
|
|
54
54
|
fastmcp/utilities/tests.py,sha256=teyHcl3j7WGfYJ6m42VuQYB_IVpGvPdFqIpC-UxsN78,3369
|
|
55
55
|
fastmcp/utilities/types.py,sha256=6CcqAQ1QqCO2HGSFlPS6FO5JRWnacjCcO2-EhyEnZV0,4400
|
|
56
|
-
fastmcp-2.5.
|
|
57
|
-
fastmcp-2.5.
|
|
58
|
-
fastmcp-2.5.
|
|
59
|
-
fastmcp-2.5.
|
|
60
|
-
fastmcp-2.5.
|
|
56
|
+
fastmcp-2.5.2.dist-info/METADATA,sha256=EU1bb1c0HO8jLeLAlTo-UCPA619yiw1kiAiFNA8bMMk,16510
|
|
57
|
+
fastmcp-2.5.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
58
|
+
fastmcp-2.5.2.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
|
|
59
|
+
fastmcp-2.5.2.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
60
|
+
fastmcp-2.5.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|