hanzo-mcp 0.7.6__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 +7 -1
  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.6.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.6.dist-info/RECORD +0 -182
  176. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/WHEEL +0 -0
  177. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
  178. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/top_level.txt +0 -0
@@ -8,18 +8,19 @@ Handles loading and saving configuration from multiple sources:
8
8
  5. CLI arguments (highest priority)
9
9
  """
10
10
 
11
- import json
12
11
  import os
12
+ import json
13
+ from typing import Any, Dict, List, Optional
13
14
  from pathlib import Path
14
- from typing import Any, Dict, List, Optional, Union
15
- from dataclasses import dataclass, asdict, field
15
+ from dataclasses import field, asdict, dataclass
16
16
 
17
- from .tool_config import TOOL_REGISTRY, ToolCategory
17
+ from .tool_config import TOOL_REGISTRY
18
18
 
19
19
 
20
20
  @dataclass
21
21
  class MCPServerConfig:
22
22
  """Configuration for an external MCP server."""
23
+
23
24
  name: str
24
25
  command: str
25
26
  args: List[str] = field(default_factory=list)
@@ -27,11 +28,12 @@ class MCPServerConfig:
27
28
  trusted: bool = False # Whether this server is trusted
28
29
  description: str = ""
29
30
  capabilities: List[str] = field(default_factory=list) # tools, prompts, resources
30
-
31
-
31
+
32
+
32
33
  @dataclass
33
34
  class VectorStoreConfig:
34
35
  """Configuration for the vector store."""
36
+
35
37
  enabled: bool = False
36
38
  provider: str = "infinity" # infinity, chroma, etc.
37
39
  data_path: Optional[str] = None # Will default to ~/.config/hanzo/vector-store
@@ -40,9 +42,10 @@ class VectorStoreConfig:
40
42
  chunk_overlap: int = 200
41
43
 
42
44
 
43
- @dataclass
45
+ @dataclass
44
46
  class ProjectConfig:
45
47
  """Configuration specific to a project."""
48
+
46
49
  name: str
47
50
  root_path: str
48
51
  rules: List[str] = field(default_factory=list)
@@ -50,12 +53,15 @@ class ProjectConfig:
50
53
  tasks: List[Dict[str, Any]] = field(default_factory=list)
51
54
  enabled_tools: Dict[str, bool] = field(default_factory=dict)
52
55
  disabled_tools: List[str] = field(default_factory=list)
53
- mcp_servers: List[str] = field(default_factory=list) # Names of enabled MCP servers for this project
56
+ mcp_servers: List[str] = field(
57
+ default_factory=list
58
+ ) # Names of enabled MCP servers for this project
54
59
 
55
60
 
56
61
  @dataclass
57
62
  class AgentConfig:
58
63
  """Configuration for agent tools."""
64
+
59
65
  enabled: bool = False
60
66
  model: Optional[str] = None
61
67
  api_key: Optional[str] = None
@@ -66,9 +72,10 @@ class AgentConfig:
66
72
  max_tool_uses: int = 30
67
73
 
68
74
 
69
- @dataclass
75
+ @dataclass
70
76
  class ServerConfig:
71
77
  """Configuration for the MCP server."""
78
+
72
79
  name: str = "hanzo-mcp"
73
80
  host: str = "127.0.0.1"
74
81
  port: int = 8888
@@ -80,53 +87,63 @@ class ServerConfig:
80
87
  @dataclass
81
88
  class HanzoMCPSettings:
82
89
  """Complete configuration for Hanzo AI."""
90
+
83
91
  # Server settings
84
92
  server: ServerConfig = field(default_factory=ServerConfig)
85
-
93
+
86
94
  # Paths and permissions
87
95
  allowed_paths: List[str] = field(default_factory=list)
88
96
  project_paths: List[str] = field(default_factory=list)
89
97
  project_dir: Optional[str] = None
90
-
98
+
91
99
  # Tool configuration
92
100
  enabled_tools: Dict[str, bool] = field(default_factory=dict)
93
101
  disabled_tools: List[str] = field(default_factory=list)
94
-
102
+
95
103
  # Agent configuration
96
104
  agent: AgentConfig = field(default_factory=AgentConfig)
97
-
105
+
98
106
  # Vector store configuration
99
107
  vector_store: VectorStoreConfig = field(default_factory=VectorStoreConfig)
100
-
108
+
101
109
  # MCP Hub configuration
102
110
  mcp_servers: Dict[str, MCPServerConfig] = field(default_factory=dict)
103
111
  hub_enabled: bool = False
104
- trusted_servers: List[str] = field(default_factory=list) # Whitelist of trusted server names
105
-
112
+ trusted_servers: List[str] = field(
113
+ default_factory=list
114
+ ) # Whitelist of trusted server names
115
+
106
116
  # Project-specific configurations
107
117
  projects: Dict[str, ProjectConfig] = field(default_factory=dict)
108
118
  current_project: Optional[str] = None
109
-
119
+
110
120
  # Mode configuration
111
121
  active_mode: Optional[str] = None
112
-
122
+
113
123
  def __post_init__(self):
114
124
  """Initialize default tool states if not specified."""
115
125
  if not self.enabled_tools:
116
- self.enabled_tools = {name: config.enabled for name, config in TOOL_REGISTRY.items()}
117
-
126
+ self.enabled_tools = {
127
+ name: config.enabled for name, config in TOOL_REGISTRY.items()
128
+ }
129
+
118
130
  # Apply disabled_tools list to enabled_tools dict
119
131
  for tool_name in self.disabled_tools:
120
132
  if tool_name in TOOL_REGISTRY:
121
133
  self.enabled_tools[tool_name] = False
122
-
134
+
123
135
  def is_tool_enabled(self, tool_name: str) -> bool:
124
136
  """Check if a specific tool is enabled."""
125
137
  # Check disabled_tools list first (highest priority)
126
138
  if tool_name in self.disabled_tools:
127
139
  return False
128
- return self.enabled_tools.get(tool_name, TOOL_REGISTRY.get(tool_name, type('obj', (object,), {'enabled': False})).enabled)
129
-
140
+ return self.enabled_tools.get(
141
+ tool_name,
142
+ TOOL_REGISTRY.get(
143
+ tool_name, type("obj", (object,), {"enabled": False})
144
+ ).enabled,
145
+ )
146
+
130
147
  def enable_tool(self, tool_name: str) -> bool:
131
148
  """Enable a specific tool."""
132
149
  if tool_name in TOOL_REGISTRY:
@@ -136,7 +153,7 @@ class HanzoMCPSettings:
136
153
  self.disabled_tools.remove(tool_name)
137
154
  return True
138
155
  return False
139
-
156
+
140
157
  def disable_tool(self, tool_name: str) -> bool:
141
158
  """Disable a specific tool."""
142
159
  if tool_name in TOOL_REGISTRY:
@@ -146,15 +163,15 @@ class HanzoMCPSettings:
146
163
  self.disabled_tools.append(tool_name)
147
164
  return True
148
165
  return False
149
-
166
+
150
167
  def get_enabled_tools(self) -> List[str]:
151
168
  """Get list of enabled tool names."""
152
169
  return [name for name in TOOL_REGISTRY.keys() if self.is_tool_enabled(name)]
153
-
170
+
154
171
  def get_disabled_tools(self) -> List[str]:
155
172
  """Get list of disabled tool names."""
156
173
  return [name for name in TOOL_REGISTRY.keys() if not self.is_tool_enabled(name)]
157
-
174
+
158
175
  # MCP Server Management
159
176
  def add_mcp_server(self, server_config: MCPServerConfig) -> bool:
160
177
  """Add a new MCP server configuration."""
@@ -162,7 +179,7 @@ class HanzoMCPSettings:
162
179
  return False # Server already exists
163
180
  self.mcp_servers[server_config.name] = server_config
164
181
  return True
165
-
182
+
166
183
  def remove_mcp_server(self, server_name: str) -> bool:
167
184
  """Remove an MCP server configuration."""
168
185
  if server_name in self.mcp_servers:
@@ -172,21 +189,21 @@ class HanzoMCPSettings:
172
189
  self.trusted_servers.remove(server_name)
173
190
  return True
174
191
  return False
175
-
192
+
176
193
  def enable_mcp_server(self, server_name: str) -> bool:
177
194
  """Enable an MCP server."""
178
195
  if server_name in self.mcp_servers:
179
196
  self.mcp_servers[server_name].enabled = True
180
197
  return True
181
198
  return False
182
-
199
+
183
200
  def disable_mcp_server(self, server_name: str) -> bool:
184
201
  """Disable an MCP server."""
185
202
  if server_name in self.mcp_servers:
186
203
  self.mcp_servers[server_name].enabled = False
187
204
  return True
188
205
  return False
189
-
206
+
190
207
  def trust_mcp_server(self, server_name: str) -> bool:
191
208
  """Add server to trusted list."""
192
209
  if server_name in self.mcp_servers:
@@ -195,15 +212,15 @@ class HanzoMCPSettings:
195
212
  self.mcp_servers[server_name].trusted = True
196
213
  return True
197
214
  return False
198
-
215
+
199
216
  def get_enabled_mcp_servers(self) -> List[MCPServerConfig]:
200
217
  """Get list of enabled MCP servers."""
201
218
  return [server for server in self.mcp_servers.values() if server.enabled]
202
-
219
+
203
220
  def get_trusted_mcp_servers(self) -> List[MCPServerConfig]:
204
221
  """Get list of trusted MCP servers."""
205
222
  return [server for server in self.mcp_servers.values() if server.trusted]
206
-
223
+
207
224
  # Project Management
208
225
  def add_project(self, project_config: ProjectConfig) -> bool:
209
226
  """Add a project configuration."""
@@ -211,7 +228,7 @@ class HanzoMCPSettings:
211
228
  return False # Project already exists
212
229
  self.projects[project_config.name] = project_config
213
230
  return True
214
-
231
+
215
232
  def remove_project(self, project_name: str) -> bool:
216
233
  """Remove a project configuration."""
217
234
  if project_name in self.projects:
@@ -220,25 +237,25 @@ class HanzoMCPSettings:
220
237
  self.current_project = None
221
238
  return True
222
239
  return False
223
-
240
+
224
241
  def set_current_project(self, project_name: str) -> bool:
225
242
  """Set the current active project."""
226
243
  if project_name in self.projects:
227
244
  self.current_project = project_name
228
245
  return True
229
246
  return False
230
-
247
+
231
248
  def get_current_project(self) -> Optional[ProjectConfig]:
232
249
  """Get the current project configuration."""
233
250
  if self.current_project:
234
251
  return self.projects.get(self.current_project)
235
252
  return None
236
-
253
+
237
254
  def get_project_tools(self, project_name: Optional[str] = None) -> Dict[str, bool]:
238
255
  """Get tool configuration for a specific project."""
239
256
  if not project_name:
240
257
  project_name = self.current_project
241
-
258
+
242
259
  if project_name and project_name in self.projects:
243
260
  project = self.projects[project_name]
244
261
  # Start with global tool settings
@@ -249,7 +266,7 @@ class HanzoMCPSettings:
249
266
  for tool_name in project.disabled_tools:
250
267
  tools[tool_name] = False
251
268
  return tools
252
-
269
+
253
270
  return self.enabled_tools
254
271
 
255
272
 
@@ -259,7 +276,7 @@ def get_config_dir() -> Path:
259
276
  config_dir = Path(os.environ.get("APPDATA", "")) / "hanzo"
260
277
  else: # Unix/macOS
261
278
  config_dir = Path.home() / ".config" / "hanzo"
262
-
279
+
263
280
  config_dir.mkdir(parents=True, exist_ok=True)
264
281
  return config_dir
265
282
 
@@ -289,10 +306,10 @@ def ensure_project_hanzo_dir(project_dir: str) -> Path:
289
306
  project_path = Path(project_dir)
290
307
  hanzo_dir = project_path / ".hanzo"
291
308
  hanzo_dir.mkdir(exist_ok=True)
292
-
309
+
293
310
  # Create default structure
294
311
  (hanzo_dir / "db").mkdir(exist_ok=True) # Vector database
295
-
312
+
296
313
  # Create default project config if it doesn't exist
297
314
  config_path = hanzo_dir / "mcp-settings.json"
298
315
  if not config_path.exists():
@@ -302,27 +319,27 @@ def ensure_project_hanzo_dir(project_dir: str) -> Path:
302
319
  "rules": [
303
320
  "Follow project-specific coding standards",
304
321
  "Test all changes before committing",
305
- "Update documentation for new features"
322
+ "Update documentation for new features",
306
323
  ],
307
324
  "workflows": {
308
325
  "development": {
309
326
  "steps": ["edit", "test", "commit"],
310
- "tools": ["read", "write", "edit", "run_command"]
327
+ "tools": ["read", "write", "edit", "run_command"],
311
328
  },
312
329
  "documentation": {
313
330
  "steps": ["read", "analyze", "write"],
314
- "tools": ["read", "write", "vector_search"]
315
- }
331
+ "tools": ["read", "write", "vector_search"],
332
+ },
316
333
  },
317
334
  "tasks": [],
318
335
  "enabled_tools": {},
319
336
  "disabled_tools": [],
320
- "mcp_servers": []
337
+ "mcp_servers": [],
321
338
  }
322
-
323
- with open(config_path, 'w') as f:
339
+
340
+ with open(config_path, "w") as f:
324
341
  json.dump(default_project_config, f, indent=2)
325
-
342
+
326
343
  return hanzo_dir
327
344
 
328
345
 
@@ -330,7 +347,7 @@ def detect_project_from_path(file_path: str) -> Optional[Dict[str, str]]:
330
347
  """Detect project information from a file path by looking for LLM.md."""
331
348
  path = Path(file_path).resolve()
332
349
  current_path = path.parent if path.is_file() else path
333
-
350
+
334
351
  while current_path != current_path.parent: # Stop at filesystem root
335
352
  llm_md_path = current_path / "LLM.md"
336
353
  if llm_md_path.exists():
@@ -338,72 +355,75 @@ def detect_project_from_path(file_path: str) -> Optional[Dict[str, str]]:
338
355
  "name": current_path.name,
339
356
  "root_path": str(current_path),
340
357
  "llm_md_path": str(llm_md_path),
341
- "hanzo_dir": str(ensure_project_hanzo_dir(str(current_path)))
358
+ "hanzo_dir": str(ensure_project_hanzo_dir(str(current_path))),
342
359
  }
343
360
  current_path = current_path.parent
344
-
361
+
345
362
  return None
346
363
 
347
364
 
348
365
  def _load_from_env() -> Dict[str, Any]:
349
366
  """Load configuration from environment variables."""
350
367
  config = {}
351
-
368
+
352
369
  # Check for agent API keys
353
370
  has_api_keys = False
354
-
371
+
355
372
  # HANZO_API_KEY
356
373
  if hanzo_key := os.environ.get("HANZO_API_KEY"):
357
374
  config.setdefault("agent", {})["hanzo_api_key"] = hanzo_key
358
375
  config["agent"]["enabled"] = True
359
376
  has_api_keys = True
360
-
377
+
361
378
  # Check for other API keys
362
379
  api_key_env_vars = [
363
380
  ("OPENAI_API_KEY", "openai"),
364
- ("ANTHROPIC_API_KEY", "anthropic"),
381
+ ("ANTHROPIC_API_KEY", "anthropic"),
365
382
  ("GOOGLE_API_KEY", "google"),
366
383
  ("GROQ_API_KEY", "groq"),
367
384
  ("TOGETHER_API_KEY", "together"),
368
385
  ("MISTRAL_API_KEY", "mistral"),
369
386
  ("PERPLEXITY_API_KEY", "perplexity"),
370
387
  ]
371
-
372
- for env_var, provider in api_key_env_vars:
388
+
389
+ for env_var, _provider in api_key_env_vars:
373
390
  if os.environ.get(env_var):
374
391
  has_api_keys = True
375
392
  break
376
-
393
+
377
394
  # Auto-enable agent and consensus tools if API keys present
378
395
  if has_api_keys:
379
396
  config.setdefault("enabled_tools", {})
380
397
  config["enabled_tools"]["agent"] = True
381
398
  config["enabled_tools"]["consensus"] = True
382
399
  config.setdefault("agent", {})["enabled"] = True
383
-
400
+
384
401
  # Check for MODE/PERSONALITY/HANZO_MODE
385
- if mode := os.environ.get("HANZO_MODE") or os.environ.get("PERSONALITY") or os.environ.get("MODE"):
402
+ if (
403
+ mode := os.environ.get("HANZO_MODE")
404
+ or os.environ.get("PERSONALITY")
405
+ or os.environ.get("MODE")
406
+ ):
386
407
  config["active_mode"] = mode
387
-
408
+
388
409
  # Check for other environment overrides
389
410
  if project_dir := os.environ.get("HANZO_PROJECT_DIR"):
390
411
  config["project_dir"] = project_dir
391
-
412
+
392
413
  if log_level := os.environ.get("HANZO_LOG_LEVEL"):
393
414
  config.setdefault("server", {})["log_level"] = log_level
394
-
415
+
395
416
  if allowed_paths := os.environ.get("HANZO_ALLOWED_PATHS"):
396
417
  config["allowed_paths"] = allowed_paths.split(":")
397
-
418
+
398
419
  return config
399
420
 
400
421
 
401
422
  def load_settings(
402
- project_dir: Optional[str] = None,
403
- config_overrides: Optional[Dict[str, Any]] = None
423
+ project_dir: Optional[str] = None, config_overrides: Optional[Dict[str, Any]] = None
404
424
  ) -> HanzoMCPSettings:
405
425
  """Load settings from all sources in priority order.
406
-
426
+
407
427
  Priority (highest to lowest):
408
428
  1. config_overrides (usually from CLI)
409
429
  2. Environment variables
@@ -413,7 +433,7 @@ def load_settings(
413
433
  """
414
434
  # Start with defaults
415
435
  settings = HanzoMCPSettings()
416
-
436
+
417
437
  # Load global config
418
438
  global_config_path = get_global_config_path()
419
439
  if global_config_path.exists():
@@ -423,9 +443,10 @@ def load_settings(
423
443
  settings = _merge_config(settings, global_config)
424
444
  except Exception as e:
425
445
  import logging
446
+
426
447
  logger = logging.getLogger(__name__)
427
448
  logger.warning(f"Failed to load global config: {e}")
428
-
449
+
429
450
  # Load project config
430
451
  project_config_path = get_project_config_path(project_dir)
431
452
  if project_config_path:
@@ -435,28 +456,29 @@ def load_settings(
435
456
  settings = _merge_config(settings, project_config)
436
457
  except Exception as e:
437
458
  import logging
459
+
438
460
  logger = logging.getLogger(__name__)
439
461
  logger.warning(f"Failed to load project config: {e}")
440
-
462
+
441
463
  # Apply environment variables
442
464
  env_config = _load_from_env()
443
465
  if env_config:
444
466
  settings = _merge_config(settings, env_config)
445
-
467
+
446
468
  # Apply CLI overrides
447
469
  if config_overrides:
448
470
  settings = _merge_config(settings, config_overrides)
449
-
471
+
450
472
  return settings
451
473
 
452
474
 
453
475
  def save_settings(settings: HanzoMCPSettings, global_config: bool = True) -> Path:
454
476
  """Save settings to configuration file.
455
-
477
+
456
478
  Args:
457
479
  settings: Settings to save
458
480
  global_config: If True, save to global config, otherwise save to project config
459
-
481
+
460
482
  Returns:
461
483
  Path where settings were saved
462
484
  """
@@ -465,20 +487,22 @@ def save_settings(settings: HanzoMCPSettings, global_config: bool = True) -> Pat
465
487
  else:
466
488
  # Save to current directory project config
467
489
  config_path = Path.cwd() / ".hanzo-mcp.json"
468
-
490
+
469
491
  config_path.parent.mkdir(parents=True, exist_ok=True)
470
-
471
- with open(config_path, 'w') as f:
492
+
493
+ with open(config_path, "w") as f:
472
494
  json.dump(asdict(settings), f, indent=2)
473
-
495
+
474
496
  return config_path
475
497
 
476
498
 
477
- def _merge_config(base_settings: HanzoMCPSettings, config_dict: Dict[str, Any]) -> HanzoMCPSettings:
499
+ def _merge_config(
500
+ base_settings: HanzoMCPSettings, config_dict: Dict[str, Any]
501
+ ) -> HanzoMCPSettings:
478
502
  """Merge configuration dictionary into settings object."""
479
503
  # Convert to dict, merge, then convert back
480
504
  base_dict = asdict(base_settings)
481
-
505
+
482
506
  def deep_merge(base: Dict[str, Any], update: Dict[str, Any]) -> Dict[str, Any]:
483
507
  """Deep merge two dictionaries."""
484
508
  for key, value in update.items():
@@ -487,18 +511,18 @@ def _merge_config(base_settings: HanzoMCPSettings, config_dict: Dict[str, Any])
487
511
  else:
488
512
  base[key] = value
489
513
  return base
490
-
514
+
491
515
  merged = deep_merge(base_dict, config_dict)
492
-
516
+
493
517
  # Reconstruct the settings object
494
518
  mcp_servers = {}
495
519
  for name, server_data in merged.get("mcp_servers", {}).items():
496
520
  mcp_servers[name] = MCPServerConfig(**server_data)
497
-
521
+
498
522
  projects = {}
499
523
  for name, project_data in merged.get("projects", {}).items():
500
524
  projects[name] = ProjectConfig(**project_data)
501
-
525
+
502
526
  return HanzoMCPSettings(
503
527
  server=ServerConfig(**merged.get("server", {})),
504
528
  allowed_paths=merged.get("allowed_paths", []),
@@ -513,4 +537,4 @@ def _merge_config(base_settings: HanzoMCPSettings, config_dict: Dict[str, Any])
513
537
  trusted_servers=merged.get("trusted_servers", []),
514
538
  projects=projects,
515
539
  current_project=merged.get("current_project"),
516
- )
540
+ )