fastmcp: allow passing Tool directly to FastMCP constructor (#699)

This commit is contained in:
Vadym Barda 2025-05-19 22:52:02 -04:00 • committed by GitHub
commit 5a9340af71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 47 additions and 4 deletions

View file

@ -37,7 +37,7 @@ from mcp.server.auth.settings import (
from mcp.server.fastmcp.exceptions import ResourceError from mcp.server.fastmcp.exceptions import ResourceError
from mcp.server.fastmcp.prompts import Prompt, PromptManager from mcp.server.fastmcp.prompts import Prompt, PromptManager
from mcp.server.fastmcp.resources import FunctionResource, Resource, ResourceManager 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.logging import configure_logging, get_logger
from mcp.server.fastmcp.utilities.types import Image from mcp.server.fastmcp.utilities.types import Image
from mcp.server.lowlevel.helper_types import ReadResourceContents from mcp.server.lowlevel.helper_types import ReadResourceContents
@ -141,6 +141,8 @@ class FastMCP:
auth_server_provider: OAuthAuthorizationServerProvider[Any, Any, Any] auth_server_provider: OAuthAuthorizationServerProvider[Any, Any, Any]
| None = None, | None = None,
event_store: EventStore | None = None, event_store: EventStore | None = None,
*,
tools: list[Tool] | None = None,
**settings: Any, **settings: Any,
): ):
self.settings = Settings(**settings) self.settings = Settings(**settings)
@ -155,7 +157,7 @@ class FastMCP:
), ),
) )
self._tool_manager = ToolManager( 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( self._resource_manager = ResourceManager(
warn_on_duplicate_resources=self.settings.warn_on_duplicate_resources warn_on_duplicate_resources=self.settings.warn_on_duplicate_resources

View file

@ -19,8 +19,19 @@ logger = get_logger(__name__)
class ToolManager: class ToolManager:
"""Manages FastMCP tools.""" """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] = {} 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 self.warn_on_duplicate_tools = warn_on_duplicate_tools
def get_tool(self, name: str) -> Tool | None: def get_tool(self, name: str) -> Tool | None:

View file

@ -6,7 +6,8 @@ from pydantic import BaseModel
from mcp.server.fastmcp import Context, FastMCP from mcp.server.fastmcp import Context, FastMCP
from mcp.server.fastmcp.exceptions import ToolError 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.server.session import ServerSessionT
from mcp.shared.context import LifespanContextT from mcp.shared.context import LifespanContextT
from mcp.types import ToolAnnotations from mcp.types import ToolAnnotations
@ -31,6 +32,35 @@ class TestAddTools:
assert tool.parameters["properties"]["a"]["type"] == "integer" assert tool.parameters["properties"]["a"]["type"] == "integer"
assert tool.parameters["properties"]["b"]["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 @pytest.mark.anyio
async def test_async_function(self): async def test_async_function(self):
"""Test registering and running an async function.""" """Test registering and running an async function."""