diff --git a/fastmcp_slim/fastmcp/tools/function_parsing.py b/fastmcp_slim/fastmcp/tools/function_parsing.py index 9f653a336..79967f9a2 100644 --- a/fastmcp_slim/fastmcp/tools/function_parsing.py +++ b/fastmcp_slim/fastmcp/tools/function_parsing.py @@ -182,8 +182,16 @@ class ParsedFunction: ) -> ParsedFunction: if validate: sig = inspect.signature(fn) - # Reject functions with *args or **kwargs + # Reject signatures that cannot be represented by MCP's + # object-shaped tool arguments. for param in sig.parameters.values(): + if param.kind == inspect.Parameter.POSITIONAL_ONLY: + raise ValueError( + "Functions with positional-only parameters are not " + "supported as tools because MCP passes tool arguments by " + "name. Replace them with standard parameters that can be " + "passed as keywords." + ) if param.kind == inspect.Parameter.VAR_POSITIONAL: raise ValueError("Functions with *args are not supported as tools") if param.kind == inspect.Parameter.VAR_KEYWORD: diff --git a/tests/tools/tool/test_tool.py b/tests/tools/tool/test_tool.py index d877b7046..b6165cb5e 100644 --- a/tests/tools/tool/test_tool.py +++ b/tests/tools/tool/test_tool.py @@ -341,6 +341,32 @@ class TestToolFromFunction: ): Tool.from_function(func) + def test_tool_with_positional_only_parameters_not_allowed(self): + def func(a: int, /, b: int) -> int: + return a + b + + with pytest.raises( + ValueError, + match=( + "Functions with positional-only parameters are not supported as " + "tools.*standard parameters" + ), + ): + Tool.from_function(func) + + def test_tool_with_keyword_capable_parameters(self): + def func(a: int, *, b: int) -> int: + return a + b + + tool = Tool.from_function(func) + + assert tool.parameters["type"] == "object" + assert tool.parameters["required"] == ["a", "b"] + assert tool.parameters["properties"] == { + "a": {"type": "integer"}, + "b": {"type": "integer"}, + } + def test_tool_with_varkwargs_not_allowed(self): def func(a: int, b: int, **kwargs: int) -> int: """Add two numbers."""