hanzo-mcp 0.6.10__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.
- hanzo_mcp/__init__.py +11 -2
- hanzo_mcp/cli.py +69 -19
- hanzo_mcp/cli_enhanced.py +15 -12
- hanzo_mcp/cli_plugin.py +91 -0
- hanzo_mcp/config/__init__.py +1 -1
- hanzo_mcp/config/settings.py +75 -8
- hanzo_mcp/config/tool_config.py +2 -2
- hanzo_mcp/dev_server.py +20 -15
- hanzo_mcp/prompts/project_system.py +1 -1
- hanzo_mcp/server.py +18 -4
- hanzo_mcp/server_enhanced.py +69 -0
- hanzo_mcp/tools/__init__.py +78 -30
- hanzo_mcp/tools/agent/__init__.py +1 -1
- hanzo_mcp/tools/agent/agent_tool.py +2 -2
- hanzo_mcp/tools/common/__init__.py +15 -1
- hanzo_mcp/tools/common/base.py +4 -4
- hanzo_mcp/tools/common/batch_tool.py +1 -1
- hanzo_mcp/tools/common/config_tool.py +2 -2
- hanzo_mcp/tools/common/context.py +2 -2
- hanzo_mcp/tools/common/context_fix.py +26 -0
- hanzo_mcp/tools/common/critic_tool.py +196 -0
- hanzo_mcp/tools/common/decorators.py +208 -0
- hanzo_mcp/tools/common/enhanced_base.py +106 -0
- hanzo_mcp/tools/common/mode.py +116 -0
- hanzo_mcp/tools/common/mode_loader.py +105 -0
- hanzo_mcp/tools/common/permissions.py +1 -1
- hanzo_mcp/tools/common/personality.py +936 -0
- hanzo_mcp/tools/common/plugin_loader.py +287 -0
- hanzo_mcp/tools/common/stats.py +4 -4
- hanzo_mcp/tools/common/tool_list.py +1 -1
- hanzo_mcp/tools/common/validation.py +1 -1
- hanzo_mcp/tools/config/__init__.py +3 -1
- hanzo_mcp/tools/config/config_tool.py +1 -1
- hanzo_mcp/tools/config/mode_tool.py +209 -0
- hanzo_mcp/tools/database/__init__.py +1 -1
- hanzo_mcp/tools/editor/__init__.py +1 -1
- hanzo_mcp/tools/filesystem/__init__.py +19 -14
- hanzo_mcp/tools/filesystem/batch_search.py +3 -3
- hanzo_mcp/tools/filesystem/diff.py +2 -2
- hanzo_mcp/tools/filesystem/rules_tool.py +235 -0
- hanzo_mcp/tools/filesystem/{unified_search.py → search_tool.py} +12 -12
- hanzo_mcp/tools/filesystem/{symbols_unified.py → symbols_tool.py} +104 -5
- hanzo_mcp/tools/filesystem/watch.py +3 -2
- hanzo_mcp/tools/jupyter/__init__.py +2 -2
- hanzo_mcp/tools/jupyter/jupyter.py +1 -1
- hanzo_mcp/tools/llm/__init__.py +3 -3
- hanzo_mcp/tools/llm/llm_tool.py +648 -143
- hanzo_mcp/tools/mcp/__init__.py +2 -2
- hanzo_mcp/tools/mcp/{mcp_unified.py → mcp_tool.py} +3 -3
- hanzo_mcp/tools/shell/__init__.py +6 -6
- hanzo_mcp/tools/shell/base_process.py +4 -2
- hanzo_mcp/tools/shell/bash_session_executor.py +8 -5
- hanzo_mcp/tools/shell/{bash_unified.py → bash_tool.py} +1 -1
- hanzo_mcp/tools/shell/command_executor.py +8 -6
- hanzo_mcp/tools/shell/{npx_unified.py → npx_tool.py} +1 -1
- hanzo_mcp/tools/shell/open.py +2 -2
- hanzo_mcp/tools/shell/{process_unified.py → process_tool.py} +1 -1
- hanzo_mcp/tools/shell/run_command_windows.py +1 -1
- hanzo_mcp/tools/shell/uvx.py +47 -2
- hanzo_mcp/tools/shell/uvx_background.py +47 -2
- hanzo_mcp/tools/shell/{uvx_unified.py → uvx_tool.py} +1 -1
- hanzo_mcp/tools/todo/__init__.py +14 -19
- hanzo_mcp/tools/todo/todo.py +22 -1
- hanzo_mcp/tools/vector/__init__.py +7 -3
- hanzo_mcp/tools/vector/ast_analyzer.py +12 -4
- hanzo_mcp/tools/vector/infinity_store.py +11 -5
- hanzo_mcp/tools/vector/project_manager.py +4 -2
- hanzo_mcp-0.6.13.dist-info/METADATA +359 -0
- {hanzo_mcp-0.6.10.dist-info → hanzo_mcp-0.6.13.dist-info}/RECORD +73 -65
- {hanzo_mcp-0.6.10.dist-info → hanzo_mcp-0.6.13.dist-info}/entry_points.txt +1 -0
- hanzo_mcp/tools/common/palette.py +0 -344
- hanzo_mcp/tools/common/palette_loader.py +0 -108
- hanzo_mcp/tools/config/palette_tool.py +0 -179
- hanzo_mcp/tools/llm/llm_unified.py +0 -851
- hanzo_mcp-0.6.10.dist-info/METADATA +0 -339
- {hanzo_mcp-0.6.10.dist-info → hanzo_mcp-0.6.13.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.6.10.dist-info → hanzo_mcp-0.6.13.dist-info}/licenses/LICENSE +0 -0
- {hanzo_mcp-0.6.10.dist-info → hanzo_mcp-0.6.13.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Enhanced MCP server with automatic context normalization.
|
|
2
|
+
|
|
3
|
+
This module provides an enhanced FastMCP server that automatically
|
|
4
|
+
applies context normalization to all registered tools.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
from functools import wraps
|
|
9
|
+
|
|
10
|
+
from mcp.server import FastMCP
|
|
11
|
+
|
|
12
|
+
from hanzo_mcp.tools.common.decorators import with_context_normalization
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class EnhancedFastMCP(FastMCP):
|
|
16
|
+
"""Enhanced FastMCP server with automatic context normalization.
|
|
17
|
+
|
|
18
|
+
This server automatically wraps all tool registrations with context
|
|
19
|
+
normalization, ensuring that tools work properly when called externally
|
|
20
|
+
with serialized context parameters.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def tool(
|
|
24
|
+
self,
|
|
25
|
+
name: str | None = None,
|
|
26
|
+
description: str | None = None
|
|
27
|
+
) -> Callable:
|
|
28
|
+
"""Enhanced tool decorator that includes automatic context normalization.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
name: Tool name (defaults to function name)
|
|
32
|
+
description: Tool description
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Decorator function that registers the tool with context normalization
|
|
36
|
+
"""
|
|
37
|
+
# Get the original decorator from parent class
|
|
38
|
+
original_decorator = super().tool(
|
|
39
|
+
name=name,
|
|
40
|
+
description=description
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Create our enhanced decorator
|
|
44
|
+
def enhanced_decorator(func: Callable) -> Callable:
|
|
45
|
+
# Apply context normalization first
|
|
46
|
+
# Check if function has ctx parameter
|
|
47
|
+
import inspect
|
|
48
|
+
sig = inspect.signature(func)
|
|
49
|
+
if 'ctx' in sig.parameters:
|
|
50
|
+
normalized_func = with_context_normalization(func)
|
|
51
|
+
else:
|
|
52
|
+
normalized_func = func
|
|
53
|
+
|
|
54
|
+
# Then apply the original decorator
|
|
55
|
+
return original_decorator(normalized_func)
|
|
56
|
+
|
|
57
|
+
return enhanced_decorator
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def create_enhanced_server(name: str = "hanzo") -> EnhancedFastMCP:
|
|
61
|
+
"""Create an enhanced MCP server with automatic context normalization.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
name: Server name
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Enhanced FastMCP server instance
|
|
68
|
+
"""
|
|
69
|
+
return EnhancedFastMCP(name)
|
hanzo_mcp/tools/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"""Tools package for Hanzo
|
|
1
|
+
"""Tools package for Hanzo AI.
|
|
2
2
|
|
|
3
|
-
This package contains all the tools for the Hanzo
|
|
4
|
-
It provides a
|
|
3
|
+
This package contains all the tools for the Hanzo AI server.
|
|
4
|
+
It provides a interface for registering all tools with an MCP server.
|
|
5
5
|
|
|
6
6
|
This includes a "think" tool implementation based on Anthropic's research showing
|
|
7
7
|
improved performance for complex tool-based interactions when Claude has a dedicated
|
|
@@ -12,7 +12,7 @@ to delegate tasks to sub-agents for concurrent execution and specialized process
|
|
|
12
12
|
from mcp.server import FastMCP
|
|
13
13
|
|
|
14
14
|
from hanzo_mcp.tools.agent import register_agent_tools
|
|
15
|
-
from hanzo_mcp.tools.common import register_batch_tool, register_thinking_tool
|
|
15
|
+
from hanzo_mcp.tools.common import register_batch_tool, register_thinking_tool, register_critic_tool
|
|
16
16
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
17
17
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
18
18
|
from hanzo_mcp.tools.common.tool_enable import ToolEnableTool
|
|
@@ -25,11 +25,13 @@ from hanzo_mcp.tools.shell import register_shell_tools
|
|
|
25
25
|
from hanzo_mcp.tools.todo import register_todo_tools
|
|
26
26
|
from hanzo_mcp.tools.vector import register_vector_tools
|
|
27
27
|
from hanzo_mcp.tools.database import register_database_tools, DatabaseManager
|
|
28
|
-
from hanzo_mcp.tools.mcp import
|
|
28
|
+
from hanzo_mcp.tools.mcp import MCPTool, McpAddTool, McpRemoveTool, McpStatsTool
|
|
29
29
|
from hanzo_mcp.tools.editor import NeovimEditTool, NeovimCommandTool, NeovimSessionTool
|
|
30
|
-
from hanzo_mcp.tools.llm import
|
|
31
|
-
from hanzo_mcp.tools.config.
|
|
32
|
-
from hanzo_mcp.tools.common.
|
|
30
|
+
from hanzo_mcp.tools.llm import LLMTool, LLMTool, ConsensusTool, LLMManageTool, create_provider_tools
|
|
31
|
+
from hanzo_mcp.tools.config.mode_tool import mode_tool
|
|
32
|
+
from hanzo_mcp.tools.common.mode_loader import ModeLoader
|
|
33
|
+
from hanzo_mcp.tools.common.mode import activate_mode_from_env
|
|
34
|
+
from hanzo_mcp.tools.common.plugin_loader import load_user_plugins
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
def register_all_tools(
|
|
@@ -46,8 +48,8 @@ def register_all_tools(
|
|
|
46
48
|
disable_search_tools: bool = False,
|
|
47
49
|
enabled_tools: dict[str, bool] | None = None,
|
|
48
50
|
vector_config: dict | None = None,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
use_mode: bool = True,
|
|
52
|
+
force_mode: str | None = None,
|
|
51
53
|
) -> None:
|
|
52
54
|
"""Register all Hanzo tools with the MCP server.
|
|
53
55
|
|
|
@@ -65,20 +67,36 @@ def register_all_tools(
|
|
|
65
67
|
disable_search_tools: Whether to disable search tools (default: False)
|
|
66
68
|
enabled_tools: Dictionary of individual tool enable/disable states (default: None)
|
|
67
69
|
vector_config: Vector store configuration (default: None)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
use_mode: Whether to use mode system for tool configuration (default: True)
|
|
71
|
+
force_mode: Force a specific mode to be active (default: None)
|
|
70
72
|
"""
|
|
71
73
|
# Dictionary to store all registered tools
|
|
72
74
|
all_tools: dict[str, BaseTool] = {}
|
|
73
75
|
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
# Load user plugins early
|
|
77
|
+
try:
|
|
78
|
+
plugins = load_user_plugins()
|
|
79
|
+
import logging
|
|
80
|
+
logger = logging.getLogger(__name__)
|
|
81
|
+
if plugins:
|
|
82
|
+
logger.info(f"Loaded {len(plugins)} user plugin tools: {', '.join(plugins.keys())}")
|
|
83
|
+
except Exception as e:
|
|
84
|
+
import logging
|
|
85
|
+
logger = logging.getLogger(__name__)
|
|
86
|
+
logger.warning(f"Failed to load user plugins: {e}")
|
|
87
|
+
plugins = {}
|
|
88
|
+
|
|
89
|
+
# Apply mode configuration if enabled
|
|
90
|
+
if use_mode:
|
|
91
|
+
# First check for mode activation from environment
|
|
92
|
+
activate_mode_from_env()
|
|
93
|
+
|
|
94
|
+
tool_config = ModeLoader.get_enabled_tools_from_mode(
|
|
77
95
|
base_enabled_tools=enabled_tools,
|
|
78
|
-
|
|
96
|
+
force_mode=force_mode
|
|
79
97
|
)
|
|
80
|
-
# Apply
|
|
81
|
-
|
|
98
|
+
# Apply mode environment variables
|
|
99
|
+
ModeLoader.apply_environment_from_mode()
|
|
82
100
|
else:
|
|
83
101
|
# Use individual tool configuration if provided, otherwise fall back to category-level flags
|
|
84
102
|
tool_config = enabled_tools or {}
|
|
@@ -97,15 +115,16 @@ def register_all_tools(
|
|
|
97
115
|
"multi_edit": is_tool_enabled("multi_edit", not disable_write_tools),
|
|
98
116
|
"directory_tree": is_tool_enabled("directory_tree", True),
|
|
99
117
|
"grep": is_tool_enabled("grep", not disable_search_tools),
|
|
100
|
-
"
|
|
118
|
+
"symbols": is_tool_enabled("symbols", not disable_search_tools),
|
|
101
119
|
"git_search": is_tool_enabled("git_search", not disable_search_tools),
|
|
102
120
|
"content_replace": is_tool_enabled("content_replace", not disable_write_tools),
|
|
103
121
|
"batch_search": is_tool_enabled("batch_search", not disable_search_tools),
|
|
104
122
|
"find_files": is_tool_enabled("find_files", True),
|
|
105
|
-
"
|
|
123
|
+
"rules": is_tool_enabled("rules", True),
|
|
124
|
+
"search": is_tool_enabled("search", not disable_search_tools),
|
|
106
125
|
}
|
|
107
126
|
|
|
108
|
-
# Vector tools setup (needed for
|
|
127
|
+
# Vector tools setup (needed for search)
|
|
109
128
|
project_manager = None
|
|
110
129
|
vector_enabled = {
|
|
111
130
|
"vector_index": is_tool_enabled("vector_index", False),
|
|
@@ -113,7 +132,7 @@ def register_all_tools(
|
|
|
113
132
|
}
|
|
114
133
|
|
|
115
134
|
# Create project manager if vector tools, batch_search, or unified_search are enabled
|
|
116
|
-
if any(vector_enabled.values()) or filesystem_enabled.get("batch_search", False) or filesystem_enabled.get("
|
|
135
|
+
if any(vector_enabled.values()) or filesystem_enabled.get("batch_search", False) or filesystem_enabled.get("search", False):
|
|
117
136
|
if vector_config:
|
|
118
137
|
from hanzo_mcp.tools.vector.project_manager import ProjectVectorManager
|
|
119
138
|
search_paths = [str(path) for path in permission_manager.allowed_paths]
|
|
@@ -124,7 +143,9 @@ def register_all_tools(
|
|
|
124
143
|
)
|
|
125
144
|
# Auto-detect projects from search paths
|
|
126
145
|
detected_projects = project_manager.detect_projects(search_paths)
|
|
127
|
-
|
|
146
|
+
import logging
|
|
147
|
+
logger = logging.getLogger(__name__)
|
|
148
|
+
logger.info(f"Detected {len(detected_projects)} projects with LLM.md files")
|
|
128
149
|
|
|
129
150
|
filesystem_tools = register_filesystem_tools(
|
|
130
151
|
mcp_server,
|
|
@@ -153,7 +174,7 @@ def register_all_tools(
|
|
|
153
174
|
all_tools[tool.name] = tool
|
|
154
175
|
|
|
155
176
|
# Register agent tools if enabled
|
|
156
|
-
agent_enabled = enable_agent_tool or is_tool_enabled("dispatch_agent", False)
|
|
177
|
+
agent_enabled = enable_agent_tool or is_tool_enabled("agent", False) or is_tool_enabled("dispatch_agent", False)
|
|
157
178
|
if agent_enabled:
|
|
158
179
|
agent_tools = register_agent_tools(
|
|
159
180
|
mcp_server,
|
|
@@ -170,12 +191,15 @@ def register_all_tools(
|
|
|
170
191
|
|
|
171
192
|
# Register todo tools if enabled
|
|
172
193
|
todo_enabled = {
|
|
194
|
+
"todo": is_tool_enabled("todo", True),
|
|
195
|
+
# Backward compatibility - if old names are used, enable the unified tool
|
|
173
196
|
"todo_read": is_tool_enabled("todo_read", True),
|
|
174
197
|
"todo_write": is_tool_enabled("todo_write", True),
|
|
175
198
|
}
|
|
176
199
|
|
|
200
|
+
# Enable unified todo if any of the todo tools are enabled
|
|
177
201
|
if any(todo_enabled.values()):
|
|
178
|
-
todo_tools = register_todo_tools(mcp_server, enabled_tools=
|
|
202
|
+
todo_tools = register_todo_tools(mcp_server, enabled_tools={"todo": True})
|
|
179
203
|
for tool in todo_tools:
|
|
180
204
|
all_tools[tool.name] = tool
|
|
181
205
|
|
|
@@ -184,6 +208,12 @@ def register_all_tools(
|
|
|
184
208
|
thinking_tool = register_thinking_tool(mcp_server)
|
|
185
209
|
for tool in thinking_tool:
|
|
186
210
|
all_tools[tool.name] = tool
|
|
211
|
+
|
|
212
|
+
# Register critic tool if enabled
|
|
213
|
+
if is_tool_enabled("critic", True):
|
|
214
|
+
critic_tools = register_critic_tool(mcp_server)
|
|
215
|
+
for tool in critic_tools:
|
|
216
|
+
all_tools[tool.name] = tool
|
|
187
217
|
|
|
188
218
|
# Register vector tools if enabled (reuse project_manager if available)
|
|
189
219
|
if any(vector_enabled.values()) and project_manager:
|
|
@@ -228,7 +258,7 @@ def register_all_tools(
|
|
|
228
258
|
|
|
229
259
|
# Register unified MCP tool if enabled
|
|
230
260
|
if is_tool_enabled("mcp", True):
|
|
231
|
-
tool =
|
|
261
|
+
tool = MCPTool()
|
|
232
262
|
tool.register(mcp_server)
|
|
233
263
|
all_tools[tool.name] = tool
|
|
234
264
|
|
|
@@ -273,9 +303,9 @@ def register_all_tools(
|
|
|
273
303
|
stats_tool.register(mcp_server)
|
|
274
304
|
all_tools[stats_tool.name] = stats_tool
|
|
275
305
|
|
|
276
|
-
#
|
|
277
|
-
|
|
278
|
-
all_tools[
|
|
306
|
+
# Mode tool (always enabled for managing tool sets)
|
|
307
|
+
mode_tool.register(mcp_server)
|
|
308
|
+
all_tools[mode_tool.name] = mode_tool
|
|
279
309
|
|
|
280
310
|
# Register editor tools if enabled
|
|
281
311
|
editor_enabled = {
|
|
@@ -301,11 +331,18 @@ def register_all_tools(
|
|
|
301
331
|
|
|
302
332
|
# Register unified LLM tool if enabled
|
|
303
333
|
if is_tool_enabled("llm", True):
|
|
304
|
-
tool =
|
|
334
|
+
tool = LLMTool()
|
|
305
335
|
if tool.available_providers: # Only register if API keys found
|
|
306
336
|
tool.register(mcp_server)
|
|
307
337
|
all_tools[tool.name] = tool
|
|
308
338
|
|
|
339
|
+
# Register consensus tool if enabled (enabled by default)
|
|
340
|
+
if is_tool_enabled("consensus", True):
|
|
341
|
+
tool = ConsensusTool()
|
|
342
|
+
if tool.llm_tool.available_providers:
|
|
343
|
+
tool.register(mcp_server)
|
|
344
|
+
all_tools[tool.name] = tool
|
|
345
|
+
|
|
309
346
|
# Register legacy LLM tools if explicitly enabled (disabled by default)
|
|
310
347
|
legacy_llm_enabled = {
|
|
311
348
|
"llm_legacy": is_tool_enabled("llm_legacy", False),
|
|
@@ -337,3 +374,14 @@ def register_all_tools(
|
|
|
337
374
|
if is_tool_enabled(tool.name, False):
|
|
338
375
|
tool.register(mcp_server)
|
|
339
376
|
all_tools[tool.name] = tool
|
|
377
|
+
|
|
378
|
+
# Register user plugins last (so they can override built-in tools)
|
|
379
|
+
for plugin_name, plugin in plugins.items():
|
|
380
|
+
if is_tool_enabled(plugin_name, True):
|
|
381
|
+
try:
|
|
382
|
+
tool = plugin.tool_class()
|
|
383
|
+
tool.register(mcp_server)
|
|
384
|
+
all_tools[tool.name] = tool
|
|
385
|
+
logger.info(f"Registered plugin tool: {plugin_name}")
|
|
386
|
+
except Exception as e:
|
|
387
|
+
logger.error(f"Failed to register plugin tool {plugin_name}: {e}")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Agent tool implementation for Hanzo
|
|
1
|
+
"""Agent tool implementation for Hanzo AI.
|
|
2
2
|
|
|
3
3
|
This module implements the AgentTool that allows Claude to delegate tasks to sub-agents,
|
|
4
4
|
enabling concurrent execution of multiple operations and specialized processing.
|
|
@@ -75,7 +75,7 @@ class AgentTool(BaseTool):
|
|
|
75
75
|
Returns:
|
|
76
76
|
Tool name
|
|
77
77
|
"""
|
|
78
|
-
return "
|
|
78
|
+
return "agent"
|
|
79
79
|
|
|
80
80
|
@property
|
|
81
81
|
@override
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
"""Common utilities for Hanzo
|
|
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
|
|
hanzo_mcp/tools/common/base.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"""Base classes for Hanzo
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
@@ -34,7 +34,7 @@ class ConfigToolParams(TypedDict, total=False):
|
|
|
34
34
|
|
|
35
35
|
@final
|
|
36
36
|
class ConfigTool(BaseTool):
|
|
37
|
-
"""Tool for managing Hanzo
|
|
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
|
|
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
|
|
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
|
|
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)
|