mcp-use 0.0.3__py3-none-any.whl → 0.0.4__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/agents/langchain_agent.py +46 -6
- mcp_use/agents/mcpagent.py +198 -39
- mcp_use/agents/prompts/default.py +11 -0
- mcp_use/client.py +44 -28
- mcp_use/connectors/http.py +91 -7
- mcp_use/connectors/stdio.py +97 -33
- mcp_use/connectors/websocket.py +124 -21
- mcp_use/task_managers/__init__.py +18 -0
- mcp_use/task_managers/base.py +151 -0
- mcp_use/task_managers/http.py +62 -0
- mcp_use/task_managers/stdio.py +73 -0
- mcp_use/task_managers/websocket.py +63 -0
- {mcp_use-0.0.3.dist-info → mcp_use-0.0.4.dist-info}/METADATA +24 -36
- mcp_use-0.0.4.dist-info/RECORD +24 -0
- {mcp_use-0.0.3.dist-info → mcp_use-0.0.4.dist-info}/WHEEL +1 -2
- mcp_use-0.0.3.dist-info/RECORD +0 -19
- mcp_use-0.0.3.dist-info/top_level.txt +0 -1
- {mcp_use-0.0.3.dist-info → mcp_use-0.0.4.dist-info}/licenses/LICENSE +0 -0
mcp_use/connectors/http.py
CHANGED
|
@@ -8,18 +8,25 @@ through HTTP APIs.
|
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
10
|
import aiohttp
|
|
11
|
+
from mcp.types import Tool
|
|
11
12
|
|
|
13
|
+
from ..logging import logger
|
|
14
|
+
from ..task_managers import ConnectionManager, HttpConnectionManager
|
|
12
15
|
from .base import BaseConnector
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
class HttpConnector(BaseConnector):
|
|
16
19
|
"""Connector for MCP implementations using HTTP transport.
|
|
17
20
|
|
|
18
|
-
This connector uses HTTP requests to communicate with remote MCP implementations
|
|
21
|
+
This connector uses HTTP requests to communicate with remote MCP implementations,
|
|
22
|
+
using a connection manager to handle the proper lifecycle management.
|
|
19
23
|
"""
|
|
20
24
|
|
|
21
25
|
def __init__(
|
|
22
|
-
self,
|
|
26
|
+
self,
|
|
27
|
+
base_url: str,
|
|
28
|
+
auth_token: str | None = None,
|
|
29
|
+
headers: dict[str, str] | None = None,
|
|
23
30
|
):
|
|
24
31
|
"""Initialize a new HTTP connector.
|
|
25
32
|
|
|
@@ -33,17 +40,70 @@ class HttpConnector(BaseConnector):
|
|
|
33
40
|
self.headers = headers or {}
|
|
34
41
|
if auth_token:
|
|
35
42
|
self.headers["Authorization"] = f"Bearer {auth_token}"
|
|
43
|
+
|
|
36
44
|
self.session: aiohttp.ClientSession | None = None
|
|
45
|
+
self._connection_manager: ConnectionManager | None = None
|
|
46
|
+
self._tools: list[Tool] | None = None
|
|
47
|
+
self._connected = False
|
|
37
48
|
|
|
38
49
|
async def connect(self) -> None:
|
|
39
50
|
"""Establish a connection to the MCP implementation."""
|
|
40
|
-
|
|
51
|
+
if self._connected:
|
|
52
|
+
logger.debug("Already connected to MCP implementation")
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
logger.info(f"Connecting to MCP implementation via HTTP: {self.base_url}")
|
|
56
|
+
try:
|
|
57
|
+
# Create and start the connection manager
|
|
58
|
+
self._connection_manager = HttpConnectionManager(self.base_url, self.headers)
|
|
59
|
+
self.session = await self._connection_manager.start()
|
|
60
|
+
|
|
61
|
+
# Mark as connected
|
|
62
|
+
self._connected = True
|
|
63
|
+
logger.info(f"Successfully connected to MCP implementation via HTTP: {self.base_url}")
|
|
64
|
+
|
|
65
|
+
except Exception as e:
|
|
66
|
+
logger.error(f"Failed to connect to MCP implementation via HTTP: {e}")
|
|
67
|
+
|
|
68
|
+
# Clean up any resources if connection failed
|
|
69
|
+
await self._cleanup_resources()
|
|
70
|
+
|
|
71
|
+
# Re-raise the original exception
|
|
72
|
+
raise
|
|
41
73
|
|
|
42
74
|
async def disconnect(self) -> None:
|
|
43
75
|
"""Close the connection to the MCP implementation."""
|
|
44
|
-
if self.
|
|
45
|
-
|
|
46
|
-
|
|
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")
|
|
47
107
|
|
|
48
108
|
async def _request(self, method: str, endpoint: str, data: dict[str, Any] | None = None) -> Any:
|
|
49
109
|
"""Send an HTTP request to the MCP API.
|
|
@@ -60,6 +120,7 @@ class HttpConnector(BaseConnector):
|
|
|
60
120
|
raise RuntimeError("HTTP session is not connected")
|
|
61
121
|
|
|
62
122
|
url = f"{self.base_url}/{endpoint.lstrip('/')}"
|
|
123
|
+
logger.debug(f"Sending {method} request to {url}")
|
|
63
124
|
|
|
64
125
|
if method.upper() == "GET" and data:
|
|
65
126
|
# For GET requests, convert data to query parameters
|
|
@@ -74,24 +135,46 @@ class HttpConnector(BaseConnector):
|
|
|
74
135
|
|
|
75
136
|
async def initialize(self) -> dict[str, Any]:
|
|
76
137
|
"""Initialize the MCP session and return session information."""
|
|
77
|
-
|
|
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
|
|
78
149
|
|
|
79
150
|
async def list_tools(self) -> list[dict[str, Any]]:
|
|
80
151
|
"""List all available tools from the MCP implementation."""
|
|
152
|
+
logger.debug("Listing tools")
|
|
81
153
|
result = await self._request("GET", "tools")
|
|
82
154
|
return result.get("tools", [])
|
|
83
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
|
+
|
|
84
163
|
async def call_tool(self, name: str, arguments: dict[str, Any]) -> Any:
|
|
85
164
|
"""Call an MCP tool with the given arguments."""
|
|
165
|
+
logger.debug(f"Calling tool '{name}' with arguments: {arguments}")
|
|
86
166
|
return await self._request("POST", f"tools/{name}", arguments)
|
|
87
167
|
|
|
88
168
|
async def list_resources(self) -> list[dict[str, Any]]:
|
|
89
169
|
"""List all available resources from the MCP implementation."""
|
|
170
|
+
logger.debug("Listing resources")
|
|
90
171
|
result = await self._request("GET", "resources")
|
|
91
172
|
return result
|
|
92
173
|
|
|
93
174
|
async def read_resource(self, uri: str) -> tuple[bytes, str]:
|
|
94
175
|
"""Read a resource by URI."""
|
|
176
|
+
logger.debug(f"Reading resource: {uri}")
|
|
177
|
+
|
|
95
178
|
# For resources, we may need to handle binary data
|
|
96
179
|
if not self.session:
|
|
97
180
|
raise RuntimeError("HTTP session is not connected")
|
|
@@ -122,5 +205,6 @@ class HttpConnector(BaseConnector):
|
|
|
122
205
|
|
|
123
206
|
async def request(self, method: str, params: dict[str, Any] | None = None) -> Any:
|
|
124
207
|
"""Send a raw request to the MCP implementation."""
|
|
208
|
+
logger.debug(f"Sending request: {method} with params: {params}")
|
|
125
209
|
# For custom methods, we'll use the RPC-style endpoint
|
|
126
210
|
return await self._request("POST", "rpc", {"method": method, "params": params or {}})
|
mcp_use/connectors/stdio.py
CHANGED
|
@@ -5,13 +5,14 @@ This module provides a connector for communicating with MCP implementations
|
|
|
5
5
|
through the standard input/output streams.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import sys
|
|
8
9
|
from typing import Any
|
|
9
10
|
|
|
10
11
|
from mcp import ClientSession, StdioServerParameters
|
|
11
|
-
from mcp.
|
|
12
|
-
from mcp.types import Tool
|
|
12
|
+
from mcp.types import CallToolResult, Tool
|
|
13
13
|
|
|
14
14
|
from ..logging import logger
|
|
15
|
+
from ..task_managers import ConnectionManager, StdioConnectionManager
|
|
15
16
|
from .base import BaseConnector
|
|
16
17
|
|
|
17
18
|
|
|
@@ -19,7 +20,8 @@ class StdioConnector(BaseConnector):
|
|
|
19
20
|
"""Connector for MCP implementations using stdio transport.
|
|
20
21
|
|
|
21
22
|
This connector uses the stdio transport to communicate with MCP implementations
|
|
22
|
-
that are executed as child processes.
|
|
23
|
+
that are executed as child processes. It uses a connection manager to handle
|
|
24
|
+
the proper lifecycle management of the stdio client.
|
|
23
25
|
"""
|
|
24
26
|
|
|
25
27
|
def __init__(
|
|
@@ -27,6 +29,7 @@ class StdioConnector(BaseConnector):
|
|
|
27
29
|
command: str = "npx",
|
|
28
30
|
args: list[str] | None = None,
|
|
29
31
|
env: dict[str, str] | None = None,
|
|
32
|
+
errlog=sys.stderr,
|
|
30
33
|
):
|
|
31
34
|
"""Initialize a new stdio connector.
|
|
32
35
|
|
|
@@ -34,53 +37,103 @@ class StdioConnector(BaseConnector):
|
|
|
34
37
|
command: The command to execute.
|
|
35
38
|
args: Optional command line arguments.
|
|
36
39
|
env: Optional environment variables.
|
|
40
|
+
errlog: Stream to write error output to.
|
|
37
41
|
"""
|
|
38
42
|
self.command = command
|
|
39
|
-
self.args = args
|
|
43
|
+
self.args = args or [] # Ensure args is never None
|
|
40
44
|
self.env = env
|
|
45
|
+
self.errlog = errlog
|
|
41
46
|
self.client: ClientSession | None = None
|
|
42
|
-
self.
|
|
47
|
+
self._connection_manager: ConnectionManager | None = None
|
|
43
48
|
self._tools: list[Tool] | None = None
|
|
49
|
+
self._connected = False
|
|
44
50
|
|
|
45
51
|
async def connect(self) -> None:
|
|
46
52
|
"""Establish a connection to the MCP implementation."""
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
if self._connected:
|
|
54
|
+
logger.debug("Already connected to MCP implementation")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
logger.info(f"Connecting to MCP implementation: {self.command}")
|
|
58
|
+
try:
|
|
59
|
+
# Create server parameters
|
|
60
|
+
server_params = StdioServerParameters(
|
|
61
|
+
command=self.command, args=self.args, env=self.env
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Create and start the connection manager
|
|
65
|
+
self._connection_manager = StdioConnectionManager(server_params, self.errlog)
|
|
66
|
+
read_stream, write_stream = await self._connection_manager.start()
|
|
67
|
+
|
|
68
|
+
# Create the client session
|
|
69
|
+
self.client = ClientSession(read_stream, write_stream, sampling_callback=None)
|
|
70
|
+
await self.client.__aenter__()
|
|
71
|
+
|
|
72
|
+
# Mark as connected
|
|
73
|
+
self._connected = True
|
|
74
|
+
logger.info(f"Successfully connected to MCP implementation: {self.command}")
|
|
75
|
+
|
|
76
|
+
except Exception as e:
|
|
77
|
+
logger.error(f"Failed to connect to MCP implementation: {e}")
|
|
78
|
+
|
|
79
|
+
# Clean up any resources if connection failed
|
|
80
|
+
await self._cleanup_resources()
|
|
81
|
+
|
|
82
|
+
# Re-raise the original exception
|
|
83
|
+
raise
|
|
52
84
|
|
|
53
85
|
async def disconnect(self) -> None:
|
|
54
86
|
"""Close the connection to the MCP implementation."""
|
|
55
|
-
|
|
56
|
-
|
|
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")
|
|
57
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:
|
|
58
110
|
self.client = None
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
await self.disconnect()
|
|
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")
|
|
78
129
|
|
|
79
130
|
async def initialize(self) -> dict[str, Any]:
|
|
80
131
|
"""Initialize the MCP session and return session information."""
|
|
81
132
|
if not self.client:
|
|
82
133
|
raise RuntimeError("MCP client is not connected")
|
|
83
134
|
|
|
135
|
+
logger.info("Initializing MCP session")
|
|
136
|
+
|
|
84
137
|
# Initialize the session
|
|
85
138
|
result = await self.client.initialize()
|
|
86
139
|
|
|
@@ -88,6 +141,8 @@ class StdioConnector(BaseConnector):
|
|
|
88
141
|
tools_result = await self.client.list_tools()
|
|
89
142
|
self._tools = tools_result.tools
|
|
90
143
|
|
|
144
|
+
logger.info(f"MCP session initialized with {len(self._tools)} tools")
|
|
145
|
+
|
|
91
146
|
return result
|
|
92
147
|
|
|
93
148
|
@property
|
|
@@ -97,16 +152,21 @@ class StdioConnector(BaseConnector):
|
|
|
97
152
|
raise RuntimeError("MCP client is not initialized")
|
|
98
153
|
return self._tools
|
|
99
154
|
|
|
100
|
-
async def call_tool(self, name: str, arguments: dict[str, Any]) ->
|
|
155
|
+
async def call_tool(self, name: str, arguments: dict[str, Any]) -> CallToolResult:
|
|
101
156
|
"""Call an MCP tool with the given arguments."""
|
|
102
157
|
if not self.client:
|
|
103
158
|
raise RuntimeError("MCP client is not connected")
|
|
104
|
-
|
|
159
|
+
|
|
160
|
+
logger.debug(f"Calling tool '{name}' with arguments: {arguments}")
|
|
161
|
+
result = await self.client.call_tool(name, arguments)
|
|
162
|
+
return result
|
|
105
163
|
|
|
106
164
|
async def list_resources(self) -> list[dict[str, Any]]:
|
|
107
165
|
"""List all available resources from the MCP implementation."""
|
|
108
166
|
if not self.client:
|
|
109
167
|
raise RuntimeError("MCP client is not connected")
|
|
168
|
+
|
|
169
|
+
logger.debug("Listing resources")
|
|
110
170
|
resources = await self.client.list_resources()
|
|
111
171
|
return resources
|
|
112
172
|
|
|
@@ -114,6 +174,8 @@ class StdioConnector(BaseConnector):
|
|
|
114
174
|
"""Read a resource by URI."""
|
|
115
175
|
if not self.client:
|
|
116
176
|
raise RuntimeError("MCP client is not connected")
|
|
177
|
+
|
|
178
|
+
logger.debug(f"Reading resource: {uri}")
|
|
117
179
|
resource = await self.client.read_resource(uri)
|
|
118
180
|
return resource.content, resource.mimeType
|
|
119
181
|
|
|
@@ -121,4 +183,6 @@ class StdioConnector(BaseConnector):
|
|
|
121
183
|
"""Send a raw request to the MCP implementation."""
|
|
122
184
|
if not self.client:
|
|
123
185
|
raise RuntimeError("MCP client is not connected")
|
|
186
|
+
|
|
187
|
+
logger.debug(f"Sending request: {method} with params: {params}")
|
|
124
188
|
return await self.client.request({"method": method, "params": params or {}})
|
mcp_use/connectors/websocket.py
CHANGED
|
@@ -10,20 +10,26 @@ import json
|
|
|
10
10
|
import uuid
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
|
-
import
|
|
13
|
+
from mcp.types import Tool
|
|
14
14
|
from websockets.client import WebSocketClientProtocol
|
|
15
15
|
|
|
16
|
+
from ..logging import logger
|
|
17
|
+
from ..task_managers import ConnectionManager, WebSocketConnectionManager
|
|
16
18
|
from .base import BaseConnector
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
class WebSocketConnector(BaseConnector):
|
|
20
22
|
"""Connector for MCP implementations using WebSocket transport.
|
|
21
23
|
|
|
22
|
-
This connector uses WebSockets to communicate with remote MCP implementations
|
|
24
|
+
This connector uses WebSockets to communicate with remote MCP implementations,
|
|
25
|
+
using a connection manager to handle the proper lifecycle management.
|
|
23
26
|
"""
|
|
24
27
|
|
|
25
28
|
def __init__(
|
|
26
|
-
self,
|
|
29
|
+
self,
|
|
30
|
+
url: str,
|
|
31
|
+
auth_token: str | None = None,
|
|
32
|
+
headers: dict[str, str] | None = None,
|
|
27
33
|
):
|
|
28
34
|
"""Initialize a new WebSocket connector.
|
|
29
35
|
|
|
@@ -37,15 +43,43 @@ class WebSocketConnector(BaseConnector):
|
|
|
37
43
|
self.headers = headers or {}
|
|
38
44
|
if auth_token:
|
|
39
45
|
self.headers["Authorization"] = f"Bearer {auth_token}"
|
|
46
|
+
|
|
40
47
|
self.ws: WebSocketClientProtocol | None = None
|
|
48
|
+
self._connection_manager: ConnectionManager | None = None
|
|
49
|
+
self._receiver_task: asyncio.Task | None = None
|
|
41
50
|
self.pending_requests: dict[str, asyncio.Future] = {}
|
|
51
|
+
self._tools: list[Tool] | None = None
|
|
52
|
+
self._connected = False
|
|
42
53
|
|
|
43
54
|
async def connect(self) -> None:
|
|
44
55
|
"""Establish a connection to the MCP implementation."""
|
|
45
|
-
|
|
56
|
+
if self._connected:
|
|
57
|
+
logger.debug("Already connected to MCP implementation")
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
logger.info(f"Connecting to MCP implementation via WebSocket: {self.url}")
|
|
61
|
+
try:
|
|
62
|
+
# Create and start the connection manager
|
|
63
|
+
self._connection_manager = WebSocketConnectionManager(self.url, self.headers)
|
|
64
|
+
self.ws = await self._connection_manager.start()
|
|
46
65
|
|
|
47
|
-
|
|
48
|
-
|
|
66
|
+
# Start the message receiver task
|
|
67
|
+
self._receiver_task = asyncio.create_task(
|
|
68
|
+
self._receive_messages(), name="websocket_receiver_task"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Mark as connected
|
|
72
|
+
self._connected = True
|
|
73
|
+
logger.info(f"Successfully connected to MCP implementation via WebSocket: {self.url}")
|
|
74
|
+
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logger.error(f"Failed to connect to MCP implementation via WebSocket: {e}")
|
|
77
|
+
|
|
78
|
+
# Clean up any resources if connection failed
|
|
79
|
+
await self._cleanup_resources()
|
|
80
|
+
|
|
81
|
+
# Re-raise the original exception
|
|
82
|
+
raise
|
|
49
83
|
|
|
50
84
|
async def _receive_messages(self) -> None:
|
|
51
85
|
"""Continuously receive and process messages from the WebSocket."""
|
|
@@ -65,7 +99,12 @@ class WebSocketConnector(BaseConnector):
|
|
|
65
99
|
future.set_result(data["result"])
|
|
66
100
|
elif "error" in data:
|
|
67
101
|
future.set_exception(Exception(data["error"]))
|
|
102
|
+
|
|
103
|
+
logger.debug(f"Received response for request {request_id}")
|
|
104
|
+
else:
|
|
105
|
+
logger.debug(f"Received message: {data}")
|
|
68
106
|
except Exception as e:
|
|
107
|
+
logger.error(f"Error in WebSocket message receiver: {e}")
|
|
69
108
|
# If the websocket connection was closed or errored,
|
|
70
109
|
# reject all pending requests
|
|
71
110
|
for future in self.pending_requests.values():
|
|
@@ -74,22 +113,63 @@ class WebSocketConnector(BaseConnector):
|
|
|
74
113
|
|
|
75
114
|
async def disconnect(self) -> None:
|
|
76
115
|
"""Close the connection to the MCP implementation."""
|
|
77
|
-
if self.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
116
|
+
if not self._connected:
|
|
117
|
+
logger.debug("Not connected to MCP implementation")
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
logger.info("Disconnecting from MCP implementation")
|
|
121
|
+
await self._cleanup_resources()
|
|
122
|
+
self._connected = False
|
|
123
|
+
logger.info("Disconnected from MCP implementation")
|
|
83
124
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
125
|
+
async def _cleanup_resources(self) -> None:
|
|
126
|
+
"""Clean up all resources associated with this connector."""
|
|
127
|
+
errors = []
|
|
128
|
+
|
|
129
|
+
# First cancel the receiver task
|
|
130
|
+
if self._receiver_task and not self._receiver_task.done():
|
|
131
|
+
try:
|
|
132
|
+
logger.debug("Cancelling WebSocket receiver task")
|
|
133
|
+
self._receiver_task.cancel()
|
|
134
|
+
try:
|
|
135
|
+
await self._receiver_task
|
|
136
|
+
except asyncio.CancelledError:
|
|
137
|
+
logger.debug("WebSocket receiver task cancelled successfully")
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logger.warning(f"Error during WebSocket receiver task cancellation: {e}")
|
|
140
|
+
except Exception as e:
|
|
141
|
+
error_msg = f"Error cancelling WebSocket receiver task: {e}"
|
|
142
|
+
logger.warning(error_msg)
|
|
143
|
+
errors.append(error_msg)
|
|
144
|
+
finally:
|
|
145
|
+
self._receiver_task = None
|
|
87
146
|
|
|
88
147
|
# Reject any pending requests
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
148
|
+
if self.pending_requests:
|
|
149
|
+
logger.debug(f"Rejecting {len(self.pending_requests)} pending requests")
|
|
150
|
+
for future in self.pending_requests.values():
|
|
151
|
+
if not future.done():
|
|
152
|
+
future.set_exception(ConnectionError("WebSocket disconnected"))
|
|
153
|
+
self.pending_requests.clear()
|
|
154
|
+
|
|
155
|
+
# Then stop the connection manager
|
|
156
|
+
if self._connection_manager:
|
|
157
|
+
try:
|
|
158
|
+
logger.debug("Stopping connection manager")
|
|
159
|
+
await self._connection_manager.stop()
|
|
160
|
+
except Exception as e:
|
|
161
|
+
error_msg = f"Error stopping connection manager: {e}"
|
|
162
|
+
logger.warning(error_msg)
|
|
163
|
+
errors.append(error_msg)
|
|
164
|
+
finally:
|
|
165
|
+
self._connection_manager = None
|
|
166
|
+
self.ws = None
|
|
167
|
+
|
|
168
|
+
# Reset tools
|
|
169
|
+
self._tools = None
|
|
170
|
+
|
|
171
|
+
if errors:
|
|
172
|
+
logger.warning(f"Encountered {len(errors)} errors during resource cleanup")
|
|
93
173
|
|
|
94
174
|
async def _send_request(self, method: str, params: dict[str, Any] | None = None) -> Any:
|
|
95
175
|
"""Send a request and wait for a response."""
|
|
@@ -106,37 +186,60 @@ class WebSocketConnector(BaseConnector):
|
|
|
106
186
|
# Send the request
|
|
107
187
|
await self.ws.send(json.dumps({"id": request_id, "method": method, "params": params or {}}))
|
|
108
188
|
|
|
189
|
+
logger.debug(f"Sent request {request_id} method: {method}")
|
|
190
|
+
|
|
109
191
|
# Wait for the response
|
|
110
192
|
try:
|
|
111
193
|
return await future
|
|
112
194
|
except Exception as e:
|
|
113
195
|
# Remove the request from pending requests
|
|
114
196
|
self.pending_requests.pop(request_id, None)
|
|
115
|
-
|
|
197
|
+
logger.error(f"Error waiting for response to request {request_id}: {e}")
|
|
198
|
+
raise
|
|
116
199
|
|
|
117
200
|
async def initialize(self) -> dict[str, Any]:
|
|
118
201
|
"""Initialize the MCP session and return session information."""
|
|
119
|
-
|
|
202
|
+
logger.info("Initializing MCP session")
|
|
203
|
+
result = await self._send_request("initialize")
|
|
204
|
+
|
|
205
|
+
# Get available tools
|
|
206
|
+
tools_result = await self.list_tools()
|
|
207
|
+
self._tools = [Tool(**tool) for tool in tools_result]
|
|
208
|
+
|
|
209
|
+
logger.info(f"MCP session initialized with {len(self._tools)} tools")
|
|
210
|
+
return result
|
|
120
211
|
|
|
121
212
|
async def list_tools(self) -> list[dict[str, Any]]:
|
|
122
213
|
"""List all available tools from the MCP implementation."""
|
|
214
|
+
logger.debug("Listing tools")
|
|
123
215
|
result = await self._send_request("tools/list")
|
|
124
216
|
return result.get("tools", [])
|
|
125
217
|
|
|
218
|
+
@property
|
|
219
|
+
def tools(self) -> list[Tool]:
|
|
220
|
+
"""Get the list of available tools."""
|
|
221
|
+
if not self._tools:
|
|
222
|
+
raise RuntimeError("MCP client is not initialized")
|
|
223
|
+
return self._tools
|
|
224
|
+
|
|
126
225
|
async def call_tool(self, name: str, arguments: dict[str, Any]) -> Any:
|
|
127
226
|
"""Call an MCP tool with the given arguments."""
|
|
227
|
+
logger.debug(f"Calling tool '{name}' with arguments: {arguments}")
|
|
128
228
|
return await self._send_request("tools/call", {"name": name, "arguments": arguments})
|
|
129
229
|
|
|
130
230
|
async def list_resources(self) -> list[dict[str, Any]]:
|
|
131
231
|
"""List all available resources from the MCP implementation."""
|
|
232
|
+
logger.debug("Listing resources")
|
|
132
233
|
result = await self._send_request("resources/list")
|
|
133
234
|
return result
|
|
134
235
|
|
|
135
236
|
async def read_resource(self, uri: str) -> tuple[bytes, str]:
|
|
136
237
|
"""Read a resource by URI."""
|
|
238
|
+
logger.debug(f"Reading resource: {uri}")
|
|
137
239
|
result = await self._send_request("resources/read", {"uri": uri})
|
|
138
240
|
return result.get("content", b""), result.get("mimeType", "")
|
|
139
241
|
|
|
140
242
|
async def request(self, method: str, params: dict[str, Any] | None = None) -> Any:
|
|
141
243
|
"""Send a raw request to the MCP implementation."""
|
|
244
|
+
logger.debug(f"Sending request: {method} with params: {params}")
|
|
142
245
|
return await self._send_request(method, params)
|
|
@@ -0,0 +1,18 @@
|
|
|
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 .http import HttpConnectionManager
|
|
10
|
+
from .stdio import StdioConnectionManager
|
|
11
|
+
from .websocket import WebSocketConnectionManager
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"ConnectionManager",
|
|
15
|
+
"HttpConnectionManager",
|
|
16
|
+
"StdioConnectionManager",
|
|
17
|
+
"WebSocketConnectionManager",
|
|
18
|
+
]
|