chuk-tool-processor 0.6.12__py3-none-any.whl → 0.6.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 chuk-tool-processor might be problematic. Click here for more details.

Files changed (56) hide show
  1. chuk_tool_processor/core/__init__.py +1 -1
  2. chuk_tool_processor/core/exceptions.py +10 -4
  3. chuk_tool_processor/core/processor.py +97 -97
  4. chuk_tool_processor/execution/strategies/inprocess_strategy.py +142 -150
  5. chuk_tool_processor/execution/strategies/subprocess_strategy.py +200 -205
  6. chuk_tool_processor/execution/tool_executor.py +82 -84
  7. chuk_tool_processor/execution/wrappers/caching.py +102 -103
  8. chuk_tool_processor/execution/wrappers/rate_limiting.py +45 -42
  9. chuk_tool_processor/execution/wrappers/retry.py +23 -25
  10. chuk_tool_processor/logging/__init__.py +23 -17
  11. chuk_tool_processor/logging/context.py +40 -45
  12. chuk_tool_processor/logging/formatter.py +22 -21
  13. chuk_tool_processor/logging/helpers.py +24 -38
  14. chuk_tool_processor/logging/metrics.py +11 -13
  15. chuk_tool_processor/mcp/__init__.py +8 -12
  16. chuk_tool_processor/mcp/mcp_tool.py +124 -112
  17. chuk_tool_processor/mcp/register_mcp_tools.py +17 -17
  18. chuk_tool_processor/mcp/setup_mcp_http_streamable.py +11 -13
  19. chuk_tool_processor/mcp/setup_mcp_sse.py +11 -13
  20. chuk_tool_processor/mcp/setup_mcp_stdio.py +7 -9
  21. chuk_tool_processor/mcp/stream_manager.py +168 -204
  22. chuk_tool_processor/mcp/transport/__init__.py +4 -4
  23. chuk_tool_processor/mcp/transport/base_transport.py +43 -58
  24. chuk_tool_processor/mcp/transport/http_streamable_transport.py +145 -163
  25. chuk_tool_processor/mcp/transport/sse_transport.py +217 -255
  26. chuk_tool_processor/mcp/transport/stdio_transport.py +171 -189
  27. chuk_tool_processor/models/__init__.py +1 -1
  28. chuk_tool_processor/models/execution_strategy.py +16 -21
  29. chuk_tool_processor/models/streaming_tool.py +28 -25
  30. chuk_tool_processor/models/tool_call.py +19 -34
  31. chuk_tool_processor/models/tool_export_mixin.py +22 -8
  32. chuk_tool_processor/models/tool_result.py +40 -77
  33. chuk_tool_processor/models/validated_tool.py +14 -16
  34. chuk_tool_processor/plugins/__init__.py +1 -1
  35. chuk_tool_processor/plugins/discovery.py +10 -10
  36. chuk_tool_processor/plugins/parsers/__init__.py +1 -1
  37. chuk_tool_processor/plugins/parsers/base.py +1 -2
  38. chuk_tool_processor/plugins/parsers/function_call_tool.py +13 -8
  39. chuk_tool_processor/plugins/parsers/json_tool.py +4 -3
  40. chuk_tool_processor/plugins/parsers/openai_tool.py +12 -7
  41. chuk_tool_processor/plugins/parsers/xml_tool.py +4 -4
  42. chuk_tool_processor/registry/__init__.py +12 -12
  43. chuk_tool_processor/registry/auto_register.py +22 -30
  44. chuk_tool_processor/registry/decorators.py +127 -129
  45. chuk_tool_processor/registry/interface.py +26 -23
  46. chuk_tool_processor/registry/metadata.py +27 -22
  47. chuk_tool_processor/registry/provider.py +17 -18
  48. chuk_tool_processor/registry/providers/__init__.py +16 -19
  49. chuk_tool_processor/registry/providers/memory.py +18 -25
  50. chuk_tool_processor/registry/tool_export.py +42 -51
  51. chuk_tool_processor/utils/validation.py +15 -16
  52. {chuk_tool_processor-0.6.12.dist-info → chuk_tool_processor-0.6.13.dist-info}/METADATA +1 -1
  53. chuk_tool_processor-0.6.13.dist-info/RECORD +60 -0
  54. chuk_tool_processor-0.6.12.dist-info/RECORD +0 -60
  55. {chuk_tool_processor-0.6.12.dist-info → chuk_tool_processor-0.6.13.dist-info}/WHEEL +0 -0
  56. {chuk_tool_processor-0.6.12.dist-info → chuk_tool_processor-0.6.13.dist-info}/top_level.txt +0 -0
@@ -2,47 +2,51 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import asyncio
5
- import json
6
- import time
7
- from typing import Dict, Any, List, Optional
8
5
  import logging
6
+ import time
7
+ from typing import Any
9
8
 
10
- from .base_transport import MCPBaseTransport
11
-
12
- # Import chuk-mcp HTTP transport components
13
- from chuk_mcp.transports.http import http_client
14
- from chuk_mcp.transports.http.parameters import StreamableHTTPParameters
15
- from chuk_mcp.protocol.messages import (
9
+ from chuk_mcp.protocol.messages import ( # type: ignore[import-untyped]
16
10
  send_initialize,
17
- send_ping,
18
- send_tools_list,
19
- send_tools_call,
11
+ send_ping,
12
+ send_prompts_get,
13
+ send_prompts_list,
20
14
  send_resources_list,
21
15
  send_resources_read,
22
- send_prompts_list,
23
- send_prompts_get,
16
+ send_tools_call,
17
+ send_tools_list,
24
18
  )
25
19
 
20
+ # Import chuk-mcp HTTP transport components
21
+ from chuk_mcp.transports.http import http_client # type: ignore[import-untyped]
22
+ from chuk_mcp.transports.http.parameters import StreamableHTTPParameters # type: ignore[import-untyped]
23
+
24
+ from .base_transport import MCPBaseTransport
25
+
26
26
  logger = logging.getLogger(__name__)
27
27
 
28
28
 
29
29
  class HTTPStreamableTransport(MCPBaseTransport):
30
30
  """
31
31
  HTTP Streamable transport using chuk-mcp HTTP client.
32
-
33
- ENHANCED: Now matches SSE transport robustness with improved connection
32
+
33
+ ENHANCED: Now matches SSE transport robustness with improved connection
34
34
  management, health monitoring, and comprehensive error handling.
35
35
  """
36
36
 
37
- def __init__(self, url: str, api_key: Optional[str] = None,
38
- headers: Optional[Dict[str, str]] = None, # NEW: Headers support
39
- connection_timeout: float = 30.0,
40
- default_timeout: float = 30.0,
41
- session_id: Optional[str] = None,
42
- enable_metrics: bool = True):
37
+ def __init__(
38
+ self,
39
+ url: str,
40
+ api_key: str | None = None,
41
+ headers: dict[str, str] | None = None, # NEW: Headers support
42
+ connection_timeout: float = 30.0,
43
+ default_timeout: float = 30.0,
44
+ session_id: str | None = None,
45
+ enable_metrics: bool = True,
46
+ ):
43
47
  """
44
48
  Initialize HTTP Streamable transport with enhanced configuration.
45
-
49
+
46
50
  Args:
47
51
  url: HTTP server URL (should end with /mcp)
48
52
  api_key: Optional API key for authentication
@@ -53,18 +57,18 @@ class HTTPStreamableTransport(MCPBaseTransport):
53
57
  enable_metrics: Whether to track performance metrics
54
58
  """
55
59
  # Ensure URL points to the /mcp endpoint
56
- if not url.endswith('/mcp'):
60
+ if not url.endswith("/mcp"):
57
61
  self.url = f"{url.rstrip('/')}/mcp"
58
62
  else:
59
63
  self.url = url
60
-
64
+
61
65
  self.api_key = api_key
62
66
  self.configured_headers = headers or {} # NEW: Store configured headers
63
67
  self.connection_timeout = connection_timeout
64
68
  self.default_timeout = default_timeout
65
69
  self.session_id = session_id
66
70
  self.enable_metrics = enable_metrics
67
-
71
+
68
72
  logger.debug("HTTP Streamable transport initialized with URL: %s", self.url)
69
73
  if self.api_key:
70
74
  logger.debug("API key configured for authentication")
@@ -72,18 +76,18 @@ class HTTPStreamableTransport(MCPBaseTransport):
72
76
  logger.debug("Custom headers configured: %s", list(self.configured_headers.keys()))
73
77
  if self.session_id:
74
78
  logger.debug("Session ID configured: %s", self.session_id)
75
-
79
+
76
80
  # State tracking (enhanced like SSE)
77
81
  self._http_context = None
78
82
  self._read_stream = None
79
83
  self._write_stream = None
80
84
  self._initialized = False
81
-
85
+
82
86
  # Health monitoring (NEW - like SSE)
83
87
  self._last_successful_ping = None
84
88
  self._consecutive_failures = 0
85
89
  self._max_consecutive_failures = 3
86
-
90
+
87
91
  # Performance metrics (enhanced like SSE)
88
92
  self._metrics = {
89
93
  "total_calls": 0,
@@ -99,35 +103,36 @@ class HTTPStreamableTransport(MCPBaseTransport):
99
103
  "recovery_attempts": 0, # NEW
100
104
  }
101
105
 
102
- def _get_headers(self) -> Dict[str, str]:
106
+ def _get_headers(self) -> dict[str, str]:
103
107
  """Get headers with authentication and custom headers (like SSE)."""
104
108
  headers = {
105
109
  "Content-Type": "application/json",
106
110
  "Accept": "application/json, text/event-stream",
107
- 'User-Agent': 'chuk-tool-processor/1.0.0',
111
+ "User-Agent": "chuk-tool-processor/1.0.0",
108
112
  }
109
-
113
+
110
114
  # Add configured headers first
111
115
  if self.configured_headers:
112
116
  headers.update(self.configured_headers)
113
-
117
+
114
118
  # Add API key as Bearer token if provided
115
119
  if self.api_key:
116
- headers['Authorization'] = f'Bearer {self.api_key}'
117
-
120
+ headers["Authorization"] = f"Bearer {self.api_key}"
121
+
118
122
  # Add session ID if provided
119
123
  if self.session_id:
120
124
  headers["X-Session-ID"] = self.session_id
121
-
125
+
122
126
  return headers
123
127
 
124
128
  async def _test_connection_health(self) -> bool:
125
129
  """Test basic HTTP connectivity (like SSE's connectivity test)."""
126
130
  try:
127
131
  import httpx
132
+
128
133
  async with httpx.AsyncClient(timeout=5.0) as client:
129
134
  # Test basic connectivity to base URL
130
- base_url = self.url.replace('/mcp', '')
135
+ base_url = self.url.replace("/mcp", "")
131
136
  response = await client.get(f"{base_url}/health", headers=self._get_headers())
132
137
  logger.debug("Health check response: %s", response.status_code)
133
138
  return response.status_code < 500 # Accept any non-server-error
@@ -140,20 +145,20 @@ class HTTPStreamableTransport(MCPBaseTransport):
140
145
  if self._initialized:
141
146
  logger.warning("Transport already initialized")
142
147
  return True
143
-
148
+
144
149
  start_time = time.time()
145
-
150
+
146
151
  try:
147
152
  logger.debug("Initializing HTTP Streamable transport to %s", self.url)
148
-
153
+
149
154
  # Test basic connectivity first (like SSE)
150
155
  if not await self._test_connection_health():
151
156
  logger.warning("Connection health test failed, proceeding anyway")
152
-
157
+
153
158
  # Build headers properly
154
159
  headers = self._get_headers()
155
160
  logger.debug("Using headers: %s", list(headers.keys()))
156
-
161
+
157
162
  # Create StreamableHTTPParameters with proper configuration
158
163
  http_params = StreamableHTTPParameters(
159
164
  url=self.url,
@@ -167,49 +172,48 @@ class HTTPStreamableTransport(MCPBaseTransport):
167
172
  retry_delay=1.0,
168
173
  user_agent="chuk-tool-processor/1.0.0",
169
174
  )
170
-
175
+
171
176
  # Create and enter the HTTP context
172
177
  self._http_context = http_client(http_params)
173
-
178
+
174
179
  logger.debug("Establishing HTTP connection...")
175
180
  self._read_stream, self._write_stream = await asyncio.wait_for(
176
- self._http_context.__aenter__(),
177
- timeout=self.connection_timeout
181
+ self._http_context.__aenter__(), timeout=self.connection_timeout
178
182
  )
179
-
183
+
180
184
  # Enhanced MCP initialize sequence
181
185
  logger.debug("Sending MCP initialize request...")
182
186
  init_start = time.time()
183
-
184
- init_result = await asyncio.wait_for(
185
- send_initialize(self._read_stream, self._write_stream),
186
- timeout=self.default_timeout
187
- )
188
-
187
+
188
+ await asyncio.wait_for(send_initialize(self._read_stream, self._write_stream), timeout=self.default_timeout)
189
+
189
190
  init_time = time.time() - init_start
190
191
  logger.debug("MCP initialize completed in %.3fs", init_time)
191
-
192
+
192
193
  # Verify connection with ping (enhanced like SSE)
193
194
  logger.debug("Verifying connection with ping...")
194
195
  ping_start = time.time()
195
196
  ping_success = await asyncio.wait_for(
196
197
  send_ping(self._read_stream, self._write_stream),
197
- timeout=10.0 # Longer timeout for initial ping
198
+ timeout=10.0, # Longer timeout for initial ping
198
199
  )
199
200
  ping_time = time.time() - ping_start
200
-
201
+
201
202
  if ping_success:
202
203
  self._initialized = True
203
204
  self._last_successful_ping = time.time()
204
205
  self._consecutive_failures = 0
205
-
206
+
206
207
  total_init_time = time.time() - start_time
207
208
  if self.enable_metrics:
208
209
  self._metrics["initialization_time"] = total_init_time
209
210
  self._metrics["last_ping_time"] = ping_time
210
-
211
- logger.debug("HTTP Streamable transport initialized successfully in %.3fs (ping: %.3fs)",
212
- total_init_time, ping_time)
211
+
212
+ logger.debug(
213
+ "HTTP Streamable transport initialized successfully in %.3fs (ping: %.3fs)",
214
+ total_init_time,
215
+ ping_time,
216
+ )
213
217
  return True
214
218
  else:
215
219
  logger.warning("HTTP connection established but ping failed")
@@ -220,7 +224,7 @@ class HTTPStreamableTransport(MCPBaseTransport):
220
224
  self._metrics["initialization_time"] = time.time() - start_time
221
225
  return True
222
226
 
223
- except asyncio.TimeoutError:
227
+ except TimeoutError:
224
228
  logger.error("HTTP Streamable initialization timed out after %ss", self.connection_timeout)
225
229
  await self._cleanup()
226
230
  if self.enable_metrics:
@@ -237,13 +241,13 @@ class HTTPStreamableTransport(MCPBaseTransport):
237
241
  """Attempt to recover from connection issues (NEW - like SSE resilience)."""
238
242
  if self.enable_metrics:
239
243
  self._metrics["recovery_attempts"] += 1
240
-
244
+
241
245
  logger.debug("Attempting HTTP connection recovery...")
242
-
246
+
243
247
  try:
244
248
  # Clean up existing connection
245
249
  await self._cleanup()
246
-
250
+
247
251
  # Re-initialize
248
252
  return await self.initialize()
249
253
  except Exception as e:
@@ -254,10 +258,10 @@ class HTTPStreamableTransport(MCPBaseTransport):
254
258
  """Close with enhanced cleanup and metrics reporting."""
255
259
  if not self._initialized:
256
260
  return
257
-
261
+
258
262
  # Enhanced metrics logging (like SSE)
259
263
  if self.enable_metrics and self._metrics["total_calls"] > 0:
260
- success_rate = (self._metrics["successful_calls"] / self._metrics["total_calls"] * 100)
264
+ success_rate = self._metrics["successful_calls"] / self._metrics["total_calls"] * 100
261
265
  logger.debug(
262
266
  "HTTP Streamable transport closing - Calls: %d, Success: %.1f%%, "
263
267
  "Avg time: %.3fs, Recoveries: %d, Errors: %d",
@@ -265,14 +269,14 @@ class HTTPStreamableTransport(MCPBaseTransport):
265
269
  success_rate,
266
270
  self._metrics["avg_response_time"],
267
271
  self._metrics["recovery_attempts"],
268
- self._metrics["connection_errors"]
272
+ self._metrics["connection_errors"],
269
273
  )
270
-
274
+
271
275
  try:
272
276
  if self._http_context is not None:
273
277
  await self._http_context.__aexit__(None, None, None)
274
278
  logger.debug("HTTP Streamable context closed")
275
-
279
+
276
280
  except Exception as e:
277
281
  logger.debug("Error during transport close: %s", e)
278
282
  finally:
@@ -290,29 +294,28 @@ class HTTPStreamableTransport(MCPBaseTransport):
290
294
  if not self._initialized or not self._read_stream:
291
295
  logger.error("Cannot send ping: transport not initialized")
292
296
  return False
293
-
297
+
294
298
  start_time = time.time()
295
299
  try:
296
300
  result = await asyncio.wait_for(
297
- send_ping(self._read_stream, self._write_stream),
298
- timeout=self.default_timeout
301
+ send_ping(self._read_stream, self._write_stream), timeout=self.default_timeout
299
302
  )
300
-
303
+
301
304
  success = bool(result)
302
-
305
+
303
306
  if success:
304
307
  self._last_successful_ping = time.time()
305
308
  self._consecutive_failures = 0
306
309
  else:
307
310
  self._consecutive_failures += 1
308
-
311
+
309
312
  if self.enable_metrics:
310
313
  ping_time = time.time() - start_time
311
314
  self._metrics["last_ping_time"] = ping_time
312
315
  logger.debug("HTTP Streamable ping completed in %.3fs: %s", ping_time, success)
313
-
316
+
314
317
  return success
315
- except asyncio.TimeoutError:
318
+ except TimeoutError:
316
319
  logger.error("HTTP Streamable ping timed out")
317
320
  self._consecutive_failures += 1
318
321
  return False
@@ -327,27 +330,26 @@ class HTTPStreamableTransport(MCPBaseTransport):
327
330
  """Enhanced connection status check (like SSE)."""
328
331
  if not self._initialized or not self._read_stream or not self._write_stream:
329
332
  return False
330
-
333
+
331
334
  # Check if we've had too many consecutive failures (like SSE)
332
335
  if self._consecutive_failures >= self._max_consecutive_failures:
333
336
  logger.warning("Connection marked unhealthy after %d failures", self._consecutive_failures)
334
337
  return False
335
-
338
+
336
339
  return True
337
340
 
338
- async def get_tools(self) -> List[Dict[str, Any]]:
341
+ async def get_tools(self) -> list[dict[str, Any]]:
339
342
  """Enhanced tools retrieval with error handling."""
340
343
  if not self._initialized:
341
344
  logger.error("Cannot get tools: transport not initialized")
342
345
  return []
343
-
346
+
344
347
  start_time = time.time()
345
348
  try:
346
349
  tools_response = await asyncio.wait_for(
347
- send_tools_list(self._read_stream, self._write_stream),
348
- timeout=self.default_timeout
350
+ send_tools_list(self._read_stream, self._write_stream), timeout=self.default_timeout
349
351
  )
350
-
352
+
351
353
  # Normalize response
352
354
  if isinstance(tools_response, dict):
353
355
  tools = tools_response.get("tools", [])
@@ -356,17 +358,17 @@ class HTTPStreamableTransport(MCPBaseTransport):
356
358
  else:
357
359
  logger.warning("Unexpected tools response type: %s", type(tools_response))
358
360
  tools = []
359
-
361
+
360
362
  # Reset failure count on success
361
363
  self._consecutive_failures = 0
362
-
364
+
363
365
  if self.enable_metrics:
364
366
  response_time = time.time() - start_time
365
367
  logger.debug("Retrieved %d tools in %.3fs", len(tools), response_time)
366
-
368
+
367
369
  return tools
368
-
369
- except asyncio.TimeoutError:
370
+
371
+ except TimeoutError:
370
372
  logger.error("Get tools timed out")
371
373
  self._consecutive_failures += 1
372
374
  return []
@@ -377,97 +379,80 @@ class HTTPStreamableTransport(MCPBaseTransport):
377
379
  self._metrics["stream_errors"] += 1
378
380
  return []
379
381
 
380
- async def call_tool(self, tool_name: str, arguments: Dict[str, Any],
381
- timeout: Optional[float] = None) -> Dict[str, Any]:
382
+ async def call_tool(
383
+ self, tool_name: str, arguments: dict[str, Any], timeout: float | None = None
384
+ ) -> dict[str, Any]:
382
385
  """Enhanced tool calling with recovery and health monitoring."""
383
386
  if not self._initialized:
384
- return {
385
- "isError": True,
386
- "error": "Transport not initialized"
387
- }
387
+ return {"isError": True, "error": "Transport not initialized"}
388
388
 
389
389
  tool_timeout = timeout or self.default_timeout
390
390
  start_time = time.time()
391
-
391
+
392
392
  if self.enable_metrics:
393
393
  self._metrics["total_calls"] += 1
394
394
 
395
395
  try:
396
396
  logger.debug("Calling tool '%s' with timeout %ss", tool_name, tool_timeout)
397
-
397
+
398
398
  # Enhanced connection check with recovery attempt
399
399
  if not self.is_connected():
400
400
  logger.warning("Connection unhealthy, attempting recovery...")
401
401
  if not await self._attempt_recovery():
402
402
  if self.enable_metrics:
403
403
  self._update_metrics(time.time() - start_time, False)
404
- return {
405
- "isError": True,
406
- "error": "Failed to recover connection"
407
- }
408
-
404
+ return {"isError": True, "error": "Failed to recover connection"}
405
+
409
406
  raw_response = await asyncio.wait_for(
410
- send_tools_call(
411
- self._read_stream,
412
- self._write_stream,
413
- tool_name,
414
- arguments
415
- ),
416
- timeout=tool_timeout
407
+ send_tools_call(self._read_stream, self._write_stream, tool_name, arguments), timeout=tool_timeout
417
408
  )
418
-
409
+
419
410
  response_time = time.time() - start_time
420
411
  result = self._normalize_mcp_response(raw_response)
421
-
412
+
422
413
  # Reset failure count on success
423
414
  self._consecutive_failures = 0
424
415
  self._last_successful_ping = time.time() # Update health timestamp
425
-
416
+
426
417
  if self.enable_metrics:
427
418
  self._update_metrics(response_time, not result.get("isError", False))
428
-
419
+
429
420
  if not result.get("isError", False):
430
421
  logger.debug("Tool '%s' completed successfully in %.3fs", tool_name, response_time)
431
422
  else:
432
- logger.warning("Tool '%s' failed in %.3fs: %s", tool_name, response_time,
433
- result.get('error', 'Unknown error'))
434
-
423
+ logger.warning(
424
+ "Tool '%s' failed in %.3fs: %s", tool_name, response_time, result.get("error", "Unknown error")
425
+ )
426
+
435
427
  return result
436
428
 
437
- except asyncio.TimeoutError:
429
+ except TimeoutError:
438
430
  response_time = time.time() - start_time
439
431
  self._consecutive_failures += 1
440
432
  if self.enable_metrics:
441
433
  self._update_metrics(response_time, False)
442
-
434
+
443
435
  error_msg = f"Tool execution timed out after {tool_timeout}s"
444
436
  logger.error("Tool '%s' %s", tool_name, error_msg)
445
- return {
446
- "isError": True,
447
- "error": error_msg
448
- }
437
+ return {"isError": True, "error": error_msg}
449
438
  except Exception as e:
450
439
  response_time = time.time() - start_time
451
440
  self._consecutive_failures += 1
452
441
  if self.enable_metrics:
453
442
  self._update_metrics(response_time, False)
454
443
  self._metrics["stream_errors"] += 1
455
-
444
+
456
445
  # Enhanced connection error detection
457
446
  error_str = str(e).lower()
458
- if any(indicator in error_str for indicator in
459
- ["connection", "disconnected", "broken pipe", "eof"]):
447
+ if any(indicator in error_str for indicator in ["connection", "disconnected", "broken pipe", "eof"]):
460
448
  logger.warning("Connection error detected: %s", e)
461
449
  self._initialized = False
462
450
  if self.enable_metrics:
463
451
  self._metrics["connection_errors"] += 1
464
-
452
+
465
453
  error_msg = f"Tool execution failed: {str(e)}"
466
454
  logger.error("Tool '%s' error: %s", tool_name, error_msg)
467
- return {
468
- "isError": True,
469
- "error": error_msg
470
- }
455
+ return {"isError": True, "error": error_msg}
471
456
 
472
457
  def _update_metrics(self, response_time: float, success: bool) -> None:
473
458
  """Enhanced metrics tracking (like SSE)."""
@@ -475,25 +460,22 @@ class HTTPStreamableTransport(MCPBaseTransport):
475
460
  self._metrics["successful_calls"] += 1
476
461
  else:
477
462
  self._metrics["failed_calls"] += 1
478
-
463
+
479
464
  self._metrics["total_time"] += response_time
480
465
  if self._metrics["total_calls"] > 0:
481
- self._metrics["avg_response_time"] = (
482
- self._metrics["total_time"] / self._metrics["total_calls"]
483
- )
466
+ self._metrics["avg_response_time"] = self._metrics["total_time"] / self._metrics["total_calls"]
484
467
 
485
- async def list_resources(self) -> Dict[str, Any]:
468
+ async def list_resources(self) -> dict[str, Any]:
486
469
  """Enhanced resource listing with error handling."""
487
470
  if not self._initialized:
488
471
  return {}
489
-
472
+
490
473
  try:
491
474
  response = await asyncio.wait_for(
492
- send_resources_list(self._read_stream, self._write_stream),
493
- timeout=self.default_timeout
475
+ send_resources_list(self._read_stream, self._write_stream), timeout=self.default_timeout
494
476
  )
495
477
  return response if isinstance(response, dict) else {}
496
- except asyncio.TimeoutError:
478
+ except TimeoutError:
497
479
  logger.error("List resources timed out")
498
480
  self._consecutive_failures += 1
499
481
  return {}
@@ -502,18 +484,17 @@ class HTTPStreamableTransport(MCPBaseTransport):
502
484
  self._consecutive_failures += 1
503
485
  return {}
504
486
 
505
- async def list_prompts(self) -> Dict[str, Any]:
487
+ async def list_prompts(self) -> dict[str, Any]:
506
488
  """Enhanced prompt listing with error handling."""
507
489
  if not self._initialized:
508
490
  return {}
509
-
491
+
510
492
  try:
511
493
  response = await asyncio.wait_for(
512
- send_prompts_list(self._read_stream, self._write_stream),
513
- timeout=self.default_timeout
494
+ send_prompts_list(self._read_stream, self._write_stream), timeout=self.default_timeout
514
495
  )
515
496
  return response if isinstance(response, dict) else {}
516
- except asyncio.TimeoutError:
497
+ except TimeoutError:
517
498
  logger.error("List prompts timed out")
518
499
  self._consecutive_failures += 1
519
500
  return {}
@@ -522,18 +503,17 @@ class HTTPStreamableTransport(MCPBaseTransport):
522
503
  self._consecutive_failures += 1
523
504
  return {}
524
505
 
525
- async def read_resource(self, uri: str) -> Dict[str, Any]:
506
+ async def read_resource(self, uri: str) -> dict[str, Any]:
526
507
  """Read a specific resource."""
527
508
  if not self._initialized:
528
509
  return {}
529
-
510
+
530
511
  try:
531
512
  response = await asyncio.wait_for(
532
- send_resources_read(self._read_stream, self._write_stream, uri),
533
- timeout=self.default_timeout
513
+ send_resources_read(self._read_stream, self._write_stream, uri), timeout=self.default_timeout
534
514
  )
535
515
  return response if isinstance(response, dict) else {}
536
- except asyncio.TimeoutError:
516
+ except TimeoutError:
537
517
  logger.error("Read resource timed out")
538
518
  self._consecutive_failures += 1
539
519
  return {}
@@ -542,18 +522,18 @@ class HTTPStreamableTransport(MCPBaseTransport):
542
522
  self._consecutive_failures += 1
543
523
  return {}
544
524
 
545
- async def get_prompt(self, name: str, arguments: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
525
+ async def get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> dict[str, Any]:
546
526
  """Get a specific prompt."""
547
527
  if not self._initialized:
548
528
  return {}
549
-
529
+
550
530
  try:
551
531
  response = await asyncio.wait_for(
552
532
  send_prompts_get(self._read_stream, self._write_stream, name, arguments or {}),
553
- timeout=self.default_timeout
533
+ timeout=self.default_timeout,
554
534
  )
555
535
  return response if isinstance(response, dict) else {}
556
- except asyncio.TimeoutError:
536
+ except TimeoutError:
557
537
  logger.error("Get prompt timed out")
558
538
  self._consecutive_failures += 1
559
539
  return {}
@@ -562,22 +542,24 @@ class HTTPStreamableTransport(MCPBaseTransport):
562
542
  self._consecutive_failures += 1
563
543
  return {}
564
544
 
565
- def get_metrics(self) -> Dict[str, Any]:
545
+ def get_metrics(self) -> dict[str, Any]:
566
546
  """Enhanced metrics with health information."""
567
547
  metrics = self._metrics.copy()
568
- metrics.update({
569
- "is_connected": self.is_connected(),
570
- "consecutive_failures": self._consecutive_failures,
571
- "last_successful_ping": self._last_successful_ping,
572
- "max_consecutive_failures": self._max_consecutive_failures,
573
- })
548
+ metrics.update(
549
+ {
550
+ "is_connected": self.is_connected(),
551
+ "consecutive_failures": self._consecutive_failures,
552
+ "last_successful_ping": self._last_successful_ping,
553
+ "max_consecutive_failures": self._max_consecutive_failures,
554
+ }
555
+ )
574
556
  return metrics
575
557
 
576
558
  def reset_metrics(self) -> None:
577
559
  """Enhanced metrics reset preserving health state."""
578
560
  preserved_init_time = self._metrics.get("initialization_time")
579
561
  preserved_last_ping = self._metrics.get("last_ping_time")
580
-
562
+
581
563
  self._metrics = {
582
564
  "total_calls": 0,
583
565
  "successful_calls": 0,
@@ -592,7 +574,7 @@ class HTTPStreamableTransport(MCPBaseTransport):
592
574
  "recovery_attempts": 0,
593
575
  }
594
576
 
595
- def get_streams(self) -> List[tuple]:
577
+ def get_streams(self) -> list[tuple]:
596
578
  """Enhanced streams access with connection check."""
597
579
  if self._initialized and self._read_stream and self._write_stream:
598
580
  return [(self._read_stream, self._write_stream)]
@@ -607,4 +589,4 @@ class HTTPStreamableTransport(MCPBaseTransport):
607
589
 
608
590
  async def __aexit__(self, exc_type, exc_val, exc_tb):
609
591
  """Enhanced context manager cleanup."""
610
- await self.close()
592
+ await self.close()