@@ -143,6 +143,18 @@ def stop(
143143 This method is defensive and can handle partial initialization states that may occur
144144 if start() fails partway through initialization.
145145
146+ Resources to cleanup:
147+ - _background_thread: Thread running the async event loop
148+ - _background_thread_session: MCP ClientSession (auto-closed by context manager)
149+ - _background_thread_event_loop: AsyncIO event loop in background thread
150+ - _close_event: AsyncIO event to signal thread shutdown
151+ - _init_future: Future for initialization synchronization
152+
153+ Cleanup order:
154+ 1. Signal close event to background thread (if session initialized)
155+ 2. Wait for background thread to complete
156+ 3. Reset all state for reuse
157+
146158 Args:
147159 exc_type: Exception type if an exception was raised in the context
148160 exc_val: Exception value if an exception was raised in the context
@@ -158,7 +170,9 @@ def stop(
158170 async def _set_close_event () -> None :
159171 self ._close_event .set ()
160172
161- self ._invoke_on_background_thread (_set_close_event ()).result ()
173+ # Not calling _invoke_on_background_thread since the session does not need to exist
174+ # we only need the thread and event loop to exist.
175+ asyncio .run_coroutine_threadsafe (coro = _set_close_event (), loop = self ._background_thread_event_loop )
162176
163177 self ._log_debug_with_thread ("waiting for background thread to join" )
164178 self ._background_thread .join ()
@@ -168,6 +182,8 @@ async def _set_close_event() -> None:
168182 self ._init_future = futures .Future ()
169183 self ._close_event = asyncio .Event ()
170184 self ._background_thread = None
185+ self ._background_thread_session = None
186+ self ._background_thread_event_loop = None
171187 self ._session_id = uuid .uuid4 ()
172188
173189 def list_tools_sync (self , pagination_token : Optional [str ] = None ) -> PaginatedList [MCPAgentTool ]:
0 commit comments