hanzo-mcp 0.7.7__py3-none-any.whl → 0.8.1__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 +6 -0
- hanzo_mcp/__main__.py +1 -1
- hanzo_mcp/analytics/__init__.py +2 -2
- hanzo_mcp/analytics/posthog_analytics.py +76 -82
- hanzo_mcp/cli.py +31 -36
- hanzo_mcp/cli_enhanced.py +94 -72
- hanzo_mcp/cli_plugin.py +27 -17
- hanzo_mcp/config/__init__.py +2 -2
- hanzo_mcp/config/settings.py +112 -88
- hanzo_mcp/config/tool_config.py +32 -34
- hanzo_mcp/dev_server.py +66 -67
- hanzo_mcp/prompts/__init__.py +94 -12
- hanzo_mcp/prompts/enhanced_prompts.py +809 -0
- hanzo_mcp/prompts/example_custom_prompt.py +6 -5
- hanzo_mcp/prompts/project_todo_reminder.py +0 -1
- hanzo_mcp/prompts/tool_explorer.py +10 -7
- hanzo_mcp/server.py +17 -21
- hanzo_mcp/server_enhanced.py +15 -22
- hanzo_mcp/tools/__init__.py +56 -28
- hanzo_mcp/tools/agent/__init__.py +16 -19
- hanzo_mcp/tools/agent/agent.py +82 -65
- hanzo_mcp/tools/agent/agent_tool.py +152 -122
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
- hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
- hanzo_mcp/tools/agent/clarification_tool.py +11 -10
- hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
- hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
- hanzo_mcp/tools/agent/code_auth.py +102 -107
- hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
- hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
- hanzo_mcp/tools/agent/critic_tool.py +86 -73
- hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/iching_tool.py +404 -139
- hanzo_mcp/tools/agent/network_tool.py +89 -73
- hanzo_mcp/tools/agent/prompt.py +2 -1
- hanzo_mcp/tools/agent/review_tool.py +101 -98
- hanzo_mcp/tools/agent/swarm_alias.py +87 -0
- hanzo_mcp/tools/agent/swarm_tool.py +246 -161
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
- hanzo_mcp/tools/agent/tool_adapter.py +21 -11
- hanzo_mcp/tools/common/__init__.py +1 -1
- hanzo_mcp/tools/common/base.py +3 -5
- hanzo_mcp/tools/common/batch_tool.py +46 -39
- hanzo_mcp/tools/common/config_tool.py +120 -84
- hanzo_mcp/tools/common/context.py +1 -5
- hanzo_mcp/tools/common/context_fix.py +5 -3
- hanzo_mcp/tools/common/critic_tool.py +4 -8
- hanzo_mcp/tools/common/decorators.py +58 -56
- hanzo_mcp/tools/common/enhanced_base.py +29 -32
- hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
- hanzo_mcp/tools/common/forgiving_edit.py +91 -87
- hanzo_mcp/tools/common/mode.py +15 -17
- hanzo_mcp/tools/common/mode_loader.py +27 -24
- hanzo_mcp/tools/common/paginated_base.py +61 -53
- hanzo_mcp/tools/common/paginated_response.py +72 -79
- hanzo_mcp/tools/common/pagination.py +50 -53
- hanzo_mcp/tools/common/permissions.py +4 -4
- hanzo_mcp/tools/common/personality.py +186 -138
- hanzo_mcp/tools/common/plugin_loader.py +54 -54
- hanzo_mcp/tools/common/stats.py +65 -47
- hanzo_mcp/tools/common/test_helpers.py +31 -0
- hanzo_mcp/tools/common/thinking_tool.py +4 -8
- hanzo_mcp/tools/common/tool_disable.py +17 -12
- hanzo_mcp/tools/common/tool_enable.py +13 -14
- hanzo_mcp/tools/common/tool_list.py +36 -28
- hanzo_mcp/tools/common/truncate.py +23 -23
- hanzo_mcp/tools/config/__init__.py +4 -4
- hanzo_mcp/tools/config/config_tool.py +42 -29
- hanzo_mcp/tools/config/index_config.py +37 -34
- hanzo_mcp/tools/config/mode_tool.py +175 -55
- hanzo_mcp/tools/database/__init__.py +15 -12
- hanzo_mcp/tools/database/database_manager.py +77 -75
- hanzo_mcp/tools/database/graph.py +137 -91
- hanzo_mcp/tools/database/graph_add.py +30 -18
- hanzo_mcp/tools/database/graph_query.py +178 -102
- hanzo_mcp/tools/database/graph_remove.py +33 -28
- hanzo_mcp/tools/database/graph_search.py +97 -75
- hanzo_mcp/tools/database/graph_stats.py +91 -59
- hanzo_mcp/tools/database/sql.py +107 -79
- hanzo_mcp/tools/database/sql_query.py +30 -24
- hanzo_mcp/tools/database/sql_search.py +29 -25
- hanzo_mcp/tools/database/sql_stats.py +47 -35
- hanzo_mcp/tools/editor/neovim_command.py +25 -28
- hanzo_mcp/tools/editor/neovim_edit.py +21 -23
- hanzo_mcp/tools/editor/neovim_session.py +60 -54
- hanzo_mcp/tools/filesystem/__init__.py +31 -30
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
- hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
- hanzo_mcp/tools/filesystem/base.py +1 -1
- hanzo_mcp/tools/filesystem/batch_search.py +316 -224
- hanzo_mcp/tools/filesystem/content_replace.py +4 -4
- hanzo_mcp/tools/filesystem/diff.py +71 -59
- hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
- hanzo_mcp/tools/filesystem/edit.py +4 -4
- hanzo_mcp/tools/filesystem/find.py +173 -80
- hanzo_mcp/tools/filesystem/find_files.py +73 -52
- hanzo_mcp/tools/filesystem/git_search.py +157 -104
- hanzo_mcp/tools/filesystem/grep.py +8 -8
- hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
- hanzo_mcp/tools/filesystem/read.py +12 -10
- hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
- hanzo_mcp/tools/filesystem/search_tool.py +263 -207
- hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
- hanzo_mcp/tools/filesystem/tree.py +35 -33
- hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
- hanzo_mcp/tools/filesystem/watch.py +37 -36
- hanzo_mcp/tools/filesystem/write.py +4 -8
- hanzo_mcp/tools/jupyter/__init__.py +4 -4
- hanzo_mcp/tools/jupyter/base.py +4 -5
- hanzo_mcp/tools/jupyter/jupyter.py +67 -47
- hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
- hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
- hanzo_mcp/tools/llm/__init__.py +5 -7
- hanzo_mcp/tools/llm/consensus_tool.py +72 -52
- hanzo_mcp/tools/llm/llm_manage.py +101 -60
- hanzo_mcp/tools/llm/llm_tool.py +226 -166
- hanzo_mcp/tools/llm/provider_tools.py +25 -26
- hanzo_mcp/tools/lsp/__init__.py +1 -1
- hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
- hanzo_mcp/tools/mcp/__init__.py +2 -3
- hanzo_mcp/tools/mcp/mcp_add.py +27 -25
- hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
- hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
- hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
- hanzo_mcp/tools/memory/__init__.py +39 -21
- hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
- hanzo_mcp/tools/memory/memory_tools.py +90 -108
- hanzo_mcp/tools/search/__init__.py +7 -2
- hanzo_mcp/tools/search/find_tool.py +297 -212
- hanzo_mcp/tools/search/unified_search.py +366 -314
- hanzo_mcp/tools/shell/__init__.py +8 -7
- hanzo_mcp/tools/shell/auto_background.py +56 -49
- hanzo_mcp/tools/shell/base.py +1 -1
- hanzo_mcp/tools/shell/base_process.py +75 -75
- hanzo_mcp/tools/shell/bash_session.py +2 -2
- hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
- hanzo_mcp/tools/shell/bash_tool.py +24 -31
- hanzo_mcp/tools/shell/command_executor.py +12 -12
- hanzo_mcp/tools/shell/logs.py +43 -33
- hanzo_mcp/tools/shell/npx.py +13 -13
- hanzo_mcp/tools/shell/npx_background.py +24 -21
- hanzo_mcp/tools/shell/npx_tool.py +18 -22
- hanzo_mcp/tools/shell/open.py +19 -21
- hanzo_mcp/tools/shell/pkill.py +31 -26
- hanzo_mcp/tools/shell/process_tool.py +32 -32
- hanzo_mcp/tools/shell/processes.py +57 -58
- hanzo_mcp/tools/shell/run_background.py +24 -25
- hanzo_mcp/tools/shell/run_command.py +5 -5
- hanzo_mcp/tools/shell/run_command_windows.py +5 -5
- hanzo_mcp/tools/shell/session_storage.py +3 -3
- hanzo_mcp/tools/shell/streaming_command.py +141 -126
- hanzo_mcp/tools/shell/uvx.py +24 -25
- hanzo_mcp/tools/shell/uvx_background.py +35 -33
- hanzo_mcp/tools/shell/uvx_tool.py +18 -22
- hanzo_mcp/tools/todo/__init__.py +6 -2
- hanzo_mcp/tools/todo/todo.py +50 -37
- hanzo_mcp/tools/todo/todo_read.py +5 -8
- hanzo_mcp/tools/todo/todo_write.py +5 -7
- hanzo_mcp/tools/vector/__init__.py +40 -28
- hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
- hanzo_mcp/tools/vector/git_ingester.py +170 -179
- hanzo_mcp/tools/vector/index_tool.py +96 -44
- hanzo_mcp/tools/vector/infinity_store.py +283 -228
- hanzo_mcp/tools/vector/mock_infinity.py +39 -40
- hanzo_mcp/tools/vector/project_manager.py +88 -78
- hanzo_mcp/tools/vector/vector.py +59 -42
- hanzo_mcp/tools/vector/vector_index.py +30 -27
- hanzo_mcp/tools/vector/vector_search.py +64 -45
- hanzo_mcp/types.py +6 -4
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/METADATA +1 -1
- hanzo_mcp-0.8.1.dist-info/RECORD +185 -0
- hanzo_mcp-0.7.7.dist-info/RECORD +0 -182
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/top_level.txt +0 -0
hanzo_mcp/config/settings.py
CHANGED
|
@@ -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
|
|
15
|
-
from dataclasses import dataclass, asdict, field
|
|
15
|
+
from dataclasses import field, asdict, dataclass
|
|
16
16
|
|
|
17
|
-
from .tool_config import TOOL_REGISTRY
|
|
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(
|
|
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(
|
|
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 = {
|
|
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(
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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(
|
|
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
|
+
)
|