|
1 | 1 | import logging
|
2 |
| - |
| 2 | +import functools |
3 | 3 | from fastmcp import FastMCP
|
4 |
| -from typing import List, Type |
| 4 | +from typing import List, Type, Callable, TypeVar |
5 | 5 | from elasticsearch import Elasticsearch
|
| 6 | +from elasticsearch_mcp_server.tools.base import handle_es_exceptions |
| 7 | + |
| 8 | +T = TypeVar('T') |
| 9 | + |
| 10 | +def with_exception_handling(tool_instance: object, mcp: FastMCP) -> None: |
| 11 | + """ |
| 12 | + Register tools from a tool instance with automatic exception handling applied to all tools. |
| 13 | + |
| 14 | + This function temporarily replaces mcp.tool with a wrapped version that automatically |
| 15 | + applies the handle_es_exceptions decorator to all registered tool methods. |
| 16 | + |
| 17 | + Args: |
| 18 | + tool_instance: The tool instance that has a register_tools method |
| 19 | + mcp: The FastMCP instance used for tool registration |
| 20 | + """ |
| 21 | + # Save the original tool method |
| 22 | + original_tool = mcp.tool |
| 23 | + |
| 24 | + @functools.wraps(original_tool) |
| 25 | + def wrapped_tool(*args, **kwargs): |
| 26 | + # Get the original decorator |
| 27 | + decorator = original_tool(*args, **kwargs) |
| 28 | + |
| 29 | + # Return a new decorator that applies both the exception handler and original decorator |
| 30 | + def combined_decorator(func): |
| 31 | + # First apply the exception handling decorator |
| 32 | + wrapped_func = handle_es_exceptions(func) |
| 33 | + # Then apply the original mcp.tool decorator |
| 34 | + return decorator(wrapped_func) |
| 35 | + |
| 36 | + return combined_decorator |
| 37 | + |
| 38 | + try: |
| 39 | + # Temporarily replace mcp.tool with our wrapped version |
| 40 | + mcp.tool = wrapped_tool |
| 41 | + |
| 42 | + # Call the registration method on the tool instance |
| 43 | + tool_instance.register_tools(mcp) |
| 44 | + finally: |
| 45 | + # Restore the original mcp.tool to avoid affecting other code that might use mcp.tool |
| 46 | + # This ensures that our modification is isolated to just this tool registration |
| 47 | + # and prevents multiple nested decorators if register_all_tools is called multiple times |
| 48 | + mcp.tool = original_tool |
| 49 | + |
6 | 50 |
|
7 | 51 | class ToolsRegister:
|
8 | 52 | """
|
@@ -39,7 +83,7 @@ def register_all_tools(self, tool_classes: List[Type]):
|
39 | 83 | # Pass the raw Elasticsearch client instead of the wrapper
|
40 | 84 | tool_instance.es_client = self.es_client
|
41 | 85 |
|
42 |
| - # Register the tools from this instance |
43 |
| - tool_instance.register_tools(self.mcp) |
| 86 | + # Register tools with automatic exception handling |
| 87 | + with_exception_handling(tool_instance, self.mcp) |
44 | 88 |
|
45 | 89 | self.logger.info(f"Registered tools from {tool_class.__name__}")
|
0 commit comments