mcp-use 1.3.0__py3-none-any.whl → 1.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mcp-use might be problematic. Click here for more details.
- mcp_use/__init__.py +2 -0
- mcp_use/adapters/base.py +2 -6
- mcp_use/adapters/langchain_adapter.py +4 -11
- mcp_use/agents/base.py +1 -3
- mcp_use/agents/mcpagent.py +121 -45
- mcp_use/agents/prompts/system_prompt_builder.py +1 -3
- mcp_use/client.py +26 -11
- mcp_use/config.py +9 -9
- mcp_use/connectors/base.py +136 -32
- mcp_use/connectors/http.py +100 -30
- mcp_use/connectors/sandbox.py +11 -16
- mcp_use/connectors/stdio.py +8 -5
- mcp_use/connectors/websocket.py +8 -5
- mcp_use/logging.py +1 -1
- mcp_use/managers/server_manager.py +5 -16
- mcp_use/managers/tools/disconnect_server.py +1 -3
- mcp_use/managers/tools/get_active_server.py +1 -4
- mcp_use/managers/tools/search_tools.py +29 -36
- mcp_use/managers/tools/use_tool.py +5 -18
- mcp_use/observability/__init__.py +8 -0
- mcp_use/observability/laminar.py +21 -0
- mcp_use/observability/langfuse.py +35 -0
- mcp_use/session.py +1 -4
- mcp_use/task_managers/__init__.py +2 -1
- mcp_use/task_managers/base.py +10 -4
- mcp_use/task_managers/streamable_http.py +81 -0
- mcp_use/task_managers/websocket.py +5 -0
- mcp_use/telemetry/__init__.py +0 -0
- mcp_use/telemetry/events.py +93 -0
- mcp_use/telemetry/telemetry.py +306 -0
- mcp_use/telemetry/utils.py +48 -0
- mcp_use/utils.py +27 -0
- {mcp_use-1.3.0.dist-info → mcp_use-1.3.2.dist-info}/METADATA +82 -26
- mcp_use-1.3.2.dist-info/RECORD +49 -0
- mcp_use/types/clientoptions.py +0 -23
- mcp_use-1.3.0.dist-info/RECORD +0 -41
- {mcp_use-1.3.0.dist-info → mcp_use-1.3.2.dist-info}/WHEEL +0 -0
- {mcp_use-1.3.0.dist-info → mcp_use-1.3.2.dist-info}/licenses/LICENSE +0 -0
mcp_use/__init__.py
CHANGED
|
@@ -7,6 +7,7 @@ to MCP tools through existing LangChain adapters.
|
|
|
7
7
|
|
|
8
8
|
from importlib.metadata import version
|
|
9
9
|
|
|
10
|
+
from . import observability
|
|
10
11
|
from .agents.mcpagent import MCPAgent
|
|
11
12
|
from .client import MCPClient
|
|
12
13
|
from .config import load_config_file
|
|
@@ -30,6 +31,7 @@ __all__ = [
|
|
|
30
31
|
"MCP_USE_DEBUG",
|
|
31
32
|
"Logger",
|
|
32
33
|
"set_debug",
|
|
34
|
+
"observability",
|
|
33
35
|
]
|
|
34
36
|
|
|
35
37
|
|
mcp_use/adapters/base.py
CHANGED
|
@@ -34,9 +34,7 @@ class BaseAdapter(ABC):
|
|
|
34
34
|
self._connector_tool_map: dict[BaseConnector, list[T]] = {}
|
|
35
35
|
|
|
36
36
|
@classmethod
|
|
37
|
-
async def create_tools(
|
|
38
|
-
cls, client: "MCPClient", disallowed_tools: list[str] | None = None
|
|
39
|
-
) -> list[T]:
|
|
37
|
+
async def create_tools(cls, client: "MCPClient", disallowed_tools: list[str] | None = None) -> list[T]:
|
|
40
38
|
"""Create tools from an MCPClient instance.
|
|
41
39
|
|
|
42
40
|
This is the recommended way to create tools from an MCPClient, as it handles
|
|
@@ -86,9 +84,7 @@ class BaseAdapter(ABC):
|
|
|
86
84
|
"""
|
|
87
85
|
# Check if we already have tools for this connector
|
|
88
86
|
if connector in self._connector_tool_map:
|
|
89
|
-
logger.debug(
|
|
90
|
-
f"Returning {len(self._connector_tool_map[connector])} existing tools for connector"
|
|
91
|
-
)
|
|
87
|
+
logger.debug(f"Returning {len(self._connector_tool_map[connector])} existing tools for connector")
|
|
92
88
|
return self._connector_tool_map[connector]
|
|
93
89
|
|
|
94
90
|
# Create tools for this connector
|
|
@@ -89,9 +89,7 @@ class LangChainAdapter(BaseAdapter):
|
|
|
89
89
|
elif hasattr(resource, "blob"):
|
|
90
90
|
# Assuming blob needs decoding or specific handling; adjust as needed
|
|
91
91
|
decoded_result += (
|
|
92
|
-
resource.blob.decode()
|
|
93
|
-
if isinstance(resource.blob, bytes)
|
|
94
|
-
else str(resource.blob)
|
|
92
|
+
resource.blob.decode() if isinstance(resource.blob, bytes) else str(resource.blob)
|
|
95
93
|
)
|
|
96
94
|
else:
|
|
97
95
|
raise ToolException(f"Unexpected resource type: {resource.type}")
|
|
@@ -154,9 +152,7 @@ class LangChainAdapter(BaseAdapter):
|
|
|
154
152
|
logger.debug(f'MCP tool: "{self.name}" received input: {kwargs}')
|
|
155
153
|
|
|
156
154
|
try:
|
|
157
|
-
tool_result: CallToolResult = await self.tool_connector.call_tool(
|
|
158
|
-
self.name, kwargs
|
|
159
|
-
)
|
|
155
|
+
tool_result: CallToolResult = await self.tool_connector.call_tool(self.name, kwargs)
|
|
160
156
|
try:
|
|
161
157
|
# Use the helper function to parse the result
|
|
162
158
|
return adapter_self._parse_mcp_tool_result(tool_result)
|
|
@@ -185,8 +181,7 @@ class LangChainAdapter(BaseAdapter):
|
|
|
185
181
|
class ResourceTool(BaseTool):
|
|
186
182
|
name: str = _sanitize(mcp_resource.name or f"resource_{mcp_resource.uri}")
|
|
187
183
|
description: str = (
|
|
188
|
-
mcp_resource.description
|
|
189
|
-
or f"Return the content of the resource located at URI {mcp_resource.uri}."
|
|
184
|
+
mcp_resource.description or f"Return the content of the resource located at URI {mcp_resource.uri}."
|
|
190
185
|
)
|
|
191
186
|
args_schema: type[BaseModel] = ReadResourceRequestParams
|
|
192
187
|
tool_connector: BaseConnector = connector
|
|
@@ -243,9 +238,7 @@ class LangChainAdapter(BaseAdapter):
|
|
|
243
238
|
Field(None, description=arg.description),
|
|
244
239
|
)
|
|
245
240
|
|
|
246
|
-
InputSchema = create_model(
|
|
247
|
-
dynamic_model_name, **field_definitions_for_create, __base__=BaseModel
|
|
248
|
-
)
|
|
241
|
+
InputSchema = create_model(dynamic_model_name, **field_definitions_for_create, __base__=BaseModel)
|
|
249
242
|
else:
|
|
250
243
|
# Create an empty Pydantic model if there are no arguments
|
|
251
244
|
InputSchema = create_model(dynamic_model_name, __base__=BaseModel)
|
mcp_use/agents/base.py
CHANGED
|
@@ -48,9 +48,7 @@ class BaseAgent(ABC):
|
|
|
48
48
|
pass
|
|
49
49
|
|
|
50
50
|
@abstractmethod
|
|
51
|
-
async def step(
|
|
52
|
-
self, query: str, previous_steps: list[dict[str, Any]] | None = None
|
|
53
|
-
) -> dict[str, Any]:
|
|
51
|
+
async def step(self, query: str, previous_steps: list[dict[str, Any]] | None = None) -> dict[str, Any]:
|
|
54
52
|
"""Perform a single step of the agent.
|
|
55
53
|
|
|
56
54
|
Args:
|
mcp_use/agents/mcpagent.py
CHANGED
|
@@ -6,6 +6,7 @@ to provide a simple interface for using MCP tools with different LLMs.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
|
+
import time
|
|
9
10
|
from collections.abc import AsyncIterator
|
|
10
11
|
|
|
11
12
|
from langchain.agents import AgentExecutor, create_tool_calling_agent
|
|
@@ -22,6 +23,8 @@ from langchain_core.utils.input import get_color_mapping
|
|
|
22
23
|
|
|
23
24
|
from mcp_use.client import MCPClient
|
|
24
25
|
from mcp_use.connectors.base import BaseConnector
|
|
26
|
+
from mcp_use.telemetry.telemetry import Telemetry
|
|
27
|
+
from mcp_use.telemetry.utils import extract_model_info
|
|
25
28
|
|
|
26
29
|
from ..adapters.langchain_adapter import LangChainAdapter
|
|
27
30
|
from ..logging import logger
|
|
@@ -93,6 +96,9 @@ class MCPAgent:
|
|
|
93
96
|
# Create the adapter for tool conversion
|
|
94
97
|
self.adapter = LangChainAdapter(disallowed_tools=self.disallowed_tools)
|
|
95
98
|
|
|
99
|
+
# Initialize telemetry
|
|
100
|
+
self.telemetry = Telemetry()
|
|
101
|
+
|
|
96
102
|
# Initialize server manager if requested
|
|
97
103
|
self.server_manager = None
|
|
98
104
|
if self.use_server_manager:
|
|
@@ -104,6 +110,9 @@ class MCPAgent:
|
|
|
104
110
|
self._agent_executor: AgentExecutor | None = None
|
|
105
111
|
self._system_message: SystemMessage | None = None
|
|
106
112
|
|
|
113
|
+
# Track model info for telemetry
|
|
114
|
+
self._model_provider, self._model_name = extract_model_info(self.llm)
|
|
115
|
+
|
|
107
116
|
async def initialize(self) -> None:
|
|
108
117
|
"""Initialize the MCP client and agent."""
|
|
109
118
|
logger.info("🚀 Initializing MCP agent and connecting to services...")
|
|
@@ -113,9 +122,7 @@ class MCPAgent:
|
|
|
113
122
|
# Get server management tools
|
|
114
123
|
management_tools = self.server_manager.tools
|
|
115
124
|
self._tools = management_tools
|
|
116
|
-
logger.info(
|
|
117
|
-
f"🔧 Server manager mode active with {len(management_tools)} management tools"
|
|
118
|
-
)
|
|
125
|
+
logger.info(f"🔧 Server manager mode active with {len(management_tools)} management tools")
|
|
119
126
|
|
|
120
127
|
# Create the system message based on available tools
|
|
121
128
|
await self._create_system_message_from_tools(self._tools)
|
|
@@ -130,6 +137,7 @@ class MCPAgent:
|
|
|
130
137
|
if not self._sessions:
|
|
131
138
|
logger.info("🔄 No active sessions found, creating new ones...")
|
|
132
139
|
self._sessions = await self.client.create_all_sessions()
|
|
140
|
+
self.connectors = [session.connector for session in self._sessions.values()]
|
|
133
141
|
logger.info(f"✅ Created {len(self._sessions)} new sessions")
|
|
134
142
|
|
|
135
143
|
# Create LangChain tools directly from the client using the adapter
|
|
@@ -141,7 +149,7 @@ class MCPAgent:
|
|
|
141
149
|
connectors_to_use = self.connectors
|
|
142
150
|
logger.info(f"🔗 Connecting to {len(connectors_to_use)} direct connectors...")
|
|
143
151
|
for connector in connectors_to_use:
|
|
144
|
-
if not hasattr(connector, "
|
|
152
|
+
if not hasattr(connector, "client_session") or connector.client_session is None:
|
|
145
153
|
await connector.connect()
|
|
146
154
|
|
|
147
155
|
# Create LangChain tools using the adapter with connectors
|
|
@@ -180,9 +188,7 @@ class MCPAgent:
|
|
|
180
188
|
|
|
181
189
|
# Update conversation history if memory is enabled
|
|
182
190
|
if self.memory_enabled:
|
|
183
|
-
history_without_system = [
|
|
184
|
-
msg for msg in self._conversation_history if not isinstance(msg, SystemMessage)
|
|
185
|
-
]
|
|
191
|
+
history_without_system = [msg for msg in self._conversation_history if not isinstance(msg, SystemMessage)]
|
|
186
192
|
self._conversation_history = [self._system_message] + history_without_system
|
|
187
193
|
|
|
188
194
|
def _create_agent(self) -> AgentExecutor:
|
|
@@ -213,9 +219,7 @@ class MCPAgent:
|
|
|
213
219
|
agent = create_tool_calling_agent(llm=self.llm, tools=self._tools, prompt=prompt)
|
|
214
220
|
|
|
215
221
|
# Use the standard AgentExecutor
|
|
216
|
-
executor = AgentExecutor(
|
|
217
|
-
agent=agent, tools=self._tools, max_iterations=self.max_steps, verbose=self.verbose
|
|
218
|
-
)
|
|
222
|
+
executor = AgentExecutor(agent=agent, tools=self._tools, max_iterations=self.max_steps, verbose=self.verbose)
|
|
219
223
|
logger.debug(f"Created agent executor with max_iterations={self.max_steps}")
|
|
220
224
|
return executor
|
|
221
225
|
|
|
@@ -263,9 +267,7 @@ class MCPAgent:
|
|
|
263
267
|
# Update conversation history if memory is enabled
|
|
264
268
|
if self.memory_enabled:
|
|
265
269
|
# Remove old system message if it exists
|
|
266
|
-
history_without_system = [
|
|
267
|
-
msg for msg in self._conversation_history if not isinstance(msg, SystemMessage)
|
|
268
|
-
]
|
|
270
|
+
history_without_system = [msg for msg in self._conversation_history if not isinstance(msg, SystemMessage)]
|
|
269
271
|
self._conversation_history = history_without_system
|
|
270
272
|
|
|
271
273
|
# Add new system message
|
|
@@ -290,9 +292,7 @@ class MCPAgent:
|
|
|
290
292
|
# If the agent is already initialized, we need to reinitialize it
|
|
291
293
|
# to apply the changes to the available tools
|
|
292
294
|
if self._initialized:
|
|
293
|
-
logger.debug(
|
|
294
|
-
"Agent already initialized. Changes will take effect on next initialization."
|
|
295
|
-
)
|
|
295
|
+
logger.debug("Agent already initialized. Changes will take effect on next initialization.")
|
|
296
296
|
# We don't automatically reinitialize here as it could be disruptive
|
|
297
297
|
# to ongoing operations. The user can call initialize() explicitly if needed.
|
|
298
298
|
|
|
@@ -325,9 +325,7 @@ class MCPAgent:
|
|
|
325
325
|
|
|
326
326
|
# 1. Initialise on-demand ------------------------------------------------
|
|
327
327
|
initialised_here = False
|
|
328
|
-
if (manage_connector and not self._initialized) or (
|
|
329
|
-
not self._initialized and self.auto_initialize
|
|
330
|
-
):
|
|
328
|
+
if (manage_connector and not self._initialized) or (not self._initialized and self.auto_initialize):
|
|
331
329
|
await self.initialize()
|
|
332
330
|
initialised_here = True
|
|
333
331
|
|
|
@@ -341,9 +339,7 @@ class MCPAgent:
|
|
|
341
339
|
if self.memory_enabled:
|
|
342
340
|
self.add_to_history(HumanMessage(content=query))
|
|
343
341
|
|
|
344
|
-
history_to_use =
|
|
345
|
-
external_history if external_history is not None else self._conversation_history
|
|
346
|
-
)
|
|
342
|
+
history_to_use = external_history if external_history is not None else self._conversation_history
|
|
347
343
|
inputs = {"input": query, "chat_history": history_to_use}
|
|
348
344
|
|
|
349
345
|
# 3. Stream & diff -------------------------------------------------------
|
|
@@ -355,9 +351,12 @@ class MCPAgent:
|
|
|
355
351
|
if not isinstance(message, ToolAgentAction):
|
|
356
352
|
self.add_to_history(message)
|
|
357
353
|
yield event
|
|
358
|
-
|
|
359
354
|
# 5. House-keeping -------------------------------------------------------
|
|
360
|
-
|
|
355
|
+
# Restrict agent cleanup in _generate_response_chunks_async to only occur
|
|
356
|
+
# when the agent was initialized in this generator and is not client-managed
|
|
357
|
+
# and the user does want us to manage the connection.
|
|
358
|
+
if not self.client and initialised_here and manage_connector:
|
|
359
|
+
logger.info("🧹 Closing agent after generator completion")
|
|
361
360
|
await self.close()
|
|
362
361
|
|
|
363
362
|
async def astream(
|
|
@@ -374,13 +373,56 @@ class MCPAgent:
|
|
|
374
373
|
async for chunk in agent.astream("hello"):
|
|
375
374
|
print(chunk, end="|", flush=True)
|
|
376
375
|
"""
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
376
|
+
start_time = time.time()
|
|
377
|
+
success = False
|
|
378
|
+
chunk_count = 0
|
|
379
|
+
total_response_length = 0
|
|
380
|
+
|
|
381
|
+
try:
|
|
382
|
+
async for chunk in self._generate_response_chunks_async(
|
|
383
|
+
query=query,
|
|
384
|
+
max_steps=max_steps,
|
|
385
|
+
manage_connector=manage_connector,
|
|
386
|
+
external_history=external_history,
|
|
387
|
+
):
|
|
388
|
+
chunk_count += 1
|
|
389
|
+
if isinstance(chunk, str):
|
|
390
|
+
total_response_length += len(chunk)
|
|
391
|
+
yield chunk
|
|
392
|
+
success = True
|
|
393
|
+
finally:
|
|
394
|
+
# Track comprehensive execution data for streaming
|
|
395
|
+
execution_time_ms = int((time.time() - start_time) * 1000)
|
|
396
|
+
|
|
397
|
+
server_count = 0
|
|
398
|
+
if self.client:
|
|
399
|
+
server_count = len(self.client.get_all_active_sessions())
|
|
400
|
+
elif self.connectors:
|
|
401
|
+
server_count = len(self.connectors)
|
|
402
|
+
|
|
403
|
+
conversation_history_length = len(self._conversation_history) if self.memory_enabled else 0
|
|
404
|
+
|
|
405
|
+
self.telemetry.track_agent_execution(
|
|
406
|
+
execution_method="astream",
|
|
407
|
+
query=query,
|
|
408
|
+
success=success,
|
|
409
|
+
model_provider=self._model_provider,
|
|
410
|
+
model_name=self._model_name,
|
|
411
|
+
server_count=server_count,
|
|
412
|
+
server_identifiers=[connector.public_identifier for connector in self.connectors],
|
|
413
|
+
total_tools_available=len(self._tools) if self._tools else 0,
|
|
414
|
+
tools_available_names=[tool.name for tool in self._tools],
|
|
415
|
+
max_steps_configured=self.max_steps,
|
|
416
|
+
memory_enabled=self.memory_enabled,
|
|
417
|
+
use_server_manager=self.use_server_manager,
|
|
418
|
+
max_steps_used=max_steps,
|
|
419
|
+
manage_connector=manage_connector,
|
|
420
|
+
external_history_used=external_history is not None,
|
|
421
|
+
response=f"[STREAMED RESPONSE - {total_response_length} chars]",
|
|
422
|
+
execution_time_ms=execution_time_ms,
|
|
423
|
+
error_type=None if success else "streaming_error",
|
|
424
|
+
conversation_history_length=conversation_history_length,
|
|
425
|
+
)
|
|
384
426
|
|
|
385
427
|
async def run(
|
|
386
428
|
self,
|
|
@@ -409,6 +451,10 @@ class MCPAgent:
|
|
|
409
451
|
"""
|
|
410
452
|
result = ""
|
|
411
453
|
initialized_here = False
|
|
454
|
+
start_time = time.time()
|
|
455
|
+
tools_used_names = []
|
|
456
|
+
steps_taken = 0
|
|
457
|
+
success = False
|
|
412
458
|
|
|
413
459
|
try:
|
|
414
460
|
# Initialize if needed
|
|
@@ -427,11 +473,7 @@ class MCPAgent:
|
|
|
427
473
|
if self._agent_executor:
|
|
428
474
|
self._agent_executor.max_iterations = steps
|
|
429
475
|
|
|
430
|
-
display_query = (
|
|
431
|
-
query[:50].replace("\n", " ") + "..."
|
|
432
|
-
if len(query) > 50
|
|
433
|
-
else query.replace("\n", " ")
|
|
434
|
-
)
|
|
476
|
+
display_query = query[:50].replace("\n", " ") + "..." if len(query) > 50 else query.replace("\n", " ")
|
|
435
477
|
logger.info(f"💬 Received query: '{display_query}'")
|
|
436
478
|
|
|
437
479
|
# Add the user query to conversation history if memory is enabled
|
|
@@ -439,9 +481,7 @@ class MCPAgent:
|
|
|
439
481
|
self.add_to_history(HumanMessage(content=query))
|
|
440
482
|
|
|
441
483
|
# Use the provided history or the internal history
|
|
442
|
-
history_to_use =
|
|
443
|
-
external_history if external_history is not None else self._conversation_history
|
|
444
|
-
)
|
|
484
|
+
history_to_use = external_history if external_history is not None else self._conversation_history
|
|
445
485
|
|
|
446
486
|
# Convert messages to format expected by LangChain agent input
|
|
447
487
|
# Exclude the main system message as it's part of the agent's prompt
|
|
@@ -457,13 +497,12 @@ class MCPAgent:
|
|
|
457
497
|
|
|
458
498
|
# Construct a mapping of tool name to tool for easy lookup
|
|
459
499
|
name_to_tool_map = {tool.name: tool for tool in self._tools}
|
|
460
|
-
color_mapping = get_color_mapping(
|
|
461
|
-
[tool.name for tool in self._tools], excluded_colors=["green", "red"]
|
|
462
|
-
)
|
|
500
|
+
color_mapping = get_color_mapping([tool.name for tool in self._tools], excluded_colors=["green", "red"])
|
|
463
501
|
|
|
464
502
|
logger.info(f"🏁 Starting agent execution with max_steps={steps}")
|
|
465
503
|
|
|
466
504
|
for step_num in range(steps):
|
|
505
|
+
steps_taken = step_num + 1
|
|
467
506
|
# --- Check for tool updates if using server manager ---
|
|
468
507
|
if self.use_server_manager and self.server_manager:
|
|
469
508
|
current_tools = self.server_manager.tools
|
|
@@ -472,7 +511,7 @@ class MCPAgent:
|
|
|
472
511
|
|
|
473
512
|
if current_tool_names != existing_tool_names:
|
|
474
513
|
logger.info(
|
|
475
|
-
f"🔄 Tools changed before step {step_num + 1}, updating agent.
|
|
514
|
+
f"🔄 Tools changed before step {step_num + 1}, updating agent."
|
|
476
515
|
f"New tools: {', '.join(current_tool_names)}"
|
|
477
516
|
)
|
|
478
517
|
self._tools = current_tools
|
|
@@ -510,9 +549,10 @@ class MCPAgent:
|
|
|
510
549
|
# If it's actions/steps, add to intermediate steps
|
|
511
550
|
intermediate_steps.extend(next_step_output)
|
|
512
551
|
|
|
513
|
-
# Log tool calls
|
|
552
|
+
# Log tool calls and track tool usage
|
|
514
553
|
for action, output in next_step_output:
|
|
515
554
|
tool_name = action.tool
|
|
555
|
+
tools_used_names.append(tool_name)
|
|
516
556
|
tool_input_str = str(action.tool_input)
|
|
517
557
|
# Truncate long inputs for readability
|
|
518
558
|
if len(tool_input_str) > 100:
|
|
@@ -555,7 +595,8 @@ class MCPAgent:
|
|
|
555
595
|
if self.memory_enabled:
|
|
556
596
|
self.add_to_history(AIMessage(content=result))
|
|
557
597
|
|
|
558
|
-
logger.info("🎉 Agent execution complete")
|
|
598
|
+
logger.info(f"🎉 Agent execution complete in {time.time() - start_time} seconds")
|
|
599
|
+
success = True
|
|
559
600
|
return result
|
|
560
601
|
|
|
561
602
|
except Exception as e:
|
|
@@ -566,6 +607,41 @@ class MCPAgent:
|
|
|
566
607
|
raise
|
|
567
608
|
|
|
568
609
|
finally:
|
|
610
|
+
# Track comprehensive execution data
|
|
611
|
+
execution_time_ms = int((time.time() - start_time) * 1000)
|
|
612
|
+
|
|
613
|
+
server_count = 0
|
|
614
|
+
if self.client:
|
|
615
|
+
server_count = len(self.client.get_all_active_sessions())
|
|
616
|
+
elif self.connectors:
|
|
617
|
+
server_count = len(self.connectors)
|
|
618
|
+
|
|
619
|
+
conversation_history_length = len(self._conversation_history) if self.memory_enabled else 0
|
|
620
|
+
self.telemetry.track_agent_execution(
|
|
621
|
+
execution_method="run",
|
|
622
|
+
query=query,
|
|
623
|
+
success=success,
|
|
624
|
+
model_provider=self._model_provider,
|
|
625
|
+
model_name=self._model_name,
|
|
626
|
+
server_count=server_count,
|
|
627
|
+
server_identifiers=[connector.public_identifier for connector in self.connectors],
|
|
628
|
+
total_tools_available=len(self._tools) if self._tools else 0,
|
|
629
|
+
tools_available_names=[tool.name for tool in self._tools],
|
|
630
|
+
max_steps_configured=self.max_steps,
|
|
631
|
+
memory_enabled=self.memory_enabled,
|
|
632
|
+
use_server_manager=self.use_server_manager,
|
|
633
|
+
max_steps_used=max_steps,
|
|
634
|
+
manage_connector=manage_connector,
|
|
635
|
+
external_history_used=external_history is not None,
|
|
636
|
+
steps_taken=steps_taken,
|
|
637
|
+
tools_used_count=len(tools_used_names),
|
|
638
|
+
tools_used_names=tools_used_names,
|
|
639
|
+
response=result,
|
|
640
|
+
execution_time_ms=execution_time_ms,
|
|
641
|
+
error_type=None if success else "execution_error",
|
|
642
|
+
conversation_history_length=conversation_history_length,
|
|
643
|
+
)
|
|
644
|
+
|
|
569
645
|
# Clean up if necessary (e.g., if not using client-managed sessions)
|
|
570
646
|
if manage_connector and not self.client and not initialized_here:
|
|
571
647
|
logger.info("🧹 Closing agent after query completion")
|
|
@@ -2,9 +2,7 @@ from langchain.schema import SystemMessage
|
|
|
2
2
|
from langchain_core.tools import BaseTool
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
def generate_tool_descriptions(
|
|
6
|
-
tools: list[BaseTool], disallowed_tools: list[str] | None = None
|
|
7
|
-
) -> list[str]:
|
|
5
|
+
def generate_tool_descriptions(tools: list[BaseTool], disallowed_tools: list[str] | None = None) -> list[str]:
|
|
8
6
|
"""
|
|
9
7
|
Generates a list of formatted tool descriptions, excluding disallowed tools.
|
|
10
8
|
|
mcp_use/client.py
CHANGED
|
@@ -9,10 +9,11 @@ import json
|
|
|
9
9
|
import warnings
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
+
from mcp_use.types.sandbox import SandboxOptions
|
|
13
|
+
|
|
12
14
|
from .config import create_connector_from_config, load_config_file
|
|
13
15
|
from .logging import logger
|
|
14
16
|
from .session import MCPSession
|
|
15
|
-
from .types.clientoptions import ClientOptions
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
class MCPClient:
|
|
@@ -25,17 +26,20 @@ class MCPClient:
|
|
|
25
26
|
def __init__(
|
|
26
27
|
self,
|
|
27
28
|
config: str | dict[str, Any] | None = None,
|
|
28
|
-
|
|
29
|
+
sandbox: bool = False,
|
|
30
|
+
sandbox_options: SandboxOptions | None = None,
|
|
29
31
|
) -> None:
|
|
30
32
|
"""Initialize a new MCP client.
|
|
31
33
|
|
|
32
34
|
Args:
|
|
33
35
|
config: Either a dict containing configuration or a path to a JSON config file.
|
|
34
36
|
If None, an empty configuration is used.
|
|
35
|
-
|
|
37
|
+
sandbox: Whether to use sandboxed execution mode for running MCP servers.
|
|
38
|
+
sandbox_options: Optional sandbox configuration options.
|
|
36
39
|
"""
|
|
37
40
|
self.config: dict[str, Any] = {}
|
|
38
|
-
self.
|
|
41
|
+
self.sandbox = sandbox
|
|
42
|
+
self.sandbox_options = sandbox_options
|
|
39
43
|
self.sessions: dict[str, MCPSession] = {}
|
|
40
44
|
self.active_sessions: list[str] = []
|
|
41
45
|
|
|
@@ -47,24 +51,33 @@ class MCPClient:
|
|
|
47
51
|
self.config = config
|
|
48
52
|
|
|
49
53
|
@classmethod
|
|
50
|
-
def from_dict(
|
|
54
|
+
def from_dict(
|
|
55
|
+
cls,
|
|
56
|
+
config: dict[str, Any],
|
|
57
|
+
sandbox: bool = False,
|
|
58
|
+
sandbox_options: SandboxOptions | None = None,
|
|
59
|
+
) -> "MCPClient":
|
|
51
60
|
"""Create a MCPClient from a dictionary.
|
|
52
61
|
|
|
53
62
|
Args:
|
|
54
63
|
config: The configuration dictionary.
|
|
55
|
-
|
|
64
|
+
sandbox: Whether to use sandboxed execution mode for running MCP servers.
|
|
65
|
+
sandbox_options: Optional sandbox configuration options.
|
|
56
66
|
"""
|
|
57
|
-
return cls(config=config,
|
|
67
|
+
return cls(config=config, sandbox=sandbox, sandbox_options=sandbox_options)
|
|
58
68
|
|
|
59
69
|
@classmethod
|
|
60
|
-
def from_config_file(
|
|
70
|
+
def from_config_file(
|
|
71
|
+
cls, filepath: str, sandbox: bool = False, sandbox_options: SandboxOptions | None = None
|
|
72
|
+
) -> "MCPClient":
|
|
61
73
|
"""Create a MCPClient from a configuration file.
|
|
62
74
|
|
|
63
75
|
Args:
|
|
64
76
|
filepath: The path to the configuration file.
|
|
65
|
-
|
|
77
|
+
sandbox: Whether to use sandboxed execution mode for running MCP servers.
|
|
78
|
+
sandbox_options: Optional sandbox configuration options.
|
|
66
79
|
"""
|
|
67
|
-
return cls(config=load_config_file(filepath),
|
|
80
|
+
return cls(config=load_config_file(filepath), sandbox=sandbox, sandbox_options=sandbox_options)
|
|
68
81
|
|
|
69
82
|
def add_server(
|
|
70
83
|
self,
|
|
@@ -137,7 +150,9 @@ class MCPClient:
|
|
|
137
150
|
server_config = servers[server_name]
|
|
138
151
|
|
|
139
152
|
# Create connector with options
|
|
140
|
-
connector = create_connector_from_config(
|
|
153
|
+
connector = create_connector_from_config(
|
|
154
|
+
server_config, sandbox=self.sandbox, sandbox_options=self.sandbox_options
|
|
155
|
+
)
|
|
141
156
|
|
|
142
157
|
# Create the session
|
|
143
158
|
session = MCPSession(connector)
|
mcp_use/config.py
CHANGED
|
@@ -7,6 +7,8 @@ This module provides functionality to load MCP configuration from JSON files.
|
|
|
7
7
|
import json
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
+
from mcp_use.types.sandbox import SandboxOptions
|
|
11
|
+
|
|
10
12
|
from .connectors import (
|
|
11
13
|
BaseConnector,
|
|
12
14
|
HttpConnector,
|
|
@@ -15,7 +17,6 @@ from .connectors import (
|
|
|
15
17
|
WebSocketConnector,
|
|
16
18
|
)
|
|
17
19
|
from .connectors.utils import is_stdio_server
|
|
18
|
-
from .types.clientoptions import ClientOptions
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def load_config_file(filepath: str) -> dict[str, Any]:
|
|
@@ -33,24 +34,23 @@ def load_config_file(filepath: str) -> dict[str, Any]:
|
|
|
33
34
|
|
|
34
35
|
def create_connector_from_config(
|
|
35
36
|
server_config: dict[str, Any],
|
|
36
|
-
|
|
37
|
+
sandbox: bool = False,
|
|
38
|
+
sandbox_options: SandboxOptions | None = None,
|
|
37
39
|
) -> BaseConnector:
|
|
38
40
|
"""Create a connector based on server configuration.
|
|
39
41
|
This function can be called with just the server_config parameter:
|
|
40
42
|
create_connector_from_config(server_config)
|
|
41
43
|
Args:
|
|
42
44
|
server_config: The server configuration section
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
sandbox: Whether to use sandboxed execution mode for running MCP servers.
|
|
46
|
+
sandbox_options: Optional sandbox configuration options.
|
|
45
47
|
|
|
46
48
|
Returns:
|
|
47
49
|
A configured connector instance
|
|
48
50
|
"""
|
|
49
|
-
# Use default options if none provided
|
|
50
|
-
options = options or {"is_sandboxed": False}
|
|
51
51
|
|
|
52
52
|
# Stdio connector (command-based)
|
|
53
|
-
if is_stdio_server(server_config) and not
|
|
53
|
+
if is_stdio_server(server_config) and not sandbox:
|
|
54
54
|
return StdioConnector(
|
|
55
55
|
command=server_config["command"],
|
|
56
56
|
args=server_config["args"],
|
|
@@ -58,12 +58,12 @@ def create_connector_from_config(
|
|
|
58
58
|
)
|
|
59
59
|
|
|
60
60
|
# Sandboxed connector
|
|
61
|
-
elif is_stdio_server(server_config) and
|
|
61
|
+
elif is_stdio_server(server_config) and sandbox:
|
|
62
62
|
return SandboxConnector(
|
|
63
63
|
command=server_config["command"],
|
|
64
64
|
args=server_config["args"],
|
|
65
65
|
env=server_config.get("env", None),
|
|
66
|
-
e2b_options=
|
|
66
|
+
e2b_options=sandbox_options,
|
|
67
67
|
)
|
|
68
68
|
|
|
69
69
|
# HTTP connector
|