mcp-use 1.3.0__py3-none-any.whl → 1.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mcp-use might be problematic. Click here for more details.
- mcp_use/__init__.py +2 -0
- mcp_use/adapters/base.py +2 -6
- mcp_use/adapters/langchain_adapter.py +4 -11
- mcp_use/agents/base.py +1 -3
- mcp_use/agents/mcpagent.py +121 -45
- mcp_use/agents/prompts/system_prompt_builder.py +1 -3
- mcp_use/client.py +26 -11
- mcp_use/config.py +9 -9
- mcp_use/connectors/base.py +136 -32
- mcp_use/connectors/http.py +100 -30
- mcp_use/connectors/sandbox.py +11 -16
- mcp_use/connectors/stdio.py +8 -5
- mcp_use/connectors/websocket.py +8 -5
- mcp_use/logging.py +1 -1
- mcp_use/managers/server_manager.py +5 -16
- mcp_use/managers/tools/disconnect_server.py +1 -3
- mcp_use/managers/tools/get_active_server.py +1 -4
- mcp_use/managers/tools/search_tools.py +29 -36
- mcp_use/managers/tools/use_tool.py +5 -18
- mcp_use/observability/__init__.py +8 -0
- mcp_use/observability/laminar.py +21 -0
- mcp_use/observability/langfuse.py +35 -0
- mcp_use/session.py +1 -4
- mcp_use/task_managers/__init__.py +2 -1
- mcp_use/task_managers/base.py +10 -4
- mcp_use/task_managers/streamable_http.py +81 -0
- mcp_use/task_managers/websocket.py +5 -0
- mcp_use/telemetry/__init__.py +0 -0
- mcp_use/telemetry/events.py +93 -0
- mcp_use/telemetry/telemetry.py +306 -0
- mcp_use/telemetry/utils.py +48 -0
- mcp_use/utils.py +27 -0
- {mcp_use-1.3.0.dist-info → mcp_use-1.3.2.dist-info}/METADATA +82 -26
- mcp_use-1.3.2.dist-info/RECORD +49 -0
- mcp_use/types/clientoptions.py +0 -23
- mcp_use-1.3.0.dist-info/RECORD +0 -41
- {mcp_use-1.3.0.dist-info → mcp_use-1.3.2.dist-info}/WHEEL +0 -0
- {mcp_use-1.3.0.dist-info → mcp_use-1.3.2.dist-info}/licenses/LICENSE +0 -0
mcp_use/connectors/base.py
CHANGED
|
@@ -24,18 +24,25 @@ class BaseConnector(ABC):
|
|
|
24
24
|
|
|
25
25
|
def __init__(self):
|
|
26
26
|
"""Initialize base connector with common attributes."""
|
|
27
|
-
self.
|
|
27
|
+
self.client_session: ClientSession | None = None
|
|
28
28
|
self._connection_manager: ConnectionManager | None = None
|
|
29
29
|
self._tools: list[Tool] | None = None
|
|
30
30
|
self._resources: list[Resource] | None = None
|
|
31
31
|
self._prompts: list[Prompt] | None = None
|
|
32
32
|
self._connected = False
|
|
33
|
+
self.auto_reconnect = True # Whether to automatically reconnect on connection loss (not configurable for now)
|
|
33
34
|
|
|
34
35
|
@abstractmethod
|
|
35
36
|
async def connect(self) -> None:
|
|
36
37
|
"""Establish a connection to the MCP implementation."""
|
|
37
38
|
pass
|
|
38
39
|
|
|
40
|
+
@property
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def public_identifier(self) -> str:
|
|
43
|
+
"""Get the identifier for the connector."""
|
|
44
|
+
pass
|
|
45
|
+
|
|
39
46
|
async def disconnect(self) -> None:
|
|
40
47
|
"""Close the connection to the MCP implementation."""
|
|
41
48
|
if not self._connected:
|
|
@@ -52,16 +59,16 @@ class BaseConnector(ABC):
|
|
|
52
59
|
errors = []
|
|
53
60
|
|
|
54
61
|
# First close the client session
|
|
55
|
-
if self.
|
|
62
|
+
if self.client_session:
|
|
56
63
|
try:
|
|
57
64
|
logger.debug("Closing client session")
|
|
58
|
-
await self.
|
|
65
|
+
await self.client_session.__aexit__(None, None, None)
|
|
59
66
|
except Exception as e:
|
|
60
67
|
error_msg = f"Error closing client session: {e}"
|
|
61
68
|
logger.warning(error_msg)
|
|
62
69
|
errors.append(error_msg)
|
|
63
70
|
finally:
|
|
64
|
-
self.
|
|
71
|
+
self.client_session = None
|
|
65
72
|
|
|
66
73
|
# Then stop the connection manager
|
|
67
74
|
if self._connection_manager:
|
|
@@ -85,13 +92,13 @@ class BaseConnector(ABC):
|
|
|
85
92
|
|
|
86
93
|
async def initialize(self) -> dict[str, Any]:
|
|
87
94
|
"""Initialize the MCP session and return session information."""
|
|
88
|
-
if not self.
|
|
95
|
+
if not self.client_session:
|
|
89
96
|
raise RuntimeError("MCP client is not connected")
|
|
90
97
|
|
|
91
98
|
logger.debug("Initializing MCP session")
|
|
92
99
|
|
|
93
100
|
# Initialize the session
|
|
94
|
-
result = await self.
|
|
101
|
+
result = await self.client_session.initialize()
|
|
95
102
|
|
|
96
103
|
server_capabilities = result.capabilities
|
|
97
104
|
|
|
@@ -145,24 +152,123 @@ class BaseConnector(ABC):
|
|
|
145
152
|
raise RuntimeError("MCP client is not initialized")
|
|
146
153
|
return self._prompts
|
|
147
154
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if
|
|
155
|
+
@property
|
|
156
|
+
def is_connected(self) -> bool:
|
|
157
|
+
"""Check if the connector is actually connected and the connection is alive.
|
|
158
|
+
|
|
159
|
+
This property checks not only the connected flag but also verifies that
|
|
160
|
+
the underlying connection manager and streams are still active.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
True if the connector is connected and the connection is alive, False otherwise.
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
# Check if we have a client session
|
|
167
|
+
if not self.client_session:
|
|
168
|
+
# Update the connected flag since we don't have a client session
|
|
169
|
+
self._connected = False
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
# First check the basic connected flag
|
|
173
|
+
if not self._connected:
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
# Check if we have a connection manager and if its task is still running
|
|
177
|
+
if self._connection_manager:
|
|
178
|
+
try:
|
|
179
|
+
# Check if the connection manager task is done (indicates disconnection)
|
|
180
|
+
if hasattr(self._connection_manager, "_task") and self._connection_manager._task:
|
|
181
|
+
if self._connection_manager._task.done():
|
|
182
|
+
logger.debug("Connection manager task is done, marking as disconnected")
|
|
183
|
+
self._connected = False
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
# For HTTP-based connectors, also check if streams are still open
|
|
187
|
+
# Use the get_streams method to get the current connection
|
|
188
|
+
streams = self._connection_manager.get_streams()
|
|
189
|
+
if streams:
|
|
190
|
+
# Connection should be a tuple of (read_stream, write_stream)
|
|
191
|
+
if isinstance(streams, tuple) and len(streams) == 2:
|
|
192
|
+
read_stream, write_stream = streams
|
|
193
|
+
# Check if streams are closed using getattr with default value
|
|
194
|
+
if getattr(read_stream, "_closed", False):
|
|
195
|
+
logger.debug("Read stream is closed, marking as disconnected")
|
|
196
|
+
self._connected = False
|
|
197
|
+
return False
|
|
198
|
+
if getattr(write_stream, "_closed", False):
|
|
199
|
+
logger.debug("Write stream is closed, marking as disconnected")
|
|
200
|
+
self._connected = False
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
except Exception as e:
|
|
204
|
+
# If we can't check the connection state, assume disconnected for safety
|
|
205
|
+
logger.debug(f"Error checking connection state: {e}, marking as disconnected")
|
|
206
|
+
self._connected = False
|
|
207
|
+
return False
|
|
208
|
+
|
|
209
|
+
return True
|
|
210
|
+
|
|
211
|
+
async def _ensure_connected(self) -> None:
|
|
212
|
+
"""Ensure the connector is connected, reconnecting if necessary.
|
|
213
|
+
|
|
214
|
+
Raises:
|
|
215
|
+
RuntimeError: If connection cannot be established and auto_reconnect is False.
|
|
216
|
+
"""
|
|
217
|
+
if not self.client_session:
|
|
151
218
|
raise RuntimeError("MCP client is not connected")
|
|
152
219
|
|
|
220
|
+
if not self.is_connected:
|
|
221
|
+
if self.auto_reconnect:
|
|
222
|
+
logger.debug("Connection lost, attempting to reconnect...")
|
|
223
|
+
try:
|
|
224
|
+
await self.connect()
|
|
225
|
+
logger.debug("Reconnection successful")
|
|
226
|
+
except Exception as e:
|
|
227
|
+
raise RuntimeError(f"Failed to reconnect to MCP server: {e}") from e
|
|
228
|
+
else:
|
|
229
|
+
raise RuntimeError(
|
|
230
|
+
"Connection to MCP server has been lost. Auto-reconnection is disabled. Please reconnect manually."
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
async def call_tool(self, name: str, arguments: dict[str, Any]) -> CallToolResult:
|
|
234
|
+
"""Call an MCP tool with automatic reconnection handling.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
name: The name of the tool to call.
|
|
238
|
+
arguments: The arguments to pass to the tool.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
The result of the tool call.
|
|
242
|
+
|
|
243
|
+
Raises:
|
|
244
|
+
RuntimeError: If the connection is lost and cannot be reestablished.
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
# Ensure we're connected
|
|
248
|
+
await self._ensure_connected()
|
|
249
|
+
|
|
153
250
|
logger.debug(f"Calling tool '{name}' with arguments: {arguments}")
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
251
|
+
try:
|
|
252
|
+
result = await self.client_session.call_tool(name, arguments)
|
|
253
|
+
logger.debug(f"Tool '{name}' called with result: {result}")
|
|
254
|
+
return result
|
|
255
|
+
except Exception as e:
|
|
256
|
+
# Check if the error might be due to connection loss
|
|
257
|
+
if not self.is_connected:
|
|
258
|
+
raise RuntimeError(f"Tool call '{name}' failed due to connection loss: {e}") from e
|
|
259
|
+
else:
|
|
260
|
+
# Re-raise the original error if it's not connection-related
|
|
261
|
+
raise
|
|
157
262
|
|
|
158
263
|
async def list_tools(self) -> list[Tool]:
|
|
159
264
|
"""List all available tools from the MCP implementation."""
|
|
160
|
-
|
|
161
|
-
|
|
265
|
+
|
|
266
|
+
# Ensure we're connected
|
|
267
|
+
await self._ensure_connected()
|
|
162
268
|
|
|
163
269
|
logger.debug("Listing tools")
|
|
164
270
|
try:
|
|
165
|
-
result = await self.
|
|
271
|
+
result = await self.client_session.list_tools()
|
|
166
272
|
return result.tools
|
|
167
273
|
except McpError as e:
|
|
168
274
|
logger.error(f"Error listing tools: {e}")
|
|
@@ -170,12 +276,12 @@ class BaseConnector(ABC):
|
|
|
170
276
|
|
|
171
277
|
async def list_resources(self) -> list[Resource]:
|
|
172
278
|
"""List all available resources from the MCP implementation."""
|
|
173
|
-
|
|
174
|
-
|
|
279
|
+
# Ensure we're connected
|
|
280
|
+
await self._ensure_connected()
|
|
175
281
|
|
|
176
282
|
logger.debug("Listing resources")
|
|
177
283
|
try:
|
|
178
|
-
result = await self.
|
|
284
|
+
result = await self.client_session.list_resources()
|
|
179
285
|
return result.resources
|
|
180
286
|
except McpError as e:
|
|
181
287
|
logger.error(f"Error listing resources: {e}")
|
|
@@ -183,41 +289,39 @@ class BaseConnector(ABC):
|
|
|
183
289
|
|
|
184
290
|
async def read_resource(self, uri: str) -> ReadResourceResult:
|
|
185
291
|
"""Read a resource by URI."""
|
|
186
|
-
if not self.
|
|
292
|
+
if not self.client_session:
|
|
187
293
|
raise RuntimeError("MCP client is not connected")
|
|
188
294
|
|
|
189
295
|
logger.debug(f"Reading resource: {uri}")
|
|
190
|
-
result = await self.
|
|
296
|
+
result = await self.client_session.read_resource(uri)
|
|
191
297
|
return result
|
|
192
298
|
|
|
193
299
|
async def list_prompts(self) -> list[Prompt]:
|
|
194
300
|
"""List all available prompts from the MCP implementation."""
|
|
195
|
-
|
|
196
|
-
|
|
301
|
+
# Ensure we're connected
|
|
302
|
+
await self._ensure_connected()
|
|
197
303
|
|
|
198
304
|
logger.debug("Listing prompts")
|
|
199
305
|
try:
|
|
200
|
-
result = await self.
|
|
306
|
+
result = await self.client_session.list_prompts()
|
|
201
307
|
return result.prompts
|
|
202
308
|
except McpError as e:
|
|
203
309
|
logger.error(f"Error listing prompts: {e}")
|
|
204
310
|
return []
|
|
205
311
|
|
|
206
|
-
async def get_prompt(
|
|
207
|
-
self, name: str, arguments: dict[str, Any] | None = None
|
|
208
|
-
) -> GetPromptResult:
|
|
312
|
+
async def get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> GetPromptResult:
|
|
209
313
|
"""Get a prompt by name."""
|
|
210
|
-
|
|
211
|
-
|
|
314
|
+
# Ensure we're connected
|
|
315
|
+
await self._ensure_connected()
|
|
212
316
|
|
|
213
317
|
logger.debug(f"Getting prompt: {name}")
|
|
214
|
-
result = await self.
|
|
318
|
+
result = await self.client_session.get_prompt(name, arguments)
|
|
215
319
|
return result
|
|
216
320
|
|
|
217
321
|
async def request(self, method: str, params: dict[str, Any] | None = None) -> Any:
|
|
218
322
|
"""Send a raw request to the MCP implementation."""
|
|
219
|
-
|
|
220
|
-
|
|
323
|
+
# Ensure we're connected
|
|
324
|
+
await self._ensure_connected()
|
|
221
325
|
|
|
222
326
|
logger.debug(f"Sending request: {method} with params: {params}")
|
|
223
|
-
return await self.
|
|
327
|
+
return await self.client_session.request({"method": method, "params": params or {}})
|
mcp_use/connectors/http.py
CHANGED
|
@@ -2,20 +2,21 @@
|
|
|
2
2
|
HTTP connector for MCP implementations.
|
|
3
3
|
|
|
4
4
|
This module provides a connector for communicating with MCP implementations
|
|
5
|
-
through HTTP APIs with SSE for transport.
|
|
5
|
+
through HTTP APIs with SSE or Streamable HTTP for transport.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import httpx
|
|
8
9
|
from mcp import ClientSession
|
|
9
10
|
|
|
10
11
|
from ..logging import logger
|
|
11
|
-
from ..task_managers import SseConnectionManager
|
|
12
|
+
from ..task_managers import ConnectionManager, SseConnectionManager, StreamableHttpConnectionManager
|
|
12
13
|
from .base import BaseConnector
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class HttpConnector(BaseConnector):
|
|
16
|
-
"""Connector for MCP implementations using HTTP transport with SSE.
|
|
17
|
+
"""Connector for MCP implementations using HTTP transport with SSE or streamable HTTP.
|
|
17
18
|
|
|
18
|
-
This connector uses HTTP/SSE to communicate with remote MCP implementations,
|
|
19
|
+
This connector uses HTTP/SSE or streamable HTTP to communicate with remote MCP implementations,
|
|
19
20
|
using a connection manager to handle the proper lifecycle management.
|
|
20
21
|
"""
|
|
21
22
|
|
|
@@ -45,38 +46,107 @@ class HttpConnector(BaseConnector):
|
|
|
45
46
|
self.timeout = timeout
|
|
46
47
|
self.sse_read_timeout = sse_read_timeout
|
|
47
48
|
|
|
49
|
+
async def _setup_client(self, connection_manager: ConnectionManager) -> None:
|
|
50
|
+
"""Set up the client session with the provided connection manager."""
|
|
51
|
+
|
|
52
|
+
self._connection_manager = connection_manager
|
|
53
|
+
read_stream, write_stream = await self._connection_manager.start()
|
|
54
|
+
self.client_session = ClientSession(read_stream, write_stream, sampling_callback=None)
|
|
55
|
+
await self.client_session.__aenter__()
|
|
56
|
+
|
|
48
57
|
async def connect(self) -> None:
|
|
49
58
|
"""Establish a connection to the MCP implementation."""
|
|
50
59
|
if self._connected:
|
|
51
60
|
logger.debug("Already connected to MCP implementation")
|
|
52
61
|
return
|
|
53
62
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
# Create and start the connection manager
|
|
60
|
-
self._connection_manager = SseConnectionManager(
|
|
61
|
-
sse_url, self.headers, self.timeout, self.sse_read_timeout
|
|
62
|
-
)
|
|
63
|
-
read_stream, write_stream = await self._connection_manager.start()
|
|
63
|
+
# Try streamable HTTP first (new transport), fall back to SSE (old transport)
|
|
64
|
+
# This implements backwards compatibility per MCP specification
|
|
65
|
+
self.transport_type = None
|
|
66
|
+
connection_manager = None
|
|
64
67
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
self._connected = True
|
|
71
|
-
logger.debug(
|
|
72
|
-
f"Successfully connected to MCP implementation via HTTP/SSE: {self.base_url}"
|
|
68
|
+
try:
|
|
69
|
+
# First, try the new streamable HTTP transport
|
|
70
|
+
logger.debug(f"Attempting streamable HTTP connection to: {self.base_url}")
|
|
71
|
+
connection_manager = StreamableHttpConnectionManager(
|
|
72
|
+
self.base_url, self.headers, self.timeout, self.sse_read_timeout
|
|
73
73
|
)
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
75
|
+
# Test if this is a streamable HTTP server by attempting initialization
|
|
76
|
+
read_stream, write_stream = await connection_manager.start()
|
|
77
|
+
|
|
78
|
+
# Test if this actually works by trying to create a client session and initialize it
|
|
79
|
+
test_client = ClientSession(read_stream, write_stream, sampling_callback=None)
|
|
80
|
+
await test_client.__aenter__()
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
# Try to initialize - this is where streamable HTTP vs SSE difference should show up
|
|
84
|
+
await test_client.initialize()
|
|
85
|
+
|
|
86
|
+
# If we get here, streamable HTTP works
|
|
87
|
+
|
|
88
|
+
self.client_session = test_client
|
|
89
|
+
self.transport_type = "streamable HTTP"
|
|
90
|
+
|
|
91
|
+
except Exception as init_error:
|
|
92
|
+
# Clean up the test client
|
|
93
|
+
try:
|
|
94
|
+
await test_client.__aexit__(None, None, None)
|
|
95
|
+
except Exception:
|
|
96
|
+
pass
|
|
97
|
+
raise init_error
|
|
98
|
+
|
|
99
|
+
except Exception as streamable_error:
|
|
100
|
+
logger.debug(f"Streamable HTTP failed: {streamable_error}")
|
|
101
|
+
|
|
102
|
+
# Clean up the failed streamable HTTP connection manager
|
|
103
|
+
if connection_manager:
|
|
104
|
+
try:
|
|
105
|
+
await connection_manager.close()
|
|
106
|
+
except Exception:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
# Check if this is a 4xx error that indicates we should try SSE fallback
|
|
110
|
+
should_fallback = False
|
|
111
|
+
if isinstance(streamable_error, httpx.HTTPStatusError):
|
|
112
|
+
if streamable_error.response.status_code in [404, 405]:
|
|
113
|
+
should_fallback = True
|
|
114
|
+
elif "405 Method Not Allowed" in str(streamable_error) or "404 Not Found" in str(streamable_error):
|
|
115
|
+
should_fallback = True
|
|
116
|
+
else:
|
|
117
|
+
# For other errors, still try fallback but they might indicate
|
|
118
|
+
# real connectivity issues
|
|
119
|
+
should_fallback = True
|
|
120
|
+
|
|
121
|
+
if should_fallback:
|
|
122
|
+
try:
|
|
123
|
+
# Fall back to the old SSE transport
|
|
124
|
+
logger.debug(f"Attempting SSE fallback connection to: {self.base_url}")
|
|
125
|
+
connection_manager = SseConnectionManager(
|
|
126
|
+
self.base_url, self.headers, self.timeout, self.sse_read_timeout
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
read_stream, write_stream = await connection_manager.start()
|
|
130
|
+
|
|
131
|
+
# Create the client session for SSE
|
|
132
|
+
self.client_session = ClientSession(read_stream, write_stream, sampling_callback=None)
|
|
133
|
+
await self.client_session.__aenter__()
|
|
134
|
+
self.transport_type = "SSE"
|
|
135
|
+
|
|
136
|
+
except Exception as sse_error:
|
|
137
|
+
logger.error(
|
|
138
|
+
f"Both transport methods failed. Streamable HTTP: {streamable_error}, SSE: {sse_error}"
|
|
139
|
+
)
|
|
140
|
+
raise sse_error
|
|
141
|
+
else:
|
|
142
|
+
raise streamable_error
|
|
143
|
+
|
|
144
|
+
# Store the successful connection manager and mark as connected
|
|
145
|
+
self._connection_manager = connection_manager
|
|
146
|
+
self._connected = True
|
|
147
|
+
logger.debug(f"Successfully connected to MCP implementation via {self.transport_type}: {self.base_url}")
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def public_identifier(self) -> str:
|
|
151
|
+
"""Get the identifier for the connector."""
|
|
152
|
+
return {"type": self.transport_type, "base_url": self.base_url}
|
mcp_use/connectors/sandbox.py
CHANGED
|
@@ -79,14 +79,12 @@ class SandboxConnector(BaseConnector):
|
|
|
79
79
|
self.api_key = _e2b_options.get("api_key") or os.environ.get("E2B_API_KEY")
|
|
80
80
|
if not self.api_key:
|
|
81
81
|
raise ValueError(
|
|
82
|
-
"E2B API key is required. Provide it via '
|
|
82
|
+
"E2B API key is required. Provide it via 'sandbox_options.api_key' "
|
|
83
83
|
"or the E2B_API_KEY environment variable."
|
|
84
84
|
)
|
|
85
85
|
|
|
86
86
|
self.sandbox_template_id = _e2b_options.get("sandbox_template_id", "base")
|
|
87
|
-
self.supergateway_cmd_parts = _e2b_options.get(
|
|
88
|
-
"supergateway_command", "npx -y supergateway"
|
|
89
|
-
)
|
|
87
|
+
self.supergateway_cmd_parts = _e2b_options.get("supergateway_command", "npx -y supergateway")
|
|
90
88
|
|
|
91
89
|
self.sandbox: Sandbox | None = None
|
|
92
90
|
self.process: CommandHandle | None = None
|
|
@@ -138,10 +136,7 @@ class SandboxConnector(BaseConnector):
|
|
|
138
136
|
async with session.get(ping_url, timeout=2) as response:
|
|
139
137
|
if response.status == 200:
|
|
140
138
|
elapsed = time.time() - start_time
|
|
141
|
-
logger.info(
|
|
142
|
-
f"Server is ready! "
|
|
143
|
-
f"SSE endpoint responded with 200 after {elapsed:.1f}s"
|
|
144
|
-
)
|
|
139
|
+
logger.info(f"Server is ready! SSE endpoint responded with 200 after {elapsed:.1f}s")
|
|
145
140
|
return True
|
|
146
141
|
except Exception:
|
|
147
142
|
# If sse endpoint doesn't work, try the base URL
|
|
@@ -149,8 +144,7 @@ class SandboxConnector(BaseConnector):
|
|
|
149
144
|
if response.status < 500: # Accept any non-server error
|
|
150
145
|
elapsed = time.time() - start_time
|
|
151
146
|
logger.info(
|
|
152
|
-
f"Server is ready! Base URL responded with "
|
|
153
|
-
f"{response.status} after {elapsed:.1f}s"
|
|
147
|
+
f"Server is ready! Base URL responded with {response.status} after {elapsed:.1f}s"
|
|
154
148
|
)
|
|
155
149
|
return True
|
|
156
150
|
except Exception:
|
|
@@ -220,9 +214,7 @@ class SandboxConnector(BaseConnector):
|
|
|
220
214
|
sse_url = f"{self.base_url}/sse"
|
|
221
215
|
|
|
222
216
|
# Create and start the connection manager
|
|
223
|
-
self._connection_manager = SseConnectionManager(
|
|
224
|
-
sse_url, self.headers, self.timeout, self.sse_read_timeout
|
|
225
|
-
)
|
|
217
|
+
self._connection_manager = SseConnectionManager(sse_url, self.headers, self.timeout, self.sse_read_timeout)
|
|
226
218
|
read_stream, write_stream = await self._connection_manager.start()
|
|
227
219
|
|
|
228
220
|
# Create the client session
|
|
@@ -231,9 +223,7 @@ class SandboxConnector(BaseConnector):
|
|
|
231
223
|
|
|
232
224
|
# Mark as connected
|
|
233
225
|
self._connected = True
|
|
234
|
-
logger.debug(
|
|
235
|
-
f"Successfully connected to MCP implementation via HTTP/SSE: {self.base_url}"
|
|
236
|
-
)
|
|
226
|
+
logger.debug(f"Successfully connected to MCP implementation via HTTP/SSE: {self.base_url}")
|
|
237
227
|
|
|
238
228
|
except Exception as e:
|
|
239
229
|
logger.error(f"Failed to connect to MCP implementation: {e}")
|
|
@@ -289,3 +279,8 @@ class SandboxConnector(BaseConnector):
|
|
|
289
279
|
await self._cleanup_resources()
|
|
290
280
|
self._connected = False
|
|
291
281
|
logger.debug("Disconnected from MCP implementation")
|
|
282
|
+
|
|
283
|
+
@property
|
|
284
|
+
def public_identifier(self) -> str:
|
|
285
|
+
"""Get the identifier for the connector."""
|
|
286
|
+
return {"type": "sandbox", "command": self.user_command, "args": self.user_args}
|
mcp_use/connectors/stdio.py
CHANGED
|
@@ -52,17 +52,15 @@ class StdioConnector(BaseConnector):
|
|
|
52
52
|
logger.debug(f"Connecting to MCP implementation: {self.command}")
|
|
53
53
|
try:
|
|
54
54
|
# Create server parameters
|
|
55
|
-
server_params = StdioServerParameters(
|
|
56
|
-
command=self.command, args=self.args, env=self.env
|
|
57
|
-
)
|
|
55
|
+
server_params = StdioServerParameters(command=self.command, args=self.args, env=self.env)
|
|
58
56
|
|
|
59
57
|
# Create and start the connection manager
|
|
60
58
|
self._connection_manager = StdioConnectionManager(server_params, self.errlog)
|
|
61
59
|
read_stream, write_stream = await self._connection_manager.start()
|
|
62
60
|
|
|
63
61
|
# Create the client session
|
|
64
|
-
self.
|
|
65
|
-
await self.
|
|
62
|
+
self.client_session = ClientSession(read_stream, write_stream, sampling_callback=None)
|
|
63
|
+
await self.client_session.__aenter__()
|
|
66
64
|
|
|
67
65
|
# Mark as connected
|
|
68
66
|
self._connected = True
|
|
@@ -76,3 +74,8 @@ class StdioConnector(BaseConnector):
|
|
|
76
74
|
|
|
77
75
|
# Re-raise the original exception
|
|
78
76
|
raise
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def public_identifier(self) -> str:
|
|
80
|
+
"""Get the identifier for the connector."""
|
|
81
|
+
return {"type": "stdio", "command&args": f"{self.command} {' '.join(self.args)}"}
|
mcp_use/connectors/websocket.py
CHANGED
|
@@ -11,7 +11,7 @@ import uuid
|
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
13
|
from mcp.types import Tool
|
|
14
|
-
from websockets
|
|
14
|
+
from websockets import ClientConnection
|
|
15
15
|
|
|
16
16
|
from ..logging import logger
|
|
17
17
|
from ..task_managers import ConnectionManager, WebSocketConnectionManager
|
|
@@ -44,7 +44,7 @@ class WebSocketConnector(BaseConnector):
|
|
|
44
44
|
if auth_token:
|
|
45
45
|
self.headers["Authorization"] = f"Bearer {auth_token}"
|
|
46
46
|
|
|
47
|
-
self.ws:
|
|
47
|
+
self.ws: ClientConnection | None = None
|
|
48
48
|
self._connection_manager: ConnectionManager | None = None
|
|
49
49
|
self._receiver_task: asyncio.Task | None = None
|
|
50
50
|
self.pending_requests: dict[str, asyncio.Future] = {}
|
|
@@ -64,9 +64,7 @@ class WebSocketConnector(BaseConnector):
|
|
|
64
64
|
self.ws = await self._connection_manager.start()
|
|
65
65
|
|
|
66
66
|
# Start the message receiver task
|
|
67
|
-
self._receiver_task = asyncio.create_task(
|
|
68
|
-
self._receive_messages(), name="websocket_receiver_task"
|
|
69
|
-
)
|
|
67
|
+
self._receiver_task = asyncio.create_task(self._receive_messages(), name="websocket_receiver_task")
|
|
70
68
|
|
|
71
69
|
# Mark as connected
|
|
72
70
|
self._connected = True
|
|
@@ -243,3 +241,8 @@ class WebSocketConnector(BaseConnector):
|
|
|
243
241
|
"""Send a raw request to the MCP implementation."""
|
|
244
242
|
logger.debug(f"Sending request: {method} with params: {params}")
|
|
245
243
|
return await self._send_request(method, params)
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def public_identifier(self) -> str:
|
|
247
|
+
"""Get the identifier for the connector."""
|
|
248
|
+
return {"type": "websocket", "url": self.url}
|
mcp_use/logging.py
CHANGED
|
@@ -49,19 +49,13 @@ class ServerManager:
|
|
|
49
49
|
session = None
|
|
50
50
|
try:
|
|
51
51
|
session = self.client.get_session(server_name)
|
|
52
|
-
logger.debug(
|
|
53
|
-
f"Using existing session for server '{server_name}' to prefetch tools."
|
|
54
|
-
)
|
|
52
|
+
logger.debug(f"Using existing session for server '{server_name}' to prefetch tools.")
|
|
55
53
|
except ValueError:
|
|
56
54
|
try:
|
|
57
55
|
session = await self.client.create_session(server_name)
|
|
58
|
-
logger.debug(
|
|
59
|
-
f"Temporarily created session for '{server_name}' to prefetch tools"
|
|
60
|
-
)
|
|
56
|
+
logger.debug(f"Temporarily created session for '{server_name}' to prefetch tools")
|
|
61
57
|
except Exception:
|
|
62
|
-
logger.warning(
|
|
63
|
-
f"Could not create session for '{server_name}' during prefetch"
|
|
64
|
-
)
|
|
58
|
+
logger.warning(f"Could not create session for '{server_name}' during prefetch")
|
|
65
59
|
continue
|
|
66
60
|
|
|
67
61
|
# Fetch tools if session is available
|
|
@@ -70,17 +64,12 @@ class ServerManager:
|
|
|
70
64
|
tools = await self.adapter._create_tools_from_connectors([connector])
|
|
71
65
|
|
|
72
66
|
# Check if this server's tools have changed
|
|
73
|
-
if
|
|
74
|
-
server_name not in self._server_tools
|
|
75
|
-
or self._server_tools[server_name] != tools
|
|
76
|
-
):
|
|
67
|
+
if server_name not in self._server_tools or self._server_tools[server_name] != tools:
|
|
77
68
|
self._server_tools[server_name] = tools # Cache tools
|
|
78
69
|
self.initialized_servers[server_name] = True # Mark as initialized
|
|
79
70
|
logger.debug(f"Prefetched {len(tools)} tools for server '{server_name}'.")
|
|
80
71
|
else:
|
|
81
|
-
logger.debug(
|
|
82
|
-
f"Tools for server '{server_name}' unchanged, using cached version."
|
|
83
|
-
)
|
|
72
|
+
logger.debug(f"Tools for server '{server_name}' unchanged, using cached version.")
|
|
84
73
|
except Exception as e:
|
|
85
74
|
logger.error(f"Error prefetching tools for server '{server_name}': {e}")
|
|
86
75
|
|
|
@@ -17,9 +17,7 @@ class DisconnectServerTool(MCPServerTool):
|
|
|
17
17
|
"""Tool for disconnecting from the currently active MCP server."""
|
|
18
18
|
|
|
19
19
|
name: ClassVar[str] = "disconnect_from_mcp_server"
|
|
20
|
-
description: ClassVar[str] = (
|
|
21
|
-
"Disconnect from the currently active MCP (Model Context Protocol) server"
|
|
22
|
-
)
|
|
20
|
+
description: ClassVar[str] = "Disconnect from the currently active MCP (Model Context Protocol) server"
|
|
23
21
|
args_schema: ClassVar[type[BaseModel]] = DisconnectServerInput
|
|
24
22
|
|
|
25
23
|
def _run(self, **kwargs) -> str:
|
|
@@ -21,10 +21,7 @@ class GetActiveServerTool(MCPServerTool):
|
|
|
21
21
|
def _run(self, **kwargs) -> str:
|
|
22
22
|
"""Get the currently active MCP server."""
|
|
23
23
|
if not self.server_manager.active_server:
|
|
24
|
-
return
|
|
25
|
-
"No MCP server is currently active. "
|
|
26
|
-
"Use connect_to_mcp_server to connect to a server."
|
|
27
|
-
)
|
|
24
|
+
return "No MCP server is currently active. Use connect_to_mcp_server to connect to a server."
|
|
28
25
|
return f"Currently active MCP server: {self.server_manager.active_server}"
|
|
29
26
|
|
|
30
27
|
async def _arun(self, **kwargs) -> str:
|