mcp-use 1.3.7__py3-none-any.whl → 1.3.9__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 +0 -1
- mcp_use/adapters/base.py +3 -3
- mcp_use/adapters/langchain_adapter.py +5 -4
- mcp_use/agents/__init__.py +2 -2
- mcp_use/agents/mcpagent.py +257 -31
- mcp_use/agents/prompts/templates.py +20 -22
- mcp_use/agents/remote.py +296 -0
- mcp_use/client.py +20 -3
- mcp_use/config.py +12 -8
- mcp_use/connectors/base.py +100 -15
- mcp_use/connectors/http.py +13 -2
- mcp_use/connectors/sandbox.py +12 -6
- mcp_use/connectors/stdio.py +11 -2
- mcp_use/errors/__init__.py +1 -0
- mcp_use/errors/error_formatting.py +29 -0
- mcp_use/managers/__init__.py +3 -5
- mcp_use/managers/server_manager.py +46 -13
- mcp_use/managers/tools/__init__.py +2 -4
- mcp_use/managers/tools/connect_server.py +2 -1
- mcp_use/managers/tools/disconnect_server.py +2 -1
- mcp_use/managers/tools/list_servers_tool.py +2 -0
- mcp_use/session.py +70 -0
- mcp_use/telemetry/telemetry.py +1 -4
- {mcp_use-1.3.7.dist-info → mcp_use-1.3.9.dist-info}/METADATA +80 -57
- mcp_use-1.3.9.dist-info/RECORD +51 -0
- mcp_use/managers/tools/use_tool.py +0 -154
- mcp_use-1.3.7.dist-info/RECORD +0 -49
- {mcp_use-1.3.7.dist-info → mcp_use-1.3.9.dist-info}/WHEEL +0 -0
- {mcp_use-1.3.7.dist-info → mcp_use-1.3.9.dist-info}/licenses/LICENSE +0 -0
mcp_use/__init__.py
CHANGED
mcp_use/adapters/base.py
CHANGED
|
@@ -91,14 +91,14 @@ class BaseAdapter(ABC):
|
|
|
91
91
|
|
|
92
92
|
connector_tools = []
|
|
93
93
|
# Now create tools for each MCP tool
|
|
94
|
-
for tool in connector.
|
|
94
|
+
for tool in await connector.list_tools():
|
|
95
95
|
# Convert the tool and add it to the list if conversion was successful
|
|
96
96
|
converted_tool = self._convert_tool(tool, connector)
|
|
97
97
|
if converted_tool:
|
|
98
98
|
connector_tools.append(converted_tool)
|
|
99
99
|
|
|
100
100
|
# Convert resources to tools so that agents can access resource content directly
|
|
101
|
-
resources_list = connector.
|
|
101
|
+
resources_list = await connector.list_resources() or []
|
|
102
102
|
if resources_list:
|
|
103
103
|
for resource in resources_list:
|
|
104
104
|
converted_resource = self._convert_resource(resource, connector)
|
|
@@ -106,7 +106,7 @@ class BaseAdapter(ABC):
|
|
|
106
106
|
connector_tools.append(converted_resource)
|
|
107
107
|
|
|
108
108
|
# Convert prompts to tools so that agents can retrieve prompt content
|
|
109
|
-
prompts_list = connector.
|
|
109
|
+
prompts_list = await connector.list_prompts() or []
|
|
110
110
|
if prompts_list:
|
|
111
111
|
for prompt in prompts_list:
|
|
112
112
|
converted_prompt = self._convert_prompt(prompt, connector)
|
|
@@ -21,6 +21,7 @@ from mcp.types import (
|
|
|
21
21
|
from pydantic import BaseModel, Field, create_model
|
|
22
22
|
|
|
23
23
|
from ..connectors.base import BaseConnector
|
|
24
|
+
from ..errors.error_formatting import format_error
|
|
24
25
|
from ..logging import logger
|
|
25
26
|
from .base import BaseAdapter
|
|
26
27
|
|
|
@@ -159,11 +160,11 @@ class LangChainAdapter(BaseAdapter):
|
|
|
159
160
|
except Exception as e:
|
|
160
161
|
# Log the exception for debugging
|
|
161
162
|
logger.error(f"Error parsing tool result: {e}")
|
|
162
|
-
return
|
|
163
|
+
return format_error(e, tool=self.name, tool_content=tool_result.content)
|
|
163
164
|
|
|
164
165
|
except Exception as e:
|
|
165
166
|
if self.handle_tool_error:
|
|
166
|
-
return
|
|
167
|
+
return format_error(e, tool=self.name) # Format the error to make LLM understand it
|
|
167
168
|
raise
|
|
168
169
|
|
|
169
170
|
return McpToLangChainAdapter()
|
|
@@ -204,7 +205,7 @@ class LangChainAdapter(BaseAdapter):
|
|
|
204
205
|
return content_decoded
|
|
205
206
|
except Exception as e:
|
|
206
207
|
if self.handle_tool_error:
|
|
207
|
-
return
|
|
208
|
+
return format_error(e, tool=self.name) # Format the error to make LLM understand it
|
|
208
209
|
raise
|
|
209
210
|
|
|
210
211
|
return ResourceTool()
|
|
@@ -261,7 +262,7 @@ class LangChainAdapter(BaseAdapter):
|
|
|
261
262
|
return result.messages
|
|
262
263
|
except Exception as e:
|
|
263
264
|
if self.handle_tool_error:
|
|
264
|
-
return
|
|
265
|
+
return format_error(e, tool=self.name) # Format the error to make LLM understand it
|
|
265
266
|
raise
|
|
266
267
|
|
|
267
268
|
return PromptTool()
|
mcp_use/agents/__init__.py
CHANGED
|
@@ -5,10 +5,10 @@ This module provides ready-to-use agent implementations
|
|
|
5
5
|
that are pre-configured for using MCP tools.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from .base import BaseAgent
|
|
9
8
|
from .mcpagent import MCPAgent
|
|
9
|
+
from .remote import RemoteAgent
|
|
10
10
|
|
|
11
11
|
__all__ = [
|
|
12
|
-
"BaseAgent",
|
|
13
12
|
"MCPAgent",
|
|
13
|
+
"RemoteAgent",
|
|
14
14
|
]
|
mcp_use/agents/mcpagent.py
CHANGED
|
@@ -8,6 +8,7 @@ to provide a simple interface for using MCP tools with different LLMs.
|
|
|
8
8
|
import logging
|
|
9
9
|
import time
|
|
10
10
|
from collections.abc import AsyncGenerator, AsyncIterator
|
|
11
|
+
from typing import TypeVar
|
|
11
12
|
|
|
12
13
|
from langchain.agents import AgentExecutor, create_tool_calling_agent
|
|
13
14
|
from langchain.agents.output_parsers.tools import ToolAgentAction
|
|
@@ -20,6 +21,7 @@ from langchain_core.exceptions import OutputParserException
|
|
|
20
21
|
from langchain_core.runnables.schema import StreamEvent
|
|
21
22
|
from langchain_core.tools import BaseTool
|
|
22
23
|
from langchain_core.utils.input import get_color_mapping
|
|
24
|
+
from pydantic import BaseModel
|
|
23
25
|
|
|
24
26
|
from mcp_use.client import MCPClient
|
|
25
27
|
from mcp_use.connectors.base import BaseConnector
|
|
@@ -31,9 +33,13 @@ from ..logging import logger
|
|
|
31
33
|
from ..managers.server_manager import ServerManager
|
|
32
34
|
from .prompts.system_prompt_builder import create_system_message
|
|
33
35
|
from .prompts.templates import DEFAULT_SYSTEM_PROMPT_TEMPLATE, SERVER_MANAGER_SYSTEM_PROMPT_TEMPLATE
|
|
36
|
+
from .remote import RemoteAgent
|
|
34
37
|
|
|
35
38
|
set_debug(logger.level == logging.DEBUG)
|
|
36
39
|
|
|
40
|
+
# Type variable for structured output
|
|
41
|
+
T = TypeVar("T", bound=BaseModel)
|
|
42
|
+
|
|
37
43
|
|
|
38
44
|
class MCPAgent:
|
|
39
45
|
"""Main class for using MCP tools with various LLM providers.
|
|
@@ -44,7 +50,7 @@ class MCPAgent:
|
|
|
44
50
|
|
|
45
51
|
def __init__(
|
|
46
52
|
self,
|
|
47
|
-
llm: BaseLanguageModel,
|
|
53
|
+
llm: BaseLanguageModel | None = None,
|
|
48
54
|
client: MCPClient | None = None,
|
|
49
55
|
connectors: list[BaseConnector] | None = None,
|
|
50
56
|
max_steps: int = 5,
|
|
@@ -54,13 +60,17 @@ class MCPAgent:
|
|
|
54
60
|
system_prompt_template: str | None = None, # User can still override the template
|
|
55
61
|
additional_instructions: str | None = None,
|
|
56
62
|
disallowed_tools: list[str] | None = None,
|
|
63
|
+
tools_used_names: list[str] | None = None,
|
|
57
64
|
use_server_manager: bool = False,
|
|
58
65
|
verbose: bool = False,
|
|
66
|
+
agent_id: str | None = None,
|
|
67
|
+
api_key: str | None = None,
|
|
68
|
+
base_url: str = "https://cloud.mcp-use.com",
|
|
59
69
|
):
|
|
60
70
|
"""Initialize a new MCPAgent instance.
|
|
61
71
|
|
|
62
72
|
Args:
|
|
63
|
-
llm: The LangChain LLM to use.
|
|
73
|
+
llm: The LangChain LLM to use. Not required if agent_id is provided for remote execution.
|
|
64
74
|
client: The MCPClient to use. If provided, connector is ignored.
|
|
65
75
|
connectors: A list of MCP connectors to use if client is not provided.
|
|
66
76
|
max_steps: The maximum number of steps to take.
|
|
@@ -71,7 +81,23 @@ class MCPAgent:
|
|
|
71
81
|
additional_instructions: Extra instructions to append to the system prompt.
|
|
72
82
|
disallowed_tools: List of tool names that should not be available to the agent.
|
|
73
83
|
use_server_manager: Whether to use server manager mode instead of exposing all tools.
|
|
84
|
+
agent_id: Remote agent ID for remote execution. If provided, creates a remote agent.
|
|
85
|
+
api_key: API key for remote execution. If None, checks MCP_USE_API_KEY env var.
|
|
86
|
+
base_url: Base URL for remote API calls.
|
|
74
87
|
"""
|
|
88
|
+
# Handle remote execution
|
|
89
|
+
if agent_id is not None:
|
|
90
|
+
self._remote_agent = RemoteAgent(agent_id=agent_id, api_key=api_key, base_url=base_url)
|
|
91
|
+
self._is_remote = True
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
self._is_remote = False
|
|
95
|
+
self._remote_agent = None
|
|
96
|
+
|
|
97
|
+
# Validate requirements for local execution
|
|
98
|
+
if llm is None:
|
|
99
|
+
raise ValueError("llm is required for local execution. For remote execution, provide agent_id instead.")
|
|
100
|
+
|
|
75
101
|
self.llm = llm
|
|
76
102
|
self.client = client
|
|
77
103
|
self.connectors = connectors or []
|
|
@@ -81,6 +107,7 @@ class MCPAgent:
|
|
|
81
107
|
self._initialized = False
|
|
82
108
|
self._conversation_history: list[BaseMessage] = []
|
|
83
109
|
self.disallowed_tools = disallowed_tools or []
|
|
110
|
+
self.tools_used_names = tools_used_names or []
|
|
84
111
|
self.use_server_manager = use_server_manager
|
|
85
112
|
self.verbose = verbose
|
|
86
113
|
# System prompt configuration
|
|
@@ -307,8 +334,8 @@ class MCPAgent:
|
|
|
307
334
|
|
|
308
335
|
async def _consume_and_return(
|
|
309
336
|
self,
|
|
310
|
-
generator: AsyncGenerator[tuple[AgentAction, str],
|
|
311
|
-
) -> str:
|
|
337
|
+
generator: AsyncGenerator[tuple[AgentAction, str] | str | T, None],
|
|
338
|
+
) -> tuple[str | T, int]:
|
|
312
339
|
"""Consume the generator and return the final result.
|
|
313
340
|
|
|
314
341
|
This method manually iterates through the generator to consume the steps.
|
|
@@ -319,20 +346,24 @@ class MCPAgent:
|
|
|
319
346
|
generator: The async generator that yields steps and a final result.
|
|
320
347
|
|
|
321
348
|
Returns:
|
|
322
|
-
|
|
349
|
+
A tuple of (final_result, steps_taken). final_result can be a string
|
|
350
|
+
for regular output or a Pydantic model instance for structured output.
|
|
323
351
|
"""
|
|
324
352
|
final_result = ""
|
|
325
353
|
steps_taken = 0
|
|
326
|
-
tools_used_names = []
|
|
327
354
|
async for item in generator:
|
|
328
|
-
# If it's a string, it's the final result
|
|
355
|
+
# If it's a string, it's the final result (regular output)
|
|
329
356
|
if isinstance(item, str):
|
|
330
357
|
final_result = item
|
|
331
358
|
break
|
|
359
|
+
# If it's not a tuple, it might be structured output (Pydantic model)
|
|
360
|
+
elif not isinstance(item, tuple):
|
|
361
|
+
final_result = item
|
|
362
|
+
break
|
|
332
363
|
# Otherwise it's a step tuple, just consume it
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
return final_result, steps_taken
|
|
364
|
+
else:
|
|
365
|
+
steps_taken += 1
|
|
366
|
+
return final_result, steps_taken
|
|
336
367
|
|
|
337
368
|
async def stream(
|
|
338
369
|
self,
|
|
@@ -341,7 +372,8 @@ class MCPAgent:
|
|
|
341
372
|
manage_connector: bool = True,
|
|
342
373
|
external_history: list[BaseMessage] | None = None,
|
|
343
374
|
track_execution: bool = True,
|
|
344
|
-
|
|
375
|
+
output_schema: type[T] | None = None,
|
|
376
|
+
) -> AsyncGenerator[tuple[AgentAction, str] | str | T, None]:
|
|
345
377
|
"""Run the agent and yield intermediate steps as an async generator.
|
|
346
378
|
|
|
347
379
|
Args:
|
|
@@ -350,17 +382,48 @@ class MCPAgent:
|
|
|
350
382
|
manage_connector: Whether to handle the connector lifecycle internally.
|
|
351
383
|
external_history: Optional external history to use instead of the
|
|
352
384
|
internal conversation history.
|
|
385
|
+
track_execution: Whether to track execution for telemetry.
|
|
386
|
+
output_schema: Optional Pydantic BaseModel class for structured output.
|
|
387
|
+
If provided, the agent will attempt structured output at finish points
|
|
388
|
+
and continue execution if required information is missing.
|
|
353
389
|
|
|
354
390
|
Yields:
|
|
355
|
-
Intermediate steps as (AgentAction, str) tuples, followed by the final result
|
|
391
|
+
Intermediate steps as (AgentAction, str) tuples, followed by the final result.
|
|
392
|
+
If output_schema is provided, yields structured output as instance of the schema.
|
|
356
393
|
"""
|
|
394
|
+
# Delegate to remote agent if in remote mode
|
|
395
|
+
if self._is_remote and self._remote_agent:
|
|
396
|
+
async for item in self._remote_agent.stream(
|
|
397
|
+
query, max_steps, manage_connector, external_history, track_execution, output_schema
|
|
398
|
+
):
|
|
399
|
+
yield item
|
|
400
|
+
return
|
|
401
|
+
|
|
357
402
|
result = ""
|
|
358
403
|
initialized_here = False
|
|
359
404
|
start_time = time.time()
|
|
360
|
-
tools_used_names = []
|
|
361
405
|
steps_taken = 0
|
|
362
406
|
success = False
|
|
363
407
|
|
|
408
|
+
# Schema-aware setup for structured output
|
|
409
|
+
structured_llm = None
|
|
410
|
+
schema_description = ""
|
|
411
|
+
if output_schema:
|
|
412
|
+
query = self._enhance_query_with_schema(query, output_schema)
|
|
413
|
+
structured_llm = self.llm.with_structured_output(output_schema)
|
|
414
|
+
# Get schema description for feedback
|
|
415
|
+
schema_fields = []
|
|
416
|
+
try:
|
|
417
|
+
for field_name, field_info in output_schema.model_fields.items():
|
|
418
|
+
description = getattr(field_info, "description", "") or field_name
|
|
419
|
+
required = not hasattr(field_info, "default") or field_info.default is None
|
|
420
|
+
schema_fields.append(f"- {field_name}: {description} {'(required)' if required else '(optional)'}")
|
|
421
|
+
|
|
422
|
+
schema_description = "\n".join(schema_fields)
|
|
423
|
+
except Exception as e:
|
|
424
|
+
logger.warning(f"Could not extract schema details: {e}")
|
|
425
|
+
schema_description = f"Schema: {output_schema.__name__}"
|
|
426
|
+
|
|
364
427
|
try:
|
|
365
428
|
# Initialize if needed
|
|
366
429
|
if manage_connector and not self._initialized:
|
|
@@ -449,7 +512,49 @@ class MCPAgent:
|
|
|
449
512
|
if isinstance(next_step_output, AgentFinish):
|
|
450
513
|
logger.info(f"✅ Agent finished at step {step_num + 1}")
|
|
451
514
|
result = next_step_output.return_values.get("output", "No output generated")
|
|
452
|
-
|
|
515
|
+
|
|
516
|
+
# If structured output is requested, attempt to create it
|
|
517
|
+
if output_schema and structured_llm:
|
|
518
|
+
try:
|
|
519
|
+
logger.info("🔧 Attempting structured output...")
|
|
520
|
+
structured_result = await self._attempt_structured_output(
|
|
521
|
+
result, structured_llm, output_schema, schema_description
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
# Add the final response to conversation history if memory is enabled
|
|
525
|
+
if self.memory_enabled:
|
|
526
|
+
self.add_to_history(AIMessage(content=f"Structured result: {structured_result}"))
|
|
527
|
+
|
|
528
|
+
logger.info("✅ Structured output successful")
|
|
529
|
+
success = True
|
|
530
|
+
yield structured_result
|
|
531
|
+
return
|
|
532
|
+
|
|
533
|
+
except Exception as e:
|
|
534
|
+
logger.warning(f"⚠️ Structured output failed: {e}")
|
|
535
|
+
# Continue execution to gather missing information
|
|
536
|
+
missing_info_prompt = f"""
|
|
537
|
+
The current result cannot be formatted into the required structure.
|
|
538
|
+
Error: {str(e)}
|
|
539
|
+
|
|
540
|
+
Current information: {result}
|
|
541
|
+
|
|
542
|
+
Please continue working to gather the missing information needed for:
|
|
543
|
+
{schema_description}
|
|
544
|
+
|
|
545
|
+
Focus on finding the specific missing details.
|
|
546
|
+
"""
|
|
547
|
+
|
|
548
|
+
# Add this as feedback and continue the loop
|
|
549
|
+
inputs["input"] = missing_info_prompt
|
|
550
|
+
if self.memory_enabled:
|
|
551
|
+
self.add_to_history(HumanMessage(content=missing_info_prompt))
|
|
552
|
+
|
|
553
|
+
logger.info("🔄 Continuing execution to gather missing information...")
|
|
554
|
+
continue
|
|
555
|
+
else:
|
|
556
|
+
# Regular execution without structured output
|
|
557
|
+
break
|
|
453
558
|
|
|
454
559
|
# If it's actions/steps, add to intermediate steps and yield them
|
|
455
560
|
intermediate_steps.extend(next_step_output)
|
|
@@ -459,7 +564,7 @@ class MCPAgent:
|
|
|
459
564
|
yield agent_step
|
|
460
565
|
action, observation = agent_step
|
|
461
566
|
tool_name = action.tool
|
|
462
|
-
tools_used_names.append(tool_name)
|
|
567
|
+
self.tools_used_names.append(tool_name)
|
|
463
568
|
tool_input_str = str(action.tool_input)
|
|
464
569
|
# Truncate long inputs for readability
|
|
465
570
|
if len(tool_input_str) > 100:
|
|
@@ -498,15 +603,37 @@ class MCPAgent:
|
|
|
498
603
|
logger.warning(f"⚠️ Agent stopped after reaching max iterations ({steps})")
|
|
499
604
|
result = f"Agent stopped after reaching the maximum number of steps ({steps})."
|
|
500
605
|
|
|
501
|
-
#
|
|
502
|
-
if
|
|
606
|
+
# If structured output was requested but not achieved, attempt one final time
|
|
607
|
+
if output_schema and structured_llm and not success:
|
|
608
|
+
try:
|
|
609
|
+
logger.info("🔧 Final attempt at structured output...")
|
|
610
|
+
structured_result = await self._attempt_structured_output(
|
|
611
|
+
result, structured_llm, output_schema, schema_description
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
# Add the final response to conversation history if memory is enabled
|
|
615
|
+
if self.memory_enabled:
|
|
616
|
+
self.add_to_history(AIMessage(content=f"Structured result: {structured_result}"))
|
|
617
|
+
|
|
618
|
+
logger.info("✅ Final structured output successful")
|
|
619
|
+
success = True
|
|
620
|
+
yield structured_result
|
|
621
|
+
return
|
|
622
|
+
|
|
623
|
+
except Exception as e:
|
|
624
|
+
logger.error(f"❌ Final structured output attempt failed: {e}")
|
|
625
|
+
raise RuntimeError(f"Failed to generate structured output after {steps} steps: {str(e)}") from e
|
|
626
|
+
|
|
627
|
+
if self.memory_enabled and not output_schema:
|
|
503
628
|
self.add_to_history(AIMessage(content=result))
|
|
504
629
|
|
|
505
630
|
logger.info(f"🎉 Agent execution complete in {time.time() - start_time} seconds")
|
|
506
|
-
|
|
631
|
+
if not success:
|
|
632
|
+
success = True
|
|
507
633
|
|
|
508
|
-
# Yield the final result
|
|
509
|
-
|
|
634
|
+
# Yield the final result (only for non-structured output)
|
|
635
|
+
if not output_schema:
|
|
636
|
+
yield result
|
|
510
637
|
|
|
511
638
|
except Exception as e:
|
|
512
639
|
logger.error(f"❌ Error running query: {e}")
|
|
@@ -548,8 +675,8 @@ class MCPAgent:
|
|
|
548
675
|
manage_connector=manage_connector,
|
|
549
676
|
external_history_used=external_history is not None,
|
|
550
677
|
steps_taken=steps_taken,
|
|
551
|
-
tools_used_count=len(tools_used_names),
|
|
552
|
-
tools_used_names=tools_used_names,
|
|
678
|
+
tools_used_count=len(self.tools_used_names),
|
|
679
|
+
tools_used_names=self.tools_used_names,
|
|
553
680
|
response=result,
|
|
554
681
|
execution_time_ms=execution_time_ms,
|
|
555
682
|
error_type=None if success else "execution_error",
|
|
@@ -567,11 +694,13 @@ class MCPAgent:
|
|
|
567
694
|
max_steps: int | None = None,
|
|
568
695
|
manage_connector: bool = True,
|
|
569
696
|
external_history: list[BaseMessage] | None = None,
|
|
570
|
-
|
|
697
|
+
output_schema: type[T] | None = None,
|
|
698
|
+
) -> str | T:
|
|
571
699
|
"""Run a query using the MCP tools and return the final result.
|
|
572
700
|
|
|
573
701
|
This method uses the streaming implementation internally and returns
|
|
574
|
-
the final result after consuming all intermediate steps.
|
|
702
|
+
the final result after consuming all intermediate steps. If output_schema
|
|
703
|
+
is provided, the agent will be schema-aware and return structured output.
|
|
575
704
|
|
|
576
705
|
Args:
|
|
577
706
|
query: The query to run.
|
|
@@ -582,19 +711,47 @@ class MCPAgent:
|
|
|
582
711
|
for managing the connector lifecycle.
|
|
583
712
|
external_history: Optional external history to use instead of the
|
|
584
713
|
internal conversation history.
|
|
714
|
+
output_schema: Optional Pydantic BaseModel class for structured output.
|
|
715
|
+
If provided, the agent will attempt to return an instance of this model.
|
|
585
716
|
|
|
586
717
|
Returns:
|
|
587
|
-
The result of running the query
|
|
718
|
+
The result of running the query as a string, or if output_schema is provided,
|
|
719
|
+
an instance of the specified Pydantic model.
|
|
720
|
+
|
|
721
|
+
Example:
|
|
722
|
+
```python
|
|
723
|
+
# Regular usage
|
|
724
|
+
result = await agent.run("What's the weather like?")
|
|
725
|
+
|
|
726
|
+
# Structured output usage
|
|
727
|
+
from pydantic import BaseModel, Field
|
|
728
|
+
|
|
729
|
+
class WeatherInfo(BaseModel):
|
|
730
|
+
temperature: float = Field(description="Temperature in Celsius")
|
|
731
|
+
condition: str = Field(description="Weather condition")
|
|
732
|
+
|
|
733
|
+
weather: WeatherInfo = await agent.run(
|
|
734
|
+
"What's the weather like?",
|
|
735
|
+
output_schema=WeatherInfo
|
|
736
|
+
)
|
|
737
|
+
```
|
|
588
738
|
"""
|
|
739
|
+
# Delegate to remote agent if in remote mode
|
|
740
|
+
if self._is_remote and self._remote_agent:
|
|
741
|
+
return await self._remote_agent.run(query, max_steps, manage_connector, external_history, output_schema)
|
|
742
|
+
|
|
589
743
|
success = True
|
|
590
744
|
start_time = time.time()
|
|
591
|
-
|
|
745
|
+
|
|
746
|
+
generator = self.stream(
|
|
747
|
+
query, max_steps, manage_connector, external_history, track_execution=False, output_schema=output_schema
|
|
748
|
+
)
|
|
592
749
|
error = None
|
|
593
750
|
steps_taken = 0
|
|
594
|
-
tools_used_names = []
|
|
595
751
|
result = None
|
|
596
752
|
try:
|
|
597
|
-
result, steps_taken
|
|
753
|
+
result, steps_taken = await self._consume_and_return(generator)
|
|
754
|
+
|
|
598
755
|
except Exception as e:
|
|
599
756
|
success = False
|
|
600
757
|
error = str(e)
|
|
@@ -618,15 +775,79 @@ class MCPAgent:
|
|
|
618
775
|
manage_connector=manage_connector,
|
|
619
776
|
external_history_used=external_history is not None,
|
|
620
777
|
steps_taken=steps_taken,
|
|
621
|
-
tools_used_count=len(tools_used_names),
|
|
622
|
-
tools_used_names=tools_used_names,
|
|
623
|
-
response=result,
|
|
778
|
+
tools_used_count=len(self.tools_used_names),
|
|
779
|
+
tools_used_names=self.tools_used_names,
|
|
780
|
+
response=str(result),
|
|
624
781
|
execution_time_ms=int((time.time() - start_time) * 1000),
|
|
625
782
|
error_type=error,
|
|
626
783
|
conversation_history_length=len(self._conversation_history),
|
|
627
784
|
)
|
|
628
785
|
return result
|
|
629
786
|
|
|
787
|
+
async def _attempt_structured_output(
|
|
788
|
+
self, raw_result: str, structured_llm, output_schema: type[T], schema_description: str
|
|
789
|
+
) -> T:
|
|
790
|
+
"""Attempt to create structured output from raw result with validation."""
|
|
791
|
+
format_prompt = f"""
|
|
792
|
+
Please format the following information according to the specified schema.
|
|
793
|
+
Extract and structure the relevant information from the content below.
|
|
794
|
+
|
|
795
|
+
Required schema fields:
|
|
796
|
+
{schema_description}
|
|
797
|
+
|
|
798
|
+
Content to format:
|
|
799
|
+
{raw_result}
|
|
800
|
+
|
|
801
|
+
Please provide the information in the requested structured format.
|
|
802
|
+
If any required information is missing, you must indicate this clearly.
|
|
803
|
+
"""
|
|
804
|
+
|
|
805
|
+
structured_result = await structured_llm.ainvoke(format_prompt)
|
|
806
|
+
|
|
807
|
+
try:
|
|
808
|
+
for field_name, field_info in output_schema.model_fields.items():
|
|
809
|
+
required = not hasattr(field_info, "default") or field_info.default is None
|
|
810
|
+
if required:
|
|
811
|
+
value = getattr(structured_result, field_name, None)
|
|
812
|
+
if value is None or (isinstance(value, str) and not value.strip()):
|
|
813
|
+
raise ValueError(f"Required field '{field_name}' is missing or empty")
|
|
814
|
+
if isinstance(value, list) and len(value) == 0:
|
|
815
|
+
raise ValueError(f"Required field '{field_name}' is an empty list")
|
|
816
|
+
except Exception as e:
|
|
817
|
+
logger.debug(f"Validation details: {e}")
|
|
818
|
+
raise # Re-raise to trigger retry logic
|
|
819
|
+
|
|
820
|
+
return structured_result
|
|
821
|
+
|
|
822
|
+
def _enhance_query_with_schema(self, query: str, output_schema: type[T]) -> str:
|
|
823
|
+
"""Enhance the query with schema information to make the agent aware of required fields."""
|
|
824
|
+
schema_fields = []
|
|
825
|
+
|
|
826
|
+
try:
|
|
827
|
+
for field_name, field_info in output_schema.model_fields.items():
|
|
828
|
+
description = getattr(field_info, "description", "") or field_name
|
|
829
|
+
required = not hasattr(field_info, "default") or field_info.default is None
|
|
830
|
+
schema_fields.append(f"- {field_name}: {description} {'(required)' if required else '(optional)'}")
|
|
831
|
+
|
|
832
|
+
schema_description = "\n".join(schema_fields)
|
|
833
|
+
except Exception as e:
|
|
834
|
+
logger.warning(f"Could not extract schema details: {e}")
|
|
835
|
+
schema_description = f"Schema: {output_schema.__name__}"
|
|
836
|
+
|
|
837
|
+
# Enhance the query with schema awareness
|
|
838
|
+
enhanced_query = f"""
|
|
839
|
+
{query}
|
|
840
|
+
|
|
841
|
+
IMPORTANT: Your response must include sufficient information to populate the following structured output:
|
|
842
|
+
|
|
843
|
+
{schema_description}
|
|
844
|
+
|
|
845
|
+
Make sure you gather ALL the required information during your task execution.
|
|
846
|
+
If any required information is missing, continue working to find it.
|
|
847
|
+
"""
|
|
848
|
+
|
|
849
|
+
return enhanced_query
|
|
850
|
+
|
|
630
851
|
async def _generate_response_chunks_async(
|
|
631
852
|
self,
|
|
632
853
|
query: str,
|
|
@@ -749,6 +970,11 @@ class MCPAgent:
|
|
|
749
970
|
|
|
750
971
|
async def close(self) -> None:
|
|
751
972
|
"""Close the MCP connection with improved error handling."""
|
|
973
|
+
# Delegate to remote agent if in remote mode
|
|
974
|
+
if self._is_remote and self._remote_agent:
|
|
975
|
+
await self._remote_agent.close()
|
|
976
|
+
return
|
|
977
|
+
|
|
752
978
|
logger.info("🔌 Closing agent and cleaning up resources...")
|
|
753
979
|
try:
|
|
754
980
|
# Clean up the agent first
|
|
@@ -17,27 +17,25 @@ Thought: I now know the final answer
|
|
|
17
17
|
Final Answer: the final answer to the original input question"""
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
SERVER_MANAGER_SYSTEM_PROMPT_TEMPLATE = """You are a helpful assistant designed
|
|
21
|
-
(Model Context Protocol) servers. You can manage connections
|
|
22
|
-
provided by the currently active server.
|
|
23
|
-
|
|
24
|
-
Important: The available tools change
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
available
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
Here are the tools *currently* available to you (this list includes server management tools and will
|
|
41
|
-
change when you connect to a server):
|
|
20
|
+
SERVER_MANAGER_SYSTEM_PROMPT_TEMPLATE = """You are a helpful assistant designed
|
|
21
|
+
to interact with MCP (Model Context Protocol) servers. You can manage connections
|
|
22
|
+
to different servers and use the tools provided by the currently active server.
|
|
23
|
+
|
|
24
|
+
Important: The available tools change dynamically based on which server is active.
|
|
25
|
+
|
|
26
|
+
- When you connect to a server using 'connect_to_mcp_server', that server's tools are automatically added to
|
|
27
|
+
your available tools with their full schemas
|
|
28
|
+
- When you disconnect using 'disconnect_from_mcp_server', the server's tools are removed from your available tools
|
|
29
|
+
- The tool list below will automatically update when you connect/disconnect from servers
|
|
30
|
+
|
|
31
|
+
If a request requires tools not currently listed below (e.g., file operations, web browsing, image manipulation),
|
|
32
|
+
you MUST first connect to the appropriate server using 'connect_to_mcp_server'. Use 'list_mcp_servers' to find
|
|
33
|
+
available servers if you are unsure which one to connect to.
|
|
34
|
+
|
|
35
|
+
After connecting to a server, you can immediately use its tools - they will be directly available to you with their
|
|
36
|
+
proper schemas and validation. No additional steps are needed.
|
|
37
|
+
|
|
38
|
+
Here are the tools currently available to you (this list dynamically updates when
|
|
39
|
+
connecting/disconnecting from servers):
|
|
42
40
|
{tool_descriptions}
|
|
43
41
|
"""
|