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
|
@@ -4,12 +4,12 @@ This module provides the ContentReplaceTool for replacing text patterns in files
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import fnmatch
|
|
7
|
+
from typing import Unpack, Annotated, TypedDict, final, override
|
|
7
8
|
from pathlib import Path
|
|
8
|
-
from typing import Annotated, TypedDict, Unpack, final, override
|
|
9
9
|
|
|
10
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
11
|
-
from mcp.server import FastMCP
|
|
12
10
|
from pydantic import Field
|
|
11
|
+
from mcp.server import FastMCP
|
|
12
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
13
13
|
|
|
14
14
|
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
|
|
15
15
|
|
|
@@ -280,7 +280,7 @@ Only works within allowed directories."""
|
|
|
280
280
|
replacement: Replacement,
|
|
281
281
|
path: SearchPath,
|
|
282
282
|
file_pattern: FilePattern = "*",
|
|
283
|
-
dry_run: DryRun = False
|
|
283
|
+
dry_run: DryRun = False,
|
|
284
284
|
) -> str:
|
|
285
285
|
return await tool_self.call(
|
|
286
286
|
ctx,
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
"""Diff tool for comparing files."""
|
|
2
2
|
|
|
3
3
|
import difflib
|
|
4
|
+
from typing import override
|
|
4
5
|
from pathlib import Path
|
|
5
|
-
from typing import Optional, override
|
|
6
6
|
|
|
7
|
+
from mcp.server import FastMCP
|
|
7
8
|
from mcp.server.fastmcp import Context as MCPContext
|
|
8
9
|
|
|
9
10
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
10
11
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
11
|
-
from mcp.server import FastMCP
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class DiffTool(BaseTool):
|
|
15
15
|
"""Tool for comparing files and showing differences."""
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
name = "diff"
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
def __init__(self, permission_manager: PermissionManager):
|
|
20
20
|
"""Initialize the diff tool.
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
Args:
|
|
23
23
|
permission_manager: Permission manager for access control
|
|
24
24
|
"""
|
|
25
25
|
super().__init__()
|
|
26
26
|
self.permission_manager = permission_manager
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
@property
|
|
29
29
|
@override
|
|
30
30
|
def description(self) -> str:
|
|
@@ -36,7 +36,7 @@ diff file1.py file2.py
|
|
|
36
36
|
diff old_version.js new_version.js --context 5
|
|
37
37
|
diff before.txt after.txt --unified
|
|
38
38
|
diff a.json b.json --ignore-whitespace"""
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
@override
|
|
41
41
|
async def run(
|
|
42
42
|
self,
|
|
@@ -49,7 +49,7 @@ diff a.json b.json --ignore-whitespace"""
|
|
|
49
49
|
show_line_numbers: bool = True,
|
|
50
50
|
) -> str:
|
|
51
51
|
"""Compare two files and show differences.
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
Args:
|
|
54
54
|
ctx: MCP context
|
|
55
55
|
file1: Path to first file
|
|
@@ -58,100 +58,104 @@ diff a.json b.json --ignore-whitespace"""
|
|
|
58
58
|
context: Number of context lines to show (default: 3)
|
|
59
59
|
ignore_whitespace: Ignore whitespace differences (default: False)
|
|
60
60
|
show_line_numbers: Show line numbers in output (default: True)
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
Returns:
|
|
63
63
|
Diff output showing differences between files
|
|
64
64
|
"""
|
|
65
65
|
# Resolve file paths
|
|
66
66
|
path1 = Path(file1).expanduser().resolve()
|
|
67
67
|
path2 = Path(file2).expanduser().resolve()
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
# Check permissions
|
|
70
70
|
if not self.permission_manager.is_path_allowed(str(path1)):
|
|
71
71
|
raise PermissionError(f"Access denied to path: {path1}")
|
|
72
72
|
if not self.permission_manager.is_path_allowed(str(path2)):
|
|
73
73
|
raise PermissionError(f"Access denied to path: {path2}")
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
# Check if files exist
|
|
76
76
|
if not path1.exists():
|
|
77
77
|
raise ValueError(f"File not found: {path1}")
|
|
78
78
|
if not path2.exists():
|
|
79
79
|
raise ValueError(f"File not found: {path2}")
|
|
80
|
-
|
|
80
|
+
|
|
81
81
|
# Read file contents
|
|
82
82
|
try:
|
|
83
|
-
with open(path1,
|
|
83
|
+
with open(path1, "r", encoding="utf-8") as f:
|
|
84
84
|
lines1 = f.readlines()
|
|
85
85
|
except Exception as e:
|
|
86
86
|
raise RuntimeError(f"Error reading {path1}: {e}")
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
try:
|
|
89
|
-
with open(path2,
|
|
89
|
+
with open(path2, "r", encoding="utf-8") as f:
|
|
90
90
|
lines2 = f.readlines()
|
|
91
91
|
except Exception as e:
|
|
92
92
|
raise RuntimeError(f"Error reading {path2}: {e}")
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
# Optionally normalize whitespace
|
|
95
95
|
if ignore_whitespace:
|
|
96
|
-
lines1 = [line.strip() +
|
|
97
|
-
lines2 = [line.strip() +
|
|
98
|
-
|
|
96
|
+
lines1 = [line.strip() + "\n" for line in lines1]
|
|
97
|
+
lines2 = [line.strip() + "\n" for line in lines2]
|
|
98
|
+
|
|
99
99
|
# Generate diff
|
|
100
100
|
if unified:
|
|
101
101
|
# diff format
|
|
102
|
-
diff_lines = list(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
diff_lines = list(
|
|
103
|
+
difflib.unified_diff(
|
|
104
|
+
lines1,
|
|
105
|
+
lines2,
|
|
106
|
+
fromfile=str(path1),
|
|
107
|
+
tofile=str(path2),
|
|
108
|
+
n=context,
|
|
109
|
+
lineterm="",
|
|
110
|
+
)
|
|
111
|
+
)
|
|
110
112
|
else:
|
|
111
113
|
# Context diff format
|
|
112
|
-
diff_lines = list(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
114
|
+
diff_lines = list(
|
|
115
|
+
difflib.context_diff(
|
|
116
|
+
lines1,
|
|
117
|
+
lines2,
|
|
118
|
+
fromfile=str(path1),
|
|
119
|
+
tofile=str(path2),
|
|
120
|
+
n=context,
|
|
121
|
+
lineterm="",
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
|
|
121
125
|
if not diff_lines:
|
|
122
126
|
return f"Files {path1.name} and {path2.name} are identical"
|
|
123
|
-
|
|
127
|
+
|
|
124
128
|
# Format output
|
|
125
129
|
output = []
|
|
126
|
-
|
|
130
|
+
|
|
127
131
|
# Add header
|
|
128
132
|
output.append(f"Comparing: {path1.name} vs {path2.name}")
|
|
129
133
|
output.append("=" * 60)
|
|
130
|
-
|
|
134
|
+
|
|
131
135
|
# Add diff with optional line numbers
|
|
132
136
|
if show_line_numbers and unified:
|
|
133
137
|
# Parse unified diff to add line numbers
|
|
134
138
|
current_line1 = 0
|
|
135
139
|
current_line2 = 0
|
|
136
|
-
|
|
140
|
+
|
|
137
141
|
for line in diff_lines:
|
|
138
|
-
if line.startswith(
|
|
142
|
+
if line.startswith("@@"):
|
|
139
143
|
# Parse hunk header
|
|
140
144
|
parts = line.split()
|
|
141
145
|
if len(parts) >= 3:
|
|
142
146
|
# Extract line numbers
|
|
143
|
-
old_info = parts[1].strip(
|
|
144
|
-
new_info = parts[2].strip(
|
|
147
|
+
old_info = parts[1].strip("-").split(",")
|
|
148
|
+
new_info = parts[2].strip("+").split(",")
|
|
145
149
|
current_line1 = int(old_info[0]) - 1
|
|
146
150
|
current_line2 = int(new_info[0]) - 1
|
|
147
151
|
output.append(line)
|
|
148
|
-
elif line.startswith(
|
|
152
|
+
elif line.startswith("-"):
|
|
149
153
|
current_line1 += 1
|
|
150
154
|
output.append(f"{current_line1:4d}- {line[1:]}")
|
|
151
|
-
elif line.startswith(
|
|
155
|
+
elif line.startswith("+"):
|
|
152
156
|
current_line2 += 1
|
|
153
157
|
output.append(f"{current_line2:4d}+ {line[1:]}")
|
|
154
|
-
elif line.startswith(
|
|
158
|
+
elif line.startswith(" "):
|
|
155
159
|
current_line1 += 1
|
|
156
160
|
current_line2 += 1
|
|
157
161
|
output.append(f"{current_line1:4d} {line[1:]}")
|
|
@@ -160,21 +164,29 @@ diff a.json b.json --ignore-whitespace"""
|
|
|
160
164
|
else:
|
|
161
165
|
# Standard diff output
|
|
162
166
|
output.extend(diff_lines)
|
|
163
|
-
|
|
167
|
+
|
|
164
168
|
# Add summary
|
|
165
|
-
additions = sum(
|
|
166
|
-
|
|
167
|
-
|
|
169
|
+
additions = sum(
|
|
170
|
+
1
|
|
171
|
+
for line in diff_lines
|
|
172
|
+
if line.startswith("+") and not line.startswith("+++")
|
|
173
|
+
)
|
|
174
|
+
deletions = sum(
|
|
175
|
+
1
|
|
176
|
+
for line in diff_lines
|
|
177
|
+
if line.startswith("-") and not line.startswith("---")
|
|
178
|
+
)
|
|
179
|
+
|
|
168
180
|
output.append("")
|
|
169
181
|
output.append("=" * 60)
|
|
170
182
|
output.append(f"Summary: {additions} additions, {deletions} deletions")
|
|
171
|
-
|
|
172
|
-
return
|
|
183
|
+
|
|
184
|
+
return "\n".join(output)
|
|
173
185
|
|
|
174
186
|
def register(self, server: FastMCP) -> None:
|
|
175
187
|
"""Register the tool with the MCP server."""
|
|
176
188
|
tool_self = self
|
|
177
|
-
|
|
189
|
+
|
|
178
190
|
@server.tool(name=self.name, description=self.description)
|
|
179
191
|
async def diff_handler(
|
|
180
192
|
ctx: MCPContext,
|
|
@@ -183,7 +195,7 @@ diff a.json b.json --ignore-whitespace"""
|
|
|
183
195
|
unified: bool = True,
|
|
184
196
|
context: int = 3,
|
|
185
197
|
ignore_whitespace: bool = False,
|
|
186
|
-
show_line_numbers: bool = True
|
|
198
|
+
show_line_numbers: bool = True,
|
|
187
199
|
) -> str:
|
|
188
200
|
"""Handle diff tool calls."""
|
|
189
201
|
return await tool_self.run(
|
|
@@ -195,7 +207,7 @@ diff a.json b.json --ignore-whitespace"""
|
|
|
195
207
|
ignore_whitespace=ignore_whitespace,
|
|
196
208
|
show_line_numbers=show_line_numbers,
|
|
197
209
|
)
|
|
198
|
-
|
|
210
|
+
|
|
199
211
|
async def call(self, ctx: MCPContext, **params) -> str:
|
|
200
212
|
"""Call the tool with arguments."""
|
|
201
213
|
return await self.run(
|
|
@@ -205,18 +217,18 @@ diff a.json b.json --ignore-whitespace"""
|
|
|
205
217
|
unified=params.get("unified", True),
|
|
206
218
|
context=params.get("context", 3),
|
|
207
219
|
ignore_whitespace=params.get("ignore_whitespace", False),
|
|
208
|
-
show_line_numbers=params.get("show_line_numbers", True)
|
|
220
|
+
show_line_numbers=params.get("show_line_numbers", True),
|
|
209
221
|
)
|
|
210
222
|
|
|
211
223
|
|
|
212
224
|
# Create tool instance (requires permission manager to be set)
|
|
213
225
|
def create_diff_tool(permission_manager: PermissionManager) -> DiffTool:
|
|
214
226
|
"""Create a diff tool instance.
|
|
215
|
-
|
|
227
|
+
|
|
216
228
|
Args:
|
|
217
229
|
permission_manager: Permission manager for access control
|
|
218
|
-
|
|
230
|
+
|
|
219
231
|
Returns:
|
|
220
232
|
Configured diff tool instance
|
|
221
233
|
"""
|
|
222
|
-
return DiffTool(permission_manager)
|
|
234
|
+
return DiffTool(permission_manager)
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
This module provides the DirectoryTreeTool for viewing file and directory structures.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
from typing import Any, Unpack, Annotated, TypedDict, final, override
|
|
6
7
|
from pathlib import Path
|
|
7
|
-
from typing import Annotated, Any, TypedDict, Unpack, final, override
|
|
8
8
|
|
|
9
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
10
|
-
from mcp.server import FastMCP
|
|
11
9
|
from pydantic import Field
|
|
10
|
+
from mcp.server import FastMCP
|
|
11
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
12
12
|
|
|
13
|
-
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
|
|
14
13
|
from hanzo_mcp.tools.common.truncate import truncate_response
|
|
14
|
+
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
|
|
15
15
|
|
|
16
16
|
DirectoryPath = Annotated[
|
|
17
17
|
str,
|
|
@@ -283,9 +283,9 @@ requested. Only works within allowed directories."""
|
|
|
283
283
|
# Truncate response to stay within token limits
|
|
284
284
|
full_response = formatted_output + summary
|
|
285
285
|
return truncate_response(
|
|
286
|
-
full_response,
|
|
286
|
+
full_response,
|
|
287
287
|
max_tokens=25000,
|
|
288
|
-
truncation_message="\n\n[Response truncated due to token limit. Please use a smaller depth, specific subdirectory, or the paginated version of this tool.]"
|
|
288
|
+
truncation_message="\n\n[Response truncated due to token limit. Please use a smaller depth, specific subdirectory, or the paginated version of this tool.]",
|
|
289
289
|
)
|
|
290
290
|
except Exception as e:
|
|
291
291
|
await tool_ctx.error(f"Error generating directory tree: {str(e)}")
|
|
@@ -308,7 +308,7 @@ requested. Only works within allowed directories."""
|
|
|
308
308
|
ctx: MCPContext,
|
|
309
309
|
path: DirectoryPath,
|
|
310
310
|
depth: Depth = 3,
|
|
311
|
-
include_filtered: IncludeFiltered = False
|
|
311
|
+
include_filtered: IncludeFiltered = False,
|
|
312
312
|
) -> str:
|
|
313
313
|
return await tool_self.call(
|
|
314
314
|
ctx, path=path, depth=depth, include_filtered=include_filtered
|
|
@@ -4,18 +4,27 @@ This module provides a paginated version of DirectoryTreeTool that supports
|
|
|
4
4
|
MCP cursor-based pagination for large directory structures.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from typing import (
|
|
8
|
+
Any,
|
|
9
|
+
Dict,
|
|
10
|
+
List,
|
|
11
|
+
Unpack,
|
|
12
|
+
Optional,
|
|
13
|
+
Annotated,
|
|
14
|
+
TypedDict,
|
|
15
|
+
final,
|
|
16
|
+
override,
|
|
17
|
+
)
|
|
7
18
|
from pathlib import Path
|
|
8
|
-
from typing import Annotated, Any, Dict, List, Optional, TypedDict, Unpack, final, override
|
|
9
19
|
|
|
10
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
11
|
-
from mcp.server import FastMCP
|
|
12
20
|
from pydantic import Field
|
|
21
|
+
from mcp.server import FastMCP
|
|
22
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
13
23
|
|
|
14
24
|
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
|
|
15
25
|
from hanzo_mcp.tools.common.pagination import (
|
|
16
26
|
CursorManager,
|
|
17
|
-
|
|
18
|
-
paginate_list
|
|
27
|
+
paginate_list,
|
|
19
28
|
)
|
|
20
29
|
|
|
21
30
|
DirectoryPath = Annotated[
|
|
@@ -128,7 +137,7 @@ Returns nextCursor if more entries are available."""
|
|
|
128
137
|
depth = params.get("depth", 3)
|
|
129
138
|
include_filtered = params.get("include_filtered", False)
|
|
130
139
|
page_size = params.get("page_size", 100)
|
|
131
|
-
cursor = params.get("cursor"
|
|
140
|
+
cursor = params.get("cursor")
|
|
132
141
|
|
|
133
142
|
# Validate cursor if provided
|
|
134
143
|
if cursor:
|
|
@@ -196,9 +205,7 @@ Returns nextCursor if more entries are available."""
|
|
|
196
205
|
|
|
197
206
|
# Build the tree and collect entries
|
|
198
207
|
def collect_entries(
|
|
199
|
-
current_path: Path,
|
|
200
|
-
current_depth: int = 0,
|
|
201
|
-
parent_path: str = ""
|
|
208
|
+
current_path: Path, current_depth: int = 0, parent_path: str = ""
|
|
202
209
|
) -> None:
|
|
203
210
|
"""Collect entries in a flat list for pagination."""
|
|
204
211
|
if not self.is_path_allowed(str(current_path)):
|
|
@@ -207,8 +214,7 @@ Returns nextCursor if more entries are available."""
|
|
|
207
214
|
try:
|
|
208
215
|
# Sort entries: directories first, then files alphabetically
|
|
209
216
|
entries = sorted(
|
|
210
|
-
current_path.iterdir(),
|
|
211
|
-
key=lambda x: (not x.is_dir(), x.name)
|
|
217
|
+
current_path.iterdir(), key=lambda x: (not x.is_dir(), x.name)
|
|
212
218
|
)
|
|
213
219
|
|
|
214
220
|
for entry in entries:
|
|
@@ -216,7 +222,9 @@ Returns nextCursor if more entries are available."""
|
|
|
216
222
|
continue
|
|
217
223
|
|
|
218
224
|
# Calculate relative path for display
|
|
219
|
-
relative_path =
|
|
225
|
+
relative_path = (
|
|
226
|
+
f"{parent_path}/{entry.name}" if parent_path else entry.name
|
|
227
|
+
)
|
|
220
228
|
|
|
221
229
|
if entry.is_dir():
|
|
222
230
|
entry_data: Dict[str, Any] = {
|
|
@@ -241,19 +249,17 @@ Returns nextCursor if more entries are available."""
|
|
|
241
249
|
all_entries.append(entry_data)
|
|
242
250
|
|
|
243
251
|
# Process children recursively
|
|
244
|
-
collect_entries(
|
|
245
|
-
entry,
|
|
246
|
-
current_depth + 1,
|
|
247
|
-
relative_path
|
|
248
|
-
)
|
|
252
|
+
collect_entries(entry, current_depth + 1, relative_path)
|
|
249
253
|
else:
|
|
250
254
|
# Add file entry
|
|
251
255
|
if depth <= 0 or current_depth < depth:
|
|
252
|
-
all_entries.append(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
256
|
+
all_entries.append(
|
|
257
|
+
{
|
|
258
|
+
"path": relative_path,
|
|
259
|
+
"type": "file",
|
|
260
|
+
"depth": current_depth,
|
|
261
|
+
}
|
|
262
|
+
)
|
|
257
263
|
|
|
258
264
|
except Exception as e:
|
|
259
265
|
await tool_ctx.warning(f"Error processing {current_path}: {str(e)}")
|
|
@@ -271,28 +277,34 @@ Returns nextCursor if more entries are available."""
|
|
|
271
277
|
indent = " " * entry["depth"]
|
|
272
278
|
if entry["type"] == "directory":
|
|
273
279
|
if "skipped" in entry:
|
|
274
|
-
formatted_entries.append(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
280
|
+
formatted_entries.append(
|
|
281
|
+
{
|
|
282
|
+
"entry": f"{indent}{entry['path'].split('/')[-1]}/ [skipped - {entry['skipped']}]",
|
|
283
|
+
"type": "directory",
|
|
284
|
+
"skipped": entry.get("skipped"),
|
|
285
|
+
}
|
|
286
|
+
)
|
|
279
287
|
else:
|
|
280
|
-
formatted_entries.append(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
288
|
+
formatted_entries.append(
|
|
289
|
+
{
|
|
290
|
+
"entry": f"{indent}{entry['path'].split('/')[-1]}/",
|
|
291
|
+
"type": "directory",
|
|
292
|
+
}
|
|
293
|
+
)
|
|
284
294
|
else:
|
|
285
|
-
formatted_entries.append(
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
295
|
+
formatted_entries.append(
|
|
296
|
+
{
|
|
297
|
+
"entry": f"{indent}{entry['path'].split('/')[-1]}",
|
|
298
|
+
"type": "file",
|
|
299
|
+
}
|
|
300
|
+
)
|
|
289
301
|
|
|
290
302
|
# Build response
|
|
291
303
|
response = {
|
|
292
304
|
"entries": formatted_entries,
|
|
293
305
|
"total_collected": len(all_entries),
|
|
294
306
|
"page_size": page_size,
|
|
295
|
-
"current_page_count": len(formatted_entries)
|
|
307
|
+
"current_page_count": len(formatted_entries),
|
|
296
308
|
}
|
|
297
309
|
|
|
298
310
|
# Add next cursor if available
|
|
@@ -335,4 +347,4 @@ Returns nextCursor if more entries are available."""
|
|
|
335
347
|
|
|
336
348
|
|
|
337
349
|
# Create the tool instance
|
|
338
|
-
directory_tree_paginated_tool = DirectoryTreePaginatedTool()
|
|
350
|
+
directory_tree_paginated_tool = DirectoryTreePaginatedTool()
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
This module provides the Edit tool for making precise text replacements in files.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
from typing import Unpack, Annotated, TypedDict, final, override
|
|
6
7
|
from difflib import unified_diff
|
|
7
8
|
from pathlib import Path
|
|
8
|
-
from typing import Annotated, TypedDict, Unpack, final, override
|
|
9
9
|
|
|
10
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
11
|
-
from mcp.server import FastMCP
|
|
12
10
|
from pydantic import Field
|
|
11
|
+
from mcp.server import FastMCP
|
|
12
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
13
13
|
|
|
14
14
|
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
|
|
15
15
|
|
|
@@ -266,7 +266,7 @@ Usage:
|
|
|
266
266
|
file_path: FilePath,
|
|
267
267
|
old_string: OldString,
|
|
268
268
|
new_string: NewString,
|
|
269
|
-
expected_replacements: ExpectedReplacements = 1
|
|
269
|
+
expected_replacements: ExpectedReplacements = 1,
|
|
270
270
|
) -> str:
|
|
271
271
|
return await tool_self.call(
|
|
272
272
|
ctx,
|