mcp-use 1.3.12__py3-none-any.whl → 1.3.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mcp-use might be problematic. Click here for more details.
- mcp_use/__init__.py +1 -1
- mcp_use/adapters/.deprecated +0 -0
- mcp_use/adapters/__init__.py +18 -7
- mcp_use/adapters/base.py +12 -185
- mcp_use/adapters/langchain_adapter.py +12 -219
- mcp_use/agents/adapters/__init__.py +10 -0
- mcp_use/agents/adapters/base.py +193 -0
- mcp_use/agents/adapters/langchain_adapter.py +228 -0
- mcp_use/agents/base.py +1 -1
- mcp_use/agents/managers/__init__.py +19 -0
- mcp_use/agents/managers/base.py +36 -0
- mcp_use/agents/managers/server_manager.py +131 -0
- mcp_use/agents/managers/tools/__init__.py +15 -0
- mcp_use/agents/managers/tools/base_tool.py +19 -0
- mcp_use/agents/managers/tools/connect_server.py +69 -0
- mcp_use/agents/managers/tools/disconnect_server.py +43 -0
- mcp_use/agents/managers/tools/get_active_server.py +29 -0
- mcp_use/agents/managers/tools/list_servers_tool.py +53 -0
- mcp_use/agents/managers/tools/search_tools.py +328 -0
- mcp_use/agents/mcpagent.py +16 -14
- mcp_use/agents/remote.py +14 -1
- mcp_use/auth/.deprecated +0 -0
- mcp_use/auth/__init__.py +19 -4
- mcp_use/auth/bearer.py +11 -12
- mcp_use/auth/oauth.py +11 -620
- mcp_use/auth/oauth_callback.py +16 -207
- mcp_use/client/__init__.py +1 -0
- mcp_use/client/auth/__init__.py +6 -0
- mcp_use/client/auth/bearer.py +23 -0
- mcp_use/client/auth/oauth.py +629 -0
- mcp_use/client/auth/oauth_callback.py +214 -0
- mcp_use/client/client.py +356 -0
- mcp_use/client/config.py +106 -0
- mcp_use/client/connectors/__init__.py +20 -0
- mcp_use/client/connectors/base.py +470 -0
- mcp_use/client/connectors/http.py +304 -0
- mcp_use/client/connectors/sandbox.py +332 -0
- mcp_use/client/connectors/stdio.py +109 -0
- mcp_use/client/connectors/utils.py +13 -0
- mcp_use/client/connectors/websocket.py +257 -0
- mcp_use/client/exceptions.py +31 -0
- mcp_use/client/middleware/__init__.py +50 -0
- mcp_use/client/middleware/logging.py +31 -0
- mcp_use/client/middleware/metrics.py +314 -0
- mcp_use/client/middleware/middleware.py +266 -0
- mcp_use/client/session.py +162 -0
- mcp_use/client/task_managers/__init__.py +20 -0
- mcp_use/client/task_managers/base.py +145 -0
- mcp_use/client/task_managers/sse.py +84 -0
- mcp_use/client/task_managers/stdio.py +69 -0
- mcp_use/client/task_managers/streamable_http.py +86 -0
- mcp_use/client/task_managers/websocket.py +68 -0
- mcp_use/client.py +12 -344
- mcp_use/config.py +20 -97
- mcp_use/connectors/.deprecated +0 -0
- mcp_use/connectors/__init__.py +46 -20
- mcp_use/connectors/base.py +12 -455
- mcp_use/connectors/http.py +13 -300
- mcp_use/connectors/sandbox.py +13 -306
- mcp_use/connectors/stdio.py +13 -104
- mcp_use/connectors/utils.py +15 -8
- mcp_use/connectors/websocket.py +13 -252
- mcp_use/exceptions.py +33 -18
- mcp_use/managers/.deprecated +0 -0
- mcp_use/managers/__init__.py +56 -17
- mcp_use/managers/base.py +13 -31
- mcp_use/managers/server_manager.py +13 -119
- mcp_use/managers/tools/__init__.py +45 -15
- mcp_use/managers/tools/base_tool.py +5 -16
- mcp_use/managers/tools/connect_server.py +5 -67
- mcp_use/managers/tools/disconnect_server.py +5 -41
- mcp_use/managers/tools/get_active_server.py +5 -26
- mcp_use/managers/tools/list_servers_tool.py +5 -51
- mcp_use/managers/tools/search_tools.py +17 -321
- mcp_use/middleware/.deprecated +0 -0
- mcp_use/middleware/__init__.py +89 -50
- mcp_use/middleware/logging.py +14 -26
- mcp_use/middleware/metrics.py +30 -303
- mcp_use/middleware/middleware.py +39 -246
- mcp_use/session.py +13 -149
- mcp_use/task_managers/.deprecated +0 -0
- mcp_use/task_managers/__init__.py +48 -20
- mcp_use/task_managers/base.py +13 -140
- mcp_use/task_managers/sse.py +13 -79
- mcp_use/task_managers/stdio.py +13 -64
- mcp_use/task_managers/streamable_http.py +15 -81
- mcp_use/task_managers/websocket.py +13 -63
- mcp_use/telemetry/events.py +58 -0
- mcp_use/telemetry/telemetry.py +71 -1
- mcp_use/types/.deprecated +0 -0
- mcp_use/types/sandbox.py +13 -18
- {mcp_use-1.3.12.dist-info → mcp_use-1.3.13.dist-info}/METADATA +59 -34
- mcp_use-1.3.13.dist-info/RECORD +109 -0
- mcp_use-1.3.12.dist-info/RECORD +0 -64
- mcp_use-1.3.12.dist-info/licenses/LICENSE +0 -21
- /mcp_use/{observability → agents/observability}/__init__.py +0 -0
- /mcp_use/{observability → agents/observability}/callbacks_manager.py +0 -0
- /mcp_use/{observability → agents/observability}/laminar.py +0 -0
- /mcp_use/{observability → agents/observability}/langfuse.py +0 -0
- {mcp_use-1.3.12.dist-info → mcp_use-1.3.13.dist-info}/WHEEL +0 -0
- {mcp_use-1.3.12.dist-info → mcp_use-1.3.13.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base connector for MCP implementations.
|
|
3
|
+
|
|
4
|
+
This module provides the base connector interface that all MCP connectors
|
|
5
|
+
must implement.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import warnings
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from datetime import timedelta
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from mcp import ClientSession, Implementation
|
|
14
|
+
from mcp.client.session import ElicitationFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
|
|
15
|
+
from mcp.shared.exceptions import McpError
|
|
16
|
+
from mcp.types import (
|
|
17
|
+
CallToolResult,
|
|
18
|
+
GetPromptResult,
|
|
19
|
+
Prompt,
|
|
20
|
+
PromptListChangedNotification,
|
|
21
|
+
ReadResourceResult,
|
|
22
|
+
Resource,
|
|
23
|
+
ResourceListChangedNotification,
|
|
24
|
+
ServerCapabilities,
|
|
25
|
+
ServerNotification,
|
|
26
|
+
Tool,
|
|
27
|
+
ToolListChangedNotification,
|
|
28
|
+
)
|
|
29
|
+
from pydantic import AnyUrl
|
|
30
|
+
|
|
31
|
+
import mcp_use
|
|
32
|
+
from mcp_use.client.middleware import Middleware, MiddlewareManager
|
|
33
|
+
from mcp_use.client.task_managers import ConnectionManager
|
|
34
|
+
from mcp_use.logging import logger
|
|
35
|
+
from mcp_use.telemetry.telemetry import telemetry
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BaseConnector(ABC):
|
|
39
|
+
"""Base class for MCP connectors.
|
|
40
|
+
|
|
41
|
+
This class defines the interface that all MCP connectors must implement.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
sampling_callback: SamplingFnT | None = None,
|
|
47
|
+
elicitation_callback: ElicitationFnT | None = None,
|
|
48
|
+
message_handler: MessageHandlerFnT | None = None,
|
|
49
|
+
logging_callback: LoggingFnT | None = None,
|
|
50
|
+
middleware: list[Middleware] | None = None,
|
|
51
|
+
):
|
|
52
|
+
"""Initialize base connector with common attributes."""
|
|
53
|
+
self.client_session: ClientSession | None = None
|
|
54
|
+
self._connection_manager: ConnectionManager | None = None
|
|
55
|
+
self._tools: list[Tool] | None = None
|
|
56
|
+
self._resources: list[Resource] | None = None
|
|
57
|
+
self._prompts: list[Prompt] | None = None
|
|
58
|
+
self._connected = False
|
|
59
|
+
self._initialized = False # Track if client_session.initialize() has been called
|
|
60
|
+
self.auto_reconnect = True # Whether to automatically reconnect on connection loss (not configurable for now)
|
|
61
|
+
self.sampling_callback = sampling_callback
|
|
62
|
+
self.elicitation_callback = elicitation_callback
|
|
63
|
+
self.message_handler = message_handler
|
|
64
|
+
self.logging_callback = logging_callback
|
|
65
|
+
self.capabilities: ServerCapabilities | None = None
|
|
66
|
+
|
|
67
|
+
# Set up middleware manager
|
|
68
|
+
self.middleware_manager = MiddlewareManager()
|
|
69
|
+
if middleware:
|
|
70
|
+
for mw in middleware:
|
|
71
|
+
self.middleware_manager.add_middleware(mw)
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def client_info(self) -> Implementation:
|
|
75
|
+
"""Get the client info for the connector."""
|
|
76
|
+
return Implementation(
|
|
77
|
+
name="mcp-use",
|
|
78
|
+
version=mcp_use.__version__,
|
|
79
|
+
url="https://github.com/mcp-use/mcp-use",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
async def _internal_message_handler(self, message: Any) -> None:
|
|
83
|
+
"""Wrap the user-provided message handler."""
|
|
84
|
+
if isinstance(message, ServerNotification):
|
|
85
|
+
if isinstance(message.root, ToolListChangedNotification):
|
|
86
|
+
logger.debug("Received tool list changed notification")
|
|
87
|
+
elif isinstance(message.root, ResourceListChangedNotification):
|
|
88
|
+
logger.debug("Received resource list changed notification")
|
|
89
|
+
elif isinstance(message.root, PromptListChangedNotification):
|
|
90
|
+
logger.debug("Received prompt list changed notification")
|
|
91
|
+
|
|
92
|
+
# Call the user's handler
|
|
93
|
+
if self.message_handler:
|
|
94
|
+
await self.message_handler(message)
|
|
95
|
+
|
|
96
|
+
@abstractmethod
|
|
97
|
+
@telemetry("connector_connect")
|
|
98
|
+
async def connect(self) -> None:
|
|
99
|
+
"""Establish a connection to the MCP implementation."""
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
@abstractmethod
|
|
104
|
+
def public_identifier(self) -> str:
|
|
105
|
+
"""Get the identifier for the connector."""
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
@telemetry("connector_disconnect")
|
|
109
|
+
async def disconnect(self) -> None:
|
|
110
|
+
"""Close the connection to the MCP implementation."""
|
|
111
|
+
if not self._connected:
|
|
112
|
+
logger.debug("Not connected to MCP implementation")
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
logger.debug("Disconnecting from MCP implementation")
|
|
116
|
+
await self._cleanup_resources()
|
|
117
|
+
self._connected = False
|
|
118
|
+
logger.debug("Disconnected from MCP implementation")
|
|
119
|
+
|
|
120
|
+
async def _cleanup_resources(self) -> None:
|
|
121
|
+
"""Clean up all resources associated with this connector."""
|
|
122
|
+
errors = []
|
|
123
|
+
|
|
124
|
+
# First stop the connection manager, this closes the ClientSession inside
|
|
125
|
+
# the same task where it was opened, avoiding cancel-scope mismatches.
|
|
126
|
+
if self._connection_manager:
|
|
127
|
+
try:
|
|
128
|
+
logger.debug("Stopping connection manager")
|
|
129
|
+
await self._connection_manager.stop()
|
|
130
|
+
except Exception as e:
|
|
131
|
+
error_msg = f"Error stopping connection manager: {e}"
|
|
132
|
+
logger.warning(error_msg)
|
|
133
|
+
errors.append(error_msg)
|
|
134
|
+
finally:
|
|
135
|
+
self._connection_manager = None
|
|
136
|
+
|
|
137
|
+
# Ensure the client_session reference is cleared (it should already be
|
|
138
|
+
# closed by the connection manager). Only attempt a direct __aexit__ if
|
|
139
|
+
# the connection manager did *not* exist, this covers edge-cases like
|
|
140
|
+
# failed connections where no manager was started.
|
|
141
|
+
if self.client_session:
|
|
142
|
+
try:
|
|
143
|
+
if not self._connection_manager:
|
|
144
|
+
logger.debug("Closing client session (no connection manager)")
|
|
145
|
+
await self.client_session.__aexit__(None, None, None)
|
|
146
|
+
except Exception as e:
|
|
147
|
+
error_msg = f"Error closing client session: {e}"
|
|
148
|
+
logger.warning(error_msg)
|
|
149
|
+
errors.append(error_msg)
|
|
150
|
+
finally:
|
|
151
|
+
self.client_session = None
|
|
152
|
+
|
|
153
|
+
# Reset tools
|
|
154
|
+
self._tools = None
|
|
155
|
+
self._resources = None
|
|
156
|
+
self._prompts = None
|
|
157
|
+
self._initialized = False # Reset initialization flag
|
|
158
|
+
|
|
159
|
+
if errors:
|
|
160
|
+
logger.warning(f"Encountered {len(errors)} errors during resource cleanup")
|
|
161
|
+
|
|
162
|
+
@telemetry("connector_initialize")
|
|
163
|
+
async def initialize(self) -> dict[str, Any]:
|
|
164
|
+
"""Initialize the MCP session and return session information."""
|
|
165
|
+
if not self.client_session:
|
|
166
|
+
raise RuntimeError("MCP client is not connected")
|
|
167
|
+
|
|
168
|
+
# Check if already initialized
|
|
169
|
+
if self._initialized:
|
|
170
|
+
return {"status": "already_initialized"}
|
|
171
|
+
|
|
172
|
+
# Initialize the session
|
|
173
|
+
result = await self.client_session.initialize()
|
|
174
|
+
self._initialized = True # Mark as initialized
|
|
175
|
+
|
|
176
|
+
self.capabilities = result.capabilities
|
|
177
|
+
|
|
178
|
+
if self.capabilities.tools:
|
|
179
|
+
# Get available tools directly from client session
|
|
180
|
+
try:
|
|
181
|
+
tools_result = await self.client_session.list_tools()
|
|
182
|
+
self._tools = tools_result.tools if tools_result else []
|
|
183
|
+
except Exception as e:
|
|
184
|
+
logger.error(f"Error listing tools for connector {self.public_identifier}: {e}")
|
|
185
|
+
self._tools = []
|
|
186
|
+
else:
|
|
187
|
+
self._tools = []
|
|
188
|
+
|
|
189
|
+
if self.capabilities.resources:
|
|
190
|
+
# Get available resources directly from client session
|
|
191
|
+
try:
|
|
192
|
+
resources_result = await self.client_session.list_resources()
|
|
193
|
+
self._resources = resources_result.resources if resources_result else []
|
|
194
|
+
except Exception as e:
|
|
195
|
+
logger.error(f"Error listing resources for connector {self.public_identifier}: {e}")
|
|
196
|
+
self._resources = []
|
|
197
|
+
else:
|
|
198
|
+
self._resources = []
|
|
199
|
+
|
|
200
|
+
if self.capabilities.prompts:
|
|
201
|
+
# Get available prompts directly from client session
|
|
202
|
+
try:
|
|
203
|
+
prompts_result = await self.client_session.list_prompts()
|
|
204
|
+
self._prompts = prompts_result.prompts if prompts_result else []
|
|
205
|
+
except Exception as e:
|
|
206
|
+
logger.error(f"Error listing prompts for connector {self.public_identifier}: {e}")
|
|
207
|
+
self._prompts = []
|
|
208
|
+
else:
|
|
209
|
+
self._prompts = []
|
|
210
|
+
|
|
211
|
+
logger.debug(
|
|
212
|
+
f"MCP session initialized with {len(self._tools)} tools, "
|
|
213
|
+
f"{len(self._resources)} resources, "
|
|
214
|
+
f"and {len(self._prompts)} prompts"
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
return result
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def tools(self) -> list[Tool]:
|
|
221
|
+
"""Get the list of available tools.
|
|
222
|
+
|
|
223
|
+
.. deprecated::
|
|
224
|
+
This property is deprecated because it may return stale data when the server
|
|
225
|
+
sends list change notifications. Use `await list_tools()` instead to ensure
|
|
226
|
+
you always get the latest data.
|
|
227
|
+
"""
|
|
228
|
+
warnings.warn(
|
|
229
|
+
"The 'tools' property is deprecated and may return stale data. "
|
|
230
|
+
"Use 'await list_tools()' instead to ensure fresh data.",
|
|
231
|
+
DeprecationWarning,
|
|
232
|
+
stacklevel=2,
|
|
233
|
+
)
|
|
234
|
+
if self._tools is None:
|
|
235
|
+
raise RuntimeError("MCP client is not initialized")
|
|
236
|
+
return self._tools
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def resources(self) -> list[Resource]:
|
|
240
|
+
"""Get the list of available resources.
|
|
241
|
+
|
|
242
|
+
.. deprecated::
|
|
243
|
+
This property is deprecated because it may return stale data when the server
|
|
244
|
+
sends list change notifications. Use `await list_resources()` instead to ensure
|
|
245
|
+
you always get the latest data.
|
|
246
|
+
"""
|
|
247
|
+
warnings.warn(
|
|
248
|
+
"The 'resources' property is deprecated and may return stale data. "
|
|
249
|
+
"Use 'await list_resources()' instead to ensure fresh data.",
|
|
250
|
+
DeprecationWarning,
|
|
251
|
+
stacklevel=2,
|
|
252
|
+
)
|
|
253
|
+
if self._resources is None:
|
|
254
|
+
raise RuntimeError("MCP client is not initialized")
|
|
255
|
+
return self._resources
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def prompts(self) -> list[Prompt]:
|
|
259
|
+
"""Get the list of available prompts.
|
|
260
|
+
|
|
261
|
+
.. deprecated::
|
|
262
|
+
This property is deprecated because it may return stale data when the server
|
|
263
|
+
sends list change notifications. Use `await list_prompts()' instead to ensure
|
|
264
|
+
you always get the latest data.
|
|
265
|
+
"""
|
|
266
|
+
warnings.warn(
|
|
267
|
+
"The 'prompts' property is deprecated and may return stale data. "
|
|
268
|
+
"Use 'await list_prompts()' instead to ensure fresh data.",
|
|
269
|
+
DeprecationWarning,
|
|
270
|
+
stacklevel=2,
|
|
271
|
+
)
|
|
272
|
+
if self._prompts is None:
|
|
273
|
+
raise RuntimeError("MCP client is not initialized")
|
|
274
|
+
return self._prompts
|
|
275
|
+
|
|
276
|
+
@property
|
|
277
|
+
def is_connected(self) -> bool:
|
|
278
|
+
"""Check if the connector is actually connected and the connection is alive.
|
|
279
|
+
|
|
280
|
+
This property checks not only the connected flag but also verifies that
|
|
281
|
+
the underlying connection manager and streams are still active.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
True if the connector is connected and the connection is alive, False otherwise.
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
# Check if we have a client session
|
|
288
|
+
if not self.client_session:
|
|
289
|
+
# Update the connected flag since we don't have a client session
|
|
290
|
+
self._connected = False
|
|
291
|
+
return False
|
|
292
|
+
|
|
293
|
+
# First check the basic connected flag
|
|
294
|
+
if not self._connected:
|
|
295
|
+
return False
|
|
296
|
+
|
|
297
|
+
# Check if we have a connection manager and if its task is still running
|
|
298
|
+
if self._connection_manager:
|
|
299
|
+
try:
|
|
300
|
+
# Check if the connection manager task is done (indicates disconnection)
|
|
301
|
+
if hasattr(self._connection_manager, "_task") and self._connection_manager._task:
|
|
302
|
+
if self._connection_manager._task.done():
|
|
303
|
+
logger.debug("Connection manager task is done, marking as disconnected")
|
|
304
|
+
self._connected = False
|
|
305
|
+
return False
|
|
306
|
+
|
|
307
|
+
# For HTTP-based connectors, also check if streams are still open
|
|
308
|
+
# Use the get_streams method to get the current connection
|
|
309
|
+
streams = self._connection_manager.get_streams()
|
|
310
|
+
if streams:
|
|
311
|
+
# Connection should be a tuple of (read_stream, write_stream)
|
|
312
|
+
if isinstance(streams, tuple) and len(streams) == 2:
|
|
313
|
+
read_stream, write_stream = streams
|
|
314
|
+
# Check if streams are closed using getattr with default value
|
|
315
|
+
if getattr(read_stream, "_closed", False):
|
|
316
|
+
logger.debug("Read stream is closed, marking as disconnected")
|
|
317
|
+
self._connected = False
|
|
318
|
+
return False
|
|
319
|
+
if getattr(write_stream, "_closed", False):
|
|
320
|
+
logger.debug("Write stream is closed, marking as disconnected")
|
|
321
|
+
self._connected = False
|
|
322
|
+
return False
|
|
323
|
+
|
|
324
|
+
except Exception as e:
|
|
325
|
+
# If we can't check the connection state, assume disconnected for safety
|
|
326
|
+
logger.debug(f"Error checking connection state: {e}, marking as disconnected")
|
|
327
|
+
self._connected = False
|
|
328
|
+
return False
|
|
329
|
+
|
|
330
|
+
return True
|
|
331
|
+
|
|
332
|
+
async def _ensure_connected(self) -> None:
|
|
333
|
+
"""Ensure the connector is connected, reconnecting if necessary.
|
|
334
|
+
|
|
335
|
+
Raises:
|
|
336
|
+
RuntimeError: If connection cannot be established and auto_reconnect is False.
|
|
337
|
+
"""
|
|
338
|
+
if not self.client_session:
|
|
339
|
+
raise RuntimeError("MCP client is not connected")
|
|
340
|
+
|
|
341
|
+
if not self.is_connected:
|
|
342
|
+
if self.auto_reconnect:
|
|
343
|
+
logger.debug("Connection lost, attempting to reconnect...")
|
|
344
|
+
try:
|
|
345
|
+
await self.connect()
|
|
346
|
+
logger.debug("Reconnection successful")
|
|
347
|
+
except Exception as e:
|
|
348
|
+
raise RuntimeError(f"Failed to reconnect to MCP server: {e}") from e
|
|
349
|
+
else:
|
|
350
|
+
raise RuntimeError(
|
|
351
|
+
"Connection to MCP server has been lost. Auto-reconnection is disabled. Please reconnect manually."
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
@telemetry("connector_call_tool")
|
|
355
|
+
async def call_tool(
|
|
356
|
+
self, name: str, arguments: dict[str, Any], read_timeout_seconds: timedelta | None = None
|
|
357
|
+
) -> CallToolResult:
|
|
358
|
+
"""Call an MCP tool with automatic reconnection handling.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
name: The name of the tool to call.
|
|
362
|
+
arguments: The arguments to pass to the tool.
|
|
363
|
+
read_timeout_seconds: timeout seconds when calling tool
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
The result of the tool call.
|
|
367
|
+
|
|
368
|
+
Raises:
|
|
369
|
+
RuntimeError: If the connection is lost and cannot be reestablished.
|
|
370
|
+
"""
|
|
371
|
+
|
|
372
|
+
# Ensure we're connected
|
|
373
|
+
await self._ensure_connected()
|
|
374
|
+
|
|
375
|
+
logger.debug(f"Calling tool '{name}' with arguments: {arguments}")
|
|
376
|
+
try:
|
|
377
|
+
result = await self.client_session.call_tool(name, arguments, read_timeout_seconds)
|
|
378
|
+
logger.debug(f"Tool '{name}' called with result: {result}")
|
|
379
|
+
return result
|
|
380
|
+
except Exception as e:
|
|
381
|
+
# Check if the error might be due to connection loss
|
|
382
|
+
if not self.is_connected:
|
|
383
|
+
raise RuntimeError(f"Tool call '{name}' failed due to connection loss: {e}") from e
|
|
384
|
+
else:
|
|
385
|
+
# Re-raise the original error if it's not connection-related
|
|
386
|
+
raise
|
|
387
|
+
|
|
388
|
+
@telemetry("connector_list_tools")
|
|
389
|
+
async def list_tools(self) -> list[Tool]:
|
|
390
|
+
"""List all available tools from the MCP implementation."""
|
|
391
|
+
|
|
392
|
+
if self.capabilities and not self.capabilities.tools:
|
|
393
|
+
logger.debug(f"Server {self.public_identifier} does not support tools")
|
|
394
|
+
return []
|
|
395
|
+
|
|
396
|
+
# Ensure we're connected
|
|
397
|
+
await self._ensure_connected()
|
|
398
|
+
|
|
399
|
+
logger.debug("Listing tools")
|
|
400
|
+
try:
|
|
401
|
+
result = await self.client_session.list_tools()
|
|
402
|
+
self._tools = result.tools
|
|
403
|
+
return result.tools
|
|
404
|
+
except McpError as e:
|
|
405
|
+
logger.error(f"Error listing tools for connector {self.public_identifier}: {e}")
|
|
406
|
+
return []
|
|
407
|
+
|
|
408
|
+
@telemetry("connector_list_resources")
|
|
409
|
+
async def list_resources(self) -> list[Resource]:
|
|
410
|
+
"""List all available resources from the MCP implementation."""
|
|
411
|
+
|
|
412
|
+
if self.capabilities and not self.capabilities.resources:
|
|
413
|
+
logger.debug(f"Server {self.public_identifier} does not support resources")
|
|
414
|
+
return []
|
|
415
|
+
|
|
416
|
+
# Ensure we're connected
|
|
417
|
+
await self._ensure_connected()
|
|
418
|
+
|
|
419
|
+
logger.debug("Listing resources")
|
|
420
|
+
try:
|
|
421
|
+
result = await self.client_session.list_resources()
|
|
422
|
+
self._resources = result.resources
|
|
423
|
+
return result.resources
|
|
424
|
+
except McpError as e:
|
|
425
|
+
logger.warning(f"Error listing resources for connector {self.public_identifier}: {e}")
|
|
426
|
+
return []
|
|
427
|
+
|
|
428
|
+
@telemetry("connector_read_resource")
|
|
429
|
+
async def read_resource(self, uri: AnyUrl) -> ReadResourceResult:
|
|
430
|
+
"""Read a resource by URI."""
|
|
431
|
+
await self._ensure_connected()
|
|
432
|
+
|
|
433
|
+
logger.debug(f"Reading resource: {uri}")
|
|
434
|
+
result = await self.client_session.read_resource(uri)
|
|
435
|
+
return result
|
|
436
|
+
|
|
437
|
+
@telemetry("connector_list_prompts")
|
|
438
|
+
async def list_prompts(self) -> list[Prompt]:
|
|
439
|
+
"""List all available prompts from the MCP implementation."""
|
|
440
|
+
|
|
441
|
+
if self.capabilities and not self.capabilities.prompts:
|
|
442
|
+
logger.debug(f"Server {self.public_identifier} does not support prompts")
|
|
443
|
+
return []
|
|
444
|
+
|
|
445
|
+
await self._ensure_connected()
|
|
446
|
+
|
|
447
|
+
logger.debug("Listing prompts")
|
|
448
|
+
try:
|
|
449
|
+
result = await self.client_session.list_prompts()
|
|
450
|
+
self._prompts = result.prompts
|
|
451
|
+
return result.prompts
|
|
452
|
+
except McpError as e:
|
|
453
|
+
logger.error(f"Error listing prompts for connector {self.public_identifier}: {e}")
|
|
454
|
+
return []
|
|
455
|
+
|
|
456
|
+
@telemetry("connector_get_prompt")
|
|
457
|
+
async def get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> GetPromptResult:
|
|
458
|
+
"""Get a prompt by name."""
|
|
459
|
+
await self._ensure_connected()
|
|
460
|
+
|
|
461
|
+
logger.debug(f"Getting prompt: {name}")
|
|
462
|
+
result = await self.client_session.get_prompt(name, arguments)
|
|
463
|
+
return result
|
|
464
|
+
|
|
465
|
+
async def request(self, method: str, params: dict[str, Any] | None = None) -> Any:
|
|
466
|
+
"""Send a raw request to the MCP implementation."""
|
|
467
|
+
await self._ensure_connected()
|
|
468
|
+
|
|
469
|
+
logger.debug(f"Sending request: {method} with params: {params}")
|
|
470
|
+
return await self.client_session.request({"method": method, "params": params or {}})
|