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 CHANGED
@@ -7,12 +7,16 @@ to MCP tools through existing LangChain adapters.
7
7
 
8
8
  from importlib.metadata import version
9
9
 
10
- from . import observability
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")
@@ -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
- # Initialize server manager if requested
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(agent=agent, tools=self._tools, max_iterations=self.max_steps, verbose=self.verbose)
251
- logger.debug(f"Created agent executor with max_iterations={self.max_steps}")
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
- # Use the internal _atake_next_step which handles planning and execution
502
- # This requires providing the necessary context like maps and intermediate steps
503
- next_step_output = await self._agent_executor._atake_next_step(
504
- name_to_tool_map=name_to_tool_map,
505
- color_mapping=color_mapping,
506
- inputs=inputs,
507
- intermediate_steps=intermediate_steps,
508
- run_manager=None,
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
- return await self._remote_agent.run(query, max_steps, manage_connector, external_history, output_schema)
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__(self, agent_id: str, api_key: str | None = None, base_url: str = "https://cloud.mcp-use.com"):
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 _create_chat_session(self, query: str) -> str:
113
- """Create a persistent chat session for the agent.
114
- Args:
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 of the created session
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"📝 Creating chat session for agent {self.agent_id}")
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
- logger.info(f"✅ Chat session created: {chat_id}")
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
- manage_connector: Ignored for remote execution
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: Create a chat session for this agent (only if we don't have one)
182
- if self._chat_id is None:
183
- self._chat_id = await self._create_chat_session(query)
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._chat_id
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}