diff --git a/src/fastmcp/server/proxy.py b/src/fastmcp/server/proxy.py index 91a03c89e..0d82c9cb3 100644 --- a/src/fastmcp/server/proxy.py +++ b/src/fastmcp/server/proxy.py @@ -172,8 +172,11 @@ class FastMCPProxy(FastMCP): super().__init__(**kwargs) self.client = client - async def get_tools(self) -> dict[str, Tool]: - tools = await super().get_tools() + async def _list_tools(self, apply_middleware: bool = True) -> list[Tool]: + tools = { + tool.name: tool + for tool in await super()._list_tools(apply_middleware=apply_middleware) + } async with self.client: try: @@ -187,12 +190,17 @@ class FastMCPProxy(FastMCP): # don't overwrite tools defined in the server if tool.name not in tools: tool_proxy = await ProxyTool.from_client(self.client, tool) - tools[tool_proxy.name] = tool_proxy + tools[tool_proxy.key] = tool_proxy - return tools + return list(tools.values()) - async def get_resources(self) -> dict[str, Resource]: - resources = await super().get_resources() + async def _list_resources(self, apply_middleware: bool = True) -> list[Resource]: + resources = { + resource.uri: resource + for resource in await super()._list_resources( + apply_middleware=apply_middleware + ) + } async with self.client: try: @@ -204,16 +212,19 @@ class FastMCPProxy(FastMCP): raise e for resource in client_resources: # don't overwrite resources defined in the server - if str(resource.uri) not in resources: + if resource.uri not in resources: resource_proxy = await ProxyResource.from_client( self.client, resource ) - resources[str(resource_proxy.uri)] = resource_proxy + resources[resource_proxy.uri] = resource_proxy - return resources + return list(resources.values()) - async def get_resource_templates(self) -> dict[str, ResourceTemplate]: - templates = await super().get_resource_templates() + async def _list_resource_templates(self) -> list[ResourceTemplate]: + templates = { + template.uri_template: template + for template in await super()._list_resource_templates() + } async with self.client: try: @@ -231,10 +242,10 @@ class FastMCPProxy(FastMCP): ) templates[template_proxy.uri_template] = template_proxy - return templates + return list(templates.values()) - async def get_prompts(self) -> dict[str, Prompt]: - prompts = await super().get_prompts() + async def _list_prompts(self) -> list[Prompt]: + prompts = {prompt.name: prompt for prompt in await super()._list_prompts()} async with self.client: try: @@ -250,7 +261,7 @@ class FastMCPProxy(FastMCP): prompt_proxy = await ProxyPrompt.from_client(self.client, prompt) prompts[prompt_proxy.name] = prompt_proxy - return prompts + return list(prompts.values()) async def _call_tool(self, key: str, arguments: dict[str, Any]) -> list[MCPContent]: try: diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index 8eabe815d..0805f98e1 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -435,7 +435,7 @@ class FastMCP(Generic[LifespanResultT]): with fastmcp.server.context.Context(fastmcp=self): tools = await self._middleware_list_tools() - return [tool.to_mcp_tool(name=tool.name) for tool in tools] + return [tool.to_mcp_tool(name=tool.key) for tool in tools] async def _middleware_list_tools(self) -> list[Tool]: """ @@ -475,7 +475,7 @@ class FastMCP(Generic[LifespanResultT]): """ if (tools := self._cache.get("tools")) is self._cache.NOT_FOUND: - tools: list[Tool] = [] + tools: dict[str, Tool] = {} # iterate such that new mounts overwrite older ones for mounted_server in self._mounted_servers: @@ -490,17 +490,17 @@ class FastMCP(Generic[LifespanResultT]): if mounted_server.prefix: for tool in server_tools: tool = tool.with_key(f"{mounted_server.prefix}_{tool.key}") - tools.append(tool) + tools[tool.key] = tool else: - tools.extend(server_tools) + tools.update({tool.key: tool for tool in server_tools}) except Exception as e: logger.warning( f"Failed to get tools from mounted server '{mounted_server.prefix}': {e}" ) continue - tools.extend(self._tool_manager.get_tools().values()) + tools.update(self._tool_manager.get_tools()) self._cache.set("tools", tools) - return tools + return list(tools.values()) async def _mcp_list_resources(self) -> list[MCPResource]: logger.debug("Handler called: list_resources") @@ -549,7 +549,7 @@ class FastMCP(Generic[LifespanResultT]): """ if (resources := self._cache.get("resources")) is self._cache.NOT_FOUND: - resources: list[Resource] = [] + resources: dict[str, Resource] = {} # iterate such that new mounts overwrite older ones for mounted_server in self._mounted_servers: @@ -570,17 +570,19 @@ class FastMCP(Generic[LifespanResultT]): self.resource_prefix_format, ) ) - resources.append(resource) + resources[resource.key] = resource else: - resources.extend(server_resources) + resources.update( + {resource.key: resource for resource in server_resources} + ) except Exception as e: logger.warning( f"Failed to get resources from mounted server '{mounted_server.prefix}': {e}" ) continue - resources.extend(self._resource_manager.get_resources().values()) + resources.update(self._resource_manager.get_resources()) self._cache.set("resources", resources) - return resources + return list(resources.values()) async def _mcp_list_resource_templates(self) -> list[MCPResourceTemplate]: logger.debug("Handler called: list_resource_templates") @@ -634,7 +636,7 @@ class FastMCP(Generic[LifespanResultT]): if ( templates := self._cache.get("resource_templates") ) is self._cache.NOT_FOUND: - templates: list[ResourceTemplate] = [] + templates: dict[str, ResourceTemplate] = {} # iterate such that new mounts overwrite older ones for mounted_server in self._mounted_servers: @@ -655,18 +657,20 @@ class FastMCP(Generic[LifespanResultT]): self.resource_prefix_format, ) ) - templates.append(template) + templates[template.key] = template else: - templates.extend(server_templates) + templates.update( + {template.key: template for template in server_templates} + ) except Exception as e: logger.warning( "Failed to get resource templates from mounted server " f"'{mounted_server.prefix}': {e}" ) continue - templates.extend(self._resource_manager.get_templates().values()) + templates.update(self._resource_manager.get_templates()) self._cache.set("resource_templates", templates) - return templates + return list(templates.values()) async def _mcp_list_prompts(self) -> list[MCPPrompt]: logger.debug("Handler called: list_prompts") @@ -713,7 +717,7 @@ class FastMCP(Generic[LifespanResultT]): """ if (prompts := self._cache.get("prompts")) is self._cache.NOT_FOUND: - prompts: list[Prompt] = [] + prompts: dict[str, Prompt] = {} # iterate such that new mounts overwrite older ones for mounted_server in self._mounted_servers: @@ -730,17 +734,19 @@ class FastMCP(Generic[LifespanResultT]): prompt = prompt.with_key( f"{mounted_server.prefix}_{prompt.key}" ) - prompts.append(prompt) + prompts[prompt.key] = prompt else: - prompts.extend(server_prompts) + prompts.update( + {prompt.key: prompt for prompt in server_prompts} + ) except Exception as e: logger.warning( f"Failed to get prompts from mounted server '{mounted_server.prefix}': {e}" ) continue - prompts.extend(self._prompt_manager.get_prompts().values()) + prompts.update(self._prompt_manager.get_prompts()) self._cache.set("prompts", prompts) - return prompts + return list(prompts.values()) async def _mcp_call_tool( self, key: str, arguments: dict[str, Any]