diff --git a/src/strands/tools/mcp/mcp_client.py b/src/strands/tools/mcp/mcp_client.py index bb5dca19c..7a26cdd6b 100644 --- a/src/strands/tools/mcp/mcp_client.py +++ b/src/strands/tools/mcp/mcp_client.py @@ -330,6 +330,9 @@ async def _set_close_event() -> None: self._log_debug_with_thread("waiting for background thread to join") self._background_thread.join() + if self._background_thread_event_loop is not None: + self._background_thread_event_loop.close() + self._log_debug_with_thread("background thread is closed, MCPClient context exited") # Reset fields to allow instance reuse diff --git a/tests/strands/tools/mcp/test_mcp_client.py b/tests/strands/tools/mcp/test_mcp_client.py index ec77b48a2..e72aebd92 100644 --- a/tests/strands/tools/mcp/test_mcp_client.py +++ b/tests/strands/tools/mcp/test_mcp_client.py @@ -524,6 +524,33 @@ def test_stop_with_background_thread_but_no_event_loop(): assert client._background_thread is None +def test_stop_closes_event_loop(): + """Test that stop() properly closes the event loop when it exists.""" + client = MCPClient(MagicMock()) + + # Mock a background thread with event loop + mock_thread = MagicMock() + mock_thread.join = MagicMock() + mock_event_loop = MagicMock() + mock_event_loop.close = MagicMock() + + client._background_thread = mock_thread + client._background_thread_event_loop = mock_event_loop + + # Should close the event loop and join the thread + client.stop(None, None, None) + + # Verify thread was joined + mock_thread.join.assert_called_once() + + # Verify event loop was closed + mock_event_loop.close.assert_called_once() + + # Verify cleanup occurred + assert client._background_thread is None + assert client._background_thread_event_loop is None + + def test_mcp_client_state_reset_after_timeout(): """Test that all client state is properly reset after timeout."""