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.
- mcp_use/__init__.py +1 -1
- mcp_use/adapters/.deprecated +0 -0
- mcp_use/adapters/__init__.py +18 -7
- mcp_use/adapters/base.py +12 -185
- mcp_use/adapters/langchain_adapter.py +12 -264
- mcp_use/agents/adapters/__init__.py +10 -0
- mcp_use/agents/adapters/base.py +193 -0
- mcp_use/agents/adapters/langchain_adapter.py +228 -0
- mcp_use/agents/base.py +1 -1
- mcp_use/agents/managers/__init__.py +19 -0
- mcp_use/agents/managers/base.py +36 -0
- mcp_use/agents/managers/server_manager.py +131 -0
- mcp_use/agents/managers/tools/__init__.py +15 -0
- mcp_use/agents/managers/tools/base_tool.py +19 -0
- mcp_use/agents/managers/tools/connect_server.py +69 -0
- mcp_use/agents/managers/tools/disconnect_server.py +43 -0
- mcp_use/agents/managers/tools/get_active_server.py +29 -0
- mcp_use/agents/managers/tools/list_servers_tool.py +53 -0
- mcp_use/agents/managers/tools/search_tools.py +328 -0
- mcp_use/agents/mcpagent.py +88 -47
- mcp_use/agents/remote.py +168 -129
- mcp_use/auth/.deprecated +0 -0
- mcp_use/auth/__init__.py +19 -4
- mcp_use/auth/bearer.py +11 -12
- mcp_use/auth/oauth.py +11 -620
- mcp_use/auth/oauth_callback.py +16 -207
- mcp_use/client/__init__.py +1 -0
- mcp_use/client/auth/__init__.py +6 -0
- mcp_use/client/auth/bearer.py +23 -0
- mcp_use/client/auth/oauth.py +629 -0
- mcp_use/client/auth/oauth_callback.py +214 -0
- mcp_use/client/client.py +356 -0
- mcp_use/client/config.py +106 -0
- mcp_use/client/connectors/__init__.py +20 -0
- mcp_use/client/connectors/base.py +470 -0
- mcp_use/client/connectors/http.py +304 -0
- mcp_use/client/connectors/sandbox.py +332 -0
- mcp_use/client/connectors/stdio.py +109 -0
- mcp_use/client/connectors/utils.py +13 -0
- mcp_use/client/connectors/websocket.py +257 -0
- mcp_use/client/exceptions.py +31 -0
- mcp_use/client/middleware/__init__.py +50 -0
- mcp_use/client/middleware/logging.py +31 -0
- mcp_use/client/middleware/metrics.py +314 -0
- mcp_use/client/middleware/middleware.py +266 -0
- mcp_use/client/session.py +162 -0
- mcp_use/client/task_managers/__init__.py +20 -0
- mcp_use/client/task_managers/base.py +145 -0
- mcp_use/client/task_managers/sse.py +84 -0
- mcp_use/client/task_managers/stdio.py +69 -0
- mcp_use/client/task_managers/streamable_http.py +86 -0
- mcp_use/client/task_managers/websocket.py +68 -0
- mcp_use/client.py +12 -320
- mcp_use/config.py +20 -92
- mcp_use/connectors/.deprecated +0 -0
- mcp_use/connectors/__init__.py +46 -20
- mcp_use/connectors/base.py +12 -447
- mcp_use/connectors/http.py +13 -288
- mcp_use/connectors/sandbox.py +13 -297
- mcp_use/connectors/stdio.py +13 -96
- mcp_use/connectors/utils.py +15 -8
- mcp_use/connectors/websocket.py +13 -252
- mcp_use/exceptions.py +33 -18
- mcp_use/managers/.deprecated +0 -0
- mcp_use/managers/__init__.py +56 -17
- mcp_use/managers/base.py +13 -31
- mcp_use/managers/server_manager.py +13 -119
- mcp_use/managers/tools/__init__.py +45 -15
- mcp_use/managers/tools/base_tool.py +5 -16
- mcp_use/managers/tools/connect_server.py +5 -67
- mcp_use/managers/tools/disconnect_server.py +5 -41
- mcp_use/managers/tools/get_active_server.py +5 -26
- mcp_use/managers/tools/list_servers_tool.py +5 -51
- mcp_use/managers/tools/search_tools.py +17 -321
- mcp_use/middleware/.deprecated +0 -0
- mcp_use/middleware/__init__.py +89 -0
- mcp_use/middleware/logging.py +19 -0
- mcp_use/middleware/metrics.py +41 -0
- mcp_use/middleware/middleware.py +55 -0
- mcp_use/session.py +13 -149
- mcp_use/task_managers/.deprecated +0 -0
- mcp_use/task_managers/__init__.py +48 -20
- mcp_use/task_managers/base.py +13 -140
- mcp_use/task_managers/sse.py +13 -79
- mcp_use/task_managers/stdio.py +13 -64
- mcp_use/task_managers/streamable_http.py +15 -81
- mcp_use/task_managers/websocket.py +13 -63
- mcp_use/telemetry/events.py +58 -0
- mcp_use/telemetry/telemetry.py +71 -1
- mcp_use/types/.deprecated +0 -0
- mcp_use/types/sandbox.py +13 -18
- {mcp_use-1.3.11.dist-info → mcp_use-1.3.13.dist-info}/METADATA +66 -40
- mcp_use-1.3.13.dist-info/RECORD +109 -0
- mcp_use-1.3.11.dist-info/RECORD +0 -60
- mcp_use-1.3.11.dist-info/licenses/LICENSE +0 -21
- /mcp_use/{observability → agents/observability}/__init__.py +0 -0
- /mcp_use/{observability → agents/observability}/callbacks_manager.py +0 -0
- /mcp_use/{observability → agents/observability}/laminar.py +0 -0
- /mcp_use/{observability → agents/observability}/langfuse.py +0 -0
- {mcp_use-1.3.11.dist-info → mcp_use-1.3.13.dist-info}/WHEEL +0 -0
- {mcp_use-1.3.11.dist-info → mcp_use-1.3.13.dist-info}/entry_points.txt +0 -0
mcp_use/agents/mcpagent.py
CHANGED
|
@@ -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.
|
|
27
|
-
from mcp_use.
|
|
28
|
-
from mcp_use.
|
|
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
|
|
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
|
-
|
|
237
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
179
|
+
logger.info(f"✅ [{self.chat_id}] New chat session created")
|
|
166
180
|
else:
|
|
167
|
-
logger.info(f"✅ Resumed chat session
|
|
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
|
|
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
|
|
192
|
-
"""
|
|
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
|
-
|
|
207
|
-
logger.info(f"
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
275
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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"
|
|
322
|
-
raise RuntimeError(f"
|
|
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."""
|
mcp_use/auth/.deprecated
ADDED
|
File without changes
|
mcp_use/auth/__init__.py
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
# mcp_use/auth/__init__.py
|
|
2
|
+
import warnings
|
|
2
3
|
|
|
3
|
-
from
|
|
4
|
-
from .oauth import OAuth
|
|
4
|
+
from typing_extensions import deprecated
|
|
5
5
|
|
|
6
|
-
|
|
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): ...
|