From 8c833ab0bc9e800c1ed2b2d32e70198f88471cb1 Mon Sep 17 00:00:00 2001 From: Morrow588 Date: Tue, 13 Oct 2026 21:58:02 +0500 Subject: [PATCH 1/3] fix(custom-tools): allow required parameters after optional ones --- Server/src/services/custom_tool_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/services/custom_tool_service.py b/Server/src/services/custom_tool_service.py index f81b7c70e..36685e3bb 100644 --- a/Server/src/services/custom_tool_service.py +++ b/Server/src/services/custom_tool_service.py @@ -433,7 +433,7 @@ def _build_signature(self, definition: ToolDefinitionModel) -> inspect.Signature params.append( inspect.Parameter( param.name, - inspect.Parameter.POSITIONAL_OR_KEYWORD, + inspect.Parameter.KEYWORD_ONLY, default=default, annotation=self._map_param_type(param), ) From becf6787317673650a2ac51292153ed7fcf6498f Mon Sep 17 00:00:00 2001 From: Morrow588 Date: Tue, 13 Oct 2026 21:59:16 +0500 Subject: [PATCH 2/3] test(custom-tools): cover mixed parameter ordering --- .../tests/test_custom_tool_parameter_order.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Server/tests/test_custom_tool_parameter_order.py diff --git a/Server/tests/test_custom_tool_parameter_order.py b/Server/tests/test_custom_tool_parameter_order.py new file mode 100644 index 000000000..726a5a6c3 --- /dev/null +++ b/Server/tests/test_custom_tool_parameter_order.py @@ -0,0 +1,45 @@ +import inspect +from unittest.mock import Mock + +import pytest + +from models.models import ToolDefinitionModel, ToolParameterModel +from services.custom_tool_service import CustomToolService + + +@pytest.mark.parametrize("optional_first", [True, False]) +def test_custom_tool_registration_preserves_required_and_optional_parameters( + monkeypatch, optional_first +): + monkeypatch.setattr(CustomToolService, "_instance", None) + mcp = Mock() + registered = {} + + def register(**kwargs): + def decorator(handler): + registered[kwargs["name"]] = handler + return handler + return decorator + + mcp.tool.side_effect = register + service = CustomToolService(mcp) + optional = ToolParameterModel( + name="limit", type="integer", required=False, default_value="10" + ) + required = ToolParameterModel(name="action", required=True) + parameters = [optional, required] if optional_first else [required, optional] + definition = ToolDefinitionModel(name="ordered_tool", parameters=parameters) + + service.register_global_tools([definition]) + + assert "ordered_tool" in registered + signature = inspect.signature(registered["ordered_tool"]) + assert signature.parameters["action"].default is inspect.Parameter.empty + assert signature.parameters["action"].annotation is str + assert signature.parameters["limit"].annotation is int + assert list(signature.parameters) == ["ctx"] + [p.name for p in parameters] + bound = signature.bind(object(), action="list") + bound.apply_defaults() + assert bound.arguments["limit"] == 10 + with pytest.raises(TypeError, match="action"): + signature.bind(object()) From 4657a408e54161a703db7bcf8698cc308e9a51ea Mon Sep 17 00:00:00 2001 From: Shutong Wu <51266340+Scriptwonder@users.noreply.github.com> Date: Sat, 17 Oct 2026 16:59:26 -0400 Subject: [PATCH 3/3] Delete Server/tests/test_custom_tool_parameter_order.py --- .../tests/test_custom_tool_parameter_order.py | 45 ------------------- 1 file changed, 45 deletions(-) delete mode 100644 Server/tests/test_custom_tool_parameter_order.py diff --git a/Server/tests/test_custom_tool_parameter_order.py b/Server/tests/test_custom_tool_parameter_order.py deleted file mode 100644 index 726a5a6c3..000000000 --- a/Server/tests/test_custom_tool_parameter_order.py +++ /dev/null @@ -1,45 +0,0 @@ -import inspect -from unittest.mock import Mock - -import pytest - -from models.models import ToolDefinitionModel, ToolParameterModel -from services.custom_tool_service import CustomToolService - - -@pytest.mark.parametrize("optional_first", [True, False]) -def test_custom_tool_registration_preserves_required_and_optional_parameters( - monkeypatch, optional_first -): - monkeypatch.setattr(CustomToolService, "_instance", None) - mcp = Mock() - registered = {} - - def register(**kwargs): - def decorator(handler): - registered[kwargs["name"]] = handler - return handler - return decorator - - mcp.tool.side_effect = register - service = CustomToolService(mcp) - optional = ToolParameterModel( - name="limit", type="integer", required=False, default_value="10" - ) - required = ToolParameterModel(name="action", required=True) - parameters = [optional, required] if optional_first else [required, optional] - definition = ToolDefinitionModel(name="ordered_tool", parameters=parameters) - - service.register_global_tools([definition]) - - assert "ordered_tool" in registered - signature = inspect.signature(registered["ordered_tool"]) - assert signature.parameters["action"].default is inspect.Parameter.empty - assert signature.parameters["action"].annotation is str - assert signature.parameters["limit"].annotation is int - assert list(signature.parameters) == ["ctx"] + [p.name for p in parameters] - bound = signature.bind(object(), action="list") - bound.apply_defaults() - assert bound.arguments["limit"] == 10 - with pytest.raises(TypeError, match="action"): - signature.bind(object())