Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9c6dc85

Browse files
lorenss-mCopilot
andauthored
Fix subprocess cleanup for multiple MCP clients (#231)
* init * add logging callback * cleaner shutdown of multiple clients * linting * do not clear manager and fix stop for http error * http fallback change * Update mcp_use/connectors/http.py Co-authored-by: Copilot <[email protected]> Signed-off-by: lorenss <[email protected]> --------- Signed-off-by: lorenss <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 445cfde commit 9c6dc85

File tree

3 files changed

+46
-35
lines changed

3 files changed

+46
-35
lines changed

mcp_use/connectors/base.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -111,29 +111,34 @@ async def _cleanup_resources(self) -> None:
111111
"""Clean up all resources associated with this connector."""
112112
errors = []
113113

114-
# First close the client session
115-
if self.client_session:
114+
# First stop the connection manager, this closes the ClientSession inside
115+
# the same task where it was opened, avoiding cancel-scope mismatches.
116+
if self._connection_manager:
116117
try:
117-
logger.debug("Closing client session")
118-
await self.client_session.__aexit__(None, None, None)
118+
logger.debug("Stopping connection manager")
119+
await self._connection_manager.stop()
119120
except Exception as e:
120-
error_msg = f"Error closing client session: {e}"
121+
error_msg = f"Error stopping connection manager: {e}"
121122
logger.warning(error_msg)
122123
errors.append(error_msg)
123124
finally:
124-
self.client_session = None
125+
self._connection_manager = None
125126

126-
# Then stop the connection manager
127-
if self._connection_manager:
127+
# Ensure the client_session reference is cleared (it should already be
128+
# closed by the connection manager). Only attempt a direct __aexit__ if
129+
# the connection manager did *not* exist, this covers edge-cases like
130+
# failed connections where no manager was started.
131+
if self.client_session:
128132
try:
129-
logger.debug("Stopping connection manager")
130-
await self._connection_manager.stop()
133+
if not self._connection_manager:
134+
logger.debug("Closing client session (no connection manager)")
135+
await self.client_session.__aexit__(None, None, None)
131136
except Exception as e:
132-
error_msg = f"Error stopping connection manager: {e}"
137+
error_msg = f"Error closing client session: {e}"
133138
logger.warning(error_msg)
134139
errors.append(error_msg)
135140
finally:
136-
self._connection_manager = None
141+
self.client_session = None
137142

138143
# Reset tools
139144
self._tools = None

mcp_use/connectors/http.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import httpx
99
from mcp import ClientSession
1010
from mcp.client.session import ElicitationFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
11+
from mcp.shared.exceptions import McpError
1112

1213
from ..logging import logger
1314
from ..task_managers import SseConnectionManager, StreamableHttpConnectionManager
@@ -125,6 +126,21 @@ async def connect(self) -> None:
125126
else:
126127
self._prompts = []
127128

129+
except McpError as mcp_error:
130+
# This is a protocol error, not a transport error
131+
# The server is reachable and speaking MCP, but rejecting our request
132+
logger.error("MCP protocol error during initialization: %s", mcp_error)
133+
134+
# Clean up the test client
135+
try:
136+
await test_client.__aexit__(None, None, None)
137+
except Exception:
138+
pass
139+
140+
# Don't try SSE fallback for protocol errors - the server is working,
141+
# it just doesn't like our request
142+
raise mcp_error
143+
128144
except Exception as init_error:
129145
# Clean up the test client
130146
try:

mcp_use/task_managers/base.py

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@ class ConnectionManager(Generic[T], ABC):
2222
used with MCP connectors.
2323
"""
2424

25-
def __init__(self):
25+
def __init__(self) -> None:
2626
"""Initialize a new connection manager."""
2727
self._ready_event = asyncio.Event()
2828
self._done_event = asyncio.Event()
29+
self._stop_event = asyncio.Event()
2930
self._exception: Exception | None = None
3031
self._connection: T | None = None
31-
self._task: asyncio.Task | None = None
32+
self._task: asyncio.Task[None] | None = None
3233

3334
@abstractmethod
3435
async def _establish_connection(self) -> T:
@@ -86,20 +87,15 @@ async def start(self) -> T:
8687

8788
async def stop(self) -> None:
8889
"""Stop the connection manager and close the connection."""
90+
# Signal stop to the connection task instead of cancelling it, avoids
91+
# propagating CancelledError to unrelated tasks.
8992
if self._task and not self._task.done():
90-
# Cancel the task
91-
logger.debug(f"Cancelling {self.__class__.__name__} task")
92-
self._task.cancel()
93-
94-
# Wait for it to complete
95-
try:
96-
await self._task
97-
except asyncio.CancelledError:
98-
logger.debug(f"{self.__class__.__name__} task cancelled successfully")
99-
except Exception as e:
100-
logger.warning(f"Error stopping {self.__class__.__name__} task: {e}")
101-
102-
# Wait for the connection to be done
93+
logger.debug(f"Signaling stop to {self.__class__.__name__} task")
94+
self._stop_event.set()
95+
# Wait for it to finish gracefully
96+
await self._task
97+
98+
# Ensure cleanup completed
10399
await self._done_event.wait()
104100
logger.debug(f"{self.__class__.__name__} task completed")
105101

@@ -125,14 +121,8 @@ async def _connection_task(self) -> None:
125121
# Signal that the connection is ready
126122
self._ready_event.set()
127123

128-
# Wait indefinitely until cancelled
129-
try:
130-
# This keeps the connection open until cancelled
131-
await asyncio.Event().wait()
132-
except asyncio.CancelledError:
133-
# Expected when stopping
134-
logger.debug(f"{self.__class__.__name__} task received cancellation")
135-
pass
124+
# Wait until stop is requested
125+
await self._stop_event.wait()
136126

137127
except Exception as e:
138128
# Store the exception

0 commit comments

Comments
 (0)