Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
407f55e
1
Parent(s): f8721ad
feat: support FastMCP v1 server transport
Browse files- docs/clients/client.mdx +1 -1
- docs/clients/transports.mdx +2 -2
- src/fastmcp/client/transports.py +17 -9
- tests/client/test_client.py +15 -2
docs/clients/client.mdx
CHANGED
|
@@ -37,7 +37,7 @@ Clients must be initialized with a `transport`. You can either provide an alread
|
|
| 37 |
The following inference rules are used to determine the appropriate `ClientTransport` based on the input type:
|
| 38 |
|
| 39 |
1. **`ClientTransport` Instance**: If you provide an already instantiated transport object, it's used directly.
|
| 40 |
-
2. **`FastMCP` Instance**: Creates a `FastMCPTransport` for efficient in-memory communication (ideal for testing).
|
| 41 |
3. **`Path` or `str` pointing to an existing file**:
|
| 42 |
* If it ends with `.py`: Creates a `PythonStdioTransport` to run the script using `python`.
|
| 43 |
* If it ends with `.js`: Creates a `NodeStdioTransport` to run the script using `node`.
|
|
|
|
| 37 |
The following inference rules are used to determine the appropriate `ClientTransport` based on the input type:
|
| 38 |
|
| 39 |
1. **`ClientTransport` Instance**: If you provide an already instantiated transport object, it's used directly.
|
| 40 |
+
2. **`FastMCP` Instance**: Creates a `FastMCPTransport` for efficient in-memory communication (ideal for testing). This also works with a **FastMCP 1.0 server** created via `mcp.server.fastmcp.FastMCP`.
|
| 41 |
3. **`Path` or `str` pointing to an existing file**:
|
| 42 |
* If it ends with `.py`: Creates a `PythonStdioTransport` to run the script using `python`.
|
| 43 |
* If it ends with `.js`: Creates a `NodeStdioTransport` to run the script using `node`.
|
docs/clients/transports.mdx
CHANGED
|
@@ -290,8 +290,8 @@ asyncio.run(main())
|
|
| 290 |
### FastMCP Transport
|
| 291 |
|
| 292 |
- **Class:** `fastmcp.client.transports.FastMCPTransport`
|
| 293 |
-
- **Inferred From:** An instance of `fastmcp.server.FastMCP`
|
| 294 |
-
- **Use Case:** Connecting directly to a
|
| 295 |
|
| 296 |
This is extremely useful for testing your FastMCP servers.
|
| 297 |
|
|
|
|
| 290 |
### FastMCP Transport
|
| 291 |
|
| 292 |
- **Class:** `fastmcp.client.transports.FastMCPTransport`
|
| 293 |
+
- **Inferred From:** An instance of `fastmcp.server.FastMCP` or a **FastMCP 1.0 server** (`mcp.server.fastmcp.FastMCP`)
|
| 294 |
+
- **Use Case:** Connecting directly to a FastMCP server instance in the same Python process
|
| 295 |
|
| 296 |
This is extremely useful for testing your FastMCP servers.
|
| 297 |
|
src/fastmcp/client/transports.py
CHANGED
|
@@ -19,6 +19,7 @@ from mcp.client.sse import sse_client
|
|
| 19 |
from mcp.client.stdio import stdio_client
|
| 20 |
from mcp.client.streamable_http import streamablehttp_client
|
| 21 |
from mcp.client.websocket import websocket_client
|
|
|
|
| 22 |
from mcp.shared.memory import create_connected_server_and_client_session
|
| 23 |
from pydantic import AnyUrl
|
| 24 |
from typing_extensions import Unpack
|
|
@@ -448,15 +449,21 @@ class NpxStdioTransport(StdioTransport):
|
|
| 448 |
|
| 449 |
|
| 450 |
class FastMCPTransport(ClientTransport):
|
| 451 |
-
"""
|
| 452 |
-
Special transport for in-memory connections to an MCP server.
|
| 453 |
|
| 454 |
-
This
|
| 455 |
-
|
|
|
|
|
|
|
| 456 |
"""
|
| 457 |
|
| 458 |
-
def __init__(self, mcp: FastMCPServer):
|
| 459 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 460 |
|
| 461 |
@contextlib.asynccontextmanager
|
| 462 |
async def connect_session(
|
|
@@ -558,6 +565,7 @@ class MCPConfigTransport(ClientTransport):
|
|
| 558 |
def infer_transport(
|
| 559 |
transport: ClientTransport
|
| 560 |
| FastMCPServer
|
|
|
|
| 561 |
| AnyUrl
|
| 562 |
| Path
|
| 563 |
| MCPConfig
|
|
@@ -573,7 +581,7 @@ def infer_transport(
|
|
| 573 |
|
| 574 |
The function supports these input types:
|
| 575 |
- ClientTransport: Used directly without modification
|
| 576 |
-
- FastMCPServer: Creates an in-memory FastMCPTransport
|
| 577 |
- Path or str (file path): Creates PythonStdioTransport (.py) or NodeStdioTransport (.js)
|
| 578 |
- AnyUrl or str (URL): Creates StreamableHttpTransport (default) or SSETransport (for /sse endpoints)
|
| 579 |
- MCPConfig or dict: Creates MCPConfigTransport, potentially connecting to multiple servers
|
|
@@ -610,8 +618,8 @@ def infer_transport(
|
|
| 610 |
if isinstance(transport, ClientTransport):
|
| 611 |
return transport
|
| 612 |
|
| 613 |
-
# the transport is a FastMCP server
|
| 614 |
-
elif isinstance(transport, FastMCPServer):
|
| 615 |
inferred_transport = FastMCPTransport(mcp=transport)
|
| 616 |
|
| 617 |
# the transport is a path to a script
|
|
|
|
| 19 |
from mcp.client.stdio import stdio_client
|
| 20 |
from mcp.client.streamable_http import streamablehttp_client
|
| 21 |
from mcp.client.websocket import websocket_client
|
| 22 |
+
from mcp.server.fastmcp import FastMCP as FastMCP1Server
|
| 23 |
from mcp.shared.memory import create_connected_server_and_client_session
|
| 24 |
from pydantic import AnyUrl
|
| 25 |
from typing_extensions import Unpack
|
|
|
|
| 449 |
|
| 450 |
|
| 451 |
class FastMCPTransport(ClientTransport):
|
| 452 |
+
"""In-memory transport for FastMCP servers.
|
|
|
|
| 453 |
|
| 454 |
+
This transport connects directly to a FastMCP server instance in the same
|
| 455 |
+
Python process. It works with both FastMCP 2.x servers and FastMCP 1.0
|
| 456 |
+
servers from the low-level MCP SDK. This is particularly useful for unit
|
| 457 |
+
tests or scenarios where client and server run in the same runtime.
|
| 458 |
"""
|
| 459 |
|
| 460 |
+
def __init__(self, mcp: FastMCPServer | FastMCP1Server):
|
| 461 |
+
"""Initialize a FastMCPTransport from a FastMCP server instance."""
|
| 462 |
+
|
| 463 |
+
# Accept both FastMCP 2.x and FastMCP 1.0 servers. Both expose a
|
| 464 |
+
# ``_mcp_server`` attribute pointing to the underlying MCP server
|
| 465 |
+
# implementation, so we can treat them identically.
|
| 466 |
+
self.server = mcp
|
| 467 |
|
| 468 |
@contextlib.asynccontextmanager
|
| 469 |
async def connect_session(
|
|
|
|
| 565 |
def infer_transport(
|
| 566 |
transport: ClientTransport
|
| 567 |
| FastMCPServer
|
| 568 |
+
| FastMCP1Server
|
| 569 |
| AnyUrl
|
| 570 |
| Path
|
| 571 |
| MCPConfig
|
|
|
|
| 581 |
|
| 582 |
The function supports these input types:
|
| 583 |
- ClientTransport: Used directly without modification
|
| 584 |
+
- FastMCPServer or FastMCP1Server: Creates an in-memory FastMCPTransport
|
| 585 |
- Path or str (file path): Creates PythonStdioTransport (.py) or NodeStdioTransport (.js)
|
| 586 |
- AnyUrl or str (URL): Creates StreamableHttpTransport (default) or SSETransport (for /sse endpoints)
|
| 587 |
- MCPConfig or dict: Creates MCPConfigTransport, potentially connecting to multiple servers
|
|
|
|
| 618 |
if isinstance(transport, ClientTransport):
|
| 619 |
return transport
|
| 620 |
|
| 621 |
+
# the transport is a FastMCP server (2.x or 1.0)
|
| 622 |
+
elif isinstance(transport, FastMCPServer | FastMCP1Server):
|
| 623 |
inferred_transport = FastMCPTransport(mcp=transport)
|
| 624 |
|
| 625 |
# the transport is a path to a script
|
tests/client/test_client.py
CHANGED
|
@@ -673,7 +673,7 @@ class TestInferTransport:
|
|
| 673 |
assert transport.transport.command == "echo"
|
| 674 |
assert transport.transport.args == ["hello"]
|
| 675 |
|
| 676 |
-
def test_infer_composite_client(
|
| 677 |
config = {
|
| 678 |
"mcpServers": {
|
| 679 |
"local": {
|
|
@@ -689,4 +689,17 @@ class TestInferTransport:
|
|
| 689 |
transport = infer_transport(config)
|
| 690 |
assert isinstance(transport, MCPConfigTransport)
|
| 691 |
assert isinstance(transport.transport, FastMCPTransport)
|
| 692 |
-
assert len(transport.transport.server._mounted_servers) == 2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 673 |
assert transport.transport.command == "echo"
|
| 674 |
assert transport.transport.args == ["hello"]
|
| 675 |
|
| 676 |
+
def test_infer_composite_client(self):
|
| 677 |
config = {
|
| 678 |
"mcpServers": {
|
| 679 |
"local": {
|
|
|
|
| 689 |
transport = infer_transport(config)
|
| 690 |
assert isinstance(transport, MCPConfigTransport)
|
| 691 |
assert isinstance(transport.transport, FastMCPTransport)
|
| 692 |
+
assert len(cast(FastMCP, transport.transport.server)._mounted_servers) == 2
|
| 693 |
+
|
| 694 |
+
def test_infer_fastmcp_server(self, fastmcp_server):
|
| 695 |
+
"""FastMCP server instances should infer to FastMCPTransport."""
|
| 696 |
+
transport = infer_transport(fastmcp_server)
|
| 697 |
+
assert isinstance(transport, FastMCPTransport)
|
| 698 |
+
|
| 699 |
+
def test_infer_fastmcp_v1_server(self):
|
| 700 |
+
"""FastMCP 1.0 server instances should infer to FastMCPTransport."""
|
| 701 |
+
from mcp.server.fastmcp import FastMCP as FastMCP1
|
| 702 |
+
|
| 703 |
+
server = FastMCP1()
|
| 704 |
+
transport = infer_transport(server)
|
| 705 |
+
assert isinstance(transport, FastMCPTransport)
|