chuk-tool-processor 0.6.7__py3-none-any.whl → 0.6.9__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.
- chuk_tool_processor/mcp/stream_manager.py +53 -34
- chuk_tool_processor/mcp/transport/__init__.py +10 -27
- chuk_tool_processor/mcp/transport/base_transport.py +197 -46
- chuk_tool_processor/mcp/transport/http_streamable_transport.py +134 -173
- chuk_tool_processor/mcp/transport/sse_transport.py +238 -119
- chuk_tool_processor/mcp/transport/stdio_transport.py +297 -81
- {chuk_tool_processor-0.6.7.dist-info → chuk_tool_processor-0.6.9.dist-info}/METADATA +1 -1
- {chuk_tool_processor-0.6.7.dist-info → chuk_tool_processor-0.6.9.dist-info}/RECORD +10 -10
- {chuk_tool_processor-0.6.7.dist-info → chuk_tool_processor-0.6.9.dist-info}/WHEEL +0 -0
- {chuk_tool_processor-0.6.7.dist-info → chuk_tool_processor-0.6.9.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# chuk_tool_processor/mcp/transport/http_streamable_transport.py
|
|
1
|
+
# chuk_tool_processor/mcp/transport/http_streamable_transport.py - FIXED
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import asyncio
|
|
@@ -10,30 +10,18 @@ import logging
|
|
|
10
10
|
from .base_transport import MCPBaseTransport
|
|
11
11
|
|
|
12
12
|
# Import chuk-mcp HTTP transport components
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
# Import optional resource and prompt support
|
|
27
|
-
try:
|
|
28
|
-
from chuk_mcp.protocol.messages import (
|
|
29
|
-
send_resources_list,
|
|
30
|
-
send_resources_read,
|
|
31
|
-
send_prompts_list,
|
|
32
|
-
send_prompts_get,
|
|
33
|
-
)
|
|
34
|
-
HAS_RESOURCES_PROMPTS = True
|
|
35
|
-
except ImportError:
|
|
36
|
-
HAS_RESOURCES_PROMPTS = False
|
|
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 (
|
|
16
|
+
send_initialize,
|
|
17
|
+
send_ping,
|
|
18
|
+
send_tools_list,
|
|
19
|
+
send_tools_call,
|
|
20
|
+
send_resources_list,
|
|
21
|
+
send_resources_read,
|
|
22
|
+
send_prompts_list,
|
|
23
|
+
send_prompts_get,
|
|
24
|
+
)
|
|
37
25
|
|
|
38
26
|
logger = logging.getLogger(__name__)
|
|
39
27
|
|
|
@@ -42,13 +30,15 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
42
30
|
"""
|
|
43
31
|
HTTP Streamable transport using chuk-mcp HTTP client.
|
|
44
32
|
|
|
45
|
-
|
|
46
|
-
|
|
33
|
+
FIXED: Improved connection management and parameter configuration
|
|
34
|
+
to eliminate "server disconnected" errors.
|
|
47
35
|
"""
|
|
48
36
|
|
|
49
37
|
def __init__(self, url: str, api_key: Optional[str] = None,
|
|
50
|
-
connection_timeout: float = 30.0,
|
|
51
|
-
|
|
38
|
+
connection_timeout: float = 30.0,
|
|
39
|
+
default_timeout: float = 30.0,
|
|
40
|
+
session_id: Optional[str] = None,
|
|
41
|
+
enable_metrics: bool = True):
|
|
52
42
|
"""
|
|
53
43
|
Initialize HTTP Streamable transport with chuk-mcp.
|
|
54
44
|
|
|
@@ -72,13 +62,19 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
72
62
|
self.session_id = session_id
|
|
73
63
|
self.enable_metrics = enable_metrics
|
|
74
64
|
|
|
75
|
-
|
|
65
|
+
logger.debug("HTTP Streamable transport initialized with URL: %s", self.url)
|
|
66
|
+
if self.api_key:
|
|
67
|
+
logger.debug("API key configured for authentication")
|
|
68
|
+
if self.session_id:
|
|
69
|
+
logger.debug("Session ID configured: %s", self.session_id)
|
|
70
|
+
|
|
71
|
+
# State tracking
|
|
76
72
|
self._http_context = None
|
|
77
73
|
self._read_stream = None
|
|
78
74
|
self._write_stream = None
|
|
79
75
|
self._initialized = False
|
|
80
76
|
|
|
81
|
-
# Performance metrics (
|
|
77
|
+
# Performance metrics (consistent with other transports)
|
|
82
78
|
self._metrics = {
|
|
83
79
|
"total_calls": 0,
|
|
84
80
|
"successful_calls": 0,
|
|
@@ -86,20 +82,13 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
86
82
|
"total_time": 0.0,
|
|
87
83
|
"avg_response_time": 0.0,
|
|
88
84
|
"last_ping_time": None,
|
|
89
|
-
"initialization_time": None
|
|
85
|
+
"initialization_time": None,
|
|
86
|
+
"connection_resets": 0,
|
|
87
|
+
"stream_errors": 0
|
|
90
88
|
}
|
|
91
|
-
|
|
92
|
-
if not HAS_HTTP_SUPPORT:
|
|
93
|
-
logger.warning("HTTP Streamable transport not available - operations will fail")
|
|
94
|
-
if not HAS_RESOURCES_PROMPTS:
|
|
95
|
-
logger.debug("Resources/prompts not available in chuk-mcp")
|
|
96
89
|
|
|
97
90
|
async def initialize(self) -> bool:
|
|
98
|
-
"""Initialize using chuk-mcp http_client
|
|
99
|
-
if not HAS_HTTP_SUPPORT:
|
|
100
|
-
logger.error("HTTP Streamable transport not available in chuk-mcp")
|
|
101
|
-
return False
|
|
102
|
-
|
|
91
|
+
"""Initialize using chuk-mcp http_client with improved configuration."""
|
|
103
92
|
if self._initialized:
|
|
104
93
|
logger.warning("Transport already initialized")
|
|
105
94
|
return True
|
|
@@ -109,8 +98,14 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
109
98
|
try:
|
|
110
99
|
logger.debug("Initializing HTTP Streamable transport to %s", self.url)
|
|
111
100
|
|
|
112
|
-
#
|
|
113
|
-
headers = {
|
|
101
|
+
# FIXED: Proper HTTP headers (match working diagnostic)
|
|
102
|
+
headers = {
|
|
103
|
+
"Content-Type": "application/json",
|
|
104
|
+
"Accept": "application/json, text/event-stream",
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# FIXED: Only set Authorization header, not both bearer_token and headers
|
|
108
|
+
bearer_token = None
|
|
114
109
|
if self.api_key:
|
|
115
110
|
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
116
111
|
logger.debug("API key configured for authentication")
|
|
@@ -119,17 +114,21 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
119
114
|
headers["X-Session-ID"] = self.session_id
|
|
120
115
|
logger.debug("Using session ID: %s", self.session_id)
|
|
121
116
|
|
|
117
|
+
# FIXED: Use only valid StreamableHTTPParameters
|
|
122
118
|
http_params = StreamableHTTPParameters(
|
|
123
119
|
url=self.url,
|
|
124
|
-
timeout=self.
|
|
120
|
+
timeout=self.default_timeout, # FIXED: Use default_timeout for operations
|
|
125
121
|
headers=headers,
|
|
126
|
-
bearer_token=
|
|
122
|
+
bearer_token=bearer_token, # FIXED: Don't duplicate auth
|
|
127
123
|
session_id=self.session_id,
|
|
128
|
-
enable_streaming=True,
|
|
129
|
-
max_concurrent_requests=
|
|
124
|
+
enable_streaming=True,
|
|
125
|
+
max_concurrent_requests=5, # FIXED: Reduce concurrency for stability
|
|
126
|
+
max_retries=2, # FIXED: Add retry configuration
|
|
127
|
+
retry_delay=1.0, # FIXED: Short retry delay
|
|
128
|
+
user_agent="chuk-tool-processor/1.0.0",
|
|
130
129
|
)
|
|
131
130
|
|
|
132
|
-
# Create and enter the HTTP context
|
|
131
|
+
# Create and enter the HTTP context
|
|
133
132
|
self._http_context = http_client(http_params)
|
|
134
133
|
|
|
135
134
|
logger.debug("Establishing HTTP connection and MCP handshake...")
|
|
@@ -138,8 +137,20 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
138
137
|
timeout=self.connection_timeout
|
|
139
138
|
)
|
|
140
139
|
|
|
141
|
-
#
|
|
142
|
-
|
|
140
|
+
# FIXED: Simplified MCP initialize sequence (match working diagnostic)
|
|
141
|
+
logger.debug("Sending MCP initialize request...")
|
|
142
|
+
init_start = time.time()
|
|
143
|
+
|
|
144
|
+
# Send initialize request with default parameters
|
|
145
|
+
init_result = await asyncio.wait_for(
|
|
146
|
+
send_initialize(self._read_stream, self._write_stream),
|
|
147
|
+
timeout=self.default_timeout
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
init_time = time.time() - init_start
|
|
151
|
+
logger.debug("MCP initialize completed in %.3fs", init_time)
|
|
152
|
+
|
|
153
|
+
# Verify the connection works with a simple ping
|
|
143
154
|
logger.debug("Verifying connection with ping...")
|
|
144
155
|
ping_start = time.time()
|
|
145
156
|
ping_success = await asyncio.wait_for(
|
|
@@ -150,17 +161,19 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
150
161
|
|
|
151
162
|
if ping_success:
|
|
152
163
|
self._initialized = True
|
|
153
|
-
|
|
154
|
-
self.
|
|
155
|
-
|
|
164
|
+
total_init_time = time.time() - start_time
|
|
165
|
+
if self.enable_metrics:
|
|
166
|
+
self._metrics["initialization_time"] = total_init_time
|
|
167
|
+
self._metrics["last_ping_time"] = ping_time
|
|
156
168
|
|
|
157
|
-
logger.debug("HTTP Streamable transport initialized successfully in %.3fs (ping: %.3fs)",
|
|
169
|
+
logger.debug("HTTP Streamable transport initialized successfully in %.3fs (ping: %.3fs)", total_init_time, ping_time)
|
|
158
170
|
return True
|
|
159
171
|
else:
|
|
160
172
|
logger.warning("HTTP connection established but ping failed")
|
|
161
|
-
# Still consider it initialized since connection was established
|
|
173
|
+
# Still consider it initialized since connection was established
|
|
162
174
|
self._initialized = True
|
|
163
|
-
self.
|
|
175
|
+
if self.enable_metrics:
|
|
176
|
+
self._metrics["initialization_time"] = time.time() - start_time
|
|
164
177
|
return True
|
|
165
178
|
|
|
166
179
|
except asyncio.TimeoutError:
|
|
@@ -174,11 +187,11 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
174
187
|
return False
|
|
175
188
|
|
|
176
189
|
async def close(self) -> None:
|
|
177
|
-
"""Close the HTTP Streamable transport properly
|
|
190
|
+
"""Close the HTTP Streamable transport properly."""
|
|
178
191
|
if not self._initialized:
|
|
179
192
|
return
|
|
180
193
|
|
|
181
|
-
# Log final metrics
|
|
194
|
+
# Log final metrics
|
|
182
195
|
if self.enable_metrics and self._metrics["total_calls"] > 0:
|
|
183
196
|
logger.debug(
|
|
184
197
|
"HTTP Streamable transport closing - Total calls: %d, Success rate: %.1f%%, Avg response time: %.3fs",
|
|
@@ -198,14 +211,14 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
198
211
|
await self._cleanup()
|
|
199
212
|
|
|
200
213
|
async def _cleanup(self) -> None:
|
|
201
|
-
"""Clean up internal state
|
|
214
|
+
"""Clean up internal state."""
|
|
202
215
|
self._http_context = None
|
|
203
216
|
self._read_stream = None
|
|
204
217
|
self._write_stream = None
|
|
205
218
|
self._initialized = False
|
|
206
219
|
|
|
207
220
|
async def send_ping(self) -> bool:
|
|
208
|
-
"""Send ping with performance tracking
|
|
221
|
+
"""Send ping with performance tracking."""
|
|
209
222
|
if not self._initialized or not self._read_stream:
|
|
210
223
|
logger.error("Cannot send ping: transport not initialized")
|
|
211
224
|
return False
|
|
@@ -220,18 +233,24 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
220
233
|
if self.enable_metrics:
|
|
221
234
|
ping_time = time.time() - start_time
|
|
222
235
|
self._metrics["last_ping_time"] = ping_time
|
|
223
|
-
logger.debug("
|
|
236
|
+
logger.debug("HTTP Streamable ping completed in %.3fs: %s", ping_time, result)
|
|
224
237
|
|
|
225
238
|
return bool(result)
|
|
226
239
|
except asyncio.TimeoutError:
|
|
227
|
-
logger.error("
|
|
240
|
+
logger.error("HTTP Streamable ping timed out")
|
|
228
241
|
return False
|
|
229
242
|
except Exception as e:
|
|
230
|
-
logger.error("
|
|
243
|
+
logger.error("HTTP Streamable ping failed: %s", e)
|
|
244
|
+
if self.enable_metrics:
|
|
245
|
+
self._metrics["stream_errors"] += 1
|
|
231
246
|
return False
|
|
232
247
|
|
|
248
|
+
def is_connected(self) -> bool:
|
|
249
|
+
"""Check connection status."""
|
|
250
|
+
return self._initialized and self._read_stream is not None and self._write_stream is not None
|
|
251
|
+
|
|
233
252
|
async def get_tools(self) -> List[Dict[str, Any]]:
|
|
234
|
-
"""Get tools list with performance tracking
|
|
253
|
+
"""Get tools list with performance tracking."""
|
|
235
254
|
if not self._initialized:
|
|
236
255
|
logger.error("Cannot get tools: transport not initialized")
|
|
237
256
|
return []
|
|
@@ -243,7 +262,7 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
243
262
|
timeout=self.default_timeout
|
|
244
263
|
)
|
|
245
264
|
|
|
246
|
-
# Normalize response
|
|
265
|
+
# Normalize response
|
|
247
266
|
if isinstance(tools_response, dict):
|
|
248
267
|
tools = tools_response.get("tools", [])
|
|
249
268
|
elif isinstance(tools_response, list):
|
|
@@ -263,6 +282,8 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
263
282
|
return []
|
|
264
283
|
except Exception as e:
|
|
265
284
|
logger.error("Error getting tools: %s", e)
|
|
285
|
+
if self.enable_metrics:
|
|
286
|
+
self._metrics["stream_errors"] += 1
|
|
266
287
|
return []
|
|
267
288
|
|
|
268
289
|
async def call_tool(self, tool_name: str, arguments: Dict[str, Any],
|
|
@@ -283,6 +304,15 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
283
304
|
try:
|
|
284
305
|
logger.debug("Calling tool '%s' with timeout %ss", tool_name, tool_timeout)
|
|
285
306
|
|
|
307
|
+
# FIXED: Add connection state check before making call
|
|
308
|
+
if not self.is_connected():
|
|
309
|
+
logger.warning("Connection lost, attempting to reconnect...")
|
|
310
|
+
if not await self.initialize():
|
|
311
|
+
return {
|
|
312
|
+
"isError": True,
|
|
313
|
+
"error": "Failed to reconnect to server"
|
|
314
|
+
}
|
|
315
|
+
|
|
286
316
|
raw_response = await asyncio.wait_for(
|
|
287
317
|
send_tools_call(
|
|
288
318
|
self._read_stream,
|
|
@@ -294,7 +324,7 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
294
324
|
)
|
|
295
325
|
|
|
296
326
|
response_time = time.time() - start_time
|
|
297
|
-
result = self.
|
|
327
|
+
result = self._normalize_mcp_response(raw_response)
|
|
298
328
|
|
|
299
329
|
if self.enable_metrics:
|
|
300
330
|
self._update_metrics(response_time, not result.get("isError", False))
|
|
@@ -321,6 +351,12 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
321
351
|
response_time = time.time() - start_time
|
|
322
352
|
if self.enable_metrics:
|
|
323
353
|
self._update_metrics(response_time, False)
|
|
354
|
+
self._metrics["stream_errors"] += 1
|
|
355
|
+
|
|
356
|
+
# FIXED: Check if this is a connection error that should trigger reconnect
|
|
357
|
+
if "connection" in str(e).lower() or "disconnected" in str(e).lower():
|
|
358
|
+
logger.warning("Connection error detected, marking as disconnected: %s", e)
|
|
359
|
+
self._initialized = False
|
|
324
360
|
|
|
325
361
|
error_msg = f"Tool execution failed: {str(e)}"
|
|
326
362
|
logger.error("Tool '%s' error: %s", tool_name, error_msg)
|
|
@@ -330,23 +366,20 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
330
366
|
}
|
|
331
367
|
|
|
332
368
|
def _update_metrics(self, response_time: float, success: bool) -> None:
|
|
333
|
-
"""Update performance metrics
|
|
369
|
+
"""Update performance metrics."""
|
|
334
370
|
if success:
|
|
335
371
|
self._metrics["successful_calls"] += 1
|
|
336
372
|
else:
|
|
337
373
|
self._metrics["failed_calls"] += 1
|
|
338
374
|
|
|
339
375
|
self._metrics["total_time"] += response_time
|
|
340
|
-
self._metrics["
|
|
341
|
-
self._metrics["
|
|
342
|
-
|
|
376
|
+
if self._metrics["total_calls"] > 0:
|
|
377
|
+
self._metrics["avg_response_time"] = (
|
|
378
|
+
self._metrics["total_time"] / self._metrics["total_calls"]
|
|
379
|
+
)
|
|
343
380
|
|
|
344
381
|
async def list_resources(self) -> Dict[str, Any]:
|
|
345
|
-
"""List resources using chuk-mcp
|
|
346
|
-
if not HAS_RESOURCES_PROMPTS:
|
|
347
|
-
logger.debug("Resources/prompts not available in chuk-mcp")
|
|
348
|
-
return {}
|
|
349
|
-
|
|
382
|
+
"""List resources using chuk-mcp."""
|
|
350
383
|
if not self._initialized:
|
|
351
384
|
return {}
|
|
352
385
|
|
|
@@ -364,11 +397,7 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
364
397
|
return {}
|
|
365
398
|
|
|
366
399
|
async def list_prompts(self) -> Dict[str, Any]:
|
|
367
|
-
"""List prompts using chuk-mcp
|
|
368
|
-
if not HAS_RESOURCES_PROMPTS:
|
|
369
|
-
logger.debug("Resources/prompts not available in chuk-mcp")
|
|
370
|
-
return {}
|
|
371
|
-
|
|
400
|
+
"""List prompts using chuk-mcp."""
|
|
372
401
|
if not self._initialized:
|
|
373
402
|
return {}
|
|
374
403
|
|
|
@@ -385,87 +414,15 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
385
414
|
logger.debug("Error listing prompts: %s", e)
|
|
386
415
|
return {}
|
|
387
416
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
if "error" in raw_response:
|
|
392
|
-
error_info = raw_response["error"]
|
|
393
|
-
if isinstance(error_info, dict):
|
|
394
|
-
error_msg = error_info.get("message", "Unknown error")
|
|
395
|
-
else:
|
|
396
|
-
error_msg = str(error_info)
|
|
397
|
-
|
|
398
|
-
return {
|
|
399
|
-
"isError": True,
|
|
400
|
-
"error": error_msg
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
# Handle successful response with result
|
|
404
|
-
if "result" in raw_response:
|
|
405
|
-
result = raw_response["result"]
|
|
406
|
-
|
|
407
|
-
if isinstance(result, dict) and "content" in result:
|
|
408
|
-
return {
|
|
409
|
-
"isError": False,
|
|
410
|
-
"content": self._extract_content(result["content"])
|
|
411
|
-
}
|
|
412
|
-
else:
|
|
413
|
-
return {
|
|
414
|
-
"isError": False,
|
|
415
|
-
"content": result
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
# Handle direct content-based response
|
|
419
|
-
if "content" in raw_response:
|
|
420
|
-
return {
|
|
421
|
-
"isError": False,
|
|
422
|
-
"content": self._extract_content(raw_response["content"])
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
# Fallback
|
|
426
|
-
return {
|
|
427
|
-
"isError": False,
|
|
428
|
-
"content": raw_response
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
def _extract_content(self, content_list: Any) -> Any:
|
|
432
|
-
"""Extract content from MCP content format (same as SSE)."""
|
|
433
|
-
if not isinstance(content_list, list) or not content_list:
|
|
434
|
-
return content_list
|
|
435
|
-
|
|
436
|
-
# Handle single content item
|
|
437
|
-
if len(content_list) == 1:
|
|
438
|
-
content_item = content_list[0]
|
|
439
|
-
if isinstance(content_item, dict):
|
|
440
|
-
if content_item.get("type") == "text":
|
|
441
|
-
text_content = content_item.get("text", "")
|
|
442
|
-
# Try to parse JSON, fall back to plain text
|
|
443
|
-
try:
|
|
444
|
-
return json.loads(text_content)
|
|
445
|
-
except json.JSONDecodeError:
|
|
446
|
-
return text_content
|
|
447
|
-
else:
|
|
448
|
-
return content_item
|
|
449
|
-
|
|
450
|
-
# Multiple content items
|
|
451
|
-
return content_list
|
|
452
|
-
|
|
453
|
-
def get_streams(self) -> List[tuple]:
|
|
454
|
-
"""Provide streams for backward compatibility (same as SSE)."""
|
|
455
|
-
if self._initialized and self._read_stream and self._write_stream:
|
|
456
|
-
return [(self._read_stream, self._write_stream)]
|
|
457
|
-
return []
|
|
458
|
-
|
|
459
|
-
def is_connected(self) -> bool:
|
|
460
|
-
"""Check connection status (same as SSE)."""
|
|
461
|
-
return self._initialized and self._read_stream is not None and self._write_stream is not None
|
|
462
|
-
|
|
417
|
+
# ------------------------------------------------------------------ #
|
|
418
|
+
# Metrics and monitoring (consistent with other transports) #
|
|
419
|
+
# ------------------------------------------------------------------ #
|
|
463
420
|
def get_metrics(self) -> Dict[str, Any]:
|
|
464
|
-
"""Get performance metrics
|
|
421
|
+
"""Get performance metrics."""
|
|
465
422
|
return self._metrics.copy()
|
|
466
423
|
|
|
467
424
|
def reset_metrics(self) -> None:
|
|
468
|
-
"""Reset performance metrics
|
|
425
|
+
"""Reset performance metrics."""
|
|
469
426
|
self._metrics = {
|
|
470
427
|
"total_calls": 0,
|
|
471
428
|
"successful_calls": 0,
|
|
@@ -473,26 +430,30 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
473
430
|
"total_time": 0.0,
|
|
474
431
|
"avg_response_time": 0.0,
|
|
475
432
|
"last_ping_time": self._metrics.get("last_ping_time"),
|
|
476
|
-
"initialization_time": self._metrics.get("initialization_time")
|
|
433
|
+
"initialization_time": self._metrics.get("initialization_time"),
|
|
434
|
+
"connection_resets": self._metrics.get("connection_resets", 0),
|
|
435
|
+
"stream_errors": 0
|
|
477
436
|
}
|
|
478
437
|
|
|
438
|
+
# ------------------------------------------------------------------ #
|
|
439
|
+
# Backward compatibility #
|
|
440
|
+
# ------------------------------------------------------------------ #
|
|
441
|
+
def get_streams(self) -> List[tuple]:
|
|
442
|
+
"""Provide streams for backward compatibility."""
|
|
443
|
+
if self._initialized and self._read_stream and self._write_stream:
|
|
444
|
+
return [(self._read_stream, self._write_stream)]
|
|
445
|
+
return []
|
|
446
|
+
|
|
447
|
+
# ------------------------------------------------------------------ #
|
|
448
|
+
# Context manager support #
|
|
449
|
+
# ------------------------------------------------------------------ #
|
|
479
450
|
async def __aenter__(self):
|
|
480
|
-
"""Context manager support
|
|
451
|
+
"""Context manager support."""
|
|
481
452
|
success = await self.initialize()
|
|
482
453
|
if not success:
|
|
483
|
-
raise RuntimeError("Failed to initialize
|
|
454
|
+
raise RuntimeError("Failed to initialize HTTPStreamableTransport")
|
|
484
455
|
return self
|
|
485
456
|
|
|
486
457
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
487
|
-
"""Context manager cleanup
|
|
488
|
-
await self.close()
|
|
489
|
-
|
|
490
|
-
def __repr__(self) -> str:
|
|
491
|
-
"""Enhanced string representation for debugging."""
|
|
492
|
-
status = "initialized" if self._initialized else "not initialized"
|
|
493
|
-
metrics_info = ""
|
|
494
|
-
if self.enable_metrics and self._metrics["total_calls"] > 0:
|
|
495
|
-
success_rate = (self._metrics["successful_calls"] / self._metrics["total_calls"]) * 100
|
|
496
|
-
metrics_info = f", calls: {self._metrics['total_calls']}, success: {success_rate:.1f}%"
|
|
497
|
-
|
|
498
|
-
return f"HTTPStreamableTransport(status={status}, url={self.url}{metrics_info})"
|
|
458
|
+
"""Context manager cleanup."""
|
|
459
|
+
await self.close()
|