mcp-use 1.3.11__py3-none-any.whl → 1.3.12__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/client.py CHANGED
@@ -15,6 +15,7 @@ from mcp_use.types.sandbox import SandboxOptions
15
15
 
16
16
  from .config import create_connector_from_config, load_config_file
17
17
  from .logging import logger
18
+ from .middleware import Middleware, default_logging_middleware
18
19
  from .session import MCPSession
19
20
 
20
21
 
@@ -35,6 +36,7 @@ class MCPClient:
35
36
  elicitation_callback: ElicitationFnT | None = None,
36
37
  message_handler: MessageHandlerFnT | None = None,
37
38
  logging_callback: LoggingFnT | None = None,
39
+ middleware: list[Middleware] | None = None,
38
40
  ) -> None:
39
41
  """Initialize a new MCP client.
40
42
 
@@ -55,6 +57,12 @@ class MCPClient:
55
57
  self.elicitation_callback = elicitation_callback
56
58
  self.message_handler = message_handler
57
59
  self.logging_callback = logging_callback
60
+ # Add default logging middleware if no middleware provided, or prepend it to existing middleware
61
+ default_middleware = [default_logging_middleware]
62
+ if middleware:
63
+ self.middleware = default_middleware + middleware
64
+ else:
65
+ self.middleware = default_middleware
58
66
  # Load configuration if provided
59
67
  if config is not None:
60
68
  if isinstance(config, str):
@@ -151,6 +159,21 @@ class MCPClient:
151
159
  if name in self.active_sessions:
152
160
  self.active_sessions.remove(name)
153
161
 
162
+ def add_middleware(self, middleware: Middleware) -> None:
163
+ """Add a middleware.
164
+
165
+ Args:
166
+ middleware: The middleware to add
167
+ """
168
+ if len(self.sessions) == 0 and middleware not in self.middleware:
169
+ self.middleware.append(middleware)
170
+ return
171
+
172
+ if middleware not in self.middleware:
173
+ self.middleware.append(middleware)
174
+ for session in self.sessions.values():
175
+ session.connector.middleware_manager.add_middleware(middleware)
176
+
154
177
  def get_server_names(self) -> list[str]:
155
178
  """Get the list of configured server names.
156
179
 
@@ -201,6 +224,7 @@ class MCPClient:
201
224
  elicitation_callback=self.elicitation_callback,
202
225
  message_handler=self.message_handler,
203
226
  logging_callback=self.logging_callback,
227
+ middleware=self.middleware,
204
228
  )
205
229
 
206
230
  # Create the session
mcp_use/config.py CHANGED
@@ -13,6 +13,7 @@ from mcp_use.types.sandbox import SandboxOptions
13
13
 
14
14
  from .connectors import BaseConnector, HttpConnector, SandboxConnector, StdioConnector, WebSocketConnector
15
15
  from .connectors.utils import is_stdio_server
16
+ from .middleware import Middleware
16
17
 
17
18
 
18
19
  def load_config_file(filepath: str) -> dict[str, Any]:
@@ -36,6 +37,7 @@ def create_connector_from_config(
36
37
  elicitation_callback: ElicitationFnT | None = None,
37
38
  message_handler: MessageHandlerFnT | None = None,
38
39
  logging_callback: LoggingFnT | None = None,
40
+ middleware: list[Middleware] | None = None,
39
41
  ) -> BaseConnector:
40
42
  """Create a connector based on server configuration.
41
43
  This function can be called with just the server_config parameter:
@@ -59,6 +61,7 @@ def create_connector_from_config(
59
61
  elicitation_callback=elicitation_callback,
60
62
  message_handler=message_handler,
61
63
  logging_callback=logging_callback,
64
+ middleware=middleware,
62
65
  )
63
66
 
64
67
  # Sandboxed connector
@@ -72,6 +75,7 @@ def create_connector_from_config(
72
75
  elicitation_callback=elicitation_callback,
73
76
  message_handler=message_handler,
74
77
  logging_callback=logging_callback,
78
+ middleware=middleware,
75
79
  )
76
80
 
77
81
  # HTTP connector
@@ -86,6 +90,7 @@ def create_connector_from_config(
86
90
  elicitation_callback=elicitation_callback,
87
91
  message_handler=message_handler,
88
92
  logging_callback=logging_callback,
93
+ middleware=middleware,
89
94
  )
90
95
 
91
96
  # WebSocket connector
@@ -31,6 +31,7 @@ from pydantic import AnyUrl
31
31
  import mcp_use
32
32
 
33
33
  from ..logging import logger
34
+ from ..middleware import Middleware, MiddlewareManager
34
35
  from ..task_managers import ConnectionManager
35
36
 
36
37
 
@@ -46,6 +47,7 @@ class BaseConnector(ABC):
46
47
  elicitation_callback: ElicitationFnT | None = None,
47
48
  message_handler: MessageHandlerFnT | None = None,
48
49
  logging_callback: LoggingFnT | None = None,
50
+ middleware: list[Middleware] | None = None,
49
51
  ):
50
52
  """Initialize base connector with common attributes."""
51
53
  self.client_session: ClientSession | None = None
@@ -62,6 +64,12 @@ class BaseConnector(ABC):
62
64
  self.logging_callback = logging_callback
63
65
  self.capabilities: ServerCapabilities | None = None
64
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
+
65
73
  @property
66
74
  def client_info(self) -> Implementation:
67
75
  """Get the client info for the connector."""
@@ -17,6 +17,7 @@ from mcp_use.auth.oauth import OAuthClientProvider
17
17
  from ..auth import BearerAuth, OAuth
18
18
  from ..exceptions import OAuthAuthenticationError, OAuthDiscoveryError
19
19
  from ..logging import logger
20
+ from ..middleware import CallbackClientSession, Middleware
20
21
  from ..task_managers import SseConnectionManager, StreamableHttpConnectionManager
21
22
  from .base import BaseConnector
22
23
 
@@ -39,6 +40,7 @@ class HttpConnector(BaseConnector):
39
40
  elicitation_callback: ElicitationFnT | None = None,
40
41
  message_handler: MessageHandlerFnT | None = None,
41
42
  logging_callback: LoggingFnT | None = None,
43
+ middleware: list[Middleware] | None = None,
42
44
  ):
43
45
  """Initialize a new HTTP connector.
44
46
 
@@ -59,6 +61,7 @@ class HttpConnector(BaseConnector):
59
61
  elicitation_callback=elicitation_callback,
60
62
  message_handler=message_handler,
61
63
  logging_callback=logging_callback,
64
+ middleware=middleware,
62
65
  )
63
66
  self.base_url = base_url.rstrip("/")
64
67
  self.headers = headers or {}
@@ -158,7 +161,7 @@ class HttpConnector(BaseConnector):
158
161
  read_stream, write_stream = await connection_manager.start()
159
162
 
160
163
  # Test if this actually works by trying to create a client session and initialize it
161
- test_client = ClientSession(
164
+ raw_test_client = ClientSession(
162
165
  read_stream,
163
166
  write_stream,
164
167
  sampling_callback=self.sampling_callback,
@@ -167,7 +170,10 @@ class HttpConnector(BaseConnector):
167
170
  logging_callback=self.logging_callback,
168
171
  client_info=self.client_info,
169
172
  )
170
- await test_client.__aenter__()
173
+ await raw_test_client.__aenter__()
174
+
175
+ # Wrap test client with middleware temporarily for testing
176
+ test_client = CallbackClientSession(raw_test_client, self.public_identifier, self.middleware_manager)
171
177
 
172
178
  try:
173
179
  # Try to initialize - this is where streamable HTTP vs SSE difference should show up
@@ -209,7 +215,7 @@ class HttpConnector(BaseConnector):
209
215
  logger.error("MCP protocol error during initialization: %s", mcp_error.error)
210
216
  # Clean up the test client
211
217
  try:
212
- await test_client.__aexit__(None, None, None)
218
+ await raw_test_client.__aexit__(None, None, None)
213
219
  except Exception:
214
220
  pass
215
221
  raise mcp_error
@@ -218,7 +224,7 @@ class HttpConnector(BaseConnector):
218
224
  # This catches non-McpError exceptions, like a direct httpx timeout
219
225
  # but in the most cases this won't happen. It's for safety.
220
226
  try:
221
- await test_client.__aexit__(None, None, None)
227
+ await raw_test_client.__aexit__(None, None, None)
222
228
  except Exception:
223
229
  pass
224
230
  raise init_error
@@ -251,7 +257,7 @@ class HttpConnector(BaseConnector):
251
257
  read_stream, write_stream = await connection_manager.start()
252
258
 
253
259
  # Create the client session for SSE
254
- self.client_session = ClientSession(
260
+ raw_client_session = ClientSession(
255
261
  read_stream,
256
262
  write_stream,
257
263
  sampling_callback=self.sampling_callback,
@@ -260,7 +266,12 @@ class HttpConnector(BaseConnector):
260
266
  logging_callback=self.logging_callback,
261
267
  client_info=self.client_info,
262
268
  )
263
- await self.client_session.__aenter__()
269
+ await raw_client_session.__aenter__()
270
+
271
+ # Wrap with middleware
272
+ self.client_session = CallbackClientSession(
273
+ raw_client_session, self.public_identifier, self.middleware_manager
274
+ )
264
275
  self.transport_type = "SSE"
265
276
 
266
277
  except* Exception as sse_error:
@@ -290,4 +301,5 @@ class HttpConnector(BaseConnector):
290
301
  @property
291
302
  def public_identifier(self) -> str:
292
303
  """Get the identifier for the connector."""
293
- return {"type": self.transport_type, "base_url": self.base_url}
304
+ transport_type = getattr(self, "transport_type", "http")
305
+ return f"{transport_type}:{self.base_url}"
@@ -15,6 +15,7 @@ from mcp import ClientSession
15
15
  from mcp.client.session import ElicitationFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
16
16
 
17
17
  from ..logging import logger
18
+ from ..middleware import CallbackClientSession, Middleware
18
19
  from ..task_managers import SseConnectionManager
19
20
 
20
21
  # Import E2B SDK components (optional dependency)
@@ -52,6 +53,7 @@ class SandboxConnector(BaseConnector):
52
53
  elicitation_callback: ElicitationFnT | None = None,
53
54
  message_handler: MessageHandlerFnT | None = None,
54
55
  logging_callback: LoggingFnT | None = None,
56
+ middleware: list[Middleware] | None = None,
55
57
  ):
56
58
  """Initialize a new sandbox connector.
57
59
 
@@ -71,6 +73,7 @@ class SandboxConnector(BaseConnector):
71
73
  elicitation_callback=elicitation_callback,
72
74
  message_handler=message_handler,
73
75
  logging_callback=logging_callback,
76
+ middleware=middleware,
74
77
  )
75
78
  if Sandbox is None:
76
79
  raise ImportError(
@@ -226,7 +229,7 @@ class SandboxConnector(BaseConnector):
226
229
  read_stream, write_stream = await self._connection_manager.start()
227
230
 
228
231
  # Create the client session
229
- self.client_session = ClientSession(
232
+ raw_client_session = ClientSession(
230
233
  read_stream,
231
234
  write_stream,
232
235
  sampling_callback=self.sampling_callback,
@@ -235,7 +238,12 @@ class SandboxConnector(BaseConnector):
235
238
  logging_callback=self.logging_callback,
236
239
  client_info=self.client_info,
237
240
  )
238
- await self.client_session.__aenter__()
241
+ await raw_client_session.__aenter__()
242
+
243
+ # Wrap with middleware
244
+ self.client_session = CallbackClientSession(
245
+ raw_client_session, self.public_identifier, self.middleware_manager
246
+ )
239
247
 
240
248
  # Mark as connected
241
249
  self._connected = True
@@ -299,4 +307,5 @@ class SandboxConnector(BaseConnector):
299
307
  @property
300
308
  def public_identifier(self) -> str:
301
309
  """Get the identifier for the connector."""
302
- return {"type": "sandbox", "command": self.user_command, "args": self.user_args}
310
+ args_str = " ".join(self.user_args) if self.user_args else ""
311
+ return f"sandbox:{self.user_command} {args_str}".strip()
@@ -11,6 +11,7 @@ from mcp import ClientSession, StdioServerParameters
11
11
  from mcp.client.session import ElicitationFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
12
12
 
13
13
  from ..logging import logger
14
+ from ..middleware import CallbackClientSession, Middleware
14
15
  from ..task_managers import StdioConnectionManager
15
16
  from .base import BaseConnector
16
17
 
@@ -33,6 +34,7 @@ class StdioConnector(BaseConnector):
33
34
  elicitation_callback: ElicitationFnT | None = None,
34
35
  message_handler: MessageHandlerFnT | None = None,
35
36
  logging_callback: LoggingFnT | None = None,
37
+ middleware: list[Middleware] | None = None,
36
38
  ):
37
39
  """Initialize a new stdio connector.
38
40
 
@@ -49,6 +51,7 @@ class StdioConnector(BaseConnector):
49
51
  elicitation_callback=elicitation_callback,
50
52
  message_handler=message_handler,
51
53
  logging_callback=logging_callback,
54
+ middleware=middleware,
52
55
  )
53
56
  self.command = command
54
57
  self.args = args or [] # Ensure args is never None
@@ -71,7 +74,7 @@ class StdioConnector(BaseConnector):
71
74
  read_stream, write_stream = await self._connection_manager.start()
72
75
 
73
76
  # Create the client session
74
- self.client_session = ClientSession(
77
+ raw_client_session = ClientSession(
75
78
  read_stream,
76
79
  write_stream,
77
80
  sampling_callback=self.sampling_callback,
@@ -80,7 +83,12 @@ class StdioConnector(BaseConnector):
80
83
  logging_callback=self.logging_callback,
81
84
  client_info=self.client_info,
82
85
  )
83
- await self.client_session.__aenter__()
86
+ await raw_client_session.__aenter__()
87
+
88
+ # Wrap with middleware
89
+ self.client_session = CallbackClientSession(
90
+ raw_client_session, self.public_identifier, self.middleware_manager
91
+ )
84
92
 
85
93
  # Mark as connected
86
94
  self._connected = True
@@ -98,4 +106,4 @@ class StdioConnector(BaseConnector):
98
106
  @property
99
107
  def public_identifier(self) -> str:
100
108
  """Get the identifier for the connector."""
101
- return {"type": "stdio", "command&args": f"{self.command} {' '.join(self.args)}"}
109
+ return f"stdio:{self.command} {' '.join(self.args)}"
@@ -254,4 +254,4 @@ class WebSocketConnector(BaseConnector):
254
254
  @property
255
255
  def public_identifier(self) -> str:
256
256
  """Get the identifier for the connector."""
257
- return {"type": "websocket", "url": self.url}
257
+ return f"websocket:{self.url}"
@@ -0,0 +1,50 @@
1
+ """
2
+ Middleware package for MCP request interception and processing.
3
+
4
+ This package provides a flexible middleware system for intercepting MCP requests
5
+ and responses, enabling logging, metrics, caching, and custom processing.
6
+
7
+ The middleware system follows an Express.js-style pattern where middleware functions
8
+ receive a request context and a call_next function, allowing them to process both
9
+ incoming requests and outgoing responses.
10
+ """
11
+
12
+ # Core middleware implementation
13
+ # Default logging middleware
14
+ from .logging import default_logging_middleware
15
+
16
+ # Metrics middleware classes
17
+ from .metrics import (
18
+ CombinedAnalyticsMiddleware,
19
+ ErrorTrackingMiddleware,
20
+ MetricsMiddleware,
21
+ PerformanceMetricsMiddleware,
22
+ )
23
+
24
+ # Protocol types for type-safe middleware
25
+ from .middleware import (
26
+ CallbackClientSession,
27
+ MCPResponseContext,
28
+ Middleware,
29
+ MiddlewareContext,
30
+ MiddlewareManager,
31
+ NextFunctionT,
32
+ )
33
+
34
+ __all__ = [
35
+ # Core types and classes
36
+ "MiddlewareContext",
37
+ "MCPResponseContext",
38
+ "Middleware",
39
+ "MiddlewareManager",
40
+ "CallbackClientSession",
41
+ # Protocol types
42
+ "NextFunctionT",
43
+ # Default logging middleware
44
+ "default_logging_middleware",
45
+ # Metrics middleware
46
+ "MetricsMiddleware",
47
+ "PerformanceMetricsMiddleware",
48
+ "ErrorTrackingMiddleware",
49
+ "CombinedAnalyticsMiddleware",
50
+ ]
@@ -0,0 +1,31 @@
1
+ """
2
+ Default logging middleware for MCP requests.
3
+
4
+ Simple debug logging for all MCP requests and responses.
5
+ """
6
+
7
+ import time
8
+ from typing import Any
9
+
10
+ from ..logging import logger
11
+ from .middleware import Middleware, MiddlewareContext, NextFunctionT
12
+
13
+
14
+ class LoggingMiddleware(Middleware):
15
+ """Default logging middleware that logs all MCP requests and responses with logger.debug."""
16
+
17
+ async def on_request(self, context: MiddlewareContext[Any], call_next: NextFunctionT) -> Any:
18
+ """Logs all MCP requests and responses with logger.debug."""
19
+ logger.debug(f"[{context.id}] {context.connection_id} -> {context.method}")
20
+ try:
21
+ result = await call_next(context)
22
+ duration = time.time() - context.timestamp
23
+ logger.debug(f"[{context.id}] {context.connection_id} <- {context.method} ({duration:.3f}s)")
24
+ return result
25
+ except Exception as e:
26
+ duration = time.time() - context.timestamp
27
+ logger.debug(f"[{context.id}] {context.connection_id} <- {context.method} FAILED ({duration:.3f}s): {e}")
28
+ raise
29
+
30
+
31
+ default_logging_middleware = LoggingMiddleware()