hanzo-mcp 0.5.2__py3-none-any.whl → 0.6.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 +1 -1
- hanzo_mcp/cli.py +32 -0
- hanzo_mcp/dev_server.py +246 -0
- hanzo_mcp/prompts/__init__.py +1 -1
- hanzo_mcp/prompts/project_system.py +43 -7
- hanzo_mcp/server.py +5 -1
- hanzo_mcp/tools/__init__.py +66 -35
- hanzo_mcp/tools/agent/__init__.py +1 -1
- hanzo_mcp/tools/agent/agent.py +401 -0
- hanzo_mcp/tools/agent/agent_tool.py +3 -4
- hanzo_mcp/tools/common/__init__.py +1 -1
- hanzo_mcp/tools/common/base.py +2 -2
- hanzo_mcp/tools/common/batch_tool.py +3 -5
- hanzo_mcp/tools/common/config_tool.py +1 -1
- hanzo_mcp/tools/common/context.py +1 -1
- hanzo_mcp/tools/common/palette.py +344 -0
- hanzo_mcp/tools/common/palette_loader.py +108 -0
- hanzo_mcp/tools/common/stats.py +1 -1
- hanzo_mcp/tools/common/thinking_tool.py +3 -5
- hanzo_mcp/tools/common/tool_disable.py +1 -1
- hanzo_mcp/tools/common/tool_enable.py +1 -1
- hanzo_mcp/tools/common/tool_list.py +49 -52
- hanzo_mcp/tools/config/__init__.py +10 -0
- hanzo_mcp/tools/config/config_tool.py +212 -0
- hanzo_mcp/tools/config/index_config.py +176 -0
- hanzo_mcp/tools/config/palette_tool.py +166 -0
- hanzo_mcp/tools/database/__init__.py +1 -1
- hanzo_mcp/tools/database/graph.py +482 -0
- hanzo_mcp/tools/database/graph_add.py +1 -1
- hanzo_mcp/tools/database/graph_query.py +1 -1
- hanzo_mcp/tools/database/graph_remove.py +1 -1
- hanzo_mcp/tools/database/graph_search.py +1 -1
- hanzo_mcp/tools/database/graph_stats.py +1 -1
- hanzo_mcp/tools/database/sql.py +411 -0
- hanzo_mcp/tools/database/sql_query.py +1 -1
- hanzo_mcp/tools/database/sql_search.py +1 -1
- hanzo_mcp/tools/database/sql_stats.py +1 -1
- hanzo_mcp/tools/editor/neovim_command.py +1 -1
- hanzo_mcp/tools/editor/neovim_edit.py +1 -1
- hanzo_mcp/tools/editor/neovim_session.py +1 -1
- hanzo_mcp/tools/filesystem/__init__.py +42 -13
- hanzo_mcp/tools/filesystem/base.py +1 -1
- hanzo_mcp/tools/filesystem/batch_search.py +4 -4
- hanzo_mcp/tools/filesystem/content_replace.py +3 -5
- hanzo_mcp/tools/filesystem/diff.py +193 -0
- hanzo_mcp/tools/filesystem/directory_tree.py +3 -5
- hanzo_mcp/tools/filesystem/edit.py +3 -5
- hanzo_mcp/tools/filesystem/find.py +443 -0
- hanzo_mcp/tools/filesystem/find_files.py +1 -1
- hanzo_mcp/tools/filesystem/git_search.py +1 -1
- hanzo_mcp/tools/filesystem/grep.py +2 -2
- hanzo_mcp/tools/filesystem/multi_edit.py +3 -5
- hanzo_mcp/tools/filesystem/read.py +17 -5
- hanzo_mcp/tools/filesystem/{grep_ast_tool.py → symbols.py} +17 -27
- hanzo_mcp/tools/filesystem/symbols_unified.py +376 -0
- hanzo_mcp/tools/filesystem/tree.py +268 -0
- hanzo_mcp/tools/filesystem/unified_search.py +711 -0
- hanzo_mcp/tools/filesystem/unix_aliases.py +99 -0
- hanzo_mcp/tools/filesystem/watch.py +174 -0
- hanzo_mcp/tools/filesystem/write.py +3 -5
- hanzo_mcp/tools/jupyter/__init__.py +9 -12
- hanzo_mcp/tools/jupyter/base.py +1 -1
- hanzo_mcp/tools/jupyter/jupyter.py +326 -0
- hanzo_mcp/tools/jupyter/notebook_edit.py +3 -4
- hanzo_mcp/tools/jupyter/notebook_read.py +3 -5
- hanzo_mcp/tools/llm/__init__.py +4 -0
- hanzo_mcp/tools/llm/consensus_tool.py +1 -1
- hanzo_mcp/tools/llm/llm_manage.py +1 -1
- hanzo_mcp/tools/llm/llm_tool.py +1 -1
- hanzo_mcp/tools/llm/llm_unified.py +851 -0
- hanzo_mcp/tools/llm/provider_tools.py +1 -1
- hanzo_mcp/tools/mcp/__init__.py +4 -0
- hanzo_mcp/tools/mcp/mcp_add.py +1 -1
- hanzo_mcp/tools/mcp/mcp_remove.py +1 -1
- hanzo_mcp/tools/mcp/mcp_stats.py +1 -1
- hanzo_mcp/tools/mcp/mcp_unified.py +503 -0
- hanzo_mcp/tools/shell/__init__.py +20 -42
- hanzo_mcp/tools/shell/base.py +1 -1
- hanzo_mcp/tools/shell/base_process.py +303 -0
- hanzo_mcp/tools/shell/bash_unified.py +134 -0
- hanzo_mcp/tools/shell/logs.py +1 -1
- hanzo_mcp/tools/shell/npx.py +1 -1
- hanzo_mcp/tools/shell/npx_background.py +1 -1
- hanzo_mcp/tools/shell/npx_unified.py +101 -0
- hanzo_mcp/tools/shell/open.py +107 -0
- hanzo_mcp/tools/shell/pkill.py +1 -1
- hanzo_mcp/tools/shell/process_unified.py +131 -0
- hanzo_mcp/tools/shell/processes.py +1 -1
- hanzo_mcp/tools/shell/run_background.py +1 -1
- hanzo_mcp/tools/shell/run_command.py +3 -4
- hanzo_mcp/tools/shell/run_command_windows.py +3 -4
- hanzo_mcp/tools/shell/uvx.py +1 -1
- hanzo_mcp/tools/shell/uvx_background.py +1 -1
- hanzo_mcp/tools/shell/uvx_unified.py +101 -0
- hanzo_mcp/tools/todo/__init__.py +1 -1
- hanzo_mcp/tools/todo/base.py +1 -1
- hanzo_mcp/tools/todo/todo.py +265 -0
- hanzo_mcp/tools/todo/todo_read.py +3 -5
- hanzo_mcp/tools/todo/todo_write.py +3 -5
- hanzo_mcp/tools/vector/__init__.py +1 -1
- hanzo_mcp/tools/vector/index_tool.py +1 -1
- hanzo_mcp/tools/vector/project_manager.py +27 -5
- hanzo_mcp/tools/vector/vector.py +311 -0
- hanzo_mcp/tools/vector/vector_index.py +1 -1
- hanzo_mcp/tools/vector/vector_search.py +1 -1
- hanzo_mcp-0.6.1.dist-info/METADATA +336 -0
- hanzo_mcp-0.6.1.dist-info/RECORD +134 -0
- hanzo_mcp-0.6.1.dist-info/entry_points.txt +3 -0
- hanzo_mcp-0.5.2.dist-info/METADATA +0 -276
- hanzo_mcp-0.5.2.dist-info/RECORD +0 -106
- hanzo_mcp-0.5.2.dist-info/entry_points.txt +0 -2
- {hanzo_mcp-0.5.2.dist-info → hanzo_mcp-0.6.1.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.5.2.dist-info → hanzo_mcp-0.6.1.dist-info}/licenses/LICENSE +0 -0
- {hanzo_mcp-0.5.2.dist-info → hanzo_mcp-0.6.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""Configuration tool for Hanzo MCP.
|
|
2
|
+
|
|
3
|
+
Git-style config tool for managing settings.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Annotated, TypedDict, Unpack, final, override, Optional, Dict, Any
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
11
|
+
from pydantic import Field
|
|
12
|
+
|
|
13
|
+
from hanzo_mcp.tools.common.base import BaseTool
|
|
14
|
+
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
15
|
+
from hanzo_mcp.tools.config.index_config import IndexConfig, IndexScope
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Parameter types
|
|
19
|
+
Action = Annotated[
|
|
20
|
+
str,
|
|
21
|
+
Field(
|
|
22
|
+
description="Action: get (default), set, list, toggle",
|
|
23
|
+
default="get",
|
|
24
|
+
),
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
Key = Annotated[
|
|
28
|
+
Optional[str],
|
|
29
|
+
Field(
|
|
30
|
+
description="Configuration key (e.g., index.scope, vector.enabled)",
|
|
31
|
+
default=None,
|
|
32
|
+
),
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
Value = Annotated[
|
|
36
|
+
Optional[str],
|
|
37
|
+
Field(
|
|
38
|
+
description="Configuration value",
|
|
39
|
+
default=None,
|
|
40
|
+
),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
Scope = Annotated[
|
|
44
|
+
str,
|
|
45
|
+
Field(
|
|
46
|
+
description="Config scope: local (project) or global",
|
|
47
|
+
default="local",
|
|
48
|
+
),
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
ConfigPath = Annotated[
|
|
52
|
+
Optional[str],
|
|
53
|
+
Field(
|
|
54
|
+
description="Path for project-specific config",
|
|
55
|
+
default=None,
|
|
56
|
+
),
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ConfigParams(TypedDict, total=False):
|
|
61
|
+
"""Parameters for config tool."""
|
|
62
|
+
action: str
|
|
63
|
+
key: Optional[str]
|
|
64
|
+
value: Optional[str]
|
|
65
|
+
scope: str
|
|
66
|
+
path: Optional[str]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@final
|
|
70
|
+
class ConfigTool(BaseTool):
|
|
71
|
+
"""Git-style configuration management tool."""
|
|
72
|
+
|
|
73
|
+
def __init__(self, permission_manager: PermissionManager):
|
|
74
|
+
"""Initialize config tool."""
|
|
75
|
+
super().__init__(permission_manager)
|
|
76
|
+
self.index_config = IndexConfig()
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
@override
|
|
80
|
+
def name(self) -> str:
|
|
81
|
+
"""Get the tool name."""
|
|
82
|
+
return "config"
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
@override
|
|
86
|
+
def description(self) -> str:
|
|
87
|
+
"""Get the tool description."""
|
|
88
|
+
return """Git-style configuration. Actions: get (default), set, list, toggle.
|
|
89
|
+
|
|
90
|
+
Usage:
|
|
91
|
+
config index.scope
|
|
92
|
+
config --action set index.scope project
|
|
93
|
+
config --action list
|
|
94
|
+
config --action toggle index.scope --path ./project"""
|
|
95
|
+
|
|
96
|
+
@override
|
|
97
|
+
async def call(
|
|
98
|
+
self,
|
|
99
|
+
ctx: MCPContext,
|
|
100
|
+
**params: Unpack[ConfigParams],
|
|
101
|
+
) -> str:
|
|
102
|
+
"""Execute config operation."""
|
|
103
|
+
tool_ctx = self.create_tool_context(ctx)
|
|
104
|
+
|
|
105
|
+
# Extract parameters
|
|
106
|
+
action = params.get("action", "get")
|
|
107
|
+
key = params.get("key")
|
|
108
|
+
value = params.get("value")
|
|
109
|
+
scope = params.get("scope", "local")
|
|
110
|
+
path = params.get("path")
|
|
111
|
+
|
|
112
|
+
# Route to handler
|
|
113
|
+
if action == "get":
|
|
114
|
+
return await self._handle_get(key, scope, path, tool_ctx)
|
|
115
|
+
elif action == "set":
|
|
116
|
+
return await self._handle_set(key, value, scope, path, tool_ctx)
|
|
117
|
+
elif action == "list":
|
|
118
|
+
return await self._handle_list(scope, path, tool_ctx)
|
|
119
|
+
elif action == "toggle":
|
|
120
|
+
return await self._handle_toggle(key, scope, path, tool_ctx)
|
|
121
|
+
else:
|
|
122
|
+
return f"Error: Unknown action '{action}'. Valid actions: get, set, list, toggle"
|
|
123
|
+
|
|
124
|
+
async def _handle_get(self, key: Optional[str], scope: str, path: Optional[str], tool_ctx) -> str:
|
|
125
|
+
"""Get configuration value."""
|
|
126
|
+
if not key:
|
|
127
|
+
return "Error: key required for get action"
|
|
128
|
+
|
|
129
|
+
# Handle index scope
|
|
130
|
+
if key == "index.scope":
|
|
131
|
+
current_scope = self.index_config.get_scope(path)
|
|
132
|
+
return f"index.scope={current_scope.value}"
|
|
133
|
+
|
|
134
|
+
# Handle tool-specific settings
|
|
135
|
+
if "." in key:
|
|
136
|
+
tool, setting = key.split(".", 1)
|
|
137
|
+
if setting == "enabled":
|
|
138
|
+
enabled = self.index_config.is_indexing_enabled(tool)
|
|
139
|
+
return f"{key}={enabled}"
|
|
140
|
+
|
|
141
|
+
return f"Unknown key: {key}"
|
|
142
|
+
|
|
143
|
+
async def _handle_set(self, key: Optional[str], value: Optional[str], scope: str, path: Optional[str], tool_ctx) -> str:
|
|
144
|
+
"""Set configuration value."""
|
|
145
|
+
if not key:
|
|
146
|
+
return "Error: key required for set action"
|
|
147
|
+
if not value:
|
|
148
|
+
return "Error: value required for set action"
|
|
149
|
+
|
|
150
|
+
# Handle index scope
|
|
151
|
+
if key == "index.scope":
|
|
152
|
+
try:
|
|
153
|
+
new_scope = IndexScope(value)
|
|
154
|
+
self.index_config.set_scope(new_scope, path if scope == "local" else None)
|
|
155
|
+
return f"Set {key}={value} ({'project' if path else 'global'})"
|
|
156
|
+
except ValueError:
|
|
157
|
+
return f"Error: Invalid scope value '{value}'. Valid: project, global, auto"
|
|
158
|
+
|
|
159
|
+
# Handle tool-specific settings
|
|
160
|
+
if "." in key:
|
|
161
|
+
tool, setting = key.split(".", 1)
|
|
162
|
+
if setting == "enabled":
|
|
163
|
+
enabled = value.lower() in ["true", "yes", "1", "on"]
|
|
164
|
+
self.index_config.set_indexing_enabled(tool, enabled)
|
|
165
|
+
return f"Set {key}={enabled}"
|
|
166
|
+
|
|
167
|
+
return f"Unknown key: {key}"
|
|
168
|
+
|
|
169
|
+
async def _handle_list(self, scope: str, path: Optional[str], tool_ctx) -> str:
|
|
170
|
+
"""List all configuration."""
|
|
171
|
+
status = self.index_config.get_status()
|
|
172
|
+
|
|
173
|
+
output = ["=== Configuration ==="]
|
|
174
|
+
output.append(f"\nDefault scope: {status['default_scope']}")
|
|
175
|
+
|
|
176
|
+
if path:
|
|
177
|
+
current_scope = self.index_config.get_scope(path)
|
|
178
|
+
output.append(f"Current path scope: {current_scope.value}")
|
|
179
|
+
|
|
180
|
+
output.append(f"\nProjects with custom config: {status['project_count']}")
|
|
181
|
+
|
|
182
|
+
output.append("\nTool settings:")
|
|
183
|
+
for tool, settings in status["tools"].items():
|
|
184
|
+
output.append(f" {tool}:")
|
|
185
|
+
output.append(f" enabled: {settings['enabled']}")
|
|
186
|
+
output.append(f" per_project: {settings['per_project']}")
|
|
187
|
+
|
|
188
|
+
return "\n".join(output)
|
|
189
|
+
|
|
190
|
+
async def _handle_toggle(self, key: Optional[str], scope: str, path: Optional[str], tool_ctx) -> str:
|
|
191
|
+
"""Toggle configuration value."""
|
|
192
|
+
if not key:
|
|
193
|
+
return "Error: key required for toggle action"
|
|
194
|
+
|
|
195
|
+
# Handle index scope toggle
|
|
196
|
+
if key == "index.scope":
|
|
197
|
+
new_scope = self.index_config.toggle_scope(path if scope == "local" else None)
|
|
198
|
+
return f"Toggled index.scope to {new_scope.value}"
|
|
199
|
+
|
|
200
|
+
# Handle tool enable/disable toggle
|
|
201
|
+
if "." in key:
|
|
202
|
+
tool, setting = key.split(".", 1)
|
|
203
|
+
if setting == "enabled":
|
|
204
|
+
current = self.index_config.is_indexing_enabled(tool)
|
|
205
|
+
self.index_config.set_indexing_enabled(tool, not current)
|
|
206
|
+
return f"Toggled {key} to {not current}"
|
|
207
|
+
|
|
208
|
+
return f"Cannot toggle key: {key}"
|
|
209
|
+
|
|
210
|
+
def register(self, mcp_server) -> None:
|
|
211
|
+
"""Register this tool with the MCP server."""
|
|
212
|
+
pass
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Index configuration for per-project vs global indexing.
|
|
2
|
+
|
|
3
|
+
This module manages indexing configuration for different scopes.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, Any, Optional
|
|
9
|
+
from enum import Enum
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class IndexScope(Enum):
|
|
13
|
+
"""Indexing scope options."""
|
|
14
|
+
PROJECT = "project" # Per-project indexing
|
|
15
|
+
GLOBAL = "global" # Global indexing
|
|
16
|
+
AUTO = "auto" # Auto-detect based on git root
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class IndexConfig:
|
|
20
|
+
"""Manages indexing configuration."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, config_dir: Optional[Path] = None):
|
|
23
|
+
"""Initialize index configuration."""
|
|
24
|
+
self.config_dir = config_dir or Path.home() / ".hanzo" / "mcp"
|
|
25
|
+
self.config_file = self.config_dir / "index_config.json"
|
|
26
|
+
self._config = self._load_config()
|
|
27
|
+
|
|
28
|
+
def _load_config(self) -> Dict[str, Any]:
|
|
29
|
+
"""Load configuration from disk."""
|
|
30
|
+
if self.config_file.exists():
|
|
31
|
+
try:
|
|
32
|
+
with open(self.config_file, "r") as f:
|
|
33
|
+
return json.load(f)
|
|
34
|
+
except:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
# Default configuration
|
|
38
|
+
return {
|
|
39
|
+
"default_scope": IndexScope.AUTO.value,
|
|
40
|
+
"project_configs": {},
|
|
41
|
+
"global_index_paths": [],
|
|
42
|
+
"index_settings": {
|
|
43
|
+
"vector": {
|
|
44
|
+
"enabled": True,
|
|
45
|
+
"auto_index": True,
|
|
46
|
+
"include_git_history": True,
|
|
47
|
+
},
|
|
48
|
+
"symbols": {
|
|
49
|
+
"enabled": True,
|
|
50
|
+
"auto_index": False,
|
|
51
|
+
},
|
|
52
|
+
"sql": {
|
|
53
|
+
"enabled": True,
|
|
54
|
+
"per_project": True,
|
|
55
|
+
},
|
|
56
|
+
"graph": {
|
|
57
|
+
"enabled": True,
|
|
58
|
+
"per_project": True,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
def save_config(self) -> None:
|
|
64
|
+
"""Save configuration to disk."""
|
|
65
|
+
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
66
|
+
with open(self.config_file, "w") as f:
|
|
67
|
+
json.dump(self._config, f, indent=2)
|
|
68
|
+
|
|
69
|
+
def get_scope(self, path: Optional[str] = None) -> IndexScope:
|
|
70
|
+
"""Get indexing scope for a path."""
|
|
71
|
+
if not path:
|
|
72
|
+
return IndexScope(self._config["default_scope"])
|
|
73
|
+
|
|
74
|
+
# Check project-specific config
|
|
75
|
+
project_root = self._find_project_root(path)
|
|
76
|
+
if project_root:
|
|
77
|
+
project_config = self._config["project_configs"].get(str(project_root))
|
|
78
|
+
if project_config and "scope" in project_config:
|
|
79
|
+
return IndexScope(project_config["scope"])
|
|
80
|
+
|
|
81
|
+
# Use default
|
|
82
|
+
scope = IndexScope(self._config["default_scope"])
|
|
83
|
+
|
|
84
|
+
# Handle auto mode
|
|
85
|
+
if scope == IndexScope.AUTO:
|
|
86
|
+
if project_root:
|
|
87
|
+
return IndexScope.PROJECT
|
|
88
|
+
else:
|
|
89
|
+
return IndexScope.GLOBAL
|
|
90
|
+
|
|
91
|
+
return scope
|
|
92
|
+
|
|
93
|
+
def set_scope(self, scope: IndexScope, path: Optional[str] = None) -> None:
|
|
94
|
+
"""Set indexing scope."""
|
|
95
|
+
if path:
|
|
96
|
+
# Set for specific project
|
|
97
|
+
project_root = self._find_project_root(path)
|
|
98
|
+
if project_root:
|
|
99
|
+
if str(project_root) not in self._config["project_configs"]:
|
|
100
|
+
self._config["project_configs"][str(project_root)] = {}
|
|
101
|
+
self._config["project_configs"][str(project_root)]["scope"] = scope.value
|
|
102
|
+
else:
|
|
103
|
+
# Set global default
|
|
104
|
+
self._config["default_scope"] = scope.value
|
|
105
|
+
|
|
106
|
+
self.save_config()
|
|
107
|
+
|
|
108
|
+
def get_index_path(self, tool: str, path: Optional[str] = None) -> Path:
|
|
109
|
+
"""Get index path for a tool and location."""
|
|
110
|
+
scope = self.get_scope(path)
|
|
111
|
+
|
|
112
|
+
if scope == IndexScope.PROJECT and path:
|
|
113
|
+
project_root = self._find_project_root(path)
|
|
114
|
+
if project_root:
|
|
115
|
+
return Path(project_root) / ".hanzo" / "index" / tool
|
|
116
|
+
|
|
117
|
+
# Global index
|
|
118
|
+
return self.config_dir / "index" / tool
|
|
119
|
+
|
|
120
|
+
def is_indexing_enabled(self, tool: str) -> bool:
|
|
121
|
+
"""Check if indexing is enabled for a tool."""
|
|
122
|
+
return self._config["index_settings"].get(tool, {}).get("enabled", True)
|
|
123
|
+
|
|
124
|
+
def set_indexing_enabled(self, tool: str, enabled: bool) -> None:
|
|
125
|
+
"""Enable/disable indexing for a tool."""
|
|
126
|
+
if tool not in self._config["index_settings"]:
|
|
127
|
+
self._config["index_settings"][tool] = {}
|
|
128
|
+
self._config["index_settings"][tool]["enabled"] = enabled
|
|
129
|
+
self.save_config()
|
|
130
|
+
|
|
131
|
+
def toggle_scope(self, path: Optional[str] = None) -> IndexScope:
|
|
132
|
+
"""Toggle between project and global scope."""
|
|
133
|
+
current = self.get_scope(path)
|
|
134
|
+
|
|
135
|
+
if current == IndexScope.PROJECT:
|
|
136
|
+
new_scope = IndexScope.GLOBAL
|
|
137
|
+
elif current == IndexScope.GLOBAL:
|
|
138
|
+
new_scope = IndexScope.PROJECT
|
|
139
|
+
else: # AUTO
|
|
140
|
+
# Determine what auto resolves to and toggle
|
|
141
|
+
if path and self._find_project_root(path):
|
|
142
|
+
new_scope = IndexScope.GLOBAL
|
|
143
|
+
else:
|
|
144
|
+
new_scope = IndexScope.PROJECT
|
|
145
|
+
|
|
146
|
+
self.set_scope(new_scope, path)
|
|
147
|
+
return new_scope
|
|
148
|
+
|
|
149
|
+
def _find_project_root(self, path: str) -> Optional[Path]:
|
|
150
|
+
"""Find project root (git root or similar)."""
|
|
151
|
+
current = Path(path).resolve()
|
|
152
|
+
|
|
153
|
+
# Walk up looking for markers
|
|
154
|
+
markers = [".git", ".hg", "pyproject.toml", "package.json", "Cargo.toml"]
|
|
155
|
+
|
|
156
|
+
while current != current.parent:
|
|
157
|
+
for marker in markers:
|
|
158
|
+
if (current / marker).exists():
|
|
159
|
+
return current
|
|
160
|
+
current = current.parent
|
|
161
|
+
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
def get_status(self) -> Dict[str, Any]:
|
|
165
|
+
"""Get current configuration status."""
|
|
166
|
+
return {
|
|
167
|
+
"default_scope": self._config["default_scope"],
|
|
168
|
+
"project_count": len(self._config["project_configs"]),
|
|
169
|
+
"tools": {
|
|
170
|
+
tool: {
|
|
171
|
+
"enabled": settings.get("enabled", True),
|
|
172
|
+
"per_project": settings.get("per_project", True),
|
|
173
|
+
}
|
|
174
|
+
for tool, settings in self._config["index_settings"].items()
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Tool for managing development tool palettes."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, override
|
|
4
|
+
|
|
5
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
6
|
+
|
|
7
|
+
from hanzo_mcp.tools.common.base import BaseTool
|
|
8
|
+
from hanzo_mcp.tools.common.palette import PaletteRegistry, register_default_palettes
|
|
9
|
+
from mcp.server import FastMCP
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PaletteTool(BaseTool):
|
|
13
|
+
"""Tool for managing tool palettes."""
|
|
14
|
+
|
|
15
|
+
name = "palette"
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
"""Initialize the palette tool."""
|
|
19
|
+
super().__init__()
|
|
20
|
+
# Register default palettes on initialization
|
|
21
|
+
register_default_palettes()
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
@override
|
|
25
|
+
def description(self) -> str:
|
|
26
|
+
"""Get the tool description."""
|
|
27
|
+
return """Manage tool palettes. Actions: list (default), activate, show, current.
|
|
28
|
+
|
|
29
|
+
Usage:
|
|
30
|
+
palette
|
|
31
|
+
palette --action list
|
|
32
|
+
palette --action activate python
|
|
33
|
+
palette --action show javascript
|
|
34
|
+
palette --action current"""
|
|
35
|
+
|
|
36
|
+
@override
|
|
37
|
+
async def run(
|
|
38
|
+
self,
|
|
39
|
+
ctx: MCPContext,
|
|
40
|
+
action: str = "list",
|
|
41
|
+
name: Optional[str] = None,
|
|
42
|
+
) -> str:
|
|
43
|
+
"""Manage tool palettes.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
ctx: MCP context
|
|
47
|
+
action: Action to perform (list, activate, show, current)
|
|
48
|
+
name: Palette name (for activate/show actions)
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Action result
|
|
52
|
+
"""
|
|
53
|
+
if action == "list":
|
|
54
|
+
palettes = PaletteRegistry.list()
|
|
55
|
+
if not palettes:
|
|
56
|
+
return "No palettes registered"
|
|
57
|
+
|
|
58
|
+
output = ["Available tool palettes:"]
|
|
59
|
+
active = PaletteRegistry.get_active()
|
|
60
|
+
|
|
61
|
+
for palette in sorted(palettes, key=lambda p: p.name):
|
|
62
|
+
marker = " (active)" if active and active.name == palette.name else ""
|
|
63
|
+
author = f" by {palette.author}" if palette.author else ""
|
|
64
|
+
output.append(f"\n{palette.name}{marker}:")
|
|
65
|
+
output.append(f" {palette.description}{author}")
|
|
66
|
+
output.append(f" Tools: {len(palette.tools)} enabled")
|
|
67
|
+
|
|
68
|
+
return "\n".join(output)
|
|
69
|
+
|
|
70
|
+
elif action == "activate":
|
|
71
|
+
if not name:
|
|
72
|
+
return "Error: Palette name required for activate action"
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
PaletteRegistry.set_active(name)
|
|
76
|
+
palette = PaletteRegistry.get(name)
|
|
77
|
+
|
|
78
|
+
output = [f"Activated palette: {palette.name}"]
|
|
79
|
+
if palette.author:
|
|
80
|
+
output.append(f"Author: {palette.author}")
|
|
81
|
+
output.append(f"Description: {palette.description}")
|
|
82
|
+
output.append(f"\nEnabled tools ({len(palette.tools)}):")
|
|
83
|
+
|
|
84
|
+
# Group tools by category
|
|
85
|
+
core_tools = []
|
|
86
|
+
package_tools = []
|
|
87
|
+
other_tools = []
|
|
88
|
+
|
|
89
|
+
for tool in sorted(palette.tools):
|
|
90
|
+
if tool in ["read", "write", "edit", "grep", "tree", "find", "bash"]:
|
|
91
|
+
core_tools.append(tool)
|
|
92
|
+
elif tool in ["npx", "uvx", "pip", "cargo", "gem"]:
|
|
93
|
+
package_tools.append(tool)
|
|
94
|
+
else:
|
|
95
|
+
other_tools.append(tool)
|
|
96
|
+
|
|
97
|
+
if core_tools:
|
|
98
|
+
output.append(f" Core: {', '.join(core_tools)}")
|
|
99
|
+
if package_tools:
|
|
100
|
+
output.append(f" Package managers: {', '.join(package_tools)}")
|
|
101
|
+
if other_tools:
|
|
102
|
+
output.append(f" Specialized: {', '.join(other_tools)}")
|
|
103
|
+
|
|
104
|
+
if palette.environment:
|
|
105
|
+
output.append("\nEnvironment variables:")
|
|
106
|
+
for key, value in palette.environment.items():
|
|
107
|
+
output.append(f" {key}={value}")
|
|
108
|
+
|
|
109
|
+
output.append("\nNote: Restart MCP session for changes to take full effect")
|
|
110
|
+
|
|
111
|
+
return "\n".join(output)
|
|
112
|
+
|
|
113
|
+
except ValueError as e:
|
|
114
|
+
return str(e)
|
|
115
|
+
|
|
116
|
+
elif action == "show":
|
|
117
|
+
if not name:
|
|
118
|
+
return "Error: Palette name required for show action"
|
|
119
|
+
|
|
120
|
+
palette = PaletteRegistry.get(name)
|
|
121
|
+
if not palette:
|
|
122
|
+
return f"Palette '{name}' not found"
|
|
123
|
+
|
|
124
|
+
output = [f"Palette: {palette.name}"]
|
|
125
|
+
if palette.author:
|
|
126
|
+
output.append(f"Author: {palette.author}")
|
|
127
|
+
output.append(f"Description: {palette.description}")
|
|
128
|
+
output.append(f"\nTools ({len(palette.tools)}):")
|
|
129
|
+
|
|
130
|
+
for tool in sorted(palette.tools):
|
|
131
|
+
output.append(f" - {tool}")
|
|
132
|
+
|
|
133
|
+
if palette.environment:
|
|
134
|
+
output.append("\nEnvironment:")
|
|
135
|
+
for key, value in palette.environment.items():
|
|
136
|
+
output.append(f" {key}={value}")
|
|
137
|
+
|
|
138
|
+
return "\n".join(output)
|
|
139
|
+
|
|
140
|
+
elif action == "current":
|
|
141
|
+
active = PaletteRegistry.get_active()
|
|
142
|
+
if not active:
|
|
143
|
+
return "No palette currently active\nUse 'palette --action activate <name>' to activate one"
|
|
144
|
+
|
|
145
|
+
output = [f"Current palette: {active.name}"]
|
|
146
|
+
if active.author:
|
|
147
|
+
output.append(f"Author: {active.author}")
|
|
148
|
+
output.append(f"Description: {active.description}")
|
|
149
|
+
output.append(f"Enabled tools: {len(active.tools)}")
|
|
150
|
+
|
|
151
|
+
return "\n".join(output)
|
|
152
|
+
|
|
153
|
+
else:
|
|
154
|
+
return f"Unknown action: {action}. Use 'list', 'activate', 'show', or 'current'"
|
|
155
|
+
|
|
156
|
+
def register(self, server: FastMCP) -> None:
|
|
157
|
+
"""Register the tool with the MCP server."""
|
|
158
|
+
server.tool(name=self.name, description=self.description)(self.call)
|
|
159
|
+
|
|
160
|
+
async def call(self, **kwargs) -> str:
|
|
161
|
+
"""Call the tool with arguments."""
|
|
162
|
+
return await self.run(None, **kwargs)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# Create tool instance
|
|
166
|
+
palette_tool = PaletteTool()
|
|
@@ -6,7 +6,7 @@ and graph databases in projects.
|
|
|
6
6
|
|
|
7
7
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
8
8
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
9
|
-
from
|
|
9
|
+
from mcp.server import FastMCP
|
|
10
10
|
|
|
11
11
|
# Import database tools
|
|
12
12
|
from .sql_query import SqlQueryTool
|