From d3a6551ff6c7c6dd4427ea3470004a4fdacc6e69 Mon Sep 17 00:00:00 2001 From: kllw538_azu Date: Mon, 5 Jan 2026 15:23:22 +0000 Subject: [PATCH 1/3] Fix tool search to support substring matching in name and ID The get_tools(name=query) bioblend API call does not support substring matching and returns empty results for partial matches. This fix changes search_tools_by_name() to: 1. Fetch all tools using get_tools() without the name parameter 2. Filter client-side for substring matches in both tool name and ID 3. Support case-insensitive matching This ensures tools like 'celltypist_predict' are found when searching for 'celltypist', matching the behavior of search_tools_by_keywords. Updated tests to reflect the new behavior where get_tools() is called without parameters and filtering happens client-side. --- mcp-server-galaxy-py/src/galaxy_mcp/server.py | 20 ++++++-- .../tests/test_tool_operations.py | 47 ++++++++++--------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/mcp-server-galaxy-py/src/galaxy_mcp/server.py b/mcp-server-galaxy-py/src/galaxy_mcp/server.py index 46259ba..afb7fd0 100644 --- a/mcp-server-galaxy-py/src/galaxy_mcp/server.py +++ b/mcp-server-galaxy-py/src/galaxy_mcp/server.py @@ -342,10 +342,10 @@ def connect(url: str | None = None, api_key: str | None = None) -> dict[str, Any @mcp.tool() def search_tools_by_name(query: str) -> dict[str, Any]: """ - Search Galaxy tools whose name contains the given query (substring match). + Search Galaxy tools whose name or ID contains the given query (substring match). Args: - query: Search query (tool name to filter on) + query: Search query (tool name or ID to filter on) Returns: List of tools matching the query @@ -354,9 +354,19 @@ def search_tools_by_name(query: str) -> dict[str, Any]: gi: GalaxyInstance = state["gi"] try: - # The get_tools method is used with name filter parameter - tools = gi.tools.get_tools(name=query) - return {"tools": tools} + # Get all tools and filter client-side for substring matching + # The get_tools(name=query) parameter doesn't support substring matching + all_tools = gi.tools.get_tools() + query_lower = query.lower() + + # Filter tools by substring match in name or ID + matching_tools = [ + tool for tool in all_tools + if query_lower in tool.get("name", "").lower() + or query_lower in tool.get("id", "").lower() + ] + + return {"tools": matching_tools} except Exception as e: raise ValueError(format_error("Search tools", e, {"query": query})) from e diff --git a/mcp-server-galaxy-py/tests/test_tool_operations.py b/mcp-server-galaxy-py/tests/test_tool_operations.py index a697f9a..b741d1f 100644 --- a/mcp-server-galaxy-py/tests/test_tool_operations.py +++ b/mcp-server-galaxy-py/tests/test_tool_operations.py @@ -20,21 +20,29 @@ class TestToolOperations: def test_search_tools_fn(self, mock_galaxy_instance): """Test tool search functionality""" with patch.dict(galaxy_state, {"connected": True, "gi": mock_galaxy_instance}): - # Search should return dict with 'tools' key + # Mock get_tools to return all tools (no name parameter) + mock_galaxy_instance.tools.get_tools.return_value = [ + {"id": "tool1", "name": "Test Tool 1", "description": "Aligns sequences"}, + {"id": "tool2", "name": "Test Tool 2", "description": "Other tool"} + ] + + # Search with empty query should return all tools result = search_tools_fn("") assert "tools" in result assert len(result["tools"]) == 2 assert result["tools"][0]["id"] == "tool1" - # Search with query - mock_galaxy_instance.tools.get_tools.return_value = [ - {"id": "tool1", "name": "Test Tool 1", "description": "Aligns sequences"} - ] - - result = search_tools_fn("align") + # Search with query should filter by name substring + result = search_tools_fn("tool 1") assert "tools" in result assert len(result["tools"]) == 1 - assert "align" in result["tools"][0]["description"].lower() + assert result["tools"][0]["id"] == "tool1" + + # Search should also filter by ID substring + result = search_tools_fn("tool2") + assert "tools" in result + assert len(result["tools"]) == 1 + assert result["tools"][0]["id"] == "tool2" def test_search_tools_with_results(self, mock_galaxy_instance): """Test search tools returns filtered results""" @@ -45,25 +53,22 @@ def test_search_tools_with_results(self, mock_galaxy_instance): ] with patch.dict(galaxy_state, {"connected": True, "gi": mock_galaxy_instance}): - # Mock filtering behavior - def mock_get_tools(name=None): - if name and name.lower() == "align": - return [ - t - for t in all_tools - if "align" in t["name"].lower() or "align" in t["description"].lower() - ] - return all_tools - - mock_galaxy_instance.tools.get_tools.side_effect = mock_get_tools - - # Search for aligners + # Mock get_tools to return all tools + mock_galaxy_instance.tools.get_tools.return_value = all_tools + + # Search for aligners by name substring result = search_tools_fn("align") assert "tools" in result aligners = result["tools"] assert len(aligners) == 2 assert any("BWA" in t["name"] for t in aligners) assert any("HISAT2" in t["name"] for t in aligners) + + # Search by ID substring + result = search_tools_fn("tool1") + assert "tools" in result + assert len(result["tools"]) == 1 + assert result["tools"][0]["id"] == "tool1" def test_run_tool_fn(self, mock_galaxy_instance): """Test running a tool""" From 137352ae95b43dd77f834ed172343c1916df0ce4 Mon Sep 17 00:00:00 2001 From: kllw538_azu Date: Mon, 5 Jan 2026 15:40:40 +0000 Subject: [PATCH 2/3] Fix code formatting issues (trailing whitespace and ruff format) --- mcp-server-galaxy-py/src/galaxy_mcp/server.py | 9 +++++---- mcp-server-galaxy-py/tests/test_tool_operations.py | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/mcp-server-galaxy-py/src/galaxy_mcp/server.py b/mcp-server-galaxy-py/src/galaxy_mcp/server.py index afb7fd0..e18b5ce 100644 --- a/mcp-server-galaxy-py/src/galaxy_mcp/server.py +++ b/mcp-server-galaxy-py/src/galaxy_mcp/server.py @@ -358,14 +358,15 @@ def search_tools_by_name(query: str) -> dict[str, Any]: # The get_tools(name=query) parameter doesn't support substring matching all_tools = gi.tools.get_tools() query_lower = query.lower() - + # Filter tools by substring match in name or ID matching_tools = [ - tool for tool in all_tools - if query_lower in tool.get("name", "").lower() + tool + for tool in all_tools + if query_lower in tool.get("name", "").lower() or query_lower in tool.get("id", "").lower() ] - + return {"tools": matching_tools} except Exception as e: raise ValueError(format_error("Search tools", e, {"query": query})) from e diff --git a/mcp-server-galaxy-py/tests/test_tool_operations.py b/mcp-server-galaxy-py/tests/test_tool_operations.py index b741d1f..b0c13a4 100644 --- a/mcp-server-galaxy-py/tests/test_tool_operations.py +++ b/mcp-server-galaxy-py/tests/test_tool_operations.py @@ -23,9 +23,9 @@ def test_search_tools_fn(self, mock_galaxy_instance): # Mock get_tools to return all tools (no name parameter) mock_galaxy_instance.tools.get_tools.return_value = [ {"id": "tool1", "name": "Test Tool 1", "description": "Aligns sequences"}, - {"id": "tool2", "name": "Test Tool 2", "description": "Other tool"} + {"id": "tool2", "name": "Test Tool 2", "description": "Other tool"}, ] - + # Search with empty query should return all tools result = search_tools_fn("") assert "tools" in result @@ -37,7 +37,7 @@ def test_search_tools_fn(self, mock_galaxy_instance): assert "tools" in result assert len(result["tools"]) == 1 assert result["tools"][0]["id"] == "tool1" - + # Search should also filter by ID substring result = search_tools_fn("tool2") assert "tools" in result @@ -63,7 +63,7 @@ def test_search_tools_with_results(self, mock_galaxy_instance): assert len(aligners) == 2 assert any("BWA" in t["name"] for t in aligners) assert any("HISAT2" in t["name"] for t in aligners) - + # Search by ID substring result = search_tools_fn("tool1") assert "tools" in result From 176a00c53e98df3cd40322bd536283519be21f42 Mon Sep 17 00:00:00 2001 From: kllw538_azu Date: Mon, 5 Jan 2026 16:41:22 +0000 Subject: [PATCH 3/3] Also search in tool description field for better discoverability --- mcp-server-galaxy-py/src/galaxy_mcp/server.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mcp-server-galaxy-py/src/galaxy_mcp/server.py b/mcp-server-galaxy-py/src/galaxy_mcp/server.py index e18b5ce..b45f7dd 100644 --- a/mcp-server-galaxy-py/src/galaxy_mcp/server.py +++ b/mcp-server-galaxy-py/src/galaxy_mcp/server.py @@ -342,10 +342,10 @@ def connect(url: str | None = None, api_key: str | None = None) -> dict[str, Any @mcp.tool() def search_tools_by_name(query: str) -> dict[str, Any]: """ - Search Galaxy tools whose name or ID contains the given query (substring match). + Search Galaxy tools whose name, ID, or description contains the given query (substring match). Args: - query: Search query (tool name or ID to filter on) + query: Search query (tool name, ID, or description to filter on) Returns: List of tools matching the query @@ -359,12 +359,13 @@ def search_tools_by_name(query: str) -> dict[str, Any]: all_tools = gi.tools.get_tools() query_lower = query.lower() - # Filter tools by substring match in name or ID + # Filter tools by substring match in name, ID, or description matching_tools = [ tool for tool in all_tools if query_lower in tool.get("name", "").lower() or query_lower in tool.get("id", "").lower() + or query_lower in tool.get("description", "").lower() ] return {"tools": matching_tools}