mcp-use 0.1.0__py3-none-any.whl → 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mcp-use might be problematic. Click here for more details.

@@ -5,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.client.stdio import stdio_client
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._stdio_ctx = None
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
- server_params = StdioServerParameters(command=self.command, args=self.args, env=self.env)
48
- self._stdio_ctx = stdio_client(server_params)
49
- read_stream, write_stream = await self._stdio_ctx.__aenter__()
50
- self.client = ClientSession(read_stream, write_stream, sampling_callback=None)
51
- await self.client.__aenter__()
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
- try:
56
- if self.client:
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
- if self._stdio_ctx:
60
- await self._stdio_ctx.__aexit__(None, None, None)
61
- self._stdio_ctx = None
62
- except Exception as e:
63
- logger.warning(f"Warning: Error during stdio connector disconnect: {e}")
64
- finally:
65
- # Always clean up references even if there were errors
66
- self.client = None
67
- self._stdio_ctx = None
68
- self._tools = None
69
-
70
- async def __aenter__(self) -> "StdioConnector":
71
- """Enter the async context manager."""
72
- await self.connect()
73
- return self
74
-
75
- async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
76
- """Exit the async context manager."""
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]) -> 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
- return await self.client.call_tool(name, arguments)
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 {}})
@@ -10,20 +10,26 @@ import json
10
10
  import uuid
11
11
  from typing import Any
12
12
 
13
- import websockets
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, url: str, auth_token: str | None = None, headers: dict[str, str] | None = None
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
- self.ws = await websockets.connect(self.url, extra_headers=self.headers)
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
- # Start the message receiver task
48
- self._receiver_task = asyncio.create_task(self._receive_messages())
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._receiver_task:
78
- self._receiver_task.cancel()
79
- try:
80
- await self._receiver_task
81
- except asyncio.CancelledError:
82
- pass
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
- if self.ws:
85
- await self.ws.close()
86
- self.ws = None
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
- for future in self.pending_requests.values():
90
- if not future.done():
91
- future.set_exception(ConnectionError("WebSocket disconnected"))
92
- self.pending_requests.clear()
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
- raise e
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
- return await self._send_request("initialize")
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)
mcp_use/session.py CHANGED
@@ -8,7 +8,6 @@ which handles authentication, initialization, and tool discovery.
8
8
  from typing import Any
9
9
 
10
10
  from .connectors.base import BaseConnector
11
- from .tools.converter import ModelProvider, ToolConverter
12
11
 
13
12
 
14
13
  class MCPSession:
@@ -21,18 +20,15 @@ class MCPSession:
21
20
  def __init__(
22
21
  self,
23
22
  connector: BaseConnector,
24
- model_provider: str | ModelProvider,
25
23
  auto_connect: bool = True,
26
24
  ) -> None:
27
25
  """Initialize a new MCP session.
28
26
 
29
27
  Args:
30
28
  connector: The connector to use for communicating with the MCP implementation.
31
- model_provider: The model provider to convert tools for.
32
29
  auto_connect: Whether to automatically connect to the MCP implementation.
33
30
  """
34
31
  self.connector = connector
35
- self.tool_converter = ToolConverter(model_provider)
36
32
  self.session_info: dict[str, Any] | None = None
37
33
  self.tools: list[dict[str, Any]] = []
38
34
  self.auto_connect = auto_connect
@@ -100,14 +96,6 @@ class MCPSession:
100
96
  self.tools = self.connector.tools
101
97
  return self.tools
102
98
 
103
- def get_tools_for_llm(self) -> list[dict[str, Any]]:
104
- """Get the tools in the format required by the LLM.
105
-
106
- Returns:
107
- The list of tools in the LLM-specific format.
108
- """
109
- return self.tool_converter.convert_tools(self.tools)
110
-
111
99
  async def call_tool(self, name: str, arguments: dict[str, Any]) -> Any:
112
100
  """Call an MCP tool with the given arguments.
113
101
 
@@ -123,46 +111,3 @@ class MCPSession:
123
111
  await self.connect()
124
112
 
125
113
  return await self.connector.call_tool(name, arguments)
126
-
127
- async def process_tool_calls(self, response: dict[str, Any]) -> list[dict[str, Any]]:
128
- """Process tool calls from an LLM response.
129
-
130
- This method parses tool calls from the LLM response, executes them,
131
- and returns the results.
132
-
133
- Args:
134
- response: The response from the LLM.
135
-
136
- Returns:
137
- A list of tool call results, each containing 'name', 'arguments', and 'result' keys.
138
- """
139
- # Parse tool calls from the response
140
- tool_calls = self.tool_converter.parse_tool_calls(response)
141
-
142
- # Execute each tool call
143
- results = []
144
- for tool_call in tool_calls:
145
- name = tool_call["name"]
146
- arguments = tool_call["arguments"]
147
-
148
- try:
149
- result = await self.call_tool(name, arguments)
150
- results.append(
151
- {
152
- "name": name,
153
- "arguments": arguments,
154
- "result": result,
155
- "error": None,
156
- }
157
- )
158
- except Exception as e:
159
- results.append(
160
- {
161
- "name": name,
162
- "arguments": arguments,
163
- "result": None,
164
- "error": str(e),
165
- }
166
- )
167
-
168
- return results
@@ -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
+ ]