Skip to content

Commit d6c3da9

Browse files
committed
fastmcp: allow passing Tool directly to .add_tool
1 parent 1cb7407 commit d6c3da9

3 files changed

Lines changed: 69 additions & 11 deletions

File tree

src/mcp/server/fastmcp/server.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
asynccontextmanager,
1111
)
1212
from itertools import chain
13-
from typing import Any, Generic, Literal
13+
from typing import Any, Generic, Literal, overload
1414

1515
import anyio
1616
import pydantic_core
@@ -37,7 +37,7 @@
3737
from mcp.server.fastmcp.exceptions import ResourceError
3838
from mcp.server.fastmcp.prompts import Prompt, PromptManager
3939
from mcp.server.fastmcp.resources import FunctionResource, Resource, ResourceManager
40-
from mcp.server.fastmcp.tools import ToolManager
40+
from mcp.server.fastmcp.tools import Tool, ToolManager
4141
from mcp.server.fastmcp.utilities.logging import configure_logging, get_logger
4242
from mcp.server.fastmcp.utilities.types import Image
4343
from mcp.server.lowlevel.helper_types import ReadResourceContents
@@ -315,27 +315,42 @@ async def read_resource(self, uri: AnyUrl | str) -> Iterable[ReadResourceContent
315315
logger.error(f"Error reading resource {uri}: {e}")
316316
raise ResourceError(str(e))
317317

318+
@overload
319+
def add_tool(self, fn: Tool) -> None: ...
320+
321+
@overload
318322
def add_tool(
319323
self,
320324
fn: AnyFunction,
321325
name: str | None = None,
322326
description: str | None = None,
323327
annotations: ToolAnnotations | None = None,
328+
) -> None: ...
329+
330+
def add_tool(
331+
self,
332+
fn: AnyFunction | Tool,
333+
name: str | None = None,
334+
description: str | None = None,
335+
annotations: ToolAnnotations | None = None,
324336
) -> None:
325337
"""Add a tool to the server.
326338
327339
The tool function can optionally request a Context object by adding a parameter
328340
with the Context type annotation. See the @tool decorator for examples.
329341
330342
Args:
331-
fn: The function to register as a tool
343+
fn: The function to register as a tool or a Tool instance
332344
name: Optional name for the tool (defaults to function name)
333345
description: Optional description of what the tool does
334346
annotations: Optional ToolAnnotations providing additional tool information
335347
"""
336-
self._tool_manager.add_tool(
337-
fn, name=name, description=description, annotations=annotations
338-
)
348+
if isinstance(fn, Tool):
349+
self._tool_manager.add_tool(fn)
350+
else:
351+
self._tool_manager.add_tool(
352+
fn, name=name, description=description, annotations=annotations
353+
)
339354

340355
def tool(
341356
self,

src/mcp/server/fastmcp/tools/tool_manager.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations as _annotations
22

33
from collections.abc import Callable
4-
from typing import TYPE_CHECKING, Any
4+
from typing import TYPE_CHECKING, Any, overload
55

66
from mcp.server.fastmcp.exceptions import ToolError
77
from mcp.server.fastmcp.tools.base import Tool
@@ -31,17 +31,35 @@ def list_tools(self) -> list[Tool]:
3131
"""List all registered tools."""
3232
return list(self._tools.values())
3333

34+
@overload
35+
def add_tool(
36+
self,
37+
fn: Tool,
38+
) -> Tool: ...
39+
40+
@overload
3441
def add_tool(
3542
self,
3643
fn: Callable[..., Any],
3744
name: str | None = None,
3845
description: str | None = None,
3946
annotations: ToolAnnotations | None = None,
47+
) -> Tool: ...
48+
49+
def add_tool(
50+
self,
51+
fn: Callable[..., Any] | Tool,
52+
name: str | None = None,
53+
description: str | None = None,
54+
annotations: ToolAnnotations | None = None,
4055
) -> Tool:
4156
"""Add a tool to the server."""
42-
tool = Tool.from_function(
43-
fn, name=name, description=description, annotations=annotations
44-
)
57+
if isinstance(fn, Tool):
58+
tool = fn
59+
else:
60+
tool = Tool.from_function(
61+
fn, name=name, description=description, annotations=annotations
62+
)
4563
existing = self._tools.get(tool.name)
4664
if existing:
4765
if self.warn_on_duplicate_tools:

tests/server/fastmcp/test_tool_manager.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
from mcp.server.fastmcp import Context, FastMCP
88
from mcp.server.fastmcp.exceptions import ToolError
9-
from mcp.server.fastmcp.tools import ToolManager
9+
from mcp.server.fastmcp.tools import Tool, ToolManager
10+
from mcp.server.fastmcp.utilities.func_metadata import ArgModelBase, FuncMetadata
1011
from mcp.server.session import ServerSessionT
1112
from mcp.shared.context import LifespanContextT
1213
from mcp.types import ToolAnnotations
@@ -31,6 +32,30 @@ def add(a: int, b: int) -> int:
3132
assert tool.parameters["properties"]["a"]["type"] == "integer"
3233
assert tool.parameters["properties"]["b"]["type"] == "integer"
3334

35+
def test_add_tool_directly(self):
36+
manager = ToolManager()
37+
38+
def add(a: int, b: int) -> int:
39+
return a + b
40+
41+
class AddArguments(ArgModelBase):
42+
a: int
43+
b: int
44+
45+
fn_metadata = FuncMetadata(arg_model=AddArguments)
46+
47+
original_tool = Tool(
48+
name="add",
49+
description="Add two numbers.",
50+
fn=add,
51+
fn_metadata=fn_metadata,
52+
is_async=False,
53+
parameters=AddArguments.model_json_schema(),
54+
)
55+
manager.add_tool(original_tool)
56+
saved_tool = manager.get_tool("add")
57+
assert saved_tool == original_tool
58+
3459
@pytest.mark.anyio
3560
async def test_async_function(self):
3661
"""Test registering and running an async function."""

0 commit comments

Comments
 (0)