From 4f7e739a350cecda2234855e12618be85b3bc178 Mon Sep 17 00:00:00 2001 From: zzstoatzz Date: Tue, 21 Oct 2025 22:49:30 -0500 Subject: [PATCH] fix: apply prefix to tool name when mounting servers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When mounting a server with a prefix, both the key and name fields should be updated to include the prefix. Previously only the key was updated, causing inconsistency between tool.key and tool.name. This makes tool prefixing consistent with how resources and templates are already handled. 🤖 Generated with Claude Code Co-Authored-By: Claude --- src/fastmcp/server/server.py | 11 ++++++++-- tests/server/test_mount.py | 39 ++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index c758f64cb..9d75ea037 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -464,7 +464,12 @@ class FastMCP(Generic[LifespanResultT]): child_tools = await mounted.server.get_tools() for key, tool in child_tools.items(): new_key = f"{mounted.prefix}_{key}" if mounted.prefix else key - all_tools[new_key] = tool.model_copy(key=new_key) + all_tools[new_key] = tool.model_copy( + key=new_key, + update={"name": f"{mounted.prefix}_{tool.name}"} + if mounted.prefix + else {}, + ) except Exception as e: logger.warning( f"Failed to get tools from mounted server {mounted.server.name!r}: {e}" @@ -722,7 +727,9 @@ class FastMCP(Generic[LifespanResultT]): key = tool.key if mounted.prefix: key = f"{mounted.prefix}_{tool.key}" - tool = tool.model_copy(key=key) + tool = tool.model_copy( + key=key, update={"name": f"{mounted.prefix}_{tool.name}"} + ) # Later mounted servers override earlier ones all_tools[key] = tool except Exception as e: diff --git a/tests/server/test_mount.py b/tests/server/test_mount.py index d02e86b6c..bfb8d2dae 100644 --- a/tests/server/test_mount.py +++ b/tests/server/test_mount.py @@ -972,6 +972,45 @@ class TestAsProxyKwarg: assert lifespan_check.count("start") >= 2 +class TestToolNamePrefixing: + """Test that tool names are prefixed when mounted.""" + + async def test_tool_name_prefixing(self): + """Test that tool names (not just keys) are prefixed when mounted with a prefix.""" + # Create a sub-app with a tool + sub_app = FastMCP("SubApp") + + @sub_app.tool + def my_tool() -> str: + return "Tool result" + + # Create main app and mount sub-app with prefix + main_app = FastMCP("MainApp") + main_app.mount(sub_app, "prefix") + + # Get tools from main app + tools = await main_app.get_tools() + + # Should have prefixed key + assert "prefix_my_tool" in tools + + # The tool name should also be prefixed + tool = tools["prefix_my_tool"] + assert tool.name == "prefix_my_tool" + assert tool.key == "prefix_my_tool" + + # Test via client to ensure both discovery and calling work + async with Client(main_app) as client: + # List tools should show prefixed name + tool_list = await client.list_tools() + tool_names = [t.name for t in tool_list] + assert "prefix_my_tool" in tool_names + + # Calling with the prefixed name should work + result = await client.call_tool("prefix_my_tool", {}) + assert result.data == "Tool result" + + class TestResourceNamePrefixing: """Test that resource and resource template names get prefixed when mounted."""