fastmcp 2.3.0__py3-none-any.whl → 2.3.1__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/low_level/README.md +1 -0
- fastmcp/low_level/__init__.py +0 -0
- fastmcp/low_level/sse_server_transport.py +104 -0
- fastmcp/server/http.py +19 -15
- fastmcp/server/proxy.py +1 -1
- fastmcp/server/server.py +1 -1
- fastmcp/tools/tool.py +1 -1
- {fastmcp-2.3.0.dist-info → fastmcp-2.3.1.dist-info}/METADATA +41 -45
- {fastmcp-2.3.0.dist-info → fastmcp-2.3.1.dist-info}/RECORD +12 -9
- {fastmcp-2.3.0.dist-info → fastmcp-2.3.1.dist-info}/WHEEL +0 -0
- {fastmcp-2.3.0.dist-info → fastmcp-2.3.1.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.3.0.dist-info → fastmcp-2.3.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Patched low-level objects. When possible, we prefer the official SDK, but we patch bugs here if necessary.
|
|
File without changes
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from contextlib import asynccontextmanager
|
|
3
|
+
from typing import Any
|
|
4
|
+
from urllib.parse import quote
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
|
|
7
|
+
import anyio
|
|
8
|
+
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
|
9
|
+
from mcp.server.sse import SseServerTransport as LowLevelSSEServerTransport
|
|
10
|
+
from mcp.shared.message import SessionMessage
|
|
11
|
+
from sse_starlette import EventSourceResponse
|
|
12
|
+
from starlette.types import Receive, Scope, Send
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SseServerTransport(LowLevelSSEServerTransport):
|
|
18
|
+
"""
|
|
19
|
+
Patched SSE server transport
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@asynccontextmanager
|
|
23
|
+
async def connect_sse(self, scope: Scope, receive: Receive, send: Send):
|
|
24
|
+
"""
|
|
25
|
+
See https://github.com/modelcontextprotocol/python-sdk/pull/659/
|
|
26
|
+
"""
|
|
27
|
+
if scope["type"] != "http":
|
|
28
|
+
logger.error("connect_sse received non-HTTP request")
|
|
29
|
+
raise ValueError("connect_sse can only handle HTTP requests")
|
|
30
|
+
|
|
31
|
+
logger.debug("Setting up SSE connection")
|
|
32
|
+
read_stream: MemoryObjectReceiveStream[SessionMessage | Exception]
|
|
33
|
+
read_stream_writer: MemoryObjectSendStream[SessionMessage | Exception]
|
|
34
|
+
|
|
35
|
+
write_stream: MemoryObjectSendStream[SessionMessage]
|
|
36
|
+
write_stream_reader: MemoryObjectReceiveStream[SessionMessage]
|
|
37
|
+
|
|
38
|
+
read_stream_writer, read_stream = anyio.create_memory_object_stream(0)
|
|
39
|
+
write_stream, write_stream_reader = anyio.create_memory_object_stream(0)
|
|
40
|
+
|
|
41
|
+
session_id = uuid4()
|
|
42
|
+
self._read_stream_writers[session_id] = read_stream_writer
|
|
43
|
+
logger.debug(f"Created new session with ID: {session_id}")
|
|
44
|
+
|
|
45
|
+
# Determine the full path for the message endpoint to be sent to the client.
|
|
46
|
+
# scope['root_path'] is the prefix where the current Starlette app
|
|
47
|
+
# instance is mounted.
|
|
48
|
+
# e.g., "" if top-level, or "/api_prefix" if mounted under "/api_prefix".
|
|
49
|
+
root_path = scope.get("root_path", "")
|
|
50
|
+
|
|
51
|
+
# self._endpoint is the path *within* this app, e.g., "/messages".
|
|
52
|
+
# Concatenating them gives the full absolute path from the server root.
|
|
53
|
+
# e.g., "" + "/messages" -> "/messages"
|
|
54
|
+
# e.g., "/api_prefix" + "/messages" -> "/api_prefix/messages"
|
|
55
|
+
full_message_path_for_client = root_path.rstrip("/") + self._endpoint
|
|
56
|
+
|
|
57
|
+
# This is the URI (path + query) the client will use to POST messages.
|
|
58
|
+
client_post_uri_data = (
|
|
59
|
+
f"{quote(full_message_path_for_client)}?session_id={session_id.hex}"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
sse_stream_writer, sse_stream_reader = anyio.create_memory_object_stream[
|
|
63
|
+
dict[str, Any]
|
|
64
|
+
](0)
|
|
65
|
+
|
|
66
|
+
async def sse_writer():
|
|
67
|
+
logger.debug("Starting SSE writer")
|
|
68
|
+
async with sse_stream_writer, write_stream_reader:
|
|
69
|
+
await sse_stream_writer.send(
|
|
70
|
+
{"event": "endpoint", "data": client_post_uri_data}
|
|
71
|
+
)
|
|
72
|
+
logger.debug(f"Sent endpoint event: {client_post_uri_data}")
|
|
73
|
+
|
|
74
|
+
async for session_message in write_stream_reader:
|
|
75
|
+
logger.debug(f"Sending message via SSE: {session_message}")
|
|
76
|
+
await sse_stream_writer.send(
|
|
77
|
+
{
|
|
78
|
+
"event": "message",
|
|
79
|
+
"data": session_message.message.model_dump_json(
|
|
80
|
+
by_alias=True, exclude_none=True
|
|
81
|
+
),
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
async with anyio.create_task_group() as tg:
|
|
86
|
+
|
|
87
|
+
async def response_wrapper(scope: Scope, receive: Receive, send: Send):
|
|
88
|
+
"""
|
|
89
|
+
The EventSourceResponse returning signals a client close / disconnect.
|
|
90
|
+
In this case we close our side of the streams to signal the client that
|
|
91
|
+
the connection has been closed.
|
|
92
|
+
"""
|
|
93
|
+
await EventSourceResponse(
|
|
94
|
+
content=sse_stream_reader, data_sender_callable=sse_writer
|
|
95
|
+
)(scope, receive, send)
|
|
96
|
+
await read_stream_writer.aclose()
|
|
97
|
+
await write_stream_reader.aclose()
|
|
98
|
+
logging.debug(f"Client session disconnected {session_id}")
|
|
99
|
+
|
|
100
|
+
logger.debug("Starting SSE response task")
|
|
101
|
+
tg.start_soon(response_wrapper, scope, receive, send)
|
|
102
|
+
|
|
103
|
+
logger.debug("Yielding read and write streams")
|
|
104
|
+
yield (read_stream, write_stream)
|
fastmcp/server/http.py
CHANGED
|
@@ -13,7 +13,6 @@ from mcp.server.auth.middleware.bearer_auth import (
|
|
|
13
13
|
from mcp.server.auth.provider import OAuthAuthorizationServerProvider
|
|
14
14
|
from mcp.server.auth.routes import create_auth_routes
|
|
15
15
|
from mcp.server.auth.settings import AuthSettings
|
|
16
|
-
from mcp.server.sse import SseServerTransport
|
|
17
16
|
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
|
|
18
17
|
from starlette.applications import Starlette
|
|
19
18
|
from starlette.middleware import Middleware
|
|
@@ -23,6 +22,7 @@ from starlette.responses import Response
|
|
|
23
22
|
from starlette.routing import Mount, Route
|
|
24
23
|
from starlette.types import Receive, Scope, Send
|
|
25
24
|
|
|
25
|
+
from fastmcp.low_level.sse_server_transport import SseServerTransport
|
|
26
26
|
from fastmcp.utilities.logging import get_logger
|
|
27
27
|
|
|
28
28
|
if TYPE_CHECKING:
|
|
@@ -110,7 +110,7 @@ def setup_auth_middleware_and_routes(
|
|
|
110
110
|
def create_base_app(
|
|
111
111
|
routes: list[Route | Mount],
|
|
112
112
|
middleware: list[Middleware],
|
|
113
|
-
debug: bool,
|
|
113
|
+
debug: bool = False,
|
|
114
114
|
lifespan: Callable | None = None,
|
|
115
115
|
) -> Starlette:
|
|
116
116
|
"""Create a base Starlette app with common middleware and routes.
|
|
@@ -127,17 +127,12 @@ def create_base_app(
|
|
|
127
127
|
# Always add RequestContextMiddleware as the outermost middleware
|
|
128
128
|
middleware.append(Middleware(RequestContextMiddleware))
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if lifespan:
|
|
138
|
-
app_kwargs["lifespan"] = lifespan
|
|
139
|
-
|
|
140
|
-
return Starlette(**app_kwargs)
|
|
130
|
+
return Starlette(
|
|
131
|
+
routes=routes,
|
|
132
|
+
middleware=middleware,
|
|
133
|
+
debug=debug,
|
|
134
|
+
lifespan=lifespan,
|
|
135
|
+
)
|
|
141
136
|
|
|
142
137
|
|
|
143
138
|
def create_sse_app(
|
|
@@ -224,7 +219,11 @@ def create_sse_app(
|
|
|
224
219
|
routes.extend(cast(list[Route | Mount], additional_routes))
|
|
225
220
|
|
|
226
221
|
# Create and return the app
|
|
227
|
-
return create_base_app(
|
|
222
|
+
return create_base_app(
|
|
223
|
+
routes=routes,
|
|
224
|
+
middleware=middleware,
|
|
225
|
+
debug=debug,
|
|
226
|
+
)
|
|
228
227
|
|
|
229
228
|
|
|
230
229
|
def create_streamable_http_app(
|
|
@@ -305,4 +304,9 @@ def create_streamable_http_app(
|
|
|
305
304
|
yield
|
|
306
305
|
|
|
307
306
|
# Create and return the app with lifespan
|
|
308
|
-
return create_base_app(
|
|
307
|
+
return create_base_app(
|
|
308
|
+
routes=routes,
|
|
309
|
+
middleware=middleware,
|
|
310
|
+
debug=debug,
|
|
311
|
+
lifespan=lifespan,
|
|
312
|
+
)
|
fastmcp/server/proxy.py
CHANGED
|
@@ -124,7 +124,7 @@ class ProxyTemplate(ResourceTemplate):
|
|
|
124
124
|
params: dict[str, Any],
|
|
125
125
|
context: Context | None = None,
|
|
126
126
|
) -> ProxyResource:
|
|
127
|
-
#
|
|
127
|
+
# don't use the provided uri, because it may not be the same as the
|
|
128
128
|
# uri_template on the remote server.
|
|
129
129
|
# quote params to ensure they are valid for the uri_template
|
|
130
130
|
parameterized_uri = self.uri_template.format(
|
fastmcp/server/server.py
CHANGED
|
@@ -892,7 +892,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
892
892
|
future changes to the imported server will not be reflected in the
|
|
893
893
|
importing server. Server-level configurations and lifespans are not imported.
|
|
894
894
|
|
|
895
|
-
When
|
|
895
|
+
When a server is mounted: - The tools are imported with prefixed names
|
|
896
896
|
using the tool_separator
|
|
897
897
|
Example: If server has a tool named "get_weather", it will be
|
|
898
898
|
available as "weatherget_weather"
|
fastmcp/tools/tool.py
CHANGED
|
@@ -192,7 +192,7 @@ def _convert_to_content(
|
|
|
192
192
|
other_content.append(item)
|
|
193
193
|
if other_content:
|
|
194
194
|
other_content = _convert_to_content(
|
|
195
|
-
other_content, _process_as_single_item=True
|
|
195
|
+
other_content, serializer=serializer, _process_as_single_item=True
|
|
196
196
|
)
|
|
197
197
|
|
|
198
198
|
return other_content + mcp_types
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmcp
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.1
|
|
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
|
|
@@ -76,7 +76,13 @@ fastmcp run server.py
|
|
|
76
76
|
|
|
77
77
|
### 📚 Documentation
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
FastMCP's complete documentation is available at **[gofastmcp.com](https://gofastmcp.com)**, including detailed guides, API references, and advanced patterns. This readme provides only a high-level overview.
|
|
80
|
+
|
|
81
|
+
Documentation is also available in [llms.txt format](https://llmstxt.org/), which is a simple markdown standard that LLMs can consume easily.
|
|
82
|
+
|
|
83
|
+
There are two ways to access the LLM-friendly documentation:
|
|
84
|
+
- [`llms.txt`](https://gofastmcp.com/llms.txt) is essentially a sitemap, listing all the pages in the documentation.
|
|
85
|
+
- [`llms-full.txt`](https://gofastmcp.com/llms-full.txt) contains the entire documentation. Note this may exceed the context window of your LLM.
|
|
80
86
|
|
|
81
87
|
---
|
|
82
88
|
|
|
@@ -302,50 +308,40 @@ Learn more: [**OpenAPI Integration**](https://gofastmcp.com/patterns/openapi) |
|
|
|
302
308
|
|
|
303
309
|
## Running Your Server
|
|
304
310
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
#
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
uv run python server.py
|
|
338
|
-
```
|
|
339
|
-
4. **Claude Desktop Integration (`fastmcp install`)**: The easiest way to make your server persistently available in the Claude Desktop app. It handles creating an isolated environment using `uv`.
|
|
340
|
-
```bash
|
|
341
|
-
fastmcp install server.py --name "My Analysis Tool"
|
|
342
|
-
# Optionally add dependencies and environment variables
|
|
343
|
-
fastmcp install server.py --with requests -v API_KEY=123 -f .env
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
See the [**Server Documentation**](https://gofastmcp.com/servers/fastmcp#running-the-server) for more details on transports and configuration.
|
|
311
|
+
The main way to run a FastMCP server is by calling the `run()` method on your server instance:
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
# server.py
|
|
315
|
+
from fastmcp import FastMCP
|
|
316
|
+
|
|
317
|
+
mcp = FastMCP("Demo 🚀")
|
|
318
|
+
|
|
319
|
+
@mcp.tool()
|
|
320
|
+
def hello(name: str) -> str:
|
|
321
|
+
return f"Hello, {name}!"
|
|
322
|
+
|
|
323
|
+
if __name__ == "__main__":
|
|
324
|
+
mcp.run() # Default: uses STDIO transport
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
FastMCP supports three transport protocols:
|
|
328
|
+
|
|
329
|
+
**STDIO (Default)**: Best for local tools and command-line scripts.
|
|
330
|
+
```python
|
|
331
|
+
mcp.run(transport="stdio") # Default, so transport argument is optional
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Streamable HTTP**: Recommended for web deployments.
|
|
335
|
+
```python
|
|
336
|
+
mcp.run(transport="streamable-http", host="127.0.0.1", port=8000, path="/mcp")
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**SSE**: For compatibility with existing SSE clients.
|
|
340
|
+
```python
|
|
341
|
+
mcp.run(transport="sse", host="127.0.0.1", port=8000)
|
|
342
|
+
```
|
|
348
343
|
|
|
344
|
+
See the [**Running Server Documentation**](https://gofastmcp.com/deployment/running-server) for more details.
|
|
349
345
|
|
|
350
346
|
## Contributing
|
|
351
347
|
|
|
@@ -21,6 +21,9 @@ fastmcp/contrib/mcp_mixin/README.md,sha256=9DDTJXWkA3yv1fp5V58gofmARPQ2xWDhblYGv
|
|
|
21
21
|
fastmcp/contrib/mcp_mixin/__init__.py,sha256=aw9IQ1ssNjCgws4ZNt8bkdpossAAGVAwwjBpMp9O5ZQ,153
|
|
22
22
|
fastmcp/contrib/mcp_mixin/example.py,sha256=GnunkXmtG5hLLTUsM8aW5ZURU52Z8vI4tNLl-fK7Dg0,1228
|
|
23
23
|
fastmcp/contrib/mcp_mixin/mcp_mixin.py,sha256=cfIRbnSxsVzglTD-auyTE0izVQeHP7Oz18qzYoBZJgg,7899
|
|
24
|
+
fastmcp/low_level/README.md,sha256=IRvElvOOc_RLLsqbUm7e6VOEwrKHPJeox0pV7JVKHWw,106
|
|
25
|
+
fastmcp/low_level/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
fastmcp/low_level/sse_server_transport.py,sha256=pUG3AL4Wjf9LgH9fj1l3emGEjFDFDhmKcDfgiiFJcuQ,4448
|
|
24
27
|
fastmcp/prompts/__init__.py,sha256=An8uMBUh9Hrb7qqcn_5_Hent7IOeSh7EA2IUVsIrtHc,179
|
|
25
28
|
fastmcp/prompts/prompt.py,sha256=psc-YiBRttbjETINaP9P9QV328yk96mDBsZgjOHVyKM,7777
|
|
26
29
|
fastmcp/prompts/prompt_manager.py,sha256=9VcioLE-AoUKe1e9SynNQME9SvWy0q1QAvO1ewIWVmI,3126
|
|
@@ -32,12 +35,12 @@ fastmcp/resources/types.py,sha256=QPDeka_cM1hmvwW4FeFhqy6BEEi4MlwtpvhWUVWh5Fc,64
|
|
|
32
35
|
fastmcp/server/__init__.py,sha256=bMD4aQD4yJqLz7-mudoNsyeV8UgQfRAg3PRwPvwTEds,119
|
|
33
36
|
fastmcp/server/context.py,sha256=ykitQygA7zT5prbFTLCuYlnAzuljf_9ErUT0FYBPv3E,8135
|
|
34
37
|
fastmcp/server/dependencies.py,sha256=1utkxFsV37HZcWBwI69JyngVN2ppGO_PEgxUlUHHy_Q,742
|
|
35
|
-
fastmcp/server/http.py,sha256=
|
|
38
|
+
fastmcp/server/http.py,sha256=esmeQZJCOxbvYBwF9gTzniymnL93n09hbjgRpmv-9bw,10076
|
|
36
39
|
fastmcp/server/openapi.py,sha256=0nANnwHJ5VZInNyo2f9ErmO0K3igMv6bwyxf3G-BSls,23473
|
|
37
|
-
fastmcp/server/proxy.py,sha256=
|
|
38
|
-
fastmcp/server/server.py,sha256=
|
|
40
|
+
fastmcp/server/proxy.py,sha256=LDTjzc_iQj8AldsfMU37flGRAfJic1w6qsherfyHPAA,9622
|
|
41
|
+
fastmcp/server/server.py,sha256=2pwYzjHFlg42Mq8qfjX5VttP1OjVJYNOCbW5H7R0hjk,40826
|
|
39
42
|
fastmcp/tools/__init__.py,sha256=ocw-SFTtN6vQ8fgnlF8iNAOflRmh79xS1xdO0Bc3QPE,96
|
|
40
|
-
fastmcp/tools/tool.py,sha256=
|
|
43
|
+
fastmcp/tools/tool.py,sha256=HGcHjMecqAeN6eI-IfE_2UBcd1KpTV-VOTFLx9tlbpU,7809
|
|
41
44
|
fastmcp/tools/tool_manager.py,sha256=p2nHyLFgz28tbsLpWOurkbWRU2Z34_HcDohjrvwjI0E,3369
|
|
42
45
|
fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
|
|
43
46
|
fastmcp/utilities/cache.py,sha256=aV3oZ-ZhMgLSM9iAotlUlEy5jFvGXrVo0Y5Bj4PBtqY,707
|
|
@@ -47,8 +50,8 @@ fastmcp/utilities/logging.py,sha256=zav8pnFxG_fvGJHUV2XpobmT9WVrmv1mlQBSCz-CPx4,
|
|
|
47
50
|
fastmcp/utilities/openapi.py,sha256=Er3G1MyFwiWVxZXicXtD2j-BvttHEDTi1dgkq1KiBQc,51073
|
|
48
51
|
fastmcp/utilities/tests.py,sha256=uUV-8CkhCe5zZJkxhgJXnxrjJ3Yq7cCMZN8xWKGuqdY,3181
|
|
49
52
|
fastmcp/utilities/types.py,sha256=6CcqAQ1QqCO2HGSFlPS6FO5JRWnacjCcO2-EhyEnZV0,4400
|
|
50
|
-
fastmcp-2.3.
|
|
51
|
-
fastmcp-2.3.
|
|
52
|
-
fastmcp-2.3.
|
|
53
|
-
fastmcp-2.3.
|
|
54
|
-
fastmcp-2.3.
|
|
53
|
+
fastmcp-2.3.1.dist-info/METADATA,sha256=EM2ah3-Gf3_vtum6tbRL4627SfRuugJ0xJdbsNa8EJE,15746
|
|
54
|
+
fastmcp-2.3.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
55
|
+
fastmcp-2.3.1.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
|
|
56
|
+
fastmcp-2.3.1.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
57
|
+
fastmcp-2.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|