mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-09-01 03:53:19 +02:00
Co-authored-by: Bill Easton <strawgate@users.noreply.github.com>
🤖 Generated with Claude Code
130 lines
4.4 KiB
Python
130 lines
4.4 KiB
Python
"""Tests for functools.partial support as tools.
|
|
|
|
See https://github.com/PrefectHQ/fastmcp/issues/3266
|
|
"""
|
|
|
|
import functools
|
|
|
|
from mcp.types import TextContent
|
|
|
|
from fastmcp import FastMCP
|
|
from fastmcp.tools.tool import Tool
|
|
|
|
|
|
class TestPartialTool:
|
|
"""Test tools created from functools.partial objects."""
|
|
|
|
async def test_partial_sync(self):
|
|
"""Test that a sync functools.partial works as a tool."""
|
|
|
|
def add(x: int, y: int) -> int:
|
|
return x + y
|
|
|
|
partial_add = functools.partial(add, y=10)
|
|
functools.update_wrapper(partial_add, add)
|
|
|
|
tool = Tool.from_function(partial_add)
|
|
result = await tool.run({"x": 5})
|
|
assert result.content == [TextContent(type="text", text="15")]
|
|
|
|
async def test_partial_async(self):
|
|
"""Test that an async functools.partial works as a tool."""
|
|
|
|
async def multiply(x: int, factor: int) -> int:
|
|
return x * factor
|
|
|
|
partial_mul = functools.partial(multiply, factor=3)
|
|
functools.update_wrapper(partial_mul, multiply)
|
|
|
|
tool = Tool.from_function(partial_mul)
|
|
result = await tool.run({"x": 7})
|
|
assert result.content == [TextContent(type="text", text="21")]
|
|
|
|
async def test_partial_preserves_name(self):
|
|
"""Test that the tool name comes from the wrapped function."""
|
|
|
|
def greet(name: str, greeting: str = "Hello") -> str:
|
|
"""Greet someone."""
|
|
return f"{greeting}, {name}!"
|
|
|
|
partial_greet = functools.partial(greet, greeting="Hi")
|
|
functools.update_wrapper(partial_greet, greet)
|
|
|
|
tool = Tool.from_function(partial_greet)
|
|
assert tool.name == "greet"
|
|
assert tool.description == "Greet someone."
|
|
|
|
async def test_partial_custom_name(self):
|
|
"""Test that a custom name overrides the partial's wrapped name."""
|
|
|
|
def compute(x: int, op: str) -> str:
|
|
return f"{op}({x})"
|
|
|
|
partial_fn = functools.partial(compute, op="square")
|
|
functools.update_wrapper(partial_fn, compute)
|
|
|
|
tool = Tool.from_function(partial_fn, name="square")
|
|
assert tool.name == "square"
|
|
|
|
async def test_partial_schema_shows_bound_args_as_optional(self):
|
|
"""Test that bound arguments appear as optional with default values."""
|
|
|
|
def process(a: int, b: str, c: float = 1.0) -> str:
|
|
return f"{a}-{b}-{c}"
|
|
|
|
partial_fn = functools.partial(process, b="fixed")
|
|
functools.update_wrapper(partial_fn, process)
|
|
|
|
tool = Tool.from_function(partial_fn)
|
|
props = tool.parameters.get("properties", {})
|
|
required = tool.parameters.get("required", [])
|
|
assert "a" in props
|
|
assert "c" in props
|
|
# b is bound by the partial so it appears as optional with its
|
|
# bound value as the default
|
|
assert "b" in props
|
|
assert props["b"]["default"] == "fixed"
|
|
assert "b" not in required
|
|
|
|
async def test_partial_without_update_wrapper(self):
|
|
"""Test that functools.partial works without update_wrapper."""
|
|
|
|
def add(x: int, y: int) -> int:
|
|
return x + y
|
|
|
|
partial_add = functools.partial(add, y=10)
|
|
# No update_wrapper call — name comes from the partial class
|
|
|
|
tool = Tool.from_function(partial_add, name="add_ten")
|
|
result = await tool.run({"x": 5})
|
|
assert result.content == [TextContent(type="text", text="15")]
|
|
|
|
async def test_partial_with_add_tool(self):
|
|
"""Test registering a functools.partial via mcp.add_tool()."""
|
|
mcp = FastMCP("test")
|
|
|
|
def greet(name: str, greeting: str = "Hello") -> str:
|
|
return f"{greeting}, {name}!"
|
|
|
|
partial_greet = functools.partial(greet, greeting="Hey")
|
|
functools.update_wrapper(partial_greet, greet)
|
|
|
|
mcp.add_tool(partial_greet)
|
|
|
|
result = await mcp.call_tool("greet", {"name": "World"})
|
|
assert result.content == [TextContent(type="text", text="Hey, World!")]
|
|
|
|
async def test_partial_with_server_tool_decorator(self):
|
|
"""Test registering a functools.partial via mcp.tool()."""
|
|
mcp = FastMCP("test")
|
|
|
|
def add(x: int, y: int) -> int:
|
|
return x + y
|
|
|
|
partial_add = functools.partial(add, y=100)
|
|
functools.update_wrapper(partial_add, add)
|
|
|
|
mcp.tool(partial_add)
|
|
|
|
result = await mcp.call_tool("add", {"x": 5})
|
|
assert result.content == [TextContent(type="text", text="105")]
|