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.
- hanzo_mcp/__init__.py +7 -1
- 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.6.dist-info → hanzo_mcp-0.8.0.dist-info}/METADATA +1 -1
- hanzo_mcp-0.8.0.dist-info/RECORD +185 -0
- hanzo_mcp-0.7.6.dist-info/RECORD +0 -182
- {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/top_level.txt +0 -0
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
"""Enable tools dynamically."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
from typing import Annotated, TypedDict,
|
|
4
|
+
from typing import Unpack, Annotated, TypedDict, final, override
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
8
7
|
from pydantic import Field
|
|
8
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
9
9
|
|
|
10
10
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
11
11
|
from hanzo_mcp.tools.common.context import create_tool_context
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
ToolName = Annotated[
|
|
15
14
|
str,
|
|
16
15
|
Field(
|
|
@@ -38,7 +37,7 @@ class ToolEnableParams(TypedDict, total=False):
|
|
|
38
37
|
@final
|
|
39
38
|
class ToolEnableTool(BaseTool):
|
|
40
39
|
"""Tool for enabling other tools dynamically."""
|
|
41
|
-
|
|
40
|
+
|
|
42
41
|
# Class variable to track enabled/disabled tools
|
|
43
42
|
_tool_states = {}
|
|
44
43
|
_config_file = Path.home() / ".hanzo" / "mcp" / "tool_states.json"
|
|
@@ -55,7 +54,7 @@ class ToolEnableTool(BaseTool):
|
|
|
55
54
|
"""Load tool states from config file."""
|
|
56
55
|
if cls._config_file.exists():
|
|
57
56
|
try:
|
|
58
|
-
with open(cls._config_file,
|
|
57
|
+
with open(cls._config_file, "r") as f:
|
|
59
58
|
cls._tool_states = json.load(f)
|
|
60
59
|
except Exception:
|
|
61
60
|
cls._tool_states = {}
|
|
@@ -67,16 +66,16 @@ class ToolEnableTool(BaseTool):
|
|
|
67
66
|
def _save_states(cls):
|
|
68
67
|
"""Save tool states to config file."""
|
|
69
68
|
cls._config_file.parent.mkdir(parents=True, exist_ok=True)
|
|
70
|
-
with open(cls._config_file,
|
|
69
|
+
with open(cls._config_file, "w") as f:
|
|
71
70
|
json.dump(cls._tool_states, f, indent=2)
|
|
72
71
|
|
|
73
72
|
@classmethod
|
|
74
73
|
def is_tool_enabled(cls, tool_name: str) -> bool:
|
|
75
74
|
"""Check if a tool is enabled.
|
|
76
|
-
|
|
75
|
+
|
|
77
76
|
Args:
|
|
78
77
|
tool_name: Name of the tool
|
|
79
|
-
|
|
78
|
+
|
|
80
79
|
Returns:
|
|
81
80
|
True if enabled (default), False if explicitly disabled
|
|
82
81
|
"""
|
|
@@ -84,7 +83,7 @@ class ToolEnableTool(BaseTool):
|
|
|
84
83
|
if not cls._initialized:
|
|
85
84
|
cls._load_states()
|
|
86
85
|
cls._initialized = True
|
|
87
|
-
|
|
86
|
+
|
|
88
87
|
# Default to enabled if not in states
|
|
89
88
|
return cls._tool_states.get(tool_name, True)
|
|
90
89
|
|
|
@@ -146,13 +145,13 @@ Use 'tool_list' to see all available tools and their status.
|
|
|
146
145
|
|
|
147
146
|
# Check current state
|
|
148
147
|
was_enabled = self.is_tool_enabled(tool_name)
|
|
149
|
-
|
|
148
|
+
|
|
150
149
|
if was_enabled:
|
|
151
150
|
return f"Tool '{tool_name}' is already enabled."
|
|
152
151
|
|
|
153
152
|
# Enable the tool
|
|
154
153
|
self._tool_states[tool_name] = True
|
|
155
|
-
|
|
154
|
+
|
|
156
155
|
# Persist if requested
|
|
157
156
|
if persist:
|
|
158
157
|
self._save_states()
|
|
@@ -165,16 +164,16 @@ Use 'tool_list' to see all available tools and their status.
|
|
|
165
164
|
"",
|
|
166
165
|
"The tool is now available for use.",
|
|
167
166
|
]
|
|
168
|
-
|
|
167
|
+
|
|
169
168
|
if not persist:
|
|
170
169
|
output.append("Note: This change is temporary and will be lost on restart.")
|
|
171
|
-
|
|
170
|
+
|
|
172
171
|
# Count enabled/disabled tools
|
|
173
172
|
disabled_count = sum(1 for enabled in self._tool_states.values() if not enabled)
|
|
174
173
|
if disabled_count > 0:
|
|
175
174
|
output.append(f"\nCurrently disabled tools: {disabled_count}")
|
|
176
175
|
output.append("Use 'tool_list --disabled' to see them.")
|
|
177
|
-
|
|
176
|
+
|
|
178
177
|
return "\n".join(output)
|
|
179
178
|
|
|
180
179
|
def register(self, mcp_server) -> None:
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
"""List all available tools and their status."""
|
|
2
2
|
|
|
3
|
-
from typing import Annotated, TypedDict,
|
|
3
|
+
from typing import Unpack, Optional, Annotated, TypedDict, final, override
|
|
4
4
|
|
|
5
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
6
5
|
from pydantic import Field
|
|
6
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
7
7
|
|
|
8
8
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
9
9
|
from hanzo_mcp.tools.common.context import create_tool_context
|
|
10
10
|
from hanzo_mcp.tools.common.tool_enable import ToolEnableTool
|
|
11
11
|
|
|
12
|
-
|
|
13
12
|
ShowDisabled = Annotated[
|
|
14
13
|
bool,
|
|
15
14
|
Field(
|
|
@@ -46,7 +45,7 @@ class ToolListParams(TypedDict, total=False):
|
|
|
46
45
|
@final
|
|
47
46
|
class ToolListTool(BaseTool):
|
|
48
47
|
"""Tool for listing all available tools and their status."""
|
|
49
|
-
|
|
48
|
+
|
|
50
49
|
# Tool information organized by category
|
|
51
50
|
TOOL_INFO = {
|
|
52
51
|
"filesystem": [
|
|
@@ -80,7 +79,10 @@ class ToolListTool(BaseTool):
|
|
|
80
79
|
("llm", "LLM interface (query/consensus/list/models/enable/disable)"),
|
|
81
80
|
("agent", "AI agents (run/start/call/stop/list with A2A support)"),
|
|
82
81
|
("swarm", "Parallel agent execution across multiple files"),
|
|
83
|
-
(
|
|
82
|
+
(
|
|
83
|
+
"hierarchical_swarm",
|
|
84
|
+
"Hierarchical agent teams with Claude Code integration",
|
|
85
|
+
),
|
|
84
86
|
("mcp", "MCP servers (list/add/remove/enable/disable/restart)"),
|
|
85
87
|
],
|
|
86
88
|
"config": [
|
|
@@ -171,7 +173,7 @@ Use 'tool_enable' and 'tool_disable' to change tool status.
|
|
|
171
173
|
List of tools and their status
|
|
172
174
|
"""
|
|
173
175
|
tool_ctx = create_tool_context(ctx)
|
|
174
|
-
|
|
176
|
+
tool_ctx.set_tool_info(self.name)
|
|
175
177
|
|
|
176
178
|
# Extract parameters
|
|
177
179
|
show_disabled = params.get("show_disabled", False)
|
|
@@ -180,9 +182,9 @@ Use 'tool_enable' and 'tool_disable' to change tool status.
|
|
|
180
182
|
|
|
181
183
|
# Get all tool states
|
|
182
184
|
all_states = ToolEnableTool.get_all_states()
|
|
183
|
-
|
|
185
|
+
|
|
184
186
|
output = []
|
|
185
|
-
|
|
187
|
+
|
|
186
188
|
# Header
|
|
187
189
|
if show_disabled:
|
|
188
190
|
output.append("=== Disabled Tools ===")
|
|
@@ -190,56 +192,62 @@ Use 'tool_enable' and 'tool_disable' to change tool status.
|
|
|
190
192
|
output.append("=== Enabled Tools ===")
|
|
191
193
|
else:
|
|
192
194
|
output.append("=== All Available Tools ===")
|
|
193
|
-
|
|
195
|
+
|
|
194
196
|
if category_filter:
|
|
195
197
|
output.append(f"Category: {category_filter}")
|
|
196
|
-
|
|
198
|
+
|
|
197
199
|
output.append("")
|
|
198
|
-
|
|
200
|
+
|
|
199
201
|
# Count statistics
|
|
200
202
|
total_tools = 0
|
|
201
203
|
disabled_count = 0
|
|
202
204
|
shown_count = 0
|
|
203
|
-
|
|
205
|
+
|
|
204
206
|
# Iterate through categories
|
|
205
|
-
categories =
|
|
206
|
-
|
|
207
|
+
categories = (
|
|
208
|
+
[category_filter]
|
|
209
|
+
if category_filter and category_filter in self.TOOL_INFO
|
|
210
|
+
else self.TOOL_INFO.keys()
|
|
211
|
+
)
|
|
212
|
+
|
|
207
213
|
for category in categories:
|
|
208
214
|
if category not in self.TOOL_INFO:
|
|
209
215
|
continue
|
|
210
|
-
|
|
216
|
+
|
|
211
217
|
category_tools = self.TOOL_INFO[category]
|
|
212
218
|
category_shown = []
|
|
213
|
-
|
|
219
|
+
|
|
214
220
|
for tool_name, description in category_tools:
|
|
215
221
|
total_tools += 1
|
|
216
222
|
is_enabled = ToolEnableTool.is_tool_enabled(tool_name)
|
|
217
|
-
|
|
223
|
+
|
|
218
224
|
if not is_enabled:
|
|
219
225
|
disabled_count += 1
|
|
220
|
-
|
|
226
|
+
|
|
221
227
|
# Apply filters
|
|
222
228
|
if show_disabled and is_enabled:
|
|
223
229
|
continue
|
|
224
230
|
if show_enabled and not is_enabled:
|
|
225
231
|
continue
|
|
226
|
-
|
|
232
|
+
|
|
227
233
|
status = "✅" if is_enabled else "❌"
|
|
228
234
|
category_shown.append((tool_name, description, status))
|
|
229
235
|
shown_count += 1
|
|
230
|
-
|
|
236
|
+
|
|
231
237
|
# Show category if it has tools
|
|
232
238
|
if category_shown:
|
|
233
239
|
output.append(f"=== {category.title()} Tools ===")
|
|
234
|
-
|
|
240
|
+
|
|
235
241
|
# Find max tool name length for alignment
|
|
236
242
|
max_name_len = max(len(name) for name, _, _ in category_shown)
|
|
237
|
-
|
|
243
|
+
|
|
238
244
|
for tool_name, description, status in category_shown:
|
|
239
|
-
output.append(
|
|
240
|
-
|
|
245
|
+
output.append(
|
|
246
|
+
f"{status} {tool_name.ljust(max_name_len)} - {description}"
|
|
247
|
+
)
|
|
248
|
+
|
|
241
249
|
output.append("")
|
|
242
|
-
|
|
250
|
+
|
|
243
251
|
# Summary
|
|
244
252
|
if not show_disabled and not show_enabled:
|
|
245
253
|
output.append("=== Summary ===")
|
|
@@ -248,14 +256,14 @@ Use 'tool_enable' and 'tool_disable' to change tool status.
|
|
|
248
256
|
output.append(f"Disabled: {disabled_count}")
|
|
249
257
|
else:
|
|
250
258
|
output.append(f"Showing {shown_count} tool(s)")
|
|
251
|
-
|
|
259
|
+
|
|
252
260
|
if disabled_count > 0 and not show_disabled:
|
|
253
261
|
output.append("\nUse 'tool_list --show-disabled' to see disabled tools.")
|
|
254
262
|
output.append("Use 'tool_enable --tool <name>' to enable a tool.")
|
|
255
|
-
|
|
263
|
+
|
|
256
264
|
if show_disabled:
|
|
257
265
|
output.append("\nUse 'tool_enable --tool <name>' to enable these tools.")
|
|
258
|
-
|
|
266
|
+
|
|
259
267
|
return "\n".join(output)
|
|
260
268
|
|
|
261
269
|
def register(self, mcp_server) -> None:
|
|
@@ -8,11 +8,11 @@ import tiktoken
|
|
|
8
8
|
|
|
9
9
|
def estimate_tokens(text: str, model: str = "gpt-4") -> int:
|
|
10
10
|
"""Estimate the number of tokens in a text string.
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
Args:
|
|
13
13
|
text: The text to estimate tokens for
|
|
14
14
|
model: The model to use for token estimation (default: gpt-4)
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
Returns:
|
|
17
17
|
Estimated number of tokens
|
|
18
18
|
"""
|
|
@@ -22,80 +22,80 @@ def estimate_tokens(text: str, model: str = "gpt-4") -> int:
|
|
|
22
22
|
except KeyError:
|
|
23
23
|
# Fall back to cl100k_base which is used by newer models
|
|
24
24
|
encoding = tiktoken.get_encoding("cl100k_base")
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
return len(encoding.encode(text))
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def truncate_response(
|
|
30
|
-
response: str,
|
|
30
|
+
response: str,
|
|
31
31
|
max_tokens: int = 20000,
|
|
32
|
-
truncation_message: str = "\n\n[Response truncated due to length. Please use pagination, filtering, or limit parameters to see more.]"
|
|
32
|
+
truncation_message: str = "\n\n[Response truncated due to length. Please use pagination, filtering, or limit parameters to see more.]",
|
|
33
33
|
) -> str:
|
|
34
34
|
"""Truncate a response to fit within token limits.
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
Args:
|
|
37
37
|
response: The response text to truncate
|
|
38
38
|
max_tokens: Maximum number of tokens allowed (default: 20000)
|
|
39
39
|
truncation_message: Message to append when truncating
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
Returns:
|
|
42
42
|
Truncated response if needed, original response otherwise
|
|
43
43
|
"""
|
|
44
44
|
# Quick check - if response is short, no need to count tokens
|
|
45
45
|
if len(response) < max_tokens * 2: # Rough estimate: 1 token ≈ 2-4 chars
|
|
46
46
|
return response
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
# Estimate tokens
|
|
49
49
|
token_count = estimate_tokens(response)
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
# If within limit, return as-is
|
|
52
52
|
if token_count <= max_tokens:
|
|
53
53
|
return response
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
# Need to truncate
|
|
56
56
|
# Binary search to find the right truncation point
|
|
57
57
|
left, right = 0, len(response)
|
|
58
58
|
truncation_msg_tokens = estimate_tokens(truncation_message)
|
|
59
59
|
target_tokens = max_tokens - truncation_msg_tokens
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
while left < right - 1:
|
|
62
62
|
mid = (left + right) // 2
|
|
63
63
|
mid_tokens = estimate_tokens(response[:mid])
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
if mid_tokens <= target_tokens:
|
|
66
66
|
left = mid
|
|
67
67
|
else:
|
|
68
68
|
right = mid
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
# Find a good break point (newline or space)
|
|
71
71
|
truncate_at = left
|
|
72
72
|
for i in range(min(100, left), -1, -1):
|
|
73
|
-
if response[left - i] in
|
|
73
|
+
if response[left - i] in "\n ":
|
|
74
74
|
truncate_at = left - i
|
|
75
75
|
break
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
return response[:truncate_at] + truncation_message
|
|
78
78
|
|
|
79
79
|
|
|
80
80
|
def truncate_lines(
|
|
81
81
|
response: str,
|
|
82
82
|
max_lines: int = 1000,
|
|
83
|
-
truncation_message: str = "\n\n[Response truncated to {max_lines} lines. Please use pagination or filtering to see more.]"
|
|
83
|
+
truncation_message: str = "\n\n[Response truncated to {max_lines} lines. Please use pagination or filtering to see more.]",
|
|
84
84
|
) -> str:
|
|
85
85
|
"""Truncate a response by number of lines.
|
|
86
|
-
|
|
86
|
+
|
|
87
87
|
Args:
|
|
88
88
|
response: The response text to truncate
|
|
89
89
|
max_lines: Maximum number of lines allowed (default: 1000)
|
|
90
90
|
truncation_message: Message template to append when truncating
|
|
91
|
-
|
|
91
|
+
|
|
92
92
|
Returns:
|
|
93
93
|
Truncated response if needed, original response otherwise
|
|
94
94
|
"""
|
|
95
|
-
lines = response.split(
|
|
96
|
-
|
|
95
|
+
lines = response.split("\n")
|
|
96
|
+
|
|
97
97
|
if len(lines) <= max_lines:
|
|
98
98
|
return response
|
|
99
|
-
|
|
100
|
-
truncated =
|
|
101
|
-
return truncated + truncation_message.format(max_lines=max_lines)
|
|
99
|
+
|
|
100
|
+
truncated = "\n".join(lines[:max_lines])
|
|
101
|
+
return truncated + truncation_message.format(max_lines=max_lines)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""Configuration tools for Hanzo AI."""
|
|
2
2
|
|
|
3
|
-
from hanzo_mcp.tools.config.config_tool import ConfigTool
|
|
4
|
-
from hanzo_mcp.tools.config.index_config import IndexConfig, IndexScope
|
|
5
3
|
from hanzo_mcp.tools.config.mode_tool import mode_tool
|
|
4
|
+
from hanzo_mcp.tools.config.config_tool import ConfigTool
|
|
5
|
+
from hanzo_mcp.tools.config.index_config import IndexScope, IndexConfig
|
|
6
6
|
|
|
7
7
|
__all__ = [
|
|
8
8
|
"ConfigTool",
|
|
9
|
-
"IndexConfig",
|
|
9
|
+
"IndexConfig",
|
|
10
10
|
"IndexScope",
|
|
11
11
|
"mode_tool",
|
|
12
|
-
]
|
|
12
|
+
]
|
|
@@ -3,17 +3,14 @@
|
|
|
3
3
|
Git-style config tool for managing settings.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from typing import
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
import json
|
|
6
|
+
from typing import Unpack, Optional, Annotated, TypedDict, final, override
|
|
9
7
|
|
|
10
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
11
8
|
from pydantic import Field
|
|
9
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
12
10
|
|
|
13
11
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
14
12
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
15
|
-
from hanzo_mcp.tools.config.index_config import
|
|
16
|
-
|
|
13
|
+
from hanzo_mcp.tools.config.index_config import IndexScope, IndexConfig
|
|
17
14
|
|
|
18
15
|
# Parameter types
|
|
19
16
|
Action = Annotated[
|
|
@@ -59,6 +56,7 @@ ConfigPath = Annotated[
|
|
|
59
56
|
|
|
60
57
|
class ConfigParams(TypedDict, total=False):
|
|
61
58
|
"""Parameters for config tool."""
|
|
59
|
+
|
|
62
60
|
action: str
|
|
63
61
|
key: Optional[str]
|
|
64
62
|
value: Optional[str]
|
|
@@ -69,7 +67,7 @@ class ConfigParams(TypedDict, total=False):
|
|
|
69
67
|
@final
|
|
70
68
|
class ConfigTool(BaseTool):
|
|
71
69
|
"""Git-style configuration management tool."""
|
|
72
|
-
|
|
70
|
+
|
|
73
71
|
def __init__(self, permission_manager: PermissionManager):
|
|
74
72
|
"""Initialize config tool."""
|
|
75
73
|
super().__init__(permission_manager)
|
|
@@ -101,14 +99,14 @@ config --action toggle index.scope --path ./project"""
|
|
|
101
99
|
) -> str:
|
|
102
100
|
"""Execute config operation."""
|
|
103
101
|
tool_ctx = self.create_tool_context(ctx)
|
|
104
|
-
|
|
102
|
+
|
|
105
103
|
# Extract parameters
|
|
106
104
|
action = params.get("action", "get")
|
|
107
105
|
key = params.get("key")
|
|
108
106
|
value = params.get("value")
|
|
109
107
|
scope = params.get("scope", "local")
|
|
110
108
|
path = params.get("path")
|
|
111
|
-
|
|
109
|
+
|
|
112
110
|
# Route to handler
|
|
113
111
|
if action == "get":
|
|
114
112
|
return await self._handle_get(key, scope, path, tool_ctx)
|
|
@@ -121,41 +119,52 @@ config --action toggle index.scope --path ./project"""
|
|
|
121
119
|
else:
|
|
122
120
|
return f"Error: Unknown action '{action}'. Valid actions: get, set, list, toggle"
|
|
123
121
|
|
|
124
|
-
async def _handle_get(
|
|
122
|
+
async def _handle_get(
|
|
123
|
+
self, key: Optional[str], scope: str, path: Optional[str], tool_ctx
|
|
124
|
+
) -> str:
|
|
125
125
|
"""Get configuration value."""
|
|
126
126
|
if not key:
|
|
127
127
|
return "Error: key required for get action"
|
|
128
|
-
|
|
128
|
+
|
|
129
129
|
# Handle index scope
|
|
130
130
|
if key == "index.scope":
|
|
131
131
|
current_scope = self.index_config.get_scope(path)
|
|
132
132
|
return f"index.scope={current_scope.value}"
|
|
133
|
-
|
|
133
|
+
|
|
134
134
|
# Handle tool-specific settings
|
|
135
135
|
if "." in key:
|
|
136
136
|
tool, setting = key.split(".", 1)
|
|
137
137
|
if setting == "enabled":
|
|
138
138
|
enabled = self.index_config.is_indexing_enabled(tool)
|
|
139
139
|
return f"{key}={enabled}"
|
|
140
|
-
|
|
140
|
+
|
|
141
141
|
return f"Unknown key: {key}"
|
|
142
142
|
|
|
143
|
-
async def _handle_set(
|
|
143
|
+
async def _handle_set(
|
|
144
|
+
self,
|
|
145
|
+
key: Optional[str],
|
|
146
|
+
value: Optional[str],
|
|
147
|
+
scope: str,
|
|
148
|
+
path: Optional[str],
|
|
149
|
+
tool_ctx,
|
|
150
|
+
) -> str:
|
|
144
151
|
"""Set configuration value."""
|
|
145
152
|
if not key:
|
|
146
153
|
return "Error: key required for set action"
|
|
147
154
|
if not value:
|
|
148
155
|
return "Error: value required for set action"
|
|
149
|
-
|
|
156
|
+
|
|
150
157
|
# Handle index scope
|
|
151
158
|
if key == "index.scope":
|
|
152
159
|
try:
|
|
153
160
|
new_scope = IndexScope(value)
|
|
154
|
-
self.index_config.set_scope(
|
|
161
|
+
self.index_config.set_scope(
|
|
162
|
+
new_scope, path if scope == "local" else None
|
|
163
|
+
)
|
|
155
164
|
return f"Set {key}={value} ({'project' if path else 'global'})"
|
|
156
165
|
except ValueError:
|
|
157
166
|
return f"Error: Invalid scope value '{value}'. Valid: project, global, auto"
|
|
158
|
-
|
|
167
|
+
|
|
159
168
|
# Handle tool-specific settings
|
|
160
169
|
if "." in key:
|
|
161
170
|
tool, setting = key.split(".", 1)
|
|
@@ -163,40 +172,44 @@ config --action toggle index.scope --path ./project"""
|
|
|
163
172
|
enabled = value.lower() in ["true", "yes", "1", "on"]
|
|
164
173
|
self.index_config.set_indexing_enabled(tool, enabled)
|
|
165
174
|
return f"Set {key}={enabled}"
|
|
166
|
-
|
|
175
|
+
|
|
167
176
|
return f"Unknown key: {key}"
|
|
168
177
|
|
|
169
178
|
async def _handle_list(self, scope: str, path: Optional[str], tool_ctx) -> str:
|
|
170
179
|
"""List all configuration."""
|
|
171
180
|
status = self.index_config.get_status()
|
|
172
|
-
|
|
181
|
+
|
|
173
182
|
output = ["=== Configuration ==="]
|
|
174
183
|
output.append(f"\nDefault scope: {status['default_scope']}")
|
|
175
|
-
|
|
184
|
+
|
|
176
185
|
if path:
|
|
177
186
|
current_scope = self.index_config.get_scope(path)
|
|
178
187
|
output.append(f"Current path scope: {current_scope.value}")
|
|
179
|
-
|
|
188
|
+
|
|
180
189
|
output.append(f"\nProjects with custom config: {status['project_count']}")
|
|
181
|
-
|
|
190
|
+
|
|
182
191
|
output.append("\nTool settings:")
|
|
183
192
|
for tool, settings in status["tools"].items():
|
|
184
193
|
output.append(f" {tool}:")
|
|
185
194
|
output.append(f" enabled: {settings['enabled']}")
|
|
186
195
|
output.append(f" per_project: {settings['per_project']}")
|
|
187
|
-
|
|
196
|
+
|
|
188
197
|
return "\n".join(output)
|
|
189
198
|
|
|
190
|
-
async def _handle_toggle(
|
|
199
|
+
async def _handle_toggle(
|
|
200
|
+
self, key: Optional[str], scope: str, path: Optional[str], tool_ctx
|
|
201
|
+
) -> str:
|
|
191
202
|
"""Toggle configuration value."""
|
|
192
203
|
if not key:
|
|
193
204
|
return "Error: key required for toggle action"
|
|
194
|
-
|
|
205
|
+
|
|
195
206
|
# Handle index scope toggle
|
|
196
207
|
if key == "index.scope":
|
|
197
|
-
new_scope = self.index_config.toggle_scope(
|
|
208
|
+
new_scope = self.index_config.toggle_scope(
|
|
209
|
+
path if scope == "local" else None
|
|
210
|
+
)
|
|
198
211
|
return f"Toggled index.scope to {new_scope.value}"
|
|
199
|
-
|
|
212
|
+
|
|
200
213
|
# Handle tool enable/disable toggle
|
|
201
214
|
if "." in key:
|
|
202
215
|
tool, setting = key.split(".", 1)
|
|
@@ -204,9 +217,9 @@ config --action toggle index.scope --path ./project"""
|
|
|
204
217
|
current = self.index_config.is_indexing_enabled(tool)
|
|
205
218
|
self.index_config.set_indexing_enabled(tool, not current)
|
|
206
219
|
return f"Toggled {key} to {not current}"
|
|
207
|
-
|
|
220
|
+
|
|
208
221
|
return f"Cannot toggle key: {key}"
|
|
209
222
|
|
|
210
223
|
def register(self, mcp_server) -> None:
|
|
211
224
|
"""Register this tool with the MCP server."""
|
|
212
|
-
pass
|
|
225
|
+
pass
|