chuk-tool-processor 0.1.1__py3-none-any.whl → 0.1.3__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.
@@ -1 +0,0 @@
1
- # chuk_tool_processor/__init__.py
@@ -72,14 +72,14 @@ class ToolProcessor:
72
72
 
73
73
  # Apply optional wrappers
74
74
  if enable_retries:
75
- self.logger.info("Enabling retry logic")
75
+ self.logger.debug("Enabling retry logic")
76
76
  self.executor = RetryableToolExecutor(
77
77
  executor=self.executor,
78
78
  default_config=RetryConfig(max_retries=max_retries)
79
79
  )
80
80
 
81
81
  if enable_rate_limiting:
82
- self.logger.info("Enabling rate limiting")
82
+ self.logger.debug("Enabling rate limiting")
83
83
  rate_limiter = RateLimiter(
84
84
  global_limit=global_rate_limit,
85
85
  tool_limits=tool_rate_limits
@@ -90,7 +90,7 @@ class ToolProcessor:
90
90
  )
91
91
 
92
92
  if enable_caching:
93
- self.logger.info("Enabling result caching")
93
+ self.logger.debug("Enabling result caching")
94
94
  cache = InMemoryCache(default_ttl=cache_ttl)
95
95
  self.executor = CachingToolExecutor(
96
96
  executor=self.executor,
@@ -116,7 +116,7 @@ class ToolProcessor:
116
116
  for name in parser_names
117
117
  ]
118
118
 
119
- self.logger.info(f"Initialized with {len(self.parsers)} parser plugins")
119
+ self.logger.debug(f"Initialized with {len(self.parsers)} parser plugins")
120
120
 
121
121
  async def process_text(
122
122
  self,
@@ -139,16 +139,16 @@ class ToolProcessor:
139
139
  """
140
140
  # Create request context
141
141
  with request_logging(request_id) as req_id:
142
- self.logger.info(f"Processing text ({len(text)} chars)")
142
+ self.logger.debug(f"Processing text ({len(text)} chars)")
143
143
 
144
144
  # Extract tool calls
145
145
  calls = await self._extract_tool_calls(text)
146
146
 
147
147
  if not calls:
148
- self.logger.info("No tool calls found")
148
+ self.logger.debug("No tool calls found")
149
149
  return []
150
150
 
151
- self.logger.info(f"Found {len(calls)} tool calls")
151
+ self.logger.debug(f"Found {len(calls)} tool calls")
152
152
 
153
153
  # Execute tool calls
154
154
  with log_context_span("tool_execution", {"num_calls": len(calls)}):
@@ -103,7 +103,7 @@ class RetryableToolExecutor:
103
103
  if result.error:
104
104
  last_error = result.error
105
105
  if config.should_retry(attempt, error_str=result.error):
106
- logger.info(
106
+ logger.debug(
107
107
  f"Retrying tool {call.tool} after error: {result.error} (attempt {attempt + 1})"
108
108
  )
109
109
  await asyncio.sleep(config.get_delay(attempt))
@@ -6,14 +6,12 @@ Other modules can continue to import:
6
6
 
7
7
  from chuk_tool_processor.logging import get_logger, log_context_span, ...
8
8
  """
9
-
10
9
  from __future__ import annotations
11
- import logging
12
- import sys
10
+ import logging, sys
13
11
 
14
12
  from .formatter import StructuredFormatter
15
- from .context import get_logger, log_context, StructuredAdapter
16
- from .helpers import log_context_span, request_logging, log_tool_call, metrics
13
+ from .context import get_logger, log_context, StructuredAdapter
14
+ from .helpers import log_context_span, request_logging, log_tool_call, metrics
17
15
 
18
16
  __all__ = [
19
17
  "get_logger",
@@ -27,9 +25,9 @@ __all__ = [
27
25
  # root logger & handler wiring (done once at import time)
28
26
  # --------------------------------------------------------------------------- #
29
27
  root_logger = logging.getLogger("chuk_tool_processor")
30
- root_logger.setLevel(logging.INFO)
28
+ root_logger.setLevel(logging.WARNING) # ← quieter default
31
29
 
32
30
  _handler = logging.StreamHandler(sys.stderr)
33
- _handler.setLevel(logging.INFO)
31
+ _handler.setLevel(logging.WARNING) # match the logger
34
32
  _handler.setFormatter(StructuredFormatter())
35
33
  root_logger.addHandler(_handler)
@@ -0,0 +1,21 @@
1
+ # chuk_tool_processor/mcp/__init__.py
2
+ """
3
+ MCP integration for CHUK Tool Processor.
4
+ """
5
+ from chuk_tool_processor.mcp.transport import MCPBaseTransport, StdioTransport, SSETransport
6
+ from chuk_tool_processor.mcp.stream_manager import StreamManager
7
+ from chuk_tool_processor.mcp.mcp_tool import MCPTool
8
+ from chuk_tool_processor.mcp.register_mcp_tools import register_mcp_tools
9
+ from chuk_tool_processor.mcp.setup_mcp_stdio import setup_mcp_stdio
10
+ from chuk_tool_processor.mcp.setup_mcp_sse import setup_mcp_sse
11
+
12
+ __all__ = [
13
+ "MCPBaseTransport",
14
+ "StdioTransport",
15
+ "SSETransport",
16
+ "StreamManager",
17
+ "MCPTool",
18
+ "register_mcp_tools",
19
+ "setup_mcp_stdio",
20
+ "setup_mcp_sse"
21
+ ]
@@ -0,0 +1,53 @@
1
+ # chuk_tool_processor/mcp/mcp_tool.py
2
+ """
3
+ MCP tool that uses StreamManager for execution.
4
+ """
5
+
6
+ from typing import Any
7
+
8
+ from chuk_tool_processor.mcp.stream_manager import StreamManager
9
+ from chuk_tool_processor.logging import get_logger
10
+
11
+ logger = get_logger("chuk_tool_processor.mcp.mcp_tool")
12
+
13
+ class MCPTool:
14
+ """
15
+ MCP tool that uses StreamManager for execution.
16
+
17
+ This tool handles both namespaced and non-namespaced execution.
18
+ """
19
+
20
+ def __init__(self, tool_name: str, stream_manager: StreamManager):
21
+ """
22
+ Initialize the MCP tool.
23
+
24
+ Args:
25
+ tool_name: Name of the MCP tool
26
+ stream_manager: StreamManager instance
27
+ """
28
+ self.tool_name = tool_name
29
+ self.stream_manager = stream_manager
30
+
31
+ async def execute(self, **kwargs: Any) -> Any:
32
+ """
33
+ Execute the tool using StreamManager.
34
+
35
+ Args:
36
+ **kwargs: Tool arguments
37
+
38
+ Returns:
39
+ Tool result
40
+ """
41
+ logger.debug(f"Executing MCP tool {self.tool_name}")
42
+
43
+ result = await self.stream_manager.call_tool(
44
+ tool_name=self.tool_name,
45
+ arguments=kwargs
46
+ )
47
+
48
+ if result.get("isError"):
49
+ error_msg = result.get("error", "Unknown error")
50
+ logger.error(f"Error executing MCP tool {self.tool_name}: {error_msg}")
51
+ raise RuntimeError(error_msg)
52
+
53
+ return result.get("content")
@@ -0,0 +1,82 @@
1
+ # chuk_tool_processor/mcp/register_mcp_tools.py
2
+ """
3
+ Registration functions for MCP tools.
4
+ """
5
+
6
+ from typing import List, Dict, Any
7
+
8
+ from chuk_tool_processor.mcp.mcp_tool import MCPTool
9
+ from chuk_tool_processor.mcp.stream_manager import StreamManager
10
+ from chuk_tool_processor.registry.provider import ToolRegistryProvider
11
+ from chuk_tool_processor.logging import get_logger
12
+
13
+ logger = get_logger("chuk_tool_processor.mcp.register")
14
+
15
+
16
+ def register_mcp_tools(
17
+ stream_manager: StreamManager,
18
+ namespace: str = "mcp"
19
+ ) -> List[str]:
20
+ """
21
+ Register MCP tools with the CHUK registry.
22
+
23
+ Args:
24
+ stream_manager: StreamManager instance
25
+ namespace: Namespace for the tools
26
+
27
+ Returns:
28
+ List of registered tool names
29
+ """
30
+ registry = ToolRegistryProvider.get_registry()
31
+ registered_tools = []
32
+
33
+ # Get all tools from StreamManager
34
+ mcp_tools = stream_manager.get_all_tools()
35
+
36
+ for tool_def in mcp_tools:
37
+ tool_name = tool_def.get("name")
38
+ if not tool_name:
39
+ logger.warning("Tool definition missing name")
40
+ continue
41
+
42
+ description = tool_def.get("description", f"MCP tool: {tool_name}")
43
+
44
+ try:
45
+ # Create tool
46
+ tool = MCPTool(tool_name, stream_manager)
47
+
48
+ # Register with registry under the original name in the given namespace
49
+ registry.register_tool(
50
+ tool,
51
+ name=tool_name,
52
+ namespace=namespace,
53
+ metadata={
54
+ "description": description,
55
+ "is_async": True,
56
+ "tags": {"mcp", "remote"},
57
+ "argument_schema": tool_def.get("inputSchema", {})
58
+ }
59
+ )
60
+
61
+ # Also register the tool in the default namespace with the namespaced name
62
+ # This allows calling the tool as either "echo" or "stdio.echo" from parsers
63
+ namespaced_tool_name = f"{namespace}.{tool_name}"
64
+ registry.register_tool(
65
+ tool,
66
+ name=namespaced_tool_name,
67
+ namespace="default",
68
+ metadata={
69
+ "description": description,
70
+ "is_async": True,
71
+ "tags": {"mcp", "remote", "namespaced"},
72
+ "argument_schema": tool_def.get("inputSchema", {})
73
+ }
74
+ )
75
+
76
+ registered_tools.append(tool_name)
77
+ logger.info(f"Registered MCP tool '{tool_name}' in namespace '{namespace}' (also as '{namespaced_tool_name}' in default)")
78
+
79
+ except Exception as e:
80
+ logger.error(f"Error registering MCP tool '{tool_name}': {e}")
81
+
82
+ return registered_tools
@@ -0,0 +1,74 @@
1
+ # chuk_tool_processor/mcp/setup_mcp_sse.py
2
+ """
3
+ Setup function for SSE transport MCP integration.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from typing import Dict, List, Optional
8
+
9
+ from chuk_tool_processor.core.processor import ToolProcessor
10
+ from chuk_tool_processor.mcp.stream_manager import StreamManager
11
+ from chuk_tool_processor.mcp.register_mcp_tools import register_mcp_tools
12
+ from chuk_tool_processor.logging import get_logger
13
+
14
+ logger = get_logger("chuk_tool_processor.mcp.setup_sse")
15
+
16
+
17
+ async def setup_mcp_sse(
18
+ servers: List[Dict[str, str]],
19
+ server_names: Optional[Dict[int, str]] = None,
20
+ default_timeout: float = 10.0,
21
+ max_concurrency: Optional[int] = None,
22
+ enable_caching: bool = True,
23
+ cache_ttl: int = 300,
24
+ enable_rate_limiting: bool = False,
25
+ global_rate_limit: Optional[int] = None,
26
+ tool_rate_limits: Optional[Dict[str, tuple]] = None,
27
+ enable_retries: bool = True,
28
+ max_retries: int = 3,
29
+ namespace: str = "mcp"
30
+ ) -> tuple[ToolProcessor, StreamManager]:
31
+ """
32
+ Set up MCP with SSE transport and CHUK Tool Processor.
33
+
34
+ Args:
35
+ servers: List of server configurations with "name" and "url" keys
36
+ server_names: Optional mapping of server indices to names
37
+ default_timeout: Default timeout for tool execution
38
+ max_concurrency: Maximum concurrent executions
39
+ enable_caching: Whether to enable caching
40
+ cache_ttl: Cache TTL in seconds
41
+ enable_rate_limiting: Whether to enable rate limiting
42
+ global_rate_limit: Global rate limit (requests per minute)
43
+ tool_rate_limits: Per-tool rate limits
44
+ enable_retries: Whether to enable retries
45
+ max_retries: Maximum retry attempts
46
+ namespace: Namespace for MCP tools
47
+
48
+ Returns:
49
+ Tuple of (processor, stream_manager)
50
+ """
51
+ # Create and initialize StreamManager with SSE transport
52
+ stream_manager = await StreamManager.create_with_sse(
53
+ servers=servers,
54
+ server_names=server_names
55
+ )
56
+
57
+ # Register MCP tools
58
+ registered_tools = register_mcp_tools(stream_manager, namespace)
59
+
60
+ # Create processor
61
+ processor = ToolProcessor(
62
+ default_timeout=default_timeout,
63
+ max_concurrency=max_concurrency,
64
+ enable_caching=enable_caching,
65
+ cache_ttl=cache_ttl,
66
+ enable_rate_limiting=enable_rate_limiting,
67
+ global_rate_limit=global_rate_limit,
68
+ tool_rate_limits=tool_rate_limits,
69
+ enable_retries=enable_retries,
70
+ max_retries=max_retries
71
+ )
72
+
73
+ logger.info(f"Set up MCP (SSE) with {len(registered_tools)} tools")
74
+ return processor, stream_manager
@@ -0,0 +1,78 @@
1
+ # chuk_tool_processor/mcp/setup_mcp_stdio.py
2
+ """
3
+ Setup function for stdio transport MCP integration.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from typing import Dict, List, Optional
8
+
9
+ from chuk_tool_processor.core.processor import ToolProcessor
10
+ from chuk_tool_processor.mcp.stream_manager import StreamManager
11
+ from chuk_tool_processor.mcp.register_mcp_tools import register_mcp_tools
12
+ from chuk_tool_processor.logging import get_logger
13
+
14
+ logger = get_logger("chuk_tool_processor.mcp.setup_stdio")
15
+
16
+
17
+ async def setup_mcp_stdio(
18
+ config_file: str,
19
+ servers: List[str],
20
+ server_names: Optional[Dict[int, str]] = None,
21
+ default_timeout: float = 10.0,
22
+ max_concurrency: Optional[int] = None,
23
+ enable_caching: bool = True,
24
+ cache_ttl: int = 300,
25
+ enable_rate_limiting: bool = False,
26
+ global_rate_limit: Optional[int] = None,
27
+ tool_rate_limits: Optional[Dict[str, tuple]] = None,
28
+ enable_retries: bool = True,
29
+ max_retries: int = 3,
30
+ namespace: str = "mcp"
31
+ ) -> tuple[ToolProcessor, StreamManager]:
32
+ """
33
+ Set up MCP with stdio transport and CHUK Tool Processor.
34
+
35
+ Args:
36
+ config_file: Path to the config file
37
+ servers: List of server names to connect to
38
+ server_names: Optional mapping of server indices to names
39
+ default_timeout: Default timeout for tool execution
40
+ max_concurrency: Maximum concurrent executions
41
+ enable_caching: Whether to enable caching
42
+ cache_ttl: Cache TTL in seconds
43
+ enable_rate_limiting: Whether to enable rate limiting
44
+ global_rate_limit: Global rate limit (requests per minute)
45
+ tool_rate_limits: Per-tool rate limits
46
+ enable_retries: Whether to enable retries
47
+ max_retries: Maximum retry attempts
48
+ namespace: Namespace for MCP tools
49
+
50
+ Returns:
51
+ Tuple of (processor, stream_manager)
52
+ """
53
+ # Create and initialize StreamManager with stdio transport
54
+ stream_manager = await StreamManager.create(
55
+ config_file=config_file,
56
+ servers=servers,
57
+ server_names=server_names,
58
+ transport_type="stdio"
59
+ )
60
+
61
+ # Register MCP tools
62
+ registered_tools = register_mcp_tools(stream_manager, namespace)
63
+
64
+ # Create processor
65
+ processor = ToolProcessor(
66
+ default_timeout=default_timeout,
67
+ max_concurrency=max_concurrency,
68
+ enable_caching=enable_caching,
69
+ cache_ttl=cache_ttl,
70
+ enable_rate_limiting=enable_rate_limiting,
71
+ global_rate_limit=global_rate_limit,
72
+ tool_rate_limits=tool_rate_limits,
73
+ enable_retries=enable_retries,
74
+ max_retries=max_retries
75
+ )
76
+
77
+ logger.info(f"Set up MCP (stdio) with {len(registered_tools)} tools")
78
+ return processor, stream_manager
@@ -0,0 +1,303 @@
1
+ # chuk_tool_processor/mcp/stream_manager.py
2
+ """
3
+ StreamManager for CHUK Tool Processor.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import asyncio
8
+ import json
9
+ from typing import Dict, List, Optional, Any
10
+
11
+ # tool processor imports
12
+ from chuk_mcp.config import load_config
13
+ from chuk_tool_processor.mcp.transport import MCPBaseTransport, StdioTransport, SSETransport
14
+ from chuk_tool_processor.logging import get_logger
15
+
16
+ # logger
17
+ logger = get_logger("chuk_tool_processor.mcp.stream_manager")
18
+
19
+ class StreamManager:
20
+ """
21
+ Manager for MCP server streams with support for multiple transport types.
22
+ """
23
+
24
+ def __init__(self):
25
+ """Initialize the StreamManager."""
26
+ self.transports: Dict[str, MCPBaseTransport] = {}
27
+ self.server_info: List[Dict[str, Any]] = []
28
+ self.tool_to_server_map: Dict[str, str] = {}
29
+ self.server_names: Dict[int, str] = {}
30
+ self.all_tools: List[Dict[str, Any]] = []
31
+ self._lock = asyncio.Lock()
32
+
33
+ @classmethod
34
+ async def create(
35
+ cls,
36
+ config_file: str,
37
+ servers: List[str],
38
+ server_names: Optional[Dict[int, str]] = None,
39
+ transport_type: str = "stdio"
40
+ ) -> StreamManager:
41
+ """
42
+ Create and initialize a StreamManager.
43
+
44
+ Args:
45
+ config_file: Path to the config file
46
+ servers: List of server names to connect to
47
+ server_names: Optional mapping of server indices to names
48
+ transport_type: Transport type ("stdio" or "sse")
49
+
50
+ Returns:
51
+ Initialized StreamManager
52
+ """
53
+ manager = cls()
54
+ await manager.initialize(config_file, servers, server_names, transport_type)
55
+ return manager
56
+
57
+ @classmethod
58
+ async def create_with_sse(
59
+ cls,
60
+ servers: List[Dict[str, str]],
61
+ server_names: Optional[Dict[int, str]] = None
62
+ ) -> StreamManager:
63
+ """
64
+ Create and initialize a StreamManager with SSE transport.
65
+
66
+ Args:
67
+ servers: List of server configurations with "name" and "url" keys
68
+ server_names: Optional mapping of server indices to names
69
+
70
+ Returns:
71
+ Initialized StreamManager
72
+ """
73
+ manager = cls()
74
+ await manager.initialize_with_sse(servers, server_names)
75
+ return manager
76
+
77
+ async def initialize(
78
+ self,
79
+ config_file: str,
80
+ servers: List[str],
81
+ server_names: Optional[Dict[int, str]] = None,
82
+ transport_type: str = "stdio"
83
+ ) -> None:
84
+ """
85
+ Initialize the StreamManager.
86
+
87
+ Args:
88
+ config_file: Path to the config file
89
+ servers: List of server names to connect to
90
+ server_names: Optional mapping of server indices to names
91
+ transport_type: Transport type ("stdio" or "sse")
92
+ """
93
+ async with self._lock:
94
+ # Store server names mapping
95
+ self.server_names = server_names or {}
96
+
97
+ # Initialize servers
98
+ for i, server_name in enumerate(servers):
99
+ try:
100
+ if transport_type == "stdio":
101
+ # Load configuration
102
+ server_params = await load_config(config_file, server_name)
103
+
104
+ # Create transport
105
+ transport = StdioTransport(server_params)
106
+ elif transport_type == "sse":
107
+ # For SSE, we would parse the config differently
108
+ # This is just a placeholder
109
+ transport = SSETransport("http://localhost:8000")
110
+ else:
111
+ logger.error(f"Unsupported transport type: {transport_type}")
112
+ continue
113
+
114
+ # Initialize transport
115
+ if not await transport.initialize():
116
+ logger.error(f"Failed to initialize transport for server: {server_name}")
117
+ continue
118
+
119
+ # Store transport
120
+ self.transports[server_name] = transport
121
+
122
+ # Check server is responsive
123
+ ping_result = await transport.send_ping()
124
+ status = "Up" if ping_result else "Down"
125
+
126
+ # Get available tools
127
+ tools = await transport.get_tools()
128
+
129
+ # Map tools to server
130
+ for tool in tools:
131
+ tool_name = tool.get("name")
132
+ if tool_name:
133
+ self.tool_to_server_map[tool_name] = server_name
134
+
135
+ # Add to all tools
136
+ self.all_tools.extend(tools)
137
+
138
+ # Add server info
139
+ self.server_info.append({
140
+ "id": i,
141
+ "name": server_name,
142
+ "tools": len(tools),
143
+ "status": status
144
+ })
145
+
146
+ logger.info(f"Initialized server {server_name} with {len(tools)} tools")
147
+
148
+ except Exception as e:
149
+ logger.error(f"Error initializing server {server_name}: {e}")
150
+
151
+ logger.info(f"StreamManager initialized with {len(self.transports)} servers and {len(self.all_tools)} tools")
152
+
153
+ async def initialize_with_sse(
154
+ self,
155
+ servers: List[Dict[str, str]],
156
+ server_names: Optional[Dict[int, str]] = None
157
+ ) -> None:
158
+ """
159
+ Initialize the StreamManager with SSE transport.
160
+
161
+ Args:
162
+ servers: List of server configurations with "name" and "url" keys
163
+ server_names: Optional mapping of server indices to names
164
+ """
165
+ async with self._lock:
166
+ # Store server names mapping
167
+ self.server_names = server_names or {}
168
+
169
+ # Initialize servers
170
+ for i, server_config in enumerate(servers):
171
+ server_name = server_config.get("name")
172
+ url = server_config.get("url")
173
+ api_key = server_config.get("api_key")
174
+
175
+ if not server_name or not url:
176
+ logger.error(f"Invalid server configuration: {server_config}")
177
+ continue
178
+
179
+ try:
180
+ # Create transport
181
+ transport = SSETransport(url, api_key)
182
+
183
+ # Initialize transport
184
+ if not await transport.initialize():
185
+ logger.error(f"Failed to initialize SSE transport for server: {server_name}")
186
+ continue
187
+
188
+ # Store transport
189
+ self.transports[server_name] = transport
190
+
191
+ # Check server is responsive
192
+ ping_result = await transport.send_ping()
193
+ status = "Up" if ping_result else "Down"
194
+
195
+ # Get available tools
196
+ tools = await transport.get_tools()
197
+
198
+ # Map tools to server
199
+ for tool in tools:
200
+ tool_name = tool.get("name")
201
+ if tool_name:
202
+ self.tool_to_server_map[tool_name] = server_name
203
+
204
+ # Add to all tools
205
+ self.all_tools.extend(tools)
206
+
207
+ # Add server info
208
+ self.server_info.append({
209
+ "id": i,
210
+ "name": server_name,
211
+ "tools": len(tools),
212
+ "status": status
213
+ })
214
+
215
+ logger.info(f"Initialized SSE server {server_name} with {len(tools)} tools")
216
+
217
+ except Exception as e:
218
+ logger.error(f"Error initializing SSE server {server_name}: {e}")
219
+
220
+ logger.info(f"StreamManager initialized with {len(self.transports)} SSE servers and {len(self.all_tools)} tools")
221
+
222
+ def get_all_tools(self) -> List[Dict[str, Any]]:
223
+ """
224
+ Get all available tools.
225
+
226
+ Returns:
227
+ List of tool definitions
228
+ """
229
+ return self.all_tools
230
+
231
+ def get_server_for_tool(self, tool_name: str) -> Optional[str]:
232
+ """
233
+ Get the server name for a tool.
234
+
235
+ Args:
236
+ tool_name: Tool name
237
+
238
+ Returns:
239
+ Server name or None if not found
240
+ """
241
+ return self.tool_to_server_map.get(tool_name)
242
+
243
+ def get_server_info(self) -> List[Dict[str, Any]]:
244
+ """
245
+ Get information about all servers.
246
+
247
+ Returns:
248
+ List of server info dictionaries
249
+ """
250
+ return self.server_info
251
+
252
+ async def call_tool(
253
+ self,
254
+ tool_name: str,
255
+ arguments: Dict[str, Any],
256
+ server_name: Optional[str] = None
257
+ ) -> Dict[str, Any]:
258
+ """
259
+ Call a tool.
260
+
261
+ Args:
262
+ tool_name: Tool name
263
+ arguments: Tool arguments
264
+ server_name: Optional server name override
265
+
266
+ Returns:
267
+ Tool result
268
+ """
269
+ # Get server name
270
+ if not server_name:
271
+ server_name = self.get_server_for_tool(tool_name)
272
+
273
+ if not server_name or server_name not in self.transports:
274
+ return {
275
+ "isError": True,
276
+ "error": f"No server found for tool: {tool_name}"
277
+ }
278
+
279
+ # Get transport
280
+ transport = self.transports[server_name]
281
+
282
+ # Call tool
283
+ return await transport.call_tool(tool_name, arguments)
284
+
285
+ async def close(self) -> None:
286
+ """Close all transports."""
287
+ close_tasks = []
288
+ for name, transport in self.transports.items():
289
+ close_tasks.append(transport.close())
290
+
291
+ if close_tasks:
292
+ try:
293
+ await asyncio.gather(*close_tasks)
294
+ except asyncio.CancelledError:
295
+ # Ignore cancellation during cleanup
296
+ pass
297
+ except Exception as e:
298
+ logger.error(f"Error closing transports: {e}")
299
+
300
+ self.transports.clear()
301
+ self.server_info.clear()
302
+ self.tool_to_server_map.clear()
303
+ self.all_tools.clear()
@@ -0,0 +1,14 @@
1
+ # chuk_tool_processor/mcp/transport/__init__.py
2
+ """
3
+ MCP transport implementations.
4
+ """
5
+
6
+ from .base_transport import MCPBaseTransport
7
+ from .stdio_transport import StdioTransport
8
+ from .sse_transport import SSETransport
9
+
10
+ __all__ = [
11
+ "MCPBaseTransport",
12
+ "StdioTransport",
13
+ "SSETransport"
14
+ ]
@@ -0,0 +1,64 @@
1
+ # chuk_tool_processor/mcp/transport/base_transport.py
2
+ """
3
+ Abstract transport layer for MCP communication.
4
+ """
5
+ from abc import ABC, abstractmethod
6
+ from typing import Any, Dict, List
7
+
8
+ class MCPBaseTransport(ABC):
9
+ """
10
+ Abstract base class for MCP transport mechanisms.
11
+ """
12
+
13
+ @abstractmethod
14
+ async def initialize(self) -> bool:
15
+ """
16
+ Initialize the transport connection.
17
+
18
+ Returns:
19
+ True if successful, False otherwise
20
+ """
21
+ pass
22
+
23
+ @abstractmethod
24
+ async def send_ping(self) -> bool:
25
+ """
26
+ Send a ping message.
27
+
28
+ Returns:
29
+ True if successful, False otherwise
30
+ """
31
+ pass
32
+
33
+ @abstractmethod
34
+ async def get_tools(self) -> List[Dict[str, Any]]:
35
+ """
36
+ Get available tools.
37
+
38
+ Returns:
39
+ List of tool definitions
40
+ """
41
+ pass
42
+
43
+ @abstractmethod
44
+ async def call_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
45
+ """
46
+ Call a tool.
47
+
48
+ Args:
49
+ tool_name: Tool name
50
+ arguments: Tool arguments
51
+
52
+ Returns:
53
+ Tool result
54
+ """
55
+ pass
56
+
57
+ @abstractmethod
58
+ async def close(self) -> None:
59
+ """Close the transport connection."""
60
+ pass
61
+
62
+
63
+
64
+
@@ -0,0 +1,59 @@
1
+ # chuk_tool_processor/mcp/transport/sse_transport.py
2
+ """
3
+ Server-Sent Events (SSE) transport for MCP communication.
4
+ """
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ # imports
8
+ from .base_transport import MCPBaseTransport
9
+
10
+ class SSETransport(MCPBaseTransport):
11
+ """
12
+ Server-Sent Events (SSE) transport for MCP communication.
13
+ """
14
+
15
+ def __init__(self, url: str, api_key: Optional[str] = None):
16
+ """
17
+ Initialize the SSE transport.
18
+
19
+ Args:
20
+ url: Server URL
21
+ api_key: Optional API key
22
+ """
23
+ self.url = url
24
+ self.api_key = api_key
25
+ self.session = None
26
+ self.connection_id = None
27
+
28
+ async def initialize(self) -> bool:
29
+ """
30
+ Initialize the SSE connection.
31
+
32
+ Returns:
33
+ True if successful, False otherwise
34
+ """
35
+ # TODO: Implement SSE connection logic
36
+ # This is currently a placeholder
37
+ import logging
38
+ logging.info(f"SSE transport not yet implemented for {self.url}")
39
+ return False
40
+
41
+ async def send_ping(self) -> bool:
42
+ """Send a ping message."""
43
+ # TODO: Implement SSE ping logic
44
+ return False
45
+
46
+ async def get_tools(self) -> List[Dict[str, Any]]:
47
+ """Get available tools."""
48
+ # TODO: Implement SSE tool retrieval logic
49
+ return []
50
+
51
+ async def call_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
52
+ """Call a tool via SSE."""
53
+ # TODO: Implement SSE tool calling logic
54
+ return {"isError": True, "error": "SSE transport not implemented"}
55
+
56
+ async def close(self) -> None:
57
+ """Close the SSE connection."""
58
+ # TODO: Implement SSE connection closure logic
59
+ pass
@@ -0,0 +1,125 @@
1
+ # chuk_tool_processor/mcp/transport/stdio_transport.py
2
+ from typing import Dict, Any, List, Optional
3
+ from contextlib import AsyncExitStack
4
+ import json
5
+
6
+ from .base_transport import MCPBaseTransport
7
+
8
+ # chuk-protocol imports
9
+ from chuk_mcp.mcp_client.transport.stdio.stdio_client import stdio_client
10
+ from chuk_mcp.mcp_client.messages.initialize.send_messages import send_initialize
11
+ from chuk_mcp.mcp_client.messages.ping.send_messages import send_ping
12
+ from chuk_mcp.mcp_client.messages.tools.send_messages import send_tools_call, send_tools_list
13
+
14
+
15
+ class StdioTransport(MCPBaseTransport):
16
+ """
17
+ Stdio transport for MCP communication.
18
+ """
19
+
20
+ def __init__(self, server_params):
21
+ self.server_params = server_params
22
+ self.read_stream = None
23
+ self.write_stream = None
24
+ self._context_stack: Optional[AsyncExitStack] = None
25
+
26
+ # --------------------------------------------------------------------- #
27
+ # Connection management #
28
+ # --------------------------------------------------------------------- #
29
+ async def initialize(self) -> bool:
30
+ try:
31
+ self._context_stack = AsyncExitStack()
32
+ await self._context_stack.__aenter__()
33
+
34
+ ctx = stdio_client(self.server_params)
35
+ self.read_stream, self.write_stream = await self._context_stack.enter_async_context(ctx)
36
+
37
+ init_result = await send_initialize(self.read_stream, self.write_stream)
38
+ return bool(init_result)
39
+
40
+ except Exception as e: # pragma: no cover
41
+ import logging
42
+
43
+ logging.error(f"Error initializing stdio transport: {e}")
44
+ if self._context_stack:
45
+ try:
46
+ await self._context_stack.__aexit__(None, None, None)
47
+ except Exception:
48
+ pass
49
+ return False
50
+
51
+ async def close(self) -> None:
52
+ if self._context_stack:
53
+ try:
54
+ await self._context_stack.__aexit__(None, None, None)
55
+ except Exception:
56
+ pass
57
+ self.read_stream = None
58
+ self.write_stream = None
59
+ self._context_stack = None
60
+
61
+ # --------------------------------------------------------------------- #
62
+ # Utility #
63
+ # --------------------------------------------------------------------- #
64
+ async def send_ping(self) -> bool:
65
+ if not self.read_stream or not self.write_stream:
66
+ return False
67
+ return await send_ping(self.read_stream, self.write_stream)
68
+
69
+ async def get_tools(self) -> List[Dict[str, Any]]:
70
+ if not self.read_stream or not self.write_stream:
71
+ return []
72
+ tools_response = await send_tools_list(self.read_stream, self.write_stream)
73
+ return tools_response.get("tools", [])
74
+
75
+ # --------------------------------------------------------------------- #
76
+ # Main entry-point #
77
+ # --------------------------------------------------------------------- #
78
+ async def call_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
79
+ """
80
+ Execute *tool_name* with *arguments* and normalise the server’s reply.
81
+
82
+ The echo-server often returns:
83
+ {
84
+ "content": [{"type":"text","text":"{\"message\":\"…\"}"}],
85
+ "isError": false
86
+ }
87
+ We unwrap that so callers just receive either a dict or a plain string.
88
+ """
89
+ if not self.read_stream or not self.write_stream:
90
+ return {"isError": True, "error": "Transport not initialized"}
91
+
92
+ try:
93
+ raw = await send_tools_call(self.read_stream, self.write_stream, tool_name, arguments)
94
+
95
+ # Handle explicit error wrapper
96
+ if "error" in raw:
97
+ return {"isError": True,
98
+ "error": raw["error"].get("message", "Unknown error")}
99
+
100
+ # Preferred: servers that put the answer under "result"
101
+ if "result" in raw:
102
+ return {"isError": False, "content": raw["result"]}
103
+
104
+ # Common echo-server shape: top-level "content" list
105
+ if "content" in raw:
106
+ clist = raw["content"]
107
+ if isinstance(clist, list) and clist:
108
+ first = clist[0]
109
+ if isinstance(first, dict) and first.get("type") == "text":
110
+ text = first.get("text", "")
111
+ # Try to parse as JSON; fall back to plain string
112
+ try:
113
+ parsed = json.loads(text)
114
+ return {"isError": False, "content": parsed}
115
+ except json.JSONDecodeError:
116
+ return {"isError": False, "content": text}
117
+
118
+ # Fallback: give caller whatever the server sent
119
+ return {"isError": False, "content": raw}
120
+
121
+ except Exception as e: # pragma: no cover
122
+ import logging
123
+
124
+ logging.error(f"Error calling tool {tool_name}: {e}")
125
+ return {"isError": True, "error": str(e)}
@@ -1,9 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chuk-tool-processor
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
7
+ Requires-Dist: chuk-mcp>=0.1.12
7
8
  Requires-Dist: dotenv>=0.9.9
8
9
  Requires-Dist: openai>=1.76.0
9
10
  Requires-Dist: pydantic>=2.11.3
@@ -21,6 +22,7 @@ The CHUK Tool Processor is a Python library designed to handle the execution of
21
22
  2. **Executing tools** with proper isolation and error handling
22
23
  3. **Managing tool executions** with retry logic, caching, and rate limiting
23
24
  4. **Monitoring tool usage** with comprehensive logging
25
+ 5. **MCP (Model Context Protocol) Integration** for remote tool execution
24
26
 
25
27
  ## Features
26
28
 
@@ -34,6 +36,7 @@ The CHUK Tool Processor is a Python library designed to handle the execution of
34
36
  - **Retry Logic**: Automatically retry transient failures with exponential backoff
35
37
  - **Structured Logging**: Comprehensive logging system for debugging and monitoring
36
38
  - **Plugin Discovery**: Dynamically discover and load plugins from packages
39
+ - **MCP Integration**: Connect to and execute remote tools via Model Context Protocol
37
40
 
38
41
  ## Installation
39
42
 
@@ -101,6 +104,159 @@ if __name__ == "__main__":
101
104
  asyncio.run(main())
102
105
  ```
103
106
 
107
+ ## MCP Integration
108
+
109
+ The CHUK Tool Processor supports Model Context Protocol (MCP) for connecting to remote tool servers. This enables distributed tool execution and integration with third-party services.
110
+
111
+ ### MCP with Stdio Transport
112
+
113
+ ```python
114
+ import asyncio
115
+ from chuk_tool_processor.mcp import setup_mcp_stdio
116
+
117
+ async def main():
118
+ # Configure MCP server
119
+ config_file = "server_config.json"
120
+ servers = ["echo", "calculator", "search"]
121
+ server_names = {0: "echo", 1: "calculator", 2: "search"}
122
+
123
+ # Setup MCP with stdio transport
124
+ processor, stream_manager = await setup_mcp_stdio(
125
+ config_file=config_file,
126
+ servers=servers,
127
+ server_names=server_names,
128
+ namespace="mcp", # All tools will be registered under this namespace
129
+ enable_caching=True,
130
+ enable_retries=True
131
+ )
132
+
133
+ # Process text with MCP tool calls
134
+ llm_text = """
135
+ Let me echo your message using the MCP server.
136
+
137
+ <tool name="mcp.echo" args='{"message": "Hello from MCP!"}'/>
138
+ """
139
+
140
+ results = await processor.process_text(llm_text)
141
+
142
+ for result in results:
143
+ print(f"Tool: {result.tool}")
144
+ print(f"Result: {result.result}")
145
+
146
+ # Clean up
147
+ await stream_manager.close()
148
+
149
+ if __name__ == "__main__":
150
+ asyncio.run(main())
151
+ ```
152
+
153
+ ### MCP Server Configuration
154
+
155
+ Create a server configuration file (`server_config.json`):
156
+
157
+ ```json
158
+ {
159
+ "mcpServers": {
160
+ "echo": {
161
+ "command": "uv",
162
+ "args": ["--directory", "/path/to/echo-server", "run", "src/echo_server/main.py"]
163
+ },
164
+ "calculator": {
165
+ "command": "node",
166
+ "args": ["/path/to/calculator-server/index.js"]
167
+ },
168
+ "search": {
169
+ "command": "python",
170
+ "args": ["/path/to/search-server/main.py"]
171
+ }
172
+ }
173
+ }
174
+ ```
175
+
176
+ ### Namespaced Tool Access
177
+
178
+ MCP tools are automatically registered in both their namespace and the default namespace:
179
+
180
+ ```python
181
+ # These are equivalent:
182
+ <tool name="echo" args='{"message": "Hello"}'/>
183
+ <tool name="mcp.echo" args='{"message": "Hello"}'/>
184
+ ```
185
+
186
+ ### MCP with SSE Transport
187
+
188
+ ```python
189
+ import asyncio
190
+ from chuk_tool_processor.mcp import setup_mcp_sse
191
+
192
+ async def main():
193
+ # Configure SSE servers
194
+ sse_servers = [
195
+ {
196
+ "name": "weather",
197
+ "url": "https://api.example.com/sse/weather",
198
+ "api_key": "your_api_key"
199
+ },
200
+ {
201
+ "name": "geocoding",
202
+ "url": "https://api.example.com/sse/geocoding"
203
+ }
204
+ ]
205
+
206
+ # Setup MCP with SSE transport
207
+ processor, stream_manager = await setup_mcp_sse(
208
+ servers=sse_servers,
209
+ server_names={0: "weather", 1: "geocoding"},
210
+ namespace="remote",
211
+ enable_caching=True
212
+ )
213
+
214
+ # Process tool calls
215
+ llm_text = """
216
+ Get the weather for New York.
217
+
218
+ <tool name="remote.weather" args='{"location": "New York", "units": "imperial"}'/>
219
+ """
220
+
221
+ results = await processor.process_text(llm_text)
222
+
223
+ await stream_manager.close()
224
+ ```
225
+
226
+ ### MCP Stream Manager
227
+
228
+ The `StreamManager` class handles all MCP communication:
229
+
230
+ ```python
231
+ from chuk_tool_processor.mcp.stream_manager import StreamManager
232
+
233
+ # Create and initialize
234
+ stream_manager = await StreamManager.create(
235
+ config_file="config.json",
236
+ servers=["echo", "search"],
237
+ transport_type="stdio"
238
+ )
239
+
240
+ # Get available tools
241
+ tools = stream_manager.get_all_tools()
242
+ for tool in tools:
243
+ print(f"Tool: {tool['name']}")
244
+
245
+ # Get server information
246
+ server_info = stream_manager.get_server_info()
247
+ for server in server_info:
248
+ print(f"Server: {server['name']}, Status: {server['status']}")
249
+
250
+ # Call a tool directly
251
+ result = await stream_manager.call_tool(
252
+ tool_name="echo",
253
+ arguments={"message": "Hello"}
254
+ )
255
+
256
+ # Clean up
257
+ await stream_manager.close()
258
+ ```
259
+
104
260
  ## Advanced Usage
105
261
 
106
262
  ### Using Decorators for Tool Configuration
@@ -259,11 +415,16 @@ The tool processor has several key components organized into a modular structure
259
415
  - `plugins/discovery.py`: Plugin discovery mechanism
260
416
  - `plugins/parsers/`: Parser plugins for different formats
261
417
 
262
- 5. **Utils**: Shared utilities
418
+ 5. **MCP Integration**: Model Context Protocol support
419
+ - `mcp/stream_manager.py`: Manages MCP server connections
420
+ - `mcp/transport/`: Transport implementations (stdio, SSE)
421
+ - `mcp/setup_mcp_*.py`: Easy setup functions for MCP integration
422
+
423
+ 6. **Utils**: Shared utilities
263
424
  - `utils/logging.py`: Structured logging system
264
425
  - `utils/validation.py`: Argument and result validation
265
426
 
266
- 6. **Core**: Central components
427
+ 7. **Core**: Central components
267
428
  - `core/processor.py`: Main processor for handling tool calls
268
429
  - `core/exceptions.py`: Exception hierarchy
269
430
 
@@ -274,6 +435,8 @@ The repository includes several example scripts:
274
435
  - `examples/tool_registry_example.py`: Demonstrates tool registration and usage
275
436
  - `examples/plugin_example.py`: Shows how to create and use custom plugins
276
437
  - `examples/tool_calling_example_usage.py`: Basic example demonstrating tool execution
438
+ - `examples/mcp_stdio_example.py`: MCP stdio transport demonstration
439
+ - `examples/mcp_stdio_example_calling_usage.py`: Complete MCP integration example
277
440
 
278
441
  Run examples with:
279
442
 
@@ -287,6 +450,9 @@ uv run examples/plugin_example.py
287
450
  # Tool execution example
288
451
  uv run examples/tool_calling_example_usage.py
289
452
 
453
+ # MCP example
454
+ uv run examples/mcp_stdio_example.py
455
+
290
456
  # Enable debug logging
291
457
  LOGLEVEL=DEBUG uv run examples/tool_calling_example_usage.py
292
458
  ```
@@ -1,7 +1,7 @@
1
- chuk_tool_processor/__init__.py,sha256=a4pDi8hta4hjhijLGcv3vlWL8iu3F9EynkmB3si7-hg,33
1
+ chuk_tool_processor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  chuk_tool_processor/core/__init__.py,sha256=slM7pZna88tyZrF3KtN22ApYyCqGNt5Yscv-knsLOOA,38
3
3
  chuk_tool_processor/core/exceptions.py,sha256=h4zL1jpCY1Ud1wT8xDeMxZ8GR8ttmkObcv36peUHJEA,1571
4
- chuk_tool_processor/core/processor.py,sha256=RbHNFYpzn7hhjBXwqPOdRvTGWky4z8tT7k672nwRE0s,10158
4
+ chuk_tool_processor/core/processor.py,sha256=ud7ezONnUFh_aDSapiBGNx-LtZfhAFpYjFuw2m_tFXk,10165
5
5
  chuk_tool_processor/execution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  chuk_tool_processor/execution/tool_executor.py,sha256=e1EHE-744uJuB1XeZZF_6VT25Yg1RCd8XI3v8uOrOSo,1794
7
7
  chuk_tool_processor/execution/strategies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -10,12 +10,22 @@ chuk_tool_processor/execution/strategies/subprocess_strategy.py,sha256=Er8z7x94E
10
10
  chuk_tool_processor/execution/wrappers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  chuk_tool_processor/execution/wrappers/caching.py,sha256=dA2OULPQ9xCZj-r3ev5LtsCDFDPgoz8tr70YCX5A4Wg,7714
12
12
  chuk_tool_processor/execution/wrappers/rate_limiting.py,sha256=pFqD1vLzOtJzsWzpEI7J786gOAbdFY0gVeiO7ElBXbA,4991
13
- chuk_tool_processor/execution/wrappers/retry.py,sha256=tRIuT5iNAYUY3r9y3shouWCxJZB5VONkBC9qPBaaVdc,6386
14
- chuk_tool_processor/logging/__init__.py,sha256=YslBwMTCgxboYwnT4w5qX0_BnA0MR62eI8NH9le9G38,1057
13
+ chuk_tool_processor/execution/wrappers/retry.py,sha256=L3lebFWf_IL7DaAhTR541BVIxns7amm5msqYjJcQtnk,6387
14
+ chuk_tool_processor/logging/__init__.py,sha256=kow8qhzuCShXQSpQ4c6OZ0dEok18iMikcYLJTXEluoA,1124
15
15
  chuk_tool_processor/logging/context.py,sha256=hQFWGeraHX3DM28JDSiIuhQqep6TBfo1uaLlRRlGMVU,1521
16
16
  chuk_tool_processor/logging/formatter.py,sha256=4pO-fLULkD3JPLjZOSiOZPGsjV3c4Ztr5ySda1RAvi4,1754
17
17
  chuk_tool_processor/logging/helpers.py,sha256=Fk32BuecWZdyrmv8U0lh4W0AdNx4-gKcCaWBdId_rlI,3569
18
18
  chuk_tool_processor/logging/metrics.py,sha256=ti_owuslT-x9cjcbP-_j7jivrlyY-Vb41mVhU-6W-2M,1537
19
+ chuk_tool_processor/mcp/__init__.py,sha256=vR9HHxLpXlKTIIwJJRr3QTmZegcdedR1YKyb46j6FIM,689
20
+ chuk_tool_processor/mcp/mcp_tool.py,sha256=TvZEudgQvaev2jaPw6OGsqAR5GNu6_cPaUCgqiT5ogU,1504
21
+ chuk_tool_processor/mcp/register_mcp_tools.py,sha256=ofE7pEn6sKDH8HWvNamVOaXsitLOaG48M5GhcpqCBbs,2801
22
+ chuk_tool_processor/mcp/setup_mcp_sse.py,sha256=Ep2IKRdH1Y299bCxt9G0NtwnsvguYP6mpraZyUJ8OKU,2643
23
+ chuk_tool_processor/mcp/setup_mcp_stdio.py,sha256=NjTvAFqQHxxN3XubsTgYY3lTrvPVWlnwCzkzbz7WE_M,2747
24
+ chuk_tool_processor/mcp/stream_manager.py,sha256=xdTXDJ08pVpHXvxao0ibXezGqqauMBLXpJhIXgGknOs,10847
25
+ chuk_tool_processor/mcp/transport/__init__.py,sha256=7QQqeSKVKv0N9GcyJuYF0R4FDZeooii5RjggvFFg5GY,296
26
+ chuk_tool_processor/mcp/transport/base_transport.py,sha256=uJcbyHYrw_zpE5Rc9wDo6yT0mmwqwhFXXbHIJxPoOac,1379
27
+ chuk_tool_processor/mcp/transport/sse_transport.py,sha256=BcRRiOEDRiXiVK2rySB0Hm_dITDNHzrCd2h28Yv1r5c,1791
28
+ chuk_tool_processor/mcp/transport/stdio_transport.py,sha256=VxQYbN0jAyeOrQODZtTvityYRYUnbQHz3jc_eMTlv3I,5197
19
29
  chuk_tool_processor/models/__init__.py,sha256=TC__rdVa0lQsmJHM_hbLDPRgToa_pQT_UxRcPZk6iVw,40
20
30
  chuk_tool_processor/models/execution_strategy.py,sha256=ZPHysmKNHqJmahTtUXAbt1ke09vxy7EhZcsrwTdla8o,508
21
31
  chuk_tool_processor/models/tool_call.py,sha256=RZOnx2YczkJN6ym2PLiI4CRzP2qU_5hpMtHxMcFOxY4,298
@@ -41,7 +51,7 @@ chuk_tool_processor/registry/providers/__init__.py,sha256=_0dg4YhyfAV0TXuR_i4ewX
41
51
  chuk_tool_processor/registry/providers/memory.py,sha256=29aI5uvykjDmn9ymIukEdUtmTC9SXOAsDu9hw36XF44,4474
42
52
  chuk_tool_processor/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
53
  chuk_tool_processor/utils/validation.py,sha256=7ezn_o-3IHDrzOD3j6ttsAn2s3zS-jIjeBTuqicrs6A,3775
44
- chuk_tool_processor-0.1.1.dist-info/METADATA,sha256=roW8za1Cw1IKO3ZkzzHYVFzAeFZM2-b8CWKCCwa6Ag0,9300
45
- chuk_tool_processor-0.1.1.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
46
- chuk_tool_processor-0.1.1.dist-info/top_level.txt,sha256=7lTsnuRx4cOW4U2sNJWNxl4ZTt_J1ndkjTbj3pHPY5M,20
47
- chuk_tool_processor-0.1.1.dist-info/RECORD,,
54
+ chuk_tool_processor-0.1.3.dist-info/METADATA,sha256=lcYGBykoz2doY3Z8cR_SxyvdbXGgmDUUdPzIgLwP8wM,13703
55
+ chuk_tool_processor-0.1.3.dist-info/WHEEL,sha256=wXxTzcEDnjrTwFYjLPcsW_7_XihufBwmpiBeiXNBGEA,91
56
+ chuk_tool_processor-0.1.3.dist-info/top_level.txt,sha256=7lTsnuRx4cOW4U2sNJWNxl4ZTt_J1ndkjTbj3pHPY5M,20
57
+ chuk_tool_processor-0.1.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.1)
2
+ Generator: setuptools (80.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5