mcp-use 1.3.0__py3-none-any.whl → 1.3.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.

@@ -6,6 +6,7 @@ to provide a simple interface for using MCP tools with different LLMs.
6
6
  """
7
7
 
8
8
  import logging
9
+ import time
9
10
  from collections.abc import AsyncIterator
10
11
 
11
12
  from langchain.agents import AgentExecutor, create_tool_calling_agent
@@ -22,6 +23,8 @@ from langchain_core.utils.input import get_color_mapping
22
23
 
23
24
  from mcp_use.client import MCPClient
24
25
  from mcp_use.connectors.base import BaseConnector
26
+ from mcp_use.telemetry.posthog import Telemetry
27
+ from mcp_use.telemetry.utils import extract_model_info
25
28
 
26
29
  from ..adapters.langchain_adapter import LangChainAdapter
27
30
  from ..logging import logger
@@ -93,6 +96,9 @@ class MCPAgent:
93
96
  # Create the adapter for tool conversion
94
97
  self.adapter = LangChainAdapter(disallowed_tools=self.disallowed_tools)
95
98
 
99
+ # Initialize telemetry
100
+ self.telemetry = Telemetry()
101
+
96
102
  # Initialize server manager if requested
97
103
  self.server_manager = None
98
104
  if self.use_server_manager:
@@ -104,6 +110,9 @@ class MCPAgent:
104
110
  self._agent_executor: AgentExecutor | None = None
105
111
  self._system_message: SystemMessage | None = None
106
112
 
113
+ # Track model info for telemetry
114
+ self._model_provider, self._model_name = extract_model_info(self.llm)
115
+
107
116
  async def initialize(self) -> None:
108
117
  """Initialize the MCP client and agent."""
109
118
  logger.info("🚀 Initializing MCP agent and connecting to services...")
@@ -130,6 +139,7 @@ class MCPAgent:
130
139
  if not self._sessions:
131
140
  logger.info("🔄 No active sessions found, creating new ones...")
132
141
  self._sessions = await self.client.create_all_sessions()
142
+ self.connectors = [session.connector for session in self._sessions.values()]
133
143
  logger.info(f"✅ Created {len(self._sessions)} new sessions")
134
144
 
135
145
  # Create LangChain tools directly from the client using the adapter
@@ -141,7 +151,7 @@ class MCPAgent:
141
151
  connectors_to_use = self.connectors
142
152
  logger.info(f"🔗 Connecting to {len(connectors_to_use)} direct connectors...")
143
153
  for connector in connectors_to_use:
144
- if not hasattr(connector, "client") or connector.client is None:
154
+ if not hasattr(connector, "client_session") or connector.client_session is None:
145
155
  await connector.connect()
146
156
 
147
157
  # Create LangChain tools using the adapter with connectors
@@ -374,13 +384,58 @@ class MCPAgent:
374
384
  async for chunk in agent.astream("hello"):
375
385
  print(chunk, end="|", flush=True)
376
386
  """
377
- async for chunk in self._generate_response_chunks_async(
378
- query=query,
379
- max_steps=max_steps,
380
- manage_connector=manage_connector,
381
- external_history=external_history,
382
- ):
383
- yield chunk
387
+ start_time = time.time()
388
+ success = False
389
+ chunk_count = 0
390
+ total_response_length = 0
391
+
392
+ try:
393
+ async for chunk in self._generate_response_chunks_async(
394
+ query=query,
395
+ max_steps=max_steps,
396
+ manage_connector=manage_connector,
397
+ external_history=external_history,
398
+ ):
399
+ chunk_count += 1
400
+ if isinstance(chunk, str):
401
+ total_response_length += len(chunk)
402
+ yield chunk
403
+ success = True
404
+ finally:
405
+ # Track comprehensive execution data for streaming
406
+ execution_time_ms = int((time.time() - start_time) * 1000)
407
+
408
+ server_count = 0
409
+ if self.client:
410
+ server_count = len(self.client.get_all_active_sessions())
411
+ elif self.connectors:
412
+ server_count = len(self.connectors)
413
+
414
+ conversation_history_length = (
415
+ len(self._conversation_history) if self.memory_enabled else 0
416
+ )
417
+
418
+ self.telemetry.track_agent_execution(
419
+ execution_method="astream",
420
+ query=query,
421
+ success=success,
422
+ model_provider=self._model_provider,
423
+ model_name=self._model_name,
424
+ server_count=server_count,
425
+ server_identifiers=[connector.public_identifier for connector in self.connectors],
426
+ total_tools_available=len(self._tools) if self._tools else 0,
427
+ tools_available_names=[tool.name for tool in self._tools],
428
+ max_steps_configured=self.max_steps,
429
+ memory_enabled=self.memory_enabled,
430
+ use_server_manager=self.use_server_manager,
431
+ max_steps_used=max_steps,
432
+ manage_connector=manage_connector,
433
+ external_history_used=external_history is not None,
434
+ response=f"[STREAMED RESPONSE - {total_response_length} chars]",
435
+ execution_time_ms=execution_time_ms,
436
+ error_type=None if success else "streaming_error",
437
+ conversation_history_length=conversation_history_length,
438
+ )
384
439
 
385
440
  async def run(
386
441
  self,
@@ -409,6 +464,10 @@ class MCPAgent:
409
464
  """
410
465
  result = ""
411
466
  initialized_here = False
467
+ start_time = time.time()
468
+ tools_used_names = []
469
+ steps_taken = 0
470
+ success = False
412
471
 
413
472
  try:
414
473
  # Initialize if needed
@@ -464,6 +523,7 @@ class MCPAgent:
464
523
  logger.info(f"🏁 Starting agent execution with max_steps={steps}")
465
524
 
466
525
  for step_num in range(steps):
526
+ steps_taken = step_num + 1
467
527
  # --- Check for tool updates if using server manager ---
468
528
  if self.use_server_manager and self.server_manager:
469
529
  current_tools = self.server_manager.tools
@@ -510,9 +570,10 @@ class MCPAgent:
510
570
  # If it's actions/steps, add to intermediate steps
511
571
  intermediate_steps.extend(next_step_output)
512
572
 
513
- # Log tool calls
573
+ # Log tool calls and track tool usage
514
574
  for action, output in next_step_output:
515
575
  tool_name = action.tool
576
+ tools_used_names.append(tool_name)
516
577
  tool_input_str = str(action.tool_input)
517
578
  # Truncate long inputs for readability
518
579
  if len(tool_input_str) > 100:
@@ -555,7 +616,8 @@ class MCPAgent:
555
616
  if self.memory_enabled:
556
617
  self.add_to_history(AIMessage(content=result))
557
618
 
558
- logger.info("🎉 Agent execution complete")
619
+ logger.info(f"🎉 Agent execution complete in {time.time() - start_time} seconds")
620
+ success = True
559
621
  return result
560
622
 
561
623
  except Exception as e:
@@ -566,6 +628,43 @@ class MCPAgent:
566
628
  raise
567
629
 
568
630
  finally:
631
+ # Track comprehensive execution data
632
+ execution_time_ms = int((time.time() - start_time) * 1000)
633
+
634
+ server_count = 0
635
+ if self.client:
636
+ server_count = len(self.client.get_all_active_sessions())
637
+ elif self.connectors:
638
+ server_count = len(self.connectors)
639
+
640
+ conversation_history_length = (
641
+ len(self._conversation_history) if self.memory_enabled else 0
642
+ )
643
+ self.telemetry.track_agent_execution(
644
+ execution_method="run",
645
+ query=query,
646
+ success=success,
647
+ model_provider=self._model_provider,
648
+ model_name=self._model_name,
649
+ server_count=server_count,
650
+ server_identifiers=[connector.public_identifier for connector in self.connectors],
651
+ total_tools_available=len(self._tools) if self._tools else 0,
652
+ tools_available_names=[tool.name for tool in self._tools],
653
+ max_steps_configured=self.max_steps,
654
+ memory_enabled=self.memory_enabled,
655
+ use_server_manager=self.use_server_manager,
656
+ max_steps_used=max_steps,
657
+ manage_connector=manage_connector,
658
+ external_history_used=external_history is not None,
659
+ steps_taken=steps_taken,
660
+ tools_used_count=len(tools_used_names),
661
+ tools_used_names=tools_used_names,
662
+ response=result,
663
+ execution_time_ms=execution_time_ms,
664
+ error_type=None if success else "execution_error",
665
+ conversation_history_length=conversation_history_length,
666
+ )
667
+
569
668
  # Clean up if necessary (e.g., if not using client-managed sessions)
570
669
  if manage_connector and not self.client and not initialized_here:
571
670
  logger.info("🧹 Closing agent after query completion")
mcp_use/client.py CHANGED
@@ -9,10 +9,11 @@ import json
9
9
  import warnings
10
10
  from typing import Any
11
11
 
12
+ from mcp_use.types.sandbox import SandboxOptions
13
+
12
14
  from .config import create_connector_from_config, load_config_file
13
15
  from .logging import logger
14
16
  from .session import MCPSession
15
- from .types.clientoptions import ClientOptions
16
17
 
17
18
 
18
19
  class MCPClient:
@@ -25,17 +26,20 @@ class MCPClient:
25
26
  def __init__(
26
27
  self,
27
28
  config: str | dict[str, Any] | None = None,
28
- options: ClientOptions | None = None,
29
+ sandbox: bool = False,
30
+ sandbox_options: SandboxOptions | None = None,
29
31
  ) -> None:
30
32
  """Initialize a new MCP client.
31
33
 
32
34
  Args:
33
35
  config: Either a dict containing configuration or a path to a JSON config file.
34
36
  If None, an empty configuration is used.
35
- options: Configuration options for the client.
37
+ sandbox: Whether to use sandboxed execution mode for running MCP servers.
38
+ sandbox_options: Optional sandbox configuration options.
36
39
  """
37
40
  self.config: dict[str, Any] = {}
38
- self.options = options or {}
41
+ self.sandbox = sandbox
42
+ self.sandbox_options = sandbox_options
39
43
  self.sessions: dict[str, MCPSession] = {}
40
44
  self.active_sessions: list[str] = []
41
45
 
@@ -47,24 +51,35 @@ class MCPClient:
47
51
  self.config = config
48
52
 
49
53
  @classmethod
50
- def from_dict(cls, config: dict[str, Any], options: ClientOptions | None = None) -> "MCPClient":
54
+ def from_dict(
55
+ cls,
56
+ config: dict[str, Any],
57
+ sandbox: bool = False,
58
+ sandbox_options: SandboxOptions | None = None,
59
+ ) -> "MCPClient":
51
60
  """Create a MCPClient from a dictionary.
52
61
 
53
62
  Args:
54
63
  config: The configuration dictionary.
55
- options: Optional client configuration options.
64
+ sandbox: Whether to use sandboxed execution mode for running MCP servers.
65
+ sandbox_options: Optional sandbox configuration options.
56
66
  """
57
- return cls(config=config, options=options)
67
+ return cls(config=config, sandbox=sandbox, sandbox_options=sandbox_options)
58
68
 
59
69
  @classmethod
60
- def from_config_file(cls, filepath: str, options: ClientOptions | None = None) -> "MCPClient":
70
+ def from_config_file(
71
+ cls, filepath: str, sandbox: bool = False, sandbox_options: SandboxOptions | None = None
72
+ ) -> "MCPClient":
61
73
  """Create a MCPClient from a configuration file.
62
74
 
63
75
  Args:
64
76
  filepath: The path to the configuration file.
65
- options: Optional client configuration options.
77
+ sandbox: Whether to use sandboxed execution mode for running MCP servers.
78
+ sandbox_options: Optional sandbox configuration options.
66
79
  """
67
- return cls(config=load_config_file(filepath), options=options)
80
+ return cls(
81
+ config=load_config_file(filepath), sandbox=sandbox, sandbox_options=sandbox_options
82
+ )
68
83
 
69
84
  def add_server(
70
85
  self,
@@ -137,7 +152,9 @@ class MCPClient:
137
152
  server_config = servers[server_name]
138
153
 
139
154
  # Create connector with options
140
- connector = create_connector_from_config(server_config, options=self.options)
155
+ connector = create_connector_from_config(
156
+ server_config, sandbox=self.sandbox, sandbox_options=self.sandbox_options
157
+ )
141
158
 
142
159
  # Create the session
143
160
  session = MCPSession(connector)
mcp_use/config.py CHANGED
@@ -7,6 +7,8 @@ This module provides functionality to load MCP configuration from JSON files.
7
7
  import json
8
8
  from typing import Any
9
9
 
10
+ from mcp_use.types.sandbox import SandboxOptions
11
+
10
12
  from .connectors import (
11
13
  BaseConnector,
12
14
  HttpConnector,
@@ -15,7 +17,6 @@ from .connectors import (
15
17
  WebSocketConnector,
16
18
  )
17
19
  from .connectors.utils import is_stdio_server
18
- from .types.clientoptions import ClientOptions
19
20
 
20
21
 
21
22
  def load_config_file(filepath: str) -> dict[str, Any]:
@@ -33,24 +34,23 @@ def load_config_file(filepath: str) -> dict[str, Any]:
33
34
 
34
35
  def create_connector_from_config(
35
36
  server_config: dict[str, Any],
36
- options: ClientOptions | None = None,
37
+ sandbox: bool = False,
38
+ sandbox_options: SandboxOptions | None = None,
37
39
  ) -> BaseConnector:
38
40
  """Create a connector based on server configuration.
39
41
  This function can be called with just the server_config parameter:
40
42
  create_connector_from_config(server_config)
41
43
  Args:
42
44
  server_config: The server configuration section
43
- options: Optional client configuration options including sandboxing preferences.
44
- If None, default client options will be used.
45
+ sandbox: Whether to use sandboxed execution mode for running MCP servers.
46
+ sandbox_options: Optional sandbox configuration options.
45
47
 
46
48
  Returns:
47
49
  A configured connector instance
48
50
  """
49
- # Use default options if none provided
50
- options = options or {"is_sandboxed": False}
51
51
 
52
52
  # Stdio connector (command-based)
53
- if is_stdio_server(server_config) and not options.get("is_sandboxed", False):
53
+ if is_stdio_server(server_config) and not sandbox:
54
54
  return StdioConnector(
55
55
  command=server_config["command"],
56
56
  args=server_config["args"],
@@ -58,12 +58,12 @@ def create_connector_from_config(
58
58
  )
59
59
 
60
60
  # Sandboxed connector
61
- elif is_stdio_server(server_config) and options.get("is_sandboxed", False):
61
+ elif is_stdio_server(server_config) and sandbox:
62
62
  return SandboxConnector(
63
63
  command=server_config["command"],
64
64
  args=server_config["args"],
65
65
  env=server_config.get("env", None),
66
- e2b_options=options.get("sandbox_options", {}),
66
+ e2b_options=sandbox_options,
67
67
  )
68
68
 
69
69
  # HTTP connector
@@ -24,18 +24,25 @@ class BaseConnector(ABC):
24
24
 
25
25
  def __init__(self):
26
26
  """Initialize base connector with common attributes."""
27
- self.client: ClientSession | None = None
27
+ self.client_session: ClientSession | None = None
28
28
  self._connection_manager: ConnectionManager | None = None
29
29
  self._tools: list[Tool] | None = None
30
30
  self._resources: list[Resource] | None = None
31
31
  self._prompts: list[Prompt] | None = None
32
32
  self._connected = False
33
+ self.auto_reconnect = True # Whether to automatically reconnect on connection loss (not configurable for now), may be made configurable through the connector_config
33
34
 
34
35
  @abstractmethod
35
36
  async def connect(self) -> None:
36
37
  """Establish a connection to the MCP implementation."""
37
38
  pass
38
39
 
40
+ @property
41
+ @abstractmethod
42
+ def public_identifier(self) -> str:
43
+ """Get the identifier for the connector."""
44
+ pass
45
+
39
46
  async def disconnect(self) -> None:
40
47
  """Close the connection to the MCP implementation."""
41
48
  if not self._connected:
@@ -52,16 +59,16 @@ class BaseConnector(ABC):
52
59
  errors = []
53
60
 
54
61
  # First close the client session
55
- if self.client:
62
+ if self.client_session:
56
63
  try:
57
64
  logger.debug("Closing client session")
58
- await self.client.__aexit__(None, None, None)
65
+ await self.client_session.__aexit__(None, None, None)
59
66
  except Exception as e:
60
67
  error_msg = f"Error closing client session: {e}"
61
68
  logger.warning(error_msg)
62
69
  errors.append(error_msg)
63
70
  finally:
64
- self.client = None
71
+ self.client_session = None
65
72
 
66
73
  # Then stop the connection manager
67
74
  if self._connection_manager:
@@ -85,13 +92,13 @@ class BaseConnector(ABC):
85
92
 
86
93
  async def initialize(self) -> dict[str, Any]:
87
94
  """Initialize the MCP session and return session information."""
88
- if not self.client:
95
+ if not self.client_session:
89
96
  raise RuntimeError("MCP client is not connected")
90
97
 
91
98
  logger.debug("Initializing MCP session")
92
99
 
93
100
  # Initialize the session
94
- result = await self.client.initialize()
101
+ result = await self.client_session.initialize()
95
102
 
96
103
  server_capabilities = result.capabilities
97
104
 
@@ -116,11 +123,7 @@ class BaseConnector(ABC):
116
123
  else:
117
124
  self._prompts = []
118
125
 
119
- logger.debug(
120
- f"MCP session initialized with {len(self._tools)} tools, "
121
- f"{len(self._resources)} resources, "
122
- f"and {len(self._prompts)} prompts"
123
- )
126
+ logger.debug(f"MCP session initialized with {len(self._tools)} tools, " f"{len(self._resources)} resources, " f"and {len(self._prompts)} prompts")
124
127
 
125
128
  return result
126
129
 
@@ -145,24 +148,121 @@ class BaseConnector(ABC):
145
148
  raise RuntimeError("MCP client is not initialized")
146
149
  return self._prompts
147
150
 
148
- async def call_tool(self, name: str, arguments: dict[str, Any]) -> CallToolResult:
149
- """Call an MCP tool with the given arguments."""
150
- if not self.client:
151
+ @property
152
+ def is_connected(self) -> bool:
153
+ """Check if the connector is actually connected and the connection is alive.
154
+
155
+ This property checks not only the connected flag but also verifies that
156
+ the underlying connection manager and streams are still active.
157
+
158
+ Returns:
159
+ True if the connector is connected and the connection is alive, False otherwise.
160
+ """
161
+
162
+ # Check if we have a client session
163
+ if not self.client_session:
164
+ # Update the connected flag since we don't have a client session
165
+ self._connected = False
166
+ return False
167
+
168
+ # First check the basic connected flag
169
+ if not self._connected:
170
+ return False
171
+
172
+ # Check if we have a connection manager and if its task is still running
173
+ if self._connection_manager:
174
+ try:
175
+ # Check if the connection manager task is done (indicates disconnection)
176
+ if hasattr(self._connection_manager, "_task") and self._connection_manager._task:
177
+ if self._connection_manager._task.done():
178
+ logger.debug("Connection manager task is done, marking as disconnected")
179
+ self._connected = False
180
+ return False
181
+
182
+ # For HTTP-based connectors, also check if streams are still open
183
+ # Use the get_streams method to get the current connection
184
+ streams = self._connection_manager.get_streams()
185
+ if streams:
186
+ # Connection should be a tuple of (read_stream, write_stream)
187
+ if isinstance(streams, tuple) and len(streams) == 2:
188
+ read_stream, write_stream = streams
189
+ # Check if streams are closed using getattr with default value
190
+ if getattr(read_stream, "_closed", False):
191
+ logger.debug("Read stream is closed, marking as disconnected")
192
+ self._connected = False
193
+ return False
194
+ if getattr(write_stream, "_closed", False):
195
+ logger.debug("Write stream is closed, marking as disconnected")
196
+ self._connected = False
197
+ return False
198
+
199
+ except Exception as e:
200
+ # If we can't check the connection state, assume disconnected for safety
201
+ logger.debug(f"Error checking connection state: {e}, marking as disconnected")
202
+ self._connected = False
203
+ return False
204
+
205
+ return True
206
+
207
+ async def _ensure_connected(self) -> None:
208
+ """Ensure the connector is connected, reconnecting if necessary.
209
+
210
+ Raises:
211
+ RuntimeError: If connection cannot be established and auto_reconnect is False.
212
+ """
213
+ if not self.client_session:
151
214
  raise RuntimeError("MCP client is not connected")
152
215
 
216
+ if not self.is_connected:
217
+ if self.auto_reconnect:
218
+ logger.debug("Connection lost, attempting to reconnect...")
219
+ try:
220
+ await self.connect()
221
+ logger.debug("Reconnection successful")
222
+ except Exception as e:
223
+ raise RuntimeError(f"Failed to reconnect to MCP server: {e}") from e
224
+ else:
225
+ raise RuntimeError("Connection to MCP server has been lost. " "Auto-reconnection is disabled. Please reconnect manually.")
226
+
227
+ async def call_tool(self, name: str, arguments: dict[str, Any]) -> CallToolResult:
228
+ """Call an MCP tool with automatic reconnection handling.
229
+
230
+ Args:
231
+ name: The name of the tool to call.
232
+ arguments: The arguments to pass to the tool.
233
+
234
+ Returns:
235
+ The result of the tool call.
236
+
237
+ Raises:
238
+ RuntimeError: If the connection is lost and cannot be reestablished.
239
+ """
240
+
241
+ # Ensure we're connected
242
+ await self._ensure_connected()
243
+
153
244
  logger.debug(f"Calling tool '{name}' with arguments: {arguments}")
154
- result = await self.client.call_tool(name, arguments)
155
- logger.debug(f"Tool '{name}' called with result: {result}")
156
- return result
245
+ try:
246
+ result = await self.client_session.call_tool(name, arguments)
247
+ logger.debug(f"Tool '{name}' called with result: {result}")
248
+ return result
249
+ except Exception as e:
250
+ # Check if the error might be due to connection loss
251
+ if not self.is_connected:
252
+ raise RuntimeError(f"Tool call '{name}' failed due to connection loss: {e}") from e
253
+ else:
254
+ # Re-raise the original error if it's not connection-related
255
+ raise
157
256
 
158
257
  async def list_tools(self) -> list[Tool]:
159
258
  """List all available tools from the MCP implementation."""
160
- if not self.client:
161
- raise RuntimeError("MCP client is not connected")
259
+
260
+ # Ensure we're connected
261
+ await self._ensure_connected()
162
262
 
163
263
  logger.debug("Listing tools")
164
264
  try:
165
- result = await self.client.list_tools()
265
+ result = await self.client_session.list_tools()
166
266
  return result.tools
167
267
  except McpError as e:
168
268
  logger.error(f"Error listing tools: {e}")
@@ -170,12 +270,12 @@ class BaseConnector(ABC):
170
270
 
171
271
  async def list_resources(self) -> list[Resource]:
172
272
  """List all available resources from the MCP implementation."""
173
- if not self.client:
174
- raise RuntimeError("MCP client is not connected")
273
+ # Ensure we're connected
274
+ await self._ensure_connected()
175
275
 
176
276
  logger.debug("Listing resources")
177
277
  try:
178
- result = await self.client.list_resources()
278
+ result = await self.client_session.list_resources()
179
279
  return result.resources
180
280
  except McpError as e:
181
281
  logger.error(f"Error listing resources: {e}")
@@ -183,41 +283,39 @@ class BaseConnector(ABC):
183
283
 
184
284
  async def read_resource(self, uri: str) -> ReadResourceResult:
185
285
  """Read a resource by URI."""
186
- if not self.client:
286
+ if not self.client_session:
187
287
  raise RuntimeError("MCP client is not connected")
188
288
 
189
289
  logger.debug(f"Reading resource: {uri}")
190
- result = await self.client.read_resource(uri)
290
+ result = await self.client_session.read_resource(uri)
191
291
  return result
192
292
 
193
293
  async def list_prompts(self) -> list[Prompt]:
194
294
  """List all available prompts from the MCP implementation."""
195
- if not self.client:
196
- raise RuntimeError("MCP client is not connected")
295
+ # Ensure we're connected
296
+ await self._ensure_connected()
197
297
 
198
298
  logger.debug("Listing prompts")
199
299
  try:
200
- result = await self.client.list_prompts()
300
+ result = await self.client_session.list_prompts()
201
301
  return result.prompts
202
302
  except McpError as e:
203
303
  logger.error(f"Error listing prompts: {e}")
204
304
  return []
205
305
 
206
- async def get_prompt(
207
- self, name: str, arguments: dict[str, Any] | None = None
208
- ) -> GetPromptResult:
306
+ async def get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> GetPromptResult:
209
307
  """Get a prompt by name."""
210
- if not self.client:
211
- raise RuntimeError("MCP client is not connected")
308
+ # Ensure we're connected
309
+ await self._ensure_connected()
212
310
 
213
311
  logger.debug(f"Getting prompt: {name}")
214
- result = await self.client.get_prompt(name, arguments)
312
+ result = await self.client_session.get_prompt(name, arguments)
215
313
  return result
216
314
 
217
315
  async def request(self, method: str, params: dict[str, Any] | None = None) -> Any:
218
316
  """Send a raw request to the MCP implementation."""
219
- if not self.client:
220
- raise RuntimeError("MCP client is not connected")
317
+ # Ensure we're connected
318
+ await self._ensure_connected()
221
319
 
222
320
  logger.debug(f"Sending request: {method} with params: {params}")
223
- return await self.client.request({"method": method, "params": params or {}})
321
+ return await self.client_session.request({"method": method, "params": params or {}})