chuk-tool-processor 0.6.4__py3-none-any.whl → 0.9.7__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 (66) hide show
  1. chuk_tool_processor/core/__init__.py +32 -1
  2. chuk_tool_processor/core/exceptions.py +225 -13
  3. chuk_tool_processor/core/processor.py +135 -104
  4. chuk_tool_processor/execution/strategies/__init__.py +6 -0
  5. chuk_tool_processor/execution/strategies/inprocess_strategy.py +142 -150
  6. chuk_tool_processor/execution/strategies/subprocess_strategy.py +202 -206
  7. chuk_tool_processor/execution/tool_executor.py +82 -84
  8. chuk_tool_processor/execution/wrappers/__init__.py +42 -0
  9. chuk_tool_processor/execution/wrappers/caching.py +150 -116
  10. chuk_tool_processor/execution/wrappers/circuit_breaker.py +370 -0
  11. chuk_tool_processor/execution/wrappers/rate_limiting.py +76 -43
  12. chuk_tool_processor/execution/wrappers/retry.py +116 -78
  13. chuk_tool_processor/logging/__init__.py +23 -17
  14. chuk_tool_processor/logging/context.py +40 -45
  15. chuk_tool_processor/logging/formatter.py +22 -21
  16. chuk_tool_processor/logging/helpers.py +28 -42
  17. chuk_tool_processor/logging/metrics.py +13 -15
  18. chuk_tool_processor/mcp/__init__.py +8 -12
  19. chuk_tool_processor/mcp/mcp_tool.py +158 -114
  20. chuk_tool_processor/mcp/register_mcp_tools.py +22 -22
  21. chuk_tool_processor/mcp/setup_mcp_http_streamable.py +57 -17
  22. chuk_tool_processor/mcp/setup_mcp_sse.py +57 -17
  23. chuk_tool_processor/mcp/setup_mcp_stdio.py +11 -11
  24. chuk_tool_processor/mcp/stream_manager.py +333 -276
  25. chuk_tool_processor/mcp/transport/__init__.py +22 -29
  26. chuk_tool_processor/mcp/transport/base_transport.py +180 -44
  27. chuk_tool_processor/mcp/transport/http_streamable_transport.py +505 -325
  28. chuk_tool_processor/mcp/transport/models.py +100 -0
  29. chuk_tool_processor/mcp/transport/sse_transport.py +607 -276
  30. chuk_tool_processor/mcp/transport/stdio_transport.py +597 -116
  31. chuk_tool_processor/models/__init__.py +21 -1
  32. chuk_tool_processor/models/execution_strategy.py +16 -21
  33. chuk_tool_processor/models/streaming_tool.py +28 -25
  34. chuk_tool_processor/models/tool_call.py +49 -31
  35. chuk_tool_processor/models/tool_export_mixin.py +22 -8
  36. chuk_tool_processor/models/tool_result.py +40 -77
  37. chuk_tool_processor/models/tool_spec.py +350 -0
  38. chuk_tool_processor/models/validated_tool.py +36 -18
  39. chuk_tool_processor/observability/__init__.py +30 -0
  40. chuk_tool_processor/observability/metrics.py +312 -0
  41. chuk_tool_processor/observability/setup.py +105 -0
  42. chuk_tool_processor/observability/tracing.py +345 -0
  43. chuk_tool_processor/plugins/__init__.py +1 -1
  44. chuk_tool_processor/plugins/discovery.py +11 -11
  45. chuk_tool_processor/plugins/parsers/__init__.py +1 -1
  46. chuk_tool_processor/plugins/parsers/base.py +1 -2
  47. chuk_tool_processor/plugins/parsers/function_call_tool.py +13 -8
  48. chuk_tool_processor/plugins/parsers/json_tool.py +4 -3
  49. chuk_tool_processor/plugins/parsers/openai_tool.py +12 -7
  50. chuk_tool_processor/plugins/parsers/xml_tool.py +4 -4
  51. chuk_tool_processor/registry/__init__.py +12 -12
  52. chuk_tool_processor/registry/auto_register.py +22 -30
  53. chuk_tool_processor/registry/decorators.py +127 -129
  54. chuk_tool_processor/registry/interface.py +26 -23
  55. chuk_tool_processor/registry/metadata.py +27 -22
  56. chuk_tool_processor/registry/provider.py +17 -18
  57. chuk_tool_processor/registry/providers/__init__.py +16 -19
  58. chuk_tool_processor/registry/providers/memory.py +18 -25
  59. chuk_tool_processor/registry/tool_export.py +42 -51
  60. chuk_tool_processor/utils/validation.py +15 -16
  61. chuk_tool_processor-0.9.7.dist-info/METADATA +1813 -0
  62. chuk_tool_processor-0.9.7.dist-info/RECORD +67 -0
  63. chuk_tool_processor-0.6.4.dist-info/METADATA +0 -697
  64. chuk_tool_processor-0.6.4.dist-info/RECORD +0 -60
  65. {chuk_tool_processor-0.6.4.dist-info → chuk_tool_processor-0.9.7.dist-info}/WHEEL +0 -0
  66. {chuk_tool_processor-0.6.4.dist-info → chuk_tool_processor-0.9.7.dist-info}/top_level.txt +0 -0
@@ -1,39 +1,32 @@
1
1
  # chuk_tool_processor/mcp/transport/__init__.py
2
2
  """
3
- MCP Transport module providing multiple transport implementations.
3
+ MCP Transport module providing consistent transport implementations.
4
+
5
+ All transports now follow the same interface and provide consistent behavior:
6
+ - Standardized initialization and cleanup
7
+ - Unified metrics and monitoring
8
+ - Consistent error handling and timeouts
9
+ - Shared response normalization
4
10
  """
5
11
 
6
12
  from .base_transport import MCPBaseTransport
7
-
8
- # Always available transports
9
- try:
10
- from .stdio_transport import StdioTransport
11
- HAS_STDIO_TRANSPORT = True
12
- except ImportError:
13
- StdioTransport = None
14
- HAS_STDIO_TRANSPORT = False
15
-
16
- # Conditionally available transports
17
- try:
18
- from .sse_transport import SSETransport
19
- HAS_SSE_TRANSPORT = True
20
- except ImportError:
21
- SSETransport = None
22
- HAS_SSE_TRANSPORT = False
23
-
24
- try:
25
- from .http_streamable_transport import HTTPStreamableTransport
26
- HAS_HTTP_STREAMABLE_TRANSPORT = True
27
- except ImportError:
28
- HTTPStreamableTransport = None
29
- HAS_HTTP_STREAMABLE_TRANSPORT = False
13
+ from .http_streamable_transport import HTTPStreamableTransport
14
+ from .models import (
15
+ HeadersConfig,
16
+ ServerInfo,
17
+ TimeoutConfig,
18
+ TransportMetrics,
19
+ )
20
+ from .sse_transport import SSETransport
21
+ from .stdio_transport import StdioTransport
30
22
 
31
23
  __all__ = [
32
24
  "MCPBaseTransport",
33
25
  "StdioTransport",
34
- "SSETransport",
26
+ "SSETransport",
35
27
  "HTTPStreamableTransport",
36
- "HAS_STDIO_TRANSPORT",
37
- "HAS_SSE_TRANSPORT",
38
- "HAS_HTTP_STREAMABLE_TRANSPORT"
39
- ]
28
+ "TimeoutConfig",
29
+ "TransportMetrics",
30
+ "ServerInfo",
31
+ "HeadersConfig",
32
+ ]
@@ -1,103 +1,239 @@
1
1
  # chuk_tool_processor/mcp/transport/base_transport.py
2
2
  """
3
- Abstract transport layer for MCP communication.
3
+ Abstract base class for MCP transports with complete interface definition.
4
4
  """
5
+
5
6
  from __future__ import annotations
6
7
 
7
8
  from abc import ABC, abstractmethod
8
- from typing import Any, Dict, List
9
+ from typing import Any
9
10
 
10
11
 
11
12
  class MCPBaseTransport(ABC):
12
13
  """
13
- Abstract base class for MCP transport mechanisms.
14
+ Abstract base class for all MCP transport implementations.
15
+
16
+ Defines the complete interface that all transports must implement
17
+ for consistency across stdio, SSE, and HTTP streamable transports.
14
18
  """
15
19
 
16
20
  # ------------------------------------------------------------------ #
17
- # connection lifecycle #
21
+ # Core connection lifecycle #
18
22
  # ------------------------------------------------------------------ #
19
23
  @abstractmethod
20
24
  async def initialize(self) -> bool:
21
25
  """
22
- Establish the connection.
26
+ Initialize the transport connection.
23
27
 
24
- Returns
25
- -------
26
- bool
27
- ``True`` if the connection was initialised successfully.
28
+ Returns:
29
+ True if initialization was successful, False otherwise.
28
30
  """
29
- raise NotImplementedError
31
+ pass
30
32
 
31
33
  @abstractmethod
32
34
  async def close(self) -> None:
33
- """Tear down the connection and release all resources."""
34
- raise NotImplementedError
35
+ """Close the transport connection and clean up all resources."""
36
+ pass
35
37
 
36
38
  # ------------------------------------------------------------------ #
37
- # diagnostics #
39
+ # Health and diagnostics #
38
40
  # ------------------------------------------------------------------ #
39
41
  @abstractmethod
40
42
  async def send_ping(self) -> bool:
41
43
  """
42
- Send a **ping** request.
44
+ Send a ping to verify the connection is alive.
45
+
46
+ Returns:
47
+ True if ping was successful, False otherwise.
48
+ """
49
+ pass
43
50
 
44
- Returns
45
- -------
46
- bool
47
- ``True`` on success, ``False`` otherwise.
51
+ @abstractmethod
52
+ def is_connected(self) -> bool:
48
53
  """
49
- raise NotImplementedError
54
+ Check if the transport is connected and ready for operations.
55
+
56
+ Returns:
57
+ True if connected, False otherwise.
58
+ """
59
+ pass
50
60
 
51
61
  # ------------------------------------------------------------------ #
52
- # tool handling #
62
+ # Core MCP operations #
53
63
  # ------------------------------------------------------------------ #
54
64
  @abstractmethod
55
- async def get_tools(self) -> List[Dict[str, Any]]:
65
+ async def get_tools(self) -> list[dict[str, Any]]:
56
66
  """
57
- Return a list with *all* tool definitions exposed by the server.
67
+ Get the list of available tools from the server.
68
+
69
+ Returns:
70
+ List of tool definitions.
58
71
  """
59
- raise NotImplementedError
72
+ pass
60
73
 
61
74
  @abstractmethod
62
75
  async def call_tool(
63
- self, tool_name: str, arguments: Dict[str, Any]
64
- ) -> Dict[str, Any]:
76
+ self, tool_name: str, arguments: dict[str, Any], timeout: float | None = None
77
+ ) -> dict[str, Any]:
65
78
  """
66
- Execute *tool_name* with *arguments* and return the normalised result.
79
+ Call a tool with the given arguments.
80
+
81
+ Args:
82
+ tool_name: Name of the tool to call
83
+ arguments: Arguments to pass to the tool
84
+ timeout: Optional timeout for the operation
85
+
86
+ Returns:
87
+ Dictionary with 'isError' boolean and either 'content' or 'error'
67
88
  """
68
- raise NotImplementedError
89
+ pass
90
+
91
+ @abstractmethod
92
+ async def list_resources(self) -> dict[str, Any]:
93
+ """
94
+ List available resources from the server.
95
+
96
+ Returns:
97
+ Dictionary containing resources list or empty dict if not supported.
98
+ """
99
+ pass
100
+
101
+ @abstractmethod
102
+ async def list_prompts(self) -> dict[str, Any]:
103
+ """
104
+ List available prompts from the server.
105
+
106
+ Returns:
107
+ Dictionary containing prompts list or empty dict if not supported.
108
+ """
109
+ pass
69
110
 
70
111
  # ------------------------------------------------------------------ #
71
- # new: resources & prompts #
112
+ # Metrics and monitoring (all transports should support these) #
72
113
  # ------------------------------------------------------------------ #
73
114
  @abstractmethod
74
- async def list_resources(self) -> Dict[str, Any]:
115
+ def get_metrics(self) -> dict[str, Any]:
75
116
  """
76
- Retrieve the server's resources catalogue.
117
+ Get performance and connection metrics.
77
118
 
78
- Expected shape::
79
- { "resources": [ {...}, ... ], "nextCursor": "…", … }
119
+ Returns:
120
+ Dictionary containing metrics data.
80
121
  """
81
- raise NotImplementedError
122
+ pass
82
123
 
83
124
  @abstractmethod
84
- async def list_prompts(self) -> Dict[str, Any]:
125
+ def reset_metrics(self) -> None:
126
+ """Reset performance metrics to initial state."""
127
+ pass
128
+
129
+ # ------------------------------------------------------------------ #
130
+ # Backward compatibility and utility methods #
131
+ # ------------------------------------------------------------------ #
132
+ def get_streams(self) -> list[tuple]:
85
133
  """
86
- Retrieve the server's prompt catalogue.
134
+ Get underlying stream objects for backward compatibility.
87
135
 
88
- Expected shape::
89
- { "prompts": [ {...}, ... ], "nextCursor": "…", }
136
+ Returns:
137
+ List of (read_stream, write_stream) tuples, empty if not applicable.
90
138
  """
91
- raise NotImplementedError
139
+ return []
140
+
141
+ # ------------------------------------------------------------------ #
142
+ # Context manager support (all transports should support this) #
143
+ # ------------------------------------------------------------------ #
144
+ async def __aenter__(self):
145
+ """Context manager entry."""
146
+ success = await self.initialize()
147
+ if not success:
148
+ raise RuntimeError(f"Failed to initialize {self.__class__.__name__}")
149
+ return self
150
+
151
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
152
+ """Context manager cleanup."""
153
+ await self.close()
92
154
 
93
155
  # ------------------------------------------------------------------ #
94
- # optional helper (non-abstract) #
156
+ # Shared helper methods for response normalization #
95
157
  # ------------------------------------------------------------------ #
96
- def get_streams(self):
158
+ def _normalize_mcp_response(self, response: dict[str, Any]) -> dict[str, Any]:
97
159
  """
98
- Return a list of ``(read_stream, write_stream)`` tuples.
160
+ Normalize MCP response to consistent format.
99
161
 
100
- Transports that do not expose their low-level streams can simply leave
101
- the default implementation (which returns an empty list).
162
+ This provides shared logic for all transports to ensure consistent
163
+ response format regardless of transport type.
102
164
  """
103
- return []
165
+ # Handle explicit error in response
166
+ if "error" in response:
167
+ error_info = response["error"]
168
+ error_msg = error_info.get("message", "Unknown error") if isinstance(error_info, dict) else str(error_info)
169
+
170
+ return {"isError": True, "error": error_msg}
171
+
172
+ # Handle successful response with result
173
+ if "result" in response:
174
+ result = response["result"]
175
+
176
+ if isinstance(result, dict) and "content" in result:
177
+ return {"isError": False, "content": self._extract_mcp_content(result["content"])}
178
+ else:
179
+ return {"isError": False, "content": result}
180
+
181
+ # Handle direct content-based response
182
+ if "content" in response:
183
+ return {"isError": False, "content": self._extract_mcp_content(response["content"])}
184
+
185
+ # Fallback
186
+ return {"isError": False, "content": response}
187
+
188
+ def _extract_mcp_content(self, content_list: Any) -> Any:
189
+ """
190
+ Extract content from MCP content format.
191
+
192
+ Handles the standard MCP content format where content is a list
193
+ of content items with type and data.
194
+ """
195
+ if not isinstance(content_list, list) or not content_list:
196
+ return content_list
197
+
198
+ # Handle single content item
199
+ if len(content_list) == 1:
200
+ content_item = content_list[0]
201
+ if isinstance(content_item, dict):
202
+ if content_item.get("type") == "text":
203
+ text_content = content_item.get("text", "")
204
+ # Try to parse JSON, fall back to plain text
205
+ try:
206
+ import json
207
+
208
+ return json.loads(text_content)
209
+ except json.JSONDecodeError:
210
+ return text_content
211
+ else:
212
+ return content_item
213
+
214
+ # Multiple content items - return as-is
215
+ return content_list
216
+
217
+ # ------------------------------------------------------------------ #
218
+ # Standard string representation #
219
+ # ------------------------------------------------------------------ #
220
+ def __repr__(self) -> str:
221
+ """Standard string representation for all transports."""
222
+ status = "initialized" if getattr(self, "_initialized", False) else "not initialized"
223
+
224
+ # Add metrics info if available
225
+ metrics_info = ""
226
+ if hasattr(self, "enable_metrics") and getattr(self, "enable_metrics", False):
227
+ metrics = self.get_metrics()
228
+ if metrics.get("total_calls", 0) > 0:
229
+ success_rate = (metrics.get("successful_calls", 0) / metrics["total_calls"]) * 100
230
+ metrics_info = f", calls: {metrics['total_calls']}, success: {success_rate:.1f}%"
231
+
232
+ # Add transport-specific info - FIXED FORMAT
233
+ transport_info = ""
234
+ if hasattr(self, "url"):
235
+ transport_info = f", url={self.url}" # Fixed: was "url: "
236
+ elif hasattr(self, "server_params") and hasattr(self.server_params, "command"):
237
+ transport_info = f", command={self.server_params.command}" # Fixed: was "command: "
238
+
239
+ return f"{self.__class__.__name__}(status={status}{transport_info}{metrics_info})"