mcp-use 1.3.11__py3-none-any.whl → 1.3.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mcp-use might be problematic. Click here for more details.
- mcp_use/__init__.py +1 -1
- mcp_use/adapters/.deprecated +0 -0
- mcp_use/adapters/__init__.py +18 -7
- mcp_use/adapters/base.py +12 -185
- mcp_use/adapters/langchain_adapter.py +12 -264
- mcp_use/agents/adapters/__init__.py +10 -0
- mcp_use/agents/adapters/base.py +193 -0
- mcp_use/agents/adapters/langchain_adapter.py +228 -0
- mcp_use/agents/base.py +1 -1
- mcp_use/agents/managers/__init__.py +19 -0
- mcp_use/agents/managers/base.py +36 -0
- mcp_use/agents/managers/server_manager.py +131 -0
- mcp_use/agents/managers/tools/__init__.py +15 -0
- mcp_use/agents/managers/tools/base_tool.py +19 -0
- mcp_use/agents/managers/tools/connect_server.py +69 -0
- mcp_use/agents/managers/tools/disconnect_server.py +43 -0
- mcp_use/agents/managers/tools/get_active_server.py +29 -0
- mcp_use/agents/managers/tools/list_servers_tool.py +53 -0
- mcp_use/agents/managers/tools/search_tools.py +328 -0
- mcp_use/agents/mcpagent.py +88 -47
- mcp_use/agents/remote.py +168 -129
- mcp_use/auth/.deprecated +0 -0
- mcp_use/auth/__init__.py +19 -4
- mcp_use/auth/bearer.py +11 -12
- mcp_use/auth/oauth.py +11 -620
- mcp_use/auth/oauth_callback.py +16 -207
- mcp_use/client/__init__.py +1 -0
- mcp_use/client/auth/__init__.py +6 -0
- mcp_use/client/auth/bearer.py +23 -0
- mcp_use/client/auth/oauth.py +629 -0
- mcp_use/client/auth/oauth_callback.py +214 -0
- mcp_use/client/client.py +356 -0
- mcp_use/client/config.py +106 -0
- mcp_use/client/connectors/__init__.py +20 -0
- mcp_use/client/connectors/base.py +470 -0
- mcp_use/client/connectors/http.py +304 -0
- mcp_use/client/connectors/sandbox.py +332 -0
- mcp_use/client/connectors/stdio.py +109 -0
- mcp_use/client/connectors/utils.py +13 -0
- mcp_use/client/connectors/websocket.py +257 -0
- mcp_use/client/exceptions.py +31 -0
- mcp_use/client/middleware/__init__.py +50 -0
- mcp_use/client/middleware/logging.py +31 -0
- mcp_use/client/middleware/metrics.py +314 -0
- mcp_use/client/middleware/middleware.py +266 -0
- mcp_use/client/session.py +162 -0
- mcp_use/client/task_managers/__init__.py +20 -0
- mcp_use/client/task_managers/base.py +145 -0
- mcp_use/client/task_managers/sse.py +84 -0
- mcp_use/client/task_managers/stdio.py +69 -0
- mcp_use/client/task_managers/streamable_http.py +86 -0
- mcp_use/client/task_managers/websocket.py +68 -0
- mcp_use/client.py +12 -320
- mcp_use/config.py +20 -92
- mcp_use/connectors/.deprecated +0 -0
- mcp_use/connectors/__init__.py +46 -20
- mcp_use/connectors/base.py +12 -447
- mcp_use/connectors/http.py +13 -288
- mcp_use/connectors/sandbox.py +13 -297
- mcp_use/connectors/stdio.py +13 -96
- mcp_use/connectors/utils.py +15 -8
- mcp_use/connectors/websocket.py +13 -252
- mcp_use/exceptions.py +33 -18
- mcp_use/managers/.deprecated +0 -0
- mcp_use/managers/__init__.py +56 -17
- mcp_use/managers/base.py +13 -31
- mcp_use/managers/server_manager.py +13 -119
- mcp_use/managers/tools/__init__.py +45 -15
- mcp_use/managers/tools/base_tool.py +5 -16
- mcp_use/managers/tools/connect_server.py +5 -67
- mcp_use/managers/tools/disconnect_server.py +5 -41
- mcp_use/managers/tools/get_active_server.py +5 -26
- mcp_use/managers/tools/list_servers_tool.py +5 -51
- mcp_use/managers/tools/search_tools.py +17 -321
- mcp_use/middleware/.deprecated +0 -0
- mcp_use/middleware/__init__.py +89 -0
- mcp_use/middleware/logging.py +19 -0
- mcp_use/middleware/metrics.py +41 -0
- mcp_use/middleware/middleware.py +55 -0
- mcp_use/session.py +13 -149
- mcp_use/task_managers/.deprecated +0 -0
- mcp_use/task_managers/__init__.py +48 -20
- mcp_use/task_managers/base.py +13 -140
- mcp_use/task_managers/sse.py +13 -79
- mcp_use/task_managers/stdio.py +13 -64
- mcp_use/task_managers/streamable_http.py +15 -81
- mcp_use/task_managers/websocket.py +13 -63
- mcp_use/telemetry/events.py +58 -0
- mcp_use/telemetry/telemetry.py +71 -1
- mcp_use/types/.deprecated +0 -0
- mcp_use/types/sandbox.py +13 -18
- {mcp_use-1.3.11.dist-info → mcp_use-1.3.13.dist-info}/METADATA +66 -40
- mcp_use-1.3.13.dist-info/RECORD +109 -0
- mcp_use-1.3.11.dist-info/RECORD +0 -60
- mcp_use-1.3.11.dist-info/licenses/LICENSE +0 -21
- /mcp_use/{observability → agents/observability}/__init__.py +0 -0
- /mcp_use/{observability → agents/observability}/callbacks_manager.py +0 -0
- /mcp_use/{observability → agents/observability}/laminar.py +0 -0
- /mcp_use/{observability → agents/observability}/langfuse.py +0 -0
- {mcp_use-1.3.11.dist-info → mcp_use-1.3.13.dist-info}/WHEEL +0 -0
- {mcp_use-1.3.11.dist-info → mcp_use-1.3.13.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session manager for MCP connections.
|
|
3
|
+
|
|
4
|
+
This module provides a session manager for MCP connections,
|
|
5
|
+
which handles authentication, initialization, and tool discovery.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import timedelta
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from mcp.types import CallToolResult, GetPromptResult, Prompt, ReadResourceResult, Resource, Tool
|
|
12
|
+
from pydantic import AnyUrl
|
|
13
|
+
|
|
14
|
+
from mcp_use.client.connectors.base import BaseConnector
|
|
15
|
+
from mcp_use.telemetry.telemetry import telemetry
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MCPSession:
|
|
19
|
+
"""Session manager for MCP connections.
|
|
20
|
+
|
|
21
|
+
This class manages the lifecycle of an MCP connection, including
|
|
22
|
+
authentication, initialization, and tool discovery.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
connector: BaseConnector,
|
|
28
|
+
auto_connect: bool = True,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Initialize a new MCP session.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
connector: The connector to use for communicating with the MCP implementation.
|
|
34
|
+
auto_connect: Whether to automatically connect to the MCP implementation.
|
|
35
|
+
"""
|
|
36
|
+
self.connector = connector
|
|
37
|
+
self.session_info: dict[str, Any] | None = None
|
|
38
|
+
self.auto_connect = auto_connect
|
|
39
|
+
|
|
40
|
+
async def __aenter__(self) -> "MCPSession":
|
|
41
|
+
"""Enter the async context manager.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
The session instance.
|
|
45
|
+
"""
|
|
46
|
+
await self.connect()
|
|
47
|
+
return self
|
|
48
|
+
|
|
49
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
50
|
+
"""Exit the async context manager.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
exc_type: The exception type, if an exception was raised.
|
|
54
|
+
exc_val: The exception value, if an exception was raised.
|
|
55
|
+
exc_tb: The exception traceback, if an exception was raised.
|
|
56
|
+
"""
|
|
57
|
+
await self.disconnect()
|
|
58
|
+
|
|
59
|
+
async def connect(self) -> None:
|
|
60
|
+
"""Connect to the MCP implementation."""
|
|
61
|
+
await self.connector.connect()
|
|
62
|
+
|
|
63
|
+
async def disconnect(self) -> None:
|
|
64
|
+
"""Disconnect from the MCP implementation."""
|
|
65
|
+
await self.connector.disconnect()
|
|
66
|
+
|
|
67
|
+
@telemetry("session_initialize")
|
|
68
|
+
async def initialize(self) -> dict[str, Any]:
|
|
69
|
+
"""Initialize the MCP session and discover available tools.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
The session information returned by the MCP implementation.
|
|
73
|
+
"""
|
|
74
|
+
# Make sure we're connected
|
|
75
|
+
if not self.is_connected and self.auto_connect:
|
|
76
|
+
await self.connect()
|
|
77
|
+
|
|
78
|
+
# Initialize the session
|
|
79
|
+
self.session_info = await self.connector.initialize()
|
|
80
|
+
|
|
81
|
+
return self.session_info
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def is_connected(self) -> bool:
|
|
85
|
+
"""Check if the connector is connected.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
True if the connector is connected, False otherwise.
|
|
89
|
+
"""
|
|
90
|
+
return self.connector.is_connected
|
|
91
|
+
|
|
92
|
+
# Convenience methods for MCP operations
|
|
93
|
+
@telemetry("session_call_tool")
|
|
94
|
+
async def call_tool(
|
|
95
|
+
self, name: str, arguments: dict[str, Any], read_timeout_seconds: timedelta | None = None
|
|
96
|
+
) -> CallToolResult:
|
|
97
|
+
"""Call an MCP tool.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
name: The name of the tool to call.
|
|
101
|
+
arguments: The arguments to pass to the tool.
|
|
102
|
+
read_timeout_seconds: Optional timeout for the tool call.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
The result of the tool call.
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
RuntimeError: If the connection is lost and cannot be reestablished.
|
|
109
|
+
"""
|
|
110
|
+
return await self.connector.call_tool(name, arguments, read_timeout_seconds)
|
|
111
|
+
|
|
112
|
+
@telemetry("session_list_tools")
|
|
113
|
+
async def list_tools(self) -> list[Tool]:
|
|
114
|
+
"""List all available tools from the MCP server.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
List of available tools.
|
|
118
|
+
"""
|
|
119
|
+
return await self.connector.list_tools()
|
|
120
|
+
|
|
121
|
+
@telemetry("session_list_resources")
|
|
122
|
+
async def list_resources(self) -> list[Resource]:
|
|
123
|
+
"""List all available resources from the MCP server.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
List of available resources.
|
|
127
|
+
"""
|
|
128
|
+
return await self.connector.list_resources()
|
|
129
|
+
|
|
130
|
+
@telemetry("session_read_resource")
|
|
131
|
+
async def read_resource(self, uri: AnyUrl) -> ReadResourceResult:
|
|
132
|
+
"""Read a resource by URI.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
uri: The URI of the resource to read.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
The resource content.
|
|
139
|
+
"""
|
|
140
|
+
return await self.connector.read_resource(uri)
|
|
141
|
+
|
|
142
|
+
@telemetry("session_list_prompts")
|
|
143
|
+
async def list_prompts(self) -> list[Prompt]:
|
|
144
|
+
"""List all available prompts from the MCP server.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
List of available prompts.
|
|
148
|
+
"""
|
|
149
|
+
return await self.connector.list_prompts()
|
|
150
|
+
|
|
151
|
+
@telemetry("session_get_prompt")
|
|
152
|
+
async def get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> GetPromptResult:
|
|
153
|
+
"""Get a prompt by name.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
name: The name of the prompt to get.
|
|
157
|
+
arguments: Optional arguments for the prompt.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
The prompt result with messages.
|
|
161
|
+
"""
|
|
162
|
+
return await self.connector.get_prompt(name, arguments)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Connectors for various MCP transports.
|
|
3
|
+
|
|
4
|
+
This module provides interfaces for connecting to MCP implementations
|
|
5
|
+
through different transport mechanisms.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .base import ConnectionManager
|
|
9
|
+
from .sse import SseConnectionManager
|
|
10
|
+
from .stdio import StdioConnectionManager
|
|
11
|
+
from .streamable_http import StreamableHttpConnectionManager
|
|
12
|
+
from .websocket import WebSocketConnectionManager
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"ConnectionManager",
|
|
16
|
+
"StdioConnectionManager",
|
|
17
|
+
"WebSocketConnectionManager",
|
|
18
|
+
"SseConnectionManager",
|
|
19
|
+
"StreamableHttpConnectionManager",
|
|
20
|
+
]
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Connection management for MCP implementations.
|
|
3
|
+
|
|
4
|
+
This module provides an abstract base class for different types of connection
|
|
5
|
+
managers used in MCP connectors.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from typing import Generic, TypeVar
|
|
11
|
+
|
|
12
|
+
from mcp_use.logging import logger
|
|
13
|
+
|
|
14
|
+
# Type variable for connection types
|
|
15
|
+
T = TypeVar("T")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConnectionManager(Generic[T], ABC):
|
|
19
|
+
"""Abstract base class for connection managers.
|
|
20
|
+
|
|
21
|
+
This class defines the interface for different types of connection managers
|
|
22
|
+
used with MCP connectors.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self) -> None:
|
|
26
|
+
"""Initialize a new connection manager."""
|
|
27
|
+
self._ready_event = asyncio.Event()
|
|
28
|
+
self._done_event = asyncio.Event()
|
|
29
|
+
self._stop_event = asyncio.Event()
|
|
30
|
+
self._exception: Exception | None = None
|
|
31
|
+
self._connection: T | None = None
|
|
32
|
+
self._task: asyncio.Task[None] | None = None
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
async def _establish_connection(self) -> T:
|
|
36
|
+
"""Establish the connection.
|
|
37
|
+
|
|
38
|
+
This method should be implemented by subclasses to establish
|
|
39
|
+
the specific type of connection needed.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
The established connection.
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
Exception: If connection cannot be established.
|
|
46
|
+
"""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
async def _close_connection(self) -> None:
|
|
51
|
+
"""Close the connection.
|
|
52
|
+
|
|
53
|
+
This method should be implemented by subclasses to close
|
|
54
|
+
the specific type of connection.
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
async def start(self) -> T:
|
|
60
|
+
"""Start the connection manager and establish a connection.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
The established connection.
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
Exception: If connection cannot be established.
|
|
67
|
+
"""
|
|
68
|
+
# Reset state
|
|
69
|
+
self._ready_event.clear()
|
|
70
|
+
self._done_event.clear()
|
|
71
|
+
self._exception = None
|
|
72
|
+
|
|
73
|
+
# Create a task to establish and maintain the connection
|
|
74
|
+
self._task = asyncio.create_task(self._connection_task(), name=f"{self.__class__.__name__}_task")
|
|
75
|
+
|
|
76
|
+
# Wait for the connection to be ready or fail
|
|
77
|
+
await self._ready_event.wait()
|
|
78
|
+
|
|
79
|
+
# If there was an exception, raise it
|
|
80
|
+
if self._exception:
|
|
81
|
+
raise self._exception
|
|
82
|
+
|
|
83
|
+
# Return the connection
|
|
84
|
+
if self._connection is None:
|
|
85
|
+
raise RuntimeError("Connection was not established")
|
|
86
|
+
return self._connection
|
|
87
|
+
|
|
88
|
+
async def stop(self) -> None:
|
|
89
|
+
"""Stop the connection manager and close the connection."""
|
|
90
|
+
# Signal stop to the connection task instead of cancelling it, avoids
|
|
91
|
+
# propagating CancelledError to unrelated tasks.
|
|
92
|
+
if self._task and not self._task.done():
|
|
93
|
+
logger.debug(f"Signaling stop to {self.__class__.__name__} task")
|
|
94
|
+
self._stop_event.set()
|
|
95
|
+
# Wait for it to finish gracefully
|
|
96
|
+
await self._task
|
|
97
|
+
|
|
98
|
+
# Ensure cleanup completed
|
|
99
|
+
await self._done_event.wait()
|
|
100
|
+
logger.debug(f"{self.__class__.__name__} task completed")
|
|
101
|
+
|
|
102
|
+
def get_streams(self) -> T | None:
|
|
103
|
+
"""Get the current connection streams.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
The current connection (typically a tuple of read_stream, write_stream) or None if not connected.
|
|
107
|
+
"""
|
|
108
|
+
return self._connection
|
|
109
|
+
|
|
110
|
+
async def _connection_task(self) -> None:
|
|
111
|
+
"""Run the connection task.
|
|
112
|
+
|
|
113
|
+
This task establishes and maintains the connection until cancelled.
|
|
114
|
+
"""
|
|
115
|
+
logger.debug(f"Starting {self.__class__.__name__} task")
|
|
116
|
+
try:
|
|
117
|
+
# Establish the connection
|
|
118
|
+
self._connection = await self._establish_connection()
|
|
119
|
+
logger.debug(f"{self.__class__.__name__} connected successfully")
|
|
120
|
+
|
|
121
|
+
# Signal that the connection is ready
|
|
122
|
+
self._ready_event.set()
|
|
123
|
+
|
|
124
|
+
# Wait until stop is requested
|
|
125
|
+
await self._stop_event.wait()
|
|
126
|
+
|
|
127
|
+
except Exception as e:
|
|
128
|
+
# Store the exception
|
|
129
|
+
self._exception = e
|
|
130
|
+
logger.error(f"Error in {self.__class__.__name__} task: {e}")
|
|
131
|
+
|
|
132
|
+
# Signal that the connection is ready (with error)
|
|
133
|
+
self._ready_event.set()
|
|
134
|
+
|
|
135
|
+
finally:
|
|
136
|
+
# Close the connection if it was established
|
|
137
|
+
if self._connection is not None:
|
|
138
|
+
try:
|
|
139
|
+
await self._close_connection()
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.warning(f"Error closing connection in {self.__class__.__name__}: {e}")
|
|
142
|
+
self._connection = None
|
|
143
|
+
|
|
144
|
+
# Signal that the connection is done
|
|
145
|
+
self._done_event.set()
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SSE connection management for MCP implementations.
|
|
3
|
+
|
|
4
|
+
This module provides a connection manager for SSE-based MCP connections
|
|
5
|
+
that ensures proper task isolation and resource cleanup.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
from mcp.client.sse import sse_client
|
|
12
|
+
|
|
13
|
+
from mcp_use.client.task_managers.base import ConnectionManager
|
|
14
|
+
from mcp_use.logging import logger
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SseConnectionManager(ConnectionManager[tuple[Any, Any]]):
|
|
18
|
+
"""Connection manager for SSE-based MCP connections.
|
|
19
|
+
|
|
20
|
+
This class handles the proper task isolation for sse_client context managers
|
|
21
|
+
to prevent the "cancel scope in different task" error. It runs the sse_client
|
|
22
|
+
in a dedicated task and manages its lifecycle.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
url: str,
|
|
28
|
+
headers: dict[str, str] | None = None,
|
|
29
|
+
timeout: float = 5,
|
|
30
|
+
sse_read_timeout: float = 60 * 5,
|
|
31
|
+
auth: httpx.Auth | None = None,
|
|
32
|
+
):
|
|
33
|
+
"""Initialize a new SSE connection manager.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
url: The SSE endpoint URL
|
|
37
|
+
headers: Optional HTTP headers
|
|
38
|
+
timeout: Timeout for HTTP operations in seconds
|
|
39
|
+
sse_read_timeout: Timeout for SSE read operations in seconds
|
|
40
|
+
auth: Optional httpx.Auth instance for authentication
|
|
41
|
+
"""
|
|
42
|
+
super().__init__()
|
|
43
|
+
self.url = url
|
|
44
|
+
self.headers = headers or {}
|
|
45
|
+
self.timeout = timeout
|
|
46
|
+
self.sse_read_timeout = sse_read_timeout
|
|
47
|
+
self.auth = auth
|
|
48
|
+
self._sse_ctx = None
|
|
49
|
+
|
|
50
|
+
async def _establish_connection(self) -> tuple[Any, Any]:
|
|
51
|
+
"""Establish an SSE connection.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
A tuple of (read_stream, write_stream)
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
Exception: If connection cannot be established.
|
|
58
|
+
"""
|
|
59
|
+
# Create the context manager
|
|
60
|
+
self._sse_ctx = sse_client(
|
|
61
|
+
url=self.url,
|
|
62
|
+
headers=self.headers,
|
|
63
|
+
timeout=self.timeout,
|
|
64
|
+
sse_read_timeout=self.sse_read_timeout,
|
|
65
|
+
auth=self.auth,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Enter the context manager
|
|
69
|
+
read_stream, write_stream = await self._sse_ctx.__aenter__()
|
|
70
|
+
|
|
71
|
+
# Return the streams
|
|
72
|
+
return (read_stream, write_stream)
|
|
73
|
+
|
|
74
|
+
async def _close_connection(self) -> None:
|
|
75
|
+
"""Close the SSE connection."""
|
|
76
|
+
|
|
77
|
+
if self._sse_ctx:
|
|
78
|
+
# Exit the context manager
|
|
79
|
+
try:
|
|
80
|
+
await self._sse_ctx.__aexit__(None, None, None)
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.warning(f"Error closing SSE context: {e}")
|
|
83
|
+
finally:
|
|
84
|
+
self._sse_ctx = None
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
StdIO connection management for MCP implementations.
|
|
3
|
+
|
|
4
|
+
This module provides a connection manager for stdio-based MCP connections
|
|
5
|
+
that ensures proper task isolation and resource cleanup.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from typing import Any, TextIO
|
|
10
|
+
|
|
11
|
+
from mcp import StdioServerParameters
|
|
12
|
+
from mcp.client.stdio import stdio_client
|
|
13
|
+
|
|
14
|
+
from mcp_use.client.task_managers.base import ConnectionManager
|
|
15
|
+
from mcp_use.logging import logger
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class StdioConnectionManager(ConnectionManager[tuple[Any, Any]]):
|
|
19
|
+
"""Connection manager for stdio-based MCP connections.
|
|
20
|
+
|
|
21
|
+
This class handles the proper task isolation for stdio_client context managers
|
|
22
|
+
to prevent the "cancel scope in different task" error. It runs the stdio_client
|
|
23
|
+
in a dedicated task and manages its lifecycle.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
server_params: StdioServerParameters,
|
|
29
|
+
errlog: TextIO = sys.stderr,
|
|
30
|
+
):
|
|
31
|
+
"""Initialize a new stdio connection manager.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
server_params: The parameters for the stdio server
|
|
35
|
+
errlog: The error log stream
|
|
36
|
+
"""
|
|
37
|
+
super().__init__()
|
|
38
|
+
self.server_params = server_params
|
|
39
|
+
self.errlog = errlog
|
|
40
|
+
self._stdio_ctx = None
|
|
41
|
+
|
|
42
|
+
async def _establish_connection(self) -> tuple[Any, Any]:
|
|
43
|
+
"""Establish a stdio connection.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
A tuple of (read_stream, write_stream)
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
Exception: If connection cannot be established.
|
|
50
|
+
"""
|
|
51
|
+
# Create the context manager
|
|
52
|
+
self._stdio_ctx = stdio_client(self.server_params, self.errlog)
|
|
53
|
+
|
|
54
|
+
# Enter the context manager
|
|
55
|
+
read_stream, write_stream = await self._stdio_ctx.__aenter__()
|
|
56
|
+
|
|
57
|
+
# Return the streams
|
|
58
|
+
return (read_stream, write_stream)
|
|
59
|
+
|
|
60
|
+
async def _close_connection(self) -> None:
|
|
61
|
+
"""Close the stdio connection."""
|
|
62
|
+
if self._stdio_ctx:
|
|
63
|
+
# Exit the context manager
|
|
64
|
+
try:
|
|
65
|
+
await self._stdio_ctx.__aexit__(None, None, None)
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.warning(f"Error closing stdio context: {e}")
|
|
68
|
+
finally:
|
|
69
|
+
self._stdio_ctx = None
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Streamable HTTP connection management for MCP implementations.
|
|
3
|
+
|
|
4
|
+
This module provides a connection manager for streamable HTTP-based MCP connections
|
|
5
|
+
that ensures proper task isolation and resource cleanup.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import timedelta
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
13
|
+
|
|
14
|
+
from mcp_use.client.task_managers.base import ConnectionManager
|
|
15
|
+
from mcp_use.logging import logger
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class StreamableHttpConnectionManager(ConnectionManager[tuple[Any, Any]]):
|
|
19
|
+
"""Connection manager for streamable HTTP-based MCP connections.
|
|
20
|
+
|
|
21
|
+
This class handles the proper task isolation for HTTP streaming connections
|
|
22
|
+
to prevent the "cancel scope in different task" error. It runs the http_stream_client
|
|
23
|
+
in a dedicated task and manages its lifecycle.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
url: str,
|
|
29
|
+
headers: dict[str, str] | None = None,
|
|
30
|
+
timeout: float = 5,
|
|
31
|
+
read_timeout: float = 60 * 5,
|
|
32
|
+
auth: httpx.Auth | None = None,
|
|
33
|
+
):
|
|
34
|
+
"""Initialize a new streamable HTTP connection manager.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
url: The HTTP endpoint URL
|
|
38
|
+
headers: Optional HTTP headers
|
|
39
|
+
timeout: Timeout for HTTP operations in seconds
|
|
40
|
+
read_timeout: Timeout for HTTP read operations in seconds
|
|
41
|
+
auth: Optional httpx.Auth instance for authentication
|
|
42
|
+
"""
|
|
43
|
+
super().__init__()
|
|
44
|
+
self.url = url
|
|
45
|
+
self.headers = headers or {}
|
|
46
|
+
self.timeout = timedelta(seconds=timeout)
|
|
47
|
+
self.read_timeout = timedelta(seconds=read_timeout)
|
|
48
|
+
self.auth = auth
|
|
49
|
+
self._http_ctx = None
|
|
50
|
+
|
|
51
|
+
async def _establish_connection(self) -> tuple[Any, Any]:
|
|
52
|
+
"""Establish a streamable HTTP connection.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
A tuple of (read_stream, write_stream)
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
Exception: If connection cannot be established.
|
|
59
|
+
"""
|
|
60
|
+
# Create the context manager
|
|
61
|
+
self._http_ctx = streamablehttp_client(
|
|
62
|
+
url=self.url,
|
|
63
|
+
headers=self.headers,
|
|
64
|
+
timeout=self.timeout,
|
|
65
|
+
sse_read_timeout=self.read_timeout,
|
|
66
|
+
auth=self.auth,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Enter the context manager. Ignoring the session id callback
|
|
70
|
+
read_stream, write_stream, _ = await self._http_ctx.__aenter__()
|
|
71
|
+
|
|
72
|
+
# Return the streams
|
|
73
|
+
return (read_stream, write_stream)
|
|
74
|
+
|
|
75
|
+
async def _close_connection(self) -> None:
|
|
76
|
+
"""Close the streamable HTTP connection."""
|
|
77
|
+
|
|
78
|
+
if self._http_ctx:
|
|
79
|
+
# Exit the context manager
|
|
80
|
+
try:
|
|
81
|
+
await self._http_ctx.__aexit__(None, None, None)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
# Only log if it's not a normal connection termination
|
|
84
|
+
logger.debug(f"Streamable HTTP context cleanup: {e}")
|
|
85
|
+
finally:
|
|
86
|
+
self._http_ctx = None
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WebSocket connection management for MCP implementations.
|
|
3
|
+
|
|
4
|
+
This module provides a connection manager for WebSocket-based MCP connections.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from mcp.client.websocket import websocket_client
|
|
10
|
+
|
|
11
|
+
from mcp_use.client.task_managers.base import ConnectionManager
|
|
12
|
+
from mcp_use.logging import logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class WebSocketConnectionManager(ConnectionManager[tuple[Any, Any]]):
|
|
16
|
+
"""Connection manager for WebSocket-based MCP connections.
|
|
17
|
+
|
|
18
|
+
This class handles the lifecycle of WebSocket connections, ensuring proper
|
|
19
|
+
connection establishment and cleanup.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
url: str,
|
|
25
|
+
headers: dict[str, str] | None = None,
|
|
26
|
+
):
|
|
27
|
+
"""Initialize a new WebSocket connection manager.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
url: The WebSocket URL to connect to
|
|
31
|
+
headers: Optional HTTP headers
|
|
32
|
+
"""
|
|
33
|
+
super().__init__()
|
|
34
|
+
self.url = url
|
|
35
|
+
self.headers = headers or {}
|
|
36
|
+
|
|
37
|
+
async def _establish_connection(self) -> tuple[Any, Any]:
|
|
38
|
+
"""Establish a WebSocket connection.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
The established WebSocket connection
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
Exception: If connection cannot be established
|
|
45
|
+
"""
|
|
46
|
+
logger.debug(f"Connecting to WebSocket: {self.url}")
|
|
47
|
+
# Create the context manager
|
|
48
|
+
# Note: The current MCP websocket_client implementation doesn't support headers
|
|
49
|
+
# If headers need to be passed, this would need to be updated when MCP supports it
|
|
50
|
+
self._ws_ctx = websocket_client(self.url)
|
|
51
|
+
|
|
52
|
+
# Enter the context manager
|
|
53
|
+
read_stream, write_stream = await self._ws_ctx.__aenter__()
|
|
54
|
+
|
|
55
|
+
# Return the streams
|
|
56
|
+
return (read_stream, write_stream)
|
|
57
|
+
|
|
58
|
+
async def _close_connection(self) -> None:
|
|
59
|
+
"""Close the WebSocket connection."""
|
|
60
|
+
if self._ws_ctx:
|
|
61
|
+
# Exit the context manager
|
|
62
|
+
try:
|
|
63
|
+
logger.debug("Closing WebSocket connection")
|
|
64
|
+
await self._ws_ctx.__aexit__(None, None, None)
|
|
65
|
+
except Exception as e:
|
|
66
|
+
logger.warning(f"Error closing WebSocket connection: {e}")
|
|
67
|
+
finally:
|
|
68
|
+
self._ws_ctx = None
|