chuk-tool-processor 0.6.3__tar.gz → 0.6.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of chuk-tool-processor might be problematic. Click here for more details.
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/PKG-INFO +2 -2
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/pyproject.toml +2 -2
- chuk_tool_processor-0.6.4/src/chuk_tool_processor/mcp/transport/sse_transport.py +439 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor.egg-info/PKG-INFO +2 -2
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor.egg-info/requires.txt +1 -1
- chuk_tool_processor-0.6.3/src/chuk_tool_processor/mcp/transport/sse_transport.py +0 -377
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/README.md +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/setup.cfg +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/__init__.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/core/__init__.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/core/exceptions.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/core/processor.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/execution/__init__.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/execution/strategies/__init__.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/execution/strategies/inprocess_strategy.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/execution/strategies/subprocess_strategy.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/execution/tool_executor.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/execution/wrappers/__init__.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/execution/wrappers/caching.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/execution/wrappers/rate_limiting.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/execution/wrappers/retry.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/logging/__init__.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/logging/context.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/logging/formatter.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/logging/helpers.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/logging/metrics.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/mcp/__init__.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/mcp/mcp_tool.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/mcp/register_mcp_tools.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/mcp/setup_mcp_http_streamable.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/mcp/setup_mcp_sse.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/mcp/setup_mcp_stdio.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/mcp/stream_manager.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/mcp/transport/__init__.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/mcp/transport/base_transport.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/mcp/transport/http_streamable_transport.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/mcp/transport/stdio_transport.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/models/__init__.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/models/execution_strategy.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/models/streaming_tool.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/models/tool_call.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/models/tool_export_mixin.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/models/tool_result.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/models/validated_tool.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/plugins/__init__.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/plugins/discovery.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/plugins/parsers/__init__.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/plugins/parsers/base.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/plugins/parsers/function_call_tool.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/plugins/parsers/json_tool.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/plugins/parsers/openai_tool.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/plugins/parsers/xml_tool.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/registry/__init__.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/registry/auto_register.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/registry/decorators.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/registry/interface.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/registry/metadata.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/registry/provider.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/registry/providers/__init__.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/registry/providers/memory.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/registry/tool_export.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/utils/__init__.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/utils/validation.py +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor.egg-info/SOURCES.txt +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor.egg-info/dependency_links.txt +0 -0
- {chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chuk-tool-processor
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
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.1
|
|
24
24
|
Requires-Dist: dotenv>=0.9.9
|
|
25
25
|
Requires-Dist: pydantic>=2.11.3
|
|
26
26
|
Requires-Dist: uuid>=1.30
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "chuk-tool-processor"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.4"
|
|
8
8
|
description = "Async-native framework for registering, discovering, and executing tools referenced in LLM responses"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -41,7 +41,7 @@ classifiers = [
|
|
|
41
41
|
"Typing :: Typed",
|
|
42
42
|
]
|
|
43
43
|
dependencies = [
|
|
44
|
-
"chuk-mcp>=0.5",
|
|
44
|
+
"chuk-mcp>=0.5.1",
|
|
45
45
|
"dotenv>=0.9.9",
|
|
46
46
|
"pydantic>=2.11.3",
|
|
47
47
|
"uuid>=1.30",
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
# chuk_tool_processor/mcp/transport/sse_transport.py
|
|
2
|
+
"""
|
|
3
|
+
Fixed SSE transport that matches your server's actual behavior.
|
|
4
|
+
Based on your working debug script.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
import uuid
|
|
11
|
+
from typing import Dict, Any, List, Optional, Tuple
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
|
|
16
|
+
from .base_transport import MCPBaseTransport
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SSETransport(MCPBaseTransport):
|
|
22
|
+
"""
|
|
23
|
+
SSE transport that works with your server's two-step async pattern:
|
|
24
|
+
1. POST messages to /messages endpoint
|
|
25
|
+
2. Receive responses via SSE stream
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, url: str, api_key: Optional[str] = None,
|
|
29
|
+
connection_timeout: float = 30.0, default_timeout: float = 30.0):
|
|
30
|
+
"""Initialize SSE transport."""
|
|
31
|
+
self.url = url.rstrip('/')
|
|
32
|
+
self.api_key = api_key
|
|
33
|
+
self.connection_timeout = connection_timeout
|
|
34
|
+
self.default_timeout = default_timeout
|
|
35
|
+
|
|
36
|
+
# State
|
|
37
|
+
self.session_id = None
|
|
38
|
+
self.message_url = None
|
|
39
|
+
self.pending_requests: Dict[str, asyncio.Future] = {}
|
|
40
|
+
self._initialized = False
|
|
41
|
+
|
|
42
|
+
# HTTP clients
|
|
43
|
+
self.stream_client = None
|
|
44
|
+
self.send_client = None
|
|
45
|
+
|
|
46
|
+
# SSE stream
|
|
47
|
+
self.sse_task = None
|
|
48
|
+
self.sse_response = None
|
|
49
|
+
self.sse_stream_context = None
|
|
50
|
+
|
|
51
|
+
def _get_headers(self) -> Dict[str, str]:
|
|
52
|
+
"""Get headers with auth if available."""
|
|
53
|
+
headers = {}
|
|
54
|
+
if self.api_key:
|
|
55
|
+
headers['Authorization'] = f'Bearer {self.api_key}'
|
|
56
|
+
return headers
|
|
57
|
+
|
|
58
|
+
async def initialize(self) -> bool:
|
|
59
|
+
"""Initialize SSE connection and MCP handshake."""
|
|
60
|
+
if self._initialized:
|
|
61
|
+
logger.warning("Transport already initialized")
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
logger.info("Initializing SSE transport...")
|
|
66
|
+
|
|
67
|
+
# Create HTTP clients
|
|
68
|
+
self.stream_client = httpx.AsyncClient(timeout=self.connection_timeout)
|
|
69
|
+
self.send_client = httpx.AsyncClient(timeout=self.default_timeout)
|
|
70
|
+
|
|
71
|
+
# Connect to SSE stream
|
|
72
|
+
sse_url = f"{self.url}/sse"
|
|
73
|
+
logger.debug(f"Connecting to SSE: {sse_url}")
|
|
74
|
+
|
|
75
|
+
self.sse_stream_context = self.stream_client.stream(
|
|
76
|
+
'GET', sse_url, headers=self._get_headers()
|
|
77
|
+
)
|
|
78
|
+
self.sse_response = await self.sse_stream_context.__aenter__()
|
|
79
|
+
|
|
80
|
+
if self.sse_response.status_code != 200:
|
|
81
|
+
logger.error(f"SSE connection failed: {self.sse_response.status_code}")
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
logger.info("SSE streaming connection established")
|
|
85
|
+
|
|
86
|
+
# Start SSE processing task
|
|
87
|
+
self.sse_task = asyncio.create_task(self._process_sse_stream())
|
|
88
|
+
|
|
89
|
+
# Wait for session discovery
|
|
90
|
+
logger.debug("Waiting for session discovery...")
|
|
91
|
+
for i in range(50): # 5 seconds max
|
|
92
|
+
if self.message_url:
|
|
93
|
+
break
|
|
94
|
+
await asyncio.sleep(0.1)
|
|
95
|
+
|
|
96
|
+
if not self.message_url:
|
|
97
|
+
logger.error("Failed to get session info from SSE")
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
logger.info(f"Session ready: {self.session_id}")
|
|
101
|
+
|
|
102
|
+
# Now do MCP initialization
|
|
103
|
+
try:
|
|
104
|
+
init_response = await self._send_request("initialize", {
|
|
105
|
+
"protocolVersion": "2024-11-05",
|
|
106
|
+
"capabilities": {},
|
|
107
|
+
"clientInfo": {
|
|
108
|
+
"name": "chuk-tool-processor",
|
|
109
|
+
"version": "1.0.0"
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
if 'error' in init_response:
|
|
114
|
+
logger.error(f"Initialize failed: {init_response['error']}")
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
# Send initialized notification
|
|
118
|
+
await self._send_notification("notifications/initialized")
|
|
119
|
+
|
|
120
|
+
self._initialized = True
|
|
121
|
+
logger.info("SSE transport initialized successfully")
|
|
122
|
+
return True
|
|
123
|
+
|
|
124
|
+
except Exception as e:
|
|
125
|
+
logger.error(f"MCP initialization failed: {e}")
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.error(f"Error initializing SSE transport: {e}", exc_info=True)
|
|
130
|
+
await self._cleanup()
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
async def _process_sse_stream(self):
|
|
134
|
+
"""Process the persistent SSE stream."""
|
|
135
|
+
try:
|
|
136
|
+
logger.debug("Starting SSE stream processing...")
|
|
137
|
+
|
|
138
|
+
async for line in self.sse_response.aiter_lines():
|
|
139
|
+
line = line.strip()
|
|
140
|
+
if not line:
|
|
141
|
+
continue
|
|
142
|
+
|
|
143
|
+
# Handle session endpoint discovery
|
|
144
|
+
if not self.message_url and line.startswith('data:') and '/messages/' in line:
|
|
145
|
+
endpoint_path = line.split(':', 1)[1].strip()
|
|
146
|
+
self.message_url = f"{self.url}{endpoint_path}"
|
|
147
|
+
|
|
148
|
+
if 'session_id=' in endpoint_path:
|
|
149
|
+
self.session_id = endpoint_path.split('session_id=')[1].split('&')[0]
|
|
150
|
+
|
|
151
|
+
logger.debug(f"Got session info: {self.session_id}")
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
# Handle JSON-RPC responses
|
|
155
|
+
if line.startswith('data:'):
|
|
156
|
+
data_part = line.split(':', 1)[1].strip()
|
|
157
|
+
|
|
158
|
+
# Skip pings and empty data
|
|
159
|
+
if not data_part or data_part.startswith('ping'):
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
response_data = json.loads(data_part)
|
|
164
|
+
|
|
165
|
+
if 'jsonrpc' in response_data and 'id' in response_data:
|
|
166
|
+
request_id = str(response_data['id'])
|
|
167
|
+
|
|
168
|
+
# Resolve pending request
|
|
169
|
+
if request_id in self.pending_requests:
|
|
170
|
+
future = self.pending_requests.pop(request_id)
|
|
171
|
+
if not future.done():
|
|
172
|
+
future.set_result(response_data)
|
|
173
|
+
logger.debug(f"Resolved request: {request_id}")
|
|
174
|
+
|
|
175
|
+
except json.JSONDecodeError:
|
|
176
|
+
pass # Not JSON, ignore
|
|
177
|
+
|
|
178
|
+
except Exception as e:
|
|
179
|
+
logger.error(f"SSE stream error: {e}")
|
|
180
|
+
|
|
181
|
+
async def _send_request(self, method: str, params: Dict[str, Any] = None,
|
|
182
|
+
timeout: Optional[float] = None) -> Dict[str, Any]:
|
|
183
|
+
"""Send request and wait for async response."""
|
|
184
|
+
if not self.message_url:
|
|
185
|
+
raise RuntimeError("Not connected")
|
|
186
|
+
|
|
187
|
+
request_id = str(uuid.uuid4())
|
|
188
|
+
message = {
|
|
189
|
+
"jsonrpc": "2.0",
|
|
190
|
+
"id": request_id,
|
|
191
|
+
"method": method,
|
|
192
|
+
"params": params or {}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
# Create future for response
|
|
196
|
+
future = asyncio.Future()
|
|
197
|
+
self.pending_requests[request_id] = future
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
# Send message
|
|
201
|
+
headers = {
|
|
202
|
+
'Content-Type': 'application/json',
|
|
203
|
+
**self._get_headers()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
response = await self.send_client.post(
|
|
207
|
+
self.message_url,
|
|
208
|
+
headers=headers,
|
|
209
|
+
json=message
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if response.status_code == 202:
|
|
213
|
+
# Wait for async response
|
|
214
|
+
timeout = timeout or self.default_timeout
|
|
215
|
+
result = await asyncio.wait_for(future, timeout=timeout)
|
|
216
|
+
return result
|
|
217
|
+
elif response.status_code == 200:
|
|
218
|
+
# Immediate response
|
|
219
|
+
self.pending_requests.pop(request_id, None)
|
|
220
|
+
return response.json()
|
|
221
|
+
else:
|
|
222
|
+
self.pending_requests.pop(request_id, None)
|
|
223
|
+
raise RuntimeError(f"Request failed: {response.status_code}")
|
|
224
|
+
|
|
225
|
+
except asyncio.TimeoutError:
|
|
226
|
+
self.pending_requests.pop(request_id, None)
|
|
227
|
+
raise
|
|
228
|
+
except Exception:
|
|
229
|
+
self.pending_requests.pop(request_id, None)
|
|
230
|
+
raise
|
|
231
|
+
|
|
232
|
+
async def _send_notification(self, method: str, params: Dict[str, Any] = None):
|
|
233
|
+
"""Send notification (no response expected)."""
|
|
234
|
+
if not self.message_url:
|
|
235
|
+
raise RuntimeError("Not connected")
|
|
236
|
+
|
|
237
|
+
message = {
|
|
238
|
+
"jsonrpc": "2.0",
|
|
239
|
+
"method": method,
|
|
240
|
+
"params": params or {}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
headers = {
|
|
244
|
+
'Content-Type': 'application/json',
|
|
245
|
+
**self._get_headers()
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
await self.send_client.post(
|
|
249
|
+
self.message_url,
|
|
250
|
+
headers=headers,
|
|
251
|
+
json=message
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
async def send_ping(self) -> bool:
|
|
255
|
+
"""Send ping to check connection."""
|
|
256
|
+
if not self._initialized:
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
# Your server might not support ping, so we'll just check if we can list tools
|
|
261
|
+
response = await self._send_request("tools/list", {}, timeout=5.0)
|
|
262
|
+
return 'error' not in response
|
|
263
|
+
except Exception:
|
|
264
|
+
return False
|
|
265
|
+
|
|
266
|
+
async def get_tools(self) -> List[Dict[str, Any]]:
|
|
267
|
+
"""Get tools list."""
|
|
268
|
+
if not self._initialized:
|
|
269
|
+
logger.error("Cannot get tools: transport not initialized")
|
|
270
|
+
return []
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
response = await self._send_request("tools/list", {})
|
|
274
|
+
|
|
275
|
+
if 'error' in response:
|
|
276
|
+
logger.error(f"Error getting tools: {response['error']}")
|
|
277
|
+
return []
|
|
278
|
+
|
|
279
|
+
tools = response.get('result', {}).get('tools', [])
|
|
280
|
+
logger.debug(f"Retrieved {len(tools)} tools")
|
|
281
|
+
return tools
|
|
282
|
+
|
|
283
|
+
except Exception as e:
|
|
284
|
+
logger.error(f"Error getting tools: {e}")
|
|
285
|
+
return []
|
|
286
|
+
|
|
287
|
+
async def call_tool(self, tool_name: str, arguments: Dict[str, Any],
|
|
288
|
+
timeout: Optional[float] = None) -> Dict[str, Any]:
|
|
289
|
+
"""Call a tool."""
|
|
290
|
+
if not self._initialized:
|
|
291
|
+
return {
|
|
292
|
+
"isError": True,
|
|
293
|
+
"error": "Transport not initialized"
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
try:
|
|
297
|
+
logger.debug(f"Calling tool {tool_name} with args: {arguments}")
|
|
298
|
+
|
|
299
|
+
response = await self._send_request(
|
|
300
|
+
"tools/call",
|
|
301
|
+
{
|
|
302
|
+
"name": tool_name,
|
|
303
|
+
"arguments": arguments
|
|
304
|
+
},
|
|
305
|
+
timeout=timeout
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
if 'error' in response:
|
|
309
|
+
return {
|
|
310
|
+
"isError": True,
|
|
311
|
+
"error": response['error'].get('message', 'Unknown error')
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
# Extract result
|
|
315
|
+
result = response.get('result', {})
|
|
316
|
+
|
|
317
|
+
# Handle content format
|
|
318
|
+
if 'content' in result:
|
|
319
|
+
content = result['content']
|
|
320
|
+
if isinstance(content, list) and len(content) == 1:
|
|
321
|
+
content_item = content[0]
|
|
322
|
+
if isinstance(content_item, dict) and content_item.get('type') == 'text':
|
|
323
|
+
text_content = content_item.get('text', '')
|
|
324
|
+
try:
|
|
325
|
+
# Try to parse as JSON
|
|
326
|
+
parsed_content = json.loads(text_content)
|
|
327
|
+
return {
|
|
328
|
+
"isError": False,
|
|
329
|
+
"content": parsed_content
|
|
330
|
+
}
|
|
331
|
+
except json.JSONDecodeError:
|
|
332
|
+
return {
|
|
333
|
+
"isError": False,
|
|
334
|
+
"content": text_content
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
"isError": False,
|
|
339
|
+
"content": content
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
"isError": False,
|
|
344
|
+
"content": result
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
except asyncio.TimeoutError:
|
|
348
|
+
return {
|
|
349
|
+
"isError": True,
|
|
350
|
+
"error": f"Tool execution timed out"
|
|
351
|
+
}
|
|
352
|
+
except Exception as e:
|
|
353
|
+
logger.error(f"Error calling tool {tool_name}: {e}")
|
|
354
|
+
return {
|
|
355
|
+
"isError": True,
|
|
356
|
+
"error": str(e)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async def list_resources(self) -> Dict[str, Any]:
|
|
360
|
+
"""List resources."""
|
|
361
|
+
if not self._initialized:
|
|
362
|
+
return {}
|
|
363
|
+
|
|
364
|
+
try:
|
|
365
|
+
response = await self._send_request("resources/list", {}, timeout=10.0)
|
|
366
|
+
if 'error' in response:
|
|
367
|
+
logger.debug(f"Resources not supported: {response['error']}")
|
|
368
|
+
return {}
|
|
369
|
+
return response.get('result', {})
|
|
370
|
+
except Exception:
|
|
371
|
+
return {}
|
|
372
|
+
|
|
373
|
+
async def list_prompts(self) -> Dict[str, Any]:
|
|
374
|
+
"""List prompts."""
|
|
375
|
+
if not self._initialized:
|
|
376
|
+
return {}
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
response = await self._send_request("prompts/list", {}, timeout=10.0)
|
|
380
|
+
if 'error' in response:
|
|
381
|
+
logger.debug(f"Prompts not supported: {response['error']}")
|
|
382
|
+
return {}
|
|
383
|
+
return response.get('result', {})
|
|
384
|
+
except Exception:
|
|
385
|
+
return {}
|
|
386
|
+
|
|
387
|
+
async def close(self) -> None:
|
|
388
|
+
"""Close the transport."""
|
|
389
|
+
await self._cleanup()
|
|
390
|
+
|
|
391
|
+
async def _cleanup(self) -> None:
|
|
392
|
+
"""Clean up resources."""
|
|
393
|
+
if self.sse_task:
|
|
394
|
+
self.sse_task.cancel()
|
|
395
|
+
try:
|
|
396
|
+
await self.sse_task
|
|
397
|
+
except asyncio.CancelledError:
|
|
398
|
+
pass
|
|
399
|
+
|
|
400
|
+
if self.sse_stream_context:
|
|
401
|
+
try:
|
|
402
|
+
await self.sse_stream_context.__aexit__(None, None, None)
|
|
403
|
+
except Exception:
|
|
404
|
+
pass
|
|
405
|
+
|
|
406
|
+
if self.stream_client:
|
|
407
|
+
await self.stream_client.aclose()
|
|
408
|
+
|
|
409
|
+
if self.send_client:
|
|
410
|
+
await self.send_client.aclose()
|
|
411
|
+
|
|
412
|
+
self._initialized = False
|
|
413
|
+
self.session_id = None
|
|
414
|
+
self.message_url = None
|
|
415
|
+
self.pending_requests.clear()
|
|
416
|
+
|
|
417
|
+
def get_streams(self) -> List[tuple]:
|
|
418
|
+
"""Not applicable for this transport."""
|
|
419
|
+
return []
|
|
420
|
+
|
|
421
|
+
def is_connected(self) -> bool:
|
|
422
|
+
"""Check if connected."""
|
|
423
|
+
return self._initialized and self.session_id is not None
|
|
424
|
+
|
|
425
|
+
async def __aenter__(self):
|
|
426
|
+
"""Context manager support."""
|
|
427
|
+
success = await self.initialize()
|
|
428
|
+
if not success:
|
|
429
|
+
raise RuntimeError("Failed to initialize SSE transport")
|
|
430
|
+
return self
|
|
431
|
+
|
|
432
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
433
|
+
"""Context manager cleanup."""
|
|
434
|
+
await self.close()
|
|
435
|
+
|
|
436
|
+
def __repr__(self) -> str:
|
|
437
|
+
"""String representation."""
|
|
438
|
+
status = "initialized" if self._initialized else "not initialized"
|
|
439
|
+
return f"SSETransport(status={status}, url={self.url}, session={self.session_id})"
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chuk-tool-processor
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
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.1
|
|
24
24
|
Requires-Dist: dotenv>=0.9.9
|
|
25
25
|
Requires-Dist: pydantic>=2.11.3
|
|
26
26
|
Requires-Dist: uuid>=1.30
|
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
# chuk_tool_processor/mcp/transport/sse_transport.py
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import asyncio
|
|
5
|
-
import json
|
|
6
|
-
from typing import Dict, Any, List, Optional
|
|
7
|
-
import logging
|
|
8
|
-
|
|
9
|
-
from .base_transport import MCPBaseTransport
|
|
10
|
-
|
|
11
|
-
# Import latest chuk-mcp SSE transport
|
|
12
|
-
try:
|
|
13
|
-
from chuk_mcp.transports.sse import sse_client
|
|
14
|
-
from chuk_mcp.transports.sse.parameters import SSEParameters
|
|
15
|
-
from chuk_mcp.protocol.messages import (
|
|
16
|
-
send_initialize,
|
|
17
|
-
send_ping,
|
|
18
|
-
send_tools_list,
|
|
19
|
-
send_tools_call,
|
|
20
|
-
)
|
|
21
|
-
HAS_SSE_SUPPORT = True
|
|
22
|
-
except ImportError:
|
|
23
|
-
HAS_SSE_SUPPORT = False
|
|
24
|
-
|
|
25
|
-
# Import optional resource and prompt support
|
|
26
|
-
try:
|
|
27
|
-
from chuk_mcp.protocol.messages import (
|
|
28
|
-
send_resources_list,
|
|
29
|
-
send_resources_read,
|
|
30
|
-
send_prompts_list,
|
|
31
|
-
send_prompts_get,
|
|
32
|
-
)
|
|
33
|
-
HAS_RESOURCES_PROMPTS = True
|
|
34
|
-
except ImportError:
|
|
35
|
-
HAS_RESOURCES_PROMPTS = False
|
|
36
|
-
|
|
37
|
-
logger = logging.getLogger(__name__)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class SSETransport(MCPBaseTransport):
|
|
41
|
-
"""
|
|
42
|
-
Updated SSE transport using latest chuk-mcp APIs.
|
|
43
|
-
|
|
44
|
-
Supports all required abstract methods and provides full MCP functionality.
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
|
-
def __init__(self, url: str, api_key: Optional[str] = None,
|
|
48
|
-
connection_timeout: float = 30.0, default_timeout: float = 30.0):
|
|
49
|
-
"""
|
|
50
|
-
Initialize SSE transport with latest chuk-mcp.
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
url: SSE server URL
|
|
54
|
-
api_key: Optional API key for authentication
|
|
55
|
-
connection_timeout: Timeout for initial connection
|
|
56
|
-
default_timeout: Default timeout for operations
|
|
57
|
-
"""
|
|
58
|
-
self.url = url
|
|
59
|
-
self.api_key = api_key
|
|
60
|
-
self.connection_timeout = connection_timeout
|
|
61
|
-
self.default_timeout = default_timeout
|
|
62
|
-
|
|
63
|
-
# State tracking
|
|
64
|
-
self._sse_context = None
|
|
65
|
-
self._read_stream = None
|
|
66
|
-
self._write_stream = None
|
|
67
|
-
self._initialized = False
|
|
68
|
-
|
|
69
|
-
if not HAS_SSE_SUPPORT:
|
|
70
|
-
logger.warning("SSE transport not available - operations will fail")
|
|
71
|
-
|
|
72
|
-
async def initialize(self) -> bool:
|
|
73
|
-
"""Initialize using latest chuk-mcp sse_client."""
|
|
74
|
-
if not HAS_SSE_SUPPORT:
|
|
75
|
-
logger.error("SSE transport not available in chuk-mcp")
|
|
76
|
-
return False
|
|
77
|
-
|
|
78
|
-
if self._initialized:
|
|
79
|
-
logger.warning("Transport already initialized")
|
|
80
|
-
return True
|
|
81
|
-
|
|
82
|
-
try:
|
|
83
|
-
logger.info("Initializing SSE transport...")
|
|
84
|
-
|
|
85
|
-
# Create SSE parameters for latest chuk-mcp
|
|
86
|
-
sse_params = SSEParameters(
|
|
87
|
-
url=self.url,
|
|
88
|
-
timeout=self.connection_timeout,
|
|
89
|
-
auto_reconnect=True,
|
|
90
|
-
max_reconnect_attempts=3
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
# Create and enter the context - this should handle the full MCP handshake
|
|
94
|
-
self._sse_context = sse_client(sse_params)
|
|
95
|
-
|
|
96
|
-
# The sse_client should handle the entire initialization process
|
|
97
|
-
logger.debug("Establishing SSE connection and MCP handshake...")
|
|
98
|
-
self._read_stream, self._write_stream = await asyncio.wait_for(
|
|
99
|
-
self._sse_context.__aenter__(),
|
|
100
|
-
timeout=self.connection_timeout
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
# At this point, chuk-mcp should have already completed the MCP initialization
|
|
104
|
-
# Let's verify the connection works with a simple ping
|
|
105
|
-
logger.debug("Verifying connection with ping...")
|
|
106
|
-
ping_success = await asyncio.wait_for(
|
|
107
|
-
send_ping(self._read_stream, self._write_stream),
|
|
108
|
-
timeout=5.0
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
if ping_success:
|
|
112
|
-
self._initialized = True
|
|
113
|
-
logger.info("SSE transport initialized successfully")
|
|
114
|
-
return True
|
|
115
|
-
else:
|
|
116
|
-
logger.warning("SSE connection established but ping failed")
|
|
117
|
-
# Still consider it initialized since connection was established
|
|
118
|
-
self._initialized = True
|
|
119
|
-
return True
|
|
120
|
-
|
|
121
|
-
except asyncio.TimeoutError:
|
|
122
|
-
logger.error(f"SSE initialization timed out after {self.connection_timeout}s")
|
|
123
|
-
logger.error("This may indicate the server is not responding to MCP initialization")
|
|
124
|
-
await self._cleanup()
|
|
125
|
-
return False
|
|
126
|
-
except Exception as e:
|
|
127
|
-
logger.error(f"Error initializing SSE transport: {e}", exc_info=True)
|
|
128
|
-
await self._cleanup()
|
|
129
|
-
return False
|
|
130
|
-
|
|
131
|
-
async def close(self) -> None:
|
|
132
|
-
"""Close the SSE transport properly."""
|
|
133
|
-
if not self._initialized:
|
|
134
|
-
return
|
|
135
|
-
|
|
136
|
-
try:
|
|
137
|
-
if self._sse_context is not None:
|
|
138
|
-
await self._sse_context.__aexit__(None, None, None)
|
|
139
|
-
logger.debug("SSE context closed")
|
|
140
|
-
|
|
141
|
-
except Exception as e:
|
|
142
|
-
logger.debug(f"Error during transport close: {e}")
|
|
143
|
-
finally:
|
|
144
|
-
await self._cleanup()
|
|
145
|
-
|
|
146
|
-
async def _cleanup(self) -> None:
|
|
147
|
-
"""Clean up internal state."""
|
|
148
|
-
self._sse_context = None
|
|
149
|
-
self._read_stream = None
|
|
150
|
-
self._write_stream = None
|
|
151
|
-
self._initialized = False
|
|
152
|
-
|
|
153
|
-
async def send_ping(self) -> bool:
|
|
154
|
-
"""Send ping using latest chuk-mcp."""
|
|
155
|
-
if not self._initialized:
|
|
156
|
-
logger.error("Cannot send ping: transport not initialized")
|
|
157
|
-
return False
|
|
158
|
-
|
|
159
|
-
try:
|
|
160
|
-
result = await asyncio.wait_for(
|
|
161
|
-
send_ping(self._read_stream, self._write_stream),
|
|
162
|
-
timeout=self.default_timeout
|
|
163
|
-
)
|
|
164
|
-
logger.debug(f"Ping result: {result}")
|
|
165
|
-
return bool(result)
|
|
166
|
-
except asyncio.TimeoutError:
|
|
167
|
-
logger.error("Ping timed out")
|
|
168
|
-
return False
|
|
169
|
-
except Exception as e:
|
|
170
|
-
logger.error(f"Ping failed: {e}")
|
|
171
|
-
return False
|
|
172
|
-
|
|
173
|
-
async def get_tools(self) -> List[Dict[str, Any]]:
|
|
174
|
-
"""Get tools list using latest chuk-mcp."""
|
|
175
|
-
if not self._initialized:
|
|
176
|
-
logger.error("Cannot get tools: transport not initialized")
|
|
177
|
-
return []
|
|
178
|
-
|
|
179
|
-
try:
|
|
180
|
-
tools_response = await asyncio.wait_for(
|
|
181
|
-
send_tools_list(self._read_stream, self._write_stream),
|
|
182
|
-
timeout=self.default_timeout
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
# Normalize response
|
|
186
|
-
if isinstance(tools_response, dict):
|
|
187
|
-
tools = tools_response.get("tools", [])
|
|
188
|
-
elif isinstance(tools_response, list):
|
|
189
|
-
tools = tools_response
|
|
190
|
-
else:
|
|
191
|
-
logger.warning(f"Unexpected tools response type: {type(tools_response)}")
|
|
192
|
-
tools = []
|
|
193
|
-
|
|
194
|
-
logger.debug(f"Retrieved {len(tools)} tools")
|
|
195
|
-
return tools
|
|
196
|
-
|
|
197
|
-
except asyncio.TimeoutError:
|
|
198
|
-
logger.error("Get tools timed out")
|
|
199
|
-
return []
|
|
200
|
-
except Exception as e:
|
|
201
|
-
logger.error(f"Error getting tools: {e}")
|
|
202
|
-
return []
|
|
203
|
-
|
|
204
|
-
async def call_tool(self, tool_name: str, arguments: Dict[str, Any],
|
|
205
|
-
timeout: Optional[float] = None) -> Dict[str, Any]:
|
|
206
|
-
"""Call tool using latest chuk-mcp."""
|
|
207
|
-
if not self._initialized:
|
|
208
|
-
return {
|
|
209
|
-
"isError": True,
|
|
210
|
-
"error": "Transport not initialized"
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
tool_timeout = timeout or self.default_timeout
|
|
214
|
-
|
|
215
|
-
try:
|
|
216
|
-
logger.debug(f"Calling tool {tool_name} with args: {arguments}")
|
|
217
|
-
|
|
218
|
-
raw_response = await asyncio.wait_for(
|
|
219
|
-
send_tools_call(
|
|
220
|
-
self._read_stream,
|
|
221
|
-
self._write_stream,
|
|
222
|
-
tool_name,
|
|
223
|
-
arguments
|
|
224
|
-
),
|
|
225
|
-
timeout=tool_timeout
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
logger.debug(f"Tool {tool_name} raw response: {raw_response}")
|
|
229
|
-
return self._normalize_tool_response(raw_response)
|
|
230
|
-
|
|
231
|
-
except asyncio.TimeoutError:
|
|
232
|
-
logger.error(f"Tool {tool_name} timed out after {tool_timeout}s")
|
|
233
|
-
return {
|
|
234
|
-
"isError": True,
|
|
235
|
-
"error": f"Tool execution timed out after {tool_timeout}s"
|
|
236
|
-
}
|
|
237
|
-
except Exception as e:
|
|
238
|
-
logger.error(f"Error calling tool {tool_name}: {e}")
|
|
239
|
-
return {
|
|
240
|
-
"isError": True,
|
|
241
|
-
"error": f"Tool execution failed: {str(e)}"
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
async def list_resources(self) -> Dict[str, Any]:
|
|
245
|
-
"""List resources using latest chuk-mcp."""
|
|
246
|
-
if not HAS_RESOURCES_PROMPTS:
|
|
247
|
-
logger.debug("Resources/prompts not available in chuk-mcp")
|
|
248
|
-
return {}
|
|
249
|
-
|
|
250
|
-
if not self._initialized:
|
|
251
|
-
return {}
|
|
252
|
-
|
|
253
|
-
try:
|
|
254
|
-
response = await asyncio.wait_for(
|
|
255
|
-
send_resources_list(self._read_stream, self._write_stream),
|
|
256
|
-
timeout=self.default_timeout
|
|
257
|
-
)
|
|
258
|
-
return response if isinstance(response, dict) else {}
|
|
259
|
-
except asyncio.TimeoutError:
|
|
260
|
-
logger.error("List resources timed out")
|
|
261
|
-
return {}
|
|
262
|
-
except Exception as e:
|
|
263
|
-
logger.debug(f"Error listing resources: {e}")
|
|
264
|
-
return {}
|
|
265
|
-
|
|
266
|
-
async def list_prompts(self) -> Dict[str, Any]:
|
|
267
|
-
"""List prompts using latest chuk-mcp."""
|
|
268
|
-
if not HAS_RESOURCES_PROMPTS:
|
|
269
|
-
logger.debug("Resources/prompts not available in chuk-mcp")
|
|
270
|
-
return {}
|
|
271
|
-
|
|
272
|
-
if not self._initialized:
|
|
273
|
-
return {}
|
|
274
|
-
|
|
275
|
-
try:
|
|
276
|
-
response = await asyncio.wait_for(
|
|
277
|
-
send_prompts_list(self._read_stream, self._write_stream),
|
|
278
|
-
timeout=self.default_timeout
|
|
279
|
-
)
|
|
280
|
-
return response if isinstance(response, dict) else {}
|
|
281
|
-
except asyncio.TimeoutError:
|
|
282
|
-
logger.error("List prompts timed out")
|
|
283
|
-
return {}
|
|
284
|
-
except Exception as e:
|
|
285
|
-
logger.debug(f"Error listing prompts: {e}")
|
|
286
|
-
return {}
|
|
287
|
-
|
|
288
|
-
def _normalize_tool_response(self, raw_response: Dict[str, Any]) -> Dict[str, Any]:
|
|
289
|
-
"""Normalize response for backward compatibility."""
|
|
290
|
-
# Handle explicit error in response
|
|
291
|
-
if "error" in raw_response:
|
|
292
|
-
error_info = raw_response["error"]
|
|
293
|
-
if isinstance(error_info, dict):
|
|
294
|
-
error_msg = error_info.get("message", "Unknown error")
|
|
295
|
-
else:
|
|
296
|
-
error_msg = str(error_info)
|
|
297
|
-
|
|
298
|
-
return {
|
|
299
|
-
"isError": True,
|
|
300
|
-
"error": error_msg
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
# Handle successful response with result
|
|
304
|
-
if "result" in raw_response:
|
|
305
|
-
result = raw_response["result"]
|
|
306
|
-
|
|
307
|
-
if isinstance(result, dict) and "content" in result:
|
|
308
|
-
return {
|
|
309
|
-
"isError": False,
|
|
310
|
-
"content": self._extract_content(result["content"])
|
|
311
|
-
}
|
|
312
|
-
else:
|
|
313
|
-
return {
|
|
314
|
-
"isError": False,
|
|
315
|
-
"content": result
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
# Handle direct content-based response
|
|
319
|
-
if "content" in raw_response:
|
|
320
|
-
return {
|
|
321
|
-
"isError": False,
|
|
322
|
-
"content": self._extract_content(raw_response["content"])
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
# Fallback
|
|
326
|
-
return {
|
|
327
|
-
"isError": False,
|
|
328
|
-
"content": raw_response
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
def _extract_content(self, content_list: Any) -> Any:
|
|
332
|
-
"""Extract content from MCP content format."""
|
|
333
|
-
if not isinstance(content_list, list) or not content_list:
|
|
334
|
-
return content_list
|
|
335
|
-
|
|
336
|
-
# Handle single content item
|
|
337
|
-
if len(content_list) == 1:
|
|
338
|
-
content_item = content_list[0]
|
|
339
|
-
if isinstance(content_item, dict):
|
|
340
|
-
if content_item.get("type") == "text":
|
|
341
|
-
text_content = content_item.get("text", "")
|
|
342
|
-
# Try to parse JSON, fall back to plain text
|
|
343
|
-
try:
|
|
344
|
-
return json.loads(text_content)
|
|
345
|
-
except json.JSONDecodeError:
|
|
346
|
-
return text_content
|
|
347
|
-
else:
|
|
348
|
-
return content_item
|
|
349
|
-
|
|
350
|
-
# Multiple content items
|
|
351
|
-
return content_list
|
|
352
|
-
|
|
353
|
-
def get_streams(self) -> List[tuple]:
|
|
354
|
-
"""Provide streams for backward compatibility."""
|
|
355
|
-
if self._initialized and self._read_stream and self._write_stream:
|
|
356
|
-
return [(self._read_stream, self._write_stream)]
|
|
357
|
-
return []
|
|
358
|
-
|
|
359
|
-
def is_connected(self) -> bool:
|
|
360
|
-
"""Check connection status."""
|
|
361
|
-
return self._initialized and self._read_stream is not None and self._write_stream is not None
|
|
362
|
-
|
|
363
|
-
async def __aenter__(self):
|
|
364
|
-
"""Context manager support."""
|
|
365
|
-
success = await self.initialize()
|
|
366
|
-
if not success:
|
|
367
|
-
raise RuntimeError("Failed to initialize SSE transport")
|
|
368
|
-
return self
|
|
369
|
-
|
|
370
|
-
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
371
|
-
"""Context manager cleanup."""
|
|
372
|
-
await self.close()
|
|
373
|
-
|
|
374
|
-
def __repr__(self) -> str:
|
|
375
|
-
"""String representation for debugging."""
|
|
376
|
-
status = "initialized" if self._initialized else "not initialized"
|
|
377
|
-
return f"SSETransport(status={status}, url={self.url})"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/core/__init__.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/core/exceptions.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/core/processor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/logging/__init__.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/logging/context.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/logging/formatter.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/logging/helpers.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/logging/metrics.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/mcp/__init__.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/mcp/mcp_tool.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/mcp/setup_mcp_sse.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/models/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/models/tool_call.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/plugins/__init__.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/plugins/discovery.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/registry/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/registry/metadata.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/registry/provider.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/utils/__init__.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor/utils/validation.py
RENAMED
|
File without changes
|
{chuk_tool_processor-0.6.3 → chuk_tool_processor-0.6.4}/src/chuk_tool_processor.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|