mcp-use 1.3.9__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 -2
- mcp_use/agents/mcpagent.py +101 -17
- mcp_use/agents/remote.py +50 -19
- mcp_use/cli.py +581 -0
- mcp_use/logging.py +27 -12
- mcp_use/managers/base.py +36 -0
- mcp_use/managers/server_manager.py +2 -1
- 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-1.3.9.dist-info → mcp_use-1.3.10.dist-info}/METADATA +15 -14
- {mcp_use-1.3.9.dist-info → mcp_use-1.3.10.dist-info}/RECORD +16 -12
- mcp_use-1.3.10.dist-info/entry_points.txt +2 -0
- {mcp_use-1.3.9.dist-info → mcp_use-1.3.10.dist-info}/WHEEL +0 -0
- {mcp_use-1.3.9.dist-info → mcp_use-1.3.10.dist-info}/licenses/LICENSE +0 -0
mcp_use/__init__.py
CHANGED
|
@@ -7,12 +7,16 @@ to MCP tools through existing LangChain adapters.
|
|
|
7
7
|
|
|
8
8
|
from importlib.metadata import version
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
# Import logging FIRST to ensure it's configured before other modules
|
|
11
|
+
# This MUST happen before importing observability to ensure loggers are configured
|
|
12
|
+
from .logging import MCP_USE_DEBUG, Logger, logger # isort: skip
|
|
13
|
+
|
|
14
|
+
# Now import other modules - observability must come after logging
|
|
15
|
+
from . import observability # noqa: E402
|
|
11
16
|
from .agents.mcpagent import MCPAgent
|
|
12
17
|
from .client import MCPClient
|
|
13
18
|
from .config import load_config_file
|
|
14
19
|
from .connectors import BaseConnector, HttpConnector, StdioConnector, WebSocketConnector
|
|
15
|
-
from .logging import MCP_USE_DEBUG, Logger, logger
|
|
16
20
|
from .session import MCPSession
|
|
17
21
|
|
|
18
22
|
__version__ = version("mcp-use")
|
mcp_use/agents/mcpagent.py
CHANGED
|
@@ -30,7 +30,11 @@ from mcp_use.telemetry.utils import extract_model_info
|
|
|
30
30
|
|
|
31
31
|
from ..adapters.langchain_adapter import LangChainAdapter
|
|
32
32
|
from ..logging import logger
|
|
33
|
+
from ..managers.base import BaseServerManager
|
|
33
34
|
from ..managers.server_manager import ServerManager
|
|
35
|
+
|
|
36
|
+
# Import observability manager
|
|
37
|
+
from ..observability import ObservabilityManager
|
|
34
38
|
from .prompts.system_prompt_builder import create_system_message
|
|
35
39
|
from .prompts.templates import DEFAULT_SYSTEM_PROMPT_TEMPLATE, SERVER_MANAGER_SYSTEM_PROMPT_TEMPLATE
|
|
36
40
|
from .remote import RemoteAgent
|
|
@@ -62,10 +66,15 @@ class MCPAgent:
|
|
|
62
66
|
disallowed_tools: list[str] | None = None,
|
|
63
67
|
tools_used_names: list[str] | None = None,
|
|
64
68
|
use_server_manager: bool = False,
|
|
69
|
+
server_manager: BaseServerManager | None = None,
|
|
65
70
|
verbose: bool = False,
|
|
66
71
|
agent_id: str | None = None,
|
|
67
72
|
api_key: str | None = None,
|
|
68
73
|
base_url: str = "https://cloud.mcp-use.com",
|
|
74
|
+
callbacks: list | None = None,
|
|
75
|
+
chat_id: str | None = None,
|
|
76
|
+
retry_on_error: bool = True,
|
|
77
|
+
max_retries_per_step: int = 2,
|
|
69
78
|
):
|
|
70
79
|
"""Initialize a new MCPAgent instance.
|
|
71
80
|
|
|
@@ -84,10 +93,13 @@ class MCPAgent:
|
|
|
84
93
|
agent_id: Remote agent ID for remote execution. If provided, creates a remote agent.
|
|
85
94
|
api_key: API key for remote execution. If None, checks MCP_USE_API_KEY env var.
|
|
86
95
|
base_url: Base URL for remote API calls.
|
|
96
|
+
callbacks: List of LangChain callbacks to use. If None and Langfuse is configured, uses langfuse_handler.
|
|
97
|
+
retry_on_error: Whether to retry tool calls that fail due to validation errors.
|
|
98
|
+
max_retries_per_step: Maximum number of retries for validation errors per step.
|
|
87
99
|
"""
|
|
88
100
|
# Handle remote execution
|
|
89
101
|
if agent_id is not None:
|
|
90
|
-
self._remote_agent = RemoteAgent(agent_id=agent_id, api_key=api_key, base_url=base_url)
|
|
102
|
+
self._remote_agent = RemoteAgent(agent_id=agent_id, api_key=api_key, base_url=base_url, chat_id=chat_id)
|
|
91
103
|
self._is_remote = True
|
|
92
104
|
return
|
|
93
105
|
|
|
@@ -109,13 +121,20 @@ class MCPAgent:
|
|
|
109
121
|
self.disallowed_tools = disallowed_tools or []
|
|
110
122
|
self.tools_used_names = tools_used_names or []
|
|
111
123
|
self.use_server_manager = use_server_manager
|
|
124
|
+
self.server_manager = server_manager
|
|
112
125
|
self.verbose = verbose
|
|
126
|
+
self.retry_on_error = retry_on_error
|
|
127
|
+
self.max_retries_per_step = max_retries_per_step
|
|
113
128
|
# System prompt configuration
|
|
114
129
|
self.system_prompt = system_prompt # User-provided full prompt override
|
|
115
130
|
# User can provide a template override, otherwise use the imported default
|
|
116
131
|
self.system_prompt_template_override = system_prompt_template
|
|
117
132
|
self.additional_instructions = additional_instructions
|
|
118
133
|
|
|
134
|
+
# Set up observability callbacks using the ObservabilityManager
|
|
135
|
+
self.observability_manager = ObservabilityManager(custom_callbacks=callbacks)
|
|
136
|
+
self.callbacks = self.observability_manager.get_callbacks()
|
|
137
|
+
|
|
119
138
|
# Either client or connector must be provided
|
|
120
139
|
if not client and len(self.connectors) == 0:
|
|
121
140
|
raise ValueError("Either client or connector must be provided")
|
|
@@ -126,9 +145,7 @@ class MCPAgent:
|
|
|
126
145
|
# Initialize telemetry
|
|
127
146
|
self.telemetry = Telemetry()
|
|
128
147
|
|
|
129
|
-
|
|
130
|
-
self.server_manager = None
|
|
131
|
-
if self.use_server_manager:
|
|
148
|
+
if self.use_server_manager and self.server_manager is None:
|
|
132
149
|
if not self.client:
|
|
133
150
|
raise ValueError("Client must be provided when using server manager")
|
|
134
151
|
self.server_manager = ServerManager(self.client, self.adapter)
|
|
@@ -246,9 +263,15 @@ class MCPAgent:
|
|
|
246
263
|
# Use the standard create_tool_calling_agent
|
|
247
264
|
agent = create_tool_calling_agent(llm=self.llm, tools=self._tools, prompt=prompt)
|
|
248
265
|
|
|
249
|
-
# Use the standard AgentExecutor
|
|
250
|
-
executor = AgentExecutor(
|
|
251
|
-
|
|
266
|
+
# Use the standard AgentExecutor with callbacks
|
|
267
|
+
executor = AgentExecutor(
|
|
268
|
+
agent=agent,
|
|
269
|
+
tools=self._tools,
|
|
270
|
+
max_iterations=self.max_steps,
|
|
271
|
+
verbose=self.verbose,
|
|
272
|
+
callbacks=self.callbacks,
|
|
273
|
+
)
|
|
274
|
+
logger.debug(f"Created agent executor with max_iterations={self.max_steps} and {len(self.callbacks)} callbacks")
|
|
252
275
|
return executor
|
|
253
276
|
|
|
254
277
|
def get_conversation_history(self) -> list[BaseMessage]:
|
|
@@ -469,6 +492,22 @@ class MCPAgent:
|
|
|
469
492
|
|
|
470
493
|
logger.info(f"🏁 Starting agent execution with max_steps={steps}")
|
|
471
494
|
|
|
495
|
+
# Create a run manager with our callbacks if we have any - ONCE for the entire execution
|
|
496
|
+
run_manager = None
|
|
497
|
+
if self.callbacks:
|
|
498
|
+
# Create an async callback manager with our callbacks
|
|
499
|
+
from langchain_core.callbacks.manager import AsyncCallbackManager
|
|
500
|
+
|
|
501
|
+
callback_manager = AsyncCallbackManager.configure(
|
|
502
|
+
inheritable_callbacks=self.callbacks,
|
|
503
|
+
local_callbacks=self.callbacks,
|
|
504
|
+
)
|
|
505
|
+
# Create a run manager for this chain execution
|
|
506
|
+
run_manager = await callback_manager.on_chain_start(
|
|
507
|
+
{"name": "MCPAgent (mcp-use)"},
|
|
508
|
+
inputs,
|
|
509
|
+
)
|
|
510
|
+
|
|
472
511
|
for step_num in range(steps):
|
|
473
512
|
steps_taken = step_num + 1
|
|
474
513
|
# --- Check for tool updates if using server manager ---
|
|
@@ -498,20 +537,51 @@ class MCPAgent:
|
|
|
498
537
|
|
|
499
538
|
# --- Plan and execute the next step ---
|
|
500
539
|
try:
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
540
|
+
retry_count = 0
|
|
541
|
+
next_step_output = None
|
|
542
|
+
|
|
543
|
+
while retry_count <= self.max_retries_per_step:
|
|
544
|
+
try:
|
|
545
|
+
# Use the internal _atake_next_step which handles planning and execution
|
|
546
|
+
# This requires providing the necessary context like maps and intermediate steps
|
|
547
|
+
next_step_output = await self._agent_executor._atake_next_step(
|
|
548
|
+
name_to_tool_map=name_to_tool_map,
|
|
549
|
+
color_mapping=color_mapping,
|
|
550
|
+
inputs=inputs,
|
|
551
|
+
intermediate_steps=intermediate_steps,
|
|
552
|
+
run_manager=run_manager,
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
# If we get here, the step succeeded, break out of retry loop
|
|
556
|
+
break
|
|
557
|
+
|
|
558
|
+
except Exception as e:
|
|
559
|
+
if not self.retry_on_error or retry_count >= self.max_retries_per_step:
|
|
560
|
+
logger.error(f"❌ Validation error during step {step_num + 1}: {e}")
|
|
561
|
+
result = f"Agent stopped due to a validation error: {str(e)}"
|
|
562
|
+
success = False
|
|
563
|
+
yield result
|
|
564
|
+
return
|
|
565
|
+
|
|
566
|
+
retry_count += 1
|
|
567
|
+
logger.warning(
|
|
568
|
+
f"⚠️ Validation error, retrying ({retry_count}/{self.max_retries_per_step}): {e}"
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
# Create concise feedback for the LLM about the validation error
|
|
572
|
+
error_message = f"Error: {str(e)}"
|
|
573
|
+
inputs["input"] = error_message
|
|
574
|
+
|
|
575
|
+
# Continue to next iteration of retry loop
|
|
576
|
+
continue
|
|
510
577
|
|
|
511
578
|
# Process the output
|
|
512
579
|
if isinstance(next_step_output, AgentFinish):
|
|
513
580
|
logger.info(f"✅ Agent finished at step {step_num + 1}")
|
|
514
581
|
result = next_step_output.return_values.get("output", "No output generated")
|
|
582
|
+
# End the chain if we have a run manager
|
|
583
|
+
if run_manager:
|
|
584
|
+
await run_manager.on_chain_end({"output": result})
|
|
515
585
|
|
|
516
586
|
# If structured output is requested, attempt to create it
|
|
517
587
|
if output_schema and structured_llm:
|
|
@@ -563,6 +633,12 @@ class MCPAgent:
|
|
|
563
633
|
for agent_step in next_step_output:
|
|
564
634
|
yield agent_step
|
|
565
635
|
action, observation = agent_step
|
|
636
|
+
reasoning = getattr(action, "log", "")
|
|
637
|
+
if reasoning:
|
|
638
|
+
reasoning_str = reasoning.replace("\n", " ")
|
|
639
|
+
if len(reasoning_str) > 300:
|
|
640
|
+
reasoning_str = reasoning_str[:297] + "..."
|
|
641
|
+
logger.info(f"💭 Reasoning: {reasoning_str}")
|
|
566
642
|
tool_name = action.tool
|
|
567
643
|
self.tools_used_names.append(tool_name)
|
|
568
644
|
tool_input_str = str(action.tool_input)
|
|
@@ -589,12 +665,17 @@ class MCPAgent:
|
|
|
589
665
|
except OutputParserException as e:
|
|
590
666
|
logger.error(f"❌ Output parsing error during step {step_num + 1}: {e}")
|
|
591
667
|
result = f"Agent stopped due to a parsing error: {str(e)}"
|
|
668
|
+
if run_manager:
|
|
669
|
+
await run_manager.on_chain_error(e)
|
|
592
670
|
break
|
|
593
671
|
except Exception as e:
|
|
594
672
|
logger.error(f"❌ Error during agent execution step {step_num + 1}: {e}")
|
|
595
673
|
import traceback
|
|
596
674
|
|
|
597
675
|
traceback.print_exc()
|
|
676
|
+
# End the chain with error if we have a run manager
|
|
677
|
+
if run_manager:
|
|
678
|
+
await run_manager.on_chain_error(e)
|
|
598
679
|
result = f"Agent stopped due to an error: {str(e)}"
|
|
599
680
|
break
|
|
600
681
|
|
|
@@ -602,6 +683,8 @@ class MCPAgent:
|
|
|
602
683
|
if not result:
|
|
603
684
|
logger.warning(f"⚠️ Agent stopped after reaching max iterations ({steps})")
|
|
604
685
|
result = f"Agent stopped after reaching the maximum number of steps ({steps})."
|
|
686
|
+
if run_manager:
|
|
687
|
+
await run_manager.on_chain_end({"output": result})
|
|
605
688
|
|
|
606
689
|
# If structured output was requested but not achieved, attempt one final time
|
|
607
690
|
if output_schema and structured_llm and not success:
|
|
@@ -738,7 +821,8 @@ class MCPAgent:
|
|
|
738
821
|
"""
|
|
739
822
|
# Delegate to remote agent if in remote mode
|
|
740
823
|
if self._is_remote and self._remote_agent:
|
|
741
|
-
|
|
824
|
+
result = await self._remote_agent.run(query, max_steps, external_history, output_schema)
|
|
825
|
+
return result
|
|
742
826
|
|
|
743
827
|
success = True
|
|
744
828
|
start_time = time.time()
|
mcp_use/agents/remote.py
CHANGED
|
@@ -5,6 +5,7 @@ Remote agent implementation for executing agents via API.
|
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
7
|
from typing import Any, TypeVar
|
|
8
|
+
from uuid import UUID
|
|
8
9
|
|
|
9
10
|
import httpx
|
|
10
11
|
from langchain.schema import BaseMessage
|
|
@@ -15,25 +16,52 @@ from ..logging import logger
|
|
|
15
16
|
T = TypeVar("T", bound=BaseModel)
|
|
16
17
|
|
|
17
18
|
# API endpoint constants
|
|
18
|
-
API_CHATS_ENDPOINT = "/api/v1/chats"
|
|
19
|
+
API_CHATS_ENDPOINT = "/api/v1/chats/get-or-create"
|
|
19
20
|
API_CHAT_EXECUTE_ENDPOINT = "/api/v1/chats/{chat_id}/execute"
|
|
20
21
|
API_CHAT_DELETE_ENDPOINT = "/api/v1/chats/{chat_id}"
|
|
21
22
|
|
|
23
|
+
UUID_ERROR_MESSAGE = """A UUID is a 36 character string of the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \n
|
|
24
|
+
Example: 123e4567-e89b-12d3-a456-426614174000
|
|
25
|
+
To generate a UUID, you can use the following command:
|
|
26
|
+
import uuid
|
|
27
|
+
|
|
28
|
+
# Generate a random UUID
|
|
29
|
+
my_uuid = uuid.uuid4()
|
|
30
|
+
print(my_uuid)
|
|
31
|
+
"""
|
|
32
|
+
|
|
22
33
|
|
|
23
34
|
class RemoteAgent:
|
|
24
35
|
"""Agent that executes remotely via API."""
|
|
25
36
|
|
|
26
|
-
def __init__(
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
agent_id: str,
|
|
40
|
+
chat_id: str | None = None,
|
|
41
|
+
api_key: str | None = None,
|
|
42
|
+
base_url: str = "https://cloud.mcp-use.com",
|
|
43
|
+
):
|
|
27
44
|
"""Initialize remote agent.
|
|
28
45
|
|
|
29
46
|
Args:
|
|
30
47
|
agent_id: The ID of the remote agent to execute
|
|
48
|
+
chat_id: The ID of the chat session to use. If None, a new chat session will be created.
|
|
31
49
|
api_key: API key for authentication. If None, will check MCP_USE_API_KEY env var
|
|
32
50
|
base_url: Base URL for the remote API
|
|
33
51
|
"""
|
|
52
|
+
|
|
53
|
+
if chat_id is not None:
|
|
54
|
+
try:
|
|
55
|
+
chat_id = str(UUID(chat_id))
|
|
56
|
+
except ValueError as e:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
f"Invalid chat ID: {chat_id}, make sure to provide a valid UUID.\n{UUID_ERROR_MESSAGE}"
|
|
59
|
+
) from e
|
|
60
|
+
|
|
34
61
|
self.agent_id = agent_id
|
|
62
|
+
self.chat_id = chat_id
|
|
63
|
+
self._session_established = False
|
|
35
64
|
self.base_url = base_url
|
|
36
|
-
self._chat_id = None # Persistent chat session
|
|
37
65
|
|
|
38
66
|
# Handle API key validation
|
|
39
67
|
if api_key is None:
|
|
@@ -109,16 +137,14 @@ class RemoteAgent:
|
|
|
109
137
|
return output_schema.model_validate({"content": str(result_data)})
|
|
110
138
|
raise
|
|
111
139
|
|
|
112
|
-
async def
|
|
113
|
-
"""Create a persistent chat session for the agent.
|
|
114
|
-
|
|
115
|
-
query: The initial query (not used in title anymore)
|
|
140
|
+
async def _upsert_chat_session(self) -> str:
|
|
141
|
+
"""Create or resume a persistent chat session for the agent via upsert.
|
|
142
|
+
|
|
116
143
|
Returns:
|
|
117
|
-
The chat ID
|
|
118
|
-
Raises:
|
|
119
|
-
RuntimeError: If chat creation fails
|
|
144
|
+
The chat session ID
|
|
120
145
|
"""
|
|
121
146
|
chat_payload = {
|
|
147
|
+
"id": self.chat_id, # Include chat_id for resuming or None for creating
|
|
122
148
|
"title": f"Remote Agent Session - {self.agent_id}",
|
|
123
149
|
"agent_id": self.agent_id,
|
|
124
150
|
"type": "agent_execution",
|
|
@@ -127,7 +153,7 @@ class RemoteAgent:
|
|
|
127
153
|
headers = {"Content-Type": "application/json", "x-api-key": self.api_key}
|
|
128
154
|
chat_url = f"{self.base_url}{API_CHATS_ENDPOINT}"
|
|
129
155
|
|
|
130
|
-
logger.info(f"📝
|
|
156
|
+
logger.info(f"📝 Upserting chat session for agent {self.agent_id}")
|
|
131
157
|
|
|
132
158
|
try:
|
|
133
159
|
chat_response = await self._client.post(chat_url, json=chat_payload, headers=headers)
|
|
@@ -135,7 +161,11 @@ class RemoteAgent:
|
|
|
135
161
|
|
|
136
162
|
chat_data = chat_response.json()
|
|
137
163
|
chat_id = chat_data["id"]
|
|
138
|
-
|
|
164
|
+
if chat_response.status_code == 201:
|
|
165
|
+
logger.info(f"✅ New chat session created: {chat_id}")
|
|
166
|
+
else:
|
|
167
|
+
logger.info(f"✅ Resumed chat session: {chat_id}")
|
|
168
|
+
|
|
139
169
|
return chat_id
|
|
140
170
|
|
|
141
171
|
except httpx.HTTPStatusError as e:
|
|
@@ -156,7 +186,6 @@ class RemoteAgent:
|
|
|
156
186
|
self,
|
|
157
187
|
query: str,
|
|
158
188
|
max_steps: int | None = None,
|
|
159
|
-
manage_connector: bool = True,
|
|
160
189
|
external_history: list[BaseMessage] | None = None,
|
|
161
190
|
output_schema: type[T] | None = None,
|
|
162
191
|
) -> str | T:
|
|
@@ -165,8 +194,7 @@ class RemoteAgent:
|
|
|
165
194
|
Args:
|
|
166
195
|
query: The query to execute
|
|
167
196
|
max_steps: Maximum number of steps (default: 10)
|
|
168
|
-
|
|
169
|
-
external_history: Ignored for remote execution (not supported yet)
|
|
197
|
+
external_history: External history (not supported yet for remote execution)
|
|
170
198
|
output_schema: Optional Pydantic model for structured output
|
|
171
199
|
|
|
172
200
|
Returns:
|
|
@@ -178,11 +206,14 @@ class RemoteAgent:
|
|
|
178
206
|
try:
|
|
179
207
|
logger.info(f"🌐 Executing query on remote agent {self.agent_id}")
|
|
180
208
|
|
|
181
|
-
# Step 1:
|
|
182
|
-
|
|
183
|
-
|
|
209
|
+
# Step 1: Ensure chat session exists on the backend by upserting.
|
|
210
|
+
# This happens once per agent instance.
|
|
211
|
+
if not self._session_established:
|
|
212
|
+
logger.info(f"🔧 Establishing chat session for agent {self.agent_id}")
|
|
213
|
+
self.chat_id = await self._upsert_chat_session()
|
|
214
|
+
self._session_established = True
|
|
184
215
|
|
|
185
|
-
chat_id = self.
|
|
216
|
+
chat_id = self.chat_id
|
|
186
217
|
|
|
187
218
|
# Step 2: Execute the agent within the chat context
|
|
188
219
|
execution_payload = {"query": query, "max_steps": max_steps or 10}
|