Merge pull request #539 from jlowin/codex/add-support-for-fastmcp-1.0-server

Add FastMCP 1.0 server support for in-memory Client / Testing
This commit is contained in:
Jeremiah Lowin 2025-05-22 11:19:41 -04:00 committed by GitHub
commit e0b84186ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 35 additions and 14 deletions

View file

@ -37,7 +37,7 @@ Clients must be initialized with a `transport`. You can either provide an alread
The following inference rules are used to determine the appropriate `ClientTransport` based on the input type:
1. **`ClientTransport` Instance**: If you provide an already instantiated transport object, it's used directly.
2. **`FastMCP` Instance**: Creates a `FastMCPTransport` for efficient in-memory communication (ideal for testing).
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`.
3. **`Path` or `str` pointing to an existing file**:
* If it ends with `.py`: Creates a `PythonStdioTransport` to run the script using `python`.
* If it ends with `.js`: Creates a `NodeStdioTransport` to run the script using `node`.

View file

@ -290,8 +290,8 @@ asyncio.run(main())
### FastMCP Transport
- **Class:** `fastmcp.client.transports.FastMCPTransport`
- **Inferred From:** An instance of `fastmcp.server.FastMCP`
- **Use Case:** Connecting directly to a `FastMCP` server instance in the same Python process
- **Inferred From:** An instance of `fastmcp.server.FastMCP` or a **FastMCP 1.0 server** (`mcp.server.fastmcp.FastMCP`)
- **Use Case:** Connecting directly to a FastMCP server instance in the same Python process
This is extremely useful for testing your FastMCP servers.

View file

@ -19,6 +19,7 @@ from mcp.client.sse import sse_client
from mcp.client.stdio import stdio_client
from mcp.client.streamable_http import streamablehttp_client
from mcp.client.websocket import websocket_client
from mcp.server.fastmcp import FastMCP as FastMCP1Server
from mcp.shared.memory import create_connected_server_and_client_session
from pydantic import AnyUrl
from typing_extensions import Unpack
@ -448,15 +449,21 @@ class NpxStdioTransport(StdioTransport):
class FastMCPTransport(ClientTransport):
"""
Special transport for in-memory connections to an MCP server.
"""In-memory transport for FastMCP servers.
This is particularly useful for testing or when client and server
are in the same process.
This transport connects directly to a FastMCP server instance in the same
Python process. It works with both FastMCP 2.x servers and FastMCP 1.0
servers from the low-level MCP SDK. This is particularly useful for unit
tests or scenarios where client and server run in the same runtime.
"""
def __init__(self, mcp: FastMCPServer):
self.server = mcp # Can be FastMCP or MCPServer
def __init__(self, mcp: FastMCPServer | FastMCP1Server):
"""Initialize a FastMCPTransport from a FastMCP server instance."""
# Accept both FastMCP 2.x and FastMCP 1.0 servers. Both expose a
# ``_mcp_server`` attribute pointing to the underlying MCP server
# implementation, so we can treat them identically.
self.server = mcp
@contextlib.asynccontextmanager
async def connect_session(
@ -558,6 +565,7 @@ class MCPConfigTransport(ClientTransport):
def infer_transport(
transport: ClientTransport
| FastMCPServer
| FastMCP1Server
| AnyUrl
| Path
| MCPConfig
@ -573,7 +581,7 @@ def infer_transport(
The function supports these input types:
- ClientTransport: Used directly without modification
- FastMCPServer: Creates an in-memory FastMCPTransport
- FastMCPServer or FastMCP1Server: Creates an in-memory FastMCPTransport
- Path or str (file path): Creates PythonStdioTransport (.py) or NodeStdioTransport (.js)
- AnyUrl or str (URL): Creates StreamableHttpTransport (default) or SSETransport (for /sse endpoints)
- MCPConfig or dict: Creates MCPConfigTransport, potentially connecting to multiple servers
@ -610,8 +618,8 @@ def infer_transport(
if isinstance(transport, ClientTransport):
return transport
# the transport is a FastMCP server
elif isinstance(transport, FastMCPServer):
# the transport is a FastMCP server (2.x or 1.0)
elif isinstance(transport, FastMCPServer | FastMCP1Server):
inferred_transport = FastMCPTransport(mcp=transport)
# the transport is a path to a script

View file

@ -673,7 +673,7 @@ class TestInferTransport:
assert transport.transport.command == "echo"
assert transport.transport.args == ["hello"]
def test_infer_composite_client(config):
def test_infer_composite_client(self):
config = {
"mcpServers": {
"local": {
@ -689,4 +689,17 @@ class TestInferTransport:
transport = infer_transport(config)
assert isinstance(transport, MCPConfigTransport)
assert isinstance(transport.transport, FastMCPTransport)
assert len(transport.transport.server._mounted_servers) == 2
assert len(cast(FastMCP, transport.transport.server)._mounted_servers) == 2
def test_infer_fastmcp_server(self, fastmcp_server):
"""FastMCP server instances should infer to FastMCPTransport."""
transport = infer_transport(fastmcp_server)
assert isinstance(transport, FastMCPTransport)
def test_infer_fastmcp_v1_server(self):
"""FastMCP 1.0 server instances should infer to FastMCPTransport."""
from mcp.server.fastmcp import FastMCP as FastMCP1
server = FastMCP1()
transport = infer_transport(server)
assert isinstance(transport, FastMCPTransport)