diff --git a/navi/main.py b/navi/main.py index 080a7fd..2e0b849 100644 --- a/navi/main.py +++ b/navi/main.py @@ -133,6 +133,8 @@ if _mcp_manager is not None: try: await _mcp_manager.disconnect_all() + except (asyncio.CancelledError, RuntimeError): + pass except Exception: pass diff --git a/navi/mcp/client.py b/navi/mcp/client.py index 78191c8..d0f42cc 100644 --- a/navi/mcp/client.py +++ b/navi/mcp/client.py @@ -92,7 +92,12 @@ """Close transport and reset state.""" if not self._connected: return - await self._cleanup() + try: + await self._cleanup() + except (asyncio.CancelledError, RuntimeError): + # Graceful shutdown during app teardown — SSE transport teardown + # throws CancelledError / RuntimeError from anyio task scopes. + pass async def _cleanup(self) -> None: try: diff --git a/navi/mcp/manager.py b/navi/mcp/manager.py index 6ab1603..e52af03 100644 --- a/navi/mcp/manager.py +++ b/navi/mcp/manager.py @@ -59,13 +59,17 @@ """Close every open connection and clear the client pool.""" if not self._clients: return - results = await asyncio.gather( - *[c.disconnect() for c in self._clients.values()], - return_exceptions=True, - ) - for name, exc in zip(self._clients, results): - if isinstance(exc, Exception): - logger.warning("MCP server %r disconnect error: %s", name, exc) + try: + results = await asyncio.gather( + *[c.disconnect() for c in self._clients.values()], + return_exceptions=True, + ) + for name, exc in zip(self._clients, results): + if isinstance(exc, Exception): + logger.warning("MCP server %r disconnect error: %s", name, exc) + except (asyncio.CancelledError, RuntimeError): + # Shutdown-time cancellation from anyio task scopes — safe to ignore + pass self._clients.clear() async def get_all_tools(self) -> list[tuple[str, Any]]: