diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 8929eb6f..1806f0a3 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 @@ -315,6 +315,10 @@ async def read_resource(self, uri: AnyUrl | str) -> Iterable[ReadResourceContent logger.error(f"Error reading resource {uri}: {e}") raise ResourceError(str(e)) + def add_tool_instance(self, tool: Tool) -> None: + """Add a Tool instance to the server.""" + self._tool_manager.add_tool_instance(tool) + def add_tool( self, fn: AnyFunction, diff --git a/src/mcp/server/fastmcp/tools/tool_manager.py b/src/mcp/server/fastmcp/tools/tool_manager.py index cfdaeb35..6f7d0d9f 100644 --- a/src/mcp/server/fastmcp/tools/tool_manager.py +++ b/src/mcp/server/fastmcp/tools/tool_manager.py @@ -31,6 +31,16 @@ def list_tools(self) -> list[Tool]: """List all registered tools.""" return list(self._tools.values()) + def add_tool_instance(self, tool: Tool) -> Tool: + """Add a Tool instance to the server.""" + existing = self._tools.get(tool.name) + if existing: + if self.warn_on_duplicate_tools: + logger.warning(f"Tool already exists: {tool.name}") + return existing + self._tools[tool.name] = tool + return tool + def add_tool( self, fn: Callable[..., Any], @@ -42,13 +52,7 @@ def add_tool( tool = Tool.from_function( fn, name=name, description=description, annotations=annotations ) - existing = self._tools.get(tool.name) - if existing: - if self.warn_on_duplicate_tools: - logger.warning(f"Tool already exists: {tool.name}") - return existing - self._tools[tool.name] = tool - return tool + return self.add_tool_instance(tool) async def call_tool( self, diff --git a/tests/server/fastmcp/test_tool_manager.py b/tests/server/fastmcp/test_tool_manager.py index e36a09d5..9ae73da0 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,32 @@ def add(a: int, b: int) -> int: assert tool.parameters["properties"]["a"]["type"] == "integer" assert tool.parameters["properties"]["b"]["type"] == "integer" + def test_add_tool_instance(self): + manager = ToolManager() + + 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.add_tool_instance(original_tool) + saved_tool = manager.get_tool("add") + assert saved_tool == original_tool + @pytest.mark.anyio async def test_async_function(self): """Test registering and running an async function."""