hanzo-mcp 0.7.7__py3-none-any.whl → 0.8.0__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 (178) hide show
  1. hanzo_mcp/__init__.py +6 -0
  2. hanzo_mcp/__main__.py +1 -1
  3. hanzo_mcp/analytics/__init__.py +2 -2
  4. hanzo_mcp/analytics/posthog_analytics.py +76 -82
  5. hanzo_mcp/cli.py +31 -36
  6. hanzo_mcp/cli_enhanced.py +94 -72
  7. hanzo_mcp/cli_plugin.py +27 -17
  8. hanzo_mcp/config/__init__.py +2 -2
  9. hanzo_mcp/config/settings.py +112 -88
  10. hanzo_mcp/config/tool_config.py +32 -34
  11. hanzo_mcp/dev_server.py +66 -67
  12. hanzo_mcp/prompts/__init__.py +94 -12
  13. hanzo_mcp/prompts/enhanced_prompts.py +809 -0
  14. hanzo_mcp/prompts/example_custom_prompt.py +6 -5
  15. hanzo_mcp/prompts/project_todo_reminder.py +0 -1
  16. hanzo_mcp/prompts/tool_explorer.py +10 -7
  17. hanzo_mcp/server.py +17 -21
  18. hanzo_mcp/server_enhanced.py +15 -22
  19. hanzo_mcp/tools/__init__.py +56 -28
  20. hanzo_mcp/tools/agent/__init__.py +16 -19
  21. hanzo_mcp/tools/agent/agent.py +82 -65
  22. hanzo_mcp/tools/agent/agent_tool.py +152 -122
  23. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
  24. hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
  25. hanzo_mcp/tools/agent/clarification_tool.py +11 -10
  26. hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
  27. hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
  28. hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
  29. hanzo_mcp/tools/agent/code_auth.py +102 -107
  30. hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
  31. hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
  32. hanzo_mcp/tools/agent/critic_tool.py +86 -73
  33. hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
  34. hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
  35. hanzo_mcp/tools/agent/iching_tool.py +404 -139
  36. hanzo_mcp/tools/agent/network_tool.py +89 -73
  37. hanzo_mcp/tools/agent/prompt.py +2 -1
  38. hanzo_mcp/tools/agent/review_tool.py +101 -98
  39. hanzo_mcp/tools/agent/swarm_alias.py +87 -0
  40. hanzo_mcp/tools/agent/swarm_tool.py +246 -161
  41. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
  42. hanzo_mcp/tools/agent/tool_adapter.py +21 -11
  43. hanzo_mcp/tools/common/__init__.py +1 -1
  44. hanzo_mcp/tools/common/base.py +3 -5
  45. hanzo_mcp/tools/common/batch_tool.py +46 -39
  46. hanzo_mcp/tools/common/config_tool.py +120 -84
  47. hanzo_mcp/tools/common/context.py +1 -5
  48. hanzo_mcp/tools/common/context_fix.py +5 -3
  49. hanzo_mcp/tools/common/critic_tool.py +4 -8
  50. hanzo_mcp/tools/common/decorators.py +58 -56
  51. hanzo_mcp/tools/common/enhanced_base.py +29 -32
  52. hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
  53. hanzo_mcp/tools/common/forgiving_edit.py +91 -87
  54. hanzo_mcp/tools/common/mode.py +15 -17
  55. hanzo_mcp/tools/common/mode_loader.py +27 -24
  56. hanzo_mcp/tools/common/paginated_base.py +61 -53
  57. hanzo_mcp/tools/common/paginated_response.py +72 -79
  58. hanzo_mcp/tools/common/pagination.py +50 -53
  59. hanzo_mcp/tools/common/permissions.py +4 -4
  60. hanzo_mcp/tools/common/personality.py +186 -138
  61. hanzo_mcp/tools/common/plugin_loader.py +54 -54
  62. hanzo_mcp/tools/common/stats.py +65 -47
  63. hanzo_mcp/tools/common/test_helpers.py +31 -0
  64. hanzo_mcp/tools/common/thinking_tool.py +4 -8
  65. hanzo_mcp/tools/common/tool_disable.py +17 -12
  66. hanzo_mcp/tools/common/tool_enable.py +13 -14
  67. hanzo_mcp/tools/common/tool_list.py +36 -28
  68. hanzo_mcp/tools/common/truncate.py +23 -23
  69. hanzo_mcp/tools/config/__init__.py +4 -4
  70. hanzo_mcp/tools/config/config_tool.py +42 -29
  71. hanzo_mcp/tools/config/index_config.py +37 -34
  72. hanzo_mcp/tools/config/mode_tool.py +175 -55
  73. hanzo_mcp/tools/database/__init__.py +15 -12
  74. hanzo_mcp/tools/database/database_manager.py +77 -75
  75. hanzo_mcp/tools/database/graph.py +137 -91
  76. hanzo_mcp/tools/database/graph_add.py +30 -18
  77. hanzo_mcp/tools/database/graph_query.py +178 -102
  78. hanzo_mcp/tools/database/graph_remove.py +33 -28
  79. hanzo_mcp/tools/database/graph_search.py +97 -75
  80. hanzo_mcp/tools/database/graph_stats.py +91 -59
  81. hanzo_mcp/tools/database/sql.py +107 -79
  82. hanzo_mcp/tools/database/sql_query.py +30 -24
  83. hanzo_mcp/tools/database/sql_search.py +29 -25
  84. hanzo_mcp/tools/database/sql_stats.py +47 -35
  85. hanzo_mcp/tools/editor/neovim_command.py +25 -28
  86. hanzo_mcp/tools/editor/neovim_edit.py +21 -23
  87. hanzo_mcp/tools/editor/neovim_session.py +60 -54
  88. hanzo_mcp/tools/filesystem/__init__.py +31 -30
  89. hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
  90. hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
  91. hanzo_mcp/tools/filesystem/base.py +1 -1
  92. hanzo_mcp/tools/filesystem/batch_search.py +316 -224
  93. hanzo_mcp/tools/filesystem/content_replace.py +4 -4
  94. hanzo_mcp/tools/filesystem/diff.py +71 -59
  95. hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
  96. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
  97. hanzo_mcp/tools/filesystem/edit.py +4 -4
  98. hanzo_mcp/tools/filesystem/find.py +173 -80
  99. hanzo_mcp/tools/filesystem/find_files.py +73 -52
  100. hanzo_mcp/tools/filesystem/git_search.py +157 -104
  101. hanzo_mcp/tools/filesystem/grep.py +8 -8
  102. hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
  103. hanzo_mcp/tools/filesystem/read.py +12 -10
  104. hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
  105. hanzo_mcp/tools/filesystem/search_tool.py +263 -207
  106. hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
  107. hanzo_mcp/tools/filesystem/tree.py +35 -33
  108. hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
  109. hanzo_mcp/tools/filesystem/watch.py +37 -36
  110. hanzo_mcp/tools/filesystem/write.py +4 -8
  111. hanzo_mcp/tools/jupyter/__init__.py +4 -4
  112. hanzo_mcp/tools/jupyter/base.py +4 -5
  113. hanzo_mcp/tools/jupyter/jupyter.py +67 -47
  114. hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
  115. hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
  116. hanzo_mcp/tools/llm/__init__.py +5 -7
  117. hanzo_mcp/tools/llm/consensus_tool.py +72 -52
  118. hanzo_mcp/tools/llm/llm_manage.py +101 -60
  119. hanzo_mcp/tools/llm/llm_tool.py +226 -166
  120. hanzo_mcp/tools/llm/provider_tools.py +25 -26
  121. hanzo_mcp/tools/lsp/__init__.py +1 -1
  122. hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
  123. hanzo_mcp/tools/mcp/__init__.py +2 -3
  124. hanzo_mcp/tools/mcp/mcp_add.py +27 -25
  125. hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
  126. hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
  127. hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
  128. hanzo_mcp/tools/memory/__init__.py +39 -21
  129. hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
  130. hanzo_mcp/tools/memory/memory_tools.py +90 -108
  131. hanzo_mcp/tools/search/__init__.py +7 -2
  132. hanzo_mcp/tools/search/find_tool.py +297 -212
  133. hanzo_mcp/tools/search/unified_search.py +366 -314
  134. hanzo_mcp/tools/shell/__init__.py +8 -7
  135. hanzo_mcp/tools/shell/auto_background.py +56 -49
  136. hanzo_mcp/tools/shell/base.py +1 -1
  137. hanzo_mcp/tools/shell/base_process.py +75 -75
  138. hanzo_mcp/tools/shell/bash_session.py +2 -2
  139. hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
  140. hanzo_mcp/tools/shell/bash_tool.py +24 -31
  141. hanzo_mcp/tools/shell/command_executor.py +12 -12
  142. hanzo_mcp/tools/shell/logs.py +43 -33
  143. hanzo_mcp/tools/shell/npx.py +13 -13
  144. hanzo_mcp/tools/shell/npx_background.py +24 -21
  145. hanzo_mcp/tools/shell/npx_tool.py +18 -22
  146. hanzo_mcp/tools/shell/open.py +19 -21
  147. hanzo_mcp/tools/shell/pkill.py +31 -26
  148. hanzo_mcp/tools/shell/process_tool.py +32 -32
  149. hanzo_mcp/tools/shell/processes.py +57 -58
  150. hanzo_mcp/tools/shell/run_background.py +24 -25
  151. hanzo_mcp/tools/shell/run_command.py +5 -5
  152. hanzo_mcp/tools/shell/run_command_windows.py +5 -5
  153. hanzo_mcp/tools/shell/session_storage.py +3 -3
  154. hanzo_mcp/tools/shell/streaming_command.py +141 -126
  155. hanzo_mcp/tools/shell/uvx.py +24 -25
  156. hanzo_mcp/tools/shell/uvx_background.py +35 -33
  157. hanzo_mcp/tools/shell/uvx_tool.py +18 -22
  158. hanzo_mcp/tools/todo/__init__.py +6 -2
  159. hanzo_mcp/tools/todo/todo.py +50 -37
  160. hanzo_mcp/tools/todo/todo_read.py +5 -8
  161. hanzo_mcp/tools/todo/todo_write.py +5 -7
  162. hanzo_mcp/tools/vector/__init__.py +40 -28
  163. hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
  164. hanzo_mcp/tools/vector/git_ingester.py +170 -179
  165. hanzo_mcp/tools/vector/index_tool.py +96 -44
  166. hanzo_mcp/tools/vector/infinity_store.py +283 -228
  167. hanzo_mcp/tools/vector/mock_infinity.py +39 -40
  168. hanzo_mcp/tools/vector/project_manager.py +88 -78
  169. hanzo_mcp/tools/vector/vector.py +59 -42
  170. hanzo_mcp/tools/vector/vector_index.py +30 -27
  171. hanzo_mcp/tools/vector/vector_search.py +64 -45
  172. hanzo_mcp/types.py +6 -4
  173. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/METADATA +1 -1
  174. hanzo_mcp-0.8.0.dist-info/RECORD +185 -0
  175. hanzo_mcp-0.7.7.dist-info/RECORD +0 -182
  176. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/WHEEL +0 -0
  177. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
  178. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/top_level.txt +0 -0
@@ -1,21 +1,21 @@
1
1
  """Plugin loader for custom user tools."""
2
2
 
3
- import importlib.util
4
- import inspect
5
- import json
6
3
  import os
7
4
  import sys
5
+ import json
6
+ import inspect
7
+ import importlib.util
8
+ from typing import Any, Dict, List, Type, Optional
8
9
  from pathlib import Path
9
- from typing import Dict, List, Optional, Type, Any
10
10
  from dataclasses import dataclass
11
11
 
12
12
  from .base import BaseTool
13
- from .context import ToolContext
14
13
 
15
14
 
16
15
  @dataclass
17
16
  class ToolPlugin:
18
17
  """Represents a loaded tool plugin."""
18
+
19
19
  name: str
20
20
  tool_class: Type[BaseTool]
21
21
  source_path: Path
@@ -24,47 +24,47 @@ class ToolPlugin:
24
24
 
25
25
  class PluginLoader:
26
26
  """Loads custom tool plugins from user directories."""
27
-
27
+
28
28
  def __init__(self):
29
29
  self.plugins: Dict[str, ToolPlugin] = {}
30
30
  self.plugin_dirs: List[Path] = []
31
31
  self._setup_plugin_directories()
32
-
32
+
33
33
  def _setup_plugin_directories(self):
34
34
  """Set up standard plugin directories."""
35
35
  # User's home directory plugins
36
36
  home_plugins = Path.home() / ".hanzo" / "plugins"
37
37
  home_plugins.mkdir(parents=True, exist_ok=True)
38
38
  self.plugin_dirs.append(home_plugins)
39
-
39
+
40
40
  # Project-local plugins
41
41
  project_plugins = Path.cwd() / ".hanzo" / "plugins"
42
42
  if project_plugins.exists():
43
43
  self.plugin_dirs.append(project_plugins)
44
-
44
+
45
45
  # Environment variable for additional paths
46
46
  if custom_paths := os.environ.get("HANZO_PLUGIN_PATH"):
47
47
  for path in custom_paths.split(":"):
48
48
  plugin_dir = Path(path)
49
49
  if plugin_dir.exists():
50
50
  self.plugin_dirs.append(plugin_dir)
51
-
51
+
52
52
  def load_plugins(self) -> Dict[str, ToolPlugin]:
53
53
  """Load all plugins from configured directories."""
54
54
  for plugin_dir in self.plugin_dirs:
55
55
  if not plugin_dir.exists():
56
56
  continue
57
-
57
+
58
58
  # Look for Python files
59
59
  for py_file in plugin_dir.glob("*.py"):
60
60
  if py_file.name.startswith("_"):
61
61
  continue
62
-
62
+
63
63
  try:
64
64
  self._load_plugin_file(py_file)
65
65
  except Exception as e:
66
66
  print(f"Failed to load plugin {py_file}: {e}")
67
-
67
+
68
68
  # Look for plugin packages
69
69
  for package_dir in plugin_dir.iterdir():
70
70
  if package_dir.is_dir() and (package_dir / "__init__.py").exists():
@@ -72,88 +72,88 @@ class PluginLoader:
72
72
  self._load_plugin_package(package_dir)
73
73
  except Exception as e:
74
74
  print(f"Failed to load plugin package {package_dir}: {e}")
75
-
75
+
76
76
  return self.plugins
77
-
77
+
78
78
  def _load_plugin_file(self, file_path: Path):
79
79
  """Load a single plugin file."""
80
80
  # Load the module
81
81
  spec = importlib.util.spec_from_file_location(file_path.stem, file_path)
82
82
  if not spec or not spec.loader:
83
83
  return
84
-
84
+
85
85
  module = importlib.util.module_from_spec(spec)
86
86
  sys.modules[file_path.stem] = module
87
87
  spec.loader.exec_module(module)
88
-
88
+
89
89
  # Find tool classes
90
- for name, obj in inspect.getmembers(module):
91
- if (inspect.isclass(obj) and
92
- issubclass(obj, BaseTool) and
93
- obj != BaseTool and
94
- hasattr(obj, 'name')):
95
-
90
+ for _name, obj in inspect.getmembers(module):
91
+ if (
92
+ inspect.isclass(obj)
93
+ and issubclass(obj, BaseTool)
94
+ and obj != BaseTool
95
+ and hasattr(obj, "name")
96
+ ):
96
97
  # Load metadata if available
97
98
  metadata = None
98
- metadata_file = file_path.with_suffix('.json')
99
+ metadata_file = file_path.with_suffix(".json")
99
100
  if metadata_file.exists():
100
101
  with open(metadata_file) as f:
101
102
  metadata = json.load(f)
102
-
103
+
103
104
  plugin = ToolPlugin(
104
105
  name=obj.name,
105
106
  tool_class=obj,
106
107
  source_path=file_path,
107
- metadata=metadata
108
+ metadata=metadata,
108
109
  )
109
110
  self.plugins[obj.name] = plugin
110
-
111
+
111
112
  def _load_plugin_package(self, package_dir: Path):
112
113
  """Load a plugin package."""
113
114
  # Add parent to path temporarily
114
115
  parent = str(package_dir.parent)
115
116
  if parent not in sys.path:
116
117
  sys.path.insert(0, parent)
117
-
118
+
118
119
  try:
119
120
  # Import the package
120
121
  module = importlib.import_module(package_dir.name)
121
-
122
+
122
123
  # Look for tools
123
- if hasattr(module, 'TOOLS'):
124
+ if hasattr(module, "TOOLS"):
124
125
  # Package exports TOOLS list
125
126
  for tool_class in module.TOOLS:
126
127
  if issubclass(tool_class, BaseTool):
127
128
  plugin = ToolPlugin(
128
129
  name=tool_class.name,
129
130
  tool_class=tool_class,
130
- source_path=package_dir
131
+ source_path=package_dir,
131
132
  )
132
133
  self.plugins[tool_class.name] = plugin
133
134
  else:
134
135
  # Search for tool classes
135
- for name, obj in inspect.getmembers(module):
136
- if (inspect.isclass(obj) and
137
- issubclass(obj, BaseTool) and
138
- obj != BaseTool and
139
- hasattr(obj, 'name')):
140
-
136
+ for _name, obj in inspect.getmembers(module):
137
+ if (
138
+ inspect.isclass(obj)
139
+ and issubclass(obj, BaseTool)
140
+ and obj != BaseTool
141
+ and hasattr(obj, "name")
142
+ ):
141
143
  plugin = ToolPlugin(
142
- name=obj.name,
143
- tool_class=obj,
144
- source_path=package_dir
144
+ name=obj.name, tool_class=obj, source_path=package_dir
145
145
  )
146
146
  self.plugins[obj.name] = plugin
147
147
  finally:
148
148
  # Remove from path
149
149
  if parent in sys.path:
150
150
  sys.path.remove(parent)
151
-
151
+
152
152
  def get_tool_class(self, name: str) -> Optional[Type[BaseTool]]:
153
153
  """Get a tool class by name."""
154
154
  plugin = self.plugins.get(name)
155
155
  return plugin.tool_class if plugin else None
156
-
156
+
157
157
  def list_plugins(self) -> List[str]:
158
158
  """List all loaded plugin names."""
159
159
  return list(self.plugins.keys())
@@ -181,7 +181,7 @@ def list_plugin_tools() -> List[str]:
181
181
  def create_plugin_template(output_dir: Path, tool_name: str):
182
182
  """Create a template for a new plugin tool."""
183
183
  output_dir.mkdir(parents=True, exist_ok=True)
184
-
184
+
185
185
  # Create tool file
186
186
  tool_file = output_dir / f"{tool_name}_tool.py"
187
187
  tool_content = f'''"""Custom {tool_name} tool plugin."""
@@ -225,10 +225,10 @@ class {tool_name.title()}Tool(BaseTool):
225
225
  # Optional: Export tools explicitly
226
226
  TOOLS = [{tool_name.title()}Tool]
227
227
  '''
228
-
229
- with open(tool_file, 'w') as f:
228
+
229
+ with open(tool_file, "w") as f:
230
230
  f.write(tool_content)
231
-
231
+
232
232
  # Create metadata file
233
233
  metadata_file = output_dir / f"{tool_name}_tool.json"
234
234
  metadata_content = {
@@ -240,12 +240,12 @@ TOOLS = [{tool_name.title()}Tool]
240
240
  "dependencies": [],
241
241
  "config": {
242
242
  # Tool-specific configuration
243
- }
243
+ },
244
244
  }
245
-
246
- with open(metadata_file, 'w') as f:
245
+
246
+ with open(metadata_file, "w") as f:
247
247
  json.dump(metadata_content, f, indent=2)
248
-
248
+
249
249
  # Create README
250
250
  readme_file = output_dir / "README.md"
251
251
  readme_content = f"""# {tool_name.title()} Tool Plugin
@@ -276,12 +276,12 @@ Edit the `{tool_name}_tool.json` file to:
276
276
 
277
277
  Modify `{tool_name}_tool.py` to implement your custom functionality.
278
278
  """
279
-
280
- with open(readme_file, 'w') as f:
279
+
280
+ with open(readme_file, "w") as f:
281
281
  f.write(readme_content)
282
-
282
+
283
283
  print(f"Created plugin template in {output_dir}")
284
284
  print(f"Files created:")
285
285
  print(f" - {tool_file}")
286
286
  print(f" - {metadata_file}")
287
- print(f" - {readme_file}")
287
+ print(f" - {readme_file}")
@@ -1,23 +1,22 @@
1
1
  """Comprehensive system and MCP statistics."""
2
2
 
3
- import os
4
- import psutil
5
- import shutil
6
- from typing import TypedDict, Unpack, final, override
7
- from datetime import datetime
3
+ from typing import Unpack, TypedDict, final, override
8
4
  from pathlib import Path
5
+ from datetime import datetime
9
6
 
7
+ import psutil
10
8
  from mcp.server.fastmcp import Context as MCPContext
11
9
 
12
10
  from hanzo_mcp.tools.common.base import BaseTool
11
+ from hanzo_mcp.tools.mcp.mcp_add import McpAddTool
13
12
  from hanzo_mcp.tools.common.context import create_tool_context
14
13
  from hanzo_mcp.tools.shell.run_background import RunBackgroundTool
15
- from hanzo_mcp.tools.mcp.mcp_add import McpAddTool
16
14
  from hanzo_mcp.tools.database.database_manager import DatabaseManager
17
15
 
18
16
 
19
17
  class StatsParams(TypedDict, total=False):
20
18
  """Parameters for stats tool."""
19
+
21
20
  pass
22
21
 
23
22
 
@@ -27,7 +26,7 @@ class StatsTool(BaseTool):
27
26
 
28
27
  def __init__(self, db_manager: DatabaseManager = None):
29
28
  """Initialize the stats tool.
30
-
29
+
31
30
  Args:
32
31
  db_manager: Optional database manager for DB stats
33
32
  """
@@ -77,50 +76,56 @@ Example:
77
76
 
78
77
  output = []
79
78
  warnings = []
80
-
79
+
81
80
  # Header
82
81
  output.append("=== Hanzo AI System Statistics ===")
83
82
  output.append(f"Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
84
83
  output.append("")
85
-
84
+
86
85
  # System Resources
87
86
  output.append("=== System Resources ===")
88
-
87
+
89
88
  # CPU
90
89
  cpu_percent = psutil.cpu_percent(interval=1)
91
90
  cpu_count = psutil.cpu_count()
92
91
  output.append(f"CPU Usage: {cpu_percent}% ({cpu_count} cores)")
93
92
  if cpu_percent > 90:
94
93
  warnings.append(f"⚠️ HIGH CPU USAGE: {cpu_percent}%")
95
-
94
+
96
95
  # Memory
97
96
  memory = psutil.virtual_memory()
98
97
  memory_used_gb = memory.used / (1024**3)
99
98
  memory_total_gb = memory.total / (1024**3)
100
99
  memory_percent = memory.percent
101
- output.append(f"Memory: {memory_used_gb:.1f}/{memory_total_gb:.1f} GB ({memory_percent}%)")
100
+ output.append(
101
+ f"Memory: {memory_used_gb:.1f}/{memory_total_gb:.1f} GB ({memory_percent}%)"
102
+ )
102
103
  if memory_percent > 90:
103
104
  warnings.append(f"⚠️ HIGH MEMORY USAGE: {memory_percent}%")
104
-
105
+
105
106
  # Disk
106
- disk = psutil.disk_usage('/')
107
+ disk = psutil.disk_usage("/")
107
108
  disk_used_gb = disk.used / (1024**3)
108
109
  disk_total_gb = disk.total / (1024**3)
109
110
  disk_percent = disk.percent
110
111
  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(
113
+ f"Disk: {disk_used_gb:.1f}/{disk_total_gb:.1f} GB ({disk_percent}%)"
114
+ )
112
115
  output.append(f"Free Space: {disk_free_gb:.1f} GB")
113
116
  if disk_percent > 90:
114
- warnings.append(f"⚠️ LOW DISK SPACE: Only {disk_free_gb:.1f} GB free ({100-disk_percent:.1f}% remaining)")
115
-
117
+ warnings.append(
118
+ f"⚠️ LOW DISK SPACE: Only {disk_free_gb:.1f} GB free ({100 - disk_percent:.1f}% remaining)"
119
+ )
120
+
116
121
  output.append("")
117
-
122
+
118
123
  # Background Processes
119
124
  output.append("=== Background Processes ===")
120
125
  processes = RunBackgroundTool.get_processes()
121
126
  running_count = 0
122
127
  total_memory_mb = 0
123
-
128
+
124
129
  if processes:
125
130
  for proc in processes.values():
126
131
  if proc.is_running():
@@ -129,12 +134,12 @@ Example:
129
134
  ps_proc = psutil.Process(proc.process.pid)
130
135
  memory_mb = ps_proc.memory_info().rss / (1024**2)
131
136
  total_memory_mb += memory_mb
132
- except:
137
+ except Exception:
133
138
  pass
134
-
139
+
135
140
  output.append(f"Running Processes: {running_count}")
136
141
  output.append(f"Total Memory Usage: {total_memory_mb:.1f} MB")
137
-
142
+
138
143
  # List top processes by memory
139
144
  if running_count > 0:
140
145
  output.append("\nTop Processes:")
@@ -146,31 +151,33 @@ Example:
146
151
  memory_mb = ps_proc.memory_info().rss / (1024**2)
147
152
  cpu = ps_proc.cpu_percent(interval=0.1)
148
153
  proc_list.append((proc.name, memory_mb, cpu, proc_id))
149
- except:
154
+ except Exception:
150
155
  proc_list.append((proc.name, 0, 0, proc_id))
151
-
156
+
152
157
  proc_list.sort(key=lambda x: x[1], reverse=True)
153
158
  for name, mem, cpu, pid in proc_list[:5]:
154
159
  output.append(f" - {name} ({pid}): {mem:.1f} MB, {cpu:.1f}% CPU")
155
160
  else:
156
161
  output.append("No background processes running")
157
-
162
+
158
163
  output.append("")
159
-
164
+
160
165
  # Database Usage
161
166
  if self.db_manager:
162
167
  output.append("=== Database Usage ===")
163
168
  db_dir = Path.home() / ".hanzo" / "db"
164
169
  total_db_size = 0
165
-
170
+
166
171
  if db_dir.exists():
167
172
  for db_file in db_dir.rglob("*.db"):
168
173
  size = db_file.stat().st_size
169
174
  total_db_size += size
170
-
171
- output.append(f"Total Database Size: {total_db_size / (1024**2):.1f} MB")
175
+
176
+ output.append(
177
+ f"Total Database Size: {total_db_size / (1024**2):.1f} MB"
178
+ )
172
179
  output.append(f"Active Projects: {len(self.db_manager.projects)}")
173
-
180
+
174
181
  # List largest databases
175
182
  db_sizes = []
176
183
  for db_file in db_dir.rglob("*.db"):
@@ -179,7 +186,7 @@ Example:
179
186
  project = db_file.parent.parent.name
180
187
  db_type = db_file.stem
181
188
  db_sizes.append((project, db_type, size))
182
-
189
+
183
190
  if db_sizes:
184
191
  db_sizes.sort(key=lambda x: x[2], reverse=True)
185
192
  output.append("\nLargest Databases:")
@@ -187,43 +194,47 @@ Example:
187
194
  output.append(f" - {project}/{db_type}: {size:.1f} MB")
188
195
  else:
189
196
  output.append("No databases found")
190
-
197
+
191
198
  output.append("")
192
-
199
+
193
200
  # MCP Servers
194
201
  output.append("=== MCP Servers ===")
195
202
  mcp_servers = McpAddTool.get_servers()
196
203
  if mcp_servers:
197
- running_mcp = sum(1 for s in mcp_servers.values() if s.get("status") == "running")
204
+ running_mcp = sum(
205
+ 1 for s in mcp_servers.values() if s.get("status") == "running"
206
+ )
198
207
  total_mcp_tools = sum(len(s.get("tools", [])) for s in mcp_servers.values())
199
-
208
+
200
209
  output.append(f"Total Servers: {len(mcp_servers)}")
201
210
  output.append(f"Running: {running_mcp}")
202
211
  output.append(f"Total Tools Available: {total_mcp_tools}")
203
212
  else:
204
213
  output.append("No MCP servers configured")
205
-
214
+
206
215
  output.append("")
207
-
216
+
208
217
  # Hanzo AI Specifics
209
218
  output.append("=== Hanzo AI ===")
210
-
219
+
211
220
  # Log directory size
212
221
  log_dir = Path.home() / ".hanzo" / "logs"
213
222
  if log_dir.exists():
214
223
  log_size = sum(f.stat().st_size for f in log_dir.rglob("*") if f.is_file())
215
224
  log_count = len(list(log_dir.rglob("*.log")))
216
225
  output.append(f"Log Files: {log_count} ({log_size / (1024**2):.1f} MB)")
217
-
226
+
218
227
  if log_size > 100 * 1024**2: # > 100MB
219
- warnings.append(f"⚠️ Large log directory: {log_size / (1024**2):.1f} MB")
220
-
228
+ warnings.append(
229
+ f"⚠️ Large log directory: {log_size / (1024**2):.1f} MB"
230
+ )
231
+
221
232
  # Config directory
222
233
  config_dir = Path.home() / ".hanzo" / "mcp"
223
234
  if config_dir.exists():
224
235
  config_count = len(list(config_dir.rglob("*.json")))
225
236
  output.append(f"Config Files: {config_count}")
226
-
237
+
227
238
  # Tool status (if available)
228
239
  # TODO: Track tool usage statistics
229
240
  output.append("\nTool Categories:")
@@ -232,14 +243,14 @@ Example:
232
243
  output.append(" - Database: sql_query, graph_query, vector_search")
233
244
  output.append(" - Package Runners: uvx, npx, uvx_background, npx_background")
234
245
  output.append(" - MCP Management: mcp_add, mcp_remove, mcp_stats")
235
-
246
+
236
247
  # Warnings Section
237
248
  if warnings:
238
249
  output.append("\n=== ⚠️ WARNINGS ===")
239
250
  for warning in warnings:
240
251
  output.append(warning)
241
252
  output.append("")
242
-
253
+
243
254
  # Recommendations
244
255
  output.append("=== Recommendations ===")
245
256
  if disk_free_gb < 5:
@@ -250,10 +261,17 @@ Example:
250
261
  output.append("- Consider stopping unused background processes")
251
262
  if log_size > 50 * 1024**2:
252
263
  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]):
264
+
265
+ if not any(
266
+ [
267
+ disk_free_gb < 5,
268
+ memory_percent > 80,
269
+ running_count > 10,
270
+ log_size > 50 * 1024**2,
271
+ ]
272
+ ):
255
273
  output.append("✅ System resources are healthy")
256
-
274
+
257
275
  return "\n".join(output)
258
276
 
259
277
  def register(self, mcp_server) -> None:
@@ -0,0 +1,31 @@
1
+ """Test helper classes for MCP tools testing."""
2
+
3
+ from typing import Any, Dict, List, Optional
4
+
5
+
6
+ class PaginatedResponseWrapper:
7
+ """Wrapper class for paginated responses to support tests."""
8
+
9
+ def __init__(self, items=None, next_cursor=None, has_more=False, total_items=None):
10
+ """Initialize paginated response."""
11
+ self.items = items or []
12
+ self.next_cursor = next_cursor
13
+ self.has_more = has_more
14
+ self.total_items = total_items or len(self.items)
15
+
16
+ def to_json(self) -> Dict[str, Any]:
17
+ """Convert to JSON-serializable dict."""
18
+ return {
19
+ "items": self.items,
20
+ "_meta": {
21
+ "next_cursor": self.next_cursor,
22
+ "has_more": self.has_more,
23
+ "total_items": self.total_items,
24
+ },
25
+ }
26
+
27
+
28
+ # Export a convenience constructor
29
+ def PaginatedResponse(items=None, next_cursor=None, has_more=False, total_items=None):
30
+ """Create a paginated response for testing."""
31
+ return PaginatedResponseWrapper(items, next_cursor, has_more, total_items)
@@ -3,16 +3,15 @@
3
3
  This module provides the ThinkingTool for Claude to engage in structured thinking.
4
4
  """
5
5
 
6
- from typing import Annotated, TypedDict, Unpack, final, override
6
+ from typing import Unpack, Annotated, TypedDict, final, override
7
7
 
8
- from mcp.server.fastmcp import Context as MCPContext
9
- from mcp.server import FastMCP
10
8
  from pydantic import Field
9
+ from mcp.server import FastMCP
10
+ from mcp.server.fastmcp import Context as MCPContext
11
11
 
12
12
  from hanzo_mcp.tools.common.base import BaseTool
13
13
  from hanzo_mcp.tools.common.context import create_tool_context
14
14
 
15
-
16
15
  Thought = Annotated[
17
16
  str,
18
17
  Field(
@@ -144,8 +143,5 @@ Feature Implementation Planning
144
143
  tool_self = self # Create a reference to self for use in the closure
145
144
 
146
145
  @mcp_server.tool(name=self.name, description=self.description)
147
- async def think(
148
- thought: Thought,
149
- ctx: MCPContext
150
- ) -> str:
146
+ async def think(thought: Thought, ctx: MCPContext) -> str:
151
147
  return await tool_self.call(ctx, thought=thought)
@@ -1,15 +1,14 @@
1
1
  """Disable tools dynamically."""
2
2
 
3
- from typing import Annotated, TypedDict, Unpack, final, override
3
+ from typing import Unpack, Annotated, TypedDict, final, override
4
4
 
5
- from mcp.server.fastmcp import Context as MCPContext
6
5
  from pydantic import Field
6
+ from mcp.server.fastmcp import Context as MCPContext
7
7
 
8
8
  from hanzo_mcp.tools.common.base import BaseTool
9
9
  from hanzo_mcp.tools.common.context import create_tool_context
10
10
  from hanzo_mcp.tools.common.tool_enable import ToolEnableTool
11
11
 
12
-
13
12
  ToolName = Annotated[
14
13
  str,
15
14
  Field(
@@ -104,13 +103,13 @@ Use 'tool_enable' to re-enable disabled tools.
104
103
 
105
104
  # Check current state
106
105
  was_enabled = ToolEnableTool.is_tool_enabled(tool_name)
107
-
106
+
108
107
  if not was_enabled:
109
108
  return f"Tool '{tool_name}' is already disabled."
110
109
 
111
110
  # Disable the tool
112
111
  ToolEnableTool._tool_states[tool_name] = False
113
-
112
+
114
113
  # Persist if requested
115
114
  if persist:
116
115
  ToolEnableTool._save_states()
@@ -124,19 +123,25 @@ Use 'tool_enable' to re-enable disabled tools.
124
123
  "The tool is now unavailable for use.",
125
124
  f"Use 'tool_enable --tool {tool_name}' to re-enable it.",
126
125
  ]
127
-
126
+
128
127
  if not persist:
129
- output.append("\nNote: This change is temporary and will be lost on restart.")
130
-
128
+ output.append(
129
+ "\nNote: This change is temporary and will be lost on restart."
130
+ )
131
+
131
132
  # Warn about commonly used tools
132
133
  common_tools = {"grep", "read", "write", "bash", "edit"}
133
134
  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
-
135
+ output.append(
136
+ f"\n⚠️ Warning: '{tool_name}' is a commonly used tool. Disabling it may affect normal operations."
137
+ )
138
+
136
139
  # Count disabled tools
137
- disabled_count = sum(1 for enabled in ToolEnableTool._tool_states.values() if not enabled)
140
+ disabled_count = sum(
141
+ 1 for enabled in ToolEnableTool._tool_states.values() if not enabled
142
+ )
138
143
  output.append(f"\nTotal disabled tools: {disabled_count}")
139
-
144
+
140
145
  return "\n".join(output)
141
146
 
142
147
  def register(self, mcp_server) -> None: