mcp-use 1.3.0__py3-none-any.whl → 1.3.1__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/mcpagent.py +109 -10
- mcp_use/client.py +28 -11
- mcp_use/config.py +9 -9
- mcp_use/connectors/base.py +135 -37
- mcp_use/connectors/http.py +108 -30
- mcp_use/connectors/sandbox.py +6 -1
- mcp_use/connectors/stdio.py +7 -2
- mcp_use/connectors/websocket.py +7 -2
- mcp_use/session.py +1 -4
- mcp_use/task_managers/__init__.py +2 -1
- mcp_use/task_managers/base.py +10 -4
- mcp_use/task_managers/streamable_http.py +81 -0
- mcp_use/task_managers/websocket.py +5 -0
- mcp_use/telemetry/__init__.py +0 -0
- mcp_use/telemetry/events.py +93 -0
- mcp_use/telemetry/posthog.py +214 -0
- mcp_use/telemetry/utils.py +48 -0
- mcp_use/utils.py +27 -0
- {mcp_use-1.3.0.dist-info → mcp_use-1.3.1.dist-info}/METADATA +73 -23
- {mcp_use-1.3.0.dist-info → mcp_use-1.3.1.dist-info}/RECORD +22 -17
- mcp_use/types/clientoptions.py +0 -23
- {mcp_use-1.3.0.dist-info → mcp_use-1.3.1.dist-info}/WHEEL +0 -0
- {mcp_use-1.3.0.dist-info → mcp_use-1.3.1.dist-info}/licenses/LICENSE +0 -0
mcp_use/agents/mcpagent.py
CHANGED
|
@@ -6,6 +6,7 @@ to provide a simple interface for using MCP tools with different LLMs.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
|
+
import time
|
|
9
10
|
from collections.abc import AsyncIterator
|
|
10
11
|
|
|
11
12
|
from langchain.agents import AgentExecutor, create_tool_calling_agent
|
|
@@ -22,6 +23,8 @@ from langchain_core.utils.input import get_color_mapping
|
|
|
22
23
|
|
|
23
24
|
from mcp_use.client import MCPClient
|
|
24
25
|
from mcp_use.connectors.base import BaseConnector
|
|
26
|
+
from mcp_use.telemetry.posthog import Telemetry
|
|
27
|
+
from mcp_use.telemetry.utils import extract_model_info
|
|
25
28
|
|
|
26
29
|
from ..adapters.langchain_adapter import LangChainAdapter
|
|
27
30
|
from ..logging import logger
|
|
@@ -93,6 +96,9 @@ class MCPAgent:
|
|
|
93
96
|
# Create the adapter for tool conversion
|
|
94
97
|
self.adapter = LangChainAdapter(disallowed_tools=self.disallowed_tools)
|
|
95
98
|
|
|
99
|
+
# Initialize telemetry
|
|
100
|
+
self.telemetry = Telemetry()
|
|
101
|
+
|
|
96
102
|
# Initialize server manager if requested
|
|
97
103
|
self.server_manager = None
|
|
98
104
|
if self.use_server_manager:
|
|
@@ -104,6 +110,9 @@ class MCPAgent:
|
|
|
104
110
|
self._agent_executor: AgentExecutor | None = None
|
|
105
111
|
self._system_message: SystemMessage | None = None
|
|
106
112
|
|
|
113
|
+
# Track model info for telemetry
|
|
114
|
+
self._model_provider, self._model_name = extract_model_info(self.llm)
|
|
115
|
+
|
|
107
116
|
async def initialize(self) -> None:
|
|
108
117
|
"""Initialize the MCP client and agent."""
|
|
109
118
|
logger.info("🚀 Initializing MCP agent and connecting to services...")
|
|
@@ -130,6 +139,7 @@ class MCPAgent:
|
|
|
130
139
|
if not self._sessions:
|
|
131
140
|
logger.info("🔄 No active sessions found, creating new ones...")
|
|
132
141
|
self._sessions = await self.client.create_all_sessions()
|
|
142
|
+
self.connectors = [session.connector for session in self._sessions.values()]
|
|
133
143
|
logger.info(f"✅ Created {len(self._sessions)} new sessions")
|
|
134
144
|
|
|
135
145
|
# Create LangChain tools directly from the client using the adapter
|
|
@@ -141,7 +151,7 @@ class MCPAgent:
|
|
|
141
151
|
connectors_to_use = self.connectors
|
|
142
152
|
logger.info(f"🔗 Connecting to {len(connectors_to_use)} direct connectors...")
|
|
143
153
|
for connector in connectors_to_use:
|
|
144
|
-
if not hasattr(connector, "
|
|
154
|
+
if not hasattr(connector, "client_session") or connector.client_session is None:
|
|
145
155
|
await connector.connect()
|
|
146
156
|
|
|
147
157
|
# Create LangChain tools using the adapter with connectors
|
|
@@ -374,13 +384,58 @@ class MCPAgent:
|
|
|
374
384
|
async for chunk in agent.astream("hello"):
|
|
375
385
|
print(chunk, end="|", flush=True)
|
|
376
386
|
"""
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
387
|
+
start_time = time.time()
|
|
388
|
+
success = False
|
|
389
|
+
chunk_count = 0
|
|
390
|
+
total_response_length = 0
|
|
391
|
+
|
|
392
|
+
try:
|
|
393
|
+
async for chunk in self._generate_response_chunks_async(
|
|
394
|
+
query=query,
|
|
395
|
+
max_steps=max_steps,
|
|
396
|
+
manage_connector=manage_connector,
|
|
397
|
+
external_history=external_history,
|
|
398
|
+
):
|
|
399
|
+
chunk_count += 1
|
|
400
|
+
if isinstance(chunk, str):
|
|
401
|
+
total_response_length += len(chunk)
|
|
402
|
+
yield chunk
|
|
403
|
+
success = True
|
|
404
|
+
finally:
|
|
405
|
+
# Track comprehensive execution data for streaming
|
|
406
|
+
execution_time_ms = int((time.time() - start_time) * 1000)
|
|
407
|
+
|
|
408
|
+
server_count = 0
|
|
409
|
+
if self.client:
|
|
410
|
+
server_count = len(self.client.get_all_active_sessions())
|
|
411
|
+
elif self.connectors:
|
|
412
|
+
server_count = len(self.connectors)
|
|
413
|
+
|
|
414
|
+
conversation_history_length = (
|
|
415
|
+
len(self._conversation_history) if self.memory_enabled else 0
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
self.telemetry.track_agent_execution(
|
|
419
|
+
execution_method="astream",
|
|
420
|
+
query=query,
|
|
421
|
+
success=success,
|
|
422
|
+
model_provider=self._model_provider,
|
|
423
|
+
model_name=self._model_name,
|
|
424
|
+
server_count=server_count,
|
|
425
|
+
server_identifiers=[connector.public_identifier for connector in self.connectors],
|
|
426
|
+
total_tools_available=len(self._tools) if self._tools else 0,
|
|
427
|
+
tools_available_names=[tool.name for tool in self._tools],
|
|
428
|
+
max_steps_configured=self.max_steps,
|
|
429
|
+
memory_enabled=self.memory_enabled,
|
|
430
|
+
use_server_manager=self.use_server_manager,
|
|
431
|
+
max_steps_used=max_steps,
|
|
432
|
+
manage_connector=manage_connector,
|
|
433
|
+
external_history_used=external_history is not None,
|
|
434
|
+
response=f"[STREAMED RESPONSE - {total_response_length} chars]",
|
|
435
|
+
execution_time_ms=execution_time_ms,
|
|
436
|
+
error_type=None if success else "streaming_error",
|
|
437
|
+
conversation_history_length=conversation_history_length,
|
|
438
|
+
)
|
|
384
439
|
|
|
385
440
|
async def run(
|
|
386
441
|
self,
|
|
@@ -409,6 +464,10 @@ class MCPAgent:
|
|
|
409
464
|
"""
|
|
410
465
|
result = ""
|
|
411
466
|
initialized_here = False
|
|
467
|
+
start_time = time.time()
|
|
468
|
+
tools_used_names = []
|
|
469
|
+
steps_taken = 0
|
|
470
|
+
success = False
|
|
412
471
|
|
|
413
472
|
try:
|
|
414
473
|
# Initialize if needed
|
|
@@ -464,6 +523,7 @@ class MCPAgent:
|
|
|
464
523
|
logger.info(f"🏁 Starting agent execution with max_steps={steps}")
|
|
465
524
|
|
|
466
525
|
for step_num in range(steps):
|
|
526
|
+
steps_taken = step_num + 1
|
|
467
527
|
# --- Check for tool updates if using server manager ---
|
|
468
528
|
if self.use_server_manager and self.server_manager:
|
|
469
529
|
current_tools = self.server_manager.tools
|
|
@@ -510,9 +570,10 @@ class MCPAgent:
|
|
|
510
570
|
# If it's actions/steps, add to intermediate steps
|
|
511
571
|
intermediate_steps.extend(next_step_output)
|
|
512
572
|
|
|
513
|
-
# Log tool calls
|
|
573
|
+
# Log tool calls and track tool usage
|
|
514
574
|
for action, output in next_step_output:
|
|
515
575
|
tool_name = action.tool
|
|
576
|
+
tools_used_names.append(tool_name)
|
|
516
577
|
tool_input_str = str(action.tool_input)
|
|
517
578
|
# Truncate long inputs for readability
|
|
518
579
|
if len(tool_input_str) > 100:
|
|
@@ -555,7 +616,8 @@ class MCPAgent:
|
|
|
555
616
|
if self.memory_enabled:
|
|
556
617
|
self.add_to_history(AIMessage(content=result))
|
|
557
618
|
|
|
558
|
-
logger.info("🎉 Agent execution complete")
|
|
619
|
+
logger.info(f"🎉 Agent execution complete in {time.time() - start_time} seconds")
|
|
620
|
+
success = True
|
|
559
621
|
return result
|
|
560
622
|
|
|
561
623
|
except Exception as e:
|
|
@@ -566,6 +628,43 @@ class MCPAgent:
|
|
|
566
628
|
raise
|
|
567
629
|
|
|
568
630
|
finally:
|
|
631
|
+
# Track comprehensive execution data
|
|
632
|
+
execution_time_ms = int((time.time() - start_time) * 1000)
|
|
633
|
+
|
|
634
|
+
server_count = 0
|
|
635
|
+
if self.client:
|
|
636
|
+
server_count = len(self.client.get_all_active_sessions())
|
|
637
|
+
elif self.connectors:
|
|
638
|
+
server_count = len(self.connectors)
|
|
639
|
+
|
|
640
|
+
conversation_history_length = (
|
|
641
|
+
len(self._conversation_history) if self.memory_enabled else 0
|
|
642
|
+
)
|
|
643
|
+
self.telemetry.track_agent_execution(
|
|
644
|
+
execution_method="run",
|
|
645
|
+
query=query,
|
|
646
|
+
success=success,
|
|
647
|
+
model_provider=self._model_provider,
|
|
648
|
+
model_name=self._model_name,
|
|
649
|
+
server_count=server_count,
|
|
650
|
+
server_identifiers=[connector.public_identifier for connector in self.connectors],
|
|
651
|
+
total_tools_available=len(self._tools) if self._tools else 0,
|
|
652
|
+
tools_available_names=[tool.name for tool in self._tools],
|
|
653
|
+
max_steps_configured=self.max_steps,
|
|
654
|
+
memory_enabled=self.memory_enabled,
|
|
655
|
+
use_server_manager=self.use_server_manager,
|
|
656
|
+
max_steps_used=max_steps,
|
|
657
|
+
manage_connector=manage_connector,
|
|
658
|
+
external_history_used=external_history is not None,
|
|
659
|
+
steps_taken=steps_taken,
|
|
660
|
+
tools_used_count=len(tools_used_names),
|
|
661
|
+
tools_used_names=tools_used_names,
|
|
662
|
+
response=result,
|
|
663
|
+
execution_time_ms=execution_time_ms,
|
|
664
|
+
error_type=None if success else "execution_error",
|
|
665
|
+
conversation_history_length=conversation_history_length,
|
|
666
|
+
)
|
|
667
|
+
|
|
569
668
|
# Clean up if necessary (e.g., if not using client-managed sessions)
|
|
570
669
|
if manage_connector and not self.client and not initialized_here:
|
|
571
670
|
logger.info("🧹 Closing agent after query completion")
|
mcp_use/client.py
CHANGED
|
@@ -9,10 +9,11 @@ import json
|
|
|
9
9
|
import warnings
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
+
from mcp_use.types.sandbox import SandboxOptions
|
|
13
|
+
|
|
12
14
|
from .config import create_connector_from_config, load_config_file
|
|
13
15
|
from .logging import logger
|
|
14
16
|
from .session import MCPSession
|
|
15
|
-
from .types.clientoptions import ClientOptions
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
class MCPClient:
|
|
@@ -25,17 +26,20 @@ class MCPClient:
|
|
|
25
26
|
def __init__(
|
|
26
27
|
self,
|
|
27
28
|
config: str | dict[str, Any] | None = None,
|
|
28
|
-
|
|
29
|
+
sandbox: bool = False,
|
|
30
|
+
sandbox_options: SandboxOptions | None = None,
|
|
29
31
|
) -> None:
|
|
30
32
|
"""Initialize a new MCP client.
|
|
31
33
|
|
|
32
34
|
Args:
|
|
33
35
|
config: Either a dict containing configuration or a path to a JSON config file.
|
|
34
36
|
If None, an empty configuration is used.
|
|
35
|
-
|
|
37
|
+
sandbox: Whether to use sandboxed execution mode for running MCP servers.
|
|
38
|
+
sandbox_options: Optional sandbox configuration options.
|
|
36
39
|
"""
|
|
37
40
|
self.config: dict[str, Any] = {}
|
|
38
|
-
self.
|
|
41
|
+
self.sandbox = sandbox
|
|
42
|
+
self.sandbox_options = sandbox_options
|
|
39
43
|
self.sessions: dict[str, MCPSession] = {}
|
|
40
44
|
self.active_sessions: list[str] = []
|
|
41
45
|
|
|
@@ -47,24 +51,35 @@ class MCPClient:
|
|
|
47
51
|
self.config = config
|
|
48
52
|
|
|
49
53
|
@classmethod
|
|
50
|
-
def from_dict(
|
|
54
|
+
def from_dict(
|
|
55
|
+
cls,
|
|
56
|
+
config: dict[str, Any],
|
|
57
|
+
sandbox: bool = False,
|
|
58
|
+
sandbox_options: SandboxOptions | None = None,
|
|
59
|
+
) -> "MCPClient":
|
|
51
60
|
"""Create a MCPClient from a dictionary.
|
|
52
61
|
|
|
53
62
|
Args:
|
|
54
63
|
config: The configuration dictionary.
|
|
55
|
-
|
|
64
|
+
sandbox: Whether to use sandboxed execution mode for running MCP servers.
|
|
65
|
+
sandbox_options: Optional sandbox configuration options.
|
|
56
66
|
"""
|
|
57
|
-
return cls(config=config,
|
|
67
|
+
return cls(config=config, sandbox=sandbox, sandbox_options=sandbox_options)
|
|
58
68
|
|
|
59
69
|
@classmethod
|
|
60
|
-
def from_config_file(
|
|
70
|
+
def from_config_file(
|
|
71
|
+
cls, filepath: str, sandbox: bool = False, sandbox_options: SandboxOptions | None = None
|
|
72
|
+
) -> "MCPClient":
|
|
61
73
|
"""Create a MCPClient from a configuration file.
|
|
62
74
|
|
|
63
75
|
Args:
|
|
64
76
|
filepath: The path to the configuration file.
|
|
65
|
-
|
|
77
|
+
sandbox: Whether to use sandboxed execution mode for running MCP servers.
|
|
78
|
+
sandbox_options: Optional sandbox configuration options.
|
|
66
79
|
"""
|
|
67
|
-
return cls(
|
|
80
|
+
return cls(
|
|
81
|
+
config=load_config_file(filepath), sandbox=sandbox, sandbox_options=sandbox_options
|
|
82
|
+
)
|
|
68
83
|
|
|
69
84
|
def add_server(
|
|
70
85
|
self,
|
|
@@ -137,7 +152,9 @@ class MCPClient:
|
|
|
137
152
|
server_config = servers[server_name]
|
|
138
153
|
|
|
139
154
|
# Create connector with options
|
|
140
|
-
connector = create_connector_from_config(
|
|
155
|
+
connector = create_connector_from_config(
|
|
156
|
+
server_config, sandbox=self.sandbox, sandbox_options=self.sandbox_options
|
|
157
|
+
)
|
|
141
158
|
|
|
142
159
|
# Create the session
|
|
143
160
|
session = MCPSession(connector)
|
mcp_use/config.py
CHANGED
|
@@ -7,6 +7,8 @@ This module provides functionality to load MCP configuration from JSON files.
|
|
|
7
7
|
import json
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
+
from mcp_use.types.sandbox import SandboxOptions
|
|
11
|
+
|
|
10
12
|
from .connectors import (
|
|
11
13
|
BaseConnector,
|
|
12
14
|
HttpConnector,
|
|
@@ -15,7 +17,6 @@ from .connectors import (
|
|
|
15
17
|
WebSocketConnector,
|
|
16
18
|
)
|
|
17
19
|
from .connectors.utils import is_stdio_server
|
|
18
|
-
from .types.clientoptions import ClientOptions
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def load_config_file(filepath: str) -> dict[str, Any]:
|
|
@@ -33,24 +34,23 @@ def load_config_file(filepath: str) -> dict[str, Any]:
|
|
|
33
34
|
|
|
34
35
|
def create_connector_from_config(
|
|
35
36
|
server_config: dict[str, Any],
|
|
36
|
-
|
|
37
|
+
sandbox: bool = False,
|
|
38
|
+
sandbox_options: SandboxOptions | None = None,
|
|
37
39
|
) -> BaseConnector:
|
|
38
40
|
"""Create a connector based on server configuration.
|
|
39
41
|
This function can be called with just the server_config parameter:
|
|
40
42
|
create_connector_from_config(server_config)
|
|
41
43
|
Args:
|
|
42
44
|
server_config: The server configuration section
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
sandbox: Whether to use sandboxed execution mode for running MCP servers.
|
|
46
|
+
sandbox_options: Optional sandbox configuration options.
|
|
45
47
|
|
|
46
48
|
Returns:
|
|
47
49
|
A configured connector instance
|
|
48
50
|
"""
|
|
49
|
-
# Use default options if none provided
|
|
50
|
-
options = options or {"is_sandboxed": False}
|
|
51
51
|
|
|
52
52
|
# Stdio connector (command-based)
|
|
53
|
-
if is_stdio_server(server_config) and not
|
|
53
|
+
if is_stdio_server(server_config) and not sandbox:
|
|
54
54
|
return StdioConnector(
|
|
55
55
|
command=server_config["command"],
|
|
56
56
|
args=server_config["args"],
|
|
@@ -58,12 +58,12 @@ def create_connector_from_config(
|
|
|
58
58
|
)
|
|
59
59
|
|
|
60
60
|
# Sandboxed connector
|
|
61
|
-
elif is_stdio_server(server_config) and
|
|
61
|
+
elif is_stdio_server(server_config) and sandbox:
|
|
62
62
|
return SandboxConnector(
|
|
63
63
|
command=server_config["command"],
|
|
64
64
|
args=server_config["args"],
|
|
65
65
|
env=server_config.get("env", None),
|
|
66
|
-
e2b_options=
|
|
66
|
+
e2b_options=sandbox_options,
|
|
67
67
|
)
|
|
68
68
|
|
|
69
69
|
# HTTP connector
|
mcp_use/connectors/base.py
CHANGED
|
@@ -24,18 +24,25 @@ class BaseConnector(ABC):
|
|
|
24
24
|
|
|
25
25
|
def __init__(self):
|
|
26
26
|
"""Initialize base connector with common attributes."""
|
|
27
|
-
self.
|
|
27
|
+
self.client_session: ClientSession | None = None
|
|
28
28
|
self._connection_manager: ConnectionManager | None = None
|
|
29
29
|
self._tools: list[Tool] | None = None
|
|
30
30
|
self._resources: list[Resource] | None = None
|
|
31
31
|
self._prompts: list[Prompt] | None = None
|
|
32
32
|
self._connected = False
|
|
33
|
+
self.auto_reconnect = True # Whether to automatically reconnect on connection loss (not configurable for now), may be made configurable through the connector_config
|
|
33
34
|
|
|
34
35
|
@abstractmethod
|
|
35
36
|
async def connect(self) -> None:
|
|
36
37
|
"""Establish a connection to the MCP implementation."""
|
|
37
38
|
pass
|
|
38
39
|
|
|
40
|
+
@property
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def public_identifier(self) -> str:
|
|
43
|
+
"""Get the identifier for the connector."""
|
|
44
|
+
pass
|
|
45
|
+
|
|
39
46
|
async def disconnect(self) -> None:
|
|
40
47
|
"""Close the connection to the MCP implementation."""
|
|
41
48
|
if not self._connected:
|
|
@@ -52,16 +59,16 @@ class BaseConnector(ABC):
|
|
|
52
59
|
errors = []
|
|
53
60
|
|
|
54
61
|
# First close the client session
|
|
55
|
-
if self.
|
|
62
|
+
if self.client_session:
|
|
56
63
|
try:
|
|
57
64
|
logger.debug("Closing client session")
|
|
58
|
-
await self.
|
|
65
|
+
await self.client_session.__aexit__(None, None, None)
|
|
59
66
|
except Exception as e:
|
|
60
67
|
error_msg = f"Error closing client session: {e}"
|
|
61
68
|
logger.warning(error_msg)
|
|
62
69
|
errors.append(error_msg)
|
|
63
70
|
finally:
|
|
64
|
-
self.
|
|
71
|
+
self.client_session = None
|
|
65
72
|
|
|
66
73
|
# Then stop the connection manager
|
|
67
74
|
if self._connection_manager:
|
|
@@ -85,13 +92,13 @@ class BaseConnector(ABC):
|
|
|
85
92
|
|
|
86
93
|
async def initialize(self) -> dict[str, Any]:
|
|
87
94
|
"""Initialize the MCP session and return session information."""
|
|
88
|
-
if not self.
|
|
95
|
+
if not self.client_session:
|
|
89
96
|
raise RuntimeError("MCP client is not connected")
|
|
90
97
|
|
|
91
98
|
logger.debug("Initializing MCP session")
|
|
92
99
|
|
|
93
100
|
# Initialize the session
|
|
94
|
-
result = await self.
|
|
101
|
+
result = await self.client_session.initialize()
|
|
95
102
|
|
|
96
103
|
server_capabilities = result.capabilities
|
|
97
104
|
|
|
@@ -116,11 +123,7 @@ class BaseConnector(ABC):
|
|
|
116
123
|
else:
|
|
117
124
|
self._prompts = []
|
|
118
125
|
|
|
119
|
-
logger.debug(
|
|
120
|
-
f"MCP session initialized with {len(self._tools)} tools, "
|
|
121
|
-
f"{len(self._resources)} resources, "
|
|
122
|
-
f"and {len(self._prompts)} prompts"
|
|
123
|
-
)
|
|
126
|
+
logger.debug(f"MCP session initialized with {len(self._tools)} tools, " f"{len(self._resources)} resources, " f"and {len(self._prompts)} prompts")
|
|
124
127
|
|
|
125
128
|
return result
|
|
126
129
|
|
|
@@ -145,24 +148,121 @@ class BaseConnector(ABC):
|
|
|
145
148
|
raise RuntimeError("MCP client is not initialized")
|
|
146
149
|
return self._prompts
|
|
147
150
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if
|
|
151
|
+
@property
|
|
152
|
+
def is_connected(self) -> bool:
|
|
153
|
+
"""Check if the connector is actually connected and the connection is alive.
|
|
154
|
+
|
|
155
|
+
This property checks not only the connected flag but also verifies that
|
|
156
|
+
the underlying connection manager and streams are still active.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
True if the connector is connected and the connection is alive, False otherwise.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
# Check if we have a client session
|
|
163
|
+
if not self.client_session:
|
|
164
|
+
# Update the connected flag since we don't have a client session
|
|
165
|
+
self._connected = False
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
# First check the basic connected flag
|
|
169
|
+
if not self._connected:
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
# Check if we have a connection manager and if its task is still running
|
|
173
|
+
if self._connection_manager:
|
|
174
|
+
try:
|
|
175
|
+
# Check if the connection manager task is done (indicates disconnection)
|
|
176
|
+
if hasattr(self._connection_manager, "_task") and self._connection_manager._task:
|
|
177
|
+
if self._connection_manager._task.done():
|
|
178
|
+
logger.debug("Connection manager task is done, marking as disconnected")
|
|
179
|
+
self._connected = False
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
# For HTTP-based connectors, also check if streams are still open
|
|
183
|
+
# Use the get_streams method to get the current connection
|
|
184
|
+
streams = self._connection_manager.get_streams()
|
|
185
|
+
if streams:
|
|
186
|
+
# Connection should be a tuple of (read_stream, write_stream)
|
|
187
|
+
if isinstance(streams, tuple) and len(streams) == 2:
|
|
188
|
+
read_stream, write_stream = streams
|
|
189
|
+
# Check if streams are closed using getattr with default value
|
|
190
|
+
if getattr(read_stream, "_closed", False):
|
|
191
|
+
logger.debug("Read stream is closed, marking as disconnected")
|
|
192
|
+
self._connected = False
|
|
193
|
+
return False
|
|
194
|
+
if getattr(write_stream, "_closed", False):
|
|
195
|
+
logger.debug("Write stream is closed, marking as disconnected")
|
|
196
|
+
self._connected = False
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
except Exception as e:
|
|
200
|
+
# If we can't check the connection state, assume disconnected for safety
|
|
201
|
+
logger.debug(f"Error checking connection state: {e}, marking as disconnected")
|
|
202
|
+
self._connected = False
|
|
203
|
+
return False
|
|
204
|
+
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
async def _ensure_connected(self) -> None:
|
|
208
|
+
"""Ensure the connector is connected, reconnecting if necessary.
|
|
209
|
+
|
|
210
|
+
Raises:
|
|
211
|
+
RuntimeError: If connection cannot be established and auto_reconnect is False.
|
|
212
|
+
"""
|
|
213
|
+
if not self.client_session:
|
|
151
214
|
raise RuntimeError("MCP client is not connected")
|
|
152
215
|
|
|
216
|
+
if not self.is_connected:
|
|
217
|
+
if self.auto_reconnect:
|
|
218
|
+
logger.debug("Connection lost, attempting to reconnect...")
|
|
219
|
+
try:
|
|
220
|
+
await self.connect()
|
|
221
|
+
logger.debug("Reconnection successful")
|
|
222
|
+
except Exception as e:
|
|
223
|
+
raise RuntimeError(f"Failed to reconnect to MCP server: {e}") from e
|
|
224
|
+
else:
|
|
225
|
+
raise RuntimeError("Connection to MCP server has been lost. " "Auto-reconnection is disabled. Please reconnect manually.")
|
|
226
|
+
|
|
227
|
+
async def call_tool(self, name: str, arguments: dict[str, Any]) -> CallToolResult:
|
|
228
|
+
"""Call an MCP tool with automatic reconnection handling.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
name: The name of the tool to call.
|
|
232
|
+
arguments: The arguments to pass to the tool.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
The result of the tool call.
|
|
236
|
+
|
|
237
|
+
Raises:
|
|
238
|
+
RuntimeError: If the connection is lost and cannot be reestablished.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
# Ensure we're connected
|
|
242
|
+
await self._ensure_connected()
|
|
243
|
+
|
|
153
244
|
logger.debug(f"Calling tool '{name}' with arguments: {arguments}")
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
245
|
+
try:
|
|
246
|
+
result = await self.client_session.call_tool(name, arguments)
|
|
247
|
+
logger.debug(f"Tool '{name}' called with result: {result}")
|
|
248
|
+
return result
|
|
249
|
+
except Exception as e:
|
|
250
|
+
# Check if the error might be due to connection loss
|
|
251
|
+
if not self.is_connected:
|
|
252
|
+
raise RuntimeError(f"Tool call '{name}' failed due to connection loss: {e}") from e
|
|
253
|
+
else:
|
|
254
|
+
# Re-raise the original error if it's not connection-related
|
|
255
|
+
raise
|
|
157
256
|
|
|
158
257
|
async def list_tools(self) -> list[Tool]:
|
|
159
258
|
"""List all available tools from the MCP implementation."""
|
|
160
|
-
|
|
161
|
-
|
|
259
|
+
|
|
260
|
+
# Ensure we're connected
|
|
261
|
+
await self._ensure_connected()
|
|
162
262
|
|
|
163
263
|
logger.debug("Listing tools")
|
|
164
264
|
try:
|
|
165
|
-
result = await self.
|
|
265
|
+
result = await self.client_session.list_tools()
|
|
166
266
|
return result.tools
|
|
167
267
|
except McpError as e:
|
|
168
268
|
logger.error(f"Error listing tools: {e}")
|
|
@@ -170,12 +270,12 @@ class BaseConnector(ABC):
|
|
|
170
270
|
|
|
171
271
|
async def list_resources(self) -> list[Resource]:
|
|
172
272
|
"""List all available resources from the MCP implementation."""
|
|
173
|
-
|
|
174
|
-
|
|
273
|
+
# Ensure we're connected
|
|
274
|
+
await self._ensure_connected()
|
|
175
275
|
|
|
176
276
|
logger.debug("Listing resources")
|
|
177
277
|
try:
|
|
178
|
-
result = await self.
|
|
278
|
+
result = await self.client_session.list_resources()
|
|
179
279
|
return result.resources
|
|
180
280
|
except McpError as e:
|
|
181
281
|
logger.error(f"Error listing resources: {e}")
|
|
@@ -183,41 +283,39 @@ class BaseConnector(ABC):
|
|
|
183
283
|
|
|
184
284
|
async def read_resource(self, uri: str) -> ReadResourceResult:
|
|
185
285
|
"""Read a resource by URI."""
|
|
186
|
-
if not self.
|
|
286
|
+
if not self.client_session:
|
|
187
287
|
raise RuntimeError("MCP client is not connected")
|
|
188
288
|
|
|
189
289
|
logger.debug(f"Reading resource: {uri}")
|
|
190
|
-
result = await self.
|
|
290
|
+
result = await self.client_session.read_resource(uri)
|
|
191
291
|
return result
|
|
192
292
|
|
|
193
293
|
async def list_prompts(self) -> list[Prompt]:
|
|
194
294
|
"""List all available prompts from the MCP implementation."""
|
|
195
|
-
|
|
196
|
-
|
|
295
|
+
# Ensure we're connected
|
|
296
|
+
await self._ensure_connected()
|
|
197
297
|
|
|
198
298
|
logger.debug("Listing prompts")
|
|
199
299
|
try:
|
|
200
|
-
result = await self.
|
|
300
|
+
result = await self.client_session.list_prompts()
|
|
201
301
|
return result.prompts
|
|
202
302
|
except McpError as e:
|
|
203
303
|
logger.error(f"Error listing prompts: {e}")
|
|
204
304
|
return []
|
|
205
305
|
|
|
206
|
-
async def get_prompt(
|
|
207
|
-
self, name: str, arguments: dict[str, Any] | None = None
|
|
208
|
-
) -> GetPromptResult:
|
|
306
|
+
async def get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> GetPromptResult:
|
|
209
307
|
"""Get a prompt by name."""
|
|
210
|
-
|
|
211
|
-
|
|
308
|
+
# Ensure we're connected
|
|
309
|
+
await self._ensure_connected()
|
|
212
310
|
|
|
213
311
|
logger.debug(f"Getting prompt: {name}")
|
|
214
|
-
result = await self.
|
|
312
|
+
result = await self.client_session.get_prompt(name, arguments)
|
|
215
313
|
return result
|
|
216
314
|
|
|
217
315
|
async def request(self, method: str, params: dict[str, Any] | None = None) -> Any:
|
|
218
316
|
"""Send a raw request to the MCP implementation."""
|
|
219
|
-
|
|
220
|
-
|
|
317
|
+
# Ensure we're connected
|
|
318
|
+
await self._ensure_connected()
|
|
221
319
|
|
|
222
320
|
logger.debug(f"Sending request: {method} with params: {params}")
|
|
223
|
-
return await self.
|
|
321
|
+
return await self.client_session.request({"method": method, "params": params or {}})
|