chuk-tool-processor 0.1.7__py3-none-any.whl → 0.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.

Potentially problematic release.


This version of chuk-tool-processor might be problematic. Click here for more details.

@@ -265,7 +265,20 @@ class StreamManager:
265
265
  tool_name: str,
266
266
  arguments: Dict[str, Any],
267
267
  server_name: Optional[str] = None,
268
+ timeout: Optional[float] = None, # Add timeout parameter
268
269
  ) -> Dict[str, Any]:
270
+ """
271
+ Call a tool on the appropriate server with timeout support.
272
+
273
+ Args:
274
+ tool_name: Name of the tool to call
275
+ arguments: Arguments to pass to the tool
276
+ server_name: Optional server name (auto-detected if not provided)
277
+ timeout: Optional timeout for the call
278
+
279
+ Returns:
280
+ Dictionary containing the tool result or error
281
+ """
269
282
  server_name = server_name or self.get_server_for_tool(tool_name)
270
283
  if not server_name or server_name not in self.transports:
271
284
  # wording kept exactly for unit-test expectation
@@ -273,8 +286,27 @@ class StreamManager:
273
286
  "isError": True,
274
287
  "error": f"No server found for tool: {tool_name}",
275
288
  }
276
- return await self.transports[server_name].call_tool(tool_name, arguments)
277
-
289
+
290
+ transport = self.transports[server_name]
291
+
292
+ # Apply timeout if specified
293
+ if timeout is not None:
294
+ logger.debug("Calling tool '%s' with %ss timeout", tool_name, timeout)
295
+ try:
296
+ return await asyncio.wait_for(
297
+ transport.call_tool(tool_name, arguments),
298
+ timeout=timeout
299
+ )
300
+ except asyncio.TimeoutError:
301
+ logger.warning("Tool '%s' timed out after %ss", tool_name, timeout)
302
+ return {
303
+ "isError": True,
304
+ "error": f"Tool call timed out after {timeout}s",
305
+ }
306
+ else:
307
+ # No timeout specified, call directly
308
+ return await transport.call_tool(tool_name, arguments)
309
+
278
310
  # ------------------------------------------------------------------ #
279
311
  # shutdown #
280
312
  # ------------------------------------------------------------------ #
@@ -1,12 +1,20 @@
1
1
  # chuk_tool_processor/mcp/transport/sse_transport.py
2
2
  """
3
- Server-Sent Events (SSE) transport for MCP communication implemented with **httpx**.
3
+ Proper MCP SSE transport that follows the standard MCP SSE protocol.
4
+
5
+ This transport:
6
+ 1. Connects to /sse for SSE stream
7
+ 2. Listens for 'endpoint' event to get message URL
8
+ 3. Sends MCP initialize handshake FIRST
9
+ 4. Only then proceeds with tools/list and tool calls
10
+ 5. Handles async responses via SSE message events
4
11
  """
5
12
  from __future__ import annotations
6
13
 
7
14
  import asyncio
8
15
  import contextlib
9
16
  import json
17
+ import os
10
18
  from typing import Any, Dict, List, Optional
11
19
 
12
20
  import httpx
@@ -16,7 +24,7 @@ from .base_transport import MCPBaseTransport
16
24
  # --------------------------------------------------------------------------- #
17
25
  # Helpers #
18
26
  # --------------------------------------------------------------------------- #
19
- DEFAULT_TIMEOUT = 5.0 # seconds
27
+ DEFAULT_TIMEOUT = 30.0 # Longer timeout for real servers
20
28
  HEADERS_JSON: Dict[str, str] = {"accept": "application/json"}
21
29
 
22
30
 
@@ -30,160 +38,416 @@ def _url(base: str, path: str) -> str:
30
38
  # --------------------------------------------------------------------------- #
31
39
  class SSETransport(MCPBaseTransport):
32
40
  """
33
- Minimal SSE/REST transport. It speaks a simple REST dialect:
34
-
35
- GET /ping 200 OK
36
- GET /tools/list {"tools": [...]}
37
- POST /tools/call → {"name": ..., "result": ...}
38
- GET /resources/list {"resources": [...]}
39
- GET /prompts/list → {"prompts": [...]}
40
- GET /events → <text/event-stream>
41
+ Proper MCP SSE transport that follows the standard protocol:
42
+
43
+ 1. GET /sse Establishes SSE connection
44
+ 2. Waits for 'endpoint' event Gets message URL
45
+ 3. Sends MCP initialize handshake → Establishes session
46
+ 4. POST to message URL Sends tool calls
47
+ 5. Waits for async responses via SSE message events
41
48
  """
42
49
 
43
- EVENTS_PATH = "/events"
44
-
45
- # ------------------------------------------------------------------ #
46
- # Construction #
47
- # ------------------------------------------------------------------ #
48
50
  def __init__(self, url: str, api_key: Optional[str] = None) -> None:
49
51
  self.base_url = url.rstrip("/")
50
52
  self.api_key = api_key
53
+
54
+ # NEW: Auto-detect bearer token from environment if not provided
55
+ if not self.api_key:
56
+ bearer_token = os.getenv("MCP_BEARER_TOKEN")
57
+ if bearer_token:
58
+ self.api_key = bearer_token
59
+ print(f"🔑 Using bearer token from MCP_BEARER_TOKEN environment variable")
51
60
 
52
61
  # httpx client (None until initialise)
53
62
  self._client: httpx.AsyncClient | None = None
54
- self.session: httpx.AsyncClient | None = None # ← kept for legacy tests
63
+ self.session: httpx.AsyncClient | None = None
55
64
 
56
- # background reader
57
- self._reader_task: asyncio.Task | None = None
58
- self._incoming_queue: "asyncio.Queue[dict[str, Any]]" = asyncio.Queue()
65
+ # MCP SSE state
66
+ self._message_url: Optional[str] = None
67
+ self._session_id: Optional[str] = None
68
+ self._sse_task: Optional[asyncio.Task] = None
69
+ self._connected = asyncio.Event()
70
+ self._initialized = asyncio.Event() # NEW: Track MCP initialization
71
+
72
+ # Async message handling
73
+ self._pending_requests: Dict[str, asyncio.Future] = {}
74
+ self._message_lock = asyncio.Lock()
59
75
 
60
76
  # ------------------------------------------------------------------ #
61
77
  # Life-cycle #
62
78
  # ------------------------------------------------------------------ #
63
79
  async def initialize(self) -> bool:
64
- """Open the httpx client and start the /events consumer."""
65
- if self._client: # already initialised
80
+ """Initialize the MCP SSE transport."""
81
+ if self._client:
66
82
  return True
67
83
 
84
+ headers = {}
85
+ if self.api_key:
86
+ # NEW: Handle both "Bearer token" and just "token" formats
87
+ if self.api_key.startswith("Bearer "):
88
+ headers["Authorization"] = self.api_key
89
+ else:
90
+ headers["Authorization"] = f"Bearer {self.api_key}"
91
+ print(f"🔑 Added Authorization header to httpx client")
92
+
68
93
  self._client = httpx.AsyncClient(
69
- headers={"authorization": self.api_key} if self.api_key else None,
94
+ headers=headers,
70
95
  timeout=DEFAULT_TIMEOUT,
71
96
  )
72
- self.session = self._client # legacy attribute for tests
97
+ self.session = self._client
73
98
 
74
- # spawn reader (best-effort reconnect)
75
- self._reader_task = asyncio.create_task(self._consume_events(), name="sse-reader")
99
+ # Start SSE connection and wait for endpoint
100
+ self._sse_task = asyncio.create_task(self._handle_sse_connection())
101
+
102
+ try:
103
+ # Wait for endpoint event (up to 10 seconds)
104
+ await asyncio.wait_for(self._connected.wait(), timeout=10.0)
105
+
106
+ # NEW: Send MCP initialize handshake
107
+ if await self._initialize_mcp_session():
108
+ return True
109
+ else:
110
+ print("❌ MCP initialization failed")
111
+ return False
112
+
113
+ except asyncio.TimeoutError:
114
+ print("❌ Timeout waiting for SSE endpoint event")
115
+ return False
116
+ except Exception as e:
117
+ print(f"❌ SSE initialization failed: {e}")
118
+ return False
119
+
120
+ async def _initialize_mcp_session(self) -> bool:
121
+ """Send the required MCP initialize handshake."""
122
+ if not self._message_url:
123
+ print("❌ No message URL available for initialization")
124
+ return False
125
+
126
+ try:
127
+ print("🔄 Sending MCP initialize handshake...")
128
+
129
+ # Required MCP initialize message
130
+ init_message = {
131
+ "jsonrpc": "2.0",
132
+ "id": "initialize",
133
+ "method": "initialize",
134
+ "params": {
135
+ "protocolVersion": "2024-11-05",
136
+ "capabilities": {
137
+ "tools": {},
138
+ "resources": {},
139
+ "prompts": {},
140
+ "sampling": {}
141
+ },
142
+ "clientInfo": {
143
+ "name": "chuk-tool-processor",
144
+ "version": "1.0.0"
145
+ }
146
+ }
147
+ }
148
+
149
+ response = await self._send_message(init_message)
150
+
151
+ if "result" in response:
152
+ server_info = response["result"]
153
+ print(f"✅ MCP initialized: {server_info.get('serverInfo', {}).get('name', 'Unknown Server')}")
154
+
155
+ # Send initialized notification (required by MCP spec)
156
+ notification = {
157
+ "jsonrpc": "2.0",
158
+ "method": "notifications/initialized"
159
+ }
160
+
161
+ # Send notification (don't wait for response)
162
+ await self._send_notification(notification)
163
+ self._initialized.set()
164
+ return True
165
+ else:
166
+ print(f"❌ MCP initialization failed: {response}")
167
+ return False
168
+
169
+ except Exception as e:
170
+ print(f"❌ MCP initialization error: {e}")
171
+ return False
76
172
 
77
- # verify connection
78
- return await self.send_ping()
173
+ async def _send_notification(self, notification: Dict[str, Any]) -> None:
174
+ """Send a JSON-RPC notification (no response expected)."""
175
+ if not self._client or not self._message_url:
176
+ return
177
+
178
+ try:
179
+ headers = {"Content-Type": "application/json"}
180
+ await self._client.post(
181
+ self._message_url,
182
+ json=notification,
183
+ headers=headers
184
+ )
185
+ except Exception as e:
186
+ print(f"⚠️ Failed to send notification: {e}")
79
187
 
80
188
  async def close(self) -> None:
81
- """Stop background reader and close the httpx client."""
82
- if self._reader_task:
83
- self._reader_task.cancel()
189
+ """Close the transport."""
190
+ # Cancel any pending requests
191
+ for future in self._pending_requests.values():
192
+ if not future.done():
193
+ future.cancel()
194
+ self._pending_requests.clear()
195
+
196
+ if self._sse_task:
197
+ self._sse_task.cancel()
84
198
  with contextlib.suppress(asyncio.CancelledError):
85
- await self._reader_task
86
- self._reader_task = None
199
+ await self._sse_task
200
+ self._sse_task = None
87
201
 
88
202
  if self._client:
89
203
  await self._client.aclose()
90
204
  self._client = None
91
- self.session = None # keep tests happy
205
+ self.session = None
92
206
 
93
207
  # ------------------------------------------------------------------ #
94
- # Internal helpers #
208
+ # SSE Connection Handler #
95
209
  # ------------------------------------------------------------------ #
96
- async def _get_json(self, path: str) -> Any:
210
+ async def _handle_sse_connection(self) -> None:
211
+ """Handle the SSE connection and extract the endpoint URL."""
97
212
  if not self._client:
98
- raise RuntimeError("Transport not initialised")
99
-
100
- resp = await self._client.get(_url(self.base_url, path), headers=HEADERS_JSON)
101
- resp.raise_for_status()
102
- return resp.json()
213
+ return
103
214
 
104
- async def _post_json(self, path: str, payload: Dict[str, Any]) -> Any:
105
- if not self._client:
106
- raise RuntimeError("Transport not initialised")
215
+ try:
216
+ headers = {
217
+ "Accept": "text/event-stream",
218
+ "Cache-Control": "no-cache"
219
+ }
220
+
221
+ async with self._client.stream(
222
+ "GET", f"{self.base_url}/sse", headers=headers
223
+ ) as response:
224
+ response.raise_for_status()
225
+
226
+ async for line in response.aiter_lines():
227
+ if not line:
228
+ continue
229
+
230
+ # Parse SSE events
231
+ if line.startswith("event: "):
232
+ event_type = line[7:].strip()
233
+
234
+ elif line.startswith("data: ") and 'event_type' in locals():
235
+ data = line[6:].strip()
236
+
237
+ if event_type == "endpoint":
238
+ # Got the endpoint URL for messages - construct full URL
239
+ # NEW: Handle URLs that need trailing slash fix
240
+ if "/messages?" in data and "/messages/?" not in data:
241
+ data = data.replace("/messages?", "/messages/?", 1)
242
+ print(f"🔧 Fixed URL redirect: added trailing slash")
243
+
244
+ self._message_url = f"{self.base_url}{data}"
245
+
246
+ # Extract session_id if present
247
+ if "session_id=" in data:
248
+ self._session_id = data.split("session_id=")[1].split("&")[0]
249
+
250
+ print(f"✅ Got message endpoint: {self._message_url}")
251
+ self._connected.set()
252
+
253
+ elif event_type == "message":
254
+ # Handle incoming JSON-RPC responses
255
+ try:
256
+ message = json.loads(data)
257
+ await self._handle_incoming_message(message)
258
+ except json.JSONDecodeError:
259
+ print(f"❌ Failed to parse message: {data}")
260
+
261
+ except asyncio.CancelledError:
262
+ pass
263
+ except Exception as e:
264
+ print(f"❌ SSE connection failed: {e}")
107
265
 
108
- resp = await self._client.post(
109
- _url(self.base_url, path), json=payload, headers=HEADERS_JSON
110
- )
111
- resp.raise_for_status()
112
- return resp.json()
266
+ async def _handle_incoming_message(self, message: Dict[str, Any]) -> None:
267
+ """Handle incoming JSON-RPC response messages."""
268
+ message_id = message.get("id")
269
+ if message_id and message_id in self._pending_requests:
270
+ # Complete the pending request
271
+ future = self._pending_requests.pop(message_id)
272
+ if not future.done():
273
+ future.set_result(message)
113
274
 
114
275
  # ------------------------------------------------------------------ #
115
- # Public API (implements MCPBaseTransport) #
276
+ # MCP Protocol Methods #
116
277
  # ------------------------------------------------------------------ #
117
278
  async def send_ping(self) -> bool:
118
- if not self._client:
119
- return False
120
- try:
121
- await self._get_json("/ping")
122
- return True
123
- except Exception: # pragma: no cover
124
- return False
279
+ """Test if we have a working and initialized connection."""
280
+ return self._message_url is not None and self._initialized.is_set()
125
281
 
126
282
  async def get_tools(self) -> List[Dict[str, Any]]:
127
- if not self._client:
283
+ """Get available tools using tools/list."""
284
+ # NEW: Wait for initialization before proceeding
285
+ if not self._initialized.is_set():
286
+ print("⏳ Waiting for MCP initialization...")
287
+ try:
288
+ await asyncio.wait_for(self._initialized.wait(), timeout=10.0)
289
+ except asyncio.TimeoutError:
290
+ print("❌ Timeout waiting for MCP initialization")
291
+ return []
292
+
293
+ if not self._message_url:
128
294
  return []
295
+
129
296
  try:
130
- data = await self._get_json("/tools/list")
131
- return data.get("tools", []) if isinstance(data, dict) else []
132
- except Exception: # pragma: no cover
133
- return []
297
+ message = {
298
+ "jsonrpc": "2.0",
299
+ "id": "tools_list",
300
+ "method": "tools/list",
301
+ "params": {}
302
+ }
303
+
304
+ response = await self._send_message(message)
305
+
306
+ if "result" in response and "tools" in response["result"]:
307
+ return response["result"]["tools"]
308
+
309
+ except Exception as e:
310
+ print(f"❌ Failed to get tools: {e}")
311
+
312
+ return []
134
313
 
135
314
  async def call_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
136
- # ─── tests expect this specific message if *not* initialised ───
137
- if not self._client:
138
- return {"isError": True, "error": "SSE transport not implemented"}
315
+ """Execute a tool call using the MCP protocol."""
316
+ # NEW: Ensure initialization before tool calls
317
+ if not self._initialized.is_set():
318
+ return {"isError": True, "error": "MCP session not initialized"}
319
+
320
+ if not self._message_url:
321
+ return {"isError": True, "error": "No message endpoint available"}
322
+
323
+ try:
324
+ message = {
325
+ "jsonrpc": "2.0",
326
+ "id": f"call_{tool_name}",
327
+ "method": "tools/call",
328
+ "params": {
329
+ "name": tool_name,
330
+ "arguments": arguments
331
+ }
332
+ }
333
+
334
+ response = await self._send_message(message)
335
+
336
+ # Process MCP response
337
+ if "error" in response:
338
+ return {
339
+ "isError": True,
340
+ "error": response["error"].get("message", "Unknown error")
341
+ }
342
+
343
+ if "result" in response:
344
+ result = response["result"]
345
+
346
+ # Handle MCP tool response format
347
+ if "content" in result:
348
+ # Extract content from MCP format
349
+ content = result["content"]
350
+ if isinstance(content, list) and content:
351
+ # Take first content item
352
+ first_content = content[0]
353
+ if isinstance(first_content, dict) and "text" in first_content:
354
+ return {"isError": False, "content": first_content["text"]}
355
+
356
+ return {"isError": False, "content": content}
357
+
358
+ # Direct result
359
+ return {"isError": False, "content": result}
360
+
361
+ return {"isError": True, "error": "No result in response"}
362
+
363
+ except Exception as e:
364
+ return {"isError": True, "error": str(e)}
365
+
366
+ async def _send_message(self, message: Dict[str, Any]) -> Dict[str, Any]:
367
+ """Send a JSON-RPC message to the server and wait for async response."""
368
+ if not self._client or not self._message_url:
369
+ raise RuntimeError("Transport not properly initialized")
370
+
371
+ message_id = message.get("id")
372
+ if not message_id:
373
+ raise ValueError("Message must have an ID")
374
+
375
+ # Create a future for this request
376
+ future = asyncio.Future()
377
+ async with self._message_lock:
378
+ self._pending_requests[message_id] = future
139
379
 
140
380
  try:
141
- payload = {"name": tool_name, "arguments": arguments}
142
- return await self._post_json("/tools/call", payload)
143
- except Exception as exc: # pragma: no cover
144
- return {"isError": True, "error": str(exc)}
381
+ headers = {"Content-Type": "application/json"}
382
+
383
+ # Send the request
384
+ response = await self._client.post(
385
+ self._message_url,
386
+ json=message,
387
+ headers=headers
388
+ )
389
+
390
+ # Check if server accepted the request
391
+ if response.status_code == 202:
392
+ # Server accepted - wait for async response via SSE
393
+ try:
394
+ response_message = await asyncio.wait_for(future, timeout=30.0)
395
+ return response_message
396
+ except asyncio.TimeoutError:
397
+ raise RuntimeError(f"Timeout waiting for response to message {message_id}")
398
+ else:
399
+ # Immediate response - parse and return
400
+ response.raise_for_status()
401
+ return response.json()
402
+
403
+ finally:
404
+ # Clean up pending request
405
+ async with self._message_lock:
406
+ self._pending_requests.pop(message_id, None)
145
407
 
146
- # ----------------------- extras used by StreamManager ------------- #
408
+ # ------------------------------------------------------------------ #
409
+ # Additional MCP methods #
410
+ # ------------------------------------------------------------------ #
147
411
  async def list_resources(self) -> List[Dict[str, Any]]:
148
- if not self._client:
412
+ """List available resources."""
413
+ if not self._initialized.is_set() or not self._message_url:
149
414
  return []
415
+
150
416
  try:
151
- data = await self._get_json("/resources/list")
152
- return data.get("resources", []) if isinstance(data, dict) else []
153
- except Exception: # pragma: no cover
154
- return []
417
+ message = {
418
+ "jsonrpc": "2.0",
419
+ "id": "resources_list",
420
+ "method": "resources/list",
421
+ "params": {}
422
+ }
423
+
424
+ response = await self._send_message(message)
425
+ if "result" in response and "resources" in response["result"]:
426
+ return response["result"]["resources"]
427
+
428
+ except Exception:
429
+ pass
430
+
431
+ return []
155
432
 
156
433
  async def list_prompts(self) -> List[Dict[str, Any]]:
157
- if not self._client:
434
+ """List available prompts."""
435
+ if not self._initialized.is_set() or not self._message_url:
158
436
  return []
437
+
159
438
  try:
160
- data = await self._get_json("/prompts/list")
161
- return data.get("prompts", []) if isinstance(data, dict) else []
162
- except Exception: # pragma: no cover
163
- return []
164
-
165
- # ------------------------------------------------------------------ #
166
- # Background event-stream reader #
167
- # ------------------------------------------------------------------ #
168
- async def _consume_events(self) -> None: # pragma: no cover
169
- """Continuously read `/events` and push JSON objects onto a queue."""
170
- if not self._client:
171
- return
172
-
173
- while True:
174
- try:
175
- async with self._client.stream(
176
- "GET", _url(self.base_url, self.EVENTS_PATH), headers=HEADERS_JSON
177
- ) as resp:
178
- resp.raise_for_status()
179
- async for line in resp.aiter_lines():
180
- if not line:
181
- continue
182
- try:
183
- await self._incoming_queue.put(json.loads(line))
184
- except json.JSONDecodeError:
185
- continue
186
- except asyncio.CancelledError:
187
- break
188
- except Exception:
189
- await asyncio.sleep(1.0) # back-off and retry
439
+ message = {
440
+ "jsonrpc": "2.0",
441
+ "id": "prompts_list",
442
+ "method": "prompts/list",
443
+ "params": {}
444
+ }
445
+
446
+ response = await self._send_message(message)
447
+ if "result" in response and "prompts" in response["result"]:
448
+ return response["result"]["prompts"]
449
+
450
+ except Exception:
451
+ pass
452
+
453
+ return []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chuk-tool-processor
3
- Version: 0.1.7
3
+ Version: 0.3
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -5,8 +5,8 @@ chuk_tool_processor/core/processor.py,sha256=ttEYZTQHctXXiUP8gxAMCCSjbRvyOHojQe_
5
5
  chuk_tool_processor/execution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  chuk_tool_processor/execution/tool_executor.py,sha256=NSzmvqGMMyKuVapJAmPr-YtNgGhZI3fcAxhilyGG5kY,12174
7
7
  chuk_tool_processor/execution/strategies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- chuk_tool_processor/execution/strategies/inprocess_strategy.py,sha256=7Vx8zZ10DSK73tfinahAHElprctIJ1f4WKaR8lJf_Jk,21710
9
- chuk_tool_processor/execution/strategies/subprocess_strategy.py,sha256=6ByvqHhZ5fenrV7yPNRUHeid-htTiVu05Gn0n6ImXg4,20477
8
+ chuk_tool_processor/execution/strategies/inprocess_strategy.py,sha256=UJIv1g3Z9LpMsTYa9cqJB376StsI0up3cftH4OkqC2I,22582
9
+ chuk_tool_processor/execution/strategies/subprocess_strategy.py,sha256=Rb5GTffl-4dkAQG_zz8wjggqyWznVOr9gReLGHmE2io,22469
10
10
  chuk_tool_processor/execution/wrappers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  chuk_tool_processor/execution/wrappers/caching.py,sha256=1pSyouYT4H7AGkNcK_7wWAIT1d4AKnHJlKBODPO8tZw,20416
12
12
  chuk_tool_processor/execution/wrappers/rate_limiting.py,sha256=CBBsI1VLosjo8dZXLeJ3IaclGvy9VdjGyqgunY089KQ,9231
@@ -17,14 +17,14 @@ chuk_tool_processor/logging/formatter.py,sha256=RhlV6NqBYRBOtytDY49c9Y1J4l02ZjNX
17
17
  chuk_tool_processor/logging/helpers.py,sha256=c1mS1sb_rh4bKG0hisyvT7l7cirQfXPSyWeBqmqALRw,5941
18
18
  chuk_tool_processor/logging/metrics.py,sha256=s59Au8q0eqGGtJMDqmJBZhbJHh4BWGE1CzT0iI8lRS8,3624
19
19
  chuk_tool_processor/mcp/__init__.py,sha256=vR9HHxLpXlKTIIwJJRr3QTmZegcdedR1YKyb46j6FIM,689
20
- chuk_tool_processor/mcp/mcp_tool.py,sha256=BcFsW0iVXUpE0o-h3u2hYX-88EmfV80nMUoZPyZs_IY,3242
20
+ chuk_tool_processor/mcp/mcp_tool.py,sha256=a7WnBiu8DaSuZ8RI0Ums4M5A7v46RvijlqZa0020wWg,4922
21
21
  chuk_tool_processor/mcp/register_mcp_tools.py,sha256=0q73gafC1d0ei_gqNidcUeY7NUg13UZdjhVOKEFcD5o,3642
22
- chuk_tool_processor/mcp/setup_mcp_sse.py,sha256=FmXyOG9DuH2pzr1TLTFRFLlW23YkFG8rzSdpybtPCJg,2865
22
+ chuk_tool_processor/mcp/setup_mcp_sse.py,sha256=T0V27azQy06yc-RSc5uzEKyhbyAXFT-7O3pIn4k10HQ,3769
23
23
  chuk_tool_processor/mcp/setup_mcp_stdio.py,sha256=P9qSgmxoNQbsOlGp83DlLLpN9BsG__MhlRsxFplNP3M,2753
24
- chuk_tool_processor/mcp/stream_manager.py,sha256=mrmlG54P_xLbDYz_rBjdu-OPMnbi916dgyJg7BrIbjM,12798
24
+ chuk_tool_processor/mcp/stream_manager.py,sha256=cRnGiuX2A4vHLP91XxFyNKp9Qbv41ImiqMS9F3UlUoA,14030
25
25
  chuk_tool_processor/mcp/transport/__init__.py,sha256=7QQqeSKVKv0N9GcyJuYF0R4FDZeooii5RjggvFFg5GY,296
26
26
  chuk_tool_processor/mcp/transport/base_transport.py,sha256=1E29LjWw5vLQrPUDF_9TJt63P5dxAAN7n6E_KiZbGUY,3427
27
- chuk_tool_processor/mcp/transport/sse_transport.py,sha256=bryH9DOWOn5qr6LsimTriukDC4ix2kuRq6bUv9qOV20,7645
27
+ chuk_tool_processor/mcp/transport/sse_transport.py,sha256=RC_m1relMkr5gQCUbH1z9JB2raZHj2MIEIq9Qyfpw0Y,17696
28
28
  chuk_tool_processor/mcp/transport/stdio_transport.py,sha256=lFXL7p8ca4z_J0RBL8UCHrQ1UH7C2-LbC0tZhpya4V4,7763
29
29
  chuk_tool_processor/models/__init__.py,sha256=TC__rdVa0lQsmJHM_hbLDPRgToa_pQT_UxRcPZk6iVw,40
30
30
  chuk_tool_processor/models/execution_strategy.py,sha256=UVW35YIeMY2B3mpIKZD2rAkyOPayI6ckOOUALyf0YiQ,2115
@@ -52,7 +52,7 @@ chuk_tool_processor/registry/providers/__init__.py,sha256=eigwG_So11j7WbDGSWaKd3
52
52
  chuk_tool_processor/registry/providers/memory.py,sha256=LlpPUU9E7S8Se6Q3VyKxLwpNm82SvmP8GLUmI8MkHxQ,5188
53
53
  chuk_tool_processor/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
54
  chuk_tool_processor/utils/validation.py,sha256=fiTSsHq7zx-kyd755GaFCvPCa-EVasSpg0A1liNHkxU,4138
55
- chuk_tool_processor-0.1.7.dist-info/METADATA,sha256=vqD7WCOAdv5CWGyQv0Hyp3oxWKGDkslRby84vVdCvLw,10165
56
- chuk_tool_processor-0.1.7.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
57
- chuk_tool_processor-0.1.7.dist-info/top_level.txt,sha256=7lTsnuRx4cOW4U2sNJWNxl4ZTt_J1ndkjTbj3pHPY5M,20
58
- chuk_tool_processor-0.1.7.dist-info/RECORD,,
55
+ chuk_tool_processor-0.3.dist-info/METADATA,sha256=9D_wk8oZqFKWnxdjMTcqB9K2bIa0QrBk7Ep_kxqBZZ0,10163
56
+ chuk_tool_processor-0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
57
+ chuk_tool_processor-0.3.dist-info/top_level.txt,sha256=7lTsnuRx4cOW4U2sNJWNxl4ZTt_J1ndkjTbj3pHPY5M,20
58
+ chuk_tool_processor-0.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.4.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5