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.
- chuk_tool_processor/execution/strategies/inprocess_strategy.py +75 -68
- chuk_tool_processor/execution/strategies/subprocess_strategy.py +54 -19
- chuk_tool_processor/mcp/mcp_tool.py +46 -9
- chuk_tool_processor/mcp/setup_mcp_sse.py +21 -1
- chuk_tool_processor/mcp/stream_manager.py +34 -2
- chuk_tool_processor/mcp/transport/sse_transport.py +369 -105
- {chuk_tool_processor-0.1.7.dist-info → chuk_tool_processor-0.3.dist-info}/METADATA +1 -1
- {chuk_tool_processor-0.1.7.dist-info → chuk_tool_processor-0.3.dist-info}/RECORD +10 -10
- {chuk_tool_processor-0.1.7.dist-info → chuk_tool_processor-0.3.dist-info}/WHEEL +1 -1
- {chuk_tool_processor-0.1.7.dist-info → chuk_tool_processor-0.3.dist-info}/top_level.txt +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
63
|
+
self.session: httpx.AsyncClient | None = None
|
|
55
64
|
|
|
56
|
-
#
|
|
57
|
-
self.
|
|
58
|
-
self.
|
|
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
|
-
"""
|
|
65
|
-
if self._client:
|
|
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=
|
|
94
|
+
headers=headers,
|
|
70
95
|
timeout=DEFAULT_TIMEOUT,
|
|
71
96
|
)
|
|
72
|
-
self.session = self._client
|
|
97
|
+
self.session = self._client
|
|
73
98
|
|
|
74
|
-
#
|
|
75
|
-
self.
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
"""
|
|
82
|
-
|
|
83
|
-
|
|
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.
|
|
86
|
-
self.
|
|
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
|
|
205
|
+
self.session = None
|
|
92
206
|
|
|
93
207
|
# ------------------------------------------------------------------ #
|
|
94
|
-
#
|
|
208
|
+
# SSE Connection Handler #
|
|
95
209
|
# ------------------------------------------------------------------ #
|
|
96
|
-
async def
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
#
|
|
276
|
+
# MCP Protocol Methods #
|
|
116
277
|
# ------------------------------------------------------------------ #
|
|
117
278
|
async def send_ping(self) -> bool:
|
|
118
|
-
if
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
#
|
|
408
|
+
# ------------------------------------------------------------------ #
|
|
409
|
+
# Additional MCP methods #
|
|
410
|
+
# ------------------------------------------------------------------ #
|
|
147
411
|
async def list_resources(self) -> List[Dict[str, Any]]:
|
|
148
|
-
|
|
412
|
+
"""List available resources."""
|
|
413
|
+
if not self._initialized.is_set() or not self._message_url:
|
|
149
414
|
return []
|
|
415
|
+
|
|
150
416
|
try:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
434
|
+
"""List available prompts."""
|
|
435
|
+
if not self._initialized.is_set() or not self._message_url:
|
|
158
436
|
return []
|
|
437
|
+
|
|
159
438
|
try:
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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 []
|
|
@@ -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=
|
|
9
|
-
chuk_tool_processor/execution/strategies/subprocess_strategy.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
56
|
-
chuk_tool_processor-0.
|
|
57
|
-
chuk_tool_processor-0.
|
|
58
|
-
chuk_tool_processor-0.
|
|
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,,
|