mcp-use 1.0.0__py3-none-any.whl → 1.0.2__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/config.py +3 -3
- mcp_use/connectors/base.py +94 -14
- mcp_use/connectors/http.py +28 -156
- mcp_use/connectors/stdio.py +2 -112
- mcp_use/task_managers/__init__.py +2 -1
- mcp_use/task_managers/sse.py +82 -0
- mcp_use/task_managers/websocket.py +4 -4
- {mcp_use-1.0.0.dist-info → mcp_use-1.0.2.dist-info}/METADATA +3 -2
- {mcp_use-1.0.0.dist-info → mcp_use-1.0.2.dist-info}/RECORD +11 -11
- mcp_use/task_managers/http.py +0 -62
- {mcp_use-1.0.0.dist-info → mcp_use-1.0.2.dist-info}/WHEEL +0 -0
- {mcp_use-1.0.0.dist-info → mcp_use-1.0.2.dist-info}/licenses/LICENSE +0 -0
mcp_use/config.py
CHANGED
|
@@ -43,9 +43,9 @@ def create_connector_from_config(server_config: dict[str, Any]) -> BaseConnector
|
|
|
43
43
|
# HTTP connector
|
|
44
44
|
elif "url" in server_config:
|
|
45
45
|
return HttpConnector(
|
|
46
|
-
|
|
46
|
+
base_url=server_config["url"],
|
|
47
47
|
headers=server_config.get("headers", None),
|
|
48
|
-
|
|
48
|
+
auth_token=server_config.get("auth_token", None),
|
|
49
49
|
)
|
|
50
50
|
|
|
51
51
|
# WebSocket connector
|
|
@@ -53,7 +53,7 @@ def create_connector_from_config(server_config: dict[str, Any]) -> BaseConnector
|
|
|
53
53
|
return WebSocketConnector(
|
|
54
54
|
url=server_config["ws_url"],
|
|
55
55
|
headers=server_config.get("headers", None),
|
|
56
|
-
|
|
56
|
+
auth_token=server_config.get("auth_token", None),
|
|
57
57
|
)
|
|
58
58
|
|
|
59
59
|
raise ValueError("Cannot determine connector type from config")
|
mcp_use/connectors/base.py
CHANGED
|
@@ -8,8 +8,12 @@ must implement.
|
|
|
8
8
|
from abc import ABC, abstractmethod
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
|
+
from mcp import ClientSession
|
|
11
12
|
from mcp.types import CallToolResult, Tool
|
|
12
13
|
|
|
14
|
+
from ..logging import logger
|
|
15
|
+
from ..task_managers import ConnectionManager
|
|
16
|
+
|
|
13
17
|
|
|
14
18
|
class BaseConnector(ABC):
|
|
15
19
|
"""Base class for MCP connectors.
|
|
@@ -17,43 +21,119 @@ class BaseConnector(ABC):
|
|
|
17
21
|
This class defines the interface that all MCP connectors must implement.
|
|
18
22
|
"""
|
|
19
23
|
|
|
24
|
+
def __init__(self):
|
|
25
|
+
"""Initialize base connector with common attributes."""
|
|
26
|
+
self.client: ClientSession | None = None
|
|
27
|
+
self._connection_manager: ConnectionManager | None = None
|
|
28
|
+
self._tools: list[Tool] | None = None
|
|
29
|
+
self._connected = False
|
|
30
|
+
|
|
20
31
|
@abstractmethod
|
|
21
32
|
async def connect(self) -> None:
|
|
22
33
|
"""Establish a connection to the MCP implementation."""
|
|
23
34
|
pass
|
|
24
35
|
|
|
25
|
-
@abstractmethod
|
|
26
36
|
async def disconnect(self) -> None:
|
|
27
37
|
"""Close the connection to the MCP implementation."""
|
|
28
|
-
|
|
38
|
+
if not self._connected:
|
|
39
|
+
logger.debug("Not connected to MCP implementation")
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
logger.info("Disconnecting from MCP implementation")
|
|
43
|
+
await self._cleanup_resources()
|
|
44
|
+
self._connected = False
|
|
45
|
+
logger.info("Disconnected from MCP implementation")
|
|
46
|
+
|
|
47
|
+
async def _cleanup_resources(self) -> None:
|
|
48
|
+
"""Clean up all resources associated with this connector."""
|
|
49
|
+
errors = []
|
|
50
|
+
|
|
51
|
+
# First close the client session
|
|
52
|
+
if self.client:
|
|
53
|
+
try:
|
|
54
|
+
logger.debug("Closing client session")
|
|
55
|
+
await self.client.__aexit__(None, None, None)
|
|
56
|
+
except Exception as e:
|
|
57
|
+
error_msg = f"Error closing client session: {e}"
|
|
58
|
+
logger.warning(error_msg)
|
|
59
|
+
errors.append(error_msg)
|
|
60
|
+
finally:
|
|
61
|
+
self.client = None
|
|
62
|
+
|
|
63
|
+
# Then stop the connection manager
|
|
64
|
+
if self._connection_manager:
|
|
65
|
+
try:
|
|
66
|
+
logger.debug("Stopping connection manager")
|
|
67
|
+
await self._connection_manager.stop()
|
|
68
|
+
except Exception as e:
|
|
69
|
+
error_msg = f"Error stopping connection manager: {e}"
|
|
70
|
+
logger.warning(error_msg)
|
|
71
|
+
errors.append(error_msg)
|
|
72
|
+
finally:
|
|
73
|
+
self._connection_manager = None
|
|
74
|
+
|
|
75
|
+
# Reset tools
|
|
76
|
+
self._tools = None
|
|
77
|
+
|
|
78
|
+
if errors:
|
|
79
|
+
logger.warning(f"Encountered {len(errors)} errors during resource cleanup")
|
|
29
80
|
|
|
30
|
-
@abstractmethod
|
|
31
81
|
async def initialize(self) -> dict[str, Any]:
|
|
32
82
|
"""Initialize the MCP session and return session information."""
|
|
33
|
-
|
|
83
|
+
if not self.client:
|
|
84
|
+
raise RuntimeError("MCP client is not connected")
|
|
85
|
+
|
|
86
|
+
logger.info("Initializing MCP session")
|
|
87
|
+
|
|
88
|
+
# Initialize the session
|
|
89
|
+
result = await self.client.initialize()
|
|
90
|
+
|
|
91
|
+
# Get available tools
|
|
92
|
+
tools_result = await self.client.list_tools()
|
|
93
|
+
self._tools = tools_result.tools
|
|
94
|
+
|
|
95
|
+
logger.info(f"MCP session initialized with {len(self._tools)} tools")
|
|
96
|
+
|
|
97
|
+
return result
|
|
34
98
|
|
|
35
99
|
@property
|
|
36
|
-
@abstractmethod
|
|
37
100
|
def tools(self) -> list[Tool]:
|
|
38
101
|
"""Get the list of available tools."""
|
|
39
|
-
|
|
102
|
+
if not self._tools:
|
|
103
|
+
raise RuntimeError("MCP client is not initialized")
|
|
104
|
+
return self._tools
|
|
40
105
|
|
|
41
|
-
@abstractmethod
|
|
42
106
|
async def call_tool(self, name: str, arguments: dict[str, Any]) -> CallToolResult:
|
|
43
107
|
"""Call an MCP tool with the given arguments."""
|
|
44
|
-
|
|
108
|
+
if not self.client:
|
|
109
|
+
raise RuntimeError("MCP client is not connected")
|
|
110
|
+
|
|
111
|
+
logger.debug(f"Calling tool '{name}' with arguments: {arguments}")
|
|
112
|
+
result = await self.client.call_tool(name, arguments)
|
|
113
|
+
return result
|
|
45
114
|
|
|
46
|
-
@abstractmethod
|
|
47
115
|
async def list_resources(self) -> list[dict[str, Any]]:
|
|
48
116
|
"""List all available resources from the MCP implementation."""
|
|
49
|
-
|
|
117
|
+
if not self.client:
|
|
118
|
+
raise RuntimeError("MCP client is not connected")
|
|
119
|
+
|
|
120
|
+
logger.debug("Listing resources")
|
|
121
|
+
resources = await self.client.list_resources()
|
|
122
|
+
return resources
|
|
50
123
|
|
|
51
|
-
@abstractmethod
|
|
52
124
|
async def read_resource(self, uri: str) -> tuple[bytes, str]:
|
|
53
125
|
"""Read a resource by URI."""
|
|
54
|
-
|
|
126
|
+
if not self.client:
|
|
127
|
+
raise RuntimeError("MCP client is not connected")
|
|
128
|
+
|
|
129
|
+
logger.debug(f"Reading resource: {uri}")
|
|
130
|
+
resource = await self.client.read_resource(uri)
|
|
131
|
+
return resource.content, resource.mimeType
|
|
55
132
|
|
|
56
|
-
@abstractmethod
|
|
57
133
|
async def request(self, method: str, params: dict[str, Any] | None = None) -> Any:
|
|
58
134
|
"""Send a raw request to the MCP implementation."""
|
|
59
|
-
|
|
135
|
+
if not self.client:
|
|
136
|
+
raise RuntimeError("MCP client is not connected")
|
|
137
|
+
|
|
138
|
+
logger.debug(f"Sending request: {method} with params: {params}")
|
|
139
|
+
return await self.client.request({"method": method, "params": params or {}})
|
mcp_use/connectors/http.py
CHANGED
|
@@ -2,23 +2,20 @@
|
|
|
2
2
|
HTTP connector for MCP implementations.
|
|
3
3
|
|
|
4
4
|
This module provides a connector for communicating with MCP implementations
|
|
5
|
-
through HTTP APIs.
|
|
5
|
+
through HTTP APIs with SSE for transport.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
import aiohttp
|
|
11
|
-
from mcp.types import Tool
|
|
8
|
+
from mcp import ClientSession
|
|
12
9
|
|
|
13
10
|
from ..logging import logger
|
|
14
|
-
from ..task_managers import
|
|
11
|
+
from ..task_managers import SseConnectionManager
|
|
15
12
|
from .base import BaseConnector
|
|
16
13
|
|
|
17
14
|
|
|
18
15
|
class HttpConnector(BaseConnector):
|
|
19
|
-
"""Connector for MCP implementations using HTTP transport.
|
|
16
|
+
"""Connector for MCP implementations using HTTP transport with SSE.
|
|
20
17
|
|
|
21
|
-
This connector uses HTTP
|
|
18
|
+
This connector uses HTTP/SSE to communicate with remote MCP implementations,
|
|
22
19
|
using a connection manager to handle the proper lifecycle management.
|
|
23
20
|
"""
|
|
24
21
|
|
|
@@ -27,6 +24,8 @@ class HttpConnector(BaseConnector):
|
|
|
27
24
|
base_url: str,
|
|
28
25
|
auth_token: str | None = None,
|
|
29
26
|
headers: dict[str, str] | None = None,
|
|
27
|
+
timeout: float = 5,
|
|
28
|
+
sse_read_timeout: float = 60 * 5,
|
|
30
29
|
):
|
|
31
30
|
"""Initialize a new HTTP connector.
|
|
32
31
|
|
|
@@ -34,17 +33,17 @@ class HttpConnector(BaseConnector):
|
|
|
34
33
|
base_url: The base URL of the MCP HTTP API.
|
|
35
34
|
auth_token: Optional authentication token.
|
|
36
35
|
headers: Optional additional headers.
|
|
36
|
+
timeout: Timeout for HTTP operations in seconds.
|
|
37
|
+
sse_read_timeout: Timeout for SSE read operations in seconds.
|
|
37
38
|
"""
|
|
39
|
+
super().__init__()
|
|
38
40
|
self.base_url = base_url.rstrip("/")
|
|
39
41
|
self.auth_token = auth_token
|
|
40
42
|
self.headers = headers or {}
|
|
41
43
|
if auth_token:
|
|
42
44
|
self.headers["Authorization"] = f"Bearer {auth_token}"
|
|
43
|
-
|
|
44
|
-
self.
|
|
45
|
-
self._connection_manager: ConnectionManager | None = None
|
|
46
|
-
self._tools: list[Tool] | None = None
|
|
47
|
-
self._connected = False
|
|
45
|
+
self.timeout = timeout
|
|
46
|
+
self.sse_read_timeout = sse_read_timeout
|
|
48
47
|
|
|
49
48
|
async def connect(self) -> None:
|
|
50
49
|
"""Establish a connection to the MCP implementation."""
|
|
@@ -52,159 +51,32 @@ class HttpConnector(BaseConnector):
|
|
|
52
51
|
logger.debug("Already connected to MCP implementation")
|
|
53
52
|
return
|
|
54
53
|
|
|
55
|
-
logger.info(f"Connecting to MCP implementation via HTTP: {self.base_url}")
|
|
54
|
+
logger.info(f"Connecting to MCP implementation via HTTP/SSE: {self.base_url}")
|
|
56
55
|
try:
|
|
56
|
+
# Create the SSE connection URL
|
|
57
|
+
sse_url = f"{self.base_url}"
|
|
58
|
+
|
|
57
59
|
# Create and start the connection manager
|
|
58
|
-
self._connection_manager =
|
|
59
|
-
|
|
60
|
+
self._connection_manager = SseConnectionManager(
|
|
61
|
+
sse_url, self.headers, self.timeout, self.sse_read_timeout
|
|
62
|
+
)
|
|
63
|
+
read_stream, write_stream = await self._connection_manager.start()
|
|
64
|
+
|
|
65
|
+
# Create the client session
|
|
66
|
+
self.client = ClientSession(read_stream, write_stream, sampling_callback=None)
|
|
67
|
+
await self.client.__aenter__()
|
|
60
68
|
|
|
61
69
|
# Mark as connected
|
|
62
70
|
self._connected = True
|
|
63
|
-
logger.info(
|
|
71
|
+
logger.info(
|
|
72
|
+
f"Successfully connected to MCP implementation via HTTP/SSE: {self.base_url}"
|
|
73
|
+
)
|
|
64
74
|
|
|
65
75
|
except Exception as e:
|
|
66
|
-
logger.error(f"Failed to connect to MCP implementation via HTTP: {e}")
|
|
76
|
+
logger.error(f"Failed to connect to MCP implementation via HTTP/SSE: {e}")
|
|
67
77
|
|
|
68
78
|
# Clean up any resources if connection failed
|
|
69
79
|
await self._cleanup_resources()
|
|
70
80
|
|
|
71
81
|
# Re-raise the original exception
|
|
72
82
|
raise
|
|
73
|
-
|
|
74
|
-
async def disconnect(self) -> None:
|
|
75
|
-
"""Close the connection to the MCP implementation."""
|
|
76
|
-
if not self._connected:
|
|
77
|
-
logger.debug("Not connected to MCP implementation")
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
logger.info("Disconnecting from MCP implementation")
|
|
81
|
-
await self._cleanup_resources()
|
|
82
|
-
self._connected = False
|
|
83
|
-
logger.info("Disconnected from MCP implementation")
|
|
84
|
-
|
|
85
|
-
async def _cleanup_resources(self) -> None:
|
|
86
|
-
"""Clean up all resources associated with this connector."""
|
|
87
|
-
errors = []
|
|
88
|
-
|
|
89
|
-
# Stop the connection manager
|
|
90
|
-
if self._connection_manager:
|
|
91
|
-
try:
|
|
92
|
-
logger.debug("Stopping connection manager")
|
|
93
|
-
await self._connection_manager.stop()
|
|
94
|
-
except Exception as e:
|
|
95
|
-
error_msg = f"Error stopping connection manager: {e}"
|
|
96
|
-
logger.warning(error_msg)
|
|
97
|
-
errors.append(error_msg)
|
|
98
|
-
finally:
|
|
99
|
-
self._connection_manager = None
|
|
100
|
-
self.session = None
|
|
101
|
-
|
|
102
|
-
# Reset tools
|
|
103
|
-
self._tools = None
|
|
104
|
-
|
|
105
|
-
if errors:
|
|
106
|
-
logger.warning(f"Encountered {len(errors)} errors during resource cleanup")
|
|
107
|
-
|
|
108
|
-
async def _request(self, method: str, endpoint: str, data: dict[str, Any] | None = None) -> Any:
|
|
109
|
-
"""Send an HTTP request to the MCP API.
|
|
110
|
-
|
|
111
|
-
Args:
|
|
112
|
-
method: The HTTP method (GET, POST, etc.).
|
|
113
|
-
endpoint: The API endpoint path.
|
|
114
|
-
data: Optional request data.
|
|
115
|
-
|
|
116
|
-
Returns:
|
|
117
|
-
The parsed JSON response.
|
|
118
|
-
"""
|
|
119
|
-
if not self.session:
|
|
120
|
-
raise RuntimeError("HTTP session is not connected")
|
|
121
|
-
|
|
122
|
-
url = f"{self.base_url}/{endpoint.lstrip('/')}"
|
|
123
|
-
logger.debug(f"Sending {method} request to {url}")
|
|
124
|
-
|
|
125
|
-
if method.upper() == "GET" and data:
|
|
126
|
-
# For GET requests, convert data to query parameters
|
|
127
|
-
async with self.session.get(url, params=data) as response:
|
|
128
|
-
response.raise_for_status()
|
|
129
|
-
return await response.json()
|
|
130
|
-
else:
|
|
131
|
-
# For other methods, send data as JSON body
|
|
132
|
-
async with self.session.request(method, url, json=data) as response:
|
|
133
|
-
response.raise_for_status()
|
|
134
|
-
return await response.json()
|
|
135
|
-
|
|
136
|
-
async def initialize(self) -> dict[str, Any]:
|
|
137
|
-
"""Initialize the MCP session and return session information."""
|
|
138
|
-
logger.info("Initializing MCP session")
|
|
139
|
-
|
|
140
|
-
# Initialize the session
|
|
141
|
-
result = await self._request("POST", "initialize")
|
|
142
|
-
|
|
143
|
-
# Get available tools
|
|
144
|
-
tools_result = await self.list_tools()
|
|
145
|
-
self._tools = [Tool(**tool) for tool in tools_result]
|
|
146
|
-
|
|
147
|
-
logger.info(f"MCP session initialized with {len(self._tools)} tools")
|
|
148
|
-
return result
|
|
149
|
-
|
|
150
|
-
async def list_tools(self) -> list[dict[str, Any]]:
|
|
151
|
-
"""List all available tools from the MCP implementation."""
|
|
152
|
-
logger.debug("Listing tools")
|
|
153
|
-
result = await self._request("GET", "tools")
|
|
154
|
-
return result.get("tools", [])
|
|
155
|
-
|
|
156
|
-
@property
|
|
157
|
-
def tools(self) -> list[Tool]:
|
|
158
|
-
"""Get the list of available tools."""
|
|
159
|
-
if not self._tools:
|
|
160
|
-
raise RuntimeError("MCP client is not initialized")
|
|
161
|
-
return self._tools
|
|
162
|
-
|
|
163
|
-
async def call_tool(self, name: str, arguments: dict[str, Any]) -> Any:
|
|
164
|
-
"""Call an MCP tool with the given arguments."""
|
|
165
|
-
logger.debug(f"Calling tool '{name}' with arguments: {arguments}")
|
|
166
|
-
return await self._request("POST", f"tools/{name}", arguments)
|
|
167
|
-
|
|
168
|
-
async def list_resources(self) -> list[dict[str, Any]]:
|
|
169
|
-
"""List all available resources from the MCP implementation."""
|
|
170
|
-
logger.debug("Listing resources")
|
|
171
|
-
result = await self._request("GET", "resources")
|
|
172
|
-
return result
|
|
173
|
-
|
|
174
|
-
async def read_resource(self, uri: str) -> tuple[bytes, str]:
|
|
175
|
-
"""Read a resource by URI."""
|
|
176
|
-
logger.debug(f"Reading resource: {uri}")
|
|
177
|
-
|
|
178
|
-
# For resources, we may need to handle binary data
|
|
179
|
-
if not self.session:
|
|
180
|
-
raise RuntimeError("HTTP session is not connected")
|
|
181
|
-
|
|
182
|
-
url = f"{self.base_url}/resources/read"
|
|
183
|
-
|
|
184
|
-
async with self.session.get(url, params={"uri": uri}) as response:
|
|
185
|
-
response.raise_for_status()
|
|
186
|
-
|
|
187
|
-
# Check if this is a JSON response or binary data
|
|
188
|
-
content_type = response.headers.get("Content-Type", "")
|
|
189
|
-
if "application/json" in content_type:
|
|
190
|
-
data = await response.json()
|
|
191
|
-
content = data.get("content", b"")
|
|
192
|
-
mime_type = data.get("mimeType", "")
|
|
193
|
-
|
|
194
|
-
# If content is base64 encoded, decode it
|
|
195
|
-
if isinstance(content, str):
|
|
196
|
-
import base64
|
|
197
|
-
|
|
198
|
-
content = base64.b64decode(content)
|
|
199
|
-
|
|
200
|
-
return content, mime_type
|
|
201
|
-
else:
|
|
202
|
-
# Assume binary response
|
|
203
|
-
content = await response.read()
|
|
204
|
-
return content, content_type
|
|
205
|
-
|
|
206
|
-
async def request(self, method: str, params: dict[str, Any] | None = None) -> Any:
|
|
207
|
-
"""Send a raw request to the MCP implementation."""
|
|
208
|
-
logger.debug(f"Sending request: {method} with params: {params}")
|
|
209
|
-
# For custom methods, we'll use the RPC-style endpoint
|
|
210
|
-
return await self._request("POST", "rpc", {"method": method, "params": params or {}})
|
mcp_use/connectors/stdio.py
CHANGED
|
@@ -6,13 +6,11 @@ through the standard input/output streams.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import sys
|
|
9
|
-
from typing import Any
|
|
10
9
|
|
|
11
10
|
from mcp import ClientSession, StdioServerParameters
|
|
12
|
-
from mcp.types import CallToolResult, Tool
|
|
13
11
|
|
|
14
12
|
from ..logging import logger
|
|
15
|
-
from ..task_managers import
|
|
13
|
+
from ..task_managers import StdioConnectionManager
|
|
16
14
|
from .base import BaseConnector
|
|
17
15
|
|
|
18
16
|
|
|
@@ -39,14 +37,11 @@ class StdioConnector(BaseConnector):
|
|
|
39
37
|
env: Optional environment variables.
|
|
40
38
|
errlog: Stream to write error output to.
|
|
41
39
|
"""
|
|
40
|
+
super().__init__()
|
|
42
41
|
self.command = command
|
|
43
42
|
self.args = args or [] # Ensure args is never None
|
|
44
43
|
self.env = env
|
|
45
44
|
self.errlog = errlog
|
|
46
|
-
self.client: ClientSession | None = None
|
|
47
|
-
self._connection_manager: ConnectionManager | None = None
|
|
48
|
-
self._tools: list[Tool] | None = None
|
|
49
|
-
self._connected = False
|
|
50
45
|
|
|
51
46
|
async def connect(self) -> None:
|
|
52
47
|
"""Establish a connection to the MCP implementation."""
|
|
@@ -81,108 +76,3 @@ class StdioConnector(BaseConnector):
|
|
|
81
76
|
|
|
82
77
|
# Re-raise the original exception
|
|
83
78
|
raise
|
|
84
|
-
|
|
85
|
-
async def disconnect(self) -> None:
|
|
86
|
-
"""Close the connection to the MCP implementation."""
|
|
87
|
-
if not self._connected:
|
|
88
|
-
logger.debug("Not connected to MCP implementation")
|
|
89
|
-
return
|
|
90
|
-
|
|
91
|
-
logger.info("Disconnecting from MCP implementation")
|
|
92
|
-
await self._cleanup_resources()
|
|
93
|
-
self._connected = False
|
|
94
|
-
logger.info("Disconnected from MCP implementation")
|
|
95
|
-
|
|
96
|
-
async def _cleanup_resources(self) -> None:
|
|
97
|
-
"""Clean up all resources associated with this connector."""
|
|
98
|
-
errors = []
|
|
99
|
-
|
|
100
|
-
# First close the client session
|
|
101
|
-
if self.client:
|
|
102
|
-
try:
|
|
103
|
-
logger.debug("Closing client session")
|
|
104
|
-
await self.client.__aexit__(None, None, None)
|
|
105
|
-
except Exception as e:
|
|
106
|
-
error_msg = f"Error closing client session: {e}"
|
|
107
|
-
logger.warning(error_msg)
|
|
108
|
-
errors.append(error_msg)
|
|
109
|
-
finally:
|
|
110
|
-
self.client = None
|
|
111
|
-
|
|
112
|
-
# Then stop the connection manager
|
|
113
|
-
if self._connection_manager:
|
|
114
|
-
try:
|
|
115
|
-
logger.debug("Stopping connection manager")
|
|
116
|
-
await self._connection_manager.stop()
|
|
117
|
-
except Exception as e:
|
|
118
|
-
error_msg = f"Error stopping connection manager: {e}"
|
|
119
|
-
logger.warning(error_msg)
|
|
120
|
-
errors.append(error_msg)
|
|
121
|
-
finally:
|
|
122
|
-
self._connection_manager = None
|
|
123
|
-
|
|
124
|
-
# Reset tools
|
|
125
|
-
self._tools = None
|
|
126
|
-
|
|
127
|
-
if errors:
|
|
128
|
-
logger.warning(f"Encountered {len(errors)} errors during resource cleanup")
|
|
129
|
-
|
|
130
|
-
async def initialize(self) -> dict[str, Any]:
|
|
131
|
-
"""Initialize the MCP session and return session information."""
|
|
132
|
-
if not self.client:
|
|
133
|
-
raise RuntimeError("MCP client is not connected")
|
|
134
|
-
|
|
135
|
-
logger.info("Initializing MCP session")
|
|
136
|
-
|
|
137
|
-
# Initialize the session
|
|
138
|
-
result = await self.client.initialize()
|
|
139
|
-
|
|
140
|
-
# Get available tools
|
|
141
|
-
tools_result = await self.client.list_tools()
|
|
142
|
-
self._tools = tools_result.tools
|
|
143
|
-
|
|
144
|
-
logger.info(f"MCP session initialized with {len(self._tools)} tools")
|
|
145
|
-
|
|
146
|
-
return result
|
|
147
|
-
|
|
148
|
-
@property
|
|
149
|
-
def tools(self) -> list[Tool]:
|
|
150
|
-
"""Get the list of available tools."""
|
|
151
|
-
if not self._tools:
|
|
152
|
-
raise RuntimeError("MCP client is not initialized")
|
|
153
|
-
return self._tools
|
|
154
|
-
|
|
155
|
-
async def call_tool(self, name: str, arguments: dict[str, Any]) -> CallToolResult:
|
|
156
|
-
"""Call an MCP tool with the given arguments."""
|
|
157
|
-
if not self.client:
|
|
158
|
-
raise RuntimeError("MCP client is not connected")
|
|
159
|
-
|
|
160
|
-
logger.debug(f"Calling tool '{name}' with arguments: {arguments}")
|
|
161
|
-
result = await self.client.call_tool(name, arguments)
|
|
162
|
-
return result
|
|
163
|
-
|
|
164
|
-
async def list_resources(self) -> list[dict[str, Any]]:
|
|
165
|
-
"""List all available resources from the MCP implementation."""
|
|
166
|
-
if not self.client:
|
|
167
|
-
raise RuntimeError("MCP client is not connected")
|
|
168
|
-
|
|
169
|
-
logger.debug("Listing resources")
|
|
170
|
-
resources = await self.client.list_resources()
|
|
171
|
-
return resources
|
|
172
|
-
|
|
173
|
-
async def read_resource(self, uri: str) -> tuple[bytes, str]:
|
|
174
|
-
"""Read a resource by URI."""
|
|
175
|
-
if not self.client:
|
|
176
|
-
raise RuntimeError("MCP client is not connected")
|
|
177
|
-
|
|
178
|
-
logger.debug(f"Reading resource: {uri}")
|
|
179
|
-
resource = await self.client.read_resource(uri)
|
|
180
|
-
return resource.content, resource.mimeType
|
|
181
|
-
|
|
182
|
-
async def request(self, method: str, params: dict[str, Any] | None = None) -> Any:
|
|
183
|
-
"""Send a raw request to the MCP implementation."""
|
|
184
|
-
if not self.client:
|
|
185
|
-
raise RuntimeError("MCP client is not connected")
|
|
186
|
-
|
|
187
|
-
logger.debug(f"Sending request: {method} with params: {params}")
|
|
188
|
-
return await self.client.request({"method": method, "params": params or {}})
|
|
@@ -6,7 +6,7 @@ through different transport mechanisms.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from .base import ConnectionManager
|
|
9
|
-
from .
|
|
9
|
+
from .sse import SseConnectionManager
|
|
10
10
|
from .stdio import StdioConnectionManager
|
|
11
11
|
from .websocket import WebSocketConnectionManager
|
|
12
12
|
|
|
@@ -15,4 +15,5 @@ __all__ = [
|
|
|
15
15
|
"HttpConnectionManager",
|
|
16
16
|
"StdioConnectionManager",
|
|
17
17
|
"WebSocketConnectionManager",
|
|
18
|
+
"SseConnectionManager",
|
|
18
19
|
]
|
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
from mcp.client.sse import sse_client
|
|
11
|
+
|
|
12
|
+
from ..logging import logger
|
|
13
|
+
from .base import ConnectionManager
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SseConnectionManager(ConnectionManager[tuple[Any, Any]]):
|
|
17
|
+
"""Connection manager for SSE-based MCP connections.
|
|
18
|
+
|
|
19
|
+
This class handles the proper task isolation for sse_client context managers
|
|
20
|
+
to prevent the "cancel scope in different task" error. It runs the sse_client
|
|
21
|
+
in a dedicated task and manages its lifecycle.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
url: str,
|
|
27
|
+
headers: dict[str, str] | None = None,
|
|
28
|
+
timeout: float = 5,
|
|
29
|
+
sse_read_timeout: float = 60 * 5,
|
|
30
|
+
):
|
|
31
|
+
"""Initialize a new SSE connection manager.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
url: The SSE endpoint URL
|
|
35
|
+
headers: Optional HTTP headers
|
|
36
|
+
timeout: Timeout for HTTP operations in seconds
|
|
37
|
+
sse_read_timeout: Timeout for SSE read operations in seconds
|
|
38
|
+
"""
|
|
39
|
+
super().__init__()
|
|
40
|
+
self.url = url
|
|
41
|
+
self.headers = headers or {}
|
|
42
|
+
self.timeout = timeout
|
|
43
|
+
self.sse_read_timeout = sse_read_timeout
|
|
44
|
+
self._sse_ctx = None
|
|
45
|
+
|
|
46
|
+
async def _establish_connection(self) -> tuple[Any, Any]:
|
|
47
|
+
"""Establish an SSE connection.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
A tuple of (read_stream, write_stream)
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
Exception: If connection cannot be established.
|
|
54
|
+
"""
|
|
55
|
+
# Create the context manager
|
|
56
|
+
self._sse_ctx = sse_client(
|
|
57
|
+
url=self.url,
|
|
58
|
+
headers=self.headers,
|
|
59
|
+
timeout=self.timeout,
|
|
60
|
+
sse_read_timeout=self.sse_read_timeout,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Enter the context manager
|
|
64
|
+
read_stream, write_stream = await self._sse_ctx.__aenter__()
|
|
65
|
+
|
|
66
|
+
# Return the streams
|
|
67
|
+
return (read_stream, write_stream)
|
|
68
|
+
|
|
69
|
+
async def _close_connection(self, connection: tuple[Any, Any]) -> None:
|
|
70
|
+
"""Close the SSE connection.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
connection: The connection to close (ignored, we use the context manager)
|
|
74
|
+
"""
|
|
75
|
+
if self._sse_ctx:
|
|
76
|
+
# Exit the context manager
|
|
77
|
+
try:
|
|
78
|
+
await self._sse_ctx.__aexit__(None, None, None)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
logger.warning(f"Error closing SSE context: {e}")
|
|
81
|
+
finally:
|
|
82
|
+
self._sse_ctx = None
|
|
@@ -5,13 +5,13 @@ This module provides a connection manager for WebSocket-based MCP connections.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import websockets
|
|
8
|
-
from websockets.client import
|
|
8
|
+
from websockets.client import ClientConnection
|
|
9
9
|
|
|
10
10
|
from ..logging import logger
|
|
11
11
|
from .base import ConnectionManager
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class WebSocketConnectionManager(ConnectionManager[
|
|
14
|
+
class WebSocketConnectionManager(ConnectionManager[ClientConnection]):
|
|
15
15
|
"""Connection manager for WebSocket-based MCP connections.
|
|
16
16
|
|
|
17
17
|
This class handles the lifecycle of WebSocket connections, ensuring proper
|
|
@@ -33,7 +33,7 @@ class WebSocketConnectionManager(ConnectionManager[WebSocketClientProtocol]):
|
|
|
33
33
|
self.url = url
|
|
34
34
|
self.headers = headers or {}
|
|
35
35
|
|
|
36
|
-
async def _establish_connection(self) ->
|
|
36
|
+
async def _establish_connection(self) -> ClientConnection:
|
|
37
37
|
"""Establish a WebSocket connection.
|
|
38
38
|
|
|
39
39
|
Returns:
|
|
@@ -50,7 +50,7 @@ class WebSocketConnectionManager(ConnectionManager[WebSocketClientProtocol]):
|
|
|
50
50
|
logger.error(f"Failed to connect to WebSocket: {e}")
|
|
51
51
|
raise
|
|
52
52
|
|
|
53
|
-
async def _close_connection(self, connection:
|
|
53
|
+
async def _close_connection(self, connection: ClientConnection) -> None:
|
|
54
54
|
"""Close the WebSocket connection.
|
|
55
55
|
|
|
56
56
|
Args:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-use
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: MCP Library for LLMs
|
|
5
5
|
Author-email: Pietro Zullo <pietro.zullo@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -38,7 +38,7 @@ Requires-Dist: openai>=1.10.0; extra == 'openai'
|
|
|
38
38
|
Description-Content-Type: text/markdown
|
|
39
39
|
|
|
40
40
|
<picture>
|
|
41
|
-
<img alt="" src="./static/
|
|
41
|
+
<img alt="" src="./static/image.jpg" width="full">
|
|
42
42
|
</picture>
|
|
43
43
|
|
|
44
44
|
<h1 align="center">Open Source MCP CLient Library </h1>
|
|
@@ -47,6 +47,7 @@ Description-Content-Type: text/markdown
|
|
|
47
47
|
[](https://pypi.org/project/mcp_use/)
|
|
48
48
|
[](https://pypi.org/project/mcp_use/)
|
|
49
49
|
[](https://pypi.org/project/mcp_use/)
|
|
50
|
+
[](https://docs.mcp-use.io)
|
|
50
51
|
[](https://github.com/pietrozullo/mcp-use/blob/main/LICENSE)
|
|
51
52
|
[](https://github.com/astral-sh/ruff)
|
|
52
53
|
[](https://github.com/pietrozullo/mcp-use/stargazers)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
mcp_use/__init__.py,sha256=PSoxLAu1GPjfIDPcZiJyI3k66MMS3lcfx5kERUgFb1o,723
|
|
2
2
|
mcp_use/client.py,sha256=0rvlJBwvPD19sjDRtXfnp15-F1VHJlXWxLQNt9cHwPA,8275
|
|
3
|
-
mcp_use/config.py,sha256=
|
|
3
|
+
mcp_use/config.py,sha256=O9V4pa-shZ2mPokRTrd7KZQ2GpuTcYBGUslefl1fosw,1653
|
|
4
4
|
mcp_use/logging.py,sha256=2-hSB7ZWcHEx_OFHNg8GIbSGCZx3MW4mZGGWxi2Ew3E,2690
|
|
5
5
|
mcp_use/session.py,sha256=Z4EZTUnQUX0QyGMzkJIrMRTX4SDk6qQUoBld408LIJE,3449
|
|
6
6
|
mcp_use/agents/__init__.py,sha256=ukchMTqCOID6ikvLmJ-6sldWTVFIzztGQo4BX6QeQr8,312
|
|
@@ -9,16 +9,16 @@ mcp_use/agents/langchain_agent.py,sha256=q6zIb9J9fc15HRGDjPAhmPdM_8UOqQToy8ESeyr
|
|
|
9
9
|
mcp_use/agents/mcpagent.py,sha256=lTRutdT1QIMiTbMSKfSbqlqNq_Y6uDPfkjAzJAKb6H0,12727
|
|
10
10
|
mcp_use/agents/prompts/default.py,sha256=tnwt9vOiVBhdpu-lIHhwEJo3rvE6EobPfUgS9JURBzg,941
|
|
11
11
|
mcp_use/connectors/__init__.py,sha256=jnd-7pPPJMb0UNJ6aD9lInj5Tlamc8lA_mFyG8RWJpo,385
|
|
12
|
-
mcp_use/connectors/base.py,sha256=
|
|
13
|
-
mcp_use/connectors/http.py,sha256=
|
|
14
|
-
mcp_use/connectors/stdio.py,sha256=
|
|
12
|
+
mcp_use/connectors/base.py,sha256=TCLVNJdt6qrflmphgXOZhD6xPKQQegbGqe5REmcLYg0,4813
|
|
13
|
+
mcp_use/connectors/http.py,sha256=nkF3hcioYpglwLOTR69WhU8zzLJbZDakgtQ8MsK-cMI,2835
|
|
14
|
+
mcp_use/connectors/stdio.py,sha256=36yEY7hbFXgM7zju5BtLJl4cKAZNCXVRrTaU6YOttBI,2606
|
|
15
15
|
mcp_use/connectors/websocket.py,sha256=4xqxl9UncrfU6NitvKfB80Hk2g7o0Gc0G5sm6sY3RAk,9534
|
|
16
|
-
mcp_use/task_managers/__init__.py,sha256=
|
|
16
|
+
mcp_use/task_managers/__init__.py,sha256=4dgW5N61iiPLpwjU2rrn_uqrL8mmDJFDaF9Lukzk65A,486
|
|
17
17
|
mcp_use/task_managers/base.py,sha256=ksNdxTwq8N-zqymxVoKGnWXq9iqkLYC61uB91o6Mh-4,4888
|
|
18
|
-
mcp_use/task_managers/
|
|
18
|
+
mcp_use/task_managers/sse.py,sha256=WysmjwqRI3meXMZY_F4y9tSBMvSiUZfTJQfitM5l6jQ,2529
|
|
19
19
|
mcp_use/task_managers/stdio.py,sha256=DEISpXv4mo3d5a-WT8lkWbrXJwUh7QW0nMT_IM3fHGg,2269
|
|
20
|
-
mcp_use/task_managers/websocket.py,sha256=
|
|
21
|
-
mcp_use-1.0.
|
|
22
|
-
mcp_use-1.0.
|
|
23
|
-
mcp_use-1.0.
|
|
24
|
-
mcp_use-1.0.
|
|
20
|
+
mcp_use/task_managers/websocket.py,sha256=ZbCqdGgzCRtsXzRGFws-f2OzH8cPAkN4sJNDwEpRmCc,1915
|
|
21
|
+
mcp_use-1.0.2.dist-info/METADATA,sha256=4Tqt1IT7KtUS5HVF9VIw0dt6Sgf2lBslMlv5ztkAg8k,10203
|
|
22
|
+
mcp_use-1.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
23
|
+
mcp_use-1.0.2.dist-info/licenses/LICENSE,sha256=7Pw7dbwJSBw8zH-WE03JnR5uXvitRtaGTP9QWPcexcs,1068
|
|
24
|
+
mcp_use-1.0.2.dist-info/RECORD,,
|
mcp_use/task_managers/http.py
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
HTTP connection management for MCP implementations.
|
|
3
|
-
|
|
4
|
-
This module provides a connection manager for HTTP-based MCP connections.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import aiohttp
|
|
8
|
-
|
|
9
|
-
from ..logging import logger
|
|
10
|
-
from .base import ConnectionManager
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class HttpConnectionManager(ConnectionManager[aiohttp.ClientSession]):
|
|
14
|
-
"""Connection manager for HTTP-based MCP connections.
|
|
15
|
-
|
|
16
|
-
This class handles the lifecycle of HTTP client sessions, ensuring proper
|
|
17
|
-
connection establishment and cleanup.
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
def __init__(
|
|
21
|
-
self,
|
|
22
|
-
base_url: str,
|
|
23
|
-
headers: dict[str, str] | None = None,
|
|
24
|
-
):
|
|
25
|
-
"""Initialize a new HTTP connection manager.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
base_url: The base URL for HTTP requests
|
|
29
|
-
headers: Optional headers to include in all HTTP requests
|
|
30
|
-
"""
|
|
31
|
-
super().__init__()
|
|
32
|
-
self.base_url = base_url.rstrip("/")
|
|
33
|
-
self.headers = headers or {}
|
|
34
|
-
|
|
35
|
-
async def _establish_connection(self) -> aiohttp.ClientSession:
|
|
36
|
-
"""Establish an HTTP client session.
|
|
37
|
-
|
|
38
|
-
Returns:
|
|
39
|
-
The established HTTP client session
|
|
40
|
-
|
|
41
|
-
Raises:
|
|
42
|
-
Exception: If session cannot be established
|
|
43
|
-
"""
|
|
44
|
-
logger.debug(f"Creating HTTP client session for: {self.base_url}")
|
|
45
|
-
try:
|
|
46
|
-
session = aiohttp.ClientSession(headers=self.headers)
|
|
47
|
-
return session
|
|
48
|
-
except Exception as e:
|
|
49
|
-
logger.error(f"Failed to create HTTP client session: {e}")
|
|
50
|
-
raise
|
|
51
|
-
|
|
52
|
-
async def _close_connection(self, connection: aiohttp.ClientSession) -> None:
|
|
53
|
-
"""Close the HTTP client session.
|
|
54
|
-
|
|
55
|
-
Args:
|
|
56
|
-
connection: The HTTP client session to close
|
|
57
|
-
"""
|
|
58
|
-
try:
|
|
59
|
-
logger.debug("Closing HTTP client session")
|
|
60
|
-
await connection.close()
|
|
61
|
-
except Exception as e:
|
|
62
|
-
logger.warning(f"Error closing HTTP client session: {e}")
|
|
File without changes
|
|
File without changes
|