diff --git a/wayflowcore/tests/integration/tools/test_servertool_decorator.py b/wayflowcore/tests/integration/tools/test_servertool_decorator.py index c7ca8308c..35f4a8239 100644 --- a/wayflowcore/tests/integration/tools/test_servertool_decorator.py +++ b/wayflowcore/tests/integration/tools/test_servertool_decorator.py @@ -471,6 +471,8 @@ def many_params_tool( param9: Annotated[List[Dict[str, int]], "Param 9 desc"] = [{"a": 1}], param10: Annotated[Dict[str, List[str]], "Param 10 desc"] = {"b": ["c"]}, param11: Annotated[int | str, "Param 11 desc"] = "uniontype", + param12_union_pipes: Annotated[int | str | float, "Param 12 desc"] = "a", + param13_optional_pipes: Annotated[int | None, "Param 13 desc"] = None, ) -> Annotated[str, "Output description"]: """Many parameters tool description""" return "result" @@ -546,6 +548,18 @@ def many_params_tool( "anyOf": [{"type": "integer"}, {"type": "string"}], "title": "Param11", }, + "param12_union_pipes": { + 'description': 'Param 12 desc', + 'anyOf': [{'type': 'integer'}, {'type': 'string'}, {'type': 'number'}], + 'default': 'a', + 'title': 'Param12 Union Pipes' + }, + "param13_optional_pipes": { + 'default': None, + 'description': 'Param 13 desc', + 'title': 'Param13 Optional Pipes', + 'type': 'integer' + }, } _check_tool_correctness( diff --git a/wayflowcore/tests/mcptools/start_mcp_server.py b/wayflowcore/tests/mcptools/start_mcp_server.py index 902b715db..21eb6f842 100644 --- a/wayflowcore/tests/mcptools/start_mcp_server.py +++ b/wayflowcore/tests/mcptools/start_mcp_server.py @@ -6,7 +6,7 @@ import argparse from contextvars import ContextVar from os import PathLike -from typing import Annotated, AsyncGenerator, Literal, Optional +from typing import Annotated, AsyncGenerator, Dict, List, Literal, Optional, Union import anyio from mcp.server.fastmcp import Context @@ -128,6 +128,29 @@ def zbuk_tool(a: int, b: int) -> int: def ggwp_tool(a: int, b: int) -> int: return a + b // 2 + @server.tool( + description="This tool is not useful." + ) + def all_input_types_tool( + # basic types + a: int, + b: float, + c: str, + d: bool, + # complext types + e: List[int], + f: list[bool], + g: Dict[str, int], + h: dict[str, int], + i: Optional[str], + j: int | None, + k: Union[str, int, float], + l: str | int | float, + # complex compositions + m: List[Dict[str | float, Optional[int]]] + ) -> float: + return a + b / 2 + @server.tool(description="Tool to return a random string") def generate_random_string() -> str: import random diff --git a/wayflowcore/tests/mcptools/test_mcp_tools.py b/wayflowcore/tests/mcptools/test_mcp_tools.py index c32cf7612..d34efe173 100644 --- a/wayflowcore/tests/mcptools/test_mcp_tools.py +++ b/wayflowcore/tests/mcptools/test_mcp_tools.py @@ -38,6 +38,7 @@ AnyProperty, BooleanProperty, DictProperty, + FloatProperty, IntegerProperty, ListProperty, NullProperty, @@ -128,15 +129,57 @@ def streamablehttp_client_transport_mtls( ssl_ca_cert=ca_cert_path, ) - def run_toolbox_test(transport: ClientTransport) -> None: toolbox = MCPToolBox(client_transport=transport) tools = toolbox.get_tools() # need - assert len(tools) == 17 + assert len(tools) == 18 + + # check some tool signatures mcp_tool = next(t for t in tools if t.name == "fooza_tool") assert mcp_tool.run(a=1, b=2) == "7" assert mcp_tool.input_descriptors == [IntegerProperty(name="a"), IntegerProperty(name="b")] + all_input_types_tool = next(t for t in tools if t.name == "all_input_types_tool") + + expected_inputs = [ + # basic types + IntegerProperty(name="a"), + FloatProperty(name="b"), + StringProperty(name="c"), + BooleanProperty(name="d"), + # complex types + ListProperty(name="e", item_type = IntegerProperty()), + ListProperty(name="f", item_type = BooleanProperty()), + # Ideally it would be: + # DictProperty(name="g", key_type = StringProperty(), value_type = IntegerProperty()), + # DictProperty(name="h", key_type = StringProperty(), value_type = IntegerProperty()), + # But key types are not provided by the MCP servers: + DictProperty(name="g", key_type = AnyProperty(), value_type = IntegerProperty()), + DictProperty(name="h", key_type = AnyProperty(), value_type = IntegerProperty()), + UnionProperty(name="i", any_of = [StringProperty(), NullProperty()]), + UnionProperty(name="j", any_of = [IntegerProperty(), NullProperty()]), + UnionProperty(name="k", any_of = [StringProperty(), IntegerProperty(), FloatProperty()]), + UnionProperty(name="l", any_of = [StringProperty(), IntegerProperty(), FloatProperty()]), + + # Ideally it would be: + # ListProperty( + # name="m", + # item_type=DictProperty( + # key_type=UnionProperty(any_of=[StringProperty(), FloatProperty()]), + # value_type=UnionProperty(any_of=[IntegerProperty(), NullProperty()]) + # ) + # ), + # But MCP doesn't pass the key type for dictionaries + ListProperty( + name="m", + item_type=DictProperty( + key_type=AnyProperty(), + value_type=UnionProperty(any_of=[IntegerProperty(), NullProperty()]) + ) + ), + ] + assert all_input_types_tool.input_descriptors == expected_inputs + @pytest.mark.parametrize( "client_transport_name", @@ -313,7 +356,7 @@ async def test_mcp_toolboxes_from_different_servers_do_not_conflict_in_same_agen # We call this method to actively retrieve tools from the lazy toolboxes all_agent_tools = await AgentConversationExecutor._collect_tools(config=agent, curr_iter=0) # 17 from toolbox 1, 2 from toolbox 2 - assert len(all_agent_tools) == 17 + 2 + assert len(all_agent_tools) == 18 + 2 # Ensure that one tool from the first toolbox and one from the second are in the list tool_names = set(tool.name for tool in all_agent_tools) assert all(tool_name in tool_names for tool_name in ["fooza_tool", "alt_mul_tool"])