From 15f0ac750610aefdc06daba87d9881a9f6a17071 Mon Sep 17 00:00:00 2001 From: David Padbury Date: Thu, 11 Dec 2025 17:40:19 -0600 Subject: [PATCH 1/2] fix: close mcp client event loop --- src/strands/tools/mcp/mcp_client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/strands/tools/mcp/mcp_client.py b/src/strands/tools/mcp/mcp_client.py index bb5dca19c..7aa57a39f 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 From 169b62476b08582e672a2be7671ba71fe597f677 Mon Sep 17 00:00:00 2001 From: Dean Schmigelski Date: Fri, 12 Dec 2025 10:21:06 -0500 Subject: [PATCH 2/2] feat: close event loop regardless of if thread exists --- src/strands/tools/mcp/mcp_client.py | 4 ++-- tests/strands/tools/mcp/test_mcp_client.py | 27 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/strands/tools/mcp/mcp_client.py b/src/strands/tools/mcp/mcp_client.py index 7aa57a39f..7a26cdd6b 100644 --- a/src/strands/tools/mcp/mcp_client.py +++ b/src/strands/tools/mcp/mcp_client.py @@ -330,8 +330,8 @@ 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() + 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") 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."""