chuk-tool-processor 0.6.4__py3-none-any.whl → 0.6.6__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/logging/helpers.py +6 -6
- chuk_tool_processor/mcp/register_mcp_tools.py +5 -5
- chuk_tool_processor/mcp/setup_mcp_http_streamable.py +2 -2
- chuk_tool_processor/mcp/setup_mcp_sse.py +2 -2
- chuk_tool_processor/mcp/setup_mcp_stdio.py +2 -2
- chuk_tool_processor/mcp/stream_manager.py +30 -30
- chuk_tool_processor/mcp/transport/http_streamable_transport.py +23 -22
- chuk_tool_processor/mcp/transport/sse_transport.py +61 -23
- chuk_tool_processor/mcp/transport/stdio_transport.py +4 -4
- {chuk_tool_processor-0.6.4.dist-info → chuk_tool_processor-0.6.6.dist-info}/METADATA +2 -2
- {chuk_tool_processor-0.6.4.dist-info → chuk_tool_processor-0.6.6.dist-info}/RECORD +13 -13
- {chuk_tool_processor-0.6.4.dist-info → chuk_tool_processor-0.6.6.dist-info}/WHEEL +0 -0
- {chuk_tool_processor-0.6.4.dist-info → chuk_tool_processor-0.6.6.dist-info}/top_level.txt +0 -0
|
@@ -58,15 +58,15 @@ async def log_context_span(
|
|
|
58
58
|
prev = log_context.get_copy()
|
|
59
59
|
log_context.update(span_ctx)
|
|
60
60
|
|
|
61
|
-
logger.
|
|
61
|
+
logger.debug("Starting %s", operation)
|
|
62
62
|
try:
|
|
63
63
|
yield
|
|
64
64
|
if log_duration:
|
|
65
|
-
logger.
|
|
65
|
+
logger.debug(
|
|
66
66
|
"Completed %s", operation, extra={"context": {"duration": time.time() - start}}
|
|
67
67
|
)
|
|
68
68
|
else:
|
|
69
|
-
logger.
|
|
69
|
+
logger.debug("Completed %s", operation)
|
|
70
70
|
except Exception as exc:
|
|
71
71
|
logger.exception(
|
|
72
72
|
"Error in %s: %s", operation, exc, extra={"context": {"duration": time.time() - start}}
|
|
@@ -97,10 +97,10 @@ async def request_logging(
|
|
|
97
97
|
logger = get_logger("chuk_tool_processor.request")
|
|
98
98
|
request_id = log_context.start_request(request_id)
|
|
99
99
|
start = time.time()
|
|
100
|
-
logger.
|
|
100
|
+
logger.debug("Starting request %s", request_id)
|
|
101
101
|
try:
|
|
102
102
|
yield request_id
|
|
103
|
-
logger.
|
|
103
|
+
logger.debug(
|
|
104
104
|
"Completed request %s",
|
|
105
105
|
request_id,
|
|
106
106
|
extra={"context": {"duration": time.time() - start}},
|
|
@@ -184,4 +184,4 @@ async def log_tool_call(tool_call: Any, tool_result: Any) -> None:
|
|
|
184
184
|
if tool_result.error:
|
|
185
185
|
logger.error("Tool %s failed: %s", tool_call.tool, tool_result.error, extra={"context": ctx})
|
|
186
186
|
else:
|
|
187
|
-
logger.
|
|
187
|
+
logger.debug("Tool %s succeeded in %.3fs", tool_call.tool, dur, extra={"context": ctx})
|
|
@@ -95,7 +95,7 @@ async def register_mcp_tools(
|
|
|
95
95
|
except Exception as exc:
|
|
96
96
|
logger.error("Failed to register MCP tool '%s': %s", tool_name, exc)
|
|
97
97
|
|
|
98
|
-
logger.
|
|
98
|
+
logger.debug("MCP registration complete - %d tool(s) available", len(registered))
|
|
99
99
|
return registered
|
|
100
100
|
|
|
101
101
|
|
|
@@ -134,14 +134,14 @@ async def update_mcp_tools_stream_manager(
|
|
|
134
134
|
if tool and hasattr(tool, 'set_stream_manager'):
|
|
135
135
|
tool.set_stream_manager(new_stream_manager)
|
|
136
136
|
updated_count += 1
|
|
137
|
-
logger.debug(
|
|
137
|
+
logger.debug("Updated StreamManager for tool '%s:%s'", namespace, tool_name)
|
|
138
138
|
except Exception as e:
|
|
139
|
-
logger.warning(
|
|
139
|
+
logger.warning("Failed to update StreamManager for tool '%s:%s': %s", namespace, tool_name, e)
|
|
140
140
|
|
|
141
141
|
action = "connected" if new_stream_manager else "disconnected"
|
|
142
|
-
logger.
|
|
142
|
+
logger.debug("StreamManager %s for %d tools in namespace '%s'", action, updated_count, namespace)
|
|
143
143
|
|
|
144
144
|
except Exception as e:
|
|
145
|
-
logger.error(
|
|
145
|
+
logger.error("Failed to update tools in namespace '%s': %s", namespace, e)
|
|
146
146
|
|
|
147
147
|
return updated_count
|
|
@@ -110,8 +110,8 @@ async def setup_mcp_http_streamable(
|
|
|
110
110
|
max_retries=max_retries,
|
|
111
111
|
)
|
|
112
112
|
|
|
113
|
-
logger.
|
|
114
|
-
"MCP (HTTP Streamable) initialised - %
|
|
113
|
+
logger.debug(
|
|
114
|
+
"MCP (HTTP Streamable) initialised - %d tool%s registered into namespace '%s'",
|
|
115
115
|
len(registered),
|
|
116
116
|
"" if len(registered) == 1 else "s",
|
|
117
117
|
namespace,
|
|
@@ -89,8 +89,8 @@ async def setup_mcp_sse( # noqa: C901 - long but just a config facade
|
|
|
89
89
|
max_retries=max_retries,
|
|
90
90
|
)
|
|
91
91
|
|
|
92
|
-
logger.
|
|
93
|
-
"MCP (SSE) initialised - %
|
|
92
|
+
logger.debug(
|
|
93
|
+
"MCP (SSE) initialised - %d tool%s registered into namespace '%s'",
|
|
94
94
|
len(registered),
|
|
95
95
|
"" if len(registered) == 1 else "s",
|
|
96
96
|
namespace,
|
|
@@ -73,8 +73,8 @@ async def setup_mcp_stdio( # noqa: C901 - long but just a config facade
|
|
|
73
73
|
max_retries=max_retries,
|
|
74
74
|
)
|
|
75
75
|
|
|
76
|
-
logger.
|
|
77
|
-
"MCP (stdio) initialised - %
|
|
76
|
+
logger.debug(
|
|
77
|
+
"MCP (stdio) initialised - %d tool%s registered into namespace '%s'",
|
|
78
78
|
len(registered),
|
|
79
79
|
"" if len(registered) == 1 else "s",
|
|
80
80
|
namespace,
|
|
@@ -73,7 +73,7 @@ class StreamManager:
|
|
|
73
73
|
)
|
|
74
74
|
return inst
|
|
75
75
|
except asyncio.TimeoutError:
|
|
76
|
-
logger.error(
|
|
76
|
+
logger.error("StreamManager initialization timed out after %ss", initialization_timeout)
|
|
77
77
|
raise RuntimeError(f"StreamManager initialization timed out after {initialization_timeout}s")
|
|
78
78
|
|
|
79
79
|
@classmethod
|
|
@@ -99,7 +99,7 @@ class StreamManager:
|
|
|
99
99
|
)
|
|
100
100
|
return inst
|
|
101
101
|
except asyncio.TimeoutError:
|
|
102
|
-
logger.error(
|
|
102
|
+
logger.error("SSE StreamManager initialization timed out after %ss", initialization_timeout)
|
|
103
103
|
raise RuntimeError(f"SSE StreamManager initialization timed out after {initialization_timeout}s")
|
|
104
104
|
|
|
105
105
|
@classmethod
|
|
@@ -125,7 +125,7 @@ class StreamManager:
|
|
|
125
125
|
)
|
|
126
126
|
return inst
|
|
127
127
|
except asyncio.TimeoutError:
|
|
128
|
-
logger.error(
|
|
128
|
+
logger.error("HTTP Streamable StreamManager initialization timed out after %ss", initialization_timeout)
|
|
129
129
|
raise RuntimeError(f"HTTP Streamable StreamManager initialization timed out after {initialization_timeout}s")
|
|
130
130
|
|
|
131
131
|
# ------------------------------------------------------------------ #
|
|
@@ -196,7 +196,7 @@ class StreamManager:
|
|
|
196
196
|
else:
|
|
197
197
|
sse_url = "http://localhost:8000"
|
|
198
198
|
api_key = None
|
|
199
|
-
logger.warning(
|
|
199
|
+
logger.warning("No URL configured for SSE transport, using default: %s", sse_url)
|
|
200
200
|
|
|
201
201
|
transport = SSETransport(
|
|
202
202
|
sse_url,
|
|
@@ -215,7 +215,7 @@ class StreamManager:
|
|
|
215
215
|
http_url = "http://localhost:8000"
|
|
216
216
|
api_key = None
|
|
217
217
|
session_id = None
|
|
218
|
-
logger.warning(
|
|
218
|
+
logger.warning("No URL configured for HTTP Streamable transport, using default: %s", http_url)
|
|
219
219
|
|
|
220
220
|
transport = HTTPStreamableTransport(
|
|
221
221
|
http_url,
|
|
@@ -252,13 +252,13 @@ class StreamManager:
|
|
|
252
252
|
"status": status,
|
|
253
253
|
}
|
|
254
254
|
)
|
|
255
|
-
logger.
|
|
255
|
+
logger.debug("Initialised %s - %d tool(s)", server_name, len(tools))
|
|
256
256
|
except asyncio.TimeoutError:
|
|
257
257
|
logger.error("Timeout initialising %s", server_name)
|
|
258
258
|
except Exception as exc:
|
|
259
259
|
logger.error("Error initialising %s: %s", server_name, exc)
|
|
260
260
|
|
|
261
|
-
logger.
|
|
261
|
+
logger.debug(
|
|
262
262
|
"StreamManager ready - %d server(s), %d tool(s)",
|
|
263
263
|
len(self.transports),
|
|
264
264
|
len(self.all_tools),
|
|
@@ -307,13 +307,13 @@ class StreamManager:
|
|
|
307
307
|
self.server_info.append(
|
|
308
308
|
{"id": idx, "name": name, "tools": len(tools), "status": status}
|
|
309
309
|
)
|
|
310
|
-
logger.
|
|
310
|
+
logger.debug("Initialised SSE %s - %d tool(s)", name, len(tools))
|
|
311
311
|
except asyncio.TimeoutError:
|
|
312
312
|
logger.error("Timeout initialising SSE %s", name)
|
|
313
313
|
except Exception as exc:
|
|
314
314
|
logger.error("Error initialising SSE %s: %s", name, exc)
|
|
315
315
|
|
|
316
|
-
logger.
|
|
316
|
+
logger.debug(
|
|
317
317
|
"StreamManager ready - %d SSE server(s), %d tool(s)",
|
|
318
318
|
len(self.transports),
|
|
319
319
|
len(self.all_tools),
|
|
@@ -364,13 +364,13 @@ class StreamManager:
|
|
|
364
364
|
self.server_info.append(
|
|
365
365
|
{"id": idx, "name": name, "tools": len(tools), "status": status}
|
|
366
366
|
)
|
|
367
|
-
logger.
|
|
367
|
+
logger.debug("Initialised HTTP Streamable %s - %d tool(s)", name, len(tools))
|
|
368
368
|
except asyncio.TimeoutError:
|
|
369
369
|
logger.error("Timeout initialising HTTP Streamable %s", name)
|
|
370
370
|
except Exception as exc:
|
|
371
371
|
logger.error("Error initialising HTTP Streamable %s: %s", name, exc)
|
|
372
372
|
|
|
373
|
-
logger.
|
|
373
|
+
logger.debug(
|
|
374
374
|
"StreamManager ready - %d HTTP Streamable server(s), %d tool(s)",
|
|
375
375
|
len(self.transports),
|
|
376
376
|
len(self.all_tools),
|
|
@@ -395,20 +395,20 @@ class StreamManager:
|
|
|
395
395
|
return []
|
|
396
396
|
|
|
397
397
|
if server_name not in self.transports:
|
|
398
|
-
logger.error(
|
|
398
|
+
logger.error("Server '%s' not found in transports", server_name)
|
|
399
399
|
return []
|
|
400
400
|
|
|
401
401
|
transport = self.transports[server_name]
|
|
402
402
|
|
|
403
403
|
try:
|
|
404
404
|
tools = await asyncio.wait_for(transport.get_tools(), timeout=10.0)
|
|
405
|
-
logger.debug(
|
|
405
|
+
logger.debug("Found %d tools for server %s", len(tools), server_name)
|
|
406
406
|
return tools
|
|
407
407
|
except asyncio.TimeoutError:
|
|
408
|
-
logger.error(
|
|
408
|
+
logger.error("Timeout listing tools for server %s", server_name)
|
|
409
409
|
return []
|
|
410
410
|
except Exception as e:
|
|
411
|
-
logger.error(
|
|
411
|
+
logger.error("Error listing tools for server %s: %s", server_name, e)
|
|
412
412
|
return []
|
|
413
413
|
|
|
414
414
|
# ------------------------------------------------------------------ #
|
|
@@ -541,7 +541,7 @@ class StreamManager:
|
|
|
541
541
|
self._closed = True
|
|
542
542
|
return
|
|
543
543
|
|
|
544
|
-
logger.debug(
|
|
544
|
+
logger.debug("Closing %d transports...", len(self.transports))
|
|
545
545
|
|
|
546
546
|
try:
|
|
547
547
|
# Use shield to protect the cleanup operation from cancellation
|
|
@@ -551,7 +551,7 @@ class StreamManager:
|
|
|
551
551
|
logger.debug("Close operation cancelled, performing synchronous cleanup")
|
|
552
552
|
self._sync_cleanup()
|
|
553
553
|
except Exception as e:
|
|
554
|
-
logger.debug(
|
|
554
|
+
logger.debug("Error during close: %s", e)
|
|
555
555
|
self._sync_cleanup()
|
|
556
556
|
finally:
|
|
557
557
|
self._closed = True
|
|
@@ -565,7 +565,7 @@ class StreamManager:
|
|
|
565
565
|
try:
|
|
566
566
|
await self._concurrent_close(transport_items, close_results)
|
|
567
567
|
except Exception as e:
|
|
568
|
-
logger.debug(
|
|
568
|
+
logger.debug("Concurrent close failed: %s, falling back to sequential close", e)
|
|
569
569
|
# Strategy 2: Fall back to sequential close
|
|
570
570
|
await self._sequential_close(transport_items, close_results)
|
|
571
571
|
|
|
@@ -575,7 +575,7 @@ class StreamManager:
|
|
|
575
575
|
# Log summary
|
|
576
576
|
if close_results:
|
|
577
577
|
successful_closes = sum(1 for _, success, _ in close_results if success)
|
|
578
|
-
logger.debug(
|
|
578
|
+
logger.debug("Transport cleanup: %d/%d closed successfully", successful_closes, len(close_results))
|
|
579
579
|
|
|
580
580
|
async def _concurrent_close(self, transport_items: List[Tuple[str, MCPBaseTransport]], close_results: List) -> None:
|
|
581
581
|
"""Try to close all transports concurrently."""
|
|
@@ -602,10 +602,10 @@ class StreamManager:
|
|
|
602
602
|
for i, (name, _) in enumerate(close_tasks):
|
|
603
603
|
result = results[i] if i < len(results) else None
|
|
604
604
|
if isinstance(result, Exception):
|
|
605
|
-
logger.debug(
|
|
605
|
+
logger.debug("Transport %s close failed: %s", name, result)
|
|
606
606
|
close_results.append((name, False, str(result)))
|
|
607
607
|
else:
|
|
608
|
-
logger.debug(
|
|
608
|
+
logger.debug("Transport %s closed successfully", name)
|
|
609
609
|
close_results.append((name, True, None))
|
|
610
610
|
|
|
611
611
|
except asyncio.TimeoutError:
|
|
@@ -632,16 +632,16 @@ class StreamManager:
|
|
|
632
632
|
self._close_single_transport(name, transport),
|
|
633
633
|
timeout=0.5 # Short timeout per transport
|
|
634
634
|
)
|
|
635
|
-
logger.debug(
|
|
635
|
+
logger.debug("Closed transport: %s", name)
|
|
636
636
|
close_results.append((name, True, None))
|
|
637
637
|
except asyncio.TimeoutError:
|
|
638
|
-
logger.debug(
|
|
638
|
+
logger.debug("Transport %s close timed out (normal during shutdown)", name)
|
|
639
639
|
close_results.append((name, False, "timeout"))
|
|
640
640
|
except asyncio.CancelledError:
|
|
641
|
-
logger.debug(
|
|
641
|
+
logger.debug("Transport %s close cancelled during event loop shutdown", name)
|
|
642
642
|
close_results.append((name, False, "cancelled"))
|
|
643
643
|
except Exception as e:
|
|
644
|
-
logger.debug(
|
|
644
|
+
logger.debug("Error closing transport %s: %s", name, e)
|
|
645
645
|
close_results.append((name, False, str(e)))
|
|
646
646
|
|
|
647
647
|
async def _close_single_transport(self, name: str, transport: MCPBaseTransport) -> None:
|
|
@@ -650,9 +650,9 @@ class StreamManager:
|
|
|
650
650
|
if hasattr(transport, 'close') and callable(transport.close):
|
|
651
651
|
await transport.close()
|
|
652
652
|
else:
|
|
653
|
-
logger.debug(
|
|
653
|
+
logger.debug("Transport %s has no close method", name)
|
|
654
654
|
except Exception as e:
|
|
655
|
-
logger.debug(
|
|
655
|
+
logger.debug("Error closing transport %s: %s", name, e)
|
|
656
656
|
raise
|
|
657
657
|
|
|
658
658
|
def _sync_cleanup(self) -> None:
|
|
@@ -660,9 +660,9 @@ class StreamManager:
|
|
|
660
660
|
try:
|
|
661
661
|
transport_count = len(self.transports)
|
|
662
662
|
self._cleanup_state()
|
|
663
|
-
logger.debug(
|
|
663
|
+
logger.debug("Synchronous cleanup completed for %d transports", transport_count)
|
|
664
664
|
except Exception as e:
|
|
665
|
-
logger.debug(
|
|
665
|
+
logger.debug("Error during synchronous cleanup: %s", e)
|
|
666
666
|
|
|
667
667
|
def _cleanup_state(self) -> None:
|
|
668
668
|
"""Clean up internal state synchronously."""
|
|
@@ -673,7 +673,7 @@ class StreamManager:
|
|
|
673
673
|
self.all_tools.clear()
|
|
674
674
|
self.server_names.clear()
|
|
675
675
|
except Exception as e:
|
|
676
|
-
logger.debug(
|
|
676
|
+
logger.debug("Error during state cleanup: %s", e)
|
|
677
677
|
|
|
678
678
|
# ------------------------------------------------------------------ #
|
|
679
679
|
# backwards-compat: streams helper #
|
|
@@ -107,7 +107,7 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
107
107
|
start_time = time.time()
|
|
108
108
|
|
|
109
109
|
try:
|
|
110
|
-
logger.
|
|
110
|
+
logger.debug("Initializing HTTP Streamable transport to %s", self.url)
|
|
111
111
|
|
|
112
112
|
# Create HTTP parameters for chuk-mcp (following SSE pattern)
|
|
113
113
|
headers = {}
|
|
@@ -117,7 +117,7 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
117
117
|
|
|
118
118
|
if self.session_id:
|
|
119
119
|
headers["X-Session-ID"] = self.session_id
|
|
120
|
-
logger.debug(
|
|
120
|
+
logger.debug("Using session ID: %s", self.session_id)
|
|
121
121
|
|
|
122
122
|
http_params = StreamableHTTPParameters(
|
|
123
123
|
url=self.url,
|
|
@@ -154,7 +154,7 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
154
154
|
self._metrics["initialization_time"] = init_time
|
|
155
155
|
self._metrics["last_ping_time"] = ping_time
|
|
156
156
|
|
|
157
|
-
logger.
|
|
157
|
+
logger.debug("HTTP Streamable transport initialized successfully in %.3fs (ping: %.3fs)", init_time, ping_time)
|
|
158
158
|
return True
|
|
159
159
|
else:
|
|
160
160
|
logger.warning("HTTP connection established but ping failed")
|
|
@@ -164,12 +164,12 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
164
164
|
return True
|
|
165
165
|
|
|
166
166
|
except asyncio.TimeoutError:
|
|
167
|
-
logger.error(
|
|
167
|
+
logger.error("HTTP Streamable initialization timed out after %ss", self.connection_timeout)
|
|
168
168
|
logger.error("This may indicate the server is not responding to MCP initialization")
|
|
169
169
|
await self._cleanup()
|
|
170
170
|
return False
|
|
171
171
|
except Exception as e:
|
|
172
|
-
logger.error(
|
|
172
|
+
logger.error("Error initializing HTTP Streamable transport: %s", e, exc_info=True)
|
|
173
173
|
await self._cleanup()
|
|
174
174
|
return False
|
|
175
175
|
|
|
@@ -180,10 +180,11 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
180
180
|
|
|
181
181
|
# Log final metrics (enhanced from SSE)
|
|
182
182
|
if self.enable_metrics and self._metrics["total_calls"] > 0:
|
|
183
|
-
logger.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
183
|
+
logger.debug(
|
|
184
|
+
"HTTP Streamable transport closing - Total calls: %d, Success rate: %.1f%%, Avg response time: %.3fs",
|
|
185
|
+
self._metrics["total_calls"],
|
|
186
|
+
(self._metrics["successful_calls"] / self._metrics["total_calls"] * 100),
|
|
187
|
+
self._metrics["avg_response_time"]
|
|
187
188
|
)
|
|
188
189
|
|
|
189
190
|
try:
|
|
@@ -192,7 +193,7 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
192
193
|
logger.debug("HTTP Streamable context closed")
|
|
193
194
|
|
|
194
195
|
except Exception as e:
|
|
195
|
-
logger.debug(
|
|
196
|
+
logger.debug("Error during transport close: %s", e)
|
|
196
197
|
finally:
|
|
197
198
|
await self._cleanup()
|
|
198
199
|
|
|
@@ -219,14 +220,14 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
219
220
|
if self.enable_metrics:
|
|
220
221
|
ping_time = time.time() - start_time
|
|
221
222
|
self._metrics["last_ping_time"] = ping_time
|
|
222
|
-
logger.debug(
|
|
223
|
+
logger.debug("Ping completed in %.3fs: %s", ping_time, result)
|
|
223
224
|
|
|
224
225
|
return bool(result)
|
|
225
226
|
except asyncio.TimeoutError:
|
|
226
227
|
logger.error("Ping timed out")
|
|
227
228
|
return False
|
|
228
229
|
except Exception as e:
|
|
229
|
-
logger.error(
|
|
230
|
+
logger.error("Ping failed: %s", e)
|
|
230
231
|
return False
|
|
231
232
|
|
|
232
233
|
async def get_tools(self) -> List[Dict[str, Any]]:
|
|
@@ -248,12 +249,12 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
248
249
|
elif isinstance(tools_response, list):
|
|
249
250
|
tools = tools_response
|
|
250
251
|
else:
|
|
251
|
-
logger.warning(
|
|
252
|
+
logger.warning("Unexpected tools response type: %s", type(tools_response))
|
|
252
253
|
tools = []
|
|
253
254
|
|
|
254
255
|
if self.enable_metrics:
|
|
255
256
|
response_time = time.time() - start_time
|
|
256
|
-
logger.debug(
|
|
257
|
+
logger.debug("Retrieved %d tools in %.3fs", len(tools), response_time)
|
|
257
258
|
|
|
258
259
|
return tools
|
|
259
260
|
|
|
@@ -261,7 +262,7 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
261
262
|
logger.error("Get tools timed out")
|
|
262
263
|
return []
|
|
263
264
|
except Exception as e:
|
|
264
|
-
logger.error(
|
|
265
|
+
logger.error("Error getting tools: %s", e)
|
|
265
266
|
return []
|
|
266
267
|
|
|
267
268
|
async def call_tool(self, tool_name: str, arguments: Dict[str, Any],
|
|
@@ -280,7 +281,7 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
280
281
|
self._metrics["total_calls"] += 1
|
|
281
282
|
|
|
282
283
|
try:
|
|
283
|
-
logger.debug(
|
|
284
|
+
logger.debug("Calling tool '%s' with timeout %ss", tool_name, tool_timeout)
|
|
284
285
|
|
|
285
286
|
raw_response = await asyncio.wait_for(
|
|
286
287
|
send_tools_call(
|
|
@@ -299,9 +300,9 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
299
300
|
self._update_metrics(response_time, not result.get("isError", False))
|
|
300
301
|
|
|
301
302
|
if not result.get("isError", False):
|
|
302
|
-
logger.debug(
|
|
303
|
+
logger.debug("Tool '%s' completed successfully in %.3fs", tool_name, response_time)
|
|
303
304
|
else:
|
|
304
|
-
logger.warning(
|
|
305
|
+
logger.warning("Tool '%s' failed in %.3fs: %s", tool_name, response_time, result.get('error', 'Unknown error'))
|
|
305
306
|
|
|
306
307
|
return result
|
|
307
308
|
|
|
@@ -311,7 +312,7 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
311
312
|
self._update_metrics(response_time, False)
|
|
312
313
|
|
|
313
314
|
error_msg = f"Tool execution timed out after {tool_timeout}s"
|
|
314
|
-
logger.error(
|
|
315
|
+
logger.error("Tool '%s' %s", tool_name, error_msg)
|
|
315
316
|
return {
|
|
316
317
|
"isError": True,
|
|
317
318
|
"error": error_msg
|
|
@@ -322,7 +323,7 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
322
323
|
self._update_metrics(response_time, False)
|
|
323
324
|
|
|
324
325
|
error_msg = f"Tool execution failed: {str(e)}"
|
|
325
|
-
logger.error(
|
|
326
|
+
logger.error("Tool '%s' error: %s", tool_name, error_msg)
|
|
326
327
|
return {
|
|
327
328
|
"isError": True,
|
|
328
329
|
"error": error_msg
|
|
@@ -359,7 +360,7 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
359
360
|
logger.error("List resources timed out")
|
|
360
361
|
return {}
|
|
361
362
|
except Exception as e:
|
|
362
|
-
logger.debug(
|
|
363
|
+
logger.debug("Error listing resources: %s", e)
|
|
363
364
|
return {}
|
|
364
365
|
|
|
365
366
|
async def list_prompts(self) -> Dict[str, Any]:
|
|
@@ -381,7 +382,7 @@ class HTTPStreamableTransport(MCPBaseTransport):
|
|
|
381
382
|
logger.error("List prompts timed out")
|
|
382
383
|
return {}
|
|
383
384
|
except Exception as e:
|
|
384
|
-
logger.debug(
|
|
385
|
+
logger.debug("Error listing prompts: %s", e)
|
|
385
386
|
return {}
|
|
386
387
|
|
|
387
388
|
def _normalize_tool_response(self, raw_response: Dict[str, Any]) -> Dict[str, Any]:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# chuk_tool_processor/mcp/transport/sse_transport.py
|
|
2
2
|
"""
|
|
3
3
|
Fixed SSE transport that matches your server's actual behavior.
|
|
4
|
-
Based on your working debug script.
|
|
4
|
+
Based on your working debug script with smart URL detection.
|
|
5
5
|
"""
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
@@ -26,13 +26,21 @@ class SSETransport(MCPBaseTransport):
|
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
28
|
def __init__(self, url: str, api_key: Optional[str] = None,
|
|
29
|
+
headers: Optional[Dict[str, str]] = None,
|
|
29
30
|
connection_timeout: float = 30.0, default_timeout: float = 30.0):
|
|
30
31
|
"""Initialize SSE transport."""
|
|
31
32
|
self.url = url.rstrip('/')
|
|
32
33
|
self.api_key = api_key
|
|
34
|
+
self.configured_headers = headers or {}
|
|
33
35
|
self.connection_timeout = connection_timeout
|
|
34
36
|
self.default_timeout = default_timeout
|
|
35
37
|
|
|
38
|
+
# DEBUG: Log what we received
|
|
39
|
+
logger.debug("SSE Transport initialized with:")
|
|
40
|
+
logger.debug(" URL: %s", self.url)
|
|
41
|
+
logger.debug(" API Key: %s", "***" if api_key else None)
|
|
42
|
+
logger.debug(" Headers: %s", {k: v[:10] + "..." if len(v) > 10 else v for k, v in self.configured_headers.items()})
|
|
43
|
+
|
|
36
44
|
# State
|
|
37
45
|
self.session_id = None
|
|
38
46
|
self.message_url = None
|
|
@@ -48,11 +56,41 @@ class SSETransport(MCPBaseTransport):
|
|
|
48
56
|
self.sse_response = None
|
|
49
57
|
self.sse_stream_context = None
|
|
50
58
|
|
|
59
|
+
def _construct_sse_url(self, base_url: str) -> str:
|
|
60
|
+
"""
|
|
61
|
+
Construct the SSE endpoint URL from the base URL.
|
|
62
|
+
|
|
63
|
+
Smart detection to avoid double-appending /sse if already present.
|
|
64
|
+
"""
|
|
65
|
+
# Remove trailing slashes
|
|
66
|
+
base_url = base_url.rstrip('/')
|
|
67
|
+
|
|
68
|
+
# Check if URL already ends with /sse
|
|
69
|
+
if base_url.endswith('/sse'):
|
|
70
|
+
# Already has /sse, use as-is
|
|
71
|
+
logger.debug("URL already contains /sse endpoint: %s", base_url)
|
|
72
|
+
return base_url
|
|
73
|
+
|
|
74
|
+
# Append /sse to the base URL
|
|
75
|
+
sse_url = f"{base_url}/sse"
|
|
76
|
+
logger.debug("Appending /sse to base URL: %s -> %s", base_url, sse_url)
|
|
77
|
+
return sse_url
|
|
78
|
+
|
|
51
79
|
def _get_headers(self) -> Dict[str, str]:
|
|
52
80
|
"""Get headers with auth if available."""
|
|
53
81
|
headers = {}
|
|
82
|
+
|
|
83
|
+
# Add configured headers first
|
|
84
|
+
if self.configured_headers:
|
|
85
|
+
headers.update(self.configured_headers)
|
|
86
|
+
|
|
87
|
+
# Add API key as Bearer token if provided (this will override any Authorization header)
|
|
54
88
|
if self.api_key:
|
|
55
89
|
headers['Authorization'] = f'Bearer {self.api_key}'
|
|
90
|
+
|
|
91
|
+
# DEBUG: Log what headers we're sending
|
|
92
|
+
logger.debug("Sending headers: %s", {k: v[:10] + "..." if len(v) > 10 else v for k, v in headers.items()})
|
|
93
|
+
|
|
56
94
|
return headers
|
|
57
95
|
|
|
58
96
|
async def initialize(self) -> bool:
|
|
@@ -62,15 +100,15 @@ class SSETransport(MCPBaseTransport):
|
|
|
62
100
|
return True
|
|
63
101
|
|
|
64
102
|
try:
|
|
65
|
-
logger.
|
|
103
|
+
logger.debug("Initializing SSE transport...")
|
|
66
104
|
|
|
67
105
|
# Create HTTP clients
|
|
68
106
|
self.stream_client = httpx.AsyncClient(timeout=self.connection_timeout)
|
|
69
107
|
self.send_client = httpx.AsyncClient(timeout=self.default_timeout)
|
|
70
108
|
|
|
71
|
-
# Connect to SSE stream
|
|
72
|
-
sse_url =
|
|
73
|
-
logger.debug(
|
|
109
|
+
# Connect to SSE stream with smart URL construction
|
|
110
|
+
sse_url = self._construct_sse_url(self.url)
|
|
111
|
+
logger.debug("Connecting to SSE: %s", sse_url)
|
|
74
112
|
|
|
75
113
|
self.sse_stream_context = self.stream_client.stream(
|
|
76
114
|
'GET', sse_url, headers=self._get_headers()
|
|
@@ -78,10 +116,10 @@ class SSETransport(MCPBaseTransport):
|
|
|
78
116
|
self.sse_response = await self.sse_stream_context.__aenter__()
|
|
79
117
|
|
|
80
118
|
if self.sse_response.status_code != 200:
|
|
81
|
-
logger.error(
|
|
119
|
+
logger.error("SSE connection failed: %s", self.sse_response.status_code)
|
|
82
120
|
return False
|
|
83
121
|
|
|
84
|
-
logger.
|
|
122
|
+
logger.debug("SSE streaming connection established")
|
|
85
123
|
|
|
86
124
|
# Start SSE processing task
|
|
87
125
|
self.sse_task = asyncio.create_task(self._process_sse_stream())
|
|
@@ -97,7 +135,7 @@ class SSETransport(MCPBaseTransport):
|
|
|
97
135
|
logger.error("Failed to get session info from SSE")
|
|
98
136
|
return False
|
|
99
137
|
|
|
100
|
-
logger.
|
|
138
|
+
logger.debug("Session ready: %s", self.session_id)
|
|
101
139
|
|
|
102
140
|
# Now do MCP initialization
|
|
103
141
|
try:
|
|
@@ -111,22 +149,22 @@ class SSETransport(MCPBaseTransport):
|
|
|
111
149
|
})
|
|
112
150
|
|
|
113
151
|
if 'error' in init_response:
|
|
114
|
-
logger.error(
|
|
152
|
+
logger.error("Initialize failed: %s", init_response['error'])
|
|
115
153
|
return False
|
|
116
154
|
|
|
117
155
|
# Send initialized notification
|
|
118
156
|
await self._send_notification("notifications/initialized")
|
|
119
157
|
|
|
120
158
|
self._initialized = True
|
|
121
|
-
logger.
|
|
159
|
+
logger.debug("SSE transport initialized successfully")
|
|
122
160
|
return True
|
|
123
161
|
|
|
124
162
|
except Exception as e:
|
|
125
|
-
logger.error(
|
|
163
|
+
logger.error("MCP initialization failed: %s", e)
|
|
126
164
|
return False
|
|
127
165
|
|
|
128
166
|
except Exception as e:
|
|
129
|
-
logger.error(
|
|
167
|
+
logger.error("Error initializing SSE transport: %s", e, exc_info=True)
|
|
130
168
|
await self._cleanup()
|
|
131
169
|
return False
|
|
132
170
|
|
|
@@ -148,7 +186,7 @@ class SSETransport(MCPBaseTransport):
|
|
|
148
186
|
if 'session_id=' in endpoint_path:
|
|
149
187
|
self.session_id = endpoint_path.split('session_id=')[1].split('&')[0]
|
|
150
188
|
|
|
151
|
-
logger.debug(
|
|
189
|
+
logger.debug("Got session info: %s", self.session_id)
|
|
152
190
|
continue
|
|
153
191
|
|
|
154
192
|
# Handle JSON-RPC responses
|
|
@@ -170,13 +208,13 @@ class SSETransport(MCPBaseTransport):
|
|
|
170
208
|
future = self.pending_requests.pop(request_id)
|
|
171
209
|
if not future.done():
|
|
172
210
|
future.set_result(response_data)
|
|
173
|
-
logger.debug(
|
|
211
|
+
logger.debug("Resolved request: %s", request_id)
|
|
174
212
|
|
|
175
213
|
except json.JSONDecodeError:
|
|
176
214
|
pass # Not JSON, ignore
|
|
177
215
|
|
|
178
216
|
except Exception as e:
|
|
179
|
-
logger.error(
|
|
217
|
+
logger.error("SSE stream error: %s", e)
|
|
180
218
|
|
|
181
219
|
async def _send_request(self, method: str, params: Dict[str, Any] = None,
|
|
182
220
|
timeout: Optional[float] = None) -> Dict[str, Any]:
|
|
@@ -273,15 +311,15 @@ class SSETransport(MCPBaseTransport):
|
|
|
273
311
|
response = await self._send_request("tools/list", {})
|
|
274
312
|
|
|
275
313
|
if 'error' in response:
|
|
276
|
-
logger.error(
|
|
314
|
+
logger.error("Error getting tools: %s", response['error'])
|
|
277
315
|
return []
|
|
278
316
|
|
|
279
317
|
tools = response.get('result', {}).get('tools', [])
|
|
280
|
-
logger.debug(
|
|
318
|
+
logger.debug("Retrieved %d tools", len(tools))
|
|
281
319
|
return tools
|
|
282
320
|
|
|
283
321
|
except Exception as e:
|
|
284
|
-
logger.error(
|
|
322
|
+
logger.error("Error getting tools: %s", e)
|
|
285
323
|
return []
|
|
286
324
|
|
|
287
325
|
async def call_tool(self, tool_name: str, arguments: Dict[str, Any],
|
|
@@ -294,7 +332,7 @@ class SSETransport(MCPBaseTransport):
|
|
|
294
332
|
}
|
|
295
333
|
|
|
296
334
|
try:
|
|
297
|
-
logger.debug(
|
|
335
|
+
logger.debug("Calling tool %s with args: %s", tool_name, arguments)
|
|
298
336
|
|
|
299
337
|
response = await self._send_request(
|
|
300
338
|
"tools/call",
|
|
@@ -347,10 +385,10 @@ class SSETransport(MCPBaseTransport):
|
|
|
347
385
|
except asyncio.TimeoutError:
|
|
348
386
|
return {
|
|
349
387
|
"isError": True,
|
|
350
|
-
"error":
|
|
388
|
+
"error": "Tool execution timed out"
|
|
351
389
|
}
|
|
352
390
|
except Exception as e:
|
|
353
|
-
logger.error(
|
|
391
|
+
logger.error("Error calling tool %s: %s", tool_name, e)
|
|
354
392
|
return {
|
|
355
393
|
"isError": True,
|
|
356
394
|
"error": str(e)
|
|
@@ -364,7 +402,7 @@ class SSETransport(MCPBaseTransport):
|
|
|
364
402
|
try:
|
|
365
403
|
response = await self._send_request("resources/list", {}, timeout=10.0)
|
|
366
404
|
if 'error' in response:
|
|
367
|
-
logger.debug(
|
|
405
|
+
logger.debug("Resources not supported: %s", response['error'])
|
|
368
406
|
return {}
|
|
369
407
|
return response.get('result', {})
|
|
370
408
|
except Exception:
|
|
@@ -378,7 +416,7 @@ class SSETransport(MCPBaseTransport):
|
|
|
378
416
|
try:
|
|
379
417
|
response = await self._send_request("prompts/list", {}, timeout=10.0)
|
|
380
418
|
if 'error' in response:
|
|
381
|
-
logger.debug(
|
|
419
|
+
logger.debug("Prompts not supported: %s", response['error'])
|
|
382
420
|
return {}
|
|
383
421
|
return response.get('result', {})
|
|
384
422
|
except Exception:
|
|
@@ -53,7 +53,7 @@ class StdioTransport(MCPBaseTransport):
|
|
|
53
53
|
return True
|
|
54
54
|
|
|
55
55
|
try:
|
|
56
|
-
logger.
|
|
56
|
+
logger.debug("Initializing STDIO transport...")
|
|
57
57
|
self._context = stdio_client(self.server_params)
|
|
58
58
|
self._streams = await self._context.__aenter__()
|
|
59
59
|
|
|
@@ -61,13 +61,13 @@ class StdioTransport(MCPBaseTransport):
|
|
|
61
61
|
init_result = await send_initialize(*self._streams)
|
|
62
62
|
if init_result:
|
|
63
63
|
self._initialized = True
|
|
64
|
-
logger.
|
|
64
|
+
logger.debug("STDIO transport initialized successfully")
|
|
65
65
|
return True
|
|
66
66
|
else:
|
|
67
67
|
await self._cleanup()
|
|
68
68
|
return False
|
|
69
69
|
except Exception as e:
|
|
70
|
-
logger.error(
|
|
70
|
+
logger.error("Error initializing STDIO transport: %s", e)
|
|
71
71
|
await self._cleanup()
|
|
72
72
|
return False
|
|
73
73
|
|
|
@@ -78,7 +78,7 @@ class StdioTransport(MCPBaseTransport):
|
|
|
78
78
|
# Simple delegation - the StreamManager now calls this in the correct context
|
|
79
79
|
await self._context.__aexit__(None, None, None)
|
|
80
80
|
except Exception as e:
|
|
81
|
-
logger.debug(
|
|
81
|
+
logger.debug("Error during close: %s", e)
|
|
82
82
|
finally:
|
|
83
83
|
await self._cleanup()
|
|
84
84
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chuk-tool-processor
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.6
|
|
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>
|
|
@@ -20,7 +20,7 @@ Classifier: Framework :: AsyncIO
|
|
|
20
20
|
Classifier: Typing :: Typed
|
|
21
21
|
Requires-Python: >=3.11
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
|
-
Requires-Dist: chuk-mcp>=0.5.
|
|
23
|
+
Requires-Dist: chuk-mcp>=0.5.2
|
|
24
24
|
Requires-Dist: dotenv>=0.9.9
|
|
25
25
|
Requires-Dist: pydantic>=2.11.3
|
|
26
26
|
Requires-Dist: uuid>=1.30
|
|
@@ -14,20 +14,20 @@ chuk_tool_processor/execution/wrappers/retry.py,sha256=giws8cxs7zfR2ZnBz3WYxtr1-
|
|
|
14
14
|
chuk_tool_processor/logging/__init__.py,sha256=UDFPYU_bzeUUSN8qw3nMpo-FMKXetIi7OYtN3W3iPbg,3794
|
|
15
15
|
chuk_tool_processor/logging/context.py,sha256=69EsAhCiya_4zyivz1uUJAhwo0rXpOoTvePWvaFYIi8,12225
|
|
16
16
|
chuk_tool_processor/logging/formatter.py,sha256=RhlV6NqBYRBOtytDY49c9Y1J4l02ZjNXIgVRn03tfSQ,3061
|
|
17
|
-
chuk_tool_processor/logging/helpers.py,sha256=
|
|
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
20
|
chuk_tool_processor/mcp/mcp_tool.py,sha256=x4zivoBaO7xiepUTN5kfbGcNDNh8rAxvQdgyqduCdRw,17768
|
|
21
|
-
chuk_tool_processor/mcp/register_mcp_tools.py,sha256=
|
|
22
|
-
chuk_tool_processor/mcp/setup_mcp_http_streamable.py,sha256=
|
|
23
|
-
chuk_tool_processor/mcp/setup_mcp_sse.py,sha256=
|
|
24
|
-
chuk_tool_processor/mcp/setup_mcp_stdio.py,sha256=
|
|
25
|
-
chuk_tool_processor/mcp/stream_manager.py,sha256=
|
|
21
|
+
chuk_tool_processor/mcp/register_mcp_tools.py,sha256=s6mQMtZr7dswT2WXDJ84zjOTSi3cOmtRTGGdLMl15bM,4897
|
|
22
|
+
chuk_tool_processor/mcp/setup_mcp_http_streamable.py,sha256=ZJUAj7LL4CRfc-CBl0SQJk0qfW12IuixR-7J2hbQ8S8,4538
|
|
23
|
+
chuk_tool_processor/mcp/setup_mcp_sse.py,sha256=4nf0V6cykAPLxtgsl8RTAYQdVWITUNu_3CIU1vcLjlo,3795
|
|
24
|
+
chuk_tool_processor/mcp/setup_mcp_stdio.py,sha256=L8anrx_b5HDsaMqAfbpWaHex084DTd76W8WBf3ClC48,2884
|
|
25
|
+
chuk_tool_processor/mcp/stream_manager.py,sha256=DN58d76J3Xkg9DI3f6EFe9qAG-ZsFa4dW0ARygOZkz4,31527
|
|
26
26
|
chuk_tool_processor/mcp/transport/__init__.py,sha256=0DX7m_VvlXPxijc-88_QTLhq4ZqAgUgzBjSMGL9C_lM,963
|
|
27
27
|
chuk_tool_processor/mcp/transport/base_transport.py,sha256=bqId34OMQMxzMXtrKq_86sot0_x0NS_ecaIllsCyy6I,3423
|
|
28
|
-
chuk_tool_processor/mcp/transport/http_streamable_transport.py,sha256=
|
|
29
|
-
chuk_tool_processor/mcp/transport/sse_transport.py,sha256=
|
|
30
|
-
chuk_tool_processor/mcp/transport/stdio_transport.py,sha256=
|
|
28
|
+
chuk_tool_processor/mcp/transport/http_streamable_transport.py,sha256=3I3tNYU8r4YqCbNhMCkoucvZc6VS2ulzeUjDe2FbcRk,19108
|
|
29
|
+
chuk_tool_processor/mcp/transport/sse_transport.py,sha256=s8an1sHLFsIJwqxUcdSvY6bYBkdyNQ-LkQMqzD5okdI,17253
|
|
30
|
+
chuk_tool_processor/mcp/transport/stdio_transport.py,sha256=DPXLR_OxuCJ2bgwYDuT_iYC_CDcUIySvLNO7JzyiPyc,9099
|
|
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
|
|
33
33
|
chuk_tool_processor/models/streaming_tool.py,sha256=0v2PSPTgZ5TS_PpVdohvVhh99fPwPQM_R_z4RU0mlLM,3541
|
|
@@ -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.6.dist-info/METADATA,sha256=xjCU03q8dyRa8VbmtEO5ZjLiJbWzid6ccy5TItWokSo,23463
|
|
58
|
+
chuk_tool_processor-0.6.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
59
|
+
chuk_tool_processor-0.6.6.dist-info/top_level.txt,sha256=7lTsnuRx4cOW4U2sNJWNxl4ZTt_J1ndkjTbj3pHPY5M,20
|
|
60
|
+
chuk_tool_processor-0.6.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|