cc21c4f78f
_stop_mcp_loop() stopped and closed the background loop without reaping
the tasks still on it. A task left suspended is resumed later by the GC,
whose finalizer drives its cleanup against the now-closed loop:
Exception ignored in: <coroutine object MCPServerTask.run ...>
File "tools/mcp_tool.py", line 2947, in run
parked = await self._wait_for_reconnect_or_shutdown(
File "tools/mcp_tool.py", line 2161, in _wait_for_reconnect_or_shutdown
t.cancel()
RuntimeError: Event loop is closed
shutdown_mcp_servers() only reaps servers held in _servers, so a server
that parked after exhausting its initial-connect budget — never inserted
there, because start() raises _error before the caller registers it — has
no owner to signal it and stays suspended until the loop is gone.
Drain the loop the way asyncio.run() does: cancel the remaining tasks and
gather them while the loop is still open, so each runs its own finally.
Cancel alone is not enough — Task.cancel() only schedules the throw.
This resolves the reported traceback, but not the ownership bug that
strands the task in the first place; that needs a follow-up. Deliberately
not using "Fixes" so #60197 stays open for it.
Addresses #60197
Addresses #66113
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>