mcp-use 1.3.11__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 -264
  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 +88 -47
  21. mcp_use/agents/remote.py +168 -129
  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 -320
  54. mcp_use/config.py +20 -92
  55. mcp_use/connectors/.deprecated +0 -0
  56. mcp_use/connectors/__init__.py +46 -20
  57. mcp_use/connectors/base.py +12 -447
  58. mcp_use/connectors/http.py +13 -288
  59. mcp_use/connectors/sandbox.py +13 -297
  60. mcp_use/connectors/stdio.py +13 -96
  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 -0
  77. mcp_use/middleware/logging.py +19 -0
  78. mcp_use/middleware/metrics.py +41 -0
  79. mcp_use/middleware/middleware.py +55 -0
  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.11.dist-info → mcp_use-1.3.13.dist-info}/METADATA +66 -40
  93. mcp_use-1.3.13.dist-info/RECORD +109 -0
  94. mcp_use-1.3.11.dist-info/RECORD +0 -60
  95. mcp_use-1.3.11.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.11.dist-info → mcp_use-1.3.13.dist-info}/WHEEL +0 -0
  101. {mcp_use-1.3.11.dist-info → mcp_use-1.3.13.dist-info}/entry_points.txt +0 -0
@@ -23,21 +23,20 @@ from langchain_core.tools import BaseTool
23
23
  from langchain_core.utils.input import get_color_mapping
24
24
  from pydantic import BaseModel
25
25
 
26
- from mcp_use.client import MCPClient
27
- from mcp_use.connectors.base import BaseConnector
28
- from mcp_use.telemetry.telemetry import Telemetry
29
- from mcp_use.telemetry.utils import extract_model_info
30
-
31
- from ..adapters.langchain_adapter import LangChainAdapter
32
- from ..logging import logger
33
- from ..managers.base import BaseServerManager
34
- from ..managers.server_manager import ServerManager
26
+ from mcp_use.agents.adapters.langchain_adapter import LangChainAdapter
27
+ from mcp_use.agents.managers.base import BaseServerManager
28
+ from mcp_use.agents.managers.server_manager import ServerManager
35
29
 
36
30
  # Import observability manager
37
- from ..observability import ObservabilityManager
38
- from .prompts.system_prompt_builder import create_system_message
39
- from .prompts.templates import DEFAULT_SYSTEM_PROMPT_TEMPLATE, SERVER_MANAGER_SYSTEM_PROMPT_TEMPLATE
40
- from .remote import RemoteAgent
31
+ from mcp_use.agents.observability import ObservabilityManager
32
+ from mcp_use.agents.prompts.system_prompt_builder import create_system_message
33
+ from mcp_use.agents.prompts.templates import DEFAULT_SYSTEM_PROMPT_TEMPLATE, SERVER_MANAGER_SYSTEM_PROMPT_TEMPLATE
34
+ from mcp_use.agents.remote import RemoteAgent
35
+ from mcp_use.client import MCPClient
36
+ from mcp_use.client.connectors.base import BaseConnector
37
+ from mcp_use.logging import logger
38
+ from mcp_use.telemetry.telemetry import Telemetry, telemetry
39
+ from mcp_use.telemetry.utils import extract_model_info
41
40
 
42
41
  set_debug(logger.level == logging.DEBUG)
43
42
 
@@ -213,6 +212,42 @@ class MCPAgent:
213
212
  self._initialized = True
214
213
  logger.info("✨ Agent initialization complete")
215
214
 
215
+ def _normalize_output(self, value: object) -> str:
216
+ """Normalize model outputs into a plain text string."""
217
+ try:
218
+ if isinstance(value, str):
219
+ return value
220
+
221
+ # LangChain messages may have .content which is str or list-like
222
+ content = getattr(value, "content", None)
223
+ if content is not None:
224
+ return self._normalize_output(content)
225
+
226
+ if isinstance(value, list):
227
+ parts: list[str] = []
228
+ for item in value:
229
+ if isinstance(item, dict):
230
+ if "text" in item and isinstance(item["text"], str):
231
+ parts.append(item["text"])
232
+ elif "content" in item:
233
+ parts.append(self._normalize_output(item["content"]))
234
+ else:
235
+ # Fallback to str for unknown shapes
236
+ parts.append(str(item))
237
+ else:
238
+ # recurse on .content or str
239
+ part_content = getattr(item, "text", None)
240
+ if isinstance(part_content, str):
241
+ parts.append(part_content)
242
+ else:
243
+ parts.append(self._normalize_output(getattr(item, "content", item)))
244
+ return "".join(parts)
245
+
246
+ return str(value)
247
+
248
+ except Exception:
249
+ return str(value)
250
+
216
251
  async def _create_system_message_from_tools(self, tools: list[BaseTool]) -> None:
217
252
  """Create the system message based on provided tools using the builder."""
218
253
  # Use the override if provided, otherwise use the imported default
@@ -232,9 +267,12 @@ class MCPAgent:
232
267
  )
233
268
 
234
269
  # Update conversation history if memory is enabled
270
+ # Note: The system message should not be included in the conversation history,
271
+ # as it will be automatically added using the create_tool_calling_agent function with the prompt parameter
235
272
  if self.memory_enabled:
236
- history_without_system = [msg for msg in self._conversation_history if not isinstance(msg, SystemMessage)]
237
- self._conversation_history = [self._system_message] + history_without_system
273
+ self._conversation_history = [
274
+ msg for msg in self._conversation_history if not isinstance(msg, SystemMessage)
275
+ ]
238
276
 
239
277
  def _create_agent(self) -> AgentExecutor:
240
278
  """Create the LangChain agent with the configured system message.
@@ -248,14 +286,25 @@ class MCPAgent:
248
286
  if self._system_message:
249
287
  system_content = self._system_message.content
250
288
 
251
- prompt = ChatPromptTemplate.from_messages(
252
- [
253
- ("system", system_content),
254
- MessagesPlaceholder(variable_name="chat_history"),
255
- ("human", "{input}"),
256
- MessagesPlaceholder(variable_name="agent_scratchpad"),
257
- ]
258
- )
289
+ if self.memory_enabled:
290
+ # Query already in chat_history — don't re-inject it
291
+ prompt = ChatPromptTemplate.from_messages(
292
+ [
293
+ ("system", system_content),
294
+ MessagesPlaceholder(variable_name="chat_history"),
295
+ ("human", "{input}"),
296
+ MessagesPlaceholder(variable_name="agent_scratchpad"),
297
+ ]
298
+ )
299
+ else:
300
+ # No memory — inject input directly
301
+ prompt = ChatPromptTemplate.from_messages(
302
+ [
303
+ ("system", system_content),
304
+ ("human", "{input}"),
305
+ MessagesPlaceholder(variable_name="agent_scratchpad"),
306
+ ]
307
+ )
259
308
 
260
309
  tool_names = [tool.name for tool in self._tools]
261
310
  logger.info(f"🧠 Agent ready with tools: {', '.join(tool_names)}")
@@ -286,10 +335,6 @@ class MCPAgent:
286
335
  """Clear the conversation history."""
287
336
  self._conversation_history = []
288
337
 
289
- # Re-add the system message if it exists
290
- if self._system_message and self.memory_enabled:
291
- self._conversation_history = [self._system_message]
292
-
293
338
  def add_to_history(self, message: BaseMessage) -> None:
294
339
  """Add a message to the conversation history.
295
340
 
@@ -315,15 +360,6 @@ class MCPAgent:
315
360
  """
316
361
  self._system_message = SystemMessage(content=message)
317
362
 
318
- # Update conversation history if memory is enabled
319
- if self.memory_enabled:
320
- # Remove old system message if it exists
321
- history_without_system = [msg for msg in self._conversation_history if not isinstance(msg, SystemMessage)]
322
- self._conversation_history = history_without_system
323
-
324
- # Add new system message
325
- self._conversation_history.insert(0, self._system_message)
326
-
327
363
  # Recreate the agent with the new system message if initialized
328
364
  if self._initialized and self._tools:
329
365
  self._agent_executor = self._create_agent()
@@ -388,6 +424,7 @@ class MCPAgent:
388
424
  steps_taken += 1
389
425
  return final_result, steps_taken
390
426
 
427
+ @telemetry("agent_stream")
391
428
  async def stream(
392
429
  self,
393
430
  query: str,
@@ -467,10 +504,6 @@ class MCPAgent:
467
504
  display_query = query[:50].replace("\n", " ") + "..." if len(query) > 50 else query.replace("\n", " ")
468
505
  logger.info(f"💬 Received query: '{display_query}'")
469
506
 
470
- # Add the user query to conversation history if memory is enabled
471
- if self.memory_enabled:
472
- self.add_to_history(HumanMessage(content=query))
473
-
474
507
  # Use the provided history or the internal history
475
508
  history_to_use = external_history if external_history is not None else self._conversation_history
476
509
 
@@ -583,7 +616,8 @@ class MCPAgent:
583
616
  if isinstance(next_step_output, AgentFinish):
584
617
  logger.info(f"✅ Agent finished at step {step_num + 1}")
585
618
  agent_finished_successfully = True
586
- result = next_step_output.return_values.get("output", "No output generated")
619
+ output_value = next_step_output.return_values.get("output", "No output generated")
620
+ result = self._normalize_output(output_value)
587
621
  # End the chain if we have a run manager
588
622
  if run_manager:
589
623
  await run_manager.on_chain_end({"output": result})
@@ -666,6 +700,7 @@ class MCPAgent:
666
700
  logger.info(f"🏆 Tool returned directly at step {step_num + 1}")
667
701
  agent_finished_successfully = True
668
702
  result = tool_return.return_values.get("output", "No output generated")
703
+ result = self._normalize_output(result)
669
704
  break
670
705
 
671
706
  except OutputParserException as e:
@@ -719,8 +754,11 @@ class MCPAgent:
719
754
  logger.error(f"❌ Final structured output attempt failed: {e}")
720
755
  raise RuntimeError(f"Failed to generate structured output after {steps} steps: {str(e)}") from e
721
756
 
757
+ if self.memory_enabled:
758
+ self.add_to_history(HumanMessage(content=query))
759
+
722
760
  if self.memory_enabled and not output_schema:
723
- self.add_to_history(AIMessage(content=result))
761
+ self.add_to_history(AIMessage(content=self._normalize_output(result)))
724
762
 
725
763
  logger.info(f"🎉 Agent execution complete in {time.time() - start_time} seconds")
726
764
  if not success:
@@ -783,6 +821,7 @@ class MCPAgent:
783
821
  logger.info("🧹 Closing agent after stream completion")
784
822
  await self.close()
785
823
 
824
+ @telemetry("agent_run")
786
825
  async def run(
787
826
  self,
788
827
  query: str,
@@ -873,7 +912,7 @@ class MCPAgent:
873
912
  steps_taken=steps_taken,
874
913
  tools_used_count=len(self.tools_used_names),
875
914
  tools_used_names=self.tools_used_names,
876
- response=str(result),
915
+ response=str(self._normalize_output(result)),
877
916
  execution_time_ms=int((time.time() - start_time) * 1000),
878
917
  error_type=error,
879
918
  conversation_history_length=len(self._conversation_history),
@@ -976,9 +1015,6 @@ class MCPAgent:
976
1015
  effective_max_steps = max_steps or self.max_steps
977
1016
  self._agent_executor.max_iterations = effective_max_steps
978
1017
 
979
- if self.memory_enabled:
980
- self.add_to_history(HumanMessage(content=query))
981
-
982
1018
  history_to_use = external_history if external_history is not None else self._conversation_history
983
1019
  inputs = {"input": query, "chat_history": history_to_use}
984
1020
 
@@ -991,6 +1027,10 @@ class MCPAgent:
991
1027
  if not isinstance(message, ToolAgentAction):
992
1028
  self.add_to_history(message)
993
1029
  yield event
1030
+
1031
+ if self.memory_enabled:
1032
+ self.add_to_history(HumanMessage(content=query))
1033
+
994
1034
  # 5. House-keeping -------------------------------------------------------
995
1035
  # Restrict agent cleanup in _generate_response_chunks_async to only occur
996
1036
  # when the agent was initialized in this generator and is not client-managed
@@ -999,6 +1039,7 @@ class MCPAgent:
999
1039
  logger.info("🧹 Closing agent after generator completion")
1000
1040
  await self.close()
1001
1041
 
1042
+ @telemetry("agent_stream_events")
1002
1043
  async def stream_events(
1003
1044
  self,
1004
1045
  query: str,
@@ -1010,7 +1051,7 @@ class MCPAgent:
1010
1051
 
1011
1052
  Example::
1012
1053
 
1013
- async for chunk in agent.astream("hello"):
1054
+ async for chunk in agent.stream("hello"):
1014
1055
  print(chunk, end="|", flush=True)
1015
1056
  """
1016
1057
  start_time = time.time()
mcp_use/agents/remote.py CHANGED
@@ -4,6 +4,7 @@ Remote agent implementation for executing agents via API.
4
4
 
5
5
  import json
6
6
  import os
7
+ from collections.abc import AsyncGenerator
7
8
  from typing import Any, TypeVar
8
9
  from uuid import UUID
9
10
 
@@ -11,13 +12,13 @@ import httpx
11
12
  from langchain.schema import BaseMessage
12
13
  from pydantic import BaseModel
13
14
 
14
- from ..logging import logger
15
+ from mcp_use.logging import logger
15
16
 
16
17
  T = TypeVar("T", bound=BaseModel)
17
18
 
18
19
  # API endpoint constants
19
20
  API_CHATS_ENDPOINT = "/api/v1/chats/get-or-create"
20
- API_CHAT_EXECUTE_ENDPOINT = "/api/v1/chats/{chat_id}/execute"
21
+ API_CHAT_STREAM_ENDPOINT = "/api/v1/chats/{chat_id}/stream"
21
22
  API_CHAT_DELETE_ENDPOINT = "/api/v1/chats/{chat_id}"
22
23
 
23
24
  UUID_ERROR_MESSAGE = """A UUID is a 36 character string of the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \n
@@ -129,12 +130,25 @@ class RemoteAgent:
129
130
 
130
131
  # Parse into the Pydantic model
131
132
  try:
133
+ logger.info(f"🔍 Attempting to validate result_data against {output_schema.__name__}")
134
+ logger.info(f"🔍 Result data type: {type(result_data)}")
135
+ logger.info(f"🔍 Result data: {result_data}")
132
136
  return output_schema.model_validate(result_data)
133
137
  except Exception as e:
134
- logger.warning(f"Failed to parse structured output: {e}")
138
+ logger.warning(f"Failed to parse structured output: {e}")
139
+ logger.warning(f"🔍 Validation error details: {type(e).__name__}: {str(e)}")
140
+ logger.warning(f"🔍 Result data that failed validation: {result_data}")
141
+
135
142
  # Fallback: try to parse it as raw content if the model has a content field
136
143
  if hasattr(output_schema, "model_fields") and "content" in output_schema.model_fields:
137
- return output_schema.model_validate({"content": str(result_data)})
144
+ logger.info("🔄 Attempting fallback with content field")
145
+ try:
146
+ fallback_result = output_schema.model_validate({"content": str(result_data)})
147
+ logger.info("✅ Fallback parsing succeeded")
148
+ return fallback_result
149
+ except Exception as fallback_e:
150
+ logger.error(f"❌ Fallback parsing also failed: {fallback_e}")
151
+ raise
138
152
  raise
139
153
 
140
154
  async def _upsert_chat_session(self) -> str:
@@ -153,7 +167,7 @@ class RemoteAgent:
153
167
  headers = {"Content-Type": "application/json", "x-api-key": self.api_key}
154
168
  chat_url = f"{self.base_url}{API_CHATS_ENDPOINT}"
155
169
 
156
- logger.info(f"📝 Upserting chat session for agent {self.agent_id}")
170
+ logger.info(f"📝 [{self.chat_id}] Upserting chat session for agent {self.agent_id}")
157
171
 
158
172
  try:
159
173
  chat_response = await self._client.post(chat_url, json=chat_payload, headers=headers)
@@ -162,9 +176,9 @@ class RemoteAgent:
162
176
  chat_data = chat_response.json()
163
177
  chat_id = chat_data["id"]
164
178
  if chat_response.status_code == 201:
165
- logger.info(f"✅ New chat session created: {chat_id}")
179
+ logger.info(f"✅ [{self.chat_id}] New chat session created")
166
180
  else:
167
- logger.info(f"✅ Resumed chat session: {chat_id}")
181
+ logger.info(f"✅ [{self.chat_id}] Resumed chat session")
168
182
 
169
183
  return chat_id
170
184
 
@@ -182,144 +196,169 @@ class RemoteAgent:
182
196
  except Exception as e:
183
197
  raise RuntimeError(f"Failed to create chat session: {str(e)}") from e
184
198
 
185
- async def run(
199
+ async def stream(
186
200
  self,
187
201
  query: str,
188
202
  max_steps: int | None = None,
189
203
  external_history: list[BaseMessage] | None = None,
190
204
  output_schema: type[T] | None = None,
191
- ) -> str | T:
192
- """Run a query on the remote agent.
193
-
194
- Args:
195
- query: The query to execute
196
- max_steps: Maximum number of steps (default: 10)
197
- external_history: External history (not supported yet for remote execution)
198
- output_schema: Optional Pydantic model for structured output
199
-
200
- Returns:
201
- The result from the remote agent execution (string or structured output)
202
- """
205
+ ) -> AsyncGenerator[str, None]:
206
+ """Stream the execution of a query on the remote agent using HTTP streaming."""
203
207
  if external_history is not None:
204
208
  logger.warning("External history is not yet supported for remote execution")
205
209
 
206
- try:
207
- logger.info(f"🌐 Executing query on remote agent {self.agent_id}")
208
-
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
215
-
216
- chat_id = self.chat_id
217
-
218
- # Step 2: Execute the agent within the chat context
219
- execution_payload = {"query": query, "max_steps": max_steps or 10}
220
-
221
- # Add structured output schema if provided
222
- if output_schema is not None:
223
- execution_payload["output_schema"] = self._pydantic_to_json_schema(output_schema)
224
- logger.info(f"🔧 Using structured output with schema: {output_schema.__name__}")
225
-
226
- headers = {"Content-Type": "application/json", "x-api-key": self.api_key}
227
- execution_url = f"{self.base_url}{API_CHAT_EXECUTE_ENDPOINT.format(chat_id=chat_id)}"
228
- logger.info(f"🚀 Executing agent in chat {chat_id}")
229
-
230
- response = await self._client.post(execution_url, json=execution_payload, headers=headers)
231
- response.raise_for_status()
232
-
233
- result = response.json()
234
- logger.info(f"🔧 Response: {result}")
235
- logger.info("✅ Remote execution completed successfully")
236
-
237
- # Check for error responses (even with 200 status)
238
- if isinstance(result, dict):
239
- # Check for actual error conditions (not just presence of error field)
240
- if result.get("status") == "error" or (result.get("error") is not None):
241
- error_msg = result.get("error", str(result))
242
- logger.error(f"❌ Remote agent execution failed: {error_msg}")
243
- raise RuntimeError(f"Remote agent execution failed: {error_msg}")
244
-
245
- # Check if the response indicates agent initialization failure
246
- if "failed to initialize" in str(result):
247
- logger.error(f"❌ Agent initialization failed: {result}")
248
- raise RuntimeError(
249
- f"Agent initialization failed on remote server. "
250
- f"This usually indicates:\n"
251
- f"• Invalid agent configuration (LLM model, system prompt)\n"
252
- f"• Missing or invalid MCP server configurations\n"
253
- f"• Network connectivity issues with MCP servers\n"
254
- f"• Missing environment variables or credentials\n"
255
- f"Raw error: {result}"
256
- )
257
-
258
- # Handle structured output
259
- if output_schema is not None:
260
- return self._parse_structured_response(result, output_schema)
210
+ if not self._session_established:
211
+ logger.info(f"🔧 [{self.chat_id}] Establishing chat session for agent {self.agent_id}")
212
+ self.chat_id = await self._upsert_chat_session()
213
+ self._session_established = True
261
214
 
262
- # Regular string output
263
- if isinstance(result, dict) and "result" in result:
264
- return result["result"]
265
- elif isinstance(result, str):
266
- return result
267
- else:
268
- return str(result)
215
+ chat_id = self.chat_id
216
+ stream_url = f"{self.base_url}{API_CHAT_STREAM_ENDPOINT.format(chat_id=chat_id)}"
217
+
218
+ # Prepare the request payload
219
+ request_payload = {"messages": [{"role": "user", "content": query}], "max_steps": max_steps or 30}
220
+ if output_schema is not None:
221
+ request_payload["output_schema"] = self._pydantic_to_json_schema(output_schema)
222
+
223
+ headers = {"Content-Type": "application/json", "x-api-key": self.api_key, "Accept": "text/event-stream"}
224
+
225
+ try:
226
+ logger.info(f"🌐 [{self.chat_id}] Connecting to HTTP stream for agent {self.agent_id}")
227
+
228
+ async with self._client.stream("POST", stream_url, headers=headers, json=request_payload) as response:
229
+ logger.info(f"✅ [{self.chat_id}] HTTP stream connection established")
230
+
231
+ if response.status_code != 200:
232
+ error_text = await response.aread()
233
+ raise RuntimeError(f"Failed to stream from remote agent: {error_text.decode()}")
234
+
235
+ # Read the streaming response line by line
236
+ try:
237
+ async for line in response.aiter_lines():
238
+ if line:
239
+ yield line
240
+ except UnicodeDecodeError as e:
241
+ logger.error(f"❌ [{self.chat_id}] UTF-8 decoding error at position {e.start}: {e.reason}")
242
+ logger.error(f"❌ [{self.chat_id}] Error occurred while reading stream for agent {self.agent_id}")
243
+ # Try to read raw bytes and decode with error handling
244
+ logger.info(f"🔄 [{self.chat_id}] Attempting to read raw bytes with error handling...")
245
+ logger.info(f"✅ [{self.chat_id}] Agent execution stream completed")
269
246
 
270
247
  except httpx.HTTPStatusError as e:
271
248
  status_code = e.response.status_code
272
249
  response_text = e.response.text
273
250
 
274
- # Provide specific error messages based on status code
275
- if status_code == 401:
276
- logger.error(f"❌ Authentication failed: {response_text}")
277
- raise RuntimeError(
278
- "Authentication failed: Invalid or missing API key. "
279
- "Please check your API key and ensure the MCP_USE_API_KEY environment variable is set correctly."
280
- ) from e
281
- elif status_code == 403:
282
- logger.error(f"❌ Access forbidden: {response_text}")
283
- raise RuntimeError(
284
- f"Access denied: You don't have permission to execute agent '{self.agent_id}'. "
285
- "Check if the agent exists and you have the necessary permissions."
286
- ) from e
287
- elif status_code == 404:
288
- logger.error(f"❌ Agent not found: {response_text}")
289
- raise RuntimeError(
290
- f"Agent not found: Agent '{self.agent_id}' does not exist or you don't have access to it. "
291
- "Please verify the agent ID and ensure it exists in your account."
292
- ) from e
293
- elif status_code == 422:
294
- logger.error(f"❌ Validation error: {response_text}")
295
- raise RuntimeError(
296
- f"Request validation failed: {response_text}. "
297
- "Please check your query parameters and output schema format."
298
- ) from e
299
- elif status_code == 500:
300
- logger.error(f"❌ Server error: {response_text}")
301
- raise RuntimeError(
302
- "Internal server error occurred during agent execution. "
303
- "Please try again later or contact support if the issue persists."
304
- ) from e
251
+ if status_code == 404:
252
+ raise RuntimeError(f"Chat or agent not found: {response_text}") from e
305
253
  else:
306
- logger.error(f" Remote execution failed with status {status_code}: {response_text}")
307
- raise RuntimeError(f"Remote agent execution failed: {status_code} - {response_text}") from e
308
- except httpx.TimeoutException as e:
309
- logger.error(f" Remote execution timed out: {e}")
310
- raise RuntimeError(
311
- "Remote agent execution timed out. The server may be overloaded or the query is taking too long to "
312
- "process. Try again or use a simpler query."
313
- ) from e
314
- except httpx.ConnectError as e:
315
- logger.error(f"❌ Remote execution connection error: {e}")
316
- raise RuntimeError(
317
- f"Remote agent connection failed: Cannot connect to {self.base_url}. "
318
- f"Check if the server is running and the URL is correct."
319
- ) from e
254
+ raise RuntimeError(f"Failed to stream from remote agent: {status_code} - {response_text}") from e
255
+ except Exception as e:
256
+ logger.error(f"❌ [{self.chat_id}] An error occurred during HTTP streaming: {e}")
257
+ raise RuntimeError(f"Failed to stream from remote agent: {str(e)}") from e
258
+
259
+ async def run(
260
+ self,
261
+ query: str,
262
+ max_steps: int | None = None,
263
+ external_history: list[BaseMessage] | None = None,
264
+ output_schema: type[T] | None = None,
265
+ ) -> str | T:
266
+ """
267
+ Executes the agent and returns the final result.
268
+ This method uses HTTP streaming to avoid timeouts for long-running tasks.
269
+ It consumes the entire stream and returns only the final result.
270
+ """
271
+ final_result = None
272
+ steps_taken = 0
273
+ finished = False
274
+
275
+ try:
276
+ # Consume the ENTIRE stream to ensure proper execution
277
+ async for event in self.stream(query, max_steps, external_history, output_schema):
278
+ logger.debug(f"[{self.chat_id}] Processing stream event: {event}...")
279
+
280
+ # Parse AI SDK format events to extract final result
281
+ # The events follow the AI SDK streaming protocol
282
+ if event.startswith("0:"): # Text event
283
+ try:
284
+ text_data = json.loads(event[2:]) # Remove "0:" prefix
285
+ # Normal text accumulation
286
+ if final_result is None:
287
+ final_result = ""
288
+ final_result += text_data
289
+ result_preview = final_result[:200] if len(final_result) > 200 else final_result
290
+ logger.debug(f"Accumulated text result: {result_preview}...")
291
+ except json.JSONDecodeError:
292
+ logger.warning(f"Failed to parse text event: {event[:100]}")
293
+ continue
294
+
295
+ elif event.startswith("9:"): # Tool call event
296
+ steps_taken += 1
297
+ logger.debug(f"Tool call executed, total steps: {steps_taken}")
298
+
299
+ elif event.startswith("d:"): # Finish event
300
+ logger.debug("Received finish event, marking as finished")
301
+ finished = True
302
+ # Continue consuming to ensure stream cleanup
303
+
304
+ elif event.startswith("3:"): # Error event
305
+ try:
306
+ error_data = json.loads(event[2:])
307
+ error_msg = error_data if isinstance(error_data, str) else json.dumps(error_data)
308
+ raise RuntimeError(f"Agent execution failed: {error_msg}")
309
+ except json.JSONDecodeError as e:
310
+ raise RuntimeError("Agent execution failed with unknown error") from e
311
+
312
+ elif event.startswith("f:"): # Structured final event
313
+ try:
314
+ structured_data = json.loads(event[2:]) # Remove "f:" prefix
315
+ logger.info(f"📋 [{self.chat_id}] Received structured final event")
316
+
317
+ # Replace accumulated text with structured output
318
+ final_result = structured_data
319
+ logger.info(f"📋 [{self.chat_id}] Replaced accumulated text with structured output")
320
+ except json.JSONDecodeError:
321
+ logger.warning(f"Failed to parse structured final event: {event[:100]}")
322
+ continue
323
+
324
+ # Log completion of stream consumption
325
+ logger.info(f"Stream consumption complete. Finished: {finished}, Steps taken: {steps_taken}")
326
+
327
+ if final_result is None:
328
+ logger.warning(f"No final result captured from stream (structured output: {output_schema is not None})")
329
+ final_result = "" # Return empty string instead of error message
330
+
331
+ # For structured output, try to parse the result
332
+ if output_schema:
333
+ logger.info(f"🔍 Attempting structured output parsing for schema: {output_schema.__name__}")
334
+ logger.info(f"🔍 Raw final result type: {type(final_result)}")
335
+ logger.info(f"🔍 Raw final result length: {len(str(final_result)) if final_result else 0}")
336
+ logger.info(f"🔍 Raw final result preview: {str(final_result)[:500] if final_result else 'None'}...")
337
+
338
+ if isinstance(final_result, str) and final_result:
339
+ try:
340
+ # Try to parse as JSON first
341
+ parsed_result = json.loads(final_result)
342
+ logger.info("✅ Successfully parsed structured result as JSON")
343
+ return self._parse_structured_response(parsed_result, output_schema)
344
+ except json.JSONDecodeError as e:
345
+ logger.warning(f"❌ Could not parse result as JSON: {e}")
346
+ logger.warning(f"🔍 Raw string content: {final_result[:1000]}...")
347
+ # Try to parse directly
348
+ return self._parse_structured_response({"content": final_result}, output_schema)
349
+ else:
350
+ logger.warning(f"❌ Final result is empty or not string: {final_result}")
351
+ # Try to parse the result directly
352
+ return self._parse_structured_response(final_result, output_schema)
353
+
354
+ # Regular string output
355
+ return final_result if isinstance(final_result, str) else str(final_result)
356
+
357
+ except RuntimeError:
358
+ raise
320
359
  except Exception as e:
321
- logger.error(f" Remote execution error: {e}")
322
- raise RuntimeError(f"Remote agent execution failed: {str(e)}") from e
360
+ logger.error(f"Error executing agent: {e}")
361
+ raise RuntimeError(f"Failed to execute agent: {str(e)}") from e
323
362
 
324
363
  async def close(self) -> None:
325
364
  """Close the HTTP client."""
File without changes
mcp_use/auth/__init__.py CHANGED
@@ -1,6 +1,21 @@
1
- """Authentication support for MCP clients."""
1
+ # mcp_use/auth/__init__.py
2
+ import warnings
2
3
 
3
- from .bearer import BearerAuth
4
- from .oauth import OAuth
4
+ from typing_extensions import deprecated
5
5
 
6
- __all__ = ["BearerAuth", "OAuth"]
6
+ from mcp_use.client.auth import BearerAuth as _BearerAuth
7
+ from mcp_use.client.auth import OAuth as _OAuth
8
+
9
+ warnings.warn(
10
+ "mcp_use.auth is deprecated. Use mcp_use.client.auth. This import will be removed in version 1.4.0",
11
+ DeprecationWarning,
12
+ stacklevel=2,
13
+ )
14
+
15
+
16
+ @deprecated("Use mcp_use.client.auth.BearerAuth")
17
+ class BearerAuth(_BearerAuth): ...
18
+
19
+
20
+ @deprecated("Use mcp_use.client.auth.OAuth")
21
+ class OAuth(_OAuth): ...