Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor tools + resourcemanager #14

Merged
merged 1 commit into from
Nov 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/fastmcp/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
DirectoryResource,
)
from .templates import ResourceTemplate
from .manager import ResourceManager
from .resource_manager import ResourceManager

__all__ = [
"Resource",
Expand Down
3 changes: 1 addition & 2 deletions src/fastmcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@
from pydantic.networks import _BaseUrl

from fastmcp.exceptions import ResourceError
from fastmcp.resources import Resource, ResourceManager
from fastmcp.resources.types import FunctionResource
from fastmcp.resources import Resource, ResourceManager, FunctionResource
from fastmcp.tools import ToolManager
from fastmcp.utilities.logging import configure_logging
from fastmcp.utilities.types import Image
Expand Down
4 changes: 4 additions & 0 deletions src/fastmcp/tools/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .base import Tool
from .tool_manager import ToolManager

__all__ = ["Tool", "ToolManager"]
55 changes: 5 additions & 50 deletions src/fastmcp/tools.py → src/fastmcp/tools/base.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
"""Tool management for FastMCP."""
import fastmcp
from fastmcp.exceptions import ToolError

import inspect
from typing import Any, Callable, Dict, Optional, TYPE_CHECKING

from pydantic import BaseModel, Field, TypeAdapter, validate_call

from .exceptions import ToolError
from .utilities.logging import get_logger
import fastmcp

import inspect
from typing import TYPE_CHECKING, Any, Callable, Optional

if TYPE_CHECKING:
from fastmcp.server import Context

logger = get_logger(__name__)


class Tool(BaseModel):
"""Internal tool registration info."""
Expand Down Expand Up @@ -80,45 +77,3 @@ async def run(self, arguments: dict, context: Optional["Context"] = None) -> Any
return self.func(**arguments)
except Exception as e:
raise ToolError(f"Error executing tool {self.name}: {e}") from e


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,
func: Callable,
name: Optional[str] = None,
description: Optional[str] = None,
) -> Tool:
"""Add a tool to the server."""
tool = Tool.from_function(func, 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)
55 changes: 55 additions & 0 deletions src/fastmcp/tools/tool_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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,
func: Callable,
name: Optional[str] = None,
description: Optional[str] = None,
) -> Tool:
"""Add a tool to the server."""
tool = Tool.from_function(func, 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)