chuk-tool-processor 0.1.5__py3-none-any.whl → 0.1.7__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/core/processor.py +345 -132
- chuk_tool_processor/execution/strategies/inprocess_strategy.py +512 -68
- chuk_tool_processor/execution/strategies/subprocess_strategy.py +523 -63
- chuk_tool_processor/execution/tool_executor.py +282 -24
- chuk_tool_processor/execution/wrappers/caching.py +465 -123
- chuk_tool_processor/execution/wrappers/rate_limiting.py +199 -86
- chuk_tool_processor/execution/wrappers/retry.py +133 -23
- chuk_tool_processor/logging/__init__.py +83 -10
- chuk_tool_processor/logging/context.py +218 -22
- chuk_tool_processor/logging/formatter.py +56 -13
- chuk_tool_processor/logging/helpers.py +91 -16
- chuk_tool_processor/logging/metrics.py +75 -6
- chuk_tool_processor/mcp/mcp_tool.py +80 -35
- chuk_tool_processor/mcp/register_mcp_tools.py +74 -56
- chuk_tool_processor/mcp/setup_mcp_sse.py +41 -36
- chuk_tool_processor/mcp/setup_mcp_stdio.py +39 -37
- chuk_tool_processor/mcp/stream_manager.py +28 -0
- chuk_tool_processor/models/execution_strategy.py +52 -3
- chuk_tool_processor/models/streaming_tool.py +110 -0
- chuk_tool_processor/models/tool_call.py +56 -4
- chuk_tool_processor/models/tool_result.py +115 -9
- chuk_tool_processor/models/validated_tool.py +15 -13
- chuk_tool_processor/plugins/discovery.py +115 -70
- chuk_tool_processor/plugins/parsers/base.py +13 -5
- chuk_tool_processor/plugins/parsers/{function_call_tool_plugin.py → function_call_tool.py} +39 -20
- chuk_tool_processor/plugins/parsers/json_tool.py +50 -0
- chuk_tool_processor/plugins/parsers/openai_tool.py +88 -0
- chuk_tool_processor/plugins/parsers/xml_tool.py +74 -20
- chuk_tool_processor/registry/__init__.py +46 -7
- chuk_tool_processor/registry/auto_register.py +92 -28
- chuk_tool_processor/registry/decorators.py +134 -11
- chuk_tool_processor/registry/interface.py +48 -14
- chuk_tool_processor/registry/metadata.py +52 -6
- chuk_tool_processor/registry/provider.py +75 -36
- chuk_tool_processor/registry/providers/__init__.py +49 -10
- chuk_tool_processor/registry/providers/memory.py +59 -48
- chuk_tool_processor/registry/tool_export.py +208 -39
- chuk_tool_processor/utils/validation.py +18 -13
- chuk_tool_processor-0.1.7.dist-info/METADATA +401 -0
- chuk_tool_processor-0.1.7.dist-info/RECORD +58 -0
- {chuk_tool_processor-0.1.5.dist-info → chuk_tool_processor-0.1.7.dist-info}/WHEEL +1 -1
- chuk_tool_processor/plugins/parsers/json_tool_plugin.py +0 -38
- chuk_tool_processor/plugins/parsers/openai_tool_plugin.py +0 -76
- chuk_tool_processor-0.1.5.dist-info/METADATA +0 -462
- chuk_tool_processor-0.1.5.dist-info/RECORD +0 -57
- {chuk_tool_processor-0.1.5.dist-info → chuk_tool_processor-0.1.7.dist-info}/top_level.txt +0 -0
|
@@ -1,25 +1,52 @@
|
|
|
1
1
|
# chuk_tool_processor/logging/metrics.py
|
|
2
|
+
"""
|
|
3
|
+
Metrics logging for tool execution.
|
|
4
|
+
"""
|
|
2
5
|
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
from typing import Dict, Any, Optional
|
|
9
|
+
|
|
10
|
+
# Import directly from context to avoid circular imports
|
|
3
11
|
from .context import get_logger
|
|
4
12
|
|
|
5
13
|
__all__ = ["metrics", "MetricsLogger"]
|
|
6
14
|
|
|
7
15
|
|
|
8
16
|
class MetricsLogger:
|
|
17
|
+
"""
|
|
18
|
+
Logger for collecting and reporting metrics about tool execution.
|
|
19
|
+
|
|
20
|
+
Provides methods to log tool execution metrics and parser metrics
|
|
21
|
+
in a structured format.
|
|
22
|
+
"""
|
|
23
|
+
|
|
9
24
|
def __init__(self):
|
|
25
|
+
"""Initialize with logger."""
|
|
10
26
|
self.logger = get_logger("chuk_tool_processor.metrics")
|
|
11
27
|
|
|
12
28
|
# ------------------------------------------------------------------
|
|
13
|
-
def log_tool_execution(
|
|
29
|
+
async def log_tool_execution(
|
|
14
30
|
self,
|
|
15
31
|
tool: str,
|
|
16
32
|
success: bool,
|
|
17
33
|
duration: float,
|
|
18
34
|
*,
|
|
19
|
-
error: str
|
|
35
|
+
error: Optional[str] = None,
|
|
20
36
|
cached: bool = False,
|
|
21
37
|
attempts: int = 1,
|
|
22
|
-
):
|
|
38
|
+
) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Log metrics for a tool execution.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
tool: Name of the tool
|
|
44
|
+
success: Whether execution was successful
|
|
45
|
+
duration: Execution duration in seconds
|
|
46
|
+
error: Optional error message if execution failed
|
|
47
|
+
cached: Whether the result was retrieved from cache
|
|
48
|
+
attempts: Number of execution attempts
|
|
49
|
+
"""
|
|
23
50
|
self.logger.info(
|
|
24
51
|
f"Tool execution metric: {tool}",
|
|
25
52
|
extra={
|
|
@@ -35,13 +62,22 @@ class MetricsLogger:
|
|
|
35
62
|
},
|
|
36
63
|
)
|
|
37
64
|
|
|
38
|
-
def log_parser_metric(
|
|
65
|
+
async def log_parser_metric(
|
|
39
66
|
self,
|
|
40
67
|
parser: str,
|
|
41
68
|
success: bool,
|
|
42
69
|
duration: float,
|
|
43
70
|
num_calls: int,
|
|
44
|
-
):
|
|
71
|
+
) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Log metrics for a parser.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
parser: Name of the parser
|
|
77
|
+
success: Whether parsing was successful
|
|
78
|
+
duration: Parsing duration in seconds
|
|
79
|
+
num_calls: Number of tool calls parsed
|
|
80
|
+
"""
|
|
45
81
|
self.logger.info(
|
|
46
82
|
f"Parser metric: {parser}",
|
|
47
83
|
extra={
|
|
@@ -54,6 +90,39 @@ class MetricsLogger:
|
|
|
54
90
|
}
|
|
55
91
|
},
|
|
56
92
|
)
|
|
93
|
+
|
|
94
|
+
async def log_registry_metric(
|
|
95
|
+
self,
|
|
96
|
+
operation: str,
|
|
97
|
+
success: bool,
|
|
98
|
+
duration: float,
|
|
99
|
+
tool: Optional[str] = None,
|
|
100
|
+
namespace: Optional[str] = None,
|
|
101
|
+
) -> None:
|
|
102
|
+
"""
|
|
103
|
+
Log metrics for registry operations.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
operation: Type of registry operation
|
|
107
|
+
success: Whether operation was successful
|
|
108
|
+
duration: Operation duration in seconds
|
|
109
|
+
tool: Optional tool name
|
|
110
|
+
namespace: Optional namespace
|
|
111
|
+
"""
|
|
112
|
+
self.logger.info(
|
|
113
|
+
f"Registry metric: {operation}",
|
|
114
|
+
extra={
|
|
115
|
+
"context": {
|
|
116
|
+
"metric_type": "registry",
|
|
117
|
+
"operation": operation,
|
|
118
|
+
"success": success,
|
|
119
|
+
"duration": duration,
|
|
120
|
+
"tool": tool,
|
|
121
|
+
"namespace": namespace,
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
)
|
|
57
125
|
|
|
58
126
|
|
|
59
|
-
|
|
127
|
+
# Create global instance
|
|
128
|
+
metrics = MetricsLogger()
|
|
@@ -1,53 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
1
2
|
# chuk_tool_processor/mcp/mcp_tool.py
|
|
2
3
|
"""
|
|
3
|
-
MCP tool that
|
|
4
|
+
MCP tool shim that delegates execution to a StreamManager,
|
|
5
|
+
handling its own lazy bootstrap when needed.
|
|
4
6
|
"""
|
|
7
|
+
from __future__ import annotations
|
|
5
8
|
|
|
6
|
-
|
|
9
|
+
import asyncio
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
7
11
|
|
|
8
|
-
from chuk_tool_processor.mcp.stream_manager import StreamManager
|
|
9
12
|
from chuk_tool_processor.logging import get_logger
|
|
13
|
+
from chuk_tool_processor.mcp.stream_manager import StreamManager
|
|
10
14
|
|
|
11
15
|
logger = get_logger("chuk_tool_processor.mcp.mcp_tool")
|
|
12
16
|
|
|
17
|
+
|
|
13
18
|
class MCPTool:
|
|
14
19
|
"""
|
|
15
|
-
MCP tool
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
Wrap a remote MCP tool so it can be called like a local tool.
|
|
21
|
+
|
|
22
|
+
You may pass an existing ``StreamManager`` *positionally* (for legacy
|
|
23
|
+
code) or via the named parameter.
|
|
24
|
+
|
|
25
|
+
If no ``StreamManager`` is supplied the class will start one on first
|
|
26
|
+
use via ``setup_mcp_stdio``.
|
|
18
27
|
"""
|
|
19
|
-
|
|
20
|
-
|
|
28
|
+
|
|
29
|
+
# ------------------------------------------------------------------ #
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
tool_name: str,
|
|
33
|
+
stream_manager: Optional[StreamManager] = None,
|
|
34
|
+
*,
|
|
35
|
+
cfg_file: str = "",
|
|
36
|
+
servers: Optional[List[str]] = None,
|
|
37
|
+
server_names: Optional[Dict[int, str]] = None,
|
|
38
|
+
namespace: str = "stdio",
|
|
39
|
+
) -> None:
|
|
40
|
+
self.tool_name = tool_name
|
|
41
|
+
self._sm: Optional[StreamManager] = stream_manager
|
|
42
|
+
|
|
43
|
+
# Boot-strap parameters (only needed if _sm is None)
|
|
44
|
+
self._cfg_file = cfg_file
|
|
45
|
+
self._servers = servers or []
|
|
46
|
+
self._server_names = server_names or {}
|
|
47
|
+
self._namespace = namespace
|
|
48
|
+
|
|
49
|
+
self._sm_lock = asyncio.Lock()
|
|
50
|
+
|
|
51
|
+
# ------------------------------------------------------------------ #
|
|
52
|
+
async def _ensure_stream_manager(self) -> StreamManager:
|
|
21
53
|
"""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
stream_manager: StreamManager instance
|
|
54
|
+
Lazily create / attach a StreamManager.
|
|
55
|
+
|
|
56
|
+
Importing ``setup_mcp_stdio`` *inside* this function prevents the
|
|
57
|
+
circular-import seen earlier. ★
|
|
27
58
|
"""
|
|
28
|
-
self.
|
|
29
|
-
|
|
30
|
-
|
|
59
|
+
if self._sm is not None:
|
|
60
|
+
return self._sm
|
|
61
|
+
|
|
62
|
+
async with self._sm_lock:
|
|
63
|
+
if self._sm is None: # re-check inside lock
|
|
64
|
+
logger.info(
|
|
65
|
+
"Boot-strapping MCP stdio transport for '%s'", self.tool_name
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# ★ local import avoids circular dependency
|
|
69
|
+
from chuk_tool_processor.mcp.setup_mcp_stdio import setup_mcp_stdio
|
|
70
|
+
|
|
71
|
+
_, self._sm = await setup_mcp_stdio(
|
|
72
|
+
config_file=self._cfg_file,
|
|
73
|
+
servers=self._servers,
|
|
74
|
+
server_names=self._server_names,
|
|
75
|
+
namespace=self._namespace,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return self._sm # type: ignore[return-value]
|
|
79
|
+
|
|
80
|
+
# ------------------------------------------------------------------ #
|
|
31
81
|
async def execute(self, **kwargs: Any) -> Any:
|
|
32
82
|
"""
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Tool result
|
|
83
|
+
Forward the call to the remote MCP tool.
|
|
84
|
+
|
|
85
|
+
Raises
|
|
86
|
+
------
|
|
87
|
+
RuntimeError
|
|
88
|
+
If the server returns an error payload.
|
|
40
89
|
"""
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
tool_name=self.tool_name,
|
|
45
|
-
arguments=kwargs
|
|
46
|
-
)
|
|
47
|
-
|
|
90
|
+
sm = await self._ensure_stream_manager()
|
|
91
|
+
result = await sm.call_tool(tool_name=self.tool_name, arguments=kwargs)
|
|
92
|
+
|
|
48
93
|
if result.get("isError"):
|
|
49
|
-
|
|
50
|
-
logger.error(
|
|
51
|
-
raise RuntimeError(
|
|
52
|
-
|
|
53
|
-
return result.get("content")
|
|
94
|
+
err = result.get("error", "Unknown error")
|
|
95
|
+
logger.error("Remote MCP error from '%s': %s", self.tool_name, err)
|
|
96
|
+
raise RuntimeError(err)
|
|
97
|
+
|
|
98
|
+
return result.get("content")
|
|
@@ -1,82 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
1
2
|
# chuk_tool_processor/mcp/register_mcp_tools.py
|
|
2
3
|
"""
|
|
3
|
-
|
|
4
|
+
Discover the remote MCP tools exposed by a :class:`~chuk_tool_processor.mcp.stream_manager.StreamManager`
|
|
5
|
+
instance and register them in the local CHUK registry.
|
|
6
|
+
|
|
7
|
+
The helper is now **async-native** – call it with ``await``.
|
|
4
8
|
"""
|
|
5
9
|
|
|
6
|
-
from
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any, Dict, List
|
|
7
13
|
|
|
14
|
+
from chuk_tool_processor.logging import get_logger
|
|
8
15
|
from chuk_tool_processor.mcp.mcp_tool import MCPTool
|
|
9
16
|
from chuk_tool_processor.mcp.stream_manager import StreamManager
|
|
10
17
|
from chuk_tool_processor.registry.provider import ToolRegistryProvider
|
|
11
|
-
from chuk_tool_processor.logging import get_logger
|
|
12
18
|
|
|
13
19
|
logger = get_logger("chuk_tool_processor.mcp.register")
|
|
14
20
|
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
# --------------------------------------------------------------------------- #
|
|
23
|
+
# public API
|
|
24
|
+
# --------------------------------------------------------------------------- #
|
|
25
|
+
async def register_mcp_tools(
|
|
17
26
|
stream_manager: StreamManager,
|
|
18
|
-
namespace: str = "mcp"
|
|
27
|
+
namespace: str = "mcp",
|
|
19
28
|
) -> List[str]:
|
|
20
29
|
"""
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
Pull the *remote* tool catalogue from *stream_manager* and create a local
|
|
31
|
+
async wrapper (:class:`MCPTool`) for each entry.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
stream_manager
|
|
36
|
+
An **initialised** :class:`~chuk_tool_processor.mcp.stream_manager.StreamManager`.
|
|
37
|
+
namespace
|
|
38
|
+
All tools are registered twice:
|
|
39
|
+
|
|
40
|
+
* under their original name in *namespace* (e.g. ``"mcp.echo"``), and
|
|
41
|
+
* mirrored into the ``"default"`` namespace as ``"{namespace}.{name}"``
|
|
42
|
+
so that parsers may reference them unambiguously.
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
list[str]
|
|
47
|
+
The *plain* tool names that were registered (duplicates are ignored).
|
|
29
48
|
"""
|
|
30
|
-
registry = ToolRegistryProvider.get_registry()
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
#
|
|
34
|
-
mcp_tools = stream_manager.get_all_tools()
|
|
35
|
-
|
|
49
|
+
registry = await ToolRegistryProvider.get_registry()
|
|
50
|
+
registered: List[str] = []
|
|
51
|
+
|
|
52
|
+
# 1️⃣ ask the stream-manager for its catalogue
|
|
53
|
+
mcp_tools: List[Dict[str, Any]] = stream_manager.get_all_tools()
|
|
54
|
+
|
|
36
55
|
for tool_def in mcp_tools:
|
|
37
56
|
tool_name = tool_def.get("name")
|
|
38
57
|
if not tool_name:
|
|
39
|
-
logger.warning("
|
|
58
|
+
logger.warning("Remote tool definition without a 'name' field – skipped")
|
|
40
59
|
continue
|
|
41
|
-
|
|
42
|
-
description = tool_def.get("description"
|
|
43
|
-
|
|
60
|
+
|
|
61
|
+
description = tool_def.get("description") or f"MCP tool • {tool_name}"
|
|
62
|
+
meta: Dict[str, Any] = {
|
|
63
|
+
"description": description,
|
|
64
|
+
"is_async": True,
|
|
65
|
+
"tags": {"mcp", "remote"},
|
|
66
|
+
"argument_schema": tool_def.get("inputSchema", {}),
|
|
67
|
+
}
|
|
68
|
+
|
|
44
69
|
try:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
tool,
|
|
70
|
+
wrapper = MCPTool(tool_name, stream_manager)
|
|
71
|
+
|
|
72
|
+
# ── primary registration ──────────────────────────────────────
|
|
73
|
+
await registry.register_tool(
|
|
74
|
+
wrapper,
|
|
51
75
|
name=tool_name,
|
|
52
76
|
namespace=namespace,
|
|
53
|
-
metadata=
|
|
54
|
-
"description": description,
|
|
55
|
-
"is_async": True,
|
|
56
|
-
"tags": {"mcp", "remote"},
|
|
57
|
-
"argument_schema": tool_def.get("inputSchema", {})
|
|
58
|
-
}
|
|
77
|
+
metadata=meta,
|
|
59
78
|
)
|
|
60
|
-
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
name=namespaced_tool_name,
|
|
79
|
+
|
|
80
|
+
# ── mirror into "default" namespace with dotted name ──────────
|
|
81
|
+
dotted_name = f"{namespace}.{tool_name}"
|
|
82
|
+
await registry.register_tool(
|
|
83
|
+
wrapper,
|
|
84
|
+
name=dotted_name,
|
|
67
85
|
namespace="default",
|
|
68
|
-
metadata={
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
86
|
+
metadata={**meta, "tags": meta["tags"] | {"namespaced"}},
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
registered.append(tool_name)
|
|
90
|
+
logger.debug(
|
|
91
|
+
"MCP tool '%s' registered (as '%s' & '%s')",
|
|
92
|
+
tool_name,
|
|
93
|
+
f"{namespace}:{tool_name}",
|
|
94
|
+
f"default:{dotted_name}",
|
|
74
95
|
)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
logger.error(f"Error registering MCP tool '{tool_name}': {e}")
|
|
81
|
-
|
|
82
|
-
return registered_tools
|
|
96
|
+
except Exception as exc: # noqa: BLE001
|
|
97
|
+
logger.error("Failed to register MCP tool '%s': %s", tool_name, exc)
|
|
98
|
+
|
|
99
|
+
logger.info("MCP registration complete – %d tool(s) available", len(registered))
|
|
100
|
+
return registered
|
|
@@ -1,20 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
1
2
|
# chuk_tool_processor/mcp/setup_mcp_sse.py
|
|
2
3
|
"""
|
|
3
|
-
|
|
4
|
+
Utility that wires up:
|
|
5
|
+
|
|
6
|
+
1. A :class:`~chuk_tool_processor.mcp.stream_manager.StreamManager`
|
|
7
|
+
using the SSE transport.
|
|
8
|
+
2. The remote MCP tools exposed by that manager (via
|
|
9
|
+
:pyfunc:`~chuk_tool_processor.mcp.register_mcp_tools.register_mcp_tools`).
|
|
10
|
+
3. A fully-featured :class:`~chuk_tool_processor.core.processor.ToolProcessor`
|
|
11
|
+
instance that can execute those tools – with optional caching,
|
|
12
|
+
rate-limiting, retries, etc.
|
|
4
13
|
"""
|
|
14
|
+
|
|
5
15
|
from __future__ import annotations
|
|
6
16
|
|
|
7
|
-
from typing import Dict, List, Optional
|
|
17
|
+
from typing import Dict, List, Optional, Tuple
|
|
8
18
|
|
|
9
19
|
from chuk_tool_processor.core.processor import ToolProcessor
|
|
10
|
-
from chuk_tool_processor.mcp.stream_manager import StreamManager
|
|
11
|
-
from chuk_tool_processor.mcp.register_mcp_tools import register_mcp_tools
|
|
12
20
|
from chuk_tool_processor.logging import get_logger
|
|
21
|
+
from chuk_tool_processor.mcp.register_mcp_tools import register_mcp_tools
|
|
22
|
+
from chuk_tool_processor.mcp.stream_manager import StreamManager
|
|
13
23
|
|
|
14
24
|
logger = get_logger("chuk_tool_processor.mcp.setup_sse")
|
|
15
25
|
|
|
16
26
|
|
|
17
|
-
|
|
27
|
+
# --------------------------------------------------------------------------- #
|
|
28
|
+
# public helper
|
|
29
|
+
# --------------------------------------------------------------------------- #
|
|
30
|
+
async def setup_mcp_sse( # noqa: C901 – long, but just a config wrapper
|
|
31
|
+
*,
|
|
18
32
|
servers: List[Dict[str, str]],
|
|
19
33
|
server_names: Optional[Dict[int, str]] = None,
|
|
20
34
|
default_timeout: float = 10.0,
|
|
@@ -26,38 +40,24 @@ async def setup_mcp_sse(
|
|
|
26
40
|
tool_rate_limits: Optional[Dict[str, tuple]] = None,
|
|
27
41
|
enable_retries: bool = True,
|
|
28
42
|
max_retries: int = 3,
|
|
29
|
-
namespace: str = "mcp"
|
|
30
|
-
) ->
|
|
43
|
+
namespace: str = "mcp",
|
|
44
|
+
) -> Tuple[ToolProcessor, StreamManager]:
|
|
31
45
|
"""
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
server_names: Optional mapping of server indices to names
|
|
37
|
-
default_timeout: Default timeout for tool execution
|
|
38
|
-
max_concurrency: Maximum concurrent executions
|
|
39
|
-
enable_caching: Whether to enable caching
|
|
40
|
-
cache_ttl: Cache TTL in seconds
|
|
41
|
-
enable_rate_limiting: Whether to enable rate limiting
|
|
42
|
-
global_rate_limit: Global rate limit (requests per minute)
|
|
43
|
-
tool_rate_limits: Per-tool rate limits
|
|
44
|
-
enable_retries: Whether to enable retries
|
|
45
|
-
max_retries: Maximum retry attempts
|
|
46
|
-
namespace: Namespace for MCP tools
|
|
47
|
-
|
|
48
|
-
Returns:
|
|
49
|
-
Tuple of (processor, stream_manager)
|
|
46
|
+
Spin up an SSE-backed *StreamManager*, register all its remote tools,
|
|
47
|
+
and return a ready-to-go :class:`ToolProcessor`.
|
|
48
|
+
|
|
49
|
+
Everything is **async-native** – call with ``await``.
|
|
50
50
|
"""
|
|
51
|
-
#
|
|
51
|
+
# 1️⃣ connect to the remote MCP servers
|
|
52
52
|
stream_manager = await StreamManager.create_with_sse(
|
|
53
53
|
servers=servers,
|
|
54
|
-
server_names=server_names
|
|
54
|
+
server_names=server_names,
|
|
55
55
|
)
|
|
56
|
-
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
#
|
|
56
|
+
|
|
57
|
+
# 2️⃣ introspect & register their tools in the local registry
|
|
58
|
+
registered = await register_mcp_tools(stream_manager, namespace=namespace)
|
|
59
|
+
|
|
60
|
+
# 3️⃣ build a processor configured to your liking
|
|
61
61
|
processor = ToolProcessor(
|
|
62
62
|
default_timeout=default_timeout,
|
|
63
63
|
max_concurrency=max_concurrency,
|
|
@@ -67,8 +67,13 @@ async def setup_mcp_sse(
|
|
|
67
67
|
global_rate_limit=global_rate_limit,
|
|
68
68
|
tool_rate_limits=tool_rate_limits,
|
|
69
69
|
enable_retries=enable_retries,
|
|
70
|
-
max_retries=max_retries
|
|
70
|
+
max_retries=max_retries,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
logger.info(
|
|
74
|
+
"MCP (SSE) initialised – %s tool%s registered into namespace '%s'",
|
|
75
|
+
len(registered),
|
|
76
|
+
"" if len(registered) == 1 else "s",
|
|
77
|
+
namespace,
|
|
71
78
|
)
|
|
72
|
-
|
|
73
|
-
logger.info(f"Set up MCP (SSE) with {len(registered_tools)} tools")
|
|
74
|
-
return processor, stream_manager
|
|
79
|
+
return processor, stream_manager
|
|
@@ -1,20 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
1
2
|
# chuk_tool_processor/mcp/setup_mcp_stdio.py
|
|
2
3
|
"""
|
|
3
|
-
|
|
4
|
+
Bootstrap helper for MCP over **stdio** transport.
|
|
5
|
+
|
|
6
|
+
It:
|
|
7
|
+
|
|
8
|
+
1. spins up :class:`~chuk_tool_processor.mcp.stream_manager.StreamManager`
|
|
9
|
+
with the `"stdio"` transport,
|
|
10
|
+
2. discovers & registers the remote MCP tools locally, and
|
|
11
|
+
3. returns a ready-to-use :class:`~chuk_tool_processor.core.processor.ToolProcessor`.
|
|
4
12
|
"""
|
|
13
|
+
|
|
5
14
|
from __future__ import annotations
|
|
6
15
|
|
|
7
|
-
from typing import Dict, List, Optional
|
|
16
|
+
from typing import Dict, List, Optional, Tuple
|
|
8
17
|
|
|
9
18
|
from chuk_tool_processor.core.processor import ToolProcessor
|
|
10
|
-
from chuk_tool_processor.mcp.stream_manager import StreamManager
|
|
11
|
-
from chuk_tool_processor.mcp.register_mcp_tools import register_mcp_tools
|
|
12
19
|
from chuk_tool_processor.logging import get_logger
|
|
20
|
+
from chuk_tool_processor.mcp.register_mcp_tools import register_mcp_tools
|
|
21
|
+
from chuk_tool_processor.mcp.stream_manager import StreamManager
|
|
13
22
|
|
|
14
23
|
logger = get_logger("chuk_tool_processor.mcp.setup_stdio")
|
|
15
24
|
|
|
16
25
|
|
|
17
|
-
|
|
26
|
+
# --------------------------------------------------------------------------- #
|
|
27
|
+
# public helper
|
|
28
|
+
# --------------------------------------------------------------------------- #
|
|
29
|
+
async def setup_mcp_stdio( # noqa: C901 – long but just a config facade
|
|
30
|
+
*,
|
|
18
31
|
config_file: str,
|
|
19
32
|
servers: List[str],
|
|
20
33
|
server_names: Optional[Dict[int, str]] = None,
|
|
@@ -27,41 +40,25 @@ async def setup_mcp_stdio(
|
|
|
27
40
|
tool_rate_limits: Optional[Dict[str, tuple]] = None,
|
|
28
41
|
enable_retries: bool = True,
|
|
29
42
|
max_retries: int = 3,
|
|
30
|
-
namespace: str = "mcp"
|
|
31
|
-
) ->
|
|
43
|
+
namespace: str = "mcp",
|
|
44
|
+
) -> Tuple[ToolProcessor, StreamManager]:
|
|
32
45
|
"""
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
config_file: Path to the config file
|
|
37
|
-
servers: List of server names to connect to
|
|
38
|
-
server_names: Optional mapping of server indices to names
|
|
39
|
-
default_timeout: Default timeout for tool execution
|
|
40
|
-
max_concurrency: Maximum concurrent executions
|
|
41
|
-
enable_caching: Whether to enable caching
|
|
42
|
-
cache_ttl: Cache TTL in seconds
|
|
43
|
-
enable_rate_limiting: Whether to enable rate limiting
|
|
44
|
-
global_rate_limit: Global rate limit (requests per minute)
|
|
45
|
-
tool_rate_limits: Per-tool rate limits
|
|
46
|
-
enable_retries: Whether to enable retries
|
|
47
|
-
max_retries: Maximum retry attempts
|
|
48
|
-
namespace: Namespace for MCP tools
|
|
49
|
-
|
|
50
|
-
Returns:
|
|
51
|
-
Tuple of (processor, stream_manager)
|
|
46
|
+
Initialise stdio-transport MCP + a :class:`ToolProcessor`.
|
|
47
|
+
|
|
48
|
+
Call with ``await`` from your async context.
|
|
52
49
|
"""
|
|
53
|
-
#
|
|
50
|
+
# 1️⃣ create & connect the stream-manager
|
|
54
51
|
stream_manager = await StreamManager.create(
|
|
55
52
|
config_file=config_file,
|
|
56
53
|
servers=servers,
|
|
57
54
|
server_names=server_names,
|
|
58
|
-
transport_type="stdio"
|
|
55
|
+
transport_type="stdio",
|
|
59
56
|
)
|
|
60
|
-
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
#
|
|
57
|
+
|
|
58
|
+
# 2️⃣ pull the remote tool list and register each one locally
|
|
59
|
+
registered = await register_mcp_tools(stream_manager, namespace=namespace)
|
|
60
|
+
|
|
61
|
+
# 3️⃣ build a processor instance configured to your taste
|
|
65
62
|
processor = ToolProcessor(
|
|
66
63
|
default_timeout=default_timeout,
|
|
67
64
|
max_concurrency=max_concurrency,
|
|
@@ -71,8 +68,13 @@ async def setup_mcp_stdio(
|
|
|
71
68
|
global_rate_limit=global_rate_limit,
|
|
72
69
|
tool_rate_limits=tool_rate_limits,
|
|
73
70
|
enable_retries=enable_retries,
|
|
74
|
-
max_retries=max_retries
|
|
71
|
+
max_retries=max_retries,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
logger.info(
|
|
75
|
+
"MCP (stdio) initialised – %s tool%s registered into namespace '%s'",
|
|
76
|
+
len(registered),
|
|
77
|
+
"" if len(registered) == 1 else "s",
|
|
78
|
+
namespace,
|
|
75
79
|
)
|
|
76
|
-
|
|
77
|
-
logger.info(f"Set up MCP (stdio) with {len(registered_tools)} tools")
|
|
78
|
-
return processor, stream_manager
|
|
80
|
+
return processor, stream_manager
|
|
@@ -174,6 +174,34 @@ class StreamManager:
|
|
|
174
174
|
|
|
175
175
|
def get_server_info(self) -> List[Dict[str, Any]]:
|
|
176
176
|
return self.server_info
|
|
177
|
+
|
|
178
|
+
async def list_tools(self, server_name: str) -> List[Dict[str, Any]]:
|
|
179
|
+
"""
|
|
180
|
+
List all tools available from a specific server.
|
|
181
|
+
|
|
182
|
+
This method is required by ProxyServerManager for proper tool discovery.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
server_name: Name of the server to query
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
List of tool definitions from the server
|
|
189
|
+
"""
|
|
190
|
+
if server_name not in self.transports:
|
|
191
|
+
logger.error(f"Server '{server_name}' not found in transports")
|
|
192
|
+
return []
|
|
193
|
+
|
|
194
|
+
# Get the transport for this server
|
|
195
|
+
transport = self.transports[server_name]
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
# Call the get_tools method on the transport
|
|
199
|
+
tools = await transport.get_tools()
|
|
200
|
+
logger.debug(f"Found {len(tools)} tools for server {server_name}")
|
|
201
|
+
return tools
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.error(f"Error listing tools for server {server_name}: {e}")
|
|
204
|
+
return []
|
|
177
205
|
|
|
178
206
|
# ------------------------------------------------------------------ #
|
|
179
207
|
# EXTRA HELPERS – ping / resources / prompts #
|