From a335a9dd890445bd78b94300a5f8f51453cf0529 Mon Sep 17 00:00:00 2001 From: Chris Guidry Date: Mon, 8 Dec 2025 13:33:57 -0500 Subject: [PATCH] Fix race condition with mounted server task results MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a server is mounted on another, both were creating their own Docket and Worker instances. With memory:// URLs they share the same FakeServer queue but have separate result_storage instances. This caused a race condition where results could be stored in one Docket's storage but looked up in another's, returning None. The fix marks mounted servers with `_is_mounted=True` flag so they skip creating their own Docket/Worker. The parent's Docket handles all task execution for mounted servers. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/fastmcp/server/server.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index 580ac1eff..63af58bf4 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -201,6 +201,9 @@ class FastMCP(Generic[LifespanResultT]): self._additional_http_routes: list[BaseRoute] = [] self._mounted_servers: list[MountedServer] = [] + self._is_mounted: bool = ( + False # Set to True when this server is mounted on another + ) self._tool_manager: ToolManager = ToolManager( duplicate_behavior=on_duplicate_tools, mask_error_details=mask_error_details, @@ -395,6 +398,13 @@ class FastMCP(Generic[LifespanResultT]): server_token = _current_server.set(weakref.ref(self)) try: + # For directly mounted servers, the parent's Docket/Worker handles all + # task execution. Skip creating our own to avoid race conditions with + # multiple workers competing for tasks from the same queue. + if self._is_mounted: + yield + return + # Create Docket instance using configured name and URL async with Docket( name=settings.docket.name, @@ -2696,6 +2706,11 @@ class FastMCP(Generic[LifespanResultT]): if as_proxy and not isinstance(server, FastMCPProxy): server = FastMCP.as_proxy(server) + # Mark the server as mounted so it skips creating its own Docket/Worker. + # The parent's Docket handles task execution, avoiding race conditions + # with multiple workers competing for tasks from the same queue. + server._is_mounted = True + # Delegate mounting to all three managers mounted_server = MountedServer( prefix=prefix,