from fastmcp.exceptions import ToolError from fastmcp.tools.base import Tool from typing import Any, Callable, Dict, Optional, TYPE_CHECKING from fastmcp.utilities.logging import get_logger if TYPE_CHECKING: from fastmcp.server import Context logger = get_logger(__name__) class ToolManager: """Manages FastMCP tools.""" def __init__(self, warn_on_duplicate_tools: bool = True): self._tools: Dict[str, Tool] = {} self.warn_on_duplicate_tools = warn_on_duplicate_tools def get_tool(self, name: str) -> Optional[Tool]: """Get tool by name.""" return self._tools.get(name) def list_tools(self) -> list[Tool]: """List all registered tools.""" return list(self._tools.values()) def add_tool( self, fn: Callable, name: Optional[str] = None, description: Optional[str] = None, ) -> Tool: """Add a tool to the server.""" tool = Tool.from_function(fn, name=name, description=description) 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 async def call_tool( self, name: str, arguments: dict, context: Optional["Context"] = None ) -> Any: """Call a tool by name with arguments.""" tool = self.get_tool(name) if not tool: raise ToolError(f"Unknown tool: {name}") return await tool.run(arguments, context=context)