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