hanzo-mcp 0.5.1__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 +168 -6
- 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 +9 -4
- 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 +261 -0
- hanzo_mcp/tools/common/thinking_tool.py +3 -5
- hanzo_mcp/tools/common/tool_disable.py +144 -0
- hanzo_mcp/tools/common/tool_enable.py +182 -0
- hanzo_mcp/tools/common/tool_list.py +260 -0
- 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 +71 -0
- hanzo_mcp/tools/database/database_manager.py +246 -0
- hanzo_mcp/tools/database/graph.py +482 -0
- hanzo_mcp/tools/database/graph_add.py +257 -0
- hanzo_mcp/tools/database/graph_query.py +536 -0
- hanzo_mcp/tools/database/graph_remove.py +267 -0
- hanzo_mcp/tools/database/graph_search.py +348 -0
- hanzo_mcp/tools/database/graph_stats.py +345 -0
- hanzo_mcp/tools/database/sql.py +411 -0
- hanzo_mcp/tools/database/sql_query.py +229 -0
- hanzo_mcp/tools/database/sql_search.py +296 -0
- hanzo_mcp/tools/database/sql_stats.py +254 -0
- hanzo_mcp/tools/editor/__init__.py +11 -0
- hanzo_mcp/tools/editor/neovim_command.py +272 -0
- hanzo_mcp/tools/editor/neovim_edit.py +290 -0
- hanzo_mcp/tools/editor/neovim_session.py +356 -0
- hanzo_mcp/tools/filesystem/__init__.py +52 -13
- hanzo_mcp/tools/filesystem/base.py +1 -1
- hanzo_mcp/tools/filesystem/batch_search.py +812 -0
- 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 +348 -0
- hanzo_mcp/tools/filesystem/git_search.py +505 -0
- 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 +465 -443
- 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 +31 -0
- hanzo_mcp/tools/llm/consensus_tool.py +351 -0
- hanzo_mcp/tools/llm/llm_manage.py +413 -0
- hanzo_mcp/tools/llm/llm_tool.py +346 -0
- hanzo_mcp/tools/llm/llm_unified.py +851 -0
- hanzo_mcp/tools/llm/provider_tools.py +412 -0
- hanzo_mcp/tools/mcp/__init__.py +15 -0
- hanzo_mcp/tools/mcp/mcp_add.py +263 -0
- hanzo_mcp/tools/mcp/mcp_remove.py +127 -0
- hanzo_mcp/tools/mcp/mcp_stats.py +165 -0
- hanzo_mcp/tools/mcp/mcp_unified.py +503 -0
- hanzo_mcp/tools/shell/__init__.py +21 -23
- 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 +265 -0
- hanzo_mcp/tools/shell/npx.py +194 -0
- hanzo_mcp/tools/shell/npx_background.py +254 -0
- hanzo_mcp/tools/shell/npx_unified.py +101 -0
- hanzo_mcp/tools/shell/open.py +107 -0
- hanzo_mcp/tools/shell/pkill.py +262 -0
- hanzo_mcp/tools/shell/process_unified.py +131 -0
- hanzo_mcp/tools/shell/processes.py +279 -0
- hanzo_mcp/tools/shell/run_background.py +326 -0
- 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 +187 -0
- hanzo_mcp/tools/shell/uvx_background.py +249 -0
- 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 +6 -1
- hanzo_mcp/tools/vector/git_ingester.py +3 -0
- hanzo_mcp/tools/vector/index_tool.py +358 -0
- hanzo_mcp/tools/vector/infinity_store.py +98 -0
- 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 +12 -7
- 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.1.dist-info/METADATA +0 -276
- hanzo_mcp-0.5.1.dist-info/RECORD +0 -68
- hanzo_mcp-0.5.1.dist-info/entry_points.txt +0 -2
- {hanzo_mcp-0.5.1.dist-info → hanzo_mcp-0.6.1.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.5.1.dist-info → hanzo_mcp-0.6.1.dist-info}/licenses/LICENSE +0 -0
- {hanzo_mcp-0.5.1.dist-info → hanzo_mcp-0.6.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""List all available tools and their status."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated, TypedDict, Unpack, final, override, Optional
|
|
4
|
+
|
|
5
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
from hanzo_mcp.tools.common.base import BaseTool
|
|
9
|
+
from hanzo_mcp.tools.common.context import create_tool_context
|
|
10
|
+
from hanzo_mcp.tools.common.tool_enable import ToolEnableTool
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
ShowDisabled = Annotated[
|
|
14
|
+
bool,
|
|
15
|
+
Field(
|
|
16
|
+
description="Show only disabled tools",
|
|
17
|
+
default=False,
|
|
18
|
+
),
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
ShowEnabled = Annotated[
|
|
22
|
+
bool,
|
|
23
|
+
Field(
|
|
24
|
+
description="Show only enabled tools",
|
|
25
|
+
default=False,
|
|
26
|
+
),
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
Category = Annotated[
|
|
30
|
+
Optional[str],
|
|
31
|
+
Field(
|
|
32
|
+
description="Filter by category (filesystem, shell, database, etc.)",
|
|
33
|
+
default=None,
|
|
34
|
+
),
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ToolListParams(TypedDict, total=False):
|
|
39
|
+
"""Parameters for tool list."""
|
|
40
|
+
|
|
41
|
+
show_disabled: bool
|
|
42
|
+
show_enabled: bool
|
|
43
|
+
category: Optional[str]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@final
|
|
47
|
+
class ToolListTool(BaseTool):
|
|
48
|
+
"""Tool for listing all available tools and their status."""
|
|
49
|
+
|
|
50
|
+
# Tool information organized by category
|
|
51
|
+
TOOL_INFO = {
|
|
52
|
+
"filesystem": [
|
|
53
|
+
("read", "Read contents of files"),
|
|
54
|
+
("write", "Write contents to files"),
|
|
55
|
+
("edit", "Edit specific parts of files"),
|
|
56
|
+
("multi_edit", "Make multiple edits to a file"),
|
|
57
|
+
("tree", "Directory tree visualization (Unix-style)"),
|
|
58
|
+
("find", "Find text in files (rg/ag/ack/grep)"),
|
|
59
|
+
("symbols", "Code symbols search with tree-sitter"),
|
|
60
|
+
("search", "Unified search (parallel grep/symbols/vector/git)"),
|
|
61
|
+
("git_search", "Search git history"),
|
|
62
|
+
("glob", "Find files by name pattern"),
|
|
63
|
+
("content_replace", "Replace content across files"),
|
|
64
|
+
],
|
|
65
|
+
"shell": [
|
|
66
|
+
("run_command", "Execute shell commands (--background option)"),
|
|
67
|
+
("processes", "List background processes"),
|
|
68
|
+
("pkill", "Kill background processes"),
|
|
69
|
+
("logs", "View process logs"),
|
|
70
|
+
("uvx", "Run Python packages (--background option)"),
|
|
71
|
+
("npx", "Run Node.js packages (--background option)"),
|
|
72
|
+
],
|
|
73
|
+
"database": [
|
|
74
|
+
("sql", "SQLite operations (query/search/schema/stats)"),
|
|
75
|
+
("graph", "Graph database (query/add/remove/search/stats)"),
|
|
76
|
+
("vector", "Semantic search (search/index/stats/clear)"),
|
|
77
|
+
],
|
|
78
|
+
"ai": [
|
|
79
|
+
("llm", "LLM interface (query/consensus/list/models/enable/disable)"),
|
|
80
|
+
("agent", "AI agents (run/start/call/stop/list with A2A support)"),
|
|
81
|
+
("mcp", "MCP servers (list/add/remove/enable/disable/restart)"),
|
|
82
|
+
],
|
|
83
|
+
"config": [
|
|
84
|
+
("config", "Git-style configuration (get/set/list/toggle)"),
|
|
85
|
+
("tool_enable", "Enable tools"),
|
|
86
|
+
("tool_disable", "Disable tools"),
|
|
87
|
+
("tool_list", "List all tools (this tool)"),
|
|
88
|
+
],
|
|
89
|
+
"productivity": [
|
|
90
|
+
("todo", "Todo management (list/add/update/remove/clear)"),
|
|
91
|
+
("jupyter", "Jupyter notebooks (read/edit/create/delete/execute)"),
|
|
92
|
+
("think", "Structured thinking space"),
|
|
93
|
+
],
|
|
94
|
+
"system": [
|
|
95
|
+
("stats", "System and resource statistics"),
|
|
96
|
+
("batch", "Run multiple tools in parallel"),
|
|
97
|
+
],
|
|
98
|
+
"legacy": [
|
|
99
|
+
("directory_tree", "Legacy: Use 'tree' instead"),
|
|
100
|
+
("grep", "Legacy: Use 'find' instead"),
|
|
101
|
+
("grep_ast", "Legacy: Use 'symbols' instead"),
|
|
102
|
+
("batch_search", "Legacy: Use 'search' instead"),
|
|
103
|
+
("find_files", "Legacy: Use 'glob' instead"),
|
|
104
|
+
("run_background", "Legacy: Use 'run_command --background'"),
|
|
105
|
+
("uvx_background", "Legacy: Use 'uvx --background'"),
|
|
106
|
+
("npx_background", "Legacy: Use 'npx --background'"),
|
|
107
|
+
("sql_query", "Legacy: Use 'sql' instead"),
|
|
108
|
+
("sql_search", "Legacy: Use 'sql --action search'"),
|
|
109
|
+
("sql_stats", "Legacy: Use 'sql --action stats'"),
|
|
110
|
+
("graph_add", "Legacy: Use 'graph --action add'"),
|
|
111
|
+
("graph_remove", "Legacy: Use 'graph --action remove'"),
|
|
112
|
+
("graph_query", "Legacy: Use 'graph' instead"),
|
|
113
|
+
("graph_search", "Legacy: Use 'graph --action search'"),
|
|
114
|
+
("graph_stats", "Legacy: Use 'graph --action stats'"),
|
|
115
|
+
("vector_index", "Legacy: Use 'vector --action index'"),
|
|
116
|
+
("vector_search", "Legacy: Use 'vector' instead"),
|
|
117
|
+
("dispatch_agent", "Legacy: Use 'agent' instead"),
|
|
118
|
+
("todo_read", "Legacy: Use 'todo' instead"),
|
|
119
|
+
("todo_write", "Legacy: Use 'todo --action add/update'"),
|
|
120
|
+
("notebook_read", "Legacy: Use 'jupyter' instead"),
|
|
121
|
+
("notebook_edit", "Legacy: Use 'jupyter --action edit'"),
|
|
122
|
+
],
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
def __init__(self):
|
|
126
|
+
"""Initialize the tool list tool."""
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
@override
|
|
131
|
+
def name(self) -> str:
|
|
132
|
+
"""Get the tool name."""
|
|
133
|
+
return "tool_list"
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
@override
|
|
137
|
+
def description(self) -> str:
|
|
138
|
+
"""Get the tool description."""
|
|
139
|
+
return """List all available tools and their current status.
|
|
140
|
+
|
|
141
|
+
Shows:
|
|
142
|
+
- Tool names and descriptions
|
|
143
|
+
- Whether each tool is enabled or disabled
|
|
144
|
+
- Tools organized by category
|
|
145
|
+
|
|
146
|
+
Examples:
|
|
147
|
+
- tool_list # Show all tools
|
|
148
|
+
- tool_list --show-disabled # Show only disabled tools
|
|
149
|
+
- tool_list --show-enabled # Show only enabled tools
|
|
150
|
+
- tool_list --category shell # Show only shell tools
|
|
151
|
+
|
|
152
|
+
Use 'tool_enable' and 'tool_disable' to change tool status.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
@override
|
|
156
|
+
async def call(
|
|
157
|
+
self,
|
|
158
|
+
ctx: MCPContext,
|
|
159
|
+
**params: Unpack[ToolListParams],
|
|
160
|
+
) -> str:
|
|
161
|
+
"""List all tools.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
ctx: MCP context
|
|
165
|
+
**params: Tool parameters
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
List of tools and their status
|
|
169
|
+
"""
|
|
170
|
+
tool_ctx = create_tool_context(ctx)
|
|
171
|
+
await tool_ctx.set_tool_info(self.name)
|
|
172
|
+
|
|
173
|
+
# Extract parameters
|
|
174
|
+
show_disabled = params.get("show_disabled", False)
|
|
175
|
+
show_enabled = params.get("show_enabled", False)
|
|
176
|
+
category_filter = params.get("category")
|
|
177
|
+
|
|
178
|
+
# Get all tool states
|
|
179
|
+
all_states = ToolEnableTool.get_all_states()
|
|
180
|
+
|
|
181
|
+
output = []
|
|
182
|
+
|
|
183
|
+
# Header
|
|
184
|
+
if show_disabled:
|
|
185
|
+
output.append("=== Disabled Tools ===")
|
|
186
|
+
elif show_enabled:
|
|
187
|
+
output.append("=== Enabled Tools ===")
|
|
188
|
+
else:
|
|
189
|
+
output.append("=== All Available Tools ===")
|
|
190
|
+
|
|
191
|
+
if category_filter:
|
|
192
|
+
output.append(f"Category: {category_filter}")
|
|
193
|
+
|
|
194
|
+
output.append("")
|
|
195
|
+
|
|
196
|
+
# Count statistics
|
|
197
|
+
total_tools = 0
|
|
198
|
+
disabled_count = 0
|
|
199
|
+
shown_count = 0
|
|
200
|
+
|
|
201
|
+
# Iterate through categories
|
|
202
|
+
categories = [category_filter] if category_filter and category_filter in self.TOOL_INFO else self.TOOL_INFO.keys()
|
|
203
|
+
|
|
204
|
+
for category in categories:
|
|
205
|
+
if category not in self.TOOL_INFO:
|
|
206
|
+
continue
|
|
207
|
+
|
|
208
|
+
category_tools = self.TOOL_INFO[category]
|
|
209
|
+
category_shown = []
|
|
210
|
+
|
|
211
|
+
for tool_name, description in category_tools:
|
|
212
|
+
total_tools += 1
|
|
213
|
+
is_enabled = ToolEnableTool.is_tool_enabled(tool_name)
|
|
214
|
+
|
|
215
|
+
if not is_enabled:
|
|
216
|
+
disabled_count += 1
|
|
217
|
+
|
|
218
|
+
# Apply filters
|
|
219
|
+
if show_disabled and is_enabled:
|
|
220
|
+
continue
|
|
221
|
+
if show_enabled and not is_enabled:
|
|
222
|
+
continue
|
|
223
|
+
|
|
224
|
+
status = "✅" if is_enabled else "❌"
|
|
225
|
+
category_shown.append((tool_name, description, status))
|
|
226
|
+
shown_count += 1
|
|
227
|
+
|
|
228
|
+
# Show category if it has tools
|
|
229
|
+
if category_shown:
|
|
230
|
+
output.append(f"=== {category.title()} Tools ===")
|
|
231
|
+
|
|
232
|
+
# Find max tool name length for alignment
|
|
233
|
+
max_name_len = max(len(name) for name, _, _ in category_shown)
|
|
234
|
+
|
|
235
|
+
for tool_name, description, status in category_shown:
|
|
236
|
+
output.append(f"{status} {tool_name.ljust(max_name_len)} - {description}")
|
|
237
|
+
|
|
238
|
+
output.append("")
|
|
239
|
+
|
|
240
|
+
# Summary
|
|
241
|
+
if not show_disabled and not show_enabled:
|
|
242
|
+
output.append("=== Summary ===")
|
|
243
|
+
output.append(f"Total tools: {total_tools}")
|
|
244
|
+
output.append(f"Enabled: {total_tools - disabled_count}")
|
|
245
|
+
output.append(f"Disabled: {disabled_count}")
|
|
246
|
+
else:
|
|
247
|
+
output.append(f"Showing {shown_count} tool(s)")
|
|
248
|
+
|
|
249
|
+
if disabled_count > 0 and not show_disabled:
|
|
250
|
+
output.append("\nUse 'tool_list --show-disabled' to see disabled tools.")
|
|
251
|
+
output.append("Use 'tool_enable --tool <name>' to enable a tool.")
|
|
252
|
+
|
|
253
|
+
if show_disabled:
|
|
254
|
+
output.append("\nUse 'tool_enable --tool <name>' to enable these tools.")
|
|
255
|
+
|
|
256
|
+
return "\n".join(output)
|
|
257
|
+
|
|
258
|
+
def register(self, mcp_server) -> None:
|
|
259
|
+
"""Register this tool with the MCP server."""
|
|
260
|
+
pass
|
|
@@ -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
|
+
}
|