chuk-tool-processor 0.6.27__tar.gz → 0.6.29__tar.gz
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-0.6.27 → chuk_tool_processor-0.6.29}/PKG-INFO +74 -1
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/README.md +73 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/pyproject.toml +1 -1
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/core/processor.py +2 -2
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/mcp/mcp_tool.py +5 -5
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/mcp/stream_manager.py +37 -25
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/mcp/transport/__init__.py +10 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/mcp/transport/http_streamable_transport.py +93 -89
- chuk_tool_processor-0.6.29/src/chuk_tool_processor/mcp/transport/models.py +100 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/mcp/transport/sse_transport.py +61 -61
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/mcp/transport/stdio_transport.py +5 -5
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/plugins/discovery.py +1 -1
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor.egg-info/PKG-INFO +74 -1
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor.egg-info/SOURCES.txt +1 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/setup.cfg +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/__init__.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/core/__init__.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/core/exceptions.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/execution/__init__.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/execution/strategies/__init__.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/execution/strategies/inprocess_strategy.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/execution/strategies/subprocess_strategy.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/execution/tool_executor.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/execution/wrappers/__init__.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/execution/wrappers/caching.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/execution/wrappers/rate_limiting.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/execution/wrappers/retry.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/logging/__init__.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/logging/context.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/logging/formatter.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/logging/helpers.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/logging/metrics.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/mcp/__init__.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/mcp/register_mcp_tools.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/mcp/setup_mcp_http_streamable.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/mcp/setup_mcp_sse.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/mcp/setup_mcp_stdio.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/mcp/transport/base_transport.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/models/__init__.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/models/execution_strategy.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/models/streaming_tool.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/models/tool_call.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/models/tool_export_mixin.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/models/tool_result.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/models/validated_tool.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/plugins/__init__.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/plugins/parsers/__init__.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/plugins/parsers/base.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/plugins/parsers/function_call_tool.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/plugins/parsers/json_tool.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/plugins/parsers/openai_tool.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/plugins/parsers/xml_tool.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/registry/__init__.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/registry/auto_register.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/registry/decorators.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/registry/interface.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/registry/metadata.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/registry/provider.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/registry/providers/__init__.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/registry/providers/memory.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/registry/tool_export.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/utils/__init__.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/utils/validation.py +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor.egg-info/dependency_links.txt +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor.egg-info/requires.txt +0 -0
- {chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chuk-tool-processor
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.29
|
|
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>
|
|
@@ -941,6 +941,79 @@ async def test_calculator():
|
|
|
941
941
|
|
|
942
942
|
## Configuration
|
|
943
943
|
|
|
944
|
+
### Timeout Configuration
|
|
945
|
+
|
|
946
|
+
CHUK Tool Processor uses a unified timeout configuration system that applies to all MCP transports (HTTP Streamable, SSE, STDIO) and the StreamManager. Instead of managing dozens of individual timeout values, there are just **4 logical timeout categories**:
|
|
947
|
+
|
|
948
|
+
```python
|
|
949
|
+
from chuk_tool_processor.mcp.transport import TimeoutConfig
|
|
950
|
+
|
|
951
|
+
# Create custom timeout configuration
|
|
952
|
+
timeout_config = TimeoutConfig(
|
|
953
|
+
connect=30.0, # Connection establishment, initialization, session discovery
|
|
954
|
+
operation=30.0, # Normal operations (tool calls, listing tools/resources/prompts)
|
|
955
|
+
quick=5.0, # Fast health checks and pings
|
|
956
|
+
shutdown=2.0 # Cleanup and shutdown operations
|
|
957
|
+
)
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
**Using timeout configuration with StreamManager:**
|
|
961
|
+
|
|
962
|
+
```python
|
|
963
|
+
from chuk_tool_processor.mcp.stream_manager import StreamManager
|
|
964
|
+
from chuk_tool_processor.mcp.transport import TimeoutConfig
|
|
965
|
+
|
|
966
|
+
# Create StreamManager with custom timeouts
|
|
967
|
+
timeout_config = TimeoutConfig(
|
|
968
|
+
connect=60.0, # Longer for slow initialization
|
|
969
|
+
operation=45.0, # Longer for heavy operations
|
|
970
|
+
quick=3.0, # Faster health checks
|
|
971
|
+
shutdown=5.0 # More time for cleanup
|
|
972
|
+
)
|
|
973
|
+
|
|
974
|
+
manager = StreamManager(timeout_config=timeout_config)
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
**Timeout categories explained:**
|
|
978
|
+
|
|
979
|
+
| Category | Default | Used For | Examples |
|
|
980
|
+
|----------|---------|----------|----------|
|
|
981
|
+
| `connect` | 30.0s | Connection setup, initialization, discovery | HTTP connection, SSE session discovery, STDIO subprocess launch |
|
|
982
|
+
| `operation` | 30.0s | Normal tool operations | Tool calls, listing tools/resources/prompts, get_tools() |
|
|
983
|
+
| `quick` | 5.0s | Fast health/status checks | Ping operations, health checks |
|
|
984
|
+
| `shutdown` | 2.0s | Cleanup and teardown | Transport close, connection cleanup |
|
|
985
|
+
|
|
986
|
+
**Why this matters:**
|
|
987
|
+
- ✅ **Simple**: 4 timeout values instead of 20+
|
|
988
|
+
- ✅ **Consistent**: Same timeout behavior across all transports
|
|
989
|
+
- ✅ **Configurable**: Adjust timeouts based on your environment (slow networks, large datasets, etc.)
|
|
990
|
+
- ✅ **Type-safe**: Pydantic validation ensures correct values
|
|
991
|
+
|
|
992
|
+
**Example: Adjusting for slow environments**
|
|
993
|
+
|
|
994
|
+
```python
|
|
995
|
+
from chuk_tool_processor.mcp import setup_mcp_stdio
|
|
996
|
+
from chuk_tool_processor.mcp.transport import TimeoutConfig
|
|
997
|
+
|
|
998
|
+
# For slow network or resource-constrained environments
|
|
999
|
+
slow_timeouts = TimeoutConfig(
|
|
1000
|
+
connect=120.0, # Allow more time for package downloads
|
|
1001
|
+
operation=60.0, # Allow more time for heavy operations
|
|
1002
|
+
quick=10.0, # Be patient with health checks
|
|
1003
|
+
shutdown=10.0 # Allow thorough cleanup
|
|
1004
|
+
)
|
|
1005
|
+
|
|
1006
|
+
processor, manager = await setup_mcp_stdio(
|
|
1007
|
+
config_file="mcp_config.json",
|
|
1008
|
+
servers=["sqlite"],
|
|
1009
|
+
namespace="db",
|
|
1010
|
+
initialization_timeout=120.0
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
# Set custom timeouts on the manager
|
|
1014
|
+
manager.timeout_config = slow_timeouts
|
|
1015
|
+
```
|
|
1016
|
+
|
|
944
1017
|
### Environment Variables
|
|
945
1018
|
|
|
946
1019
|
| Variable | Default | Description |
|
|
@@ -913,6 +913,79 @@ async def test_calculator():
|
|
|
913
913
|
|
|
914
914
|
## Configuration
|
|
915
915
|
|
|
916
|
+
### Timeout Configuration
|
|
917
|
+
|
|
918
|
+
CHUK Tool Processor uses a unified timeout configuration system that applies to all MCP transports (HTTP Streamable, SSE, STDIO) and the StreamManager. Instead of managing dozens of individual timeout values, there are just **4 logical timeout categories**:
|
|
919
|
+
|
|
920
|
+
```python
|
|
921
|
+
from chuk_tool_processor.mcp.transport import TimeoutConfig
|
|
922
|
+
|
|
923
|
+
# Create custom timeout configuration
|
|
924
|
+
timeout_config = TimeoutConfig(
|
|
925
|
+
connect=30.0, # Connection establishment, initialization, session discovery
|
|
926
|
+
operation=30.0, # Normal operations (tool calls, listing tools/resources/prompts)
|
|
927
|
+
quick=5.0, # Fast health checks and pings
|
|
928
|
+
shutdown=2.0 # Cleanup and shutdown operations
|
|
929
|
+
)
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
**Using timeout configuration with StreamManager:**
|
|
933
|
+
|
|
934
|
+
```python
|
|
935
|
+
from chuk_tool_processor.mcp.stream_manager import StreamManager
|
|
936
|
+
from chuk_tool_processor.mcp.transport import TimeoutConfig
|
|
937
|
+
|
|
938
|
+
# Create StreamManager with custom timeouts
|
|
939
|
+
timeout_config = TimeoutConfig(
|
|
940
|
+
connect=60.0, # Longer for slow initialization
|
|
941
|
+
operation=45.0, # Longer for heavy operations
|
|
942
|
+
quick=3.0, # Faster health checks
|
|
943
|
+
shutdown=5.0 # More time for cleanup
|
|
944
|
+
)
|
|
945
|
+
|
|
946
|
+
manager = StreamManager(timeout_config=timeout_config)
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
**Timeout categories explained:**
|
|
950
|
+
|
|
951
|
+
| Category | Default | Used For | Examples |
|
|
952
|
+
|----------|---------|----------|----------|
|
|
953
|
+
| `connect` | 30.0s | Connection setup, initialization, discovery | HTTP connection, SSE session discovery, STDIO subprocess launch |
|
|
954
|
+
| `operation` | 30.0s | Normal tool operations | Tool calls, listing tools/resources/prompts, get_tools() |
|
|
955
|
+
| `quick` | 5.0s | Fast health/status checks | Ping operations, health checks |
|
|
956
|
+
| `shutdown` | 2.0s | Cleanup and teardown | Transport close, connection cleanup |
|
|
957
|
+
|
|
958
|
+
**Why this matters:**
|
|
959
|
+
- ✅ **Simple**: 4 timeout values instead of 20+
|
|
960
|
+
- ✅ **Consistent**: Same timeout behavior across all transports
|
|
961
|
+
- ✅ **Configurable**: Adjust timeouts based on your environment (slow networks, large datasets, etc.)
|
|
962
|
+
- ✅ **Type-safe**: Pydantic validation ensures correct values
|
|
963
|
+
|
|
964
|
+
**Example: Adjusting for slow environments**
|
|
965
|
+
|
|
966
|
+
```python
|
|
967
|
+
from chuk_tool_processor.mcp import setup_mcp_stdio
|
|
968
|
+
from chuk_tool_processor.mcp.transport import TimeoutConfig
|
|
969
|
+
|
|
970
|
+
# For slow network or resource-constrained environments
|
|
971
|
+
slow_timeouts = TimeoutConfig(
|
|
972
|
+
connect=120.0, # Allow more time for package downloads
|
|
973
|
+
operation=60.0, # Allow more time for heavy operations
|
|
974
|
+
quick=10.0, # Be patient with health checks
|
|
975
|
+
shutdown=10.0 # Allow thorough cleanup
|
|
976
|
+
)
|
|
977
|
+
|
|
978
|
+
processor, manager = await setup_mcp_stdio(
|
|
979
|
+
config_file="mcp_config.json",
|
|
980
|
+
servers=["sqlite"],
|
|
981
|
+
namespace="db",
|
|
982
|
+
initialization_timeout=120.0
|
|
983
|
+
)
|
|
984
|
+
|
|
985
|
+
# Set custom timeouts on the manager
|
|
986
|
+
manager.timeout_config = slow_timeouts
|
|
987
|
+
```
|
|
988
|
+
|
|
916
989
|
### Environment Variables
|
|
917
990
|
|
|
918
991
|
| Variable | Default | Description |
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "chuk-tool-processor"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.29"
|
|
8
8
|
description = "Async-native framework for registering, discovering, and executing tools referenced in LLM responses"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
{chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/core/processor.py
RENAMED
|
@@ -260,7 +260,7 @@ class ToolProcessor:
|
|
|
260
260
|
unknown_tools.append(call.tool)
|
|
261
261
|
|
|
262
262
|
if unknown_tools:
|
|
263
|
-
self.logger.
|
|
263
|
+
self.logger.debug(f"Unknown tools: {unknown_tools}")
|
|
264
264
|
|
|
265
265
|
# Execute tools
|
|
266
266
|
results = await self.executor.execute(calls, timeout=timeout)
|
|
@@ -412,7 +412,7 @@ class ToolProcessor:
|
|
|
412
412
|
duration=duration,
|
|
413
413
|
num_calls=0,
|
|
414
414
|
)
|
|
415
|
-
self.logger.
|
|
415
|
+
self.logger.debug(f"Parser {parser_name} failed: {str(e)}")
|
|
416
416
|
return []
|
|
417
417
|
|
|
418
418
|
|
{chuk_tool_processor-0.6.27 → chuk_tool_processor-0.6.29}/src/chuk_tool_processor/mcp/mcp_tool.py
RENAMED
|
@@ -370,7 +370,7 @@ class MCPTool:
|
|
|
370
370
|
self._circuit_open = False
|
|
371
371
|
self._circuit_open_time = None
|
|
372
372
|
self.connection_state = ConnectionState.HEALTHY
|
|
373
|
-
logger.
|
|
373
|
+
logger.debug(f"Circuit breaker closed for tool '{self.tool_name}' after successful execution")
|
|
374
374
|
|
|
375
375
|
async def _record_failure(self, is_connection_error: bool = False) -> None:
|
|
376
376
|
"""Record a failed execution."""
|
|
@@ -407,7 +407,7 @@ class MCPTool:
|
|
|
407
407
|
self._circuit_open = False
|
|
408
408
|
self._circuit_open_time = None
|
|
409
409
|
self.connection_state = ConnectionState.HEALTHY
|
|
410
|
-
logger.
|
|
410
|
+
logger.debug(f"Circuit breaker reset for tool '{self.tool_name}' after timeout")
|
|
411
411
|
return False
|
|
412
412
|
|
|
413
413
|
return True
|
|
@@ -462,12 +462,12 @@ class MCPTool:
|
|
|
462
462
|
self._circuit_open_time = None
|
|
463
463
|
self._consecutive_failures = 0
|
|
464
464
|
self.connection_state = ConnectionState.HEALTHY
|
|
465
|
-
logger.
|
|
465
|
+
logger.debug(f"Circuit breaker manually reset for tool '{self.tool_name}'")
|
|
466
466
|
|
|
467
467
|
def disable_resilience(self) -> None:
|
|
468
468
|
"""Disable resilience features for this tool instance."""
|
|
469
469
|
self.enable_resilience = False
|
|
470
|
-
logger.
|
|
470
|
+
logger.debug(f"Resilience features disabled for tool '{self.tool_name}'")
|
|
471
471
|
|
|
472
472
|
def set_stream_manager(self, stream_manager: StreamManager | None) -> None:
|
|
473
473
|
"""
|
|
@@ -482,7 +482,7 @@ class MCPTool:
|
|
|
482
482
|
if self._circuit_open:
|
|
483
483
|
self._circuit_open = False
|
|
484
484
|
self._circuit_open_time = None
|
|
485
|
-
logger.
|
|
485
|
+
logger.debug(f"Circuit breaker closed for tool '{self.tool_name}' due to new stream manager")
|
|
486
486
|
else:
|
|
487
487
|
self.connection_state = ConnectionState.DISCONNECTED
|
|
488
488
|
|
|
@@ -21,6 +21,7 @@ from chuk_tool_processor.mcp.transport import (
|
|
|
21
21
|
MCPBaseTransport,
|
|
22
22
|
SSETransport,
|
|
23
23
|
StdioTransport,
|
|
24
|
+
TimeoutConfig,
|
|
24
25
|
)
|
|
25
26
|
|
|
26
27
|
logger = get_logger("chuk_tool_processor.mcp.stream_manager")
|
|
@@ -38,7 +39,7 @@ class StreamManager:
|
|
|
38
39
|
- HTTP Streamable (modern replacement for SSE, spec 2025-03-26) with graceful headers handling
|
|
39
40
|
"""
|
|
40
41
|
|
|
41
|
-
def __init__(self) -> None:
|
|
42
|
+
def __init__(self, timeout_config: TimeoutConfig | None = None) -> None:
|
|
42
43
|
self.transports: dict[str, MCPBaseTransport] = {}
|
|
43
44
|
self.server_info: list[dict[str, Any]] = []
|
|
44
45
|
self.tool_to_server_map: dict[str, str] = {}
|
|
@@ -46,7 +47,7 @@ class StreamManager:
|
|
|
46
47
|
self.all_tools: list[dict[str, Any]] = []
|
|
47
48
|
self._lock = asyncio.Lock()
|
|
48
49
|
self._closed = False # Track if we've been closed
|
|
49
|
-
self.
|
|
50
|
+
self.timeout_config = timeout_config or TimeoutConfig()
|
|
50
51
|
|
|
51
52
|
# ------------------------------------------------------------------ #
|
|
52
53
|
# factory helpers with enhanced error handling #
|
|
@@ -182,7 +183,7 @@ class StreamManager:
|
|
|
182
183
|
params, connection_timeout=initialization_timeout, default_timeout=default_timeout
|
|
183
184
|
)
|
|
184
185
|
elif transport_type == "sse":
|
|
185
|
-
logger.
|
|
186
|
+
logger.debug(
|
|
186
187
|
"Using SSE transport in initialize() - consider using initialize_with_sse() instead"
|
|
187
188
|
)
|
|
188
189
|
params = await load_config(config_file, server_name)
|
|
@@ -195,7 +196,7 @@ class StreamManager:
|
|
|
195
196
|
sse_url = "http://localhost:8000"
|
|
196
197
|
api_key = None
|
|
197
198
|
headers = {}
|
|
198
|
-
logger.
|
|
199
|
+
logger.debug("No URL configured for SSE transport, using default: %s", sse_url)
|
|
199
200
|
|
|
200
201
|
# Build SSE transport with optional headers
|
|
201
202
|
transport_params = {"url": sse_url, "api_key": api_key, "default_timeout": default_timeout}
|
|
@@ -205,7 +206,7 @@ class StreamManager:
|
|
|
205
206
|
transport = SSETransport(**transport_params)
|
|
206
207
|
|
|
207
208
|
elif transport_type == "http_streamable":
|
|
208
|
-
logger.
|
|
209
|
+
logger.debug(
|
|
209
210
|
"Using HTTP Streamable transport in initialize() - consider using initialize_with_http_streamable() instead"
|
|
210
211
|
)
|
|
211
212
|
params = await load_config(config_file, server_name)
|
|
@@ -220,9 +221,7 @@ class StreamManager:
|
|
|
220
221
|
api_key = None
|
|
221
222
|
headers = {}
|
|
222
223
|
session_id = None
|
|
223
|
-
logger.
|
|
224
|
-
"No URL configured for HTTP Streamable transport, using default: %s", http_url
|
|
225
|
-
)
|
|
224
|
+
logger.debug("No URL configured for HTTP Streamable transport, using default: %s", http_url)
|
|
226
225
|
|
|
227
226
|
# Build HTTP transport (headers not supported yet)
|
|
228
227
|
transport_params = {
|
|
@@ -244,7 +243,7 @@ class StreamManager:
|
|
|
244
243
|
# Initialize with timeout protection
|
|
245
244
|
try:
|
|
246
245
|
if not await asyncio.wait_for(transport.initialize(), timeout=initialization_timeout):
|
|
247
|
-
logger.
|
|
246
|
+
logger.warning("Failed to init %s", server_name)
|
|
248
247
|
continue
|
|
249
248
|
except TimeoutError:
|
|
250
249
|
logger.error("Timeout initialising %s (timeout=%ss)", server_name, initialization_timeout)
|
|
@@ -253,8 +252,12 @@ class StreamManager:
|
|
|
253
252
|
self.transports[server_name] = transport
|
|
254
253
|
|
|
255
254
|
# Ping and get tools with timeout protection (use longer timeouts for slow servers)
|
|
256
|
-
status =
|
|
257
|
-
|
|
255
|
+
status = (
|
|
256
|
+
"Up"
|
|
257
|
+
if await asyncio.wait_for(transport.send_ping(), timeout=self.timeout_config.operation)
|
|
258
|
+
else "Down"
|
|
259
|
+
)
|
|
260
|
+
tools = await asyncio.wait_for(transport.get_tools(), timeout=self.timeout_config.operation)
|
|
258
261
|
|
|
259
262
|
for t in tools:
|
|
260
263
|
name = t.get("name")
|
|
@@ -327,7 +330,7 @@ class StreamManager:
|
|
|
327
330
|
|
|
328
331
|
try:
|
|
329
332
|
if not await asyncio.wait_for(transport.initialize(), timeout=initialization_timeout):
|
|
330
|
-
logger.
|
|
333
|
+
logger.warning("Failed to init SSE %s", name)
|
|
331
334
|
continue
|
|
332
335
|
except TimeoutError:
|
|
333
336
|
logger.error("Timeout initialising SSE %s (timeout=%ss)", name, initialization_timeout)
|
|
@@ -335,8 +338,12 @@ class StreamManager:
|
|
|
335
338
|
|
|
336
339
|
self.transports[name] = transport
|
|
337
340
|
# Use longer timeouts for slow servers (ping can take time after initialization)
|
|
338
|
-
status =
|
|
339
|
-
|
|
341
|
+
status = (
|
|
342
|
+
"Up"
|
|
343
|
+
if await asyncio.wait_for(transport.send_ping(), timeout=self.timeout_config.operation)
|
|
344
|
+
else "Down"
|
|
345
|
+
)
|
|
346
|
+
tools = await asyncio.wait_for(transport.get_tools(), timeout=self.timeout_config.operation)
|
|
340
347
|
|
|
341
348
|
for t in tools:
|
|
342
349
|
tname = t.get("name")
|
|
@@ -406,7 +413,7 @@ class StreamManager:
|
|
|
406
413
|
logger.debug(f"Calling transport.initialize() for {name} with timeout={initialization_timeout}s")
|
|
407
414
|
try:
|
|
408
415
|
if not await asyncio.wait_for(transport.initialize(), timeout=initialization_timeout):
|
|
409
|
-
logger.
|
|
416
|
+
logger.warning("Failed to init HTTP Streamable %s", name)
|
|
410
417
|
continue
|
|
411
418
|
except TimeoutError:
|
|
412
419
|
logger.error(
|
|
@@ -417,8 +424,12 @@ class StreamManager:
|
|
|
417
424
|
|
|
418
425
|
self.transports[name] = transport
|
|
419
426
|
# Use longer timeouts for slow servers (ping can take time after initialization)
|
|
420
|
-
status =
|
|
421
|
-
|
|
427
|
+
status = (
|
|
428
|
+
"Up"
|
|
429
|
+
if await asyncio.wait_for(transport.send_ping(), timeout=self.timeout_config.operation)
|
|
430
|
+
else "Down"
|
|
431
|
+
)
|
|
432
|
+
tools = await asyncio.wait_for(transport.get_tools(), timeout=self.timeout_config.operation)
|
|
422
433
|
|
|
423
434
|
for t in tools:
|
|
424
435
|
tname = t.get("name")
|
|
@@ -464,7 +475,7 @@ class StreamManager:
|
|
|
464
475
|
transport = self.transports[server_name]
|
|
465
476
|
|
|
466
477
|
try:
|
|
467
|
-
tools = await asyncio.wait_for(transport.get_tools(), timeout=
|
|
478
|
+
tools = await asyncio.wait_for(transport.get_tools(), timeout=self.timeout_config.operation)
|
|
468
479
|
logger.debug("Found %d tools for server %s", len(tools), server_name)
|
|
469
480
|
return tools
|
|
470
481
|
except TimeoutError:
|
|
@@ -483,7 +494,7 @@ class StreamManager:
|
|
|
483
494
|
|
|
484
495
|
async def _ping_one(name: str, tr: MCPBaseTransport):
|
|
485
496
|
try:
|
|
486
|
-
ok = await asyncio.wait_for(tr.send_ping(), timeout=
|
|
497
|
+
ok = await asyncio.wait_for(tr.send_ping(), timeout=self.timeout_config.quick)
|
|
487
498
|
except Exception:
|
|
488
499
|
ok = False
|
|
489
500
|
return {"server": name, "ok": ok}
|
|
@@ -498,7 +509,7 @@ class StreamManager:
|
|
|
498
509
|
|
|
499
510
|
async def _one(name: str, tr: MCPBaseTransport):
|
|
500
511
|
try:
|
|
501
|
-
res = await asyncio.wait_for(tr.list_resources(), timeout=
|
|
512
|
+
res = await asyncio.wait_for(tr.list_resources(), timeout=self.timeout_config.operation)
|
|
502
513
|
resources = res.get("resources", []) if isinstance(res, dict) else res
|
|
503
514
|
for item in resources:
|
|
504
515
|
item = dict(item)
|
|
@@ -518,7 +529,7 @@ class StreamManager:
|
|
|
518
529
|
|
|
519
530
|
async def _one(name: str, tr: MCPBaseTransport):
|
|
520
531
|
try:
|
|
521
|
-
res = await asyncio.wait_for(tr.list_prompts(), timeout=
|
|
532
|
+
res = await asyncio.wait_for(tr.list_prompts(), timeout=self.timeout_config.operation)
|
|
522
533
|
prompts = res.get("prompts", []) if isinstance(res, dict) else res
|
|
523
534
|
for item in prompts:
|
|
524
535
|
item = dict(item)
|
|
@@ -645,7 +656,7 @@ class StreamManager:
|
|
|
645
656
|
try:
|
|
646
657
|
results = await asyncio.wait_for(
|
|
647
658
|
asyncio.gather(*[task for _, task in close_tasks], return_exceptions=True),
|
|
648
|
-
timeout=self.
|
|
659
|
+
timeout=self.timeout_config.shutdown,
|
|
649
660
|
)
|
|
650
661
|
|
|
651
662
|
# Process results
|
|
@@ -668,7 +679,8 @@ class StreamManager:
|
|
|
668
679
|
# Brief wait for cancellations to complete
|
|
669
680
|
with contextlib.suppress(TimeoutError):
|
|
670
681
|
await asyncio.wait_for(
|
|
671
|
-
asyncio.gather(*[task for _, task in close_tasks], return_exceptions=True),
|
|
682
|
+
asyncio.gather(*[task for _, task in close_tasks], return_exceptions=True),
|
|
683
|
+
timeout=self.timeout_config.shutdown,
|
|
672
684
|
)
|
|
673
685
|
|
|
674
686
|
async def _sequential_close(self, transport_items: list[tuple[str, MCPBaseTransport]], close_results: list) -> None:
|
|
@@ -677,7 +689,7 @@ class StreamManager:
|
|
|
677
689
|
try:
|
|
678
690
|
await asyncio.wait_for(
|
|
679
691
|
self._close_single_transport(name, transport),
|
|
680
|
-
timeout=
|
|
692
|
+
timeout=self.timeout_config.shutdown,
|
|
681
693
|
)
|
|
682
694
|
logger.debug("Closed transport: %s", name)
|
|
683
695
|
close_results.append((name, True, None))
|
|
@@ -769,7 +781,7 @@ class StreamManager:
|
|
|
769
781
|
|
|
770
782
|
for name, transport in self.transports.items():
|
|
771
783
|
try:
|
|
772
|
-
ping_ok = await asyncio.wait_for(transport.send_ping(), timeout=
|
|
784
|
+
ping_ok = await asyncio.wait_for(transport.send_ping(), timeout=self.timeout_config.quick)
|
|
773
785
|
health_info["transports"][name] = {
|
|
774
786
|
"status": "healthy" if ping_ok else "unhealthy",
|
|
775
787
|
"ping_success": ping_ok,
|
|
@@ -11,6 +11,12 @@ All transports now follow the same interface and provide consistent behavior:
|
|
|
11
11
|
|
|
12
12
|
from .base_transport import MCPBaseTransport
|
|
13
13
|
from .http_streamable_transport import HTTPStreamableTransport
|
|
14
|
+
from .models import (
|
|
15
|
+
HeadersConfig,
|
|
16
|
+
ServerInfo,
|
|
17
|
+
TimeoutConfig,
|
|
18
|
+
TransportMetrics,
|
|
19
|
+
)
|
|
14
20
|
from .sse_transport import SSETransport
|
|
15
21
|
from .stdio_transport import StdioTransport
|
|
16
22
|
|
|
@@ -19,4 +25,8 @@ __all__ = [
|
|
|
19
25
|
"StdioTransport",
|
|
20
26
|
"SSETransport",
|
|
21
27
|
"HTTPStreamableTransport",
|
|
28
|
+
"TimeoutConfig",
|
|
29
|
+
"TransportMetrics",
|
|
30
|
+
"ServerInfo",
|
|
31
|
+
"HeadersConfig",
|
|
22
32
|
]
|