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.

@@ -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
  ]
@@ -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], 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
- The final result from the generator.
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
- steps_taken += 1
334
- tools_used_names.append(item[0].tool)
335
- return final_result, steps_taken, tools_used_names
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
- ) -> AsyncGenerator[tuple[AgentAction, str] | str, None]:
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 as a string.
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
- break
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
- # Add the final response to conversation history if memory is enabled
502
- if self.memory_enabled:
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
- success = True
631
+ if not success:
632
+ success = True
507
633
 
508
- # Yield the final result as a string
509
- yield result
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
- ) -> str:
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
- generator = self.stream(query, max_steps, manage_connector, external_history, track_execution=False)
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, tools_used_names = await self._consume_and_return(generator)
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 to interact with MCP
21
- (Model Context Protocol) servers. You can manage connections to different servers and use the tools
22
- provided by the currently active server.
23
-
24
- Important: The available tools change depending on which server is active.
25
- If a request requires tools not listed below (e.g., file operations, web browsing,
26
- image manipulation), you MUST first connect to the appropriate server using
27
- 'connect_to_mcp_server'.
28
- Use 'list_mcp_servers' to find the relevant server if you are unsure.
29
- Only after successfully connecting and seeing the new tools listed in
30
- the response should you attempt to use those server-specific tools.
31
- Before attempting a task that requires specific tools, you should
32
- ensure you are connected to the correct server and aware of its
33
- available tools. If unsure, use 'list_mcp_servers' to see options
34
- or 'get_active_mcp_server' to check the current connection.
35
-
36
- When you connect to a server using 'connect_to_mcp_server',
37
- you will be informed about the new tools that become available.
38
- You can then use these server-specific tools in subsequent steps.
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
  """
@@ -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
- await self.create_session(name, auto_initialize)
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
  )
@@ -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
- "ListServersTool",
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
- @property
77
- def tools(self) -> list[BaseTool]:
78
- """Get all server management tools.
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
- list of LangChain tools for server management
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.7
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.9.3
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
- - Visit the [mcp-use.com website](https://mcp-use.com/) to know how to build and deploy MCP agents.
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>⭐ 112</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>⭐ 37</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>⭐ 29</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>⭐ 21</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>⭐ 18</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>⭐ 17</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>⭐ 6</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=ka9gE_GQj9a1fcWfz-hrAzYwSN5I1Y1JXo2Tq0uRCOU,10718
3
- mcp_use/config.py,sha256=zLw8F9bBi4hMmKmjxMIS47GIlHWmP4r35A-_K5c7Kqk,2974
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=N3eVYP2PxqNO2KcQv5fY8UMUX2W3eLTNkkzuFIJ1DUA,261
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=K3gKlPvqIPiniifi46Xs82_D2YKNO-Vn4BTTH55LPbI,35753
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=AZKrGWuI516C-PmyOPvxDBibNdqJtN24sOHTGR06bi4,1933
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=rzsJbOhtlmxNQLGcdmtmHaiExEXmiQiUuzPrAgKhAJw,439
23
- mcp_use/managers/server_manager.py,sha256=Ag1EUSxkG6-UG004vcvaZarPoDnUUMMX7BUeKReUWRI,3704
24
- mcp_use/managers/tools/__init__.py,sha256=JrA5iTRdtbgwROJE8pQ7GH1sYnqBRcgj4NzFVADKbQ4,510
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.7.dist-info/METADATA,sha256=ZElQuSVQ92ph2WHTWsEPPPIFGpwHjMHa4MZ6gDNp-KY,30337
47
- mcp_use-1.3.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
48
- mcp_use-1.3.7.dist-info/licenses/LICENSE,sha256=7Pw7dbwJSBw8zH-WE03JnR5uXvitRtaGTP9QWPcexcs,1068
49
- mcp_use-1.3.7.dist-info/RECORD,,
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.")