chuk-tool-processor 0.2__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/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 +19 -1
- {chuk_tool_processor-0.2.dist-info → chuk_tool_processor-0.3.dist-info}/METADATA +1 -1
- {chuk_tool_processor-0.2.dist-info → chuk_tool_processor-0.3.dist-info}/RECORD +8 -8
- {chuk_tool_processor-0.2.dist-info → chuk_tool_processor-0.3.dist-info}/WHEEL +0 -0
- {chuk_tool_processor-0.2.dist-info → chuk_tool_processor-0.3.dist-info}/top_level.txt +0 -0
|
@@ -36,9 +36,11 @@ class MCPTool:
|
|
|
36
36
|
servers: Optional[List[str]] = None,
|
|
37
37
|
server_names: Optional[Dict[int, str]] = None,
|
|
38
38
|
namespace: str = "stdio",
|
|
39
|
+
default_timeout: Optional[float] = None, # Add default timeout support
|
|
39
40
|
) -> None:
|
|
40
41
|
self.tool_name = tool_name
|
|
41
42
|
self._sm: Optional[StreamManager] = stream_manager
|
|
43
|
+
self.default_timeout = default_timeout or 30.0 # Default to 30s if not specified
|
|
42
44
|
|
|
43
45
|
# Boot-strap parameters (only needed if _sm is None)
|
|
44
46
|
self._cfg_file = cfg_file
|
|
@@ -78,21 +80,56 @@ class MCPTool:
|
|
|
78
80
|
return self._sm # type: ignore[return-value]
|
|
79
81
|
|
|
80
82
|
# ------------------------------------------------------------------ #
|
|
81
|
-
async def execute(self, **kwargs: Any) -> Any:
|
|
83
|
+
async def execute(self, timeout: Optional[float] = None, **kwargs: Any) -> Any:
|
|
82
84
|
"""
|
|
83
|
-
Forward the call to the remote MCP tool.
|
|
85
|
+
Forward the call to the remote MCP tool with timeout support.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
timeout: Optional timeout for this specific call. If not provided,
|
|
89
|
+
uses the instance's default_timeout.
|
|
90
|
+
**kwargs: Arguments to pass to the MCP tool.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
The result from the MCP tool call.
|
|
84
94
|
|
|
85
95
|
Raises
|
|
86
96
|
------
|
|
87
97
|
RuntimeError
|
|
88
98
|
If the server returns an error payload.
|
|
99
|
+
asyncio.TimeoutError
|
|
100
|
+
If the call times out.
|
|
89
101
|
"""
|
|
90
102
|
sm = await self._ensure_stream_manager()
|
|
91
|
-
|
|
103
|
+
|
|
104
|
+
# Use provided timeout, fall back to instance default, then global default
|
|
105
|
+
effective_timeout = timeout if timeout is not None else self.default_timeout
|
|
106
|
+
|
|
107
|
+
logger.debug("Calling MCP tool '%s' with timeout: %ss", self.tool_name, effective_timeout)
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
# Pass timeout directly to StreamManager instead of wrapping with wait_for
|
|
111
|
+
result = await sm.call_tool(
|
|
112
|
+
tool_name=self.tool_name,
|
|
113
|
+
arguments=kwargs,
|
|
114
|
+
timeout=effective_timeout
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if result.get("isError"):
|
|
118
|
+
err = result.get("error", "Unknown error")
|
|
119
|
+
logger.error("Remote MCP error from '%s': %s", self.tool_name, err)
|
|
120
|
+
raise RuntimeError(err)
|
|
121
|
+
|
|
122
|
+
return result.get("content")
|
|
123
|
+
|
|
124
|
+
except asyncio.TimeoutError:
|
|
125
|
+
logger.warning("MCP tool '%s' timed out after %ss", self.tool_name, effective_timeout)
|
|
126
|
+
raise
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logger.error("Error calling MCP tool '%s': %s", self.tool_name, e)
|
|
129
|
+
raise
|
|
92
130
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return result.get("content")
|
|
131
|
+
# ------------------------------------------------------------------ #
|
|
132
|
+
# Legacy method name support
|
|
133
|
+
async def _aexecute(self, timeout: Optional[float] = None, **kwargs: Any) -> Any:
|
|
134
|
+
"""Legacy alias for execute() method."""
|
|
135
|
+
return await self.execute(timeout=timeout, **kwargs)
|
|
@@ -14,6 +14,7 @@ Utility that wires up:
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
+
import os
|
|
17
18
|
from typing import Dict, List, Optional, Tuple
|
|
18
19
|
|
|
19
20
|
from chuk_tool_processor.core.processor import ToolProcessor
|
|
@@ -47,7 +48,26 @@ async def setup_mcp_sse( # noqa: C901 – long, but just a config wrapper
|
|
|
47
48
|
and return a ready-to-go :class:`ToolProcessor`.
|
|
48
49
|
|
|
49
50
|
Everything is **async-native** – call with ``await``.
|
|
51
|
+
|
|
52
|
+
NEW: Automatically detects and adds bearer token from MCP_BEARER_TOKEN
|
|
53
|
+
environment variable if not explicitly provided in server config.
|
|
50
54
|
"""
|
|
55
|
+
|
|
56
|
+
# NEW: Auto-detect and add bearer token to servers if available
|
|
57
|
+
bearer_token = os.getenv("MCP_BEARER_TOKEN")
|
|
58
|
+
if bearer_token:
|
|
59
|
+
logger.info("Found MCP_BEARER_TOKEN environment variable, adding to server configs")
|
|
60
|
+
|
|
61
|
+
# Add api_key to servers that don't already have it
|
|
62
|
+
enhanced_servers = []
|
|
63
|
+
for server in servers:
|
|
64
|
+
enhanced_server = dict(server) # Make a copy
|
|
65
|
+
if "api_key" not in enhanced_server and bearer_token:
|
|
66
|
+
enhanced_server["api_key"] = bearer_token
|
|
67
|
+
logger.info("Added bearer token to server: %s", enhanced_server.get("name", "unnamed"))
|
|
68
|
+
enhanced_servers.append(enhanced_server)
|
|
69
|
+
servers = enhanced_servers
|
|
70
|
+
|
|
51
71
|
# 1️⃣ connect to the remote MCP servers
|
|
52
72
|
stream_manager = await StreamManager.create_with_sse(
|
|
53
73
|
servers=servers,
|
|
@@ -76,4 +96,4 @@ async def setup_mcp_sse( # noqa: C901 – long, but just a config wrapper
|
|
|
76
96
|
"" if len(registered) == 1 else "s",
|
|
77
97
|
namespace,
|
|
78
98
|
)
|
|
79
|
-
return processor, stream_manager
|
|
99
|
+
return processor, stream_manager
|
|
@@ -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
|
# ------------------------------------------------------------------ #
|
|
@@ -14,6 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
import asyncio
|
|
15
15
|
import contextlib
|
|
16
16
|
import json
|
|
17
|
+
import os
|
|
17
18
|
from typing import Any, Dict, List, Optional
|
|
18
19
|
|
|
19
20
|
import httpx
|
|
@@ -49,6 +50,13 @@ class SSETransport(MCPBaseTransport):
|
|
|
49
50
|
def __init__(self, url: str, api_key: Optional[str] = None) -> None:
|
|
50
51
|
self.base_url = url.rstrip("/")
|
|
51
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")
|
|
52
60
|
|
|
53
61
|
# httpx client (None until initialise)
|
|
54
62
|
self._client: httpx.AsyncClient | None = None
|
|
@@ -75,7 +83,12 @@ class SSETransport(MCPBaseTransport):
|
|
|
75
83
|
|
|
76
84
|
headers = {}
|
|
77
85
|
if self.api_key:
|
|
78
|
-
|
|
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")
|
|
79
92
|
|
|
80
93
|
self._client = httpx.AsyncClient(
|
|
81
94
|
headers=headers,
|
|
@@ -223,6 +236,11 @@ class SSETransport(MCPBaseTransport):
|
|
|
223
236
|
|
|
224
237
|
if event_type == "endpoint":
|
|
225
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
|
+
|
|
226
244
|
self._message_url = f"{self.base_url}{data}"
|
|
227
245
|
|
|
228
246
|
# Extract session_id if present
|
|
@@ -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,,
|
|
File without changes
|
|
File without changes
|