hanzo-mcp 0.6.12__py3-none-any.whl → 0.6.13__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 hanzo-mcp might be problematic. Click here for more details.

Files changed (77) hide show
  1. hanzo_mcp/__init__.py +2 -2
  2. hanzo_mcp/cli.py +2 -2
  3. hanzo_mcp/cli_enhanced.py +4 -4
  4. hanzo_mcp/cli_plugin.py +91 -0
  5. hanzo_mcp/config/__init__.py +1 -1
  6. hanzo_mcp/config/settings.py +69 -6
  7. hanzo_mcp/config/tool_config.py +2 -2
  8. hanzo_mcp/dev_server.py +3 -3
  9. hanzo_mcp/prompts/project_system.py +1 -1
  10. hanzo_mcp/server.py +6 -2
  11. hanzo_mcp/server_enhanced.py +69 -0
  12. hanzo_mcp/tools/__init__.py +75 -29
  13. hanzo_mcp/tools/agent/__init__.py +1 -1
  14. hanzo_mcp/tools/agent/agent_tool.py +2 -2
  15. hanzo_mcp/tools/common/__init__.py +15 -1
  16. hanzo_mcp/tools/common/base.py +4 -4
  17. hanzo_mcp/tools/common/batch_tool.py +1 -1
  18. hanzo_mcp/tools/common/config_tool.py +2 -2
  19. hanzo_mcp/tools/common/context.py +2 -2
  20. hanzo_mcp/tools/common/context_fix.py +26 -0
  21. hanzo_mcp/tools/common/critic_tool.py +196 -0
  22. hanzo_mcp/tools/common/decorators.py +208 -0
  23. hanzo_mcp/tools/common/enhanced_base.py +106 -0
  24. hanzo_mcp/tools/common/mode.py +116 -0
  25. hanzo_mcp/tools/common/mode_loader.py +105 -0
  26. hanzo_mcp/tools/common/permissions.py +1 -1
  27. hanzo_mcp/tools/common/personality.py +936 -0
  28. hanzo_mcp/tools/common/plugin_loader.py +287 -0
  29. hanzo_mcp/tools/common/stats.py +4 -4
  30. hanzo_mcp/tools/common/tool_list.py +1 -1
  31. hanzo_mcp/tools/common/validation.py +1 -1
  32. hanzo_mcp/tools/config/__init__.py +3 -1
  33. hanzo_mcp/tools/config/config_tool.py +1 -1
  34. hanzo_mcp/tools/config/mode_tool.py +209 -0
  35. hanzo_mcp/tools/database/__init__.py +1 -1
  36. hanzo_mcp/tools/editor/__init__.py +1 -1
  37. hanzo_mcp/tools/filesystem/__init__.py +19 -14
  38. hanzo_mcp/tools/filesystem/batch_search.py +3 -3
  39. hanzo_mcp/tools/filesystem/diff.py +2 -2
  40. hanzo_mcp/tools/filesystem/rules_tool.py +235 -0
  41. hanzo_mcp/tools/filesystem/{unified_search.py → search_tool.py} +12 -12
  42. hanzo_mcp/tools/filesystem/{symbols_unified.py → symbols_tool.py} +104 -5
  43. hanzo_mcp/tools/filesystem/watch.py +3 -2
  44. hanzo_mcp/tools/jupyter/__init__.py +2 -2
  45. hanzo_mcp/tools/jupyter/jupyter.py +1 -1
  46. hanzo_mcp/tools/llm/__init__.py +3 -3
  47. hanzo_mcp/tools/llm/llm_tool.py +648 -143
  48. hanzo_mcp/tools/mcp/__init__.py +2 -2
  49. hanzo_mcp/tools/mcp/{mcp_unified.py → mcp_tool.py} +3 -3
  50. hanzo_mcp/tools/shell/__init__.py +6 -6
  51. hanzo_mcp/tools/shell/base_process.py +4 -2
  52. hanzo_mcp/tools/shell/bash_session_executor.py +1 -1
  53. hanzo_mcp/tools/shell/{bash_unified.py → bash_tool.py} +1 -1
  54. hanzo_mcp/tools/shell/command_executor.py +2 -2
  55. hanzo_mcp/tools/shell/{npx_unified.py → npx_tool.py} +1 -1
  56. hanzo_mcp/tools/shell/open.py +2 -2
  57. hanzo_mcp/tools/shell/{process_unified.py → process_tool.py} +1 -1
  58. hanzo_mcp/tools/shell/run_command_windows.py +1 -1
  59. hanzo_mcp/tools/shell/uvx.py +47 -2
  60. hanzo_mcp/tools/shell/uvx_background.py +47 -2
  61. hanzo_mcp/tools/shell/{uvx_unified.py → uvx_tool.py} +1 -1
  62. hanzo_mcp/tools/todo/__init__.py +14 -19
  63. hanzo_mcp/tools/todo/todo.py +22 -1
  64. hanzo_mcp/tools/vector/__init__.py +1 -1
  65. hanzo_mcp/tools/vector/infinity_store.py +2 -2
  66. hanzo_mcp/tools/vector/project_manager.py +1 -1
  67. hanzo_mcp-0.6.13.dist-info/METADATA +359 -0
  68. {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.6.13.dist-info}/RECORD +72 -64
  69. {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.6.13.dist-info}/entry_points.txt +1 -0
  70. hanzo_mcp/tools/common/palette.py +0 -344
  71. hanzo_mcp/tools/common/palette_loader.py +0 -108
  72. hanzo_mcp/tools/config/palette_tool.py +0 -179
  73. hanzo_mcp/tools/llm/llm_unified.py +0 -851
  74. hanzo_mcp-0.6.12.dist-info/METADATA +0 -339
  75. {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.6.13.dist-info}/WHEEL +0 -0
  76. {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.6.13.dist-info}/licenses/LICENSE +0 -0
  77. {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.6.13.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,11 @@
1
- """Common utilities for Hanzo MCP tools."""
1
+ """Common utilities for Hanzo AI tools."""
2
2
 
3
3
  from mcp.server import FastMCP
4
4
 
5
5
  from hanzo_mcp.tools.common.base import BaseTool, ToolRegistry
6
6
  from hanzo_mcp.tools.common.batch_tool import BatchTool
7
7
  from hanzo_mcp.tools.common.thinking_tool import ThinkingTool
8
+ from hanzo_mcp.tools.common.critic_tool import CriticTool
8
9
 
9
10
 
10
11
  def register_thinking_tool(
@@ -20,6 +21,19 @@ def register_thinking_tool(
20
21
  return [thinking_tool]
21
22
 
22
23
 
24
+ def register_critic_tool(
25
+ mcp_server: FastMCP,
26
+ ) -> list[BaseTool]:
27
+ """Register critic tool with the MCP server.
28
+
29
+ Args:
30
+ mcp_server: The FastMCP server instance
31
+ """
32
+ critic_tool = CriticTool()
33
+ ToolRegistry.register_tool(mcp_server, critic_tool)
34
+ return [critic_tool]
35
+
36
+
23
37
  def register_batch_tool(mcp_server: FastMCP, tools: dict[str, BaseTool]) -> None:
24
38
  """Register batch tool with the MCP server.
25
39
 
@@ -1,7 +1,7 @@
1
- """Base classes for Hanzo MCP tools.
1
+ """Base classes for Hanzo AI tools.
2
2
 
3
3
  This module provides abstract base classes that define interfaces and common functionality
4
- for all tools used in Hanzo MCP. These abstractions help ensure consistent tool
4
+ for all tools used in Hanzo AI. These abstractions help ensure consistent tool
5
5
  behavior and provide a foundation for tool registration and management.
6
6
  """
7
7
 
@@ -62,7 +62,7 @@ def handle_connection_errors(
62
62
 
63
63
 
64
64
  class BaseTool(ABC):
65
- """Abstract base class for all Hanzo MCP tools.
65
+ """Abstract base class for all Hanzo AI tools.
66
66
 
67
67
  This class defines the core interface that all tools must implement, ensuring
68
68
  consistency in how tools are registered, documented, and called.
@@ -156,7 +156,7 @@ class FileSystemTool(BaseTool, ABC):
156
156
 
157
157
  @final
158
158
  class ToolRegistry:
159
- """Registry for Hanzo MCP tools.
159
+ """Registry for Hanzo AI tools.
160
160
 
161
161
  Provides functionality for registering tool implementations with an MCP server,
162
162
  handling the conversion between tool classes and MCP tool functions.
@@ -1,4 +1,4 @@
1
- """Batch tool implementation for Hanzo MCP.
1
+ """Batch tool implementation for Hanzo AI.
2
2
 
3
3
  This module provides the BatchTool that allows for executing multiple tools in
4
4
  parallel or serial depending on their characteristics.
@@ -34,7 +34,7 @@ class ConfigToolParams(TypedDict, total=False):
34
34
 
35
35
  @final
36
36
  class ConfigTool(BaseTool):
37
- """Tool for managing Hanzo MCP configuration dynamically."""
37
+ """Tool for managing Hanzo AI configuration dynamically."""
38
38
 
39
39
  def __init__(self, permission_manager: PermissionManager):
40
40
  """Initialize the configuration tool.
@@ -52,7 +52,7 @@ class ConfigTool(BaseTool):
52
52
  @property
53
53
  def description(self) -> str:
54
54
  """Get the tool description."""
55
- return """Dynamically manage Hanzo MCP configuration settings through conversation.
55
+ return """Dynamically manage Hanzo AI configuration settings through conversation.
56
56
 
57
57
  Can get/set global settings, project-specific settings, manage MCP servers, configure tools,
58
58
  and handle project workflows. Supports dot-notation for nested settings like 'agent.enabled'.
@@ -1,4 +1,4 @@
1
- """Enhanced Context for Hanzo MCP tools.
1
+ """Enhanced Context for Hanzo AI tools.
2
2
 
3
3
  This module provides an enhanced Context class that wraps the MCP Context
4
4
  and adds additional functionality specific to Hanzo tools.
@@ -17,7 +17,7 @@ from mcp.server.lowlevel.helper_types import ReadResourceContents
17
17
 
18
18
  @final
19
19
  class ToolContext:
20
- """Enhanced context for Hanzo MCP tools.
20
+ """Enhanced context for Hanzo AI tools.
21
21
 
22
22
  This class wraps the MCP Context and adds additional functionality
23
23
  for tracking tool execution, progress reporting, and resource access.
@@ -0,0 +1,26 @@
1
+ """Context handling fix for MCP tools.
2
+
3
+ This module provides backward compatibility by re-exporting the
4
+ context normalization utilities from the decorators module.
5
+
6
+ DEPRECATED: Use hanzo_mcp.tools.common.decorators directly.
7
+ """
8
+
9
+ # Re-export for backward compatibility
10
+ from hanzo_mcp.tools.common.decorators import (
11
+ MockContext,
12
+ with_context_normalization,
13
+ _is_valid_context as is_valid_context,
14
+ )
15
+
16
+ # Backward compatibility function
17
+ def normalize_context(ctx):
18
+ """Normalize context - backward compatibility wrapper.
19
+
20
+ DEPRECATED: Use decorators.with_context_normalization instead.
21
+ """
22
+ if is_valid_context(ctx):
23
+ return ctx
24
+ return MockContext()
25
+
26
+ __all__ = ['MockContext', 'normalize_context', 'with_context_normalization']
@@ -0,0 +1,196 @@
1
+ """Critic tool implementation.
2
+
3
+ This module provides the CriticTool for Claude to engage in critical analysis and code review.
4
+ """
5
+
6
+ from typing import Annotated, TypedDict, Unpack, final, override
7
+
8
+ from mcp.server.fastmcp import Context as MCPContext
9
+ from mcp.server import FastMCP
10
+ from pydantic import Field
11
+
12
+ from hanzo_mcp.tools.common.base import BaseTool
13
+ from hanzo_mcp.tools.common.context import create_tool_context
14
+
15
+
16
+ Analysis = Annotated[
17
+ str,
18
+ Field(
19
+ description="The critical analysis to perform - code review, error detection, or improvement suggestions",
20
+ min_length=1,
21
+ ),
22
+ ]
23
+
24
+
25
+ class CriticToolParams(TypedDict):
26
+ """Parameters for the CriticTool.
27
+
28
+ Attributes:
29
+ analysis: The critical analysis to perform
30
+ """
31
+
32
+ analysis: Analysis
33
+
34
+
35
+ @final
36
+ class CriticTool(BaseTool):
37
+ """Tool for Claude to engage in critical analysis and play devil's advocate."""
38
+
39
+ @property
40
+ @override
41
+ def name(self) -> str:
42
+ """Get the tool name.
43
+
44
+ Returns:
45
+ Tool name
46
+ """
47
+ return "critic"
48
+
49
+ @property
50
+ @override
51
+ def description(self) -> str:
52
+ """Get the tool description.
53
+
54
+ Returns:
55
+ Tool description
56
+ """
57
+ return """Use this tool to perform critical analysis, play devil's advocate, and ensure high standards.
58
+ This tool forces a critical thinking mode that reviews all code for errors, improvements, and edge cases.
59
+ It ensures tests are run, tests pass, and maintains high quality standards.
60
+
61
+ This is your inner critic that:
62
+ - Always questions assumptions
63
+ - Looks for potential bugs and edge cases
64
+ - Ensures proper error handling
65
+ - Verifies test coverage
66
+ - Checks for performance issues
67
+ - Reviews security implications
68
+ - Suggests improvements and refactoring
69
+ - Ensures code follows best practices
70
+ - Questions design decisions
71
+ - Looks for missing documentation
72
+
73
+ Common use cases:
74
+ 1. Before finalizing any code changes - review for bugs, edge cases, and improvements
75
+ 2. After implementing a feature - critically analyze if it truly solves the problem
76
+ 3. When tests pass too easily - question if tests are comprehensive enough
77
+ 4. Before marking a task complete - ensure all quality standards are met
78
+ 5. When something seems too simple - look for hidden complexity or missing requirements
79
+ 6. After fixing a bug - analyze if the fix addresses root cause or just symptoms
80
+
81
+ <critic_example>
82
+ Code Review Analysis:
83
+ - Implementation Issues:
84
+ * No error handling for network failures in API calls
85
+ * Missing validation for user input boundaries
86
+ * Race condition possible in concurrent updates
87
+ * Memory leak potential in event listener registration
88
+
89
+ - Test Coverage Gaps:
90
+ * No tests for error scenarios
91
+ * Missing edge case: empty array input
92
+ * No performance benchmarks for large datasets
93
+ * Integration tests don't cover authentication failures
94
+
95
+ - Security Concerns:
96
+ * SQL injection vulnerability in query construction
97
+ * Missing rate limiting on public endpoints
98
+ * Sensitive data logged in debug mode
99
+
100
+ - Performance Issues:
101
+ * O(n²) algorithm where O(n log n) is possible
102
+ * Database queries in a loop (N+1 problem)
103
+ * No caching for expensive computations
104
+
105
+ - Code Quality:
106
+ * Functions too long and doing multiple things
107
+ * Inconsistent naming conventions
108
+ * Missing type annotations
109
+ * No documentation for complex algorithms
110
+
111
+ - Design Flaws:
112
+ * Tight coupling between modules
113
+ * Hard-coded configuration values
114
+ * No abstraction for external dependencies
115
+ * Violates single responsibility principle
116
+
117
+ Recommendations:
118
+ 1. Add comprehensive error handling with retry logic
119
+ 2. Implement input validation with clear error messages
120
+ 3. Use database transactions to prevent race conditions
121
+ 4. Add memory cleanup in component unmount
122
+ 5. Parameterize SQL queries to prevent injection
123
+ 6. Implement rate limiting middleware
124
+ 7. Use environment variables for sensitive config
125
+ 8. Refactor algorithm to use sorting approach
126
+ 9. Batch database queries
127
+ 10. Add memoization for expensive calculations
128
+ </critic_example>"""
129
+
130
+ def __init__(self) -> None:
131
+ """Initialize the critic tool."""
132
+ pass
133
+
134
+ @override
135
+ async def call(
136
+ self,
137
+ ctx: MCPContext,
138
+ **params: Unpack[CriticToolParams],
139
+ ) -> str:
140
+ """Execute the tool with the given parameters.
141
+
142
+ Args:
143
+ ctx: MCP context
144
+ **params: Tool parameters
145
+
146
+ Returns:
147
+ Tool result
148
+ """
149
+ tool_ctx = create_tool_context(ctx)
150
+ tool_ctx.set_tool_info(self.name)
151
+
152
+ # Extract parameters
153
+ analysis = params.get("analysis")
154
+
155
+ # Validate required analysis parameter
156
+ if not analysis:
157
+ await tool_ctx.error(
158
+ "Parameter 'analysis' is required but was None or empty"
159
+ )
160
+ return "Error: Parameter 'analysis' is required but was None or empty"
161
+
162
+ if analysis.strip() == "":
163
+ await tool_ctx.error("Parameter 'analysis' cannot be empty")
164
+ return "Error: Parameter 'analysis' cannot be empty"
165
+
166
+ # Log the critical analysis
167
+ await tool_ctx.info("Critical analysis recorded")
168
+
169
+ # Return confirmation with reminder to act on the analysis
170
+ return """Critical analysis complete. Remember to:
171
+ 1. Address all identified issues before proceeding
172
+ 2. Run comprehensive tests to verify fixes
173
+ 3. Ensure all tests pass with proper coverage
174
+ 4. Document any design decisions or trade-offs
175
+ 5. Consider the analysis points in your implementation
176
+
177
+ Continue with improvements based on this critical review."""
178
+
179
+ @override
180
+ def register(self, mcp_server: FastMCP) -> None:
181
+ """Register this critic tool with the MCP server.
182
+
183
+ Creates a wrapper function with explicitly defined parameters that match
184
+ the tool's parameter schema and registers it with the MCP server.
185
+
186
+ Args:
187
+ mcp_server: The FastMCP server instance
188
+ """
189
+ tool_self = self # Create a reference to self for use in the closure
190
+
191
+ @mcp_server.tool(name=self.name, description=self.description)
192
+ async def critic(
193
+ analysis: Analysis,
194
+ ctx: MCPContext
195
+ ) -> str:
196
+ return await tool_self.call(ctx, analysis=analysis)
@@ -0,0 +1,208 @@
1
+ """Decorators for MCP tools.
2
+
3
+ This module provides decorators that handle common cross-cutting concerns
4
+ for MCP tools, such as context normalization and error handling.
5
+ """
6
+
7
+ import functools
8
+ import inspect
9
+ from typing import Any, Callable, TypeVar, cast
10
+ from collections.abc import Awaitable, Coroutine
11
+
12
+ from mcp.server.fastmcp import Context as MCPContext
13
+
14
+ F = TypeVar('F', bound=Callable[..., Any])
15
+
16
+
17
+ class MockContext:
18
+ """Mock context for when no real context is available.
19
+
20
+ This is used when tools are called externally through the MCP protocol
21
+ and the Context parameter is not properly serialized.
22
+ """
23
+
24
+ def __init__(self):
25
+ self.request_id = "external-request"
26
+ self.client_id = "external-client"
27
+
28
+ async def info(self, message: str) -> None:
29
+ """Mock info logging - no-op for external calls."""
30
+ pass
31
+
32
+ async def debug(self, message: str) -> None:
33
+ """Mock debug logging - no-op for external calls."""
34
+ pass
35
+
36
+ async def warning(self, message: str) -> None:
37
+ """Mock warning logging - no-op for external calls."""
38
+ pass
39
+
40
+ async def error(self, message: str) -> None:
41
+ """Mock error logging - no-op for external calls."""
42
+ pass
43
+
44
+ async def report_progress(self, current: int, total: int) -> None:
45
+ """Mock progress reporting - no-op for external calls."""
46
+ pass
47
+
48
+ async def read_resource(self, uri: str) -> Any:
49
+ """Mock resource reading - returns empty result."""
50
+ return []
51
+
52
+
53
+ def with_context_normalization(func: F) -> F:
54
+ """Decorator that normalizes the context parameter for MCP tools.
55
+
56
+ This decorator intercepts the ctx parameter and ensures it's a valid
57
+ MCPContext object, even when called externally where it might be
58
+ passed as a string, dict, or None.
59
+
60
+ Usage:
61
+ @server.tool()
62
+ @with_context_normalization
63
+ async def my_tool(ctx: MCPContext, param: str) -> str:
64
+ # ctx is guaranteed to be a valid context object
65
+ await ctx.info("Processing...")
66
+ return "result"
67
+
68
+ Args:
69
+ func: The async function to decorate
70
+
71
+ Returns:
72
+ The decorated function with context normalization
73
+ """
74
+ @functools.wraps(func)
75
+ async def wrapper(*args: Any, **kwargs: Any) -> Any:
76
+ # Get function signature to find ctx parameter
77
+ sig = inspect.signature(func)
78
+ params = list(sig.parameters.keys())
79
+
80
+ # Handle ctx in kwargs
81
+ if 'ctx' in kwargs:
82
+ ctx_value = kwargs['ctx']
83
+ if not _is_valid_context(ctx_value):
84
+ kwargs['ctx'] = MockContext()
85
+
86
+ # Handle ctx in args (positional)
87
+ elif 'ctx' in params:
88
+ ctx_index = params.index('ctx')
89
+ if ctx_index < len(args):
90
+ ctx_value = args[ctx_index]
91
+ if not _is_valid_context(ctx_value):
92
+ args_list = list(args)
93
+ args_list[ctx_index] = MockContext()
94
+ args = tuple(args_list)
95
+
96
+ # Call the original function
97
+ return await func(*args, **kwargs)
98
+
99
+ return cast(F, wrapper)
100
+
101
+
102
+ def _is_valid_context(ctx: Any) -> bool:
103
+ """Check if an object is a valid MCPContext.
104
+
105
+ Args:
106
+ ctx: The object to check
107
+
108
+ Returns:
109
+ True if ctx is a valid context object
110
+ """
111
+ # Check for required context methods
112
+ return (
113
+ hasattr(ctx, 'info') and
114
+ hasattr(ctx, 'debug') and
115
+ hasattr(ctx, 'warning') and
116
+ hasattr(ctx, 'error') and
117
+ hasattr(ctx, 'report_progress') and
118
+ # Ensure they're callable
119
+ callable(getattr(ctx, 'info', None)) and
120
+ callable(getattr(ctx, 'debug', None))
121
+ )
122
+
123
+
124
+ def mcp_tool(
125
+ server: Any,
126
+ name: str | None = None,
127
+ description: str | None = None
128
+ ) -> Callable[[F], F]:
129
+ """Enhanced MCP tool decorator that includes context normalization.
130
+
131
+ This decorator combines the standard MCP tool registration with
132
+ automatic context normalization, providing a single-point solution
133
+ for all tools.
134
+
135
+ Usage:
136
+ @mcp_tool(server, name="my_tool", description="Does something")
137
+ async def my_tool(ctx: MCPContext, param: str) -> str:
138
+ await ctx.info("Processing...")
139
+ return "result"
140
+
141
+ Args:
142
+ server: The MCP server instance
143
+ name: Optional tool name (defaults to function name)
144
+ description: Optional tool description
145
+
146
+ Returns:
147
+ Decorator function
148
+ """
149
+ def decorator(func: F) -> F:
150
+ # Apply context normalization first
151
+ normalized_func = with_context_normalization(func)
152
+
153
+ # Then apply the server's tool decorator
154
+ if hasattr(server, 'tool'):
155
+ # Use the server's tool decorator
156
+ server_decorator = server.tool(name=name, description=description)
157
+ return server_decorator(normalized_func)
158
+ else:
159
+ # Fallback if server doesn't have tool method
160
+ return normalized_func
161
+
162
+ return decorator
163
+
164
+
165
+ def create_tool_handler(server: Any, tool: Any) -> Callable[[], None]:
166
+ """Create a standardized tool registration handler.
167
+
168
+ This function creates a registration method that automatically applies
169
+ context normalization to any tool handler registered with the server.
170
+
171
+ Usage:
172
+ class MyTool(BaseTool):
173
+ def register(self, mcp_server):
174
+ register = create_tool_handler(mcp_server, self)
175
+ register()
176
+
177
+ Args:
178
+ server: The MCP server instance
179
+ tool: The tool instance with name, description, and handler
180
+
181
+ Returns:
182
+ A function that registers the tool with context normalization
183
+ """
184
+ def register_with_normalization():
185
+ # Get the original register method
186
+ original_register = tool.__class__.register
187
+
188
+ # Temporarily replace server.tool to wrap with normalization
189
+ original_tool_decorator = server.tool
190
+
191
+ def normalized_tool_decorator(name=None, description=None):
192
+ def decorator(func):
193
+ # Apply context normalization
194
+ normalized = with_context_normalization(func)
195
+ # Apply original decorator
196
+ return original_tool_decorator(name=name, description=description)(normalized)
197
+ return decorator
198
+
199
+ # Monkey-patch temporarily
200
+ server.tool = normalized_tool_decorator
201
+ try:
202
+ # Call original register
203
+ original_register(tool, server)
204
+ finally:
205
+ # Restore original
206
+ server.tool = original_tool_decorator
207
+
208
+ return register_with_normalization
@@ -0,0 +1,106 @@
1
+ """Enhanced base classes for MCP tools with automatic context handling.
2
+
3
+ This module provides enhanced base classes that automatically handle
4
+ context normalization and other cross-cutting concerns, ensuring
5
+ consistent behavior across all tools.
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+ from typing import Any, get_type_hints, get_args, get_origin
10
+ import inspect
11
+
12
+ from mcp.server import FastMCP
13
+ from mcp.server.fastmcp import Context as MCPContext
14
+
15
+ from hanzo_mcp.tools.common.base import BaseTool
16
+ from hanzo_mcp.tools.common.decorators import with_context_normalization
17
+
18
+
19
+ class EnhancedBaseTool(BaseTool, ABC):
20
+ """Enhanced base class for MCP tools with automatic context normalization.
21
+
22
+ This base class automatically wraps the tool registration to include
23
+ context normalization, ensuring that all tools handle external calls
24
+ properly without requiring manual decoration or copy-pasted code.
25
+ """
26
+
27
+ def register(self, mcp_server: FastMCP) -> None:
28
+ """Register this tool with automatic context normalization.
29
+
30
+ This method automatically applies context normalization to the tool
31
+ handler, ensuring it works properly when called externally.
32
+
33
+ Args:
34
+ mcp_server: The FastMCP server instance
35
+ """
36
+ # Get the tool method from the subclass
37
+ tool_method = self._create_tool_handler()
38
+
39
+ # Apply context normalization decorator
40
+ normalized_method = with_context_normalization(tool_method)
41
+
42
+ # Register with the server
43
+ mcp_server.tool(
44
+ name=self.name,
45
+ description=self.description
46
+ )(normalized_method)
47
+
48
+ @abstractmethod
49
+ def _create_tool_handler(self) -> Any:
50
+ """Create the tool handler function.
51
+
52
+ Subclasses must implement this to return an async function
53
+ that will be registered as the tool handler.
54
+
55
+ Returns:
56
+ An async function that handles tool calls
57
+ """
58
+ pass
59
+
60
+
61
+ class AutoRegisterTool(BaseTool, ABC):
62
+ """Base class that automatically generates tool handlers from the call method.
63
+
64
+ This base class inspects the call method signature and automatically
65
+ creates a properly typed tool handler with context normalization.
66
+ """
67
+
68
+ def register(self, mcp_server: FastMCP) -> None:
69
+ """Register this tool with automatic handler generation.
70
+
71
+ This method inspects the call method signature and automatically
72
+ creates a tool handler with the correct parameters and types.
73
+
74
+ Args:
75
+ mcp_server: The FastMCP server instance
76
+ """
77
+ # Get the call method signature
78
+ call_method = self.call
79
+ sig = inspect.signature(call_method)
80
+
81
+ # Get type hints for proper typing
82
+ hints = get_type_hints(call_method)
83
+
84
+ # Create a dynamic handler function
85
+ tool_self = self
86
+
87
+ # Build the handler dynamically based on the call signature
88
+ params = list(sig.parameters.items())
89
+
90
+ # Skip 'self' and 'ctx' parameters
91
+ tool_params = [(name, param) for name, param in params
92
+ if name not in ('self', 'ctx')]
93
+
94
+ # Create the handler function dynamically
95
+ async def handler(ctx: MCPContext, **kwargs: Any) -> Any:
96
+ # Call the tool's call method with the context and parameters
97
+ return await tool_self.call(ctx, **kwargs)
98
+
99
+ # Apply context normalization
100
+ normalized_handler = with_context_normalization(handler)
101
+
102
+ # Register with the server
103
+ mcp_server.tool(
104
+ name=self.name,
105
+ description=self.description
106
+ )(normalized_handler)