From 1577cd25a7542da1b4cd6eb7eb7c755865ec455e Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Sun, 5 Jul 2026 21:10:10 -0400 Subject: [PATCH] Resolve lowlevel Server per type for in-memory transport (SDK v2) --- .../fastmcp/client/transports/memory.py | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/fastmcp_slim/fastmcp/client/transports/memory.py b/fastmcp_slim/fastmcp/client/transports/memory.py index 21ba62392..f6cbf6d41 100644 --- a/fastmcp_slim/fastmcp/client/transports/memory.py +++ b/fastmcp_slim/fastmcp/client/transports/memory.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Any import anyio from mcp import ClientSession +from mcp.server import Server from mcp.server.mcpserver import MCPServer as FastMCP1Server from mcp.shared.memory import create_client_server_memory_streams from typing_extensions import Unpack @@ -16,6 +17,19 @@ if TYPE_CHECKING: from fastmcp.server.server import FastMCP +def _lowlevel_of(server: "FastMCP[Any] | FastMCP1Server") -> Server: + """Resolve the underlying lowlevel MCP `Server` for either server type. + + SDK v2's `MCPServer` (FastMCP 1.0) exposes its lowlevel server as + `_lowlevel_server` and its own `run()` is synchronous, so we always drive + the async lowlevel `Server.run` here. FastMCP 2.x servers expose the same + lowlevel server as `_mcp_server`. + """ + if isinstance(server, FastMCP1Server): + return server._lowlevel_server + return server._mcp_server + + class FastMCPTransport(ClientTransport): """In-memory transport for FastMCP servers. @@ -30,9 +44,10 @@ class FastMCPTransport(ClientTransport): ): """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. + # Accept both FastMCP 2.x and FastMCP 1.0 servers. Their underlying + # lowlevel MCP ``Server`` lives on different attributes + # (``_mcp_server`` vs ``_lowlevel_server``); ``_lowlevel_of`` resolves + # it uniformly so we can drive the async ``Server.run`` for both. self.server = mcp self.raise_exceptions = raise_exceptions @@ -61,13 +76,14 @@ class FastMCPTransport(ClientTransport): # shutdown to hang for 5 seconds per test because fakeredis # blocking operations hold references that prevent clean # cancellation. + lowlevel = _lowlevel_of(self.server) async with _enter_server_lifespan(server=self.server): # noqa: SIM117 async with anyio.create_task_group() as tg: tg.start_soon( - lambda: self.server._mcp_server.run( + lambda: lowlevel.run( server_read, server_write, - self.server._mcp_server.create_initialization_options(), + lowlevel.create_initialization_options(), raise_exceptions=self.raise_exceptions, ) )