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

Files changed (54) hide show
  1. hanzo_mcp/__init__.py +1 -1
  2. hanzo_mcp/tools/__init__.py +135 -4
  3. hanzo_mcp/tools/common/base.py +7 -2
  4. hanzo_mcp/tools/common/stats.py +261 -0
  5. hanzo_mcp/tools/common/tool_disable.py +144 -0
  6. hanzo_mcp/tools/common/tool_enable.py +182 -0
  7. hanzo_mcp/tools/common/tool_list.py +263 -0
  8. hanzo_mcp/tools/database/__init__.py +71 -0
  9. hanzo_mcp/tools/database/database_manager.py +246 -0
  10. hanzo_mcp/tools/database/graph_add.py +257 -0
  11. hanzo_mcp/tools/database/graph_query.py +536 -0
  12. hanzo_mcp/tools/database/graph_remove.py +267 -0
  13. hanzo_mcp/tools/database/graph_search.py +348 -0
  14. hanzo_mcp/tools/database/graph_stats.py +345 -0
  15. hanzo_mcp/tools/database/sql_query.py +229 -0
  16. hanzo_mcp/tools/database/sql_search.py +296 -0
  17. hanzo_mcp/tools/database/sql_stats.py +254 -0
  18. hanzo_mcp/tools/editor/__init__.py +11 -0
  19. hanzo_mcp/tools/editor/neovim_command.py +272 -0
  20. hanzo_mcp/tools/editor/neovim_edit.py +290 -0
  21. hanzo_mcp/tools/editor/neovim_session.py +356 -0
  22. hanzo_mcp/tools/filesystem/__init__.py +15 -5
  23. hanzo_mcp/tools/filesystem/{unified_search.py → batch_search.py} +254 -131
  24. hanzo_mcp/tools/filesystem/find_files.py +348 -0
  25. hanzo_mcp/tools/filesystem/git_search.py +505 -0
  26. hanzo_mcp/tools/llm/__init__.py +27 -0
  27. hanzo_mcp/tools/llm/consensus_tool.py +351 -0
  28. hanzo_mcp/tools/llm/llm_manage.py +413 -0
  29. hanzo_mcp/tools/llm/llm_tool.py +346 -0
  30. hanzo_mcp/tools/llm/provider_tools.py +412 -0
  31. hanzo_mcp/tools/mcp/__init__.py +11 -0
  32. hanzo_mcp/tools/mcp/mcp_add.py +263 -0
  33. hanzo_mcp/tools/mcp/mcp_remove.py +127 -0
  34. hanzo_mcp/tools/mcp/mcp_stats.py +165 -0
  35. hanzo_mcp/tools/shell/__init__.py +27 -7
  36. hanzo_mcp/tools/shell/logs.py +265 -0
  37. hanzo_mcp/tools/shell/npx.py +194 -0
  38. hanzo_mcp/tools/shell/npx_background.py +254 -0
  39. hanzo_mcp/tools/shell/pkill.py +262 -0
  40. hanzo_mcp/tools/shell/processes.py +279 -0
  41. hanzo_mcp/tools/shell/run_background.py +326 -0
  42. hanzo_mcp/tools/shell/uvx.py +187 -0
  43. hanzo_mcp/tools/shell/uvx_background.py +249 -0
  44. hanzo_mcp/tools/vector/__init__.py +5 -0
  45. hanzo_mcp/tools/vector/git_ingester.py +3 -0
  46. hanzo_mcp/tools/vector/index_tool.py +358 -0
  47. hanzo_mcp/tools/vector/infinity_store.py +98 -0
  48. hanzo_mcp/tools/vector/vector_search.py +11 -6
  49. {hanzo_mcp-0.5.1.dist-info → hanzo_mcp-0.5.2.dist-info}/METADATA +1 -1
  50. {hanzo_mcp-0.5.1.dist-info → hanzo_mcp-0.5.2.dist-info}/RECORD +54 -16
  51. {hanzo_mcp-0.5.1.dist-info → hanzo_mcp-0.5.2.dist-info}/WHEEL +0 -0
  52. {hanzo_mcp-0.5.1.dist-info → hanzo_mcp-0.5.2.dist-info}/entry_points.txt +0 -0
  53. {hanzo_mcp-0.5.1.dist-info → hanzo_mcp-0.5.2.dist-info}/licenses/LICENSE +0 -0
  54. {hanzo_mcp-0.5.1.dist-info → hanzo_mcp-0.5.2.dist-info}/top_level.txt +0 -0
hanzo_mcp/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Hanzo MCP - Implementation of Hanzo capabilities using MCP."""
2
2
 
3
- __version__ = "0.5.1"
3
+ __version__ = "0.5.2"
@@ -14,13 +14,22 @@ from fastmcp import FastMCP
14
14
  from hanzo_mcp.tools.agent import register_agent_tools
15
15
  from hanzo_mcp.tools.common import register_batch_tool, register_thinking_tool
16
16
  from hanzo_mcp.tools.common.base import BaseTool
17
-
18
17
  from hanzo_mcp.tools.common.permissions import PermissionManager
18
+ from hanzo_mcp.tools.common.tool_enable import ToolEnableTool
19
+ from hanzo_mcp.tools.common.tool_disable import ToolDisableTool
20
+ from hanzo_mcp.tools.common.tool_list import ToolListTool
21
+ from hanzo_mcp.tools.common.stats import StatsTool
19
22
  from hanzo_mcp.tools.filesystem import register_filesystem_tools
20
23
  from hanzo_mcp.tools.jupyter import register_jupyter_tools
21
24
  from hanzo_mcp.tools.shell import register_shell_tools
22
25
  from hanzo_mcp.tools.todo import register_todo_tools
23
26
  from hanzo_mcp.tools.vector import register_vector_tools
27
+ from hanzo_mcp.tools.database import register_database_tools, DatabaseManager
28
+ from hanzo_mcp.tools.mcp.mcp_add import McpAddTool
29
+ from hanzo_mcp.tools.mcp.mcp_remove import McpRemoveTool
30
+ from hanzo_mcp.tools.mcp.mcp_stats import McpStatsTool
31
+ from hanzo_mcp.tools.editor import NeovimEditTool, NeovimCommandTool, NeovimSessionTool
32
+ from hanzo_mcp.tools.llm import LLMTool, ConsensusTool, LLMManageTool, create_provider_tools
24
33
 
25
34
 
26
35
  def register_all_tools(
@@ -76,8 +85,10 @@ def register_all_tools(
76
85
  "directory_tree": is_tool_enabled("directory_tree", True),
77
86
  "grep": is_tool_enabled("grep", not disable_search_tools),
78
87
  "grep_ast": is_tool_enabled("grep_ast", not disable_search_tools),
88
+ "git_search": is_tool_enabled("git_search", not disable_search_tools),
79
89
  "content_replace": is_tool_enabled("content_replace", not disable_write_tools),
80
- "unified_search": is_tool_enabled("unified_search", not disable_search_tools),
90
+ "batch_search": is_tool_enabled("batch_search", not disable_search_tools),
91
+ "find_files": is_tool_enabled("find_files", True),
81
92
  }
82
93
 
83
94
  # Vector tools setup (needed for unified search)
@@ -87,8 +98,8 @@ def register_all_tools(
87
98
  "vector_search": is_tool_enabled("vector_search", False),
88
99
  }
89
100
 
90
- # Create project manager if vector tools or unified search are enabled
91
- if any(vector_enabled.values()) or filesystem_enabled.get("unified_search", False):
101
+ # Create project manager if vector tools or batch_search are enabled
102
+ if any(vector_enabled.values()) or filesystem_enabled.get("batch_search", False):
92
103
  if vector_config:
93
104
  from hanzo_mcp.tools.vector.project_manager import ProjectVectorManager
94
105
  search_paths = [str(path) for path in permission_manager.allowed_paths]
@@ -175,3 +186,123 @@ def register_all_tools(
175
186
  # Register batch tool if enabled (batch tool is typically always enabled)
176
187
  if is_tool_enabled("batch", True):
177
188
  register_batch_tool(mcp_server, all_tools)
189
+
190
+ # Register database tools if enabled
191
+ db_manager = None
192
+ database_enabled = {
193
+ "sql_query": is_tool_enabled("sql_query", True),
194
+ "sql_search": is_tool_enabled("sql_search", True),
195
+ "sql_stats": is_tool_enabled("sql_stats", True),
196
+ "graph_add": is_tool_enabled("graph_add", True),
197
+ "graph_remove": is_tool_enabled("graph_remove", True),
198
+ "graph_query": is_tool_enabled("graph_query", True),
199
+ "graph_search": is_tool_enabled("graph_search", True),
200
+ "graph_stats": is_tool_enabled("graph_stats", True),
201
+ }
202
+
203
+ if any(database_enabled.values()):
204
+ db_manager = DatabaseManager(permission_manager)
205
+ database_tools = register_database_tools(
206
+ mcp_server,
207
+ permission_manager,
208
+ db_manager=db_manager,
209
+ )
210
+ # Filter based on enabled state
211
+ for tool in database_tools:
212
+ if database_enabled.get(tool.name, True):
213
+ all_tools[tool.name] = tool
214
+
215
+ # Register MCP management tools if enabled
216
+ mcp_enabled = {
217
+ "mcp_add": is_tool_enabled("mcp_add", True),
218
+ "mcp_remove": is_tool_enabled("mcp_remove", True),
219
+ "mcp_stats": is_tool_enabled("mcp_stats", True),
220
+ }
221
+
222
+ if mcp_enabled.get("mcp_add", True):
223
+ tool = McpAddTool()
224
+ tool.register(mcp_server)
225
+ all_tools[tool.name] = tool
226
+
227
+ if mcp_enabled.get("mcp_remove", True):
228
+ tool = McpRemoveTool()
229
+ tool.register(mcp_server)
230
+ all_tools[tool.name] = tool
231
+
232
+ if mcp_enabled.get("mcp_stats", True):
233
+ tool = McpStatsTool()
234
+ tool.register(mcp_server)
235
+ all_tools[tool.name] = tool
236
+
237
+ # Register system tools (always enabled)
238
+ # Tool enable/disable tools
239
+ tool_enable = ToolEnableTool()
240
+ tool_enable.register(mcp_server)
241
+ all_tools[tool_enable.name] = tool_enable
242
+
243
+ tool_disable = ToolDisableTool()
244
+ tool_disable.register(mcp_server)
245
+ all_tools[tool_disable.name] = tool_disable
246
+
247
+ tool_list = ToolListTool()
248
+ tool_list.register(mcp_server)
249
+ all_tools[tool_list.name] = tool_list
250
+
251
+ # Stats tool
252
+ stats_tool = StatsTool(db_manager=db_manager)
253
+ stats_tool.register(mcp_server)
254
+ all_tools[stats_tool.name] = stats_tool
255
+
256
+ # Register editor tools if enabled
257
+ editor_enabled = {
258
+ "neovim_edit": is_tool_enabled("neovim_edit", True),
259
+ "neovim_command": is_tool_enabled("neovim_command", True),
260
+ "neovim_session": is_tool_enabled("neovim_session", True),
261
+ }
262
+
263
+ if editor_enabled.get("neovim_edit", True):
264
+ tool = NeovimEditTool(permission_manager)
265
+ tool.register(mcp_server)
266
+ all_tools[tool.name] = tool
267
+
268
+ if editor_enabled.get("neovim_command", True):
269
+ tool = NeovimCommandTool(permission_manager)
270
+ tool.register(mcp_server)
271
+ all_tools[tool.name] = tool
272
+
273
+ if editor_enabled.get("neovim_session", True):
274
+ tool = NeovimSessionTool()
275
+ tool.register(mcp_server)
276
+ all_tools[tool.name] = tool
277
+
278
+ # Register LLM tools if enabled
279
+ llm_enabled = {
280
+ "llm": is_tool_enabled("llm", True),
281
+ "consensus": is_tool_enabled("consensus", True),
282
+ "llm_manage": is_tool_enabled("llm_manage", True),
283
+ }
284
+
285
+ if llm_enabled.get("llm", True):
286
+ tool = LLMTool()
287
+ if tool.available_providers: # Only register if API keys found
288
+ tool.register(mcp_server)
289
+ all_tools[tool.name] = tool
290
+
291
+ if llm_enabled.get("consensus", True):
292
+ tool = ConsensusTool()
293
+ if tool.llm_tool.available_providers: # Only register if API keys found
294
+ tool.register(mcp_server)
295
+ all_tools[tool.name] = tool
296
+
297
+ if llm_enabled.get("llm_manage", True):
298
+ tool = LLMManageTool()
299
+ tool.register(mcp_server)
300
+ all_tools[tool.name] = tool
301
+
302
+ # Register provider-specific LLM tools
303
+ # These are only registered if their API keys are available
304
+ provider_tools = create_provider_tools()
305
+ for tool in provider_tools:
306
+ if is_tool_enabled(tool.name, True):
307
+ tool.register(mcp_server)
308
+ all_tools[tool.name] = tool
@@ -170,8 +170,13 @@ class ToolRegistry:
170
170
  mcp_server: The FastMCP server instance
171
171
  tool: The tool to register
172
172
  """
173
- # Use the tool's register method which handles all the details
174
- tool.register(mcp_server)
173
+ # Check if tool is enabled before registering
174
+ # Import here to avoid circular imports
175
+ from hanzo_mcp.tools.common.tool_enable import ToolEnableTool
176
+
177
+ if ToolEnableTool.is_tool_enabled(tool.name):
178
+ # Use the tool's register method which handles all the details
179
+ tool.register(mcp_server)
175
180
 
176
181
  @staticmethod
177
182
  def register_tools(mcp_server: FastMCP, tools: list[BaseTool]) -> None:
@@ -0,0 +1,261 @@
1
+ """Comprehensive system and MCP statistics."""
2
+
3
+ import os
4
+ import psutil
5
+ import shutil
6
+ from typing import TypedDict, Unpack, final, override
7
+ from datetime import datetime
8
+ from pathlib import Path
9
+
10
+ from fastmcp import Context as MCPContext
11
+
12
+ from hanzo_mcp.tools.common.base import BaseTool
13
+ from hanzo_mcp.tools.common.context import create_tool_context
14
+ from hanzo_mcp.tools.shell.run_background import RunBackgroundTool
15
+ from hanzo_mcp.tools.mcp.mcp_add import McpAddTool
16
+ from hanzo_mcp.tools.database.database_manager import DatabaseManager
17
+
18
+
19
+ class StatsParams(TypedDict, total=False):
20
+ """Parameters for stats tool."""
21
+ pass
22
+
23
+
24
+ @final
25
+ class StatsTool(BaseTool):
26
+ """Tool for showing comprehensive system and MCP statistics."""
27
+
28
+ def __init__(self, db_manager: DatabaseManager = None):
29
+ """Initialize the stats tool.
30
+
31
+ Args:
32
+ db_manager: Optional database manager for DB stats
33
+ """
34
+ self.db_manager = db_manager
35
+
36
+ @property
37
+ @override
38
+ def name(self) -> str:
39
+ """Get the tool name."""
40
+ return "stats"
41
+
42
+ @property
43
+ @override
44
+ def description(self) -> str:
45
+ """Get the tool description."""
46
+ return """Show comprehensive system and Hanzo MCP statistics.
47
+
48
+ Displays:
49
+ - System resources (CPU, memory, disk)
50
+ - Running processes
51
+ - Database usage
52
+ - MCP server status
53
+ - Tool usage statistics
54
+ - Warnings for high resource usage
55
+
56
+ Example:
57
+ - stats
58
+ """
59
+
60
+ @override
61
+ async def call(
62
+ self,
63
+ ctx: MCPContext,
64
+ **params: Unpack[StatsParams],
65
+ ) -> str:
66
+ """Get comprehensive statistics.
67
+
68
+ Args:
69
+ ctx: MCP context
70
+ **params: Tool parameters
71
+
72
+ Returns:
73
+ Comprehensive statistics
74
+ """
75
+ tool_ctx = create_tool_context(ctx)
76
+ await tool_ctx.set_tool_info(self.name)
77
+
78
+ output = []
79
+ warnings = []
80
+
81
+ # Header
82
+ output.append("=== Hanzo MCP System Statistics ===")
83
+ output.append(f"Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
84
+ output.append("")
85
+
86
+ # System Resources
87
+ output.append("=== System Resources ===")
88
+
89
+ # CPU
90
+ cpu_percent = psutil.cpu_percent(interval=1)
91
+ cpu_count = psutil.cpu_count()
92
+ output.append(f"CPU Usage: {cpu_percent}% ({cpu_count} cores)")
93
+ if cpu_percent > 90:
94
+ warnings.append(f"⚠️ HIGH CPU USAGE: {cpu_percent}%")
95
+
96
+ # Memory
97
+ memory = psutil.virtual_memory()
98
+ memory_used_gb = memory.used / (1024**3)
99
+ memory_total_gb = memory.total / (1024**3)
100
+ memory_percent = memory.percent
101
+ output.append(f"Memory: {memory_used_gb:.1f}/{memory_total_gb:.1f} GB ({memory_percent}%)")
102
+ if memory_percent > 90:
103
+ warnings.append(f"⚠️ HIGH MEMORY USAGE: {memory_percent}%")
104
+
105
+ # Disk
106
+ disk = psutil.disk_usage('/')
107
+ disk_used_gb = disk.used / (1024**3)
108
+ disk_total_gb = disk.total / (1024**3)
109
+ disk_percent = disk.percent
110
+ disk_free_gb = disk.free / (1024**3)
111
+ output.append(f"Disk: {disk_used_gb:.1f}/{disk_total_gb:.1f} GB ({disk_percent}%)")
112
+ output.append(f"Free Space: {disk_free_gb:.1f} GB")
113
+ if disk_percent > 90:
114
+ warnings.append(f"⚠️ LOW DISK SPACE: Only {disk_free_gb:.1f} GB free ({100-disk_percent:.1f}% remaining)")
115
+
116
+ output.append("")
117
+
118
+ # Background Processes
119
+ output.append("=== Background Processes ===")
120
+ processes = RunBackgroundTool.get_processes()
121
+ running_count = 0
122
+ total_memory_mb = 0
123
+
124
+ if processes:
125
+ for proc in processes.values():
126
+ if proc.is_running():
127
+ running_count += 1
128
+ try:
129
+ ps_proc = psutil.Process(proc.process.pid)
130
+ memory_mb = ps_proc.memory_info().rss / (1024**2)
131
+ total_memory_mb += memory_mb
132
+ except:
133
+ pass
134
+
135
+ output.append(f"Running Processes: {running_count}")
136
+ output.append(f"Total Memory Usage: {total_memory_mb:.1f} MB")
137
+
138
+ # List top processes by memory
139
+ if running_count > 0:
140
+ output.append("\nTop Processes:")
141
+ proc_list = []
142
+ for proc_id, proc in processes.items():
143
+ if proc.is_running():
144
+ try:
145
+ ps_proc = psutil.Process(proc.process.pid)
146
+ memory_mb = ps_proc.memory_info().rss / (1024**2)
147
+ cpu = ps_proc.cpu_percent(interval=0.1)
148
+ proc_list.append((proc.name, memory_mb, cpu, proc_id))
149
+ except:
150
+ proc_list.append((proc.name, 0, 0, proc_id))
151
+
152
+ proc_list.sort(key=lambda x: x[1], reverse=True)
153
+ for name, mem, cpu, pid in proc_list[:5]:
154
+ output.append(f" - {name} ({pid}): {mem:.1f} MB, {cpu:.1f}% CPU")
155
+ else:
156
+ output.append("No background processes running")
157
+
158
+ output.append("")
159
+
160
+ # Database Usage
161
+ if self.db_manager:
162
+ output.append("=== Database Usage ===")
163
+ db_dir = Path.home() / ".hanzo" / "db"
164
+ total_db_size = 0
165
+
166
+ if db_dir.exists():
167
+ for db_file in db_dir.rglob("*.db"):
168
+ size = db_file.stat().st_size
169
+ total_db_size += size
170
+
171
+ output.append(f"Total Database Size: {total_db_size / (1024**2):.1f} MB")
172
+ output.append(f"Active Projects: {len(self.db_manager.projects)}")
173
+
174
+ # List largest databases
175
+ db_sizes = []
176
+ for db_file in db_dir.rglob("*.db"):
177
+ size = db_file.stat().st_size / (1024**2)
178
+ if size > 0.1: # Only show DBs > 100KB
179
+ project = db_file.parent.parent.name
180
+ db_type = db_file.stem
181
+ db_sizes.append((project, db_type, size))
182
+
183
+ if db_sizes:
184
+ db_sizes.sort(key=lambda x: x[2], reverse=True)
185
+ output.append("\nLargest Databases:")
186
+ for project, db_type, size in db_sizes[:5]:
187
+ output.append(f" - {project}/{db_type}: {size:.1f} MB")
188
+ else:
189
+ output.append("No databases found")
190
+
191
+ output.append("")
192
+
193
+ # MCP Servers
194
+ output.append("=== MCP Servers ===")
195
+ mcp_servers = McpAddTool.get_servers()
196
+ if mcp_servers:
197
+ running_mcp = sum(1 for s in mcp_servers.values() if s.get("status") == "running")
198
+ total_mcp_tools = sum(len(s.get("tools", [])) for s in mcp_servers.values())
199
+
200
+ output.append(f"Total Servers: {len(mcp_servers)}")
201
+ output.append(f"Running: {running_mcp}")
202
+ output.append(f"Total Tools Available: {total_mcp_tools}")
203
+ else:
204
+ output.append("No MCP servers configured")
205
+
206
+ output.append("")
207
+
208
+ # Hanzo MCP Specifics
209
+ output.append("=== Hanzo MCP ===")
210
+
211
+ # Log directory size
212
+ log_dir = Path.home() / ".hanzo" / "logs"
213
+ if log_dir.exists():
214
+ log_size = sum(f.stat().st_size for f in log_dir.rglob("*") if f.is_file())
215
+ log_count = len(list(log_dir.rglob("*.log")))
216
+ output.append(f"Log Files: {log_count} ({log_size / (1024**2):.1f} MB)")
217
+
218
+ if log_size > 100 * 1024**2: # > 100MB
219
+ warnings.append(f"⚠️ Large log directory: {log_size / (1024**2):.1f} MB")
220
+
221
+ # Config directory
222
+ config_dir = Path.home() / ".hanzo" / "mcp"
223
+ if config_dir.exists():
224
+ config_count = len(list(config_dir.rglob("*.json")))
225
+ output.append(f"Config Files: {config_count}")
226
+
227
+ # Tool status (if available)
228
+ # TODO: Track tool usage statistics
229
+ output.append("\nTool Categories:")
230
+ output.append(" - File Operations: grep, find_files, read, write, edit")
231
+ output.append(" - Shell: bash, run_background, processes, pkill")
232
+ output.append(" - Database: sql_query, graph_query, vector_search")
233
+ output.append(" - Package Runners: uvx, npx, uvx_background, npx_background")
234
+ output.append(" - MCP Management: mcp_add, mcp_remove, mcp_stats")
235
+
236
+ # Warnings Section
237
+ if warnings:
238
+ output.append("\n=== ⚠️ WARNINGS ===")
239
+ for warning in warnings:
240
+ output.append(warning)
241
+ output.append("")
242
+
243
+ # Recommendations
244
+ output.append("=== Recommendations ===")
245
+ if disk_free_gb < 5:
246
+ output.append("- Free up disk space (< 5GB remaining)")
247
+ if memory_percent > 80:
248
+ output.append("- Close unused applications to free memory")
249
+ if running_count > 10:
250
+ output.append("- Consider stopping unused background processes")
251
+ if log_size > 50 * 1024**2:
252
+ output.append("- Clean up old log files in ~/.hanzo/logs")
253
+
254
+ if not any([disk_free_gb < 5, memory_percent > 80, running_count > 10, log_size > 50 * 1024**2]):
255
+ output.append("✅ System resources are healthy")
256
+
257
+ return "\n".join(output)
258
+
259
+ def register(self, mcp_server) -> None:
260
+ """Register this tool with the MCP server."""
261
+ pass
@@ -0,0 +1,144 @@
1
+ """Disable tools dynamically."""
2
+
3
+ from typing import Annotated, TypedDict, Unpack, final, override
4
+
5
+ from fastmcp import Context as MCPContext
6
+ from pydantic import Field
7
+
8
+ from hanzo_mcp.tools.common.base import BaseTool
9
+ from hanzo_mcp.tools.common.context import create_tool_context
10
+ from hanzo_mcp.tools.common.tool_enable import ToolEnableTool
11
+
12
+
13
+ ToolName = Annotated[
14
+ str,
15
+ Field(
16
+ description="Name of the tool to disable (e.g., 'grep', 'vector_search')",
17
+ min_length=1,
18
+ ),
19
+ ]
20
+
21
+ Persist = Annotated[
22
+ bool,
23
+ Field(
24
+ description="Persist the change to config file",
25
+ default=True,
26
+ ),
27
+ ]
28
+
29
+
30
+ class ToolDisableParams(TypedDict, total=False):
31
+ """Parameters for tool disable."""
32
+
33
+ tool: str
34
+ persist: bool
35
+
36
+
37
+ @final
38
+ class ToolDisableTool(BaseTool):
39
+ """Tool for disabling other tools dynamically."""
40
+
41
+ def __init__(self):
42
+ """Initialize the tool disable tool."""
43
+ # Ensure states are loaded
44
+ if not ToolEnableTool._initialized:
45
+ ToolEnableTool._load_states()
46
+ ToolEnableTool._initialized = True
47
+
48
+ @property
49
+ @override
50
+ def name(self) -> str:
51
+ """Get the tool name."""
52
+ return "tool_disable"
53
+
54
+ @property
55
+ @override
56
+ def description(self) -> str:
57
+ """Get the tool description."""
58
+ return """Disable tools to prevent their use.
59
+
60
+ This allows you to temporarily or permanently disable tools.
61
+ Useful for testing or when a tool is misbehaving.
62
+ Changes are persisted by default.
63
+
64
+ Critical tools (tool_enable, tool_disable, tool_list) cannot be disabled.
65
+
66
+ Examples:
67
+ - tool_disable --tool vector_search
68
+ - tool_disable --tool uvx_background
69
+ - tool_disable --tool grep --no-persist
70
+
71
+ Use 'tool_list' to see all available tools and their status.
72
+ Use 'tool_enable' to re-enable disabled tools.
73
+ """
74
+
75
+ @override
76
+ async def call(
77
+ self,
78
+ ctx: MCPContext,
79
+ **params: Unpack[ToolDisableParams],
80
+ ) -> str:
81
+ """Disable a tool.
82
+
83
+ Args:
84
+ ctx: MCP context
85
+ **params: Tool parameters
86
+
87
+ Returns:
88
+ Result of disabling the tool
89
+ """
90
+ tool_ctx = create_tool_context(ctx)
91
+ await tool_ctx.set_tool_info(self.name)
92
+
93
+ # Extract parameters
94
+ tool_name = params.get("tool")
95
+ if not tool_name:
96
+ return "Error: tool name is required"
97
+
98
+ persist = params.get("persist", True)
99
+
100
+ # Prevent disabling critical tools
101
+ critical_tools = {"tool_enable", "tool_disable", "tool_list", "stats"}
102
+ if tool_name in critical_tools:
103
+ return f"Error: Cannot disable critical tool '{tool_name}'. These tools are required for system management."
104
+
105
+ # Check current state
106
+ was_enabled = ToolEnableTool.is_tool_enabled(tool_name)
107
+
108
+ if not was_enabled:
109
+ return f"Tool '{tool_name}' is already disabled."
110
+
111
+ # Disable the tool
112
+ ToolEnableTool._tool_states[tool_name] = False
113
+
114
+ # Persist if requested
115
+ if persist:
116
+ ToolEnableTool._save_states()
117
+ await tool_ctx.info(f"Disabled tool '{tool_name}' (persisted)")
118
+ else:
119
+ await tool_ctx.info(f"Disabled tool '{tool_name}' (temporary)")
120
+
121
+ output = [
122
+ f"Successfully disabled tool '{tool_name}'",
123
+ "",
124
+ "The tool is now unavailable for use.",
125
+ f"Use 'tool_enable --tool {tool_name}' to re-enable it.",
126
+ ]
127
+
128
+ if not persist:
129
+ output.append("\nNote: This change is temporary and will be lost on restart.")
130
+
131
+ # Warn about commonly used tools
132
+ common_tools = {"grep", "read", "write", "bash", "edit"}
133
+ if tool_name in common_tools:
134
+ output.append(f"\n⚠️ Warning: '{tool_name}' is a commonly used tool. Disabling it may affect normal operations.")
135
+
136
+ # Count disabled tools
137
+ disabled_count = sum(1 for enabled in ToolEnableTool._tool_states.values() if not enabled)
138
+ output.append(f"\nTotal disabled tools: {disabled_count}")
139
+
140
+ return "\n".join(output)
141
+
142
+ def register(self, mcp_server) -> None:
143
+ """Register this tool with the MCP server."""
144
+ pass