mcp-use 0.1.0__py3-none-any.whl → 1.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mcp-use might be problematic. Click here for more details.
- mcp_use/__init__.py +6 -6
- mcp_use/agents/langchain_agent.py +88 -64
- mcp_use/agents/mcpagent.py +216 -52
- mcp_use/agents/prompts/default.py +22 -0
- mcp_use/client.py +93 -68
- mcp_use/config.py +3 -57
- mcp_use/connectors/base.py +94 -16
- mcp_use/connectors/http.py +51 -95
- mcp_use/connectors/stdio.py +38 -84
- mcp_use/connectors/websocket.py +124 -21
- mcp_use/session.py +0 -55
- mcp_use/task_managers/__init__.py +19 -0
- mcp_use/task_managers/base.py +151 -0
- mcp_use/task_managers/sse.py +82 -0
- mcp_use/task_managers/stdio.py +73 -0
- mcp_use/task_managers/websocket.py +63 -0
- mcp_use-1.0.1.dist-info/METADATA +383 -0
- mcp_use-1.0.1.dist-info/RECORD +24 -0
- {mcp_use-0.1.0.dist-info → mcp_use-1.0.1.dist-info}/WHEEL +1 -2
- mcp_use/tools/__init__.py +0 -11
- mcp_use/tools/converter.py +0 -108
- mcp_use/tools/formats.py +0 -181
- mcp_use/types.py +0 -33
- mcp_use-0.1.0.dist-info/METADATA +0 -287
- mcp_use-0.1.0.dist-info/RECORD +0 -23
- mcp_use-0.1.0.dist-info/top_level.txt +0 -1
- {mcp_use-0.1.0.dist-info → mcp_use-1.0.1.dist-info}/licenses/LICENSE +0 -0
mcp_use/client.py
CHANGED
|
@@ -9,8 +9,8 @@ import json
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
11
|
from .config import create_connector_from_config, load_config_file
|
|
12
|
+
from .logging import logger
|
|
12
13
|
from .session import MCPSession
|
|
13
|
-
from .tools.converter import ModelProvider
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class MCPClient:
|
|
@@ -23,19 +23,16 @@ class MCPClient:
|
|
|
23
23
|
def __init__(
|
|
24
24
|
self,
|
|
25
25
|
config: str | dict[str, Any] | None = None,
|
|
26
|
-
model_provider: str | ModelProvider = "openai",
|
|
27
26
|
) -> None:
|
|
28
27
|
"""Initialize a new MCP client.
|
|
29
28
|
|
|
30
29
|
Args:
|
|
31
30
|
config: Either a dict containing configuration or a path to a JSON config file.
|
|
32
31
|
If None, an empty configuration is used.
|
|
33
|
-
model_provider: The model provider to use for tool conversion.
|
|
34
32
|
"""
|
|
35
|
-
self.model_provider = model_provider
|
|
36
33
|
self.config: dict[str, Any] = {}
|
|
37
34
|
self.sessions: dict[str, MCPSession] = {}
|
|
38
|
-
self.
|
|
35
|
+
self.active_sessions: list[str] = []
|
|
39
36
|
|
|
40
37
|
# Load configuration if provided
|
|
41
38
|
if config is not None:
|
|
@@ -87,9 +84,9 @@ class MCPClient:
|
|
|
87
84
|
if "mcpServers" in self.config and name in self.config["mcpServers"]:
|
|
88
85
|
del self.config["mcpServers"][name]
|
|
89
86
|
|
|
90
|
-
# If we removed
|
|
91
|
-
if name
|
|
92
|
-
self.
|
|
87
|
+
# If we removed an active session, remove it from active_sessions
|
|
88
|
+
if name in self.active_sessions:
|
|
89
|
+
self.active_sessions.remove(name)
|
|
93
90
|
|
|
94
91
|
def get_server_names(self) -> list[str]:
|
|
95
92
|
"""Get the list of configured server names.
|
|
@@ -108,17 +105,11 @@ class MCPClient:
|
|
|
108
105
|
with open(filepath, "w") as f:
|
|
109
106
|
json.dump(self.config, f, indent=2)
|
|
110
107
|
|
|
111
|
-
async def create_session(
|
|
112
|
-
self,
|
|
113
|
-
server_name: str | None = None,
|
|
114
|
-
auto_initialize: bool = True,
|
|
115
|
-
) -> MCPSession:
|
|
108
|
+
async def create_session(self, server_name: str, auto_initialize: bool = True) -> MCPSession:
|
|
116
109
|
"""Create a session for the specified server.
|
|
117
110
|
|
|
118
111
|
Args:
|
|
119
112
|
server_name: The name of the server to create a session for.
|
|
120
|
-
If None, uses the first available server.
|
|
121
|
-
auto_initialize: Whether to automatically initialize the session.
|
|
122
113
|
|
|
123
114
|
Returns:
|
|
124
115
|
The created MCPSession.
|
|
@@ -131,10 +122,6 @@ class MCPClient:
|
|
|
131
122
|
if not servers:
|
|
132
123
|
raise ValueError("No MCP servers defined in config")
|
|
133
124
|
|
|
134
|
-
# If server_name not specified, use the first one
|
|
135
|
-
if not server_name:
|
|
136
|
-
server_name = next(iter(servers.keys()))
|
|
137
|
-
|
|
138
125
|
if server_name not in servers:
|
|
139
126
|
raise ValueError(f"Server '{server_name}' not found in config")
|
|
140
127
|
|
|
@@ -142,85 +129,123 @@ class MCPClient:
|
|
|
142
129
|
connector = create_connector_from_config(server_config)
|
|
143
130
|
|
|
144
131
|
# Create the session
|
|
145
|
-
session = MCPSession(connector
|
|
146
|
-
self.sessions[server_name] = session
|
|
147
|
-
|
|
148
|
-
# Make this the active session
|
|
149
|
-
self.active_session = server_name
|
|
150
|
-
|
|
151
|
-
# Initialize if requested
|
|
132
|
+
session = MCPSession(connector)
|
|
152
133
|
if auto_initialize:
|
|
153
134
|
await session.initialize()
|
|
135
|
+
self.sessions[server_name] = session
|
|
136
|
+
|
|
137
|
+
# Add to active sessions
|
|
138
|
+
if server_name not in self.active_sessions:
|
|
139
|
+
self.active_sessions.append(server_name)
|
|
154
140
|
|
|
155
141
|
return session
|
|
156
142
|
|
|
157
|
-
def
|
|
143
|
+
async def create_all_sessions(
|
|
144
|
+
self,
|
|
145
|
+
auto_initialize: bool = True,
|
|
146
|
+
) -> dict[str, MCPSession]:
|
|
147
|
+
"""Create a session for the specified server.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
auto_initialize: Whether to automatically initialize the session.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
The created MCPSession. If server_name is None, returns the first created session.
|
|
154
|
+
|
|
155
|
+
Raises:
|
|
156
|
+
ValueError: If no servers are configured or the specified server doesn't exist.
|
|
157
|
+
"""
|
|
158
|
+
# Get server config
|
|
159
|
+
servers = self.config.get("mcpServers", {})
|
|
160
|
+
if not servers:
|
|
161
|
+
raise ValueError("No MCP servers defined in config")
|
|
162
|
+
|
|
163
|
+
# Create sessions for all servers
|
|
164
|
+
for name in servers:
|
|
165
|
+
session = await self.create_session(name, auto_initialize)
|
|
166
|
+
if auto_initialize:
|
|
167
|
+
await session.initialize()
|
|
168
|
+
|
|
169
|
+
return self.sessions
|
|
170
|
+
|
|
171
|
+
def get_session(self, server_name: str) -> MCPSession:
|
|
158
172
|
"""Get an existing session.
|
|
159
173
|
|
|
160
174
|
Args:
|
|
161
175
|
server_name: The name of the server to get the session for.
|
|
162
|
-
If None, uses the active session.
|
|
176
|
+
If None, uses the first active session.
|
|
163
177
|
|
|
164
178
|
Returns:
|
|
165
179
|
The MCPSession for the specified server.
|
|
166
180
|
|
|
167
181
|
Raises:
|
|
168
|
-
ValueError: If no active
|
|
182
|
+
ValueError: If no active sessions exist or the specified session doesn't exist.
|
|
169
183
|
"""
|
|
170
|
-
if server_name is None:
|
|
171
|
-
if self.active_session is None:
|
|
172
|
-
raise ValueError("No active session")
|
|
173
|
-
server_name = self.active_session
|
|
174
|
-
|
|
175
184
|
if server_name not in self.sessions:
|
|
176
185
|
raise ValueError(f"No session exists for server '{server_name}'")
|
|
177
186
|
|
|
178
187
|
return self.sessions[server_name]
|
|
179
188
|
|
|
180
|
-
|
|
189
|
+
def get_all_active_sessions(self) -> dict[str, MCPSession]:
|
|
190
|
+
"""Get all active sessions.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Dictionary mapping server names to their MCPSession instances.
|
|
194
|
+
"""
|
|
195
|
+
return {name: self.sessions[name] for name in self.active_sessions if name in self.sessions}
|
|
196
|
+
|
|
197
|
+
async def close_session(self, server_name: str) -> None:
|
|
181
198
|
"""Close a session.
|
|
182
199
|
|
|
183
200
|
Args:
|
|
184
201
|
server_name: The name of the server to close the session for.
|
|
185
|
-
If None, uses the active session.
|
|
202
|
+
If None, uses the first active session.
|
|
186
203
|
|
|
187
204
|
Raises:
|
|
188
|
-
ValueError: If no active
|
|
205
|
+
ValueError: If no active sessions exist or the specified session doesn't exist.
|
|
189
206
|
"""
|
|
190
|
-
session
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
207
|
+
# Check if the session exists
|
|
208
|
+
if server_name not in self.sessions:
|
|
209
|
+
logger.warning(f"No session exists for server '{server_name}', nothing to close")
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
# Get the session
|
|
213
|
+
session = self.sessions[server_name]
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
# Disconnect from the session
|
|
217
|
+
logger.info(f"Closing session for server '{server_name}'")
|
|
218
|
+
await session.disconnect()
|
|
219
|
+
except Exception as e:
|
|
220
|
+
logger.error(f"Error closing session for server '{server_name}': {e}")
|
|
221
|
+
finally:
|
|
222
|
+
# Remove the session regardless of whether disconnect succeeded
|
|
198
223
|
del self.sessions[server_name]
|
|
199
224
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
225
|
+
# Remove from active_sessions
|
|
226
|
+
if server_name in self.active_sessions:
|
|
227
|
+
self.active_sessions.remove(server_name)
|
|
203
228
|
|
|
204
229
|
async def close_all_sessions(self) -> None:
|
|
205
|
-
"""Close all active sessions.
|
|
206
|
-
for server_name in list(self.sessions.keys()):
|
|
207
|
-
await self.close_session(server_name)
|
|
208
|
-
|
|
209
|
-
async def __aenter__(self) -> "MCPClient":
|
|
210
|
-
"""Enter the async context manager.
|
|
211
|
-
|
|
212
|
-
Creates a session for the first available server if no sessions exist.
|
|
213
|
-
|
|
214
|
-
Returns:
|
|
215
|
-
The client instance.
|
|
216
|
-
"""
|
|
217
|
-
if not self.sessions and self.config.get("mcpServers"):
|
|
218
|
-
await self.create_session()
|
|
219
|
-
return self
|
|
220
|
-
|
|
221
|
-
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
222
|
-
"""Exit the async context manager.
|
|
230
|
+
"""Close all active sessions.
|
|
223
231
|
|
|
224
|
-
|
|
232
|
+
This method ensures all sessions are closed even if some fail.
|
|
225
233
|
"""
|
|
226
|
-
|
|
234
|
+
# Get a list of all session names first to avoid modification during iteration
|
|
235
|
+
server_names = list(self.sessions.keys())
|
|
236
|
+
errors = []
|
|
237
|
+
|
|
238
|
+
for server_name in server_names:
|
|
239
|
+
try:
|
|
240
|
+
logger.info(f"Closing session for server '{server_name}'")
|
|
241
|
+
await self.close_session(server_name)
|
|
242
|
+
except Exception as e:
|
|
243
|
+
error_msg = f"Failed to close session for server '{server_name}': {e}"
|
|
244
|
+
logger.error(error_msg)
|
|
245
|
+
errors.append(error_msg)
|
|
246
|
+
|
|
247
|
+
# Log summary if there were errors
|
|
248
|
+
if errors:
|
|
249
|
+
logger.error(f"Encountered {len(errors)} errors while closing sessions")
|
|
250
|
+
else:
|
|
251
|
+
logger.info("All sessions closed successfully")
|
mcp_use/config.py
CHANGED
|
@@ -5,28 +5,9 @@ This module provides functionality to load MCP configuration from JSON files.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
|
-
import os
|
|
9
|
-
import subprocess
|
|
10
8
|
from typing import Any
|
|
11
9
|
|
|
12
10
|
from .connectors import BaseConnector, HttpConnector, StdioConnector, WebSocketConnector
|
|
13
|
-
from .session import MCPSession
|
|
14
|
-
from .tools.converter import ModelProvider
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def _execute_command(command: str, args: list[str], env: dict[str, str] | None = None) -> None:
|
|
18
|
-
"""Execute a command with given arguments and environment variables.
|
|
19
|
-
|
|
20
|
-
Args:
|
|
21
|
-
command: The command to execute
|
|
22
|
-
args: List of command arguments
|
|
23
|
-
env: Optional environment variables to set
|
|
24
|
-
"""
|
|
25
|
-
full_env = os.environ.copy()
|
|
26
|
-
if env:
|
|
27
|
-
full_env.update(env)
|
|
28
|
-
|
|
29
|
-
subprocess.Popen([command] + args, env=full_env)
|
|
30
11
|
|
|
31
12
|
|
|
32
13
|
def load_config_file(filepath: str) -> dict[str, Any]:
|
|
@@ -62,9 +43,9 @@ def create_connector_from_config(server_config: dict[str, Any]) -> BaseConnector
|
|
|
62
43
|
# HTTP connector
|
|
63
44
|
elif "url" in server_config:
|
|
64
45
|
return HttpConnector(
|
|
65
|
-
|
|
46
|
+
base_url=server_config["url"],
|
|
66
47
|
headers=server_config.get("headers", None),
|
|
67
|
-
|
|
48
|
+
auth_token=server_config.get("auth_token", None),
|
|
68
49
|
)
|
|
69
50
|
|
|
70
51
|
# WebSocket connector
|
|
@@ -72,42 +53,7 @@ def create_connector_from_config(server_config: dict[str, Any]) -> BaseConnector
|
|
|
72
53
|
return WebSocketConnector(
|
|
73
54
|
url=server_config["ws_url"],
|
|
74
55
|
headers=server_config.get("headers", None),
|
|
75
|
-
|
|
56
|
+
auth_token=server_config.get("auth_token", None),
|
|
76
57
|
)
|
|
77
58
|
|
|
78
59
|
raise ValueError("Cannot determine connector type from config")
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def create_session_from_config(
|
|
82
|
-
filepath: str,
|
|
83
|
-
server_name: str | None = None,
|
|
84
|
-
model_provider: str | ModelProvider = "openai",
|
|
85
|
-
) -> MCPSession:
|
|
86
|
-
"""Create an MCPSession from a configuration file.
|
|
87
|
-
|
|
88
|
-
Args:
|
|
89
|
-
filepath: Path to the configuration file
|
|
90
|
-
server_name: Name of the server to use from config, uses first if None
|
|
91
|
-
model_provider: Model provider to use for tool conversion
|
|
92
|
-
|
|
93
|
-
Returns:
|
|
94
|
-
Configured MCPSession instance
|
|
95
|
-
"""
|
|
96
|
-
config = load_config_file(filepath)
|
|
97
|
-
|
|
98
|
-
# Get server config
|
|
99
|
-
servers = config.get("mcpServers", {})
|
|
100
|
-
if not servers:
|
|
101
|
-
raise ValueError("No MCP servers defined in config")
|
|
102
|
-
|
|
103
|
-
# If server_name not specified, use the first one
|
|
104
|
-
if not server_name:
|
|
105
|
-
server_name = next(iter(servers.keys()))
|
|
106
|
-
|
|
107
|
-
if server_name not in servers:
|
|
108
|
-
raise ValueError(f"Server '{server_name}' not found in config")
|
|
109
|
-
|
|
110
|
-
server_config = servers[server_name]
|
|
111
|
-
connector = create_connector_from_config(server_config)
|
|
112
|
-
|
|
113
|
-
return MCPSession(connector, model_provider)
|
mcp_use/connectors/base.py
CHANGED
|
@@ -8,9 +8,11 @@ must implement.
|
|
|
8
8
|
from abc import ABC, abstractmethod
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
|
-
from mcp
|
|
11
|
+
from mcp import ClientSession
|
|
12
|
+
from mcp.types import CallToolResult, Tool
|
|
12
13
|
|
|
13
|
-
from
|
|
14
|
+
from ..logging import logger
|
|
15
|
+
from ..task_managers import ConnectionManager
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class BaseConnector(ABC):
|
|
@@ -19,43 +21,119 @@ class BaseConnector(ABC):
|
|
|
19
21
|
This class defines the interface that all MCP connectors must implement.
|
|
20
22
|
"""
|
|
21
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
|
+
|
|
22
31
|
@abstractmethod
|
|
23
32
|
async def connect(self) -> None:
|
|
24
33
|
"""Establish a connection to the MCP implementation."""
|
|
25
34
|
pass
|
|
26
35
|
|
|
27
|
-
@abstractmethod
|
|
28
36
|
async def disconnect(self) -> None:
|
|
29
37
|
"""Close the connection to the MCP implementation."""
|
|
30
|
-
|
|
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")
|
|
31
80
|
|
|
32
|
-
@abstractmethod
|
|
33
81
|
async def initialize(self) -> dict[str, Any]:
|
|
34
82
|
"""Initialize the MCP session and return session information."""
|
|
35
|
-
|
|
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
|
|
36
98
|
|
|
37
99
|
@property
|
|
38
|
-
@abstractmethod
|
|
39
100
|
def tools(self) -> list[Tool]:
|
|
40
101
|
"""Get the list of available tools."""
|
|
41
|
-
|
|
102
|
+
if not self._tools:
|
|
103
|
+
raise RuntimeError("MCP client is not initialized")
|
|
104
|
+
return self._tools
|
|
42
105
|
|
|
43
|
-
@abstractmethod
|
|
44
106
|
async def call_tool(self, name: str, arguments: dict[str, Any]) -> CallToolResult:
|
|
45
107
|
"""Call an MCP tool with the given arguments."""
|
|
46
|
-
|
|
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
|
|
47
114
|
|
|
48
|
-
@abstractmethod
|
|
49
115
|
async def list_resources(self) -> list[dict[str, Any]]:
|
|
50
116
|
"""List all available resources from the MCP implementation."""
|
|
51
|
-
|
|
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
|
|
52
123
|
|
|
53
|
-
@abstractmethod
|
|
54
124
|
async def read_resource(self, uri: str) -> tuple[bytes, str]:
|
|
55
125
|
"""Read a resource by URI."""
|
|
56
|
-
|
|
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
|
|
57
132
|
|
|
58
|
-
@abstractmethod
|
|
59
133
|
async def request(self, method: str, params: dict[str, Any] | None = None) -> Any:
|
|
60
134
|
"""Send a raw request to the MCP implementation."""
|
|
61
|
-
|
|
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,24 +2,30 @@
|
|
|
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
|
|
8
|
+
from mcp import ClientSession
|
|
11
9
|
|
|
10
|
+
from ..logging import logger
|
|
11
|
+
from ..task_managers import SseConnectionManager
|
|
12
12
|
from .base import BaseConnector
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class HttpConnector(BaseConnector):
|
|
16
|
-
"""Connector for MCP implementations using HTTP transport.
|
|
16
|
+
"""Connector for MCP implementations using HTTP transport with SSE.
|
|
17
17
|
|
|
18
|
-
This connector uses HTTP
|
|
18
|
+
This connector uses HTTP/SSE to communicate with remote MCP implementations,
|
|
19
|
+
using a connection manager to handle the proper lifecycle management.
|
|
19
20
|
"""
|
|
20
21
|
|
|
21
22
|
def __init__(
|
|
22
|
-
self,
|
|
23
|
+
self,
|
|
24
|
+
base_url: str,
|
|
25
|
+
auth_token: str | None = None,
|
|
26
|
+
headers: dict[str, str] | None = None,
|
|
27
|
+
timeout: float = 5,
|
|
28
|
+
sse_read_timeout: float = 60 * 5,
|
|
23
29
|
):
|
|
24
30
|
"""Initialize a new HTTP connector.
|
|
25
31
|
|
|
@@ -27,100 +33,50 @@ class HttpConnector(BaseConnector):
|
|
|
27
33
|
base_url: The base URL of the MCP HTTP API.
|
|
28
34
|
auth_token: Optional authentication token.
|
|
29
35
|
headers: Optional additional headers.
|
|
36
|
+
timeout: Timeout for HTTP operations in seconds.
|
|
37
|
+
sse_read_timeout: Timeout for SSE read operations in seconds.
|
|
30
38
|
"""
|
|
39
|
+
super().__init__()
|
|
31
40
|
self.base_url = base_url.rstrip("/")
|
|
32
41
|
self.auth_token = auth_token
|
|
33
42
|
self.headers = headers or {}
|
|
34
43
|
if auth_token:
|
|
35
44
|
self.headers["Authorization"] = f"Bearer {auth_token}"
|
|
36
|
-
self.
|
|
45
|
+
self.timeout = timeout
|
|
46
|
+
self.sse_read_timeout = sse_read_timeout
|
|
37
47
|
|
|
38
48
|
async def connect(self) -> None:
|
|
39
49
|
"""Establish a connection to the MCP implementation."""
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return await response.json()
|
|
74
|
-
|
|
75
|
-
async def initialize(self) -> dict[str, Any]:
|
|
76
|
-
"""Initialize the MCP session and return session information."""
|
|
77
|
-
return await self._request("POST", "initialize")
|
|
78
|
-
|
|
79
|
-
async def list_tools(self) -> list[dict[str, Any]]:
|
|
80
|
-
"""List all available tools from the MCP implementation."""
|
|
81
|
-
result = await self._request("GET", "tools")
|
|
82
|
-
return result.get("tools", [])
|
|
83
|
-
|
|
84
|
-
async def call_tool(self, name: str, arguments: dict[str, Any]) -> Any:
|
|
85
|
-
"""Call an MCP tool with the given arguments."""
|
|
86
|
-
return await self._request("POST", f"tools/{name}", arguments)
|
|
87
|
-
|
|
88
|
-
async def list_resources(self) -> list[dict[str, Any]]:
|
|
89
|
-
"""List all available resources from the MCP implementation."""
|
|
90
|
-
result = await self._request("GET", "resources")
|
|
91
|
-
return result
|
|
92
|
-
|
|
93
|
-
async def read_resource(self, uri: str) -> tuple[bytes, str]:
|
|
94
|
-
"""Read a resource by URI."""
|
|
95
|
-
# For resources, we may need to handle binary data
|
|
96
|
-
if not self.session:
|
|
97
|
-
raise RuntimeError("HTTP session is not connected")
|
|
98
|
-
|
|
99
|
-
url = f"{self.base_url}/resources/read"
|
|
100
|
-
|
|
101
|
-
async with self.session.get(url, params={"uri": uri}) as response:
|
|
102
|
-
response.raise_for_status()
|
|
103
|
-
|
|
104
|
-
# Check if this is a JSON response or binary data
|
|
105
|
-
content_type = response.headers.get("Content-Type", "")
|
|
106
|
-
if "application/json" in content_type:
|
|
107
|
-
data = await response.json()
|
|
108
|
-
content = data.get("content", b"")
|
|
109
|
-
mime_type = data.get("mimeType", "")
|
|
110
|
-
|
|
111
|
-
# If content is base64 encoded, decode it
|
|
112
|
-
if isinstance(content, str):
|
|
113
|
-
import base64
|
|
114
|
-
|
|
115
|
-
content = base64.b64decode(content)
|
|
116
|
-
|
|
117
|
-
return content, mime_type
|
|
118
|
-
else:
|
|
119
|
-
# Assume binary response
|
|
120
|
-
content = await response.read()
|
|
121
|
-
return content, content_type
|
|
122
|
-
|
|
123
|
-
async def request(self, method: str, params: dict[str, Any] | None = None) -> Any:
|
|
124
|
-
"""Send a raw request to the MCP implementation."""
|
|
125
|
-
# For custom methods, we'll use the RPC-style endpoint
|
|
126
|
-
return await self._request("POST", "rpc", {"method": method, "params": params or {}})
|
|
50
|
+
if self._connected:
|
|
51
|
+
logger.debug("Already connected to MCP implementation")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
logger.info(f"Connecting to MCP implementation via HTTP/SSE: {self.base_url}")
|
|
55
|
+
try:
|
|
56
|
+
# Create the SSE connection URL
|
|
57
|
+
sse_url = f"{self.base_url}/sse"
|
|
58
|
+
|
|
59
|
+
# Create and start the connection manager
|
|
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__()
|
|
68
|
+
|
|
69
|
+
# Mark as connected
|
|
70
|
+
self._connected = True
|
|
71
|
+
logger.info(
|
|
72
|
+
f"Successfully connected to MCP implementation via HTTP/SSE: {self.base_url}"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logger.error(f"Failed to connect to MCP implementation via HTTP/SSE: {e}")
|
|
77
|
+
|
|
78
|
+
# Clean up any resources if connection failed
|
|
79
|
+
await self._cleanup_resources()
|
|
80
|
+
|
|
81
|
+
# Re-raise the original exception
|
|
82
|
+
raise
|