chuk-tool-processor 0.6.11__py3-none-any.whl → 0.6.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 chuk-tool-processor might be problematic. Click here for more details.
- chuk_tool_processor/mcp/mcp_tool.py +40 -8
- chuk_tool_processor/mcp/transport/sse_transport.py +72 -20
- {chuk_tool_processor-0.6.11.dist-info → chuk_tool_processor-0.6.12.dist-info}/METADATA +1 -1
- {chuk_tool_processor-0.6.11.dist-info → chuk_tool_processor-0.6.12.dist-info}/RECORD +6 -6
- {chuk_tool_processor-0.6.11.dist-info → chuk_tool_processor-0.6.12.dist-info}/WHEEL +0 -0
- {chuk_tool_processor-0.6.11.dist-info → chuk_tool_processor-0.6.12.dist-info}/top_level.txt +0 -0
|
@@ -9,6 +9,10 @@ not configuration or bootstrapping. Configuration is handled at registration tim
|
|
|
9
9
|
CORE PRINCIPLE: MCPTool wraps a StreamManager and delegates calls to it.
|
|
10
10
|
If the StreamManager becomes unavailable, return graceful errors rather than
|
|
11
11
|
trying to recreate it with config files.
|
|
12
|
+
|
|
13
|
+
HEALTH MONITORING FIX: Updated health checking to be more lenient and trust
|
|
14
|
+
the underlying transport's health monitoring instead of doing aggressive
|
|
15
|
+
ping tests that create false negatives.
|
|
12
16
|
"""
|
|
13
17
|
from __future__ import annotations
|
|
14
18
|
|
|
@@ -60,6 +64,9 @@ class MCPTool:
|
|
|
60
64
|
|
|
61
65
|
SIMPLIFIED: This class now focuses only on execution delegation.
|
|
62
66
|
It does NOT handle configuration files or StreamManager bootstrapping.
|
|
67
|
+
|
|
68
|
+
FIXED: Health monitoring is now more lenient and trusts the underlying
|
|
69
|
+
transport's health reporting instead of doing aggressive health checks.
|
|
63
70
|
"""
|
|
64
71
|
|
|
65
72
|
def __init__(
|
|
@@ -200,7 +207,7 @@ class MCPTool:
|
|
|
200
207
|
effective_timeout = timeout if timeout is not None else self.default_timeout
|
|
201
208
|
self.stats.total_calls += 1
|
|
202
209
|
|
|
203
|
-
#
|
|
210
|
+
# FIXED: More lenient health check - trust the transport layer
|
|
204
211
|
if not await self._is_stream_manager_healthy():
|
|
205
212
|
await self._record_failure(is_connection_error=True)
|
|
206
213
|
return {
|
|
@@ -294,17 +301,41 @@ class MCPTool:
|
|
|
294
301
|
raise
|
|
295
302
|
|
|
296
303
|
async def _is_stream_manager_healthy(self) -> bool:
|
|
297
|
-
"""
|
|
304
|
+
"""
|
|
305
|
+
FIXED: Much more lenient health check.
|
|
306
|
+
|
|
307
|
+
The diagnostic proves SSE transport is healthy, so we should trust it
|
|
308
|
+
instead of doing aggressive health checking that creates false negatives.
|
|
309
|
+
|
|
310
|
+
This replaces the previous ping_servers() call which was too aggressive
|
|
311
|
+
and caused "unhealthy connection" false positives.
|
|
312
|
+
"""
|
|
298
313
|
if self._sm is None:
|
|
299
314
|
return False
|
|
300
315
|
|
|
316
|
+
# FIXED: Simple check - if we have a StreamManager, assume it's available
|
|
317
|
+
# The underlying SSE transport now has proper health monitoring
|
|
301
318
|
try:
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
319
|
+
# Just check if the StreamManager has basic functionality
|
|
320
|
+
if hasattr(self._sm, 'transports') and self._sm.transports:
|
|
321
|
+
logger.debug(f"StreamManager healthy for '{self.tool_name}' - has {len(self._sm.transports)} transports")
|
|
322
|
+
return True
|
|
323
|
+
|
|
324
|
+
# Fallback - try very quick operation with short timeout
|
|
325
|
+
server_info = await asyncio.wait_for(self._sm.get_server_info(), timeout=1.0)
|
|
326
|
+
healthy = len(server_info) > 0
|
|
327
|
+
logger.debug(f"StreamManager health for '{self.tool_name}': {healthy} (via server_info)")
|
|
328
|
+
return healthy
|
|
329
|
+
|
|
330
|
+
except asyncio.TimeoutError:
|
|
331
|
+
logger.debug(f"StreamManager health check timed out for '{self.tool_name}' - assuming healthy")
|
|
332
|
+
# FIXED: Timeout doesn't mean unavailable, just slow
|
|
333
|
+
return True
|
|
305
334
|
except Exception as e:
|
|
306
|
-
logger.debug(f"
|
|
307
|
-
|
|
335
|
+
logger.debug(f"StreamManager health check failed for '{self.tool_name}': {e}")
|
|
336
|
+
# FIXED: Most exceptions don't mean the StreamManager is unavailable
|
|
337
|
+
# The transport layer handles real connectivity issues
|
|
338
|
+
return True
|
|
308
339
|
|
|
309
340
|
def _is_connection_error(self, exception: Exception) -> bool:
|
|
310
341
|
"""Determine if an exception indicates a connection problem."""
|
|
@@ -312,7 +343,8 @@ class MCPTool:
|
|
|
312
343
|
connection_indicators = [
|
|
313
344
|
"connection lost", "connection closed", "connection refused",
|
|
314
345
|
"broken pipe", "timeout", "eof", "pipe closed", "process died",
|
|
315
|
-
"no route to host", "no server found"
|
|
346
|
+
"no route to host", "no server found", "transport not initialized",
|
|
347
|
+
"stream manager unavailable"
|
|
316
348
|
]
|
|
317
349
|
return any(indicator in error_str for indicator in connection_indicators)
|
|
318
350
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"""
|
|
3
3
|
SSE transport for MCP communication.
|
|
4
4
|
|
|
5
|
-
FIXED:
|
|
6
|
-
The SSE endpoint works perfectly, so we
|
|
5
|
+
FIXED: Improved health monitoring to avoid false unhealthy states.
|
|
6
|
+
The SSE endpoint works perfectly, so we need more lenient health checks.
|
|
7
7
|
"""
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
@@ -25,7 +25,7 @@ class SSETransport(MCPBaseTransport):
|
|
|
25
25
|
"""
|
|
26
26
|
SSE transport implementing the MCP protocol over Server-Sent Events.
|
|
27
27
|
|
|
28
|
-
FIXED:
|
|
28
|
+
FIXED: More lenient health monitoring to avoid false unhealthy states.
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
31
|
def __init__(self, url: str, api_key: Optional[str] = None,
|
|
@@ -60,10 +60,12 @@ class SSETransport(MCPBaseTransport):
|
|
|
60
60
|
self.sse_response = None
|
|
61
61
|
self.sse_stream_context = None
|
|
62
62
|
|
|
63
|
-
#
|
|
63
|
+
# FIXED: More lenient health monitoring
|
|
64
64
|
self._last_successful_ping = None
|
|
65
65
|
self._consecutive_failures = 0
|
|
66
|
-
self._max_consecutive_failures = 3
|
|
66
|
+
self._max_consecutive_failures = 5 # INCREASED: was 3, now 5
|
|
67
|
+
self._connection_grace_period = 30.0 # NEW: Grace period after initialization
|
|
68
|
+
self._initialization_time = None # NEW: Track when we initialized
|
|
67
69
|
|
|
68
70
|
# Performance metrics
|
|
69
71
|
self._metrics = {
|
|
@@ -119,7 +121,7 @@ class SSETransport(MCPBaseTransport):
|
|
|
119
121
|
return True
|
|
120
122
|
|
|
121
123
|
async def initialize(self) -> bool:
|
|
122
|
-
"""Initialize SSE connection."""
|
|
124
|
+
"""Initialize SSE connection with improved health tracking."""
|
|
123
125
|
if self._initialized:
|
|
124
126
|
logger.warning("Transport already initialized")
|
|
125
127
|
return True
|
|
@@ -213,8 +215,11 @@ class SSETransport(MCPBaseTransport):
|
|
|
213
215
|
# Send initialized notification
|
|
214
216
|
await self._send_notification("notifications/initialized")
|
|
215
217
|
|
|
218
|
+
# FIXED: Set health tracking state
|
|
216
219
|
self._initialized = True
|
|
220
|
+
self._initialization_time = time.time()
|
|
217
221
|
self._last_successful_ping = time.time()
|
|
222
|
+
self._consecutive_failures = 0 # Reset failure count
|
|
218
223
|
|
|
219
224
|
if self.enable_metrics:
|
|
220
225
|
init_time = time.time() - start_time
|
|
@@ -311,7 +316,8 @@ class SSETransport(MCPBaseTransport):
|
|
|
311
316
|
if self.enable_metrics:
|
|
312
317
|
self._metrics["stream_errors"] += 1
|
|
313
318
|
logger.error("SSE stream processing error: %s", e)
|
|
314
|
-
|
|
319
|
+
# FIXED: Don't increment consecutive failures for stream processing errors
|
|
320
|
+
# These are often temporary and don't indicate connection health
|
|
315
321
|
|
|
316
322
|
async def _send_request(self, method: str, params: Dict[str, Any] = None,
|
|
317
323
|
timeout: Optional[float] = None) -> Dict[str, Any]:
|
|
@@ -348,25 +354,37 @@ class SSETransport(MCPBaseTransport):
|
|
|
348
354
|
# Async response - wait for result via SSE
|
|
349
355
|
request_timeout = timeout or self.default_timeout
|
|
350
356
|
result = await asyncio.wait_for(future, timeout=request_timeout)
|
|
351
|
-
|
|
357
|
+
# FIXED: Only reset failures on successful tool calls, not all requests
|
|
358
|
+
if method.startswith('tools/'):
|
|
359
|
+
self._consecutive_failures = 0
|
|
360
|
+
self._last_successful_ping = time.time()
|
|
352
361
|
return result
|
|
353
362
|
elif response.status_code == 200:
|
|
354
363
|
# Immediate response
|
|
355
364
|
self.pending_requests.pop(request_id, None)
|
|
356
|
-
|
|
365
|
+
# FIXED: Only reset failures on successful tool calls
|
|
366
|
+
if method.startswith('tools/'):
|
|
367
|
+
self._consecutive_failures = 0
|
|
368
|
+
self._last_successful_ping = time.time()
|
|
357
369
|
return response.json()
|
|
358
370
|
else:
|
|
359
371
|
self.pending_requests.pop(request_id, None)
|
|
360
|
-
|
|
372
|
+
# FIXED: Only increment failures for tool calls, not initialization
|
|
373
|
+
if method.startswith('tools/'):
|
|
374
|
+
self._consecutive_failures += 1
|
|
361
375
|
raise RuntimeError(f"HTTP request failed with status: {response.status_code}")
|
|
362
376
|
|
|
363
377
|
except asyncio.TimeoutError:
|
|
364
378
|
self.pending_requests.pop(request_id, None)
|
|
365
|
-
|
|
379
|
+
# FIXED: Only increment failures for tool calls
|
|
380
|
+
if method.startswith('tools/'):
|
|
381
|
+
self._consecutive_failures += 1
|
|
366
382
|
raise
|
|
367
383
|
except Exception:
|
|
368
384
|
self.pending_requests.pop(request_id, None)
|
|
369
|
-
|
|
385
|
+
# FIXED: Only increment failures for tool calls
|
|
386
|
+
if method.startswith('tools/'):
|
|
387
|
+
self._consecutive_failures += 1
|
|
370
388
|
raise
|
|
371
389
|
|
|
372
390
|
async def _send_notification(self, method: str, params: Dict[str, Any] = None):
|
|
@@ -395,7 +413,7 @@ class SSETransport(MCPBaseTransport):
|
|
|
395
413
|
logger.warning("Notification failed with status: %s", response.status_code)
|
|
396
414
|
|
|
397
415
|
async def send_ping(self) -> bool:
|
|
398
|
-
"""Send ping to check connection health."""
|
|
416
|
+
"""Send ping to check connection health with improved logic."""
|
|
399
417
|
if not self._initialized:
|
|
400
418
|
return False
|
|
401
419
|
|
|
@@ -408,7 +426,7 @@ class SSETransport(MCPBaseTransport):
|
|
|
408
426
|
|
|
409
427
|
if success:
|
|
410
428
|
self._last_successful_ping = time.time()
|
|
411
|
-
|
|
429
|
+
# FIXED: Don't reset consecutive failures here - let tool calls do that
|
|
412
430
|
|
|
413
431
|
if self.enable_metrics:
|
|
414
432
|
ping_time = time.time() - start_time
|
|
@@ -418,17 +436,28 @@ class SSETransport(MCPBaseTransport):
|
|
|
418
436
|
return success
|
|
419
437
|
except Exception as e:
|
|
420
438
|
logger.debug("SSE ping failed: %s", e)
|
|
421
|
-
|
|
439
|
+
# FIXED: Don't increment consecutive failures for ping failures
|
|
422
440
|
return False
|
|
423
441
|
|
|
424
442
|
def is_connected(self) -> bool:
|
|
425
|
-
"""
|
|
443
|
+
"""
|
|
444
|
+
FIXED: More lenient connection health check.
|
|
445
|
+
|
|
446
|
+
The diagnostic shows the connection works fine, so we need to be less aggressive
|
|
447
|
+
about marking it as unhealthy.
|
|
448
|
+
"""
|
|
426
449
|
if not self._initialized or not self.session_id:
|
|
427
450
|
return False
|
|
428
451
|
|
|
429
|
-
#
|
|
452
|
+
# FIXED: Grace period after initialization - always return True for a while
|
|
453
|
+
if (self._initialization_time and
|
|
454
|
+
time.time() - self._initialization_time < self._connection_grace_period):
|
|
455
|
+
logger.debug("Within grace period - connection considered healthy")
|
|
456
|
+
return True
|
|
457
|
+
|
|
458
|
+
# FIXED: More lenient failure threshold
|
|
430
459
|
if self._consecutive_failures >= self._max_consecutive_failures:
|
|
431
|
-
logger.warning(f"Connection marked unhealthy after {self._consecutive_failures} failures")
|
|
460
|
+
logger.warning(f"Connection marked unhealthy after {self._consecutive_failures} consecutive failures")
|
|
432
461
|
return False
|
|
433
462
|
|
|
434
463
|
# Check if SSE task is still running
|
|
@@ -438,6 +467,13 @@ class SSETransport(MCPBaseTransport):
|
|
|
438
467
|
logger.warning(f"SSE task died: {exception}")
|
|
439
468
|
return False
|
|
440
469
|
|
|
470
|
+
# FIXED: If we have a recent successful ping/tool call, we're healthy
|
|
471
|
+
if (self._last_successful_ping and
|
|
472
|
+
time.time() - self._last_successful_ping < 60.0): # Success within last minute
|
|
473
|
+
return True
|
|
474
|
+
|
|
475
|
+
# FIXED: Default to healthy if no clear indicators of problems
|
|
476
|
+
logger.debug("No clear health indicators - defaulting to healthy")
|
|
441
477
|
return True
|
|
442
478
|
|
|
443
479
|
async def get_tools(self) -> List[Dict[str, Any]]:
|
|
@@ -625,10 +661,26 @@ class SSETransport(MCPBaseTransport):
|
|
|
625
661
|
self.sse_stream_context = None
|
|
626
662
|
self.stream_client = None
|
|
627
663
|
self.send_client = None
|
|
664
|
+
# FIXED: Reset health tracking
|
|
665
|
+
self._consecutive_failures = 0
|
|
666
|
+
self._last_successful_ping = None
|
|
667
|
+
self._initialization_time = None
|
|
628
668
|
|
|
629
669
|
def get_metrics(self) -> Dict[str, Any]:
|
|
630
|
-
"""Get performance and connection metrics."""
|
|
631
|
-
|
|
670
|
+
"""Get performance and connection metrics with health info."""
|
|
671
|
+
metrics = self._metrics.copy()
|
|
672
|
+
metrics.update({
|
|
673
|
+
"is_connected": self.is_connected(),
|
|
674
|
+
"consecutive_failures": self._consecutive_failures,
|
|
675
|
+
"max_consecutive_failures": self._max_consecutive_failures,
|
|
676
|
+
"last_successful_ping": self._last_successful_ping,
|
|
677
|
+
"initialization_time_timestamp": self._initialization_time,
|
|
678
|
+
"grace_period_active": (
|
|
679
|
+
self._initialization_time and
|
|
680
|
+
time.time() - self._initialization_time < self._connection_grace_period
|
|
681
|
+
) if self._initialization_time else False
|
|
682
|
+
})
|
|
683
|
+
return metrics
|
|
632
684
|
|
|
633
685
|
def reset_metrics(self) -> None:
|
|
634
686
|
"""Reset performance metrics."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chuk-tool-processor
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.12
|
|
4
4
|
Summary: Async-native framework for registering, discovering, and executing tools referenced in LLM responses
|
|
5
5
|
Author-email: CHUK Team <chrishayuk@somejunkmailbox.com>
|
|
6
6
|
Maintainer-email: CHUK Team <chrishayuk@somejunkmailbox.com>
|
|
@@ -17,7 +17,7 @@ chuk_tool_processor/logging/formatter.py,sha256=RhlV6NqBYRBOtytDY49c9Y1J4l02ZjNX
|
|
|
17
17
|
chuk_tool_processor/logging/helpers.py,sha256=0j-PoFUGyzl9NQ6jJEcS3YKV8AJgs8VwUpYa-6UiWT0,5946
|
|
18
18
|
chuk_tool_processor/logging/metrics.py,sha256=s59Au8q0eqGGtJMDqmJBZhbJHh4BWGE1CzT0iI8lRS8,3624
|
|
19
19
|
chuk_tool_processor/mcp/__init__.py,sha256=QkHgRu_YAjmYNTEYMK4bYILu8KK6b0aziTKvBVTRXvI,1052
|
|
20
|
-
chuk_tool_processor/mcp/mcp_tool.py,sha256
|
|
20
|
+
chuk_tool_processor/mcp/mcp_tool.py,sha256=-9I-hmsOOMAAiKgRCP-jqBjtXGuBxTBf3kq6kFWAxkA,19531
|
|
21
21
|
chuk_tool_processor/mcp/register_mcp_tools.py,sha256=s6mQMtZr7dswT2WXDJ84zjOTSi3cOmtRTGGdLMl15bM,4897
|
|
22
22
|
chuk_tool_processor/mcp/setup_mcp_http_streamable.py,sha256=ZJUAj7LL4CRfc-CBl0SQJk0qfW12IuixR-7J2hbQ8S8,4538
|
|
23
23
|
chuk_tool_processor/mcp/setup_mcp_sse.py,sha256=4nf0V6cykAPLxtgsl8RTAYQdVWITUNu_3CIU1vcLjlo,3795
|
|
@@ -26,7 +26,7 @@ chuk_tool_processor/mcp/stream_manager.py,sha256=sRy6X893ISht4XUFVIjUHQ0zVisyP7e
|
|
|
26
26
|
chuk_tool_processor/mcp/transport/__init__.py,sha256=2IZHt5x8GD7jgPmt5JvUIhSBij4kf9vZta1rL_-UDMc,657
|
|
27
27
|
chuk_tool_processor/mcp/transport/base_transport.py,sha256=EapglrjRuSniGkEoEeL76ZGx_VriHqR1coK32nFMrd8,9175
|
|
28
28
|
chuk_tool_processor/mcp/transport/http_streamable_transport.py,sha256=EXPILzltlrkwbzB7OH6OGBuS7U7W_yP5UAvxHokmUN0,24075
|
|
29
|
-
chuk_tool_processor/mcp/transport/sse_transport.py,sha256=
|
|
29
|
+
chuk_tool_processor/mcp/transport/sse_transport.py,sha256=TD-pXfCVKKUBe_6D9WBHrn1_oYJHYJlFIo45nALSo4I,28080
|
|
30
30
|
chuk_tool_processor/mcp/transport/stdio_transport.py,sha256=VyIR0huHwbYFgHw9OHsM2a6I-l5J0QS_HcBfq8MYZj8,28173
|
|
31
31
|
chuk_tool_processor/models/__init__.py,sha256=TC__rdVa0lQsmJHM_hbLDPRgToa_pQT_UxRcPZk6iVw,40
|
|
32
32
|
chuk_tool_processor/models/execution_strategy.py,sha256=UVW35YIeMY2B3mpIKZD2rAkyOPayI6ckOOUALyf0YiQ,2115
|
|
@@ -54,7 +54,7 @@ chuk_tool_processor/registry/providers/__init__.py,sha256=eigwG_So11j7WbDGSWaKd3
|
|
|
54
54
|
chuk_tool_processor/registry/providers/memory.py,sha256=6cMtUwLO6zrk3pguQRgxJ2CReHAzewgZsizWZhsoStk,5184
|
|
55
55
|
chuk_tool_processor/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
56
56
|
chuk_tool_processor/utils/validation.py,sha256=V5N1dH9sJlHepFIbiI2k2MU82o7nvnh0hKyIt2jdgww,4136
|
|
57
|
-
chuk_tool_processor-0.6.
|
|
58
|
-
chuk_tool_processor-0.6.
|
|
59
|
-
chuk_tool_processor-0.6.
|
|
60
|
-
chuk_tool_processor-0.6.
|
|
57
|
+
chuk_tool_processor-0.6.12.dist-info/METADATA,sha256=ISYjgtXY9Q5jKxSKRIUQIDlfCcF9ZnVFl_zFz15rWWY,23493
|
|
58
|
+
chuk_tool_processor-0.6.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
59
|
+
chuk_tool_processor-0.6.12.dist-info/top_level.txt,sha256=7lTsnuRx4cOW4U2sNJWNxl4ZTt_J1ndkjTbj3pHPY5M,20
|
|
60
|
+
chuk_tool_processor-0.6.12.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|