fastmcp 2.3.4__py3-none-any.whl → 2.4.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.
@@ -71,7 +71,7 @@ def _run_server(mcp_server: FastMCP, transport: Literal["sse"], port: int) -> No
71
71
 
72
72
  @contextmanager
73
73
  def run_server_in_process(
74
- server_fn: Callable[[str, int], None], *args
74
+ server_fn: Callable[..., None], *args
75
75
  ) -> Generator[str, None, None]:
76
76
  """
77
77
  Context manager that runs a Starlette app in a separate process and returns the
@@ -109,7 +109,11 @@ def run_server_in_process(
109
109
 
110
110
  yield f"http://{host}:{port}"
111
111
 
112
- proc.kill()
113
- proc.join(timeout=2)
112
+ proc.terminate()
113
+ proc.join(timeout=5)
114
114
  if proc.is_alive():
115
- raise RuntimeError("Server process failed to terminate")
115
+ # If it's still alive, then force kill it
116
+ proc.kill()
117
+ proc.join(timeout=2)
118
+ if proc.is_alive():
119
+ raise RuntimeError("Server process failed to terminate even after kill")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastmcp
3
- Version: 2.3.4
3
+ Version: 2.4.0
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
@@ -19,7 +19,7 @@ Classifier: Typing :: Typed
19
19
  Requires-Python: >=3.10
20
20
  Requires-Dist: exceptiongroup>=1.2.2
21
21
  Requires-Dist: httpx>=0.28.1
22
- Requires-Dist: mcp<2.0.0,>=1.8.1
22
+ Requires-Dist: mcp<2.0.0,>=1.9.0
23
23
  Requires-Dist: openapi-pydantic>=0.5.1
24
24
  Requires-Dist: python-dotenv>=1.1.0
25
25
  Requires-Dist: rich>=13.9.4
@@ -282,6 +282,29 @@ async def main():
282
282
  # ... use the client
283
283
  ```
284
284
 
285
+ FastMCP also supports connecting to multiple servers through a single unified client using the standard MCP configuration format:
286
+
287
+ ```python
288
+ from fastmcp import Client
289
+
290
+ # Standard MCP configuration with multiple servers
291
+ config = {
292
+ "mcpServers": {
293
+ "weather": {"url": "https://weather-api.example.com/mcp"},
294
+ "assistant": {"command": "python", "args": ["./assistant_server.py"]}
295
+ }
296
+ }
297
+
298
+ # Create a client that connects to all servers
299
+ client = Client(config)
300
+
301
+ async def main():
302
+ async with client:
303
+ # Access tools and resources with server prefixes
304
+ forecast = await client.call_tool("weather_get_forecast", {"city": "London"})
305
+ answer = await client.call_tool("assistant_answer_question", {"query": "What is MCP?"})
306
+ ```
307
+
285
308
  Learn more in the [**Client Documentation**](https://gofastmcp.com/clients/client) and [**Transports Documentation**](https://gofastmcp.com/clients/transports).
286
309
 
287
310
  ## Advanced Features
@@ -290,7 +313,7 @@ FastMCP introduces powerful ways to structure and deploy your MCP applications.
290
313
 
291
314
  ### Proxy Servers
292
315
 
293
- Create a FastMCP server that acts as an intermediary for another local or remote MCP server using `FastMCP.from_client()`. This is especially useful for bridging transports (e.g., remote SSE to local Stdio) or adding a layer of logic to a server you don't control.
316
+ Create a FastMCP server that acts as an intermediary for another local or remote MCP server using `FastMCP.as_proxy()`. This is especially useful for bridging transports (e.g., remote SSE to local Stdio) or adding a layer of logic to a server you don't control.
294
317
 
295
318
  Learn more in the [**Proxying Documentation**](https://gofastmcp.com/patterns/proxy).
296
319
 
@@ -1,17 +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=Slo7sRSbXblgmTQcRSee6ns6CsW2yW-TvBY--kAhJqg,3856
4
+ fastmcp/settings.py,sha256=fOiB_zFZqqVxp3hlTTsndnlUZV3fmQUcqrSRtC4Zr9Q,4371
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=Tb-WiIXFZiq4nqlZ6LMXN2iYY30clC4Om_gP89HbJcE,15641
7
+ fastmcp/cli/cli.py,sha256=eRZ4tpne7dj_rhjREwiNRN5i9A1T8-ptxg1lYaHfS5o,12401
8
+ fastmcp/cli/run.py,sha256=o7Ge6JZKXYwlY2vYdMNoVX8agBchAaeU_73iPndojIM,5351
8
9
  fastmcp/client/__init__.py,sha256=Ri8GFHolIKOZnXaMzIc3VpkLcEqAmOoYGCKgmSk6NnE,550
9
10
  fastmcp/client/base.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- fastmcp/client/client.py,sha256=EOS6_2zd9zWxhCN_cY6cAiqGmCSWMX1t3g6PxemjTtw,17982
11
- fastmcp/client/logging.py,sha256=Q8jYcZj4KA15Yiz3RP8tBXj8sd9IxL3VThF_Y0O4Upc,356
11
+ fastmcp/client/client.py,sha256=Pvy1Wkdo4lRqKppvWL9LW7OYL2tEBdPF1v8TPzYTcYQ,20080
12
+ fastmcp/client/logging.py,sha256=hOPRailZUp89RUck6V4HPaWVZinVrNY8HD4hD0dd-fE,822
13
+ fastmcp/client/progress.py,sha256=WjLLDbUKMsx8DK-fqO7AGsXb83ak-6BMrLvzzznGmcI,1043
12
14
  fastmcp/client/roots.py,sha256=IxI_bHwHTmg6c2H-s1av1ZgrRnNDieHtYwdGFbzXT5c,2471
13
15
  fastmcp/client/sampling.py,sha256=UlDHxnd6k_HoU8RA3ob0g8-e6haJBc9u27N_v291QoI,1698
14
- fastmcp/client/transports.py,sha256=30SubI-fgrqVknyPlySVJ7sGkOMAjm8CP-Owrb8gu58,19704
16
+ fastmcp/client/transports.py,sha256=0GTu_W9vnT5_JPQ49gmUZsRKOP19RzCKUSiIE0F4Pek,23031
15
17
  fastmcp/contrib/README.md,sha256=rKknYSI1T192UvSszqwwDlQ2eYQpxywrNTLoj177SYU,878
16
18
  fastmcp/contrib/bulk_tool_caller/README.md,sha256=5aUUY1TSFKtz1pvTLSDqkUCkGkuqMfMZNsLeaNqEgAc,1960
17
19
  fastmcp/contrib/bulk_tool_caller/__init__.py,sha256=xvGSSaUXTQrc31erBoi1Gh7BikgOliETDiYVTP3rLxY,75
@@ -23,7 +25,6 @@ fastmcp/contrib/mcp_mixin/example.py,sha256=GnunkXmtG5hLLTUsM8aW5ZURU52Z8vI4tNLl
23
25
  fastmcp/contrib/mcp_mixin/mcp_mixin.py,sha256=cfIRbnSxsVzglTD-auyTE0izVQeHP7Oz18qzYoBZJgg,7899
24
26
  fastmcp/low_level/README.md,sha256=IRvElvOOc_RLLsqbUm7e6VOEwrKHPJeox0pV7JVKHWw,106
25
27
  fastmcp/low_level/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- fastmcp/low_level/sse_server_transport.py,sha256=pUG3AL4Wjf9LgH9fj1l3emGEjFDFDhmKcDfgiiFJcuQ,4448
27
28
  fastmcp/prompts/__init__.py,sha256=An8uMBUh9Hrb7qqcn_5_Hent7IOeSh7EA2IUVsIrtHc,179
28
29
  fastmcp/prompts/prompt.py,sha256=CGu11NbOvO0b79F3EDCG7YbajGslpoPu59VOEzanIp0,7968
29
30
  fastmcp/prompts/prompt_manager.py,sha256=9VcioLE-AoUKe1e9SynNQME9SvWy0q1QAvO1ewIWVmI,3126
@@ -33,12 +34,12 @@ fastmcp/resources/resource_manager.py,sha256=aJFbn1-Rc-oMahp3SSWNJXJEu8Nxu-gc24i
33
34
  fastmcp/resources/template.py,sha256=ex1s2kBQmGUU1zQ-b__egyJoNlNNKI42JALO0uxxAaE,7408
34
35
  fastmcp/resources/types.py,sha256=5fUFvzRlekNjtfihtq8S-fT0alKoNfclzrugqeM5JRE,6366
35
36
  fastmcp/server/__init__.py,sha256=bMD4aQD4yJqLz7-mudoNsyeV8UgQfRAg3PRwPvwTEds,119
36
- fastmcp/server/context.py,sha256=ykitQygA7zT5prbFTLCuYlnAzuljf_9ErUT0FYBPv3E,8135
37
+ fastmcp/server/context.py,sha256=obOTozUq6xwe2oNn6VQ6F4mwWGEonF5A0emi7jo1J8k,8231
37
38
  fastmcp/server/dependencies.py,sha256=1utkxFsV37HZcWBwI69JyngVN2ppGO_PEgxUlUHHy_Q,742
38
- fastmcp/server/http.py,sha256=utl7vJkMvKUnKIflCptVWk1oqOi7_sJJHqUl22g4JC8,10473
39
- fastmcp/server/openapi.py,sha256=_7U0XtPk4wCkGOBfYx3J3ujA9iqQtnsc0scA4sCsIT0,24170
39
+ fastmcp/server/http.py,sha256=2CpCt4rsck4rxEd5w8ITEWAhRxTdDjuItVb-BPXCXTE,12693
40
+ fastmcp/server/openapi.py,sha256=SrX0sxGdu6Ld8X81MKEdi6UgvWdHZQJateDlPWE6mQg,29978
40
41
  fastmcp/server/proxy.py,sha256=mt3eM6TQWfnZD5XehmTXisskZ4CBbsWyjRPjprlTjBY,9653
41
- fastmcp/server/server.py,sha256=EM5BqwXRlG73sMZg6186yMVKfXYGn5gT4qqAxfNa8lU,45454
42
+ fastmcp/server/server.py,sha256=cRms1-r0mN6qVSIjRzHkjt7MIMBiOer61VYmtIgHORc,54686
42
43
  fastmcp/tools/__init__.py,sha256=ocw-SFTtN6vQ8fgnlF8iNAOflRmh79xS1xdO0Bc3QPE,96
43
44
  fastmcp/tools/tool.py,sha256=k7awnrPoId_1XJHFce7X3mAEgBsJyr2v5kuuxXrE5Ww,7602
44
45
  fastmcp/tools/tool_manager.py,sha256=v4Ur-JXDPXUxHqHJxA52IIcZfSiCBOnoFFLOmmJR1A8,4157
@@ -48,11 +49,12 @@ fastmcp/utilities/decorators.py,sha256=AjhjsetQZF4YOPV5MTZmIxO21iFp_4fDIS3O2_KNC
48
49
  fastmcp/utilities/exceptions.py,sha256=Aax9K0larjzrrgJBS6o_PQwoIrvBvVwck2suZvgafXE,1359
49
50
  fastmcp/utilities/json_schema.py,sha256=m65XU9lPq7pCxJ9vvCeGRl0HOFr6ArezvYpMBR6-gAg,3777
50
51
  fastmcp/utilities/logging.py,sha256=n4P7P-aFDCuUFz8O-ykzUOj2sXl789HtWI_pX3ynGaY,1234
51
- fastmcp/utilities/openapi.py,sha256=V3hANT6KcD_Bloq9uHDVkVJRcGaZIq8GH5ZZ7bKVmXY,56943
52
- fastmcp/utilities/tests.py,sha256=mAV2EjDeCbm9V9NsVIUjcmzf93MgDjfj8kMvHpf4vgo,3224
52
+ fastmcp/utilities/mcp_config.py,sha256=fZKlZt45CTyVShvHP5xId_anhLSsUesXi802GuZY6Jo,2127
53
+ fastmcp/utilities/openapi.py,sha256=QQos4vP59HQ8vPDTKftWOIVv_zmW30mNxYSXVU7JUbY,38441
54
+ fastmcp/utilities/tests.py,sha256=teyHcl3j7WGfYJ6m42VuQYB_IVpGvPdFqIpC-UxsN78,3369
53
55
  fastmcp/utilities/types.py,sha256=6CcqAQ1QqCO2HGSFlPS6FO5JRWnacjCcO2-EhyEnZV0,4400
54
- fastmcp-2.3.4.dist-info/METADATA,sha256=9uZJ9YUNRVnoD6oFmDatjuiaQn8GAWt9XKzqagsngyQ,15754
55
- fastmcp-2.3.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
56
- fastmcp-2.3.4.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
57
- fastmcp-2.3.4.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
58
- fastmcp-2.3.4.dist-info/RECORD,,
56
+ fastmcp-2.4.0.dist-info/METADATA,sha256=BV9xX1Z3gYYdUJDfpjtZMH8nS_4DdpFM7yShwiGSDJs,16514
57
+ fastmcp-2.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
58
+ fastmcp-2.4.0.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
59
+ fastmcp-2.4.0.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
60
+ fastmcp-2.4.0.dist-info/RECORD,,
@@ -1,104 +0,0 @@
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)