mcp-use 1.3.7__py3-none-any.whl → 1.3.8__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/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 +239 -0
- mcp_use/client.py +5 -2
- mcp_use/config.py +2 -0
- mcp_use/managers/__init__.py +3 -5
- mcp_use/managers/server_manager.py +45 -6
- mcp_use/managers/tools/__init__.py +2 -4
- {mcp_use-1.3.7.dist-info → mcp_use-1.3.8.dist-info}/METADATA +23 -24
- {mcp_use-1.3.7.dist-info → mcp_use-1.3.8.dist-info}/RECORD +13 -13
- mcp_use/managers/tools/use_tool.py +0 -154
- {mcp_use-1.3.7.dist-info → mcp_use-1.3.8.dist-info}/WHEEL +0 -0
- {mcp_use-1.3.7.dist-info → mcp_use-1.3.8.dist-info}/licenses/LICENSE +0 -0
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
|
"""
|
mcp_use/agents/remote.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Remote agent implementation for executing agents via API.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from typing import Any, TypeVar
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
from langchain.schema import BaseMessage
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
|
|
13
|
+
from ..logging import logger
|
|
14
|
+
|
|
15
|
+
T = TypeVar("T", bound=BaseModel)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RemoteAgent:
|
|
19
|
+
"""Agent that executes remotely via API."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, agent_id: str, api_key: str | None = None, base_url: str = "https://cloud.mcp-use.com"):
|
|
22
|
+
"""Initialize remote agent.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
agent_id: The ID of the remote agent to execute
|
|
26
|
+
api_key: API key for authentication. If None, will check MCP_USE_API_KEY env var
|
|
27
|
+
base_url: Base URL for the remote API
|
|
28
|
+
"""
|
|
29
|
+
self.agent_id = agent_id
|
|
30
|
+
self.base_url = base_url
|
|
31
|
+
|
|
32
|
+
# Handle API key validation
|
|
33
|
+
if api_key is None:
|
|
34
|
+
api_key = os.getenv("MCP_USE_API_KEY")
|
|
35
|
+
if not api_key:
|
|
36
|
+
raise ValueError(
|
|
37
|
+
"API key is required for remote execution. "
|
|
38
|
+
"Please provide it as a parameter or set the MCP_USE_API_KEY environment variable. "
|
|
39
|
+
"You can get an API key from https://cloud.mcp-use.com"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
self.api_key = api_key
|
|
43
|
+
# Configure client with reasonable timeouts for agent execution
|
|
44
|
+
self._client = httpx.AsyncClient(
|
|
45
|
+
timeout=httpx.Timeout(
|
|
46
|
+
connect=10.0, # 10 seconds to establish connection
|
|
47
|
+
read=300.0, # 5 minutes to read response (agents can take time)
|
|
48
|
+
write=10.0, # 10 seconds to send request
|
|
49
|
+
pool=10.0, # 10 seconds to get connection from pool
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def _pydantic_to_json_schema(self, model_class: type[T]) -> dict[str, Any]:
|
|
54
|
+
"""Convert a Pydantic model to JSON schema for API transmission.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
model_class: The Pydantic model class to convert
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
JSON schema representation of the model
|
|
61
|
+
"""
|
|
62
|
+
return model_class.model_json_schema()
|
|
63
|
+
|
|
64
|
+
def _parse_structured_response(self, response_data: Any, output_schema: type[T]) -> T:
|
|
65
|
+
"""Parse the API response into the structured output format.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
response_data: Raw response data from the API
|
|
69
|
+
output_schema: The Pydantic model to parse into
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Parsed structured output
|
|
73
|
+
"""
|
|
74
|
+
# Handle different response formats
|
|
75
|
+
if isinstance(response_data, dict):
|
|
76
|
+
if "result" in response_data:
|
|
77
|
+
outer_result = response_data["result"]
|
|
78
|
+
# Check if this is a nested result structure (agent execution response)
|
|
79
|
+
if isinstance(outer_result, dict) and "result" in outer_result:
|
|
80
|
+
# Extract the actual structured output from the nested result
|
|
81
|
+
result_data = outer_result["result"]
|
|
82
|
+
else:
|
|
83
|
+
# Use the outer result directly
|
|
84
|
+
result_data = outer_result
|
|
85
|
+
else:
|
|
86
|
+
result_data = response_data
|
|
87
|
+
elif isinstance(response_data, str):
|
|
88
|
+
try:
|
|
89
|
+
result_data = json.loads(response_data)
|
|
90
|
+
except json.JSONDecodeError:
|
|
91
|
+
# If it's not valid JSON, try to create the model from the string content
|
|
92
|
+
result_data = {"content": response_data}
|
|
93
|
+
else:
|
|
94
|
+
result_data = response_data
|
|
95
|
+
|
|
96
|
+
# Parse into the Pydantic model
|
|
97
|
+
try:
|
|
98
|
+
return output_schema.model_validate(result_data)
|
|
99
|
+
except Exception as e:
|
|
100
|
+
logger.warning(f"Failed to parse structured output: {e}")
|
|
101
|
+
# Fallback: try to parse it as raw content if the model has a content field
|
|
102
|
+
if hasattr(output_schema, "model_fields") and "content" in output_schema.model_fields:
|
|
103
|
+
return output_schema.model_validate({"content": str(result_data)})
|
|
104
|
+
raise
|
|
105
|
+
|
|
106
|
+
async def run(
|
|
107
|
+
self,
|
|
108
|
+
query: str,
|
|
109
|
+
max_steps: int | None = None,
|
|
110
|
+
manage_connector: bool = True,
|
|
111
|
+
external_history: list[BaseMessage] | None = None,
|
|
112
|
+
output_schema: type[T] | None = None,
|
|
113
|
+
) -> str | T:
|
|
114
|
+
"""Run a query on the remote agent.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
query: The query to execute
|
|
118
|
+
max_steps: Maximum number of steps (default: 10)
|
|
119
|
+
manage_connector: Ignored for remote execution
|
|
120
|
+
external_history: Ignored for remote execution (not supported yet)
|
|
121
|
+
output_schema: Optional Pydantic model for structured output
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
The result from the remote agent execution (string or structured output)
|
|
125
|
+
"""
|
|
126
|
+
if external_history is not None:
|
|
127
|
+
logger.warning("External history is not yet supported for remote execution")
|
|
128
|
+
|
|
129
|
+
payload = {"query": query, "max_steps": max_steps or 10}
|
|
130
|
+
|
|
131
|
+
# Add structured output schema if provided
|
|
132
|
+
if output_schema is not None:
|
|
133
|
+
payload["output_schema"] = self._pydantic_to_json_schema(output_schema)
|
|
134
|
+
logger.info(f"🔧 Using structured output with schema: {output_schema.__name__}")
|
|
135
|
+
|
|
136
|
+
headers = {"Content-Type": "application/json", "x-api-key": self.api_key}
|
|
137
|
+
|
|
138
|
+
url = f"{self.base_url}/api/v1/agents/{self.agent_id}/run"
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
logger.info(f"🌐 Executing query on remote agent {self.agent_id}")
|
|
142
|
+
response = await self._client.post(url, json=payload, headers=headers)
|
|
143
|
+
response.raise_for_status()
|
|
144
|
+
|
|
145
|
+
result = response.json()
|
|
146
|
+
logger.info(f"🔧 Response: {result}")
|
|
147
|
+
logger.info("✅ Remote execution completed successfully")
|
|
148
|
+
|
|
149
|
+
# Check for error responses (even with 200 status)
|
|
150
|
+
if isinstance(result, dict):
|
|
151
|
+
# Check for actual error conditions (not just presence of error field)
|
|
152
|
+
if result.get("status") == "error" or (result.get("error") is not None):
|
|
153
|
+
error_msg = result.get("error", str(result))
|
|
154
|
+
logger.error(f"❌ Remote agent execution failed: {error_msg}")
|
|
155
|
+
raise RuntimeError(f"Remote agent execution failed: {error_msg}")
|
|
156
|
+
|
|
157
|
+
# Check if the response indicates agent initialization failure
|
|
158
|
+
if "failed to initialize" in str(result):
|
|
159
|
+
logger.error(f"❌ Agent initialization failed: {result}")
|
|
160
|
+
raise RuntimeError(
|
|
161
|
+
f"Agent initialization failed on remote server. "
|
|
162
|
+
f"This usually indicates:\n"
|
|
163
|
+
f"• Invalid agent configuration (LLM model, system prompt)\n"
|
|
164
|
+
f"• Missing or invalid MCP server configurations\n"
|
|
165
|
+
f"• Network connectivity issues with MCP servers\n"
|
|
166
|
+
f"• Missing environment variables or credentials\n"
|
|
167
|
+
f"Raw error: {result}"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Handle structured output
|
|
171
|
+
if output_schema is not None:
|
|
172
|
+
return self._parse_structured_response(result, output_schema)
|
|
173
|
+
|
|
174
|
+
# Regular string output
|
|
175
|
+
if isinstance(result, dict) and "result" in result:
|
|
176
|
+
return result["result"]
|
|
177
|
+
elif isinstance(result, str):
|
|
178
|
+
return result
|
|
179
|
+
else:
|
|
180
|
+
return str(result)
|
|
181
|
+
|
|
182
|
+
except httpx.HTTPStatusError as e:
|
|
183
|
+
status_code = e.response.status_code
|
|
184
|
+
response_text = e.response.text
|
|
185
|
+
|
|
186
|
+
# Provide specific error messages based on status code
|
|
187
|
+
if status_code == 401:
|
|
188
|
+
logger.error(f"❌ Authentication failed: {response_text}")
|
|
189
|
+
raise RuntimeError(
|
|
190
|
+
"Authentication failed: Invalid or missing API key. "
|
|
191
|
+
"Please check your API key and ensure the MCP_USE_API_KEY environment variable is set correctly."
|
|
192
|
+
) from e
|
|
193
|
+
elif status_code == 403:
|
|
194
|
+
logger.error(f"❌ Access forbidden: {response_text}")
|
|
195
|
+
raise RuntimeError(
|
|
196
|
+
f"Access denied: You don't have permission to execute agent '{self.agent_id}'. "
|
|
197
|
+
"Check if the agent exists and you have the necessary permissions."
|
|
198
|
+
) from e
|
|
199
|
+
elif status_code == 404:
|
|
200
|
+
logger.error(f"❌ Agent not found: {response_text}")
|
|
201
|
+
raise RuntimeError(
|
|
202
|
+
f"Agent not found: Agent '{self.agent_id}' does not exist or you don't have access to it. "
|
|
203
|
+
"Please verify the agent ID and ensure it exists in your account."
|
|
204
|
+
) from e
|
|
205
|
+
elif status_code == 422:
|
|
206
|
+
logger.error(f"❌ Validation error: {response_text}")
|
|
207
|
+
raise RuntimeError(
|
|
208
|
+
f"Request validation failed: {response_text}. "
|
|
209
|
+
"Please check your query parameters and output schema format."
|
|
210
|
+
) from e
|
|
211
|
+
elif status_code == 500:
|
|
212
|
+
logger.error(f"❌ Server error: {response_text}")
|
|
213
|
+
raise RuntimeError(
|
|
214
|
+
"Internal server error occurred during agent execution. "
|
|
215
|
+
"Please try again later or contact support if the issue persists."
|
|
216
|
+
) from e
|
|
217
|
+
else:
|
|
218
|
+
logger.error(f"❌ Remote execution failed with status {status_code}: {response_text}")
|
|
219
|
+
raise RuntimeError(f"Remote agent execution failed: {status_code} - {response_text}") from e
|
|
220
|
+
except httpx.TimeoutException as e:
|
|
221
|
+
logger.error(f"❌ Remote execution timed out: {e}")
|
|
222
|
+
raise RuntimeError(
|
|
223
|
+
"Remote agent execution timed out. The server may be overloaded or the query is taking too long to "
|
|
224
|
+
"process. Try again or use a simpler query."
|
|
225
|
+
) from e
|
|
226
|
+
except httpx.ConnectError as e:
|
|
227
|
+
logger.error(f"❌ Remote execution connection error: {e}")
|
|
228
|
+
raise RuntimeError(
|
|
229
|
+
f"Remote agent connection failed: Cannot connect to {self.base_url}. "
|
|
230
|
+
f"Check if the server is running and the URL is correct."
|
|
231
|
+
) from e
|
|
232
|
+
except Exception as e:
|
|
233
|
+
logger.error(f"❌ Remote execution error: {e}")
|
|
234
|
+
raise RuntimeError(f"Remote agent execution failed: {str(e)}") from e
|
|
235
|
+
|
|
236
|
+
async def close(self) -> None:
|
|
237
|
+
"""Close the HTTP client."""
|
|
238
|
+
await self._client.aclose()
|
|
239
|
+
logger.info("🔌 Remote agent client closed")
|
mcp_use/client.py
CHANGED
|
@@ -28,6 +28,7 @@ class MCPClient:
|
|
|
28
28
|
def __init__(
|
|
29
29
|
self,
|
|
30
30
|
config: str | dict[str, Any] | None = None,
|
|
31
|
+
allowed_servers: list[str] | None = None,
|
|
31
32
|
sandbox: bool = False,
|
|
32
33
|
sandbox_options: SandboxOptions | None = None,
|
|
33
34
|
sampling_callback: SamplingFnT | None = None,
|
|
@@ -43,6 +44,7 @@ class MCPClient:
|
|
|
43
44
|
sampling_callback: Optional sampling callback function.
|
|
44
45
|
"""
|
|
45
46
|
self.config: dict[str, Any] = {}
|
|
47
|
+
self.allowed_servers: list[str] = allowed_servers
|
|
46
48
|
self.sandbox = sandbox
|
|
47
49
|
self.sandbox_options = sandbox_options
|
|
48
50
|
self.sessions: dict[str, MCPSession] = {}
|
|
@@ -220,9 +222,10 @@ class MCPClient:
|
|
|
220
222
|
warnings.warn("No MCP servers defined in config", UserWarning, stacklevel=2)
|
|
221
223
|
return {}
|
|
222
224
|
|
|
223
|
-
# Create sessions for all servers
|
|
225
|
+
# Create sessions only for allowed servers if applicable else create for all servers
|
|
224
226
|
for name in servers:
|
|
225
|
-
|
|
227
|
+
if self.allowed_servers is None or name in self.allowed_servers:
|
|
228
|
+
await self.create_session(name, auto_initialize)
|
|
226
229
|
|
|
227
230
|
return self.sessions
|
|
228
231
|
|
mcp_use/config.py
CHANGED
|
@@ -80,6 +80,8 @@ def create_connector_from_config(
|
|
|
80
80
|
base_url=server_config["url"],
|
|
81
81
|
headers=server_config.get("headers", None),
|
|
82
82
|
auth_token=server_config.get("auth_token", None),
|
|
83
|
+
timeout=server_config.get("timeout", 5),
|
|
84
|
+
sse_read_timeout=server_config.get("sse_read_timeout", 60 * 5),
|
|
83
85
|
sampling_callback=sampling_callback,
|
|
84
86
|
elicitation_callback=elicitation_callback,
|
|
85
87
|
)
|
mcp_use/managers/__init__.py
CHANGED
|
@@ -6,16 +6,14 @@ from .tools import (
|
|
|
6
6
|
ListServersTool,
|
|
7
7
|
MCPServerTool,
|
|
8
8
|
SearchToolsTool,
|
|
9
|
-
UseToolFromServerTool,
|
|
10
9
|
)
|
|
11
10
|
|
|
12
11
|
__all__ = [
|
|
13
12
|
"ServerManager",
|
|
14
|
-
"
|
|
13
|
+
"MCPServerTool",
|
|
15
14
|
"ConnectServerTool",
|
|
16
|
-
"GetActiveServerTool",
|
|
17
15
|
"DisconnectServerTool",
|
|
16
|
+
"GetActiveServerTool",
|
|
17
|
+
"ListServersTool",
|
|
18
18
|
"SearchToolsTool",
|
|
19
|
-
"MCPServerTool",
|
|
20
|
-
"UseToolFromServerTool",
|
|
21
19
|
]
|
|
@@ -10,7 +10,6 @@ from .tools import (
|
|
|
10
10
|
GetActiveServerTool,
|
|
11
11
|
ListServersTool,
|
|
12
12
|
SearchToolsTool,
|
|
13
|
-
UseToolFromServerTool,
|
|
14
13
|
)
|
|
15
14
|
|
|
16
15
|
|
|
@@ -73,12 +72,21 @@ class ServerManager:
|
|
|
73
72
|
except Exception as e:
|
|
74
73
|
logger.error(f"Error prefetching tools for server '{server_name}': {e}")
|
|
75
74
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
def get_active_server_tools(self) -> list[BaseTool]:
|
|
76
|
+
"""Get tools from the currently active server.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
List of tools from the active server, or empty list if no server is active
|
|
80
|
+
"""
|
|
81
|
+
if self.active_server and self.active_server in self._server_tools:
|
|
82
|
+
return self._server_tools[self.active_server]
|
|
83
|
+
return []
|
|
84
|
+
|
|
85
|
+
def get_management_tools(self) -> list[BaseTool]:
|
|
86
|
+
"""Get the server management tools.
|
|
79
87
|
|
|
80
88
|
Returns:
|
|
81
|
-
|
|
89
|
+
List of server management tools
|
|
82
90
|
"""
|
|
83
91
|
return [
|
|
84
92
|
ListServersTool(self),
|
|
@@ -86,5 +94,36 @@ class ServerManager:
|
|
|
86
94
|
GetActiveServerTool(self),
|
|
87
95
|
DisconnectServerTool(self),
|
|
88
96
|
SearchToolsTool(self),
|
|
89
|
-
UseToolFromServerTool(self),
|
|
90
97
|
]
|
|
98
|
+
|
|
99
|
+
def has_tool_changes(self, current_tool_names: set[str]) -> bool:
|
|
100
|
+
"""Check if the available tools have changed.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
current_tool_names: Set of currently known tool names
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
True if tools have changed, False otherwise
|
|
107
|
+
"""
|
|
108
|
+
new_tool_names = {tool.name for tool in self.tools}
|
|
109
|
+
return new_tool_names != current_tool_names
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def tools(self) -> list[BaseTool]:
|
|
113
|
+
"""Get all server management tools and tools from the active server.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
list of LangChain tools for server management plus tools from active server
|
|
117
|
+
"""
|
|
118
|
+
management_tools = self.get_management_tools()
|
|
119
|
+
|
|
120
|
+
# Add tools from the active server if available
|
|
121
|
+
if self.active_server and self.active_server in self._server_tools:
|
|
122
|
+
server_tools = self._server_tools[self.active_server]
|
|
123
|
+
logger.debug(f"Including {len(server_tools)} tools from active server '{self.active_server}'")
|
|
124
|
+
logger.debug(f"Server tools: {[tool.name for tool in server_tools]}")
|
|
125
|
+
return management_tools + server_tools
|
|
126
|
+
else:
|
|
127
|
+
logger.debug("No active server - returning only management tools")
|
|
128
|
+
|
|
129
|
+
return management_tools
|
|
@@ -4,14 +4,12 @@ from .disconnect_server import DisconnectServerTool
|
|
|
4
4
|
from .get_active_server import GetActiveServerTool
|
|
5
5
|
from .list_servers_tool import ListServersTool
|
|
6
6
|
from .search_tools import SearchToolsTool
|
|
7
|
-
from .use_tool import UseToolFromServerTool
|
|
8
7
|
|
|
9
8
|
__all__ = [
|
|
10
9
|
"MCPServerTool",
|
|
11
|
-
"ListServersTool",
|
|
12
10
|
"ConnectServerTool",
|
|
13
|
-
"GetActiveServerTool",
|
|
14
11
|
"DisconnectServerTool",
|
|
12
|
+
"GetActiveServerTool",
|
|
13
|
+
"ListServersTool",
|
|
15
14
|
"SearchToolsTool",
|
|
16
|
-
"UseToolFromServerTool",
|
|
17
15
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-use
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.8
|
|
4
4
|
Summary: MCP Library for LLMs
|
|
5
5
|
Author-email: Pietro Zullo <pietro.zullo@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -17,7 +17,7 @@ Requires-Python: >=3.11
|
|
|
17
17
|
Requires-Dist: aiohttp>=3.9.0
|
|
18
18
|
Requires-Dist: jsonschema-pydantic>=0.1.0
|
|
19
19
|
Requires-Dist: langchain>=0.1.0
|
|
20
|
-
Requires-Dist: mcp>=1.
|
|
20
|
+
Requires-Dist: mcp>=1.10.0
|
|
21
21
|
Requires-Dist: posthog>=4.8.0
|
|
22
22
|
Requires-Dist: pydantic>=2.0.0
|
|
23
23
|
Requires-Dist: python-dotenv>=1.0.0
|
|
@@ -84,10 +84,9 @@ Description-Content-Type: text/markdown
|
|
|
84
84
|
|
|
85
85
|
💡 Let developers easily connect any LLM to tools like web browsing, file operations, and more.
|
|
86
86
|
|
|
87
|
-
-
|
|
87
|
+
- If you want to get started quickly check out [mcp-use.com website](https://mcp-use.com/) to build and deploy agents with your favorite MCP servers.
|
|
88
88
|
- Visit the [mcp-use docs](https://docs.mcp-use.com/) to get started with mcp-use library
|
|
89
|
-
|
|
90
|
-
💬 Get started quickly - chat with your servers on our <b>hosted version</b>! [Try mcp-use chat (beta)](https://chat.mcp-use.com).
|
|
89
|
+
- For the TypeScript version, visit [mcp-use-ts](https://github.com/mcp-use/mcp-use-ts)
|
|
91
90
|
|
|
92
91
|
| Supports | |
|
|
93
92
|
| :--- | :--- |
|
|
@@ -812,45 +811,45 @@ Thanks to all our amazing contributors!
|
|
|
812
811
|
<th width="400">Repository</th>
|
|
813
812
|
<th>Stars</th>
|
|
814
813
|
</tr>
|
|
814
|
+
<tr>
|
|
815
|
+
<td><img src="https://avatars.githubusercontent.com/u/38653995?s=40&v=4" width="20" height="20" style="vertical-align: middle; margin-right: 8px;"> <a href="https://github.com/patchy631/ai-engineering-hub"><strong>patchy631/ai-engineering-hub</strong></a></td>
|
|
816
|
+
<td>⭐ 15189</td>
|
|
817
|
+
</tr>
|
|
815
818
|
<tr>
|
|
816
819
|
<td><img src="https://avatars.githubusercontent.com/u/170207473?s=40&v=4" width="20" height="20" style="vertical-align: middle; margin-right: 8px;"> <a href="https://github.com/tavily-ai/meeting-prep-agent"><strong>tavily-ai/meeting-prep-agent</strong></a></td>
|
|
817
|
-
<td>⭐
|
|
820
|
+
<td>⭐ 129</td>
|
|
821
|
+
</tr>
|
|
822
|
+
<tr>
|
|
823
|
+
<td><img src="https://avatars.githubusercontent.com/u/187057607?s=40&v=4" width="20" height="20" style="vertical-align: middle; margin-right: 8px;"> <a href="https://github.com/hud-evals/hud-sdk"><strong>hud-evals/hud-sdk</strong></a></td>
|
|
824
|
+
<td>⭐ 75</td>
|
|
818
825
|
</tr>
|
|
819
826
|
<tr>
|
|
820
827
|
<td><img src="https://avatars.githubusercontent.com/u/20041231?s=40&v=4" width="20" height="20" style="vertical-align: middle; margin-right: 8px;"> <a href="https://github.com/krishnaik06/MCP-CRASH-Course"><strong>krishnaik06/MCP-CRASH-Course</strong></a></td>
|
|
821
|
-
<td>⭐
|
|
828
|
+
<td>⭐ 58</td>
|
|
829
|
+
</tr>
|
|
830
|
+
<tr>
|
|
831
|
+
<td><img src="https://avatars.githubusercontent.com/u/54944174?s=40&v=4" width="20" height="20" style="vertical-align: middle; margin-right: 8px;"> <a href="https://github.com/larksuite/lark-samples"><strong>larksuite/lark-samples</strong></a></td>
|
|
832
|
+
<td>⭐ 31</td>
|
|
822
833
|
</tr>
|
|
823
834
|
<tr>
|
|
824
835
|
<td><img src="https://avatars.githubusercontent.com/u/892404?s=40&v=4" width="20" height="20" style="vertical-align: middle; margin-right: 8px;"> <a href="https://github.com/truemagic-coder/solana-agent-app"><strong>truemagic-coder/solana-agent-app</strong></a></td>
|
|
825
|
-
<td>⭐
|
|
836
|
+
<td>⭐ 30</td>
|
|
826
837
|
</tr>
|
|
827
838
|
<tr>
|
|
828
839
|
<td><img src="https://avatars.githubusercontent.com/u/8344498?s=40&v=4" width="20" height="20" style="vertical-align: middle; margin-right: 8px;"> <a href="https://github.com/schogini/techietalksai"><strong>schogini/techietalksai</strong></a></td>
|
|
829
|
-
<td>⭐
|
|
840
|
+
<td>⭐ 24</td>
|
|
830
841
|
</tr>
|
|
831
842
|
<tr>
|
|
832
843
|
<td><img src="https://avatars.githubusercontent.com/u/201161342?s=40&v=4" width="20" height="20" style="vertical-align: middle; margin-right: 8px;"> <a href="https://github.com/autometa-dev/whatsapp-mcp-voice-agent"><strong>autometa-dev/whatsapp-mcp-voice-agent</strong></a></td>
|
|
833
|
-
<td>⭐
|
|
844
|
+
<td>⭐ 22</td>
|
|
834
845
|
</tr>
|
|
835
846
|
<tr>
|
|
836
847
|
<td><img src="https://avatars.githubusercontent.com/u/100749943?s=40&v=4" width="20" height="20" style="vertical-align: middle; margin-right: 8px;"> <a href="https://github.com/Deniscartin/mcp-cli"><strong>Deniscartin/mcp-cli</strong></a></td>
|
|
837
|
-
<td>⭐
|
|
838
|
-
</tr>
|
|
839
|
-
<tr>
|
|
840
|
-
<td><img src="https://avatars.githubusercontent.com/u/6764390?s=40&v=4" width="20" height="20" style="vertical-align: middle; margin-right: 8px;"> <a href="https://github.com/elastic/genai-workshops"><strong>elastic/genai-workshops</strong></a></td>
|
|
841
|
-
<td>⭐ 9</td>
|
|
848
|
+
<td>⭐ 18</td>
|
|
842
849
|
</tr>
|
|
843
850
|
<tr>
|
|
844
851
|
<td><img src="https://avatars.githubusercontent.com/u/6688805?s=40&v=4" width="20" height="20" style="vertical-align: middle; margin-right: 8px;"> <a href="https://github.com/innovaccer/Healthcare-MCP"><strong>innovaccer/Healthcare-MCP</strong></a></td>
|
|
845
|
-
<td>⭐
|
|
846
|
-
</tr>
|
|
847
|
-
<tr>
|
|
848
|
-
<td><img src="https://avatars.githubusercontent.com/u/205593730?s=40&v=4" width="20" height="20" style="vertical-align: middle; margin-right: 8px;"> <a href="https://github.com/Qingyon-AI/Revornix"><strong>Qingyon-AI/Revornix</strong></a></td>
|
|
849
|
-
<td>⭐ 5</td>
|
|
850
|
-
</tr>
|
|
851
|
-
<tr>
|
|
852
|
-
<td><img src="https://avatars.githubusercontent.com/u/68845761?s=40&v=4" width="20" height="20" style="vertical-align: middle; margin-right: 8px;"> <a href="https://github.com/entbappy/MCP-Tutorials"><strong>entbappy/MCP-Tutorials</strong></a></td>
|
|
853
|
-
<td>⭐ 5</td>
|
|
852
|
+
<td>⭐ 15</td>
|
|
854
853
|
</tr>
|
|
855
854
|
</table>
|
|
856
855
|
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
mcp_use/__init__.py,sha256=I3gFxw6Id45RksUBIZS1kxBW3ItjFXuAfoReJabpnW0,1055
|
|
2
|
-
mcp_use/client.py,sha256=
|
|
3
|
-
mcp_use/config.py,sha256=
|
|
2
|
+
mcp_use/client.py,sha256=dziKHIZIclLk64ycF4keegiVp93YKqSyGok7Rv4j8bE,10958
|
|
3
|
+
mcp_use/config.py,sha256=J3ebF9dlQxui3pSONTFtVYO035E7d0CP4HAvN0INgE4,3103
|
|
4
4
|
mcp_use/logging.py,sha256=CRtkPwR-bkXK_kQ0QOL86RikMWOHzEOi7A8VRHkNsZw,4270
|
|
5
5
|
mcp_use/session.py,sha256=4kwcB_IkTt_3FiBSTI1H17KhL1W_6N5oai3HTxFrTH4,2496
|
|
6
6
|
mcp_use/utils.py,sha256=QavJcVq2WxUUUCCpPCUeOB5bqIS0FFmpK-RAZkGc6aA,720
|
|
7
7
|
mcp_use/adapters/__init__.py,sha256=-xCrgPThuX7x0PHGFDdjb7M-mgw6QV3sKu5PM7ShnRg,275
|
|
8
8
|
mcp_use/adapters/base.py,sha256=U1z_UzojC-bytb4ZuKTRpEgEp-2F_BVBgqEXbUqLYB4,6901
|
|
9
9
|
mcp_use/adapters/langchain_adapter.py,sha256=LdlpRyLORhl8NZvtAmisgPelXkhEbBErSNdGHb8SF18,10860
|
|
10
|
-
mcp_use/agents/__init__.py,sha256=
|
|
10
|
+
mcp_use/agents/__init__.py,sha256=FzkntihbAqzixWdWe99zIrrcIfd4N3YWltNniutG9VA,267
|
|
11
11
|
mcp_use/agents/base.py,sha256=EN-dRbwOi9vIqofFg3jmi5yT2VKlwEr9Cwi1DZgB3eE,1591
|
|
12
|
-
mcp_use/agents/mcpagent.py,sha256=
|
|
12
|
+
mcp_use/agents/mcpagent.py,sha256=Vh4VOxxh-6sJwK1tTtJgUWZcp1bd3hb_JnATc7x9sKk,46698
|
|
13
|
+
mcp_use/agents/remote.py,sha256=_8TKP-hfr8NsexbpaEz8bK4LU-opILJMnTL7TbQBiY4,10575
|
|
13
14
|
mcp_use/agents/prompts/system_prompt_builder.py,sha256=E86STmxcl2Ic763_114awNqFB2RyLrQlbvgRmJajQjI,4116
|
|
14
|
-
mcp_use/agents/prompts/templates.py,sha256=
|
|
15
|
+
mcp_use/agents/prompts/templates.py,sha256=acg2Q-_uQDL-3q5ZUwwwFrP7wqqf-SEyq0XWDDHt69s,1906
|
|
15
16
|
mcp_use/connectors/__init__.py,sha256=cUF4yT0bNr8qeLkSzg28SHueiV5qDaHEB1l1GZ2K0dc,536
|
|
16
17
|
mcp_use/connectors/base.py,sha256=bCPOrSb3xzuxQRFpcLf7tCG1UmMFtr9IVM7br8JlbzI,13878
|
|
17
18
|
mcp_use/connectors/http.py,sha256=8LVzXtVtdLVQH9xMIqPzKfPEmaO_cxzMIu4g4oGIung,7912
|
|
@@ -19,16 +20,15 @@ mcp_use/connectors/sandbox.py,sha256=RX8xssn0cIObW6CjOqY7ZrO_D9lTzCZKdRcJ5lQSmQg
|
|
|
19
20
|
mcp_use/connectors/stdio.py,sha256=jTNhrsHxkRgSI9uAnj4bbFsBwe6zooc-oNcMXV_s9Xk,3378
|
|
20
21
|
mcp_use/connectors/utils.py,sha256=zQ8GdNQx0Twz3by90BoU1RsWPf9wODGof4K3-NxPXeA,366
|
|
21
22
|
mcp_use/connectors/websocket.py,sha256=G7ZeLJNPVl9AG6kCmiNJz1N2Ing_QxT7pSswigTKi8Y,9650
|
|
22
|
-
mcp_use/managers/__init__.py,sha256=
|
|
23
|
-
mcp_use/managers/server_manager.py,sha256=
|
|
24
|
-
mcp_use/managers/tools/__init__.py,sha256=
|
|
23
|
+
mcp_use/managers/__init__.py,sha256=FRTuJw5kYtY1Eo7wN9Aeqeqo1euiR5slvrx5Fl_SGvk,383
|
|
24
|
+
mcp_use/managers/server_manager.py,sha256=8F6jEwZOoAfR1y1O7zk-BSZ1LVYcLZTSQZLRClhSE2I,5278
|
|
25
|
+
mcp_use/managers/tools/__init__.py,sha256=zcpm4HXsp8NUMRJeyT6DdB8cgIMDs46pBfoTD-odhGU,437
|
|
25
26
|
mcp_use/managers/tools/base_tool.py,sha256=Jbbp7SwmHKDk8jT_6yVIv7iNsn6KaV_PljWuhhLcbXg,509
|
|
26
27
|
mcp_use/managers/tools/connect_server.py,sha256=MGYQCl11q-w6gSIYuT44dDk7ILV3Oh7kGAJ4fsNXbso,2923
|
|
27
28
|
mcp_use/managers/tools/disconnect_server.py,sha256=Y3kJN31efzsjfJwxUhpBxS-bgU21DCfGbn_LgEbzyvI,1586
|
|
28
29
|
mcp_use/managers/tools/get_active_server.py,sha256=tCaib76gYU3L5G82tEOTq4Io2cuCXWjOjPselb-92i8,964
|
|
29
30
|
mcp_use/managers/tools/list_servers_tool.py,sha256=OPDSMNe-VuAhlUyhDnR4CiuZFpoMhnhWpAablwO5S0k,1897
|
|
30
31
|
mcp_use/managers/tools/search_tools.py,sha256=4vso7ln-AfG6lQAMq9FA_CyeVtSEDYEWlHtdHtfnLps,12911
|
|
31
|
-
mcp_use/managers/tools/use_tool.py,sha256=gMNjgJrI9XDitPyJglcJcAvowbEWkO5z57yt4DT2Lpc,6626
|
|
32
32
|
mcp_use/observability/__init__.py,sha256=kTUcP0d6L5_3ktfldhdAk-3AWckzVHs7ztG-R6cye64,186
|
|
33
33
|
mcp_use/observability/laminar.py,sha256=WWjmVXP55yCfAlqlayeuJmym1gdrv8is7UyrIp4Tbn0,839
|
|
34
34
|
mcp_use/observability/langfuse.py,sha256=9vgJgnGtVpv_CbCyJqyRkzq2ELqPfYFIUGnpSbm2RCo,1334
|
|
@@ -43,7 +43,7 @@ mcp_use/telemetry/events.py,sha256=K5xqbmkum30r4gM2PWtTiUWGF8oZzGZw2DYwco1RfOQ,3
|
|
|
43
43
|
mcp_use/telemetry/telemetry.py,sha256=ck2MDFMtooafriR1W_zi41dWq-0O-ucF89pCkdkyc9E,11724
|
|
44
44
|
mcp_use/telemetry/utils.py,sha256=kDVTqt2oSeWNJbnTOlXOehr2yFO0PMyx2UGkrWkfJiw,1769
|
|
45
45
|
mcp_use/types/sandbox.py,sha256=opJ9r56F1FvaqVvPovfAj5jZbsOexgwYx5wLgSlN8_U,712
|
|
46
|
-
mcp_use-1.3.
|
|
47
|
-
mcp_use-1.3.
|
|
48
|
-
mcp_use-1.3.
|
|
49
|
-
mcp_use-1.3.
|
|
46
|
+
mcp_use-1.3.8.dist-info/METADATA,sha256=nqKWqFTWM-XqfsPrXXyn5Q6opaMyWz_oe3qfSZEfM3M,30356
|
|
47
|
+
mcp_use-1.3.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
48
|
+
mcp_use-1.3.8.dist-info/licenses/LICENSE,sha256=7Pw7dbwJSBw8zH-WE03JnR5uXvitRtaGTP9QWPcexcs,1068
|
|
49
|
+
mcp_use-1.3.8.dist-info/RECORD,,
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from typing import Any, ClassVar
|
|
3
|
-
|
|
4
|
-
from langchain_core.tools import BaseTool
|
|
5
|
-
from pydantic import BaseModel, Field
|
|
6
|
-
|
|
7
|
-
from mcp_use.logging import logger
|
|
8
|
-
|
|
9
|
-
from .base_tool import MCPServerTool
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class UseToolInput(BaseModel):
|
|
13
|
-
"""Input for using a tool from a specific server"""
|
|
14
|
-
|
|
15
|
-
server_name: str = Field(description="The name of the MCP server containing the tool")
|
|
16
|
-
tool_name: str = Field(description="The name of the tool to execute")
|
|
17
|
-
tool_input: dict[str, Any] | str = Field(
|
|
18
|
-
description="The input to pass to the tool. Can be a dictionary of parameters or a string"
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class UseToolFromServerTool(MCPServerTool):
|
|
23
|
-
"""Tool for directly executing a tool from a specific server."""
|
|
24
|
-
|
|
25
|
-
name: ClassVar[str] = "use_tool_from_server"
|
|
26
|
-
description: ClassVar[str] = (
|
|
27
|
-
"Execute a specific tool on a specific server without first connecting to it. "
|
|
28
|
-
"This is a direct execution shortcut that combines connection and tool execution "
|
|
29
|
-
"into a single step. Specify the server name, tool name, and the input to the tool."
|
|
30
|
-
)
|
|
31
|
-
args_schema: ClassVar[type[BaseModel]] = UseToolInput
|
|
32
|
-
|
|
33
|
-
async def _arun(self, server_name: str, tool_name: str, tool_input: dict[str, Any] | str) -> str:
|
|
34
|
-
"""Execute a tool from a specific server."""
|
|
35
|
-
# Check if server exists
|
|
36
|
-
servers = self.server_manager.client.get_server_names()
|
|
37
|
-
if server_name not in servers:
|
|
38
|
-
available = ", ".join(servers) if servers else "none"
|
|
39
|
-
return f"Server '{server_name}' not found. Available servers: {available}"
|
|
40
|
-
|
|
41
|
-
# Connect to the server if not already connected or not the active server
|
|
42
|
-
is_connected = server_name == self.server_manager.active_server
|
|
43
|
-
|
|
44
|
-
if not is_connected:
|
|
45
|
-
try:
|
|
46
|
-
# Create or get session for this server
|
|
47
|
-
try:
|
|
48
|
-
session = self.server_manager.client.get_session(server_name)
|
|
49
|
-
logger.debug(f"Using existing session for server '{server_name}'")
|
|
50
|
-
except ValueError:
|
|
51
|
-
logger.debug(f"Creating new session for server '{server_name}' for tool use")
|
|
52
|
-
session = await self.server_manager.client.create_session(server_name)
|
|
53
|
-
|
|
54
|
-
# Check if we have tools for this server, if not get them
|
|
55
|
-
if server_name not in self.server_manager._server_tools:
|
|
56
|
-
connector = session.connector
|
|
57
|
-
self.server_manager._server_tools[
|
|
58
|
-
server_name
|
|
59
|
-
] = await self.server_manager.adapter._create_tools_from_connectors([connector])
|
|
60
|
-
self.server_manager.initialized_servers[server_name] = True
|
|
61
|
-
except Exception as e:
|
|
62
|
-
logger.error(f"Error connecting to server '{server_name}' for tool use: {e}")
|
|
63
|
-
return f"Failed to connect to server '{server_name}': {str(e)}"
|
|
64
|
-
|
|
65
|
-
# Get tools for the server
|
|
66
|
-
server_tools = self.server_manager._server_tools.get(server_name, [])
|
|
67
|
-
if not server_tools:
|
|
68
|
-
return f"No tools found for server '{server_name}'"
|
|
69
|
-
|
|
70
|
-
# Find the requested tool
|
|
71
|
-
target_tool = None
|
|
72
|
-
for tool in server_tools:
|
|
73
|
-
if tool.name == tool_name:
|
|
74
|
-
target_tool = tool
|
|
75
|
-
break
|
|
76
|
-
|
|
77
|
-
if not target_tool:
|
|
78
|
-
tool_names = [t.name for t in server_tools]
|
|
79
|
-
return f"Tool '{tool_name}' not found on server '{server_name}'. Available tools: {', '.join(tool_names)}"
|
|
80
|
-
|
|
81
|
-
# Execute the tool with the provided input
|
|
82
|
-
try:
|
|
83
|
-
# Parse the input based on target tool's schema
|
|
84
|
-
structured_input = self._parse_tool_input(target_tool, tool_input)
|
|
85
|
-
if structured_input is None:
|
|
86
|
-
return f"Could not parse input for tool '{tool_name}'. Please check the input format and try again."
|
|
87
|
-
|
|
88
|
-
# Store the previous active server
|
|
89
|
-
previous_active = self.server_manager.active_server
|
|
90
|
-
|
|
91
|
-
# Temporarily set this server as active
|
|
92
|
-
self.server_manager.active_server = server_name
|
|
93
|
-
|
|
94
|
-
# Execute the tool
|
|
95
|
-
logger.info(f"Executing tool '{tool_name}' on server '{server_name}'with input: {{structured_input}}")
|
|
96
|
-
result = await target_tool._arun(**structured_input)
|
|
97
|
-
|
|
98
|
-
# Restore the previous active server
|
|
99
|
-
self.server_manager.active_server = previous_active
|
|
100
|
-
|
|
101
|
-
return result
|
|
102
|
-
|
|
103
|
-
except Exception as e:
|
|
104
|
-
logger.error(f"Error executing tool '{tool_name}' on server '{server_name}': {e}")
|
|
105
|
-
return (
|
|
106
|
-
f"Error executing tool '{tool_name}' on server '{server_name}': {str(e)}. "
|
|
107
|
-
f"Make sure the input format is correct for this tool."
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
def _parse_tool_input(self, tool: BaseTool, input_data: dict[str, Any] | str) -> dict[str, Any]:
|
|
111
|
-
"""
|
|
112
|
-
Parse the input data according to the tool's schema.
|
|
113
|
-
|
|
114
|
-
Args:
|
|
115
|
-
tool: The target tool
|
|
116
|
-
input_data: The input data, either a dictionary or a string
|
|
117
|
-
|
|
118
|
-
Returns:
|
|
119
|
-
A dictionary with properly structured input for the tool
|
|
120
|
-
"""
|
|
121
|
-
# If input is already a dict, use it directly
|
|
122
|
-
if isinstance(input_data, dict):
|
|
123
|
-
return input_data
|
|
124
|
-
|
|
125
|
-
# Try to parse as JSON first
|
|
126
|
-
if isinstance(input_data, str):
|
|
127
|
-
try:
|
|
128
|
-
return json.loads(input_data)
|
|
129
|
-
except json.JSONDecodeError:
|
|
130
|
-
pass
|
|
131
|
-
|
|
132
|
-
# For string input, we need to determine which parameter name to use
|
|
133
|
-
if hasattr(tool, "args_schema") and tool.args_schema:
|
|
134
|
-
schema_cls = tool.args_schema
|
|
135
|
-
field_names = list(schema_cls.__fields__.keys())
|
|
136
|
-
|
|
137
|
-
# If schema has only one field, use that
|
|
138
|
-
if len(field_names) == 1:
|
|
139
|
-
return {field_names[0]: input_data}
|
|
140
|
-
|
|
141
|
-
# Look for common input field names
|
|
142
|
-
for name in field_names:
|
|
143
|
-
if name.lower() in ["input", "query", "url", tool.name.lower()]:
|
|
144
|
-
return {name: input_data}
|
|
145
|
-
|
|
146
|
-
# Default to first field if we can't determine
|
|
147
|
-
return {field_names[0]: input_data}
|
|
148
|
-
|
|
149
|
-
# If we get here something went wrong
|
|
150
|
-
return None
|
|
151
|
-
|
|
152
|
-
def _run(self, server_name: str, tool_name: str, tool_input: dict[str, Any] | str) -> str:
|
|
153
|
-
"""Synchronous version that raises a NotImplementedError."""
|
|
154
|
-
raise NotImplementedError("UseToolFromServerTool requires async execution. Use _arun instead.")
|
|
File without changes
|
|
File without changes
|