chuk-tool-processor 0.1.6__py3-none-any.whl → 0.2__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.

Files changed (46) hide show
  1. chuk_tool_processor/core/processor.py +345 -132
  2. chuk_tool_processor/execution/strategies/inprocess_strategy.py +522 -71
  3. chuk_tool_processor/execution/strategies/subprocess_strategy.py +559 -64
  4. chuk_tool_processor/execution/tool_executor.py +282 -24
  5. chuk_tool_processor/execution/wrappers/caching.py +465 -123
  6. chuk_tool_processor/execution/wrappers/rate_limiting.py +199 -86
  7. chuk_tool_processor/execution/wrappers/retry.py +133 -23
  8. chuk_tool_processor/logging/__init__.py +83 -10
  9. chuk_tool_processor/logging/context.py +218 -22
  10. chuk_tool_processor/logging/formatter.py +56 -13
  11. chuk_tool_processor/logging/helpers.py +91 -16
  12. chuk_tool_processor/logging/metrics.py +75 -6
  13. chuk_tool_processor/mcp/mcp_tool.py +80 -35
  14. chuk_tool_processor/mcp/register_mcp_tools.py +74 -56
  15. chuk_tool_processor/mcp/setup_mcp_sse.py +41 -36
  16. chuk_tool_processor/mcp/setup_mcp_stdio.py +39 -37
  17. chuk_tool_processor/mcp/transport/sse_transport.py +351 -105
  18. chuk_tool_processor/models/execution_strategy.py +52 -3
  19. chuk_tool_processor/models/streaming_tool.py +110 -0
  20. chuk_tool_processor/models/tool_call.py +56 -4
  21. chuk_tool_processor/models/tool_result.py +115 -9
  22. chuk_tool_processor/models/validated_tool.py +15 -13
  23. chuk_tool_processor/plugins/discovery.py +115 -70
  24. chuk_tool_processor/plugins/parsers/base.py +13 -5
  25. chuk_tool_processor/plugins/parsers/{function_call_tool_plugin.py → function_call_tool.py} +39 -20
  26. chuk_tool_processor/plugins/parsers/json_tool.py +50 -0
  27. chuk_tool_processor/plugins/parsers/openai_tool.py +88 -0
  28. chuk_tool_processor/plugins/parsers/xml_tool.py +74 -20
  29. chuk_tool_processor/registry/__init__.py +46 -7
  30. chuk_tool_processor/registry/auto_register.py +92 -28
  31. chuk_tool_processor/registry/decorators.py +134 -11
  32. chuk_tool_processor/registry/interface.py +48 -14
  33. chuk_tool_processor/registry/metadata.py +52 -6
  34. chuk_tool_processor/registry/provider.py +75 -36
  35. chuk_tool_processor/registry/providers/__init__.py +49 -10
  36. chuk_tool_processor/registry/providers/memory.py +59 -48
  37. chuk_tool_processor/registry/tool_export.py +208 -39
  38. chuk_tool_processor/utils/validation.py +18 -13
  39. chuk_tool_processor-0.2.dist-info/METADATA +401 -0
  40. chuk_tool_processor-0.2.dist-info/RECORD +58 -0
  41. {chuk_tool_processor-0.1.6.dist-info → chuk_tool_processor-0.2.dist-info}/WHEEL +1 -1
  42. chuk_tool_processor/plugins/parsers/json_tool_plugin.py +0 -38
  43. chuk_tool_processor/plugins/parsers/openai_tool_plugin.py +0 -76
  44. chuk_tool_processor-0.1.6.dist-info/METADATA +0 -462
  45. chuk_tool_processor-0.1.6.dist-info/RECORD +0 -57
  46. {chuk_tool_processor-0.1.6.dist-info → chuk_tool_processor-0.2.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 | None = None,
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
- metrics = MetricsLogger()
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 uses StreamManager for execution.
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
- from typing import Any
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 that uses StreamManager for execution.
16
-
17
- This tool handles both namespaced and non-namespaced execution.
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
- def __init__(self, tool_name: str, stream_manager: StreamManager):
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
- Initialize the MCP tool.
23
-
24
- Args:
25
- tool_name: Name of the MCP tool
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.tool_name = tool_name
29
- self.stream_manager = stream_manager
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
- Execute the tool using StreamManager.
34
-
35
- Args:
36
- **kwargs: Tool arguments
37
-
38
- Returns:
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
- logger.debug(f"Executing MCP tool {self.tool_name}")
42
-
43
- result = await self.stream_manager.call_tool(
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
- error_msg = result.get("error", "Unknown error")
50
- logger.error(f"Error executing MCP tool {self.tool_name}: {error_msg}")
51
- raise RuntimeError(error_msg)
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
- Registration functions for MCP tools.
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 typing import List, Dict, Any
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
- def register_mcp_tools(
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
- Register MCP tools with the CHUK registry.
22
-
23
- Args:
24
- stream_manager: StreamManager instance
25
- namespace: Namespace for the tools
26
-
27
- Returns:
28
- List of registered tool names
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
- registered_tools = []
32
-
33
- # Get all tools from StreamManager
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("Tool definition missing name")
58
+ logger.warning("Remote tool definition without a 'name' field – skipped")
40
59
  continue
41
-
42
- description = tool_def.get("description", f"MCP tool: {tool_name}")
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
- # Create tool
46
- tool = MCPTool(tool_name, stream_manager)
47
-
48
- # Register with registry under the original name in the given namespace
49
- registry.register_tool(
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
- # Also register the tool in the default namespace with the namespaced name
62
- # This allows calling the tool as either "echo" or "stdio.echo" from parsers
63
- namespaced_tool_name = f"{namespace}.{tool_name}"
64
- registry.register_tool(
65
- tool,
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
- "description": description,
70
- "is_async": True,
71
- "tags": {"mcp", "remote", "namespaced"},
72
- "argument_schema": tool_def.get("inputSchema", {})
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
- registered_tools.append(tool_name)
77
- logger.info(f"Registered MCP tool '{tool_name}' in namespace '{namespace}' (also as '{namespaced_tool_name}' in default)")
78
-
79
- except Exception as e:
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
- Setup function for SSE transport MCP integration.
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
- async def setup_mcp_sse(
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
- ) -> tuple[ToolProcessor, StreamManager]:
43
+ namespace: str = "mcp",
44
+ ) -> Tuple[ToolProcessor, StreamManager]:
31
45
  """
32
- Set up MCP with SSE transport and CHUK Tool Processor.
33
-
34
- Args:
35
- servers: List of server configurations with "name" and "url" keys
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
- # Create and initialize StreamManager with SSE transport
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
- # Register MCP tools
58
- registered_tools = register_mcp_tools(stream_manager, namespace)
59
-
60
- # Create processor
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
- Setup function for stdio transport MCP integration.
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
- async def setup_mcp_stdio(
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
- ) -> tuple[ToolProcessor, StreamManager]:
43
+ namespace: str = "mcp",
44
+ ) -> Tuple[ToolProcessor, StreamManager]:
32
45
  """
33
- Set up MCP with stdio transport and CHUK Tool Processor.
34
-
35
- Args:
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
- # Create and initialize StreamManager with stdio transport
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
- # Register MCP tools
62
- registered_tools = register_mcp_tools(stream_manager, namespace)
63
-
64
- # Create processor
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