diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 21c31b0b33..b6639e420a 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -37,7 +37,7 @@ from mcp.server.fastmcp.exceptions import ResourceError from mcp.server.fastmcp.prompts import Prompt, PromptManager from mcp.server.fastmcp.resources import FunctionResource, Resource, ResourceManager -from mcp.server.fastmcp.tools import ToolManager +from mcp.server.fastmcp.tools import Tool, ToolManager from mcp.server.fastmcp.utilities.logging import configure_logging, get_logger from mcp.server.fastmcp.utilities.types import Image from mcp.server.lowlevel.helper_types import ReadResourceContents @@ -141,6 +141,8 @@ def __init__( auth_server_provider: OAuthAuthorizationServerProvider[Any, Any, Any] | None = None, event_store: EventStore | None = None, + *, + tools: list[Tool] | None = None, **settings: Any, ): self.settings = Settings(**settings) @@ -155,7 +157,7 @@ def __init__( ), ) self._tool_manager = ToolManager( - warn_on_duplicate_tools=self.settings.warn_on_duplicate_tools + tools=tools, warn_on_duplicate_tools=self.settings.warn_on_duplicate_tools ) self._resource_manager = ResourceManager( warn_on_duplicate_resources=self.settings.warn_on_duplicate_resources diff --git a/src/mcp/server/fastmcp/tools/tool_manager.py b/src/mcp/server/fastmcp/tools/tool_manager.py index cfdaeb350f..6ec4fd1516 100644 --- a/src/mcp/server/fastmcp/tools/tool_manager.py +++ b/src/mcp/server/fastmcp/tools/tool_manager.py @@ -19,8 +19,19 @@ class ToolManager: """Manages FastMCP tools.""" - def __init__(self, warn_on_duplicate_tools: bool = True): + def __init__( + self, + warn_on_duplicate_tools: bool = True, + *, + tools: list[Tool] | None = None, + ): self._tools: dict[str, Tool] = {} + if tools is not None: + for tool in tools: + if warn_on_duplicate_tools and tool.name in self._tools: + logger.warning(f"Tool already exists: {tool.name}") + self._tools[tool.name] = tool + self.warn_on_duplicate_tools = warn_on_duplicate_tools def get_tool(self, name: str) -> Tool | None: diff --git a/tests/server/fastmcp/test_tool_manager.py b/tests/server/fastmcp/test_tool_manager.py index e36a09d545..015974eb00 100644 --- a/tests/server/fastmcp/test_tool_manager.py +++ b/tests/server/fastmcp/test_tool_manager.py @@ -6,7 +6,8 @@ from mcp.server.fastmcp import Context, FastMCP from mcp.server.fastmcp.exceptions import ToolError -from mcp.server.fastmcp.tools import ToolManager +from mcp.server.fastmcp.tools import Tool, ToolManager +from mcp.server.fastmcp.utilities.func_metadata import ArgModelBase, FuncMetadata from mcp.server.session import ServerSessionT from mcp.shared.context import LifespanContextT from mcp.types import ToolAnnotations @@ -31,6 +32,35 @@ def add(a: int, b: int) -> int: assert tool.parameters["properties"]["a"]["type"] == "integer" assert tool.parameters["properties"]["b"]["type"] == "integer" + def test_init_with_tools(self, caplog): + def add(a: int, b: int) -> int: + return a + b + + class AddArguments(ArgModelBase): + a: int + b: int + + fn_metadata = FuncMetadata(arg_model=AddArguments) + + original_tool = Tool( + name="add", + description="Add two numbers.", + fn=add, + fn_metadata=fn_metadata, + is_async=False, + parameters=AddArguments.model_json_schema(), + context_kwarg=None, + annotations=None, + ) + manager = ToolManager(tools=[original_tool]) + saved_tool = manager.get_tool("add") + assert saved_tool == original_tool + + # warn on duplicate tools + with caplog.at_level(logging.WARNING): + manager = ToolManager(True, tools=[original_tool, original_tool]) + assert "Tool already exists: add" in caplog.text + @pytest.mark.anyio async def test_async_function(self): """Test registering and running an async function."""