mcp-use 1.2.12__py3-none-any.whl → 1.3.0__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/adapters/base.py +31 -10
- mcp_use/adapters/langchain_adapter.py +112 -2
- mcp_use/agents/mcpagent.py +8 -12
- mcp_use/client.py +18 -9
- mcp_use/config.py +30 -4
- mcp_use/connectors/__init__.py +12 -5
- mcp_use/connectors/base.py +83 -20
- mcp_use/connectors/sandbox.py +291 -0
- mcp_use/connectors/utils.py +13 -0
- mcp_use/session.py +3 -29
- mcp_use/task_managers/base.py +3 -5
- mcp_use/task_managers/sse.py +2 -5
- mcp_use/task_managers/stdio.py +2 -6
- mcp_use/task_managers/websocket.py +25 -25
- mcp_use/types/clientoptions.py +23 -0
- mcp_use/types/sandbox.py +23 -0
- {mcp_use-1.2.12.dist-info → mcp_use-1.3.0.dist-info}/METADATA +176 -33
- {mcp_use-1.2.12.dist-info → mcp_use-1.3.0.dist-info}/RECORD +20 -16
- {mcp_use-1.2.12.dist-info → mcp_use-1.3.0.dist-info}/WHEEL +0 -0
- {mcp_use-1.2.12.dist-info → mcp_use-1.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sandbox connector for MCP implementations.
|
|
3
|
+
|
|
4
|
+
This module provides a connector for communicating with MCP implementations
|
|
5
|
+
that are executed inside a sandbox environment (currently using E2B).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
|
|
13
|
+
import aiohttp
|
|
14
|
+
from mcp import ClientSession
|
|
15
|
+
|
|
16
|
+
from ..logging import logger
|
|
17
|
+
from ..task_managers import SseConnectionManager
|
|
18
|
+
|
|
19
|
+
# Import E2B SDK components (optional dependency)
|
|
20
|
+
try:
|
|
21
|
+
logger.debug("Attempting to import e2b_code_interpreter...")
|
|
22
|
+
from e2b_code_interpreter import (
|
|
23
|
+
CommandHandle,
|
|
24
|
+
Sandbox,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
logger.debug("Successfully imported e2b_code_interpreter")
|
|
28
|
+
except ImportError as e:
|
|
29
|
+
logger.debug(f"Failed to import e2b_code_interpreter: {e}")
|
|
30
|
+
CommandHandle = None
|
|
31
|
+
Sandbox = None
|
|
32
|
+
|
|
33
|
+
from ..types.sandbox import SandboxOptions
|
|
34
|
+
from .base import BaseConnector
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SandboxConnector(BaseConnector):
|
|
38
|
+
"""Connector for MCP implementations running in a sandbox environment.
|
|
39
|
+
|
|
40
|
+
This connector runs a user-defined stdio command within a sandbox environment,
|
|
41
|
+
currently implemented using E2B, potentially wrapped by a utility like 'supergateway'
|
|
42
|
+
to expose its stdio.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
command: str,
|
|
48
|
+
args: list[str],
|
|
49
|
+
env: dict[str, str] | None = None,
|
|
50
|
+
e2b_options: SandboxOptions | None = None,
|
|
51
|
+
timeout: float = 5,
|
|
52
|
+
sse_read_timeout: float = 60 * 5,
|
|
53
|
+
):
|
|
54
|
+
"""Initialize a new sandbox connector.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
command: The user's MCP server command to execute in the sandbox.
|
|
58
|
+
args: Command line arguments for the user's MCP server command.
|
|
59
|
+
env: Environment variables for the user's MCP server command.
|
|
60
|
+
e2b_options: Configuration options for the E2B sandbox environment.
|
|
61
|
+
See SandboxOptions for available options and defaults.
|
|
62
|
+
timeout: Timeout for the sandbox process in seconds.
|
|
63
|
+
sse_read_timeout: Timeout for the SSE connection in seconds.
|
|
64
|
+
"""
|
|
65
|
+
super().__init__()
|
|
66
|
+
if Sandbox is None:
|
|
67
|
+
raise ImportError(
|
|
68
|
+
"E2B SDK (e2b-code-interpreter) not found. "
|
|
69
|
+
"Please install it with 'pip install mcp-use[e2b]' "
|
|
70
|
+
"(or 'pip install e2b-code-interpreter')."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
self.user_command = command
|
|
74
|
+
self.user_args = args or []
|
|
75
|
+
self.user_env = env or {}
|
|
76
|
+
|
|
77
|
+
_e2b_options = e2b_options or {}
|
|
78
|
+
|
|
79
|
+
self.api_key = _e2b_options.get("api_key") or os.environ.get("E2B_API_KEY")
|
|
80
|
+
if not self.api_key:
|
|
81
|
+
raise ValueError(
|
|
82
|
+
"E2B API key is required. Provide it via 'e2b_options.api_key' "
|
|
83
|
+
"or the E2B_API_KEY environment variable."
|
|
84
|
+
)
|
|
85
|
+
|
|
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
|
+
)
|
|
90
|
+
|
|
91
|
+
self.sandbox: Sandbox | None = None
|
|
92
|
+
self.process: CommandHandle | None = None
|
|
93
|
+
self.client: ClientSession | None = None
|
|
94
|
+
self.errlog = sys.stderr
|
|
95
|
+
self.base_url: str | None = None
|
|
96
|
+
self._connected = False
|
|
97
|
+
self._connection_manager: SseConnectionManager | None = None
|
|
98
|
+
|
|
99
|
+
# SSE connection parameters
|
|
100
|
+
self.headers = {}
|
|
101
|
+
self.timeout = timeout
|
|
102
|
+
self.sse_read_timeout = sse_read_timeout
|
|
103
|
+
|
|
104
|
+
self.stdout_lines: list[str] = []
|
|
105
|
+
self.stderr_lines: list[str] = []
|
|
106
|
+
self._server_ready = asyncio.Event()
|
|
107
|
+
|
|
108
|
+
def _handle_stdout(self, data: str) -> None:
|
|
109
|
+
"""Handle stdout data from the sandbox process."""
|
|
110
|
+
self.stdout_lines.append(data)
|
|
111
|
+
logger.debug(f"[SANDBOX STDOUT] {data}", end="", flush=True)
|
|
112
|
+
|
|
113
|
+
def _handle_stderr(self, data: str) -> None:
|
|
114
|
+
"""Handle stderr data from the sandbox process."""
|
|
115
|
+
self.stderr_lines.append(data)
|
|
116
|
+
logger.debug(f"[SANDBOX STDERR] {data}", file=self.errlog, end="", flush=True)
|
|
117
|
+
|
|
118
|
+
async def wait_for_server_response(self, base_url: str, timeout: int = 30) -> bool:
|
|
119
|
+
"""Wait for the server to respond to HTTP requests.
|
|
120
|
+
Args:
|
|
121
|
+
base_url: The base URL to check for server readiness
|
|
122
|
+
timeout: Maximum time to wait in seconds
|
|
123
|
+
Returns:
|
|
124
|
+
True if server is responding, raises TimeoutError otherwise
|
|
125
|
+
"""
|
|
126
|
+
logger.info(f"Waiting for server at {base_url} to respond...")
|
|
127
|
+
sys.stdout.flush()
|
|
128
|
+
|
|
129
|
+
start_time = time.time()
|
|
130
|
+
ping_url = f"{base_url}/sse"
|
|
131
|
+
|
|
132
|
+
# Try to connect to the server
|
|
133
|
+
while time.time() - start_time < timeout:
|
|
134
|
+
try:
|
|
135
|
+
async with aiohttp.ClientSession() as session:
|
|
136
|
+
try:
|
|
137
|
+
# First try the endpoint
|
|
138
|
+
async with session.get(ping_url, timeout=2) as response:
|
|
139
|
+
if response.status == 200:
|
|
140
|
+
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
|
+
)
|
|
145
|
+
return True
|
|
146
|
+
except Exception:
|
|
147
|
+
# If sse endpoint doesn't work, try the base URL
|
|
148
|
+
async with session.get(base_url, timeout=2) as response:
|
|
149
|
+
if response.status < 500: # Accept any non-server error
|
|
150
|
+
elapsed = time.time() - start_time
|
|
151
|
+
logger.info(
|
|
152
|
+
f"Server is ready! Base URL responded with "
|
|
153
|
+
f"{response.status} after {elapsed:.1f}s"
|
|
154
|
+
)
|
|
155
|
+
return True
|
|
156
|
+
except Exception:
|
|
157
|
+
# Wait a bit before trying again
|
|
158
|
+
await asyncio.sleep(0.5)
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
# If we get here, the request failed
|
|
162
|
+
await asyncio.sleep(0.5)
|
|
163
|
+
|
|
164
|
+
# Log status every 5 seconds
|
|
165
|
+
elapsed = time.time() - start_time
|
|
166
|
+
if int(elapsed) % 5 == 0:
|
|
167
|
+
logger.info(f"Still waiting for server to respond... ({elapsed:.1f}s elapsed)")
|
|
168
|
+
sys.stdout.flush()
|
|
169
|
+
|
|
170
|
+
# If we get here, we timed out
|
|
171
|
+
raise TimeoutError(f"Timeout waiting for server to respond (waited {timeout} seconds)")
|
|
172
|
+
|
|
173
|
+
async def connect(self):
|
|
174
|
+
"""Connect to the sandbox and start the MCP server."""
|
|
175
|
+
|
|
176
|
+
if self._connected:
|
|
177
|
+
logger.debug("Already connected to MCP implementation")
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
logger.debug("Connecting to MCP implementation in sandbox")
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
# Create and start the sandbox
|
|
184
|
+
self.sandbox = Sandbox(
|
|
185
|
+
template=self.sandbox_template_id,
|
|
186
|
+
api_key=self.api_key,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Get the host for the sandbox
|
|
190
|
+
host = self.sandbox.get_host(3000)
|
|
191
|
+
self.base_url = f"https://{host}".rstrip("/")
|
|
192
|
+
|
|
193
|
+
# Append command with args
|
|
194
|
+
command = f"{self.user_command} {' '.join(self.user_args)}"
|
|
195
|
+
|
|
196
|
+
# Construct the full command with supergateway
|
|
197
|
+
full_command = f'{self.supergateway_cmd_parts} \
|
|
198
|
+
--base-url {self.base_url} \
|
|
199
|
+
--port 3000 \
|
|
200
|
+
--cors \
|
|
201
|
+
--stdio "{command}"'
|
|
202
|
+
|
|
203
|
+
logger.debug(f"Full command: {full_command}")
|
|
204
|
+
|
|
205
|
+
# Start the process in the sandbox with our stdout/stderr handlers
|
|
206
|
+
self.process: CommandHandle = self.sandbox.commands.run(
|
|
207
|
+
full_command,
|
|
208
|
+
envs=self.user_env,
|
|
209
|
+
timeout=1000 * 60 * 10, # 10 minutes timeout
|
|
210
|
+
background=True,
|
|
211
|
+
on_stdout=self._handle_stdout,
|
|
212
|
+
on_stderr=self._handle_stderr,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Wait for the server to be ready
|
|
216
|
+
await self.wait_for_server_response(self.base_url, timeout=30)
|
|
217
|
+
logger.debug("Initializing connection manager...")
|
|
218
|
+
|
|
219
|
+
# Create the SSE connection URL
|
|
220
|
+
sse_url = f"{self.base_url}/sse"
|
|
221
|
+
|
|
222
|
+
# Create and start the connection manager
|
|
223
|
+
self._connection_manager = SseConnectionManager(
|
|
224
|
+
sse_url, self.headers, self.timeout, self.sse_read_timeout
|
|
225
|
+
)
|
|
226
|
+
read_stream, write_stream = await self._connection_manager.start()
|
|
227
|
+
|
|
228
|
+
# Create the client session
|
|
229
|
+
self.client = ClientSession(read_stream, write_stream, sampling_callback=None)
|
|
230
|
+
await self.client.__aenter__()
|
|
231
|
+
|
|
232
|
+
# Mark as connected
|
|
233
|
+
self._connected = True
|
|
234
|
+
logger.debug(
|
|
235
|
+
f"Successfully connected to MCP implementation via HTTP/SSE: {self.base_url}"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
except Exception as e:
|
|
239
|
+
logger.error(f"Failed to connect to MCP implementation: {e}")
|
|
240
|
+
|
|
241
|
+
# Clean up any resources if connection failed
|
|
242
|
+
await self._cleanup_resources()
|
|
243
|
+
|
|
244
|
+
raise e
|
|
245
|
+
|
|
246
|
+
async def _cleanup_resources(self) -> None:
|
|
247
|
+
"""Clean up all resources associated with this connector, including the sandbox.
|
|
248
|
+
This method extends the base implementation to also terminate the sandbox instance
|
|
249
|
+
and clean up any processes running in the sandbox.
|
|
250
|
+
"""
|
|
251
|
+
logger.debug("Cleaning up sandbox resources")
|
|
252
|
+
|
|
253
|
+
# Terminate any running process
|
|
254
|
+
if self.process:
|
|
255
|
+
try:
|
|
256
|
+
logger.debug("Terminating sandbox process")
|
|
257
|
+
self.process.kill()
|
|
258
|
+
except Exception as e:
|
|
259
|
+
logger.warning(f"Error terminating sandbox process: {e}")
|
|
260
|
+
finally:
|
|
261
|
+
self.process = None
|
|
262
|
+
|
|
263
|
+
# Close the sandbox
|
|
264
|
+
if self.sandbox:
|
|
265
|
+
try:
|
|
266
|
+
logger.debug("Closing sandbox instance")
|
|
267
|
+
self.sandbox.kill()
|
|
268
|
+
logger.debug("Sandbox instance closed successfully")
|
|
269
|
+
except Exception as e:
|
|
270
|
+
logger.warning(f"Error closing sandbox: {e}")
|
|
271
|
+
finally:
|
|
272
|
+
self.sandbox = None
|
|
273
|
+
|
|
274
|
+
# Then call the parent method to clean up the rest
|
|
275
|
+
await super()._cleanup_resources()
|
|
276
|
+
|
|
277
|
+
# Clear any collected output
|
|
278
|
+
self.stdout_lines = []
|
|
279
|
+
self.stderr_lines = []
|
|
280
|
+
self.base_url = None
|
|
281
|
+
|
|
282
|
+
async def disconnect(self) -> None:
|
|
283
|
+
"""Close the connection to the MCP implementation."""
|
|
284
|
+
if not self._connected:
|
|
285
|
+
logger.debug("Not connected to MCP implementation")
|
|
286
|
+
return
|
|
287
|
+
|
|
288
|
+
logger.debug("Disconnecting from MCP implementation")
|
|
289
|
+
await self._cleanup_resources()
|
|
290
|
+
self._connected = False
|
|
291
|
+
logger.debug("Disconnected from MCP implementation")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def is_stdio_server(server_config: dict[str, Any]) -> bool:
|
|
5
|
+
"""Check if the server configuration is for a stdio server.
|
|
6
|
+
|
|
7
|
+
Args:
|
|
8
|
+
server_config: The server configuration section
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
True if the server is a stdio server, False otherwise
|
|
12
|
+
"""
|
|
13
|
+
return "command" in server_config and "args" in server_config
|
mcp_use/session.py
CHANGED
|
@@ -7,6 +7,8 @@ which handles authentication, initialization, and tool discovery.
|
|
|
7
7
|
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
+
from mcp.types import Tool
|
|
11
|
+
|
|
10
12
|
from .connectors.base import BaseConnector
|
|
11
13
|
|
|
12
14
|
|
|
@@ -30,7 +32,7 @@ class MCPSession:
|
|
|
30
32
|
"""
|
|
31
33
|
self.connector = connector
|
|
32
34
|
self.session_info: dict[str, Any] | None = None
|
|
33
|
-
self.tools: list[
|
|
35
|
+
self.tools: list[Tool] = []
|
|
34
36
|
self.auto_connect = auto_connect
|
|
35
37
|
|
|
36
38
|
async def __aenter__(self) -> "MCPSession":
|
|
@@ -73,9 +75,6 @@ class MCPSession:
|
|
|
73
75
|
# Initialize the session
|
|
74
76
|
self.session_info = await self.connector.initialize()
|
|
75
77
|
|
|
76
|
-
# Discover available tools
|
|
77
|
-
await self.discover_tools()
|
|
78
|
-
|
|
79
78
|
return self.session_info
|
|
80
79
|
|
|
81
80
|
@property
|
|
@@ -86,28 +85,3 @@ class MCPSession:
|
|
|
86
85
|
True if the connector is connected, False otherwise.
|
|
87
86
|
"""
|
|
88
87
|
return hasattr(self.connector, "client") and self.connector.client is not None
|
|
89
|
-
|
|
90
|
-
async def discover_tools(self) -> list[dict[str, Any]]:
|
|
91
|
-
"""Discover available tools from the MCP implementation.
|
|
92
|
-
|
|
93
|
-
Returns:
|
|
94
|
-
The list of available tools in MCP format.
|
|
95
|
-
"""
|
|
96
|
-
self.tools = self.connector.tools
|
|
97
|
-
return self.tools
|
|
98
|
-
|
|
99
|
-
async def call_tool(self, name: str, arguments: dict[str, Any]) -> Any:
|
|
100
|
-
"""Call an MCP tool with the given arguments.
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
name: The name of the tool to call.
|
|
104
|
-
arguments: The arguments to pass to the tool.
|
|
105
|
-
|
|
106
|
-
Returns:
|
|
107
|
-
The result of the tool call.
|
|
108
|
-
"""
|
|
109
|
-
# Make sure we're connected
|
|
110
|
-
if not self.is_connected and self.auto_connect:
|
|
111
|
-
await self.connect()
|
|
112
|
-
|
|
113
|
-
return await self.connector.call_tool(name, arguments)
|
mcp_use/task_managers/base.py
CHANGED
|
@@ -46,14 +46,12 @@ class ConnectionManager(Generic[T], ABC):
|
|
|
46
46
|
pass
|
|
47
47
|
|
|
48
48
|
@abstractmethod
|
|
49
|
-
async def _close_connection(self
|
|
49
|
+
async def _close_connection(self) -> None:
|
|
50
50
|
"""Close the connection.
|
|
51
51
|
|
|
52
52
|
This method should be implemented by subclasses to close
|
|
53
53
|
the specific type of connection.
|
|
54
54
|
|
|
55
|
-
Args:
|
|
56
|
-
connection: The connection to close.
|
|
57
55
|
"""
|
|
58
56
|
pass
|
|
59
57
|
|
|
@@ -139,10 +137,10 @@ class ConnectionManager(Generic[T], ABC):
|
|
|
139
137
|
self._ready_event.set()
|
|
140
138
|
|
|
141
139
|
finally:
|
|
142
|
-
# Close the connection if it was
|
|
140
|
+
# Close the connection if it was establishedSUPABASE_URL
|
|
143
141
|
if self._connection is not None:
|
|
144
142
|
try:
|
|
145
|
-
await self._close_connection(
|
|
143
|
+
await self._close_connection()
|
|
146
144
|
except Exception as e:
|
|
147
145
|
logger.warning(f"Error closing connection in {self.__class__.__name__}: {e}")
|
|
148
146
|
self._connection = None
|
mcp_use/task_managers/sse.py
CHANGED
|
@@ -66,12 +66,9 @@ class SseConnectionManager(ConnectionManager[tuple[Any, Any]]):
|
|
|
66
66
|
# Return the streams
|
|
67
67
|
return (read_stream, write_stream)
|
|
68
68
|
|
|
69
|
-
async def _close_connection(self
|
|
70
|
-
"""Close the SSE connection.
|
|
69
|
+
async def _close_connection(self) -> None:
|
|
70
|
+
"""Close the SSE connection."""
|
|
71
71
|
|
|
72
|
-
Args:
|
|
73
|
-
connection: The connection to close (ignored, we use the context manager)
|
|
74
|
-
"""
|
|
75
72
|
if self._sse_ctx:
|
|
76
73
|
# Exit the context manager
|
|
77
74
|
try:
|
mcp_use/task_managers/stdio.py
CHANGED
|
@@ -57,12 +57,8 @@ class StdioConnectionManager(ConnectionManager[tuple[Any, Any]]):
|
|
|
57
57
|
# Return the streams
|
|
58
58
|
return (read_stream, write_stream)
|
|
59
59
|
|
|
60
|
-
async def _close_connection(self
|
|
61
|
-
"""Close the stdio connection.
|
|
62
|
-
|
|
63
|
-
Args:
|
|
64
|
-
connection: The connection to close (ignored, we use the context manager)
|
|
65
|
-
"""
|
|
60
|
+
async def _close_connection(self) -> None:
|
|
61
|
+
"""Close the stdio connection."""
|
|
66
62
|
if self._stdio_ctx:
|
|
67
63
|
# Exit the context manager
|
|
68
64
|
try:
|
|
@@ -4,14 +4,15 @@ WebSocket connection management for MCP implementations.
|
|
|
4
4
|
This module provides a connection manager for WebSocket-based MCP connections.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import
|
|
8
|
-
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from mcp.client.websocket import websocket_client
|
|
9
10
|
|
|
10
11
|
from ..logging import logger
|
|
11
12
|
from .base import ConnectionManager
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
class WebSocketConnectionManager(ConnectionManager[
|
|
15
|
+
class WebSocketConnectionManager(ConnectionManager[tuple[Any, Any]]):
|
|
15
16
|
"""Connection manager for WebSocket-based MCP connections.
|
|
16
17
|
|
|
17
18
|
This class handles the lifecycle of WebSocket connections, ensuring proper
|
|
@@ -21,19 +22,16 @@ class WebSocketConnectionManager(ConnectionManager[ClientConnection]):
|
|
|
21
22
|
def __init__(
|
|
22
23
|
self,
|
|
23
24
|
url: str,
|
|
24
|
-
headers: dict[str, str] | None = None,
|
|
25
25
|
):
|
|
26
26
|
"""Initialize a new WebSocket connection manager.
|
|
27
27
|
|
|
28
28
|
Args:
|
|
29
29
|
url: The WebSocket URL to connect to
|
|
30
|
-
headers: Optional headers to include in the WebSocket connection
|
|
31
30
|
"""
|
|
32
31
|
super().__init__()
|
|
33
32
|
self.url = url
|
|
34
|
-
self.headers = headers or {}
|
|
35
33
|
|
|
36
|
-
async def _establish_connection(self) ->
|
|
34
|
+
async def _establish_connection(self) -> tuple[Any, Any]:
|
|
37
35
|
"""Establish a WebSocket connection.
|
|
38
36
|
|
|
39
37
|
Returns:
|
|
@@ -43,21 +41,23 @@ class WebSocketConnectionManager(ConnectionManager[ClientConnection]):
|
|
|
43
41
|
Exception: If connection cannot be established
|
|
44
42
|
"""
|
|
45
43
|
logger.debug(f"Connecting to WebSocket: {self.url}")
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
44
|
+
# Create the context manager
|
|
45
|
+
self._ws_ctx = websocket_client(self.url)
|
|
46
|
+
|
|
47
|
+
# Enter the context manager
|
|
48
|
+
read_stream, write_stream = await self._ws_ctx.__aenter__()
|
|
49
|
+
|
|
50
|
+
# Return the streams
|
|
51
|
+
return (read_stream, write_stream)
|
|
52
|
+
|
|
53
|
+
async def _close_connection(self) -> None:
|
|
54
|
+
"""Close the WebSocket connection."""
|
|
55
|
+
if self._ws_ctx:
|
|
56
|
+
# Exit the context manager
|
|
57
|
+
try:
|
|
58
|
+
logger.debug("Closing WebSocket connection")
|
|
59
|
+
await self._ws_ctx.__aexit__(None, None, None)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
logger.warning(f"Error closing WebSocket connection: {e}")
|
|
62
|
+
finally:
|
|
63
|
+
self._ws_ctx = None
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Options for MCP client configuration.
|
|
3
|
+
|
|
4
|
+
This module provides data classes and type definitions for configuring the MCP client.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import NotRequired, TypedDict
|
|
8
|
+
|
|
9
|
+
from .sandbox import SandboxOptions
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ClientOptions(TypedDict):
|
|
13
|
+
"""Options for configuring the MCP client.
|
|
14
|
+
|
|
15
|
+
This class encapsulates all configuration options for the MCPClient,
|
|
16
|
+
making it easier to extend the API without breaking backward compatibility.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
is_sandboxed: NotRequired[bool]
|
|
20
|
+
"""Whether to use sandboxed execution mode for running MCP servers."""
|
|
21
|
+
|
|
22
|
+
sandbox_options: NotRequired[SandboxOptions]
|
|
23
|
+
"""Options for sandbox configuration when is_sandboxed=True."""
|
mcp_use/types/sandbox.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Type definitions for sandbox-related configurations."""
|
|
2
|
+
|
|
3
|
+
from typing import NotRequired, TypedDict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SandboxOptions(TypedDict):
|
|
7
|
+
"""Configuration options for sandbox execution.
|
|
8
|
+
|
|
9
|
+
This type defines the configuration options available when running
|
|
10
|
+
MCP servers in a sandboxed environment (e.g., using E2B).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
api_key: str
|
|
14
|
+
"""Direct API key for sandbox provider (e.g., E2B API key).
|
|
15
|
+
If not provided, will use E2B_API_KEY environment variable."""
|
|
16
|
+
|
|
17
|
+
sandbox_template_id: NotRequired[str]
|
|
18
|
+
"""Template ID for the sandbox environment.
|
|
19
|
+
Default: 'base'"""
|
|
20
|
+
|
|
21
|
+
supergateway_command: NotRequired[str]
|
|
22
|
+
"""Command to run supergateway.
|
|
23
|
+
Default: 'npx -y supergateway'"""
|