mcp-use 1.3.8__py3-none-any.whl → 1.3.10__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 +6 -3
- mcp_use/adapters/base.py +3 -3
- mcp_use/adapters/langchain_adapter.py +5 -4
- mcp_use/agents/mcpagent.py +101 -17
- mcp_use/agents/remote.py +102 -14
- mcp_use/cli.py +581 -0
- mcp_use/client.py +15 -1
- mcp_use/config.py +10 -8
- mcp_use/connectors/base.py +100 -15
- mcp_use/connectors/http.py +13 -2
- mcp_use/connectors/sandbox.py +12 -6
- mcp_use/connectors/stdio.py +11 -2
- mcp_use/errors/__init__.py +1 -0
- mcp_use/errors/error_formatting.py +29 -0
- mcp_use/logging.py +27 -12
- mcp_use/managers/base.py +36 -0
- mcp_use/managers/server_manager.py +3 -8
- mcp_use/managers/tools/connect_server.py +2 -1
- mcp_use/managers/tools/disconnect_server.py +2 -1
- mcp_use/managers/tools/list_servers_tool.py +2 -0
- mcp_use/observability/__init__.py +2 -1
- mcp_use/observability/callbacks_manager.py +162 -0
- mcp_use/observability/laminar.py +24 -3
- mcp_use/observability/langfuse.py +27 -3
- mcp_use/session.py +70 -0
- mcp_use/telemetry/telemetry.py +1 -4
- {mcp_use-1.3.8.dist-info → mcp_use-1.3.10.dist-info}/METADATA +73 -48
- mcp_use-1.3.10.dist-info/RECORD +55 -0
- mcp_use-1.3.10.dist-info/entry_points.txt +2 -0
- mcp_use-1.3.8.dist-info/RECORD +0 -49
- {mcp_use-1.3.8.dist-info → mcp_use-1.3.10.dist-info}/WHEEL +0 -0
- {mcp_use-1.3.8.dist-info → mcp_use-1.3.10.dist-info}/licenses/LICENSE +0 -0
mcp_use/connectors/base.py
CHANGED
|
@@ -5,14 +5,27 @@ This module provides the base connector interface that all MCP connectors
|
|
|
5
5
|
must implement.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import warnings
|
|
8
9
|
from abc import ABC, abstractmethod
|
|
9
10
|
from datetime import timedelta
|
|
10
11
|
from typing import Any
|
|
11
12
|
|
|
12
13
|
from mcp import ClientSession, Implementation
|
|
13
|
-
from mcp.client.session import ElicitationFnT, SamplingFnT
|
|
14
|
+
from mcp.client.session import ElicitationFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
|
|
14
15
|
from mcp.shared.exceptions import McpError
|
|
15
|
-
from mcp.types import
|
|
16
|
+
from mcp.types import (
|
|
17
|
+
CallToolResult,
|
|
18
|
+
GetPromptResult,
|
|
19
|
+
Prompt,
|
|
20
|
+
PromptListChangedNotification,
|
|
21
|
+
ReadResourceResult,
|
|
22
|
+
Resource,
|
|
23
|
+
ResourceListChangedNotification,
|
|
24
|
+
ServerCapabilities,
|
|
25
|
+
ServerNotification,
|
|
26
|
+
Tool,
|
|
27
|
+
ToolListChangedNotification,
|
|
28
|
+
)
|
|
16
29
|
from pydantic import AnyUrl
|
|
17
30
|
|
|
18
31
|
import mcp_use
|
|
@@ -31,6 +44,8 @@ class BaseConnector(ABC):
|
|
|
31
44
|
self,
|
|
32
45
|
sampling_callback: SamplingFnT | None = None,
|
|
33
46
|
elicitation_callback: ElicitationFnT | None = None,
|
|
47
|
+
message_handler: MessageHandlerFnT | None = None,
|
|
48
|
+
logging_callback: LoggingFnT | None = None,
|
|
34
49
|
):
|
|
35
50
|
"""Initialize base connector with common attributes."""
|
|
36
51
|
self.client_session: ClientSession | None = None
|
|
@@ -43,6 +58,9 @@ class BaseConnector(ABC):
|
|
|
43
58
|
self.auto_reconnect = True # Whether to automatically reconnect on connection loss (not configurable for now)
|
|
44
59
|
self.sampling_callback = sampling_callback
|
|
45
60
|
self.elicitation_callback = elicitation_callback
|
|
61
|
+
self.message_handler = message_handler
|
|
62
|
+
self.logging_callback = logging_callback
|
|
63
|
+
self.capabilities: ServerCapabilities | None = None
|
|
46
64
|
|
|
47
65
|
@property
|
|
48
66
|
def client_info(self) -> Implementation:
|
|
@@ -53,6 +71,20 @@ class BaseConnector(ABC):
|
|
|
53
71
|
url="https://github.com/mcp-use/mcp-use",
|
|
54
72
|
)
|
|
55
73
|
|
|
74
|
+
async def _internal_message_handler(self, message: Any) -> None:
|
|
75
|
+
"""Wrap the user-provided message handler."""
|
|
76
|
+
if isinstance(message, ServerNotification):
|
|
77
|
+
if isinstance(message.root, ToolListChangedNotification):
|
|
78
|
+
logger.debug("Received tool list changed notification")
|
|
79
|
+
elif isinstance(message.root, ResourceListChangedNotification):
|
|
80
|
+
logger.debug("Received resource list changed notification")
|
|
81
|
+
elif isinstance(message.root, PromptListChangedNotification):
|
|
82
|
+
logger.debug("Received prompt list changed notification")
|
|
83
|
+
|
|
84
|
+
# Call the user's handler
|
|
85
|
+
if self.message_handler:
|
|
86
|
+
await self.message_handler(message)
|
|
87
|
+
|
|
56
88
|
@abstractmethod
|
|
57
89
|
async def connect(self) -> None:
|
|
58
90
|
"""Establish a connection to the MCP implementation."""
|
|
@@ -125,37 +157,37 @@ class BaseConnector(ABC):
|
|
|
125
157
|
result = await self.client_session.initialize()
|
|
126
158
|
self._initialized = True # Mark as initialized
|
|
127
159
|
|
|
128
|
-
|
|
160
|
+
self.capabilities = result.capabilities
|
|
129
161
|
|
|
130
|
-
if
|
|
162
|
+
if self.capabilities.tools:
|
|
131
163
|
# Get available tools directly from client session
|
|
132
164
|
try:
|
|
133
165
|
tools_result = await self.client_session.list_tools()
|
|
134
166
|
self._tools = tools_result.tools if tools_result else []
|
|
135
167
|
except Exception as e:
|
|
136
|
-
logger.error(f"Error listing tools: {e}")
|
|
168
|
+
logger.error(f"Error listing tools for connector {self.public_identifier}: {e}")
|
|
137
169
|
self._tools = []
|
|
138
170
|
else:
|
|
139
171
|
self._tools = []
|
|
140
172
|
|
|
141
|
-
if
|
|
173
|
+
if self.capabilities.resources:
|
|
142
174
|
# Get available resources directly from client session
|
|
143
175
|
try:
|
|
144
176
|
resources_result = await self.client_session.list_resources()
|
|
145
177
|
self._resources = resources_result.resources if resources_result else []
|
|
146
178
|
except Exception as e:
|
|
147
|
-
logger.error(f"Error listing resources: {e}")
|
|
179
|
+
logger.error(f"Error listing resources for connector {self.public_identifier}: {e}")
|
|
148
180
|
self._resources = []
|
|
149
181
|
else:
|
|
150
182
|
self._resources = []
|
|
151
183
|
|
|
152
|
-
if
|
|
184
|
+
if self.capabilities.prompts:
|
|
153
185
|
# Get available prompts directly from client session
|
|
154
186
|
try:
|
|
155
187
|
prompts_result = await self.client_session.list_prompts()
|
|
156
188
|
self._prompts = prompts_result.prompts if prompts_result else []
|
|
157
189
|
except Exception as e:
|
|
158
|
-
logger.error(f"Error listing prompts: {e}")
|
|
190
|
+
logger.error(f"Error listing prompts for connector {self.public_identifier}: {e}")
|
|
159
191
|
self._prompts = []
|
|
160
192
|
else:
|
|
161
193
|
self._prompts = []
|
|
@@ -170,21 +202,57 @@ class BaseConnector(ABC):
|
|
|
170
202
|
|
|
171
203
|
@property
|
|
172
204
|
def tools(self) -> list[Tool]:
|
|
173
|
-
"""Get the list of available tools.
|
|
205
|
+
"""Get the list of available tools.
|
|
206
|
+
|
|
207
|
+
.. deprecated::
|
|
208
|
+
This property is deprecated because it may return stale data when the server
|
|
209
|
+
sends list change notifications. Use `await list_tools()` instead to ensure
|
|
210
|
+
you always get the latest data.
|
|
211
|
+
"""
|
|
212
|
+
warnings.warn(
|
|
213
|
+
"The 'tools' property is deprecated and may return stale data. "
|
|
214
|
+
"Use 'await list_tools()' instead to ensure fresh data.",
|
|
215
|
+
DeprecationWarning,
|
|
216
|
+
stacklevel=2,
|
|
217
|
+
)
|
|
174
218
|
if self._tools is None:
|
|
175
219
|
raise RuntimeError("MCP client is not initialized")
|
|
176
220
|
return self._tools
|
|
177
221
|
|
|
178
222
|
@property
|
|
179
223
|
def resources(self) -> list[Resource]:
|
|
180
|
-
"""Get the list of available resources.
|
|
224
|
+
"""Get the list of available resources.
|
|
225
|
+
|
|
226
|
+
.. deprecated::
|
|
227
|
+
This property is deprecated because it may return stale data when the server
|
|
228
|
+
sends list change notifications. Use `await list_resources()` instead to ensure
|
|
229
|
+
you always get the latest data.
|
|
230
|
+
"""
|
|
231
|
+
warnings.warn(
|
|
232
|
+
"The 'resources' property is deprecated and may return stale data. "
|
|
233
|
+
"Use 'await list_resources()' instead to ensure fresh data.",
|
|
234
|
+
DeprecationWarning,
|
|
235
|
+
stacklevel=2,
|
|
236
|
+
)
|
|
181
237
|
if self._resources is None:
|
|
182
238
|
raise RuntimeError("MCP client is not initialized")
|
|
183
239
|
return self._resources
|
|
184
240
|
|
|
185
241
|
@property
|
|
186
242
|
def prompts(self) -> list[Prompt]:
|
|
187
|
-
"""Get the list of available prompts.
|
|
243
|
+
"""Get the list of available prompts.
|
|
244
|
+
|
|
245
|
+
.. deprecated::
|
|
246
|
+
This property is deprecated because it may return stale data when the server
|
|
247
|
+
sends list change notifications. Use `await list_prompts()' instead to ensure
|
|
248
|
+
you always get the latest data.
|
|
249
|
+
"""
|
|
250
|
+
warnings.warn(
|
|
251
|
+
"The 'prompts' property is deprecated and may return stale data. "
|
|
252
|
+
"Use 'await list_prompts()' instead to ensure fresh data.",
|
|
253
|
+
DeprecationWarning,
|
|
254
|
+
stacklevel=2,
|
|
255
|
+
)
|
|
188
256
|
if self._prompts is None:
|
|
189
257
|
raise RuntimeError("MCP client is not initialized")
|
|
190
258
|
return self._prompts
|
|
@@ -303,28 +371,39 @@ class BaseConnector(ABC):
|
|
|
303
371
|
async def list_tools(self) -> list[Tool]:
|
|
304
372
|
"""List all available tools from the MCP implementation."""
|
|
305
373
|
|
|
374
|
+
if self.capabilities and not self.capabilities.tools:
|
|
375
|
+
logger.debug(f"Server {self.public_identifier} does not support tools")
|
|
376
|
+
return []
|
|
377
|
+
|
|
306
378
|
# Ensure we're connected
|
|
307
379
|
await self._ensure_connected()
|
|
308
380
|
|
|
309
381
|
logger.debug("Listing tools")
|
|
310
382
|
try:
|
|
311
383
|
result = await self.client_session.list_tools()
|
|
384
|
+
self._tools = result.tools
|
|
312
385
|
return result.tools
|
|
313
386
|
except McpError as e:
|
|
314
|
-
logger.error(f"Error listing tools: {e}")
|
|
387
|
+
logger.error(f"Error listing tools for connector {self.public_identifier}: {e}")
|
|
315
388
|
return []
|
|
316
389
|
|
|
317
390
|
async def list_resources(self) -> list[Resource]:
|
|
318
391
|
"""List all available resources from the MCP implementation."""
|
|
392
|
+
|
|
393
|
+
if self.capabilities and not self.capabilities.resources:
|
|
394
|
+
logger.debug(f"Server {self.public_identifier} does not support resources")
|
|
395
|
+
return []
|
|
396
|
+
|
|
319
397
|
# Ensure we're connected
|
|
320
398
|
await self._ensure_connected()
|
|
321
399
|
|
|
322
400
|
logger.debug("Listing resources")
|
|
323
401
|
try:
|
|
324
402
|
result = await self.client_session.list_resources()
|
|
403
|
+
self._resources = result.resources
|
|
325
404
|
return result.resources
|
|
326
405
|
except McpError as e:
|
|
327
|
-
logger.
|
|
406
|
+
logger.warning(f"Error listing resources for connector {self.public_identifier}: {e}")
|
|
328
407
|
return []
|
|
329
408
|
|
|
330
409
|
async def read_resource(self, uri: AnyUrl) -> ReadResourceResult:
|
|
@@ -337,14 +416,20 @@ class BaseConnector(ABC):
|
|
|
337
416
|
|
|
338
417
|
async def list_prompts(self) -> list[Prompt]:
|
|
339
418
|
"""List all available prompts from the MCP implementation."""
|
|
419
|
+
|
|
420
|
+
if self.capabilities and not self.capabilities.prompts:
|
|
421
|
+
logger.debug(f"Server {self.public_identifier} does not support prompts")
|
|
422
|
+
return []
|
|
423
|
+
|
|
340
424
|
await self._ensure_connected()
|
|
341
425
|
|
|
342
426
|
logger.debug("Listing prompts")
|
|
343
427
|
try:
|
|
344
428
|
result = await self.client_session.list_prompts()
|
|
429
|
+
self._prompts = result.prompts
|
|
345
430
|
return result.prompts
|
|
346
431
|
except McpError as e:
|
|
347
|
-
logger.error(f"Error listing prompts: {e}")
|
|
432
|
+
logger.error(f"Error listing prompts for connector {self.public_identifier}: {e}")
|
|
348
433
|
return []
|
|
349
434
|
|
|
350
435
|
async def get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> GetPromptResult:
|
mcp_use/connectors/http.py
CHANGED
|
@@ -7,7 +7,7 @@ through HTTP APIs with SSE or Streamable HTTP for transport.
|
|
|
7
7
|
|
|
8
8
|
import httpx
|
|
9
9
|
from mcp import ClientSession
|
|
10
|
-
from mcp.client.session import ElicitationFnT, SamplingFnT
|
|
10
|
+
from mcp.client.session import ElicitationFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
|
|
11
11
|
|
|
12
12
|
from ..logging import logger
|
|
13
13
|
from ..task_managers import SseConnectionManager, StreamableHttpConnectionManager
|
|
@@ -30,6 +30,8 @@ class HttpConnector(BaseConnector):
|
|
|
30
30
|
sse_read_timeout: float = 60 * 5,
|
|
31
31
|
sampling_callback: SamplingFnT | None = None,
|
|
32
32
|
elicitation_callback: ElicitationFnT | None = None,
|
|
33
|
+
message_handler: MessageHandlerFnT | None = None,
|
|
34
|
+
logging_callback: LoggingFnT | None = None,
|
|
33
35
|
):
|
|
34
36
|
"""Initialize a new HTTP connector.
|
|
35
37
|
|
|
@@ -42,7 +44,12 @@ class HttpConnector(BaseConnector):
|
|
|
42
44
|
sampling_callback: Optional sampling callback.
|
|
43
45
|
elicitation_callback: Optional elicitation callback.
|
|
44
46
|
"""
|
|
45
|
-
super().__init__(
|
|
47
|
+
super().__init__(
|
|
48
|
+
sampling_callback=sampling_callback,
|
|
49
|
+
elicitation_callback=elicitation_callback,
|
|
50
|
+
message_handler=message_handler,
|
|
51
|
+
logging_callback=logging_callback,
|
|
52
|
+
)
|
|
46
53
|
self.base_url = base_url.rstrip("/")
|
|
47
54
|
self.auth_token = auth_token
|
|
48
55
|
self.headers = headers or {}
|
|
@@ -78,6 +85,8 @@ class HttpConnector(BaseConnector):
|
|
|
78
85
|
write_stream,
|
|
79
86
|
sampling_callback=self.sampling_callback,
|
|
80
87
|
elicitation_callback=self.elicitation_callback,
|
|
88
|
+
message_handler=self._internal_message_handler,
|
|
89
|
+
logging_callback=self.logging_callback,
|
|
81
90
|
client_info=self.client_info,
|
|
82
91
|
)
|
|
83
92
|
await test_client.__aenter__()
|
|
@@ -162,6 +171,8 @@ class HttpConnector(BaseConnector):
|
|
|
162
171
|
write_stream,
|
|
163
172
|
sampling_callback=self.sampling_callback,
|
|
164
173
|
elicitation_callback=self.elicitation_callback,
|
|
174
|
+
message_handler=self._internal_message_handler,
|
|
175
|
+
logging_callback=self.logging_callback,
|
|
165
176
|
client_info=self.client_info,
|
|
166
177
|
)
|
|
167
178
|
await self.client_session.__aenter__()
|
mcp_use/connectors/sandbox.py
CHANGED
|
@@ -12,7 +12,7 @@ import time
|
|
|
12
12
|
|
|
13
13
|
import aiohttp
|
|
14
14
|
from mcp import ClientSession
|
|
15
|
-
from mcp.client.session import ElicitationFnT, SamplingFnT
|
|
15
|
+
from mcp.client.session import ElicitationFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
|
|
16
16
|
|
|
17
17
|
from ..logging import logger
|
|
18
18
|
from ..task_managers import SseConnectionManager
|
|
@@ -20,10 +20,7 @@ from ..task_managers import SseConnectionManager
|
|
|
20
20
|
# Import E2B SDK components (optional dependency)
|
|
21
21
|
try:
|
|
22
22
|
logger.debug("Attempting to import e2b_code_interpreter...")
|
|
23
|
-
from e2b_code_interpreter import
|
|
24
|
-
CommandHandle,
|
|
25
|
-
Sandbox,
|
|
26
|
-
)
|
|
23
|
+
from e2b_code_interpreter import CommandHandle, Sandbox
|
|
27
24
|
|
|
28
25
|
logger.debug("Successfully imported e2b_code_interpreter")
|
|
29
26
|
except ImportError as e:
|
|
@@ -53,6 +50,8 @@ class SandboxConnector(BaseConnector):
|
|
|
53
50
|
sse_read_timeout: float = 60 * 5,
|
|
54
51
|
sampling_callback: SamplingFnT | None = None,
|
|
55
52
|
elicitation_callback: ElicitationFnT | None = None,
|
|
53
|
+
message_handler: MessageHandlerFnT | None = None,
|
|
54
|
+
logging_callback: LoggingFnT | None = None,
|
|
56
55
|
):
|
|
57
56
|
"""Initialize a new sandbox connector.
|
|
58
57
|
|
|
@@ -67,7 +66,12 @@ class SandboxConnector(BaseConnector):
|
|
|
67
66
|
sampling_callback: Optional sampling callback.
|
|
68
67
|
elicitation_callback: Optional elicitation callback.
|
|
69
68
|
"""
|
|
70
|
-
super().__init__(
|
|
69
|
+
super().__init__(
|
|
70
|
+
sampling_callback=sampling_callback,
|
|
71
|
+
elicitation_callback=elicitation_callback,
|
|
72
|
+
message_handler=message_handler,
|
|
73
|
+
logging_callback=logging_callback,
|
|
74
|
+
)
|
|
71
75
|
if Sandbox is None:
|
|
72
76
|
raise ImportError(
|
|
73
77
|
"E2B SDK (e2b-code-interpreter) not found. Please install it with "
|
|
@@ -227,6 +231,8 @@ class SandboxConnector(BaseConnector):
|
|
|
227
231
|
write_stream,
|
|
228
232
|
sampling_callback=self.sampling_callback,
|
|
229
233
|
elicitation_callback=self.elicitation_callback,
|
|
234
|
+
message_handler=self._internal_message_handler,
|
|
235
|
+
logging_callback=self.logging_callback,
|
|
230
236
|
client_info=self.client_info,
|
|
231
237
|
)
|
|
232
238
|
await self.client_session.__aenter__()
|
mcp_use/connectors/stdio.py
CHANGED
|
@@ -8,7 +8,7 @@ through the standard input/output streams.
|
|
|
8
8
|
import sys
|
|
9
9
|
|
|
10
10
|
from mcp import ClientSession, StdioServerParameters
|
|
11
|
-
from mcp.client.session import ElicitationFnT, SamplingFnT
|
|
11
|
+
from mcp.client.session import ElicitationFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
|
|
12
12
|
|
|
13
13
|
from ..logging import logger
|
|
14
14
|
from ..task_managers import StdioConnectionManager
|
|
@@ -31,6 +31,8 @@ class StdioConnector(BaseConnector):
|
|
|
31
31
|
errlog=sys.stderr,
|
|
32
32
|
sampling_callback: SamplingFnT | None = None,
|
|
33
33
|
elicitation_callback: ElicitationFnT | None = None,
|
|
34
|
+
message_handler: MessageHandlerFnT | None = None,
|
|
35
|
+
logging_callback: LoggingFnT | None = None,
|
|
34
36
|
):
|
|
35
37
|
"""Initialize a new stdio connector.
|
|
36
38
|
|
|
@@ -42,7 +44,12 @@ class StdioConnector(BaseConnector):
|
|
|
42
44
|
sampling_callback: Optional callback to sample the client.
|
|
43
45
|
elicitation_callback: Optional callback to elicit the client.
|
|
44
46
|
"""
|
|
45
|
-
super().__init__(
|
|
47
|
+
super().__init__(
|
|
48
|
+
sampling_callback=sampling_callback,
|
|
49
|
+
elicitation_callback=elicitation_callback,
|
|
50
|
+
message_handler=message_handler,
|
|
51
|
+
logging_callback=logging_callback,
|
|
52
|
+
)
|
|
46
53
|
self.command = command
|
|
47
54
|
self.args = args or [] # Ensure args is never None
|
|
48
55
|
self.env = env
|
|
@@ -69,6 +76,8 @@ class StdioConnector(BaseConnector):
|
|
|
69
76
|
write_stream,
|
|
70
77
|
sampling_callback=self.sampling_callback,
|
|
71
78
|
elicitation_callback=self.elicitation_callback,
|
|
79
|
+
message_handler=self._internal_message_handler,
|
|
80
|
+
logging_callback=self.logging_callback,
|
|
72
81
|
client_info=self.client_info,
|
|
73
82
|
)
|
|
74
83
|
await self.client_session.__aenter__()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
|
|
3
|
+
from ..logging import logger
|
|
4
|
+
|
|
5
|
+
retryable_exceptions = (TimeoutError, ConnectionError) # We can add more exceptions here
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def format_error(error: Exception, **context) -> dict:
|
|
9
|
+
"""
|
|
10
|
+
Formats an exception into a structured format that can be understood by LLMs.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
error: The exception to format.
|
|
14
|
+
**context: Additional context to include in the formatted error.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
A dictionary containing the formatted error.
|
|
18
|
+
"""
|
|
19
|
+
formatted_context = {
|
|
20
|
+
"error": type(error).__name__,
|
|
21
|
+
"details": str(error),
|
|
22
|
+
"isRetryable": isinstance(error, retryable_exceptions),
|
|
23
|
+
"stack": traceback.format_exc(),
|
|
24
|
+
"code": getattr(error, "code", "UNKNOWN"),
|
|
25
|
+
}
|
|
26
|
+
formatted_context.update(context)
|
|
27
|
+
|
|
28
|
+
logger.error(f"Structured error: {formatted_context}") # For observability (maybe remove later)
|
|
29
|
+
return formatted_context
|
mcp_use/logging.py
CHANGED
|
@@ -80,6 +80,9 @@ class Logger:
|
|
|
80
80
|
|
|
81
81
|
root_logger.setLevel(level)
|
|
82
82
|
|
|
83
|
+
# Set propagate to True to ensure child loggers inherit settings
|
|
84
|
+
root_logger.propagate = True
|
|
85
|
+
|
|
83
86
|
# Clear existing handlers
|
|
84
87
|
for handler in root_logger.handlers[:]:
|
|
85
88
|
root_logger.removeHandler(handler)
|
|
@@ -91,6 +94,7 @@ class Logger:
|
|
|
91
94
|
if log_to_console:
|
|
92
95
|
console_handler = logging.StreamHandler(sys.stdout)
|
|
93
96
|
console_handler.setFormatter(formatter)
|
|
97
|
+
console_handler.setLevel(level) # Ensure handler respects the level
|
|
94
98
|
root_logger.addHandler(console_handler)
|
|
95
99
|
|
|
96
100
|
# Add file handler if requested
|
|
@@ -102,6 +106,7 @@ class Logger:
|
|
|
102
106
|
|
|
103
107
|
file_handler = logging.FileHandler(log_to_file)
|
|
104
108
|
file_handler.setFormatter(formatter)
|
|
109
|
+
file_handler.setLevel(level) # Ensure handler respects the level
|
|
105
110
|
root_logger.addHandler(file_handler)
|
|
106
111
|
|
|
107
112
|
@classmethod
|
|
@@ -114,24 +119,34 @@ class Logger:
|
|
|
114
119
|
global MCP_USE_DEBUG
|
|
115
120
|
MCP_USE_DEBUG = debug_level
|
|
116
121
|
|
|
117
|
-
#
|
|
122
|
+
# Determine the target level
|
|
118
123
|
if debug_level == 2:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
langchain_set_debug(True)
|
|
124
|
+
target_level = logging.DEBUG
|
|
125
|
+
langchain_set_debug(True)
|
|
122
126
|
elif debug_level == 1:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
langchain_set_debug(False)
|
|
127
|
+
target_level = logging.INFO
|
|
128
|
+
langchain_set_debug(False)
|
|
126
129
|
else:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
target_level = logging.WARNING
|
|
131
|
+
langchain_set_debug(False)
|
|
132
|
+
|
|
133
|
+
# Update log level for existing loggers in our registry
|
|
134
|
+
for logger in cls._loggers.values():
|
|
135
|
+
logger.setLevel(target_level)
|
|
136
|
+
# Also update handler levels
|
|
137
|
+
for handler in logger.handlers:
|
|
138
|
+
handler.setLevel(target_level)
|
|
139
|
+
|
|
140
|
+
# Also update all mcp_use child loggers that might exist
|
|
141
|
+
# This ensures loggers created with logging.getLogger() are also updated
|
|
142
|
+
base_logger = logging.getLogger("mcp_use")
|
|
143
|
+
base_logger.setLevel(target_level)
|
|
144
|
+
for handler in base_logger.handlers:
|
|
145
|
+
handler.setLevel(target_level)
|
|
131
146
|
|
|
132
147
|
|
|
133
148
|
# Check environment variable for debug flag
|
|
134
|
-
debug_env = os.environ.get("DEBUG", "").lower()
|
|
149
|
+
debug_env = os.environ.get("MCP_USE_DEBUG", "").lower() or os.environ.get("DEBUG", "").lower()
|
|
135
150
|
if debug_env == "2":
|
|
136
151
|
MCP_USE_DEBUG = 2
|
|
137
152
|
elif debug_env == "1":
|
mcp_use/managers/base.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
from langchain_core.tools import BaseTool
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BaseServerManager(ABC):
|
|
7
|
+
"""Abstract base class for server managers.
|
|
8
|
+
|
|
9
|
+
This class defines the interface for server managers that can be used with MCPAgent.
|
|
10
|
+
Custom server managers should inherit from this class and implement the required methods.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
async def initialize(self) -> None:
|
|
15
|
+
"""Initialize the server manager."""
|
|
16
|
+
raise NotImplementedError
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def tools(self) -> list[BaseTool]:
|
|
21
|
+
"""Get all server management tools and tools from the active server.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
list of LangChain tools for server management plus tools from active server
|
|
25
|
+
"""
|
|
26
|
+
raise NotImplementedError
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def has_tool_changes(self, current_tool_names: set[str]) -> bool:
|
|
30
|
+
"""Check if the available tools have changed.
|
|
31
|
+
Args:
|
|
32
|
+
current_tool_names: Set of currently known tool names
|
|
33
|
+
Returns:
|
|
34
|
+
True if tools have changed, False otherwise
|
|
35
|
+
"""
|
|
36
|
+
raise NotImplementedError
|
|
@@ -4,16 +4,11 @@ from mcp_use.client import MCPClient
|
|
|
4
4
|
from mcp_use.logging import logger
|
|
5
5
|
|
|
6
6
|
from ..adapters.base import BaseAdapter
|
|
7
|
-
from .
|
|
8
|
-
|
|
9
|
-
DisconnectServerTool,
|
|
10
|
-
GetActiveServerTool,
|
|
11
|
-
ListServersTool,
|
|
12
|
-
SearchToolsTool,
|
|
13
|
-
)
|
|
7
|
+
from .base import BaseServerManager
|
|
8
|
+
from .tools import ConnectServerTool, DisconnectServerTool, GetActiveServerTool, ListServersTool, SearchToolsTool
|
|
14
9
|
|
|
15
10
|
|
|
16
|
-
class ServerManager:
|
|
11
|
+
class ServerManager(BaseServerManager):
|
|
17
12
|
"""Manages MCP servers and provides tools for server selection and management.
|
|
18
13
|
|
|
19
14
|
This class allows an agent to discover and select which MCP server to use,
|
|
@@ -2,6 +2,7 @@ from typing import ClassVar
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, Field
|
|
4
4
|
|
|
5
|
+
from mcp_use.errors.error_formatting import format_error
|
|
5
6
|
from mcp_use.logging import logger
|
|
6
7
|
|
|
7
8
|
from .base_tool import MCPServerTool
|
|
@@ -62,7 +63,7 @@ class ConnectServerTool(MCPServerTool):
|
|
|
62
63
|
|
|
63
64
|
except Exception as e:
|
|
64
65
|
logger.error(f"Error connecting to server '{server_name}': {e}")
|
|
65
|
-
return
|
|
66
|
+
return format_error(e, server_name=server_name)
|
|
66
67
|
|
|
67
68
|
def _run(self, server_name: str) -> str:
|
|
68
69
|
"""Synchronous version that raises a NotImplementedError - use _arun instead."""
|
|
@@ -2,6 +2,7 @@ from typing import ClassVar
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel
|
|
4
4
|
|
|
5
|
+
from mcp_use.errors.error_formatting import format_error
|
|
5
6
|
from mcp_use.logging import logger
|
|
6
7
|
|
|
7
8
|
from .base_tool import MCPServerTool
|
|
@@ -36,7 +37,7 @@ class DisconnectServerTool(MCPServerTool):
|
|
|
36
37
|
return f"Successfully disconnected from MCP server '{server_name}'."
|
|
37
38
|
except Exception as e:
|
|
38
39
|
logger.error(f"Error disconnecting from server '{server_name}': {e}")
|
|
39
|
-
return
|
|
40
|
+
return format_error(e, server_name=server_name)
|
|
40
41
|
|
|
41
42
|
async def _arun(self, **kwargs) -> str:
|
|
42
43
|
"""Async implementation of _run."""
|
|
@@ -2,6 +2,7 @@ from typing import ClassVar
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel
|
|
4
4
|
|
|
5
|
+
from mcp_use.errors.error_formatting import format_error
|
|
5
6
|
from mcp_use.logging import logger
|
|
6
7
|
|
|
7
8
|
from .base_tool import MCPServerTool
|
|
@@ -44,6 +45,7 @@ class ListServersTool(MCPServerTool):
|
|
|
44
45
|
result += f" {tool_count} tools available for this server\n"
|
|
45
46
|
except Exception as e:
|
|
46
47
|
logger.error(f"Unexpected error listing tools for server '{server_name}': {e}")
|
|
48
|
+
return format_error(e, server=server_name, operation="list_tools")
|
|
47
49
|
|
|
48
50
|
return result
|
|
49
51
|
|
|
@@ -4,5 +4,6 @@ from dotenv import load_dotenv
|
|
|
4
4
|
load_dotenv()
|
|
5
5
|
|
|
6
6
|
from . import laminar, langfuse # noqa
|
|
7
|
+
from .callbacks_manager import ObservabilityManager, get_default_manager, create_manager # noqa
|
|
7
8
|
|
|
8
|
-
__all__ = ["laminar", "langfuse"]
|
|
9
|
+
__all__ = ["laminar", "langfuse", "ObservabilityManager", "get_default_manager", "create_manager"]
|