mcp-use 1.3.11__py3-none-any.whl → 1.3.13__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.

Files changed (101) hide show
  1. mcp_use/__init__.py +1 -1
  2. mcp_use/adapters/.deprecated +0 -0
  3. mcp_use/adapters/__init__.py +18 -7
  4. mcp_use/adapters/base.py +12 -185
  5. mcp_use/adapters/langchain_adapter.py +12 -264
  6. mcp_use/agents/adapters/__init__.py +10 -0
  7. mcp_use/agents/adapters/base.py +193 -0
  8. mcp_use/agents/adapters/langchain_adapter.py +228 -0
  9. mcp_use/agents/base.py +1 -1
  10. mcp_use/agents/managers/__init__.py +19 -0
  11. mcp_use/agents/managers/base.py +36 -0
  12. mcp_use/agents/managers/server_manager.py +131 -0
  13. mcp_use/agents/managers/tools/__init__.py +15 -0
  14. mcp_use/agents/managers/tools/base_tool.py +19 -0
  15. mcp_use/agents/managers/tools/connect_server.py +69 -0
  16. mcp_use/agents/managers/tools/disconnect_server.py +43 -0
  17. mcp_use/agents/managers/tools/get_active_server.py +29 -0
  18. mcp_use/agents/managers/tools/list_servers_tool.py +53 -0
  19. mcp_use/agents/managers/tools/search_tools.py +328 -0
  20. mcp_use/agents/mcpagent.py +88 -47
  21. mcp_use/agents/remote.py +168 -129
  22. mcp_use/auth/.deprecated +0 -0
  23. mcp_use/auth/__init__.py +19 -4
  24. mcp_use/auth/bearer.py +11 -12
  25. mcp_use/auth/oauth.py +11 -620
  26. mcp_use/auth/oauth_callback.py +16 -207
  27. mcp_use/client/__init__.py +1 -0
  28. mcp_use/client/auth/__init__.py +6 -0
  29. mcp_use/client/auth/bearer.py +23 -0
  30. mcp_use/client/auth/oauth.py +629 -0
  31. mcp_use/client/auth/oauth_callback.py +214 -0
  32. mcp_use/client/client.py +356 -0
  33. mcp_use/client/config.py +106 -0
  34. mcp_use/client/connectors/__init__.py +20 -0
  35. mcp_use/client/connectors/base.py +470 -0
  36. mcp_use/client/connectors/http.py +304 -0
  37. mcp_use/client/connectors/sandbox.py +332 -0
  38. mcp_use/client/connectors/stdio.py +109 -0
  39. mcp_use/client/connectors/utils.py +13 -0
  40. mcp_use/client/connectors/websocket.py +257 -0
  41. mcp_use/client/exceptions.py +31 -0
  42. mcp_use/client/middleware/__init__.py +50 -0
  43. mcp_use/client/middleware/logging.py +31 -0
  44. mcp_use/client/middleware/metrics.py +314 -0
  45. mcp_use/client/middleware/middleware.py +266 -0
  46. mcp_use/client/session.py +162 -0
  47. mcp_use/client/task_managers/__init__.py +20 -0
  48. mcp_use/client/task_managers/base.py +145 -0
  49. mcp_use/client/task_managers/sse.py +84 -0
  50. mcp_use/client/task_managers/stdio.py +69 -0
  51. mcp_use/client/task_managers/streamable_http.py +86 -0
  52. mcp_use/client/task_managers/websocket.py +68 -0
  53. mcp_use/client.py +12 -320
  54. mcp_use/config.py +20 -92
  55. mcp_use/connectors/.deprecated +0 -0
  56. mcp_use/connectors/__init__.py +46 -20
  57. mcp_use/connectors/base.py +12 -447
  58. mcp_use/connectors/http.py +13 -288
  59. mcp_use/connectors/sandbox.py +13 -297
  60. mcp_use/connectors/stdio.py +13 -96
  61. mcp_use/connectors/utils.py +15 -8
  62. mcp_use/connectors/websocket.py +13 -252
  63. mcp_use/exceptions.py +33 -18
  64. mcp_use/managers/.deprecated +0 -0
  65. mcp_use/managers/__init__.py +56 -17
  66. mcp_use/managers/base.py +13 -31
  67. mcp_use/managers/server_manager.py +13 -119
  68. mcp_use/managers/tools/__init__.py +45 -15
  69. mcp_use/managers/tools/base_tool.py +5 -16
  70. mcp_use/managers/tools/connect_server.py +5 -67
  71. mcp_use/managers/tools/disconnect_server.py +5 -41
  72. mcp_use/managers/tools/get_active_server.py +5 -26
  73. mcp_use/managers/tools/list_servers_tool.py +5 -51
  74. mcp_use/managers/tools/search_tools.py +17 -321
  75. mcp_use/middleware/.deprecated +0 -0
  76. mcp_use/middleware/__init__.py +89 -0
  77. mcp_use/middleware/logging.py +19 -0
  78. mcp_use/middleware/metrics.py +41 -0
  79. mcp_use/middleware/middleware.py +55 -0
  80. mcp_use/session.py +13 -149
  81. mcp_use/task_managers/.deprecated +0 -0
  82. mcp_use/task_managers/__init__.py +48 -20
  83. mcp_use/task_managers/base.py +13 -140
  84. mcp_use/task_managers/sse.py +13 -79
  85. mcp_use/task_managers/stdio.py +13 -64
  86. mcp_use/task_managers/streamable_http.py +15 -81
  87. mcp_use/task_managers/websocket.py +13 -63
  88. mcp_use/telemetry/events.py +58 -0
  89. mcp_use/telemetry/telemetry.py +71 -1
  90. mcp_use/types/.deprecated +0 -0
  91. mcp_use/types/sandbox.py +13 -18
  92. {mcp_use-1.3.11.dist-info → mcp_use-1.3.13.dist-info}/METADATA +66 -40
  93. mcp_use-1.3.13.dist-info/RECORD +109 -0
  94. mcp_use-1.3.11.dist-info/RECORD +0 -60
  95. mcp_use-1.3.11.dist-info/licenses/LICENSE +0 -21
  96. /mcp_use/{observability → agents/observability}/__init__.py +0 -0
  97. /mcp_use/{observability → agents/observability}/callbacks_manager.py +0 -0
  98. /mcp_use/{observability → agents/observability}/laminar.py +0 -0
  99. /mcp_use/{observability → agents/observability}/langfuse.py +0 -0
  100. {mcp_use-1.3.11.dist-info → mcp_use-1.3.13.dist-info}/WHEEL +0 -0
  101. {mcp_use-1.3.11.dist-info → mcp_use-1.3.13.dist-info}/entry_points.txt +0 -0
@@ -1,293 +1,18 @@
1
- """
2
- HTTP connector for MCP implementations.
1
+ # mcp_use/connectors/http.py
2
+ import warnings
3
3
 
4
- This module provides a connector for communicating with MCP implementations
5
- through HTTP APIs with SSE or Streamable HTTP for transport.
6
- """
4
+ from typing_extensions import deprecated
7
5
 
8
- from typing import Any
6
+ from mcp_use.client.connectors.http import HttpConnector as _HttpConnector
9
7
 
10
- import httpx
11
- from mcp import ClientSession
12
- from mcp.client.session import ElicitationFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
13
- from mcp.shared.exceptions import McpError
8
+ warnings.warn(
9
+ "mcp_use.connectors.http is deprecated. "
10
+ "Use mcp_use.client.connectors.http. "
11
+ "This import will be removed in version 1.4.0",
12
+ DeprecationWarning,
13
+ stacklevel=2,
14
+ )
14
15
 
15
- from mcp_use.auth.oauth import OAuthClientProvider
16
16
 
17
- from ..auth import BearerAuth, OAuth
18
- from ..exceptions import OAuthAuthenticationError, OAuthDiscoveryError
19
- from ..logging import logger
20
- from ..task_managers import SseConnectionManager, StreamableHttpConnectionManager
21
- from .base import BaseConnector
22
-
23
-
24
- class HttpConnector(BaseConnector):
25
- """Connector for MCP implementations using HTTP transport with SSE or streamable HTTP.
26
-
27
- This connector uses HTTP/SSE or streamable HTTP to communicate with remote MCP implementations,
28
- using a connection manager to handle the proper lifecycle management.
29
- """
30
-
31
- def __init__(
32
- self,
33
- base_url: str,
34
- headers: dict[str, str] | None = None,
35
- timeout: float = 5,
36
- sse_read_timeout: float = 60 * 5,
37
- auth: str | dict[str, Any] | httpx.Auth | None = None,
38
- sampling_callback: SamplingFnT | None = None,
39
- elicitation_callback: ElicitationFnT | None = None,
40
- message_handler: MessageHandlerFnT | None = None,
41
- logging_callback: LoggingFnT | None = None,
42
- ):
43
- """Initialize a new HTTP connector.
44
-
45
- Args:
46
- base_url: The base URL of the MCP HTTP API.
47
- headers: Optional additional headers.
48
- timeout: Timeout for HTTP operations in seconds.
49
- sse_read_timeout: Timeout for SSE read operations in seconds.
50
- auth: Authentication method - can be:
51
- - A string token: Use Bearer token authentication
52
- - A dict with OAuth config: {"client_id": "...", "client_secret": "...", "scope": "..."}
53
- - An httpx.Auth object: Use custom authentication
54
- sampling_callback: Optional sampling callback.
55
- elicitation_callback: Optional elicitation callback.
56
- """
57
- super().__init__(
58
- sampling_callback=sampling_callback,
59
- elicitation_callback=elicitation_callback,
60
- message_handler=message_handler,
61
- logging_callback=logging_callback,
62
- )
63
- self.base_url = base_url.rstrip("/")
64
- self.headers = headers or {}
65
- self.timeout = timeout
66
- self.sse_read_timeout = sse_read_timeout
67
- self._auth: httpx.Auth | None = None
68
- self._oauth: OAuth | None = None
69
-
70
- # Handle authentication
71
- if auth is not None:
72
- self._set_auth(auth)
73
-
74
- def _set_auth(self, auth: str | dict[str, Any] | httpx.Auth) -> None:
75
- """Set authentication method.
76
-
77
- Args:
78
- auth: Authentication method - can be:
79
- - A string token: Use Bearer token authentication
80
- - A dict with OAuth config: {"client_id": "...", "client_secret": "...", "scope": "..."}
81
- - An httpx.Auth object: Use custom authentication
82
- """
83
- if isinstance(auth, str):
84
- # Treat as bearer token
85
- self._auth = BearerAuth(token=auth)
86
- self.headers["Authorization"] = f"Bearer {auth}"
87
- elif isinstance(auth, dict):
88
- # Check if this is an OAuth provider configuration
89
- if "oauth_provider" in auth:
90
- oauth_provider = auth["oauth_provider"]
91
- if isinstance(oauth_provider, dict):
92
- oauth_provider = OAuthClientProvider(**oauth_provider)
93
- self._oauth = OAuth(
94
- self.base_url,
95
- scope=auth.get("scope"),
96
- client_id=auth.get("client_id"),
97
- client_secret=auth.get("client_secret"),
98
- callback_port=auth.get("callback_port"),
99
- oauth_provider=oauth_provider,
100
- )
101
- self._oauth_config = auth
102
- else:
103
- self._oauth = OAuth(
104
- self.base_url,
105
- scope=auth.get("scope"),
106
- client_id=auth.get("client_id"),
107
- client_secret=auth.get("client_secret"),
108
- callback_port=auth.get("callback_port"),
109
- )
110
- self._oauth_config = auth
111
- elif isinstance(auth, httpx.Auth):
112
- self._auth = auth
113
- else:
114
- raise ValueError(f"Invalid auth type: {type(auth)}")
115
-
116
- async def connect(self) -> None:
117
- """Establish a connection to the MCP implementation."""
118
- if self._connected:
119
- logger.debug("Already connected to MCP implementation")
120
- return
121
-
122
- # Handle OAuth if needed
123
- if self._oauth:
124
- try:
125
- # Create a temporary client for OAuth metadata discovery
126
- async with httpx.AsyncClient() as client:
127
- bearer_auth = await self._oauth.initialize(client)
128
- if not bearer_auth:
129
- # Need to perform OAuth flow
130
- logger.info("OAuth authentication required")
131
- bearer_auth = await self._oauth.authenticate()
132
-
133
- # Update auth and headers
134
- self._auth = bearer_auth
135
- self.headers["Authorization"] = f"Bearer {bearer_auth.token.get_secret_value()}"
136
- except OAuthDiscoveryError:
137
- # OAuth discovery failed - it means server doesn't support OAuth default urls
138
- logger.debug("OAuth discovery failed, continuing without initialization.")
139
- self._oauth = None
140
- self._auth = None
141
- except OAuthAuthenticationError as e:
142
- logger.error(f"OAuth initialization failed: {e}")
143
- raise
144
-
145
- # Try streamable HTTP first (new transport), fall back to SSE (old transport)
146
- # This implements backwards compatibility per MCP specification
147
- self.transport_type = None
148
- connection_manager = None
149
-
150
- try:
151
- # First, try the new streamable HTTP transport
152
- logger.debug(f"Attempting streamable HTTP connection to: {self.base_url}")
153
- connection_manager = StreamableHttpConnectionManager(
154
- self.base_url, self.headers, self.timeout, self.sse_read_timeout, auth=self._auth
155
- )
156
-
157
- # Test if this is a streamable HTTP server by attempting initialization
158
- read_stream, write_stream = await connection_manager.start()
159
-
160
- # Test if this actually works by trying to create a client session and initialize it
161
- test_client = ClientSession(
162
- read_stream,
163
- write_stream,
164
- sampling_callback=self.sampling_callback,
165
- elicitation_callback=self.elicitation_callback,
166
- message_handler=self._internal_message_handler,
167
- logging_callback=self.logging_callback,
168
- client_info=self.client_info,
169
- )
170
- await test_client.__aenter__()
171
-
172
- try:
173
- # Try to initialize - this is where streamable HTTP vs SSE difference should show up
174
- result = await test_client.initialize()
175
- logger.debug(f"Streamable HTTP initialization result: {result}")
176
-
177
- # If we get here, streamable HTTP works
178
- self.client_session = test_client
179
- self.transport_type = "streamable HTTP"
180
- self._initialized = True # Mark as initialized since we just called initialize()
181
-
182
- # Populate tools, resources, and prompts since we've initialized
183
- server_capabilities = result.capabilities
184
-
185
- if server_capabilities.tools:
186
- # Get available tools directly from client session
187
- tools_result = await self.client_session.list_tools()
188
- self._tools = tools_result.tools if tools_result else []
189
- else:
190
- self._tools = []
191
-
192
- if server_capabilities.resources:
193
- # Get available resources directly from client session
194
- resources_result = await self.client_session.list_resources()
195
- self._resources = resources_result.resources if resources_result else []
196
- else:
197
- self._resources = []
198
-
199
- if server_capabilities.prompts:
200
- # Get available prompts directly from client session
201
- prompts_result = await self.client_session.list_prompts()
202
- self._prompts = prompts_result.prompts if prompts_result else []
203
- else:
204
- self._prompts = []
205
-
206
- # Only McpError is raised from client's initialization because
207
- # exceptions are handled internally.
208
- except McpError as mcp_error:
209
- logger.error("MCP protocol error during initialization: %s", mcp_error.error)
210
- # Clean up the test client
211
- try:
212
- await test_client.__aexit__(None, None, None)
213
- except Exception:
214
- pass
215
- raise mcp_error
216
-
217
- except Exception as init_error:
218
- # This catches non-McpError exceptions, like a direct httpx timeout
219
- # but in the most cases this won't happen. It's for safety.
220
- try:
221
- await test_client.__aexit__(None, None, None)
222
- except Exception:
223
- pass
224
- raise init_error
225
-
226
- # Exception from the inner try is propagated here and in
227
- # the most cases is an McpError, so checking instances is useless
228
- except Exception as streamable_error:
229
- logger.debug(f"Streamable HTTP failed: {streamable_error}")
230
-
231
- # Clean up the failed streamable HTTP connection manager
232
- if connection_manager:
233
- try:
234
- await connection_manager.close()
235
- except Exception:
236
- pass
237
-
238
- # It doesn't make sense to check error types. Because client
239
- # always return a McpError, if he can't reach the server
240
- # because it's offline, or if it has an auth problem.
241
- should_fallback = True
242
-
243
- if should_fallback:
244
- try:
245
- # Fall back to the old SSE transport
246
- logger.debug(f"Attempting SSE fallback connection to: {self.base_url}")
247
- connection_manager = SseConnectionManager(
248
- self.base_url, self.headers, self.timeout, self.sse_read_timeout, auth=self._auth
249
- )
250
-
251
- read_stream, write_stream = await connection_manager.start()
252
-
253
- # Create the client session for SSE
254
- self.client_session = ClientSession(
255
- read_stream,
256
- write_stream,
257
- sampling_callback=self.sampling_callback,
258
- elicitation_callback=self.elicitation_callback,
259
- message_handler=self._internal_message_handler,
260
- logging_callback=self.logging_callback,
261
- client_info=self.client_info,
262
- )
263
- await self.client_session.__aenter__()
264
- self.transport_type = "SSE"
265
-
266
- except* Exception as sse_error:
267
- # Get the exception from the ExceptionGroup, and here we will get the correct type.
268
- sse_error = sse_error.exceptions[0]
269
- if isinstance(sse_error, httpx.HTTPStatusError) and sse_error.response.status_code in [
270
- 401,
271
- 403,
272
- 407,
273
- ]:
274
- raise OAuthAuthenticationError(
275
- f"Server requires authentication (HTTP {sse_error.response.status_code}) "
276
- "but auth failed. Please provide auth configuration manually."
277
- ) from sse_error
278
- logger.error(
279
- f"Both transport methods failed. Streamable HTTP: {streamable_error}, SSE: {sse_error}"
280
- )
281
- raise sse_error
282
- else:
283
- raise streamable_error
284
-
285
- # Store the successful connection manager and mark as connected
286
- self._connection_manager = connection_manager
287
- self._connected = True
288
- logger.debug(f"Successfully connected to MCP implementation via {self.transport_type}: {self.base_url}")
289
-
290
- @property
291
- def public_identifier(self) -> str:
292
- """Get the identifier for the connector."""
293
- return {"type": self.transport_type, "base_url": self.base_url}
17
+ @deprecated("Use mcp_use.client.connectors.http.HttpConnector")
18
+ class HttpConnector(_HttpConnector): ...
@@ -1,302 +1,18 @@
1
- """
2
- Sandbox connector for MCP implementations.
1
+ # mcp_use/connectors/sandbox.py
2
+ import warnings
3
3
 
4
- This module provides a connector for communicating with MCP implementations
5
- that are executed inside a sandbox environment (currently using E2B).
6
- """
4
+ from typing_extensions import deprecated
7
5
 
8
- import asyncio
9
- import os
10
- import sys
11
- import time
6
+ from mcp_use.client.connectors.sandbox import SandboxConnector as _SandboxConnector
12
7
 
13
- import aiohttp
14
- from mcp import ClientSession
15
- from mcp.client.session import ElicitationFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
8
+ warnings.warn(
9
+ "mcp_use.connectors.sandbox is deprecated. "
10
+ "Use mcp_use.client.connectors.sandbox. "
11
+ "This import will be removed in version 1.4.0",
12
+ DeprecationWarning,
13
+ stacklevel=2,
14
+ )
16
15
 
17
- from ..logging import logger
18
- from ..task_managers import SseConnectionManager
19
16
 
20
- # Import E2B SDK components (optional dependency)
21
- try:
22
- logger.debug("Attempting to import e2b_code_interpreter...")
23
- from e2b_code_interpreter import CommandHandle, Sandbox
24
-
25
- logger.debug("Successfully imported e2b_code_interpreter")
26
- except ImportError as e:
27
- logger.debug(f"Failed to import e2b_code_interpreter: {e}")
28
- CommandHandle = None
29
- Sandbox = None
30
-
31
- from ..types.sandbox import SandboxOptions
32
- from .base import BaseConnector
33
-
34
-
35
- class SandboxConnector(BaseConnector):
36
- """Connector for MCP implementations running in a sandbox environment.
37
-
38
- This connector runs a user-defined stdio command within a sandbox environment,
39
- currently implemented using E2B, potentially wrapped by a utility like 'supergateway'
40
- to expose its stdio.
41
- """
42
-
43
- def __init__(
44
- self,
45
- command: str,
46
- args: list[str],
47
- env: dict[str, str] | None = None,
48
- e2b_options: SandboxOptions | None = None,
49
- timeout: float = 5,
50
- sse_read_timeout: float = 60 * 5,
51
- sampling_callback: SamplingFnT | None = None,
52
- elicitation_callback: ElicitationFnT | None = None,
53
- message_handler: MessageHandlerFnT | None = None,
54
- logging_callback: LoggingFnT | None = None,
55
- ):
56
- """Initialize a new sandbox connector.
57
-
58
- Args:
59
- command: The user's MCP server command to execute in the sandbox.
60
- args: Command line arguments for the user's MCP server command.
61
- env: Environment variables for the user's MCP server command.
62
- e2b_options: Configuration options for the E2B sandbox environment.
63
- See SandboxOptions for available options and defaults.
64
- timeout: Timeout for the sandbox process in seconds.
65
- sse_read_timeout: Timeout for the SSE connection in seconds.
66
- sampling_callback: Optional sampling callback.
67
- elicitation_callback: Optional elicitation callback.
68
- """
69
- super().__init__(
70
- sampling_callback=sampling_callback,
71
- elicitation_callback=elicitation_callback,
72
- message_handler=message_handler,
73
- logging_callback=logging_callback,
74
- )
75
- if Sandbox is None:
76
- raise ImportError(
77
- "E2B SDK (e2b-code-interpreter) not found. Please install it with "
78
- "'pip install mcp-use[e2b]' (or 'pip install e2b-code-interpreter')."
79
- )
80
-
81
- self.user_command = command
82
- self.user_args = args or []
83
- self.user_env = env or {}
84
-
85
- _e2b_options = e2b_options or {}
86
-
87
- self.api_key = _e2b_options.get("api_key") or os.environ.get("E2B_API_KEY")
88
- if not self.api_key:
89
- raise ValueError(
90
- "E2B API key is required. Provide it via 'sandbox_options.api_key'"
91
- " or the E2B_API_KEY environment variable."
92
- )
93
-
94
- self.sandbox_template_id = _e2b_options.get("sandbox_template_id", "base")
95
- self.supergateway_cmd_parts = _e2b_options.get("supergateway_command", "npx -y supergateway")
96
-
97
- self.sandbox: Sandbox | None = None
98
- self.process: CommandHandle | None = None
99
- self.client_session: ClientSession | None = None
100
- self.errlog = sys.stderr
101
- self.base_url: str | None = None
102
- self._connected = False
103
- self._connection_manager: SseConnectionManager | None = None
104
-
105
- # SSE connection parameters
106
- self.headers = {}
107
- self.timeout = timeout
108
- self.sse_read_timeout = sse_read_timeout
109
-
110
- self.stdout_lines: list[str] = []
111
- self.stderr_lines: list[str] = []
112
- self._server_ready = asyncio.Event()
113
-
114
- def _handle_stdout(self, data: str) -> None:
115
- """Handle stdout data from the sandbox process."""
116
- self.stdout_lines.append(data)
117
- logger.debug(f"[SANDBOX STDOUT] {data}", end="", flush=True)
118
-
119
- def _handle_stderr(self, data: str) -> None:
120
- """Handle stderr data from the sandbox process."""
121
- self.stderr_lines.append(data)
122
- logger.debug(f"[SANDBOX STDERR] {data}", file=self.errlog, end="", flush=True)
123
-
124
- async def wait_for_server_response(self, base_url: str, timeout: int = 30) -> bool:
125
- """Wait for the server to respond to HTTP requests.
126
- Args:
127
- base_url: The base URL to check for server readiness
128
- timeout: Maximum time to wait in seconds
129
- Returns:
130
- True if server is responding, raises TimeoutError otherwise
131
- """
132
- logger.info(f"Waiting for server at {base_url} to respond...")
133
- sys.stdout.flush()
134
-
135
- start_time = time.time()
136
- ping_url = f"{base_url}/sse"
137
-
138
- # Try to connect to the server
139
- while time.time() - start_time < timeout:
140
- try:
141
- async with aiohttp.ClientSession() as session:
142
- try:
143
- # First try the endpoint
144
- async with session.get(ping_url, timeout=2) as response:
145
- if response.status == 200:
146
- elapsed = time.time() - start_time
147
- logger.info(f"Server is ready! SSE endpoint responded with 200 after {elapsed:.1f}s")
148
- return True
149
- except Exception:
150
- # If sse endpoint doesn't work, try the base URL
151
- async with session.get(base_url, timeout=2) as response:
152
- if response.status < 500: # Accept any non-server error
153
- elapsed = time.time() - start_time
154
- logger.info(
155
- f"Server is ready! Base URL responded with {response.status} after {elapsed:.1f}s"
156
- )
157
- return True
158
- except Exception:
159
- # Wait a bit before trying again
160
- await asyncio.sleep(0.5)
161
- continue
162
-
163
- # If we get here, the request failed
164
- await asyncio.sleep(0.5)
165
-
166
- # Log status every 5 seconds
167
- elapsed = time.time() - start_time
168
- if int(elapsed) % 5 == 0:
169
- logger.info(f"Still waiting for server to respond... ({elapsed:.1f}s elapsed)")
170
- sys.stdout.flush()
171
-
172
- # If we get here, we timed out
173
- raise TimeoutError(f"Timeout waiting for server to respond (waited {timeout} seconds)")
174
-
175
- async def connect(self):
176
- """Connect to the sandbox and start the MCP server."""
177
-
178
- if self._connected:
179
- logger.debug("Already connected to MCP implementation")
180
- return
181
-
182
- logger.debug("Connecting to MCP implementation in sandbox")
183
-
184
- try:
185
- # Create and start the sandbox
186
- self.sandbox = Sandbox(
187
- template=self.sandbox_template_id,
188
- api_key=self.api_key,
189
- )
190
-
191
- # Get the host for the sandbox
192
- host = self.sandbox.get_host(3000)
193
- self.base_url = f"https://{host}".rstrip("/")
194
-
195
- # Append command with args
196
- command = f"{self.user_command} {' '.join(self.user_args)}"
197
-
198
- # Construct the full command with supergateway
199
- full_command = f'{self.supergateway_cmd_parts} \
200
- --base-url {self.base_url} \
201
- --port 3000 \
202
- --cors \
203
- --stdio "{command}"'
204
-
205
- logger.debug(f"Full command: {full_command}")
206
-
207
- # Start the process in the sandbox with our stdout/stderr handlers
208
- self.process: CommandHandle = self.sandbox.commands.run(
209
- full_command,
210
- envs=self.user_env,
211
- timeout=1000 * 60 * 10, # 10 minutes timeout
212
- background=True,
213
- on_stdout=self._handle_stdout,
214
- on_stderr=self._handle_stderr,
215
- )
216
-
217
- # Wait for the server to be ready
218
- await self.wait_for_server_response(self.base_url, timeout=30)
219
- logger.debug("Initializing connection manager...")
220
-
221
- # Create the SSE connection URL
222
- sse_url = f"{self.base_url}/sse"
223
-
224
- # Create and start the connection manager
225
- self._connection_manager = SseConnectionManager(sse_url, self.headers, self.timeout, self.sse_read_timeout)
226
- read_stream, write_stream = await self._connection_manager.start()
227
-
228
- # Create the client session
229
- self.client_session = ClientSession(
230
- read_stream,
231
- write_stream,
232
- sampling_callback=self.sampling_callback,
233
- elicitation_callback=self.elicitation_callback,
234
- message_handler=self._internal_message_handler,
235
- logging_callback=self.logging_callback,
236
- client_info=self.client_info,
237
- )
238
- await self.client_session.__aenter__()
239
-
240
- # Mark as connected
241
- self._connected = True
242
- logger.debug(f"Successfully connected to MCP implementation via HTTP/SSE: {self.base_url}")
243
-
244
- except Exception as e:
245
- logger.error(f"Failed to connect to MCP implementation: {e}")
246
-
247
- # Clean up any resources if connection failed
248
- await self._cleanup_resources()
249
-
250
- raise e
251
-
252
- async def _cleanup_resources(self) -> None:
253
- """Clean up all resources associated with this connector, including the sandbox.
254
- This method extends the base implementation to also terminate the sandbox instance
255
- and clean up any processes running in the sandbox.
256
- """
257
- logger.debug("Cleaning up sandbox resources")
258
-
259
- # Terminate any running process
260
- if self.process:
261
- try:
262
- logger.debug("Terminating sandbox process")
263
- self.process.kill()
264
- except Exception as e:
265
- logger.warning(f"Error terminating sandbox process: {e}")
266
- finally:
267
- self.process = None
268
-
269
- # Close the sandbox
270
- if self.sandbox:
271
- try:
272
- logger.debug("Closing sandbox instance")
273
- self.sandbox.kill()
274
- logger.debug("Sandbox instance closed successfully")
275
- except Exception as e:
276
- logger.warning(f"Error closing sandbox: {e}")
277
- finally:
278
- self.sandbox = None
279
-
280
- # Then call the parent method to clean up the rest
281
- await super()._cleanup_resources()
282
-
283
- # Clear any collected output
284
- self.stdout_lines = []
285
- self.stderr_lines = []
286
- self.base_url = None
287
-
288
- async def disconnect(self) -> None:
289
- """Close the connection to the MCP implementation."""
290
- if not self._connected:
291
- logger.debug("Not connected to MCP implementation")
292
- return
293
-
294
- logger.debug("Disconnecting from MCP implementation")
295
- await self._cleanup_resources()
296
- self._connected = False
297
- logger.debug("Disconnected from MCP implementation")
298
-
299
- @property
300
- def public_identifier(self) -> str:
301
- """Get the identifier for the connector."""
302
- return {"type": "sandbox", "command": self.user_command, "args": self.user_args}
17
+ @deprecated("Use mcp_use.client.connectors.sandbox.SandboxConnector")
18
+ class SandboxConnector(_SandboxConnector): ...