Skip to content

Commit 9761fc6

Browse files
wukathcopybara-github
authored andcommitted
fix: Fix McpToolset hanging indefinitely
This fixes McpToolset from hanging indefinitely during a list_tools call (#3084) by adding a timeout. Co-authored-by: Kathy Wu <wukathy@google.com> PiperOrigin-RevId: 829499276
1 parent 0ccc43c commit 9761fc6

File tree

3 files changed

+34
-1
lines changed

3 files changed

+34
-1
lines changed

src/google/adk/tools/mcp_tool/mcp_toolset.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from __future__ import annotations
1616

17+
import asyncio
1718
import logging
1819
import sys
1920
from typing import Callable
@@ -177,7 +178,17 @@ async def get_tools(
177178
session = await self._mcp_session_manager.create_session(headers=headers)
178179

179180
# Fetch available tools from the MCP server
180-
tools_response: ListToolsResult = await session.list_tools()
181+
timeout_in_seconds = (
182+
self._connection_params.timeout
183+
if hasattr(self._connection_params, "timeout")
184+
else None
185+
)
186+
try:
187+
tools_response: ListToolsResult = await asyncio.wait_for(
188+
session.list_tools(), timeout=timeout_in_seconds
189+
)
190+
except Exception as e:
191+
raise ConnectionError("Failed to get tools from MCP server.") from e
181192

182193
# Apply filtering based on context and tool_filter
183194
tools = []

tests/unittests/tools/mcp_tool/test_mcp_toolset.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import asyncio
1516
from io import StringIO
1617
import sys
1718
import unittest
@@ -304,6 +305,26 @@ async def test_close_with_exception(self):
304305
assert "Warning: Error during McpToolset cleanup" in error_output
305306
assert "Cleanup error" in error_output
306307

308+
@pytest.mark.asyncio
309+
async def test_get_tools_with_timeout(self):
310+
"""Test get_tools with timeout."""
311+
stdio_params = StdioConnectionParams(
312+
server_params=self.mock_stdio_params, timeout=0.01
313+
)
314+
toolset = MCPToolset(connection_params=stdio_params)
315+
toolset._mcp_session_manager = self.mock_session_manager
316+
317+
async def long_running_list_tools():
318+
await asyncio.sleep(0.1)
319+
return MockListToolsResult([])
320+
321+
self.mock_session.list_tools = long_running_list_tools
322+
323+
with pytest.raises(
324+
ConnectionError, match="Failed to get tools from MCP server."
325+
):
326+
await toolset.get_tools()
327+
307328
@pytest.mark.asyncio
308329
async def test_get_tools_retry_decorator(self):
309330
"""Test that get_tools has retry decorator applied."""

tests/unittests/tools/test_mcp_toolset.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ async def test_mcp_toolset_with_prefix():
4545
"""Test that McpToolset correctly applies the tool_name_prefix."""
4646
# Mock the connection parameters
4747
mock_connection_params = MagicMock()
48+
mock_connection_params.timeout = None
4849

4950
# Mock the MCPSessionManager and its create_session method
5051
mock_session_manager = MagicMock()

0 commit comments

Comments
 (0)