hanzo-mcp 0.9.0__py3-none-any.whl → 0.9.2__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/analytics/posthog_analytics.py +14 -1
- hanzo_mcp/cli.py +108 -4
- hanzo_mcp/server.py +11 -0
- hanzo_mcp/tools/__init__.py +3 -16
- hanzo_mcp/tools/agent/__init__.py +5 -0
- hanzo_mcp/tools/agent/agent.py +5 -0
- hanzo_mcp/tools/agent/agent_tool.py +3 -17
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +623 -0
- hanzo_mcp/tools/agent/clarification_tool.py +7 -1
- hanzo_mcp/tools/agent/claude_desktop_auth.py +16 -6
- hanzo_mcp/tools/agent/cli_agent_base.py +5 -0
- hanzo_mcp/tools/agent/cli_tools.py +26 -0
- hanzo_mcp/tools/agent/code_auth_tool.py +5 -0
- hanzo_mcp/tools/agent/critic_tool.py +7 -1
- hanzo_mcp/tools/agent/iching_tool.py +5 -0
- hanzo_mcp/tools/agent/network_tool.py +5 -0
- hanzo_mcp/tools/agent/review_tool.py +7 -1
- hanzo_mcp/tools/agent/swarm_alias.py +5 -0
- hanzo_mcp/tools/agent/swarm_tool.py +701 -0
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +554 -0
- hanzo_mcp/tools/agent/unified_cli_tools.py +5 -0
- hanzo_mcp/tools/common/auto_timeout.py +254 -0
- hanzo_mcp/tools/common/base.py +4 -0
- hanzo_mcp/tools/common/batch_tool.py +5 -0
- hanzo_mcp/tools/common/config_tool.py +5 -0
- hanzo_mcp/tools/common/critic_tool.py +5 -0
- hanzo_mcp/tools/common/paginated_base.py +4 -0
- hanzo_mcp/tools/common/permissions.py +38 -12
- hanzo_mcp/tools/common/personality.py +673 -980
- hanzo_mcp/tools/common/stats.py +5 -0
- hanzo_mcp/tools/common/thinking_tool.py +5 -0
- hanzo_mcp/tools/common/timeout_parser.py +103 -0
- hanzo_mcp/tools/common/tool_disable.py +5 -0
- hanzo_mcp/tools/common/tool_enable.py +5 -0
- hanzo_mcp/tools/common/tool_list.py +5 -0
- hanzo_mcp/tools/config/config_tool.py +5 -0
- hanzo_mcp/tools/config/mode_tool.py +5 -0
- hanzo_mcp/tools/database/graph.py +5 -0
- hanzo_mcp/tools/database/graph_add.py +5 -0
- hanzo_mcp/tools/database/graph_query.py +5 -0
- hanzo_mcp/tools/database/graph_remove.py +5 -0
- hanzo_mcp/tools/database/graph_search.py +5 -0
- hanzo_mcp/tools/database/graph_stats.py +5 -0
- hanzo_mcp/tools/database/sql.py +5 -0
- hanzo_mcp/tools/database/sql_query.py +2 -0
- hanzo_mcp/tools/database/sql_search.py +5 -0
- hanzo_mcp/tools/database/sql_stats.py +5 -0
- hanzo_mcp/tools/editor/neovim_command.py +5 -0
- hanzo_mcp/tools/editor/neovim_edit.py +7 -2
- hanzo_mcp/tools/editor/neovim_session.py +5 -0
- hanzo_mcp/tools/filesystem/__init__.py +23 -26
- hanzo_mcp/tools/filesystem/ast_tool.py +3 -4
- hanzo_mcp/tools/filesystem/base.py +2 -18
- hanzo_mcp/tools/filesystem/batch_search.py +825 -0
- hanzo_mcp/tools/filesystem/content_replace.py +5 -3
- hanzo_mcp/tools/filesystem/diff.py +5 -0
- hanzo_mcp/tools/filesystem/directory_tree.py +34 -281
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +345 -0
- hanzo_mcp/tools/filesystem/edit.py +6 -5
- hanzo_mcp/tools/filesystem/find.py +177 -311
- hanzo_mcp/tools/filesystem/find_files.py +370 -0
- hanzo_mcp/tools/filesystem/git_search.py +5 -3
- hanzo_mcp/tools/filesystem/grep.py +454 -0
- hanzo_mcp/tools/filesystem/multi_edit.py +6 -5
- hanzo_mcp/tools/filesystem/read.py +10 -9
- hanzo_mcp/tools/filesystem/rules_tool.py +6 -4
- hanzo_mcp/tools/filesystem/search_tool.py +728 -0
- hanzo_mcp/tools/filesystem/symbols_tool.py +510 -0
- hanzo_mcp/tools/filesystem/tree.py +273 -0
- hanzo_mcp/tools/filesystem/watch.py +6 -1
- hanzo_mcp/tools/filesystem/write.py +13 -7
- hanzo_mcp/tools/jupyter/jupyter.py +30 -2
- hanzo_mcp/tools/jupyter/notebook_edit.py +298 -0
- hanzo_mcp/tools/jupyter/notebook_read.py +148 -0
- hanzo_mcp/tools/llm/consensus_tool.py +8 -6
- hanzo_mcp/tools/llm/llm_manage.py +5 -0
- hanzo_mcp/tools/llm/llm_tool.py +2 -0
- hanzo_mcp/tools/llm/llm_unified.py +5 -0
- hanzo_mcp/tools/llm/provider_tools.py +5 -0
- hanzo_mcp/tools/lsp/lsp_tool.py +475 -622
- hanzo_mcp/tools/mcp/mcp_add.py +7 -2
- hanzo_mcp/tools/mcp/mcp_remove.py +15 -2
- hanzo_mcp/tools/mcp/mcp_stats.py +5 -0
- hanzo_mcp/tools/mcp/mcp_tool.py +5 -0
- hanzo_mcp/tools/memory/knowledge_tools.py +14 -0
- hanzo_mcp/tools/memory/memory_tools.py +17 -0
- hanzo_mcp/tools/search/find_tool.py +5 -3
- hanzo_mcp/tools/search/unified_search.py +3 -1
- hanzo_mcp/tools/shell/__init__.py +2 -14
- hanzo_mcp/tools/shell/base_process.py +4 -2
- hanzo_mcp/tools/shell/bash_tool.py +2 -0
- hanzo_mcp/tools/shell/command_executor.py +7 -7
- hanzo_mcp/tools/shell/logs.py +5 -0
- hanzo_mcp/tools/shell/npx.py +5 -0
- hanzo_mcp/tools/shell/npx_background.py +5 -0
- hanzo_mcp/tools/shell/npx_tool.py +5 -0
- hanzo_mcp/tools/shell/open.py +5 -0
- hanzo_mcp/tools/shell/pkill.py +5 -0
- hanzo_mcp/tools/shell/process_tool.py +5 -0
- hanzo_mcp/tools/shell/processes.py +5 -0
- hanzo_mcp/tools/shell/run_background.py +5 -0
- hanzo_mcp/tools/shell/run_command.py +2 -0
- hanzo_mcp/tools/shell/run_command_windows.py +5 -0
- hanzo_mcp/tools/shell/streaming_command.py +5 -0
- hanzo_mcp/tools/shell/uvx.py +5 -0
- hanzo_mcp/tools/shell/uvx_background.py +5 -0
- hanzo_mcp/tools/shell/uvx_tool.py +5 -0
- hanzo_mcp/tools/shell/zsh_tool.py +3 -0
- hanzo_mcp/tools/todo/todo.py +5 -0
- hanzo_mcp/tools/todo/todo_read.py +142 -0
- hanzo_mcp/tools/todo/todo_write.py +367 -0
- hanzo_mcp/tools/vector/__init__.py +42 -95
- hanzo_mcp/tools/vector/index_tool.py +5 -0
- hanzo_mcp/tools/vector/vector.py +5 -0
- hanzo_mcp/tools/vector/vector_index.py +5 -0
- hanzo_mcp/tools/vector/vector_search.py +5 -0
- {hanzo_mcp-0.9.0.dist-info → hanzo_mcp-0.9.2.dist-info}/METADATA +1 -1
- hanzo_mcp-0.9.2.dist-info/RECORD +195 -0
- hanzo_mcp/tools/common/path_utils.py +0 -34
- hanzo_mcp/tools/compiler/__init__.py +0 -8
- hanzo_mcp/tools/compiler/sandboxed_compiler.py +0 -681
- hanzo_mcp/tools/environment/__init__.py +0 -8
- hanzo_mcp/tools/environment/environment_detector.py +0 -594
- hanzo_mcp/tools/filesystem/search.py +0 -1160
- hanzo_mcp/tools/framework/__init__.py +0 -8
- hanzo_mcp/tools/framework/framework_modes.py +0 -714
- hanzo_mcp/tools/memory/conversation_memory.py +0 -636
- hanzo_mcp/tools/shell/run_tool.py +0 -56
- hanzo_mcp/tools/vector/node_tool.py +0 -538
- hanzo_mcp/tools/vector/unified_vector.py +0 -384
- hanzo_mcp-0.9.0.dist-info/RECORD +0 -191
- {hanzo_mcp-0.9.0.dist-info → hanzo_mcp-0.9.2.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.9.0.dist-info → hanzo_mcp-0.9.2.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.9.0.dist-info → hanzo_mcp-0.9.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"""Paginated directory tree tool implementation.
|
|
2
|
+
|
|
3
|
+
This module provides a paginated version of DirectoryTreeTool that supports
|
|
4
|
+
MCP cursor-based pagination for large directory structures.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import (
|
|
8
|
+
Any,
|
|
9
|
+
Dict,
|
|
10
|
+
List,
|
|
11
|
+
Unpack,
|
|
12
|
+
Optional,
|
|
13
|
+
Annotated,
|
|
14
|
+
TypedDict,
|
|
15
|
+
final,
|
|
16
|
+
override,
|
|
17
|
+
)
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from pydantic import Field
|
|
21
|
+
from mcp.server import FastMCP
|
|
22
|
+
|
|
23
|
+
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
|
|
24
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
25
|
+
|
|
26
|
+
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
|
|
27
|
+
from hanzo_mcp.tools.common.pagination import (
|
|
28
|
+
CursorManager,
|
|
29
|
+
paginate_list,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
DirectoryPath = Annotated[
|
|
33
|
+
str,
|
|
34
|
+
Field(
|
|
35
|
+
description="The path to the directory to view",
|
|
36
|
+
title="Path",
|
|
37
|
+
),
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
Depth = Annotated[
|
|
41
|
+
int,
|
|
42
|
+
Field(
|
|
43
|
+
default=3,
|
|
44
|
+
description="The maximum depth to traverse (0 for unlimited)",
|
|
45
|
+
title="Depth",
|
|
46
|
+
),
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
IncludeFiltered = Annotated[
|
|
50
|
+
bool,
|
|
51
|
+
Field(
|
|
52
|
+
default=False,
|
|
53
|
+
description="Include directories that are normally filtered",
|
|
54
|
+
title="Include Filtered",
|
|
55
|
+
),
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
PageSize = Annotated[
|
|
59
|
+
int,
|
|
60
|
+
Field(
|
|
61
|
+
default=100,
|
|
62
|
+
description="Number of entries per page",
|
|
63
|
+
title="Page Size",
|
|
64
|
+
),
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
Cursor = Annotated[
|
|
68
|
+
Optional[str],
|
|
69
|
+
Field(
|
|
70
|
+
default=None,
|
|
71
|
+
description="Pagination cursor for continuing from previous request",
|
|
72
|
+
title="Cursor",
|
|
73
|
+
),
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class DirectoryTreePaginatedParams(TypedDict):
|
|
78
|
+
"""Parameters for the paginated DirectoryTreeTool.
|
|
79
|
+
|
|
80
|
+
Attributes:
|
|
81
|
+
path: The path to the directory to view
|
|
82
|
+
depth: The maximum depth to traverse (0 for unlimited)
|
|
83
|
+
include_filtered: Include directories that are normally filtered
|
|
84
|
+
page_size: Number of entries per page
|
|
85
|
+
cursor: Pagination cursor
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
path: DirectoryPath
|
|
89
|
+
depth: Depth
|
|
90
|
+
include_filtered: IncludeFiltered
|
|
91
|
+
page_size: PageSize
|
|
92
|
+
cursor: Cursor
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@final
|
|
96
|
+
class DirectoryTreePaginatedTool(FilesystemBaseTool):
|
|
97
|
+
"""Tool for viewing directory structure as a tree with pagination support."""
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
@override
|
|
101
|
+
def name(self) -> str:
|
|
102
|
+
"""Get the tool name."""
|
|
103
|
+
return "directory_tree_paginated"
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
@override
|
|
107
|
+
def description(self) -> str:
|
|
108
|
+
"""Get the tool description."""
|
|
109
|
+
return """Get a paginated recursive tree view of files and directories.
|
|
110
|
+
|
|
111
|
+
This is a paginated version of directory_tree that supports cursor-based pagination
|
|
112
|
+
for large directory structures. Returns a structured view with files and subdirectories.
|
|
113
|
+
|
|
114
|
+
Directories are marked with trailing slashes. Common development directories like
|
|
115
|
+
.git, node_modules, and venv are noted but not traversed unless explicitly requested.
|
|
116
|
+
|
|
117
|
+
Use the cursor field to continue from where the previous request left off.
|
|
118
|
+
Returns nextCursor if more entries are available."""
|
|
119
|
+
|
|
120
|
+
@override
|
|
121
|
+
@auto_timeout("directory_tree_paginated")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
async def call(
|
|
125
|
+
self,
|
|
126
|
+
ctx: MCPContext,
|
|
127
|
+
**params: Unpack[DirectoryTreePaginatedParams],
|
|
128
|
+
) -> Dict[str, Any]:
|
|
129
|
+
"""Execute the tool with the given parameters.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
ctx: MCP context
|
|
133
|
+
**params: Tool parameters
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Dictionary with entries and optional nextCursor
|
|
137
|
+
"""
|
|
138
|
+
tool_ctx = self.create_tool_context(ctx)
|
|
139
|
+
|
|
140
|
+
# Extract parameters
|
|
141
|
+
path: DirectoryPath = params["path"]
|
|
142
|
+
depth = params.get("depth", 3)
|
|
143
|
+
include_filtered = params.get("include_filtered", False)
|
|
144
|
+
page_size = params.get("page_size", 100)
|
|
145
|
+
cursor = params.get("cursor")
|
|
146
|
+
|
|
147
|
+
# Validate cursor if provided
|
|
148
|
+
if cursor:
|
|
149
|
+
cursor_data = CursorManager.parse_cursor(cursor)
|
|
150
|
+
if not cursor_data:
|
|
151
|
+
await tool_ctx.error("Invalid cursor provided")
|
|
152
|
+
return {"error": "Invalid cursor"}
|
|
153
|
+
|
|
154
|
+
# Validate path parameter
|
|
155
|
+
path_validation = self.validate_path(path)
|
|
156
|
+
if path_validation.is_error:
|
|
157
|
+
await tool_ctx.error(path_validation.error_message)
|
|
158
|
+
return {"error": path_validation.error_message}
|
|
159
|
+
|
|
160
|
+
await tool_ctx.info(f"Getting paginated directory tree: {path} (depth: {depth}, page_size: {page_size})")
|
|
161
|
+
|
|
162
|
+
# Check if path is allowed
|
|
163
|
+
allowed, error_msg = await self.check_path_allowed(path, tool_ctx)
|
|
164
|
+
if not allowed:
|
|
165
|
+
return {"error": error_msg}
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
dir_path = Path(path)
|
|
169
|
+
|
|
170
|
+
# Check if path exists
|
|
171
|
+
exists, error_msg = await self.check_path_exists(path, tool_ctx)
|
|
172
|
+
if not exists:
|
|
173
|
+
return {"error": error_msg}
|
|
174
|
+
|
|
175
|
+
# Check if path is a directory
|
|
176
|
+
is_dir, error_msg = await self.check_is_directory(path, tool_ctx)
|
|
177
|
+
if not is_dir:
|
|
178
|
+
return {"error": error_msg}
|
|
179
|
+
|
|
180
|
+
# Define filtered directories
|
|
181
|
+
FILTERED_DIRECTORIES = {
|
|
182
|
+
".git",
|
|
183
|
+
"node_modules",
|
|
184
|
+
".venv",
|
|
185
|
+
"venv",
|
|
186
|
+
"__pycache__",
|
|
187
|
+
".pytest_cache",
|
|
188
|
+
".idea",
|
|
189
|
+
".vs",
|
|
190
|
+
".vscode",
|
|
191
|
+
"dist",
|
|
192
|
+
"build",
|
|
193
|
+
"target",
|
|
194
|
+
".ruff_cache",
|
|
195
|
+
".llm-context",
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# Check if a directory should be filtered
|
|
199
|
+
def should_filter(current_path: Path) -> bool:
|
|
200
|
+
if str(current_path.absolute()) == str(dir_path.absolute()):
|
|
201
|
+
return False
|
|
202
|
+
return current_path.name in FILTERED_DIRECTORIES and not include_filtered
|
|
203
|
+
|
|
204
|
+
# Collect all entries in a flat list for pagination
|
|
205
|
+
all_entries: List[Dict[str, Any]] = []
|
|
206
|
+
|
|
207
|
+
# Build the tree and collect entries
|
|
208
|
+
def collect_entries(current_path: Path, current_depth: int = 0, parent_path: str = "") -> None:
|
|
209
|
+
"""Collect entries in a flat list for pagination."""
|
|
210
|
+
if not self.is_path_allowed(str(current_path)):
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
# Sort entries: directories first, then files alphabetically
|
|
215
|
+
entries = sorted(current_path.iterdir(), key=lambda x: (not x.is_dir(), x.name))
|
|
216
|
+
|
|
217
|
+
for entry in entries:
|
|
218
|
+
if not self.is_path_allowed(str(entry)):
|
|
219
|
+
continue
|
|
220
|
+
|
|
221
|
+
# Calculate relative path for display
|
|
222
|
+
relative_path = f"{parent_path}/{entry.name}" if parent_path else entry.name
|
|
223
|
+
|
|
224
|
+
if entry.is_dir():
|
|
225
|
+
entry_data: Dict[str, Any] = {
|
|
226
|
+
"path": relative_path,
|
|
227
|
+
"type": "directory",
|
|
228
|
+
"depth": current_depth,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# Check if we should filter this directory
|
|
232
|
+
if should_filter(entry):
|
|
233
|
+
entry_data["skipped"] = "filtered-directory"
|
|
234
|
+
all_entries.append(entry_data)
|
|
235
|
+
continue
|
|
236
|
+
|
|
237
|
+
# Check depth limit
|
|
238
|
+
if depth > 0 and current_depth >= depth:
|
|
239
|
+
entry_data["skipped"] = "depth-limit"
|
|
240
|
+
all_entries.append(entry_data)
|
|
241
|
+
continue
|
|
242
|
+
|
|
243
|
+
# Add directory entry
|
|
244
|
+
all_entries.append(entry_data)
|
|
245
|
+
|
|
246
|
+
# Process children recursively
|
|
247
|
+
collect_entries(entry, current_depth + 1, relative_path)
|
|
248
|
+
else:
|
|
249
|
+
# Add file entry
|
|
250
|
+
if depth <= 0 or current_depth < depth:
|
|
251
|
+
all_entries.append(
|
|
252
|
+
{
|
|
253
|
+
"path": relative_path,
|
|
254
|
+
"type": "file",
|
|
255
|
+
"depth": current_depth,
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
except Exception as e:
|
|
260
|
+
await tool_ctx.warning(f"Error processing {current_path}: {str(e)}")
|
|
261
|
+
|
|
262
|
+
# Collect all entries
|
|
263
|
+
await tool_ctx.info("Collecting directory entries...")
|
|
264
|
+
collect_entries(dir_path)
|
|
265
|
+
|
|
266
|
+
# Paginate the results
|
|
267
|
+
paginated = paginate_list(all_entries, cursor, page_size)
|
|
268
|
+
|
|
269
|
+
# Format the paginated entries for display
|
|
270
|
+
formatted_entries = []
|
|
271
|
+
for entry in paginated.items:
|
|
272
|
+
indent = " " * entry["depth"]
|
|
273
|
+
if entry["type"] == "directory":
|
|
274
|
+
if "skipped" in entry:
|
|
275
|
+
formatted_entries.append(
|
|
276
|
+
{
|
|
277
|
+
"entry": f"{indent}{entry['path'].split('/')[-1]}/ [skipped - {entry['skipped']}]",
|
|
278
|
+
"type": "directory",
|
|
279
|
+
"skipped": entry.get("skipped"),
|
|
280
|
+
}
|
|
281
|
+
)
|
|
282
|
+
else:
|
|
283
|
+
formatted_entries.append(
|
|
284
|
+
{
|
|
285
|
+
"entry": f"{indent}{entry['path'].split('/')[-1]}/",
|
|
286
|
+
"type": "directory",
|
|
287
|
+
}
|
|
288
|
+
)
|
|
289
|
+
else:
|
|
290
|
+
formatted_entries.append(
|
|
291
|
+
{
|
|
292
|
+
"entry": f"{indent}{entry['path'].split('/')[-1]}",
|
|
293
|
+
"type": "file",
|
|
294
|
+
}
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# Build response
|
|
298
|
+
response = {
|
|
299
|
+
"entries": formatted_entries,
|
|
300
|
+
"total_collected": len(all_entries),
|
|
301
|
+
"page_size": page_size,
|
|
302
|
+
"current_page_count": len(formatted_entries),
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
# Add next cursor if available
|
|
306
|
+
if paginated.next_cursor:
|
|
307
|
+
response["nextCursor"] = paginated.next_cursor
|
|
308
|
+
|
|
309
|
+
await tool_ctx.info(
|
|
310
|
+
f"Returning page with {len(formatted_entries)} entries"
|
|
311
|
+
f"{' (more available)' if paginated.next_cursor else ' (end of results)'}"
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
return response
|
|
315
|
+
|
|
316
|
+
except Exception as e:
|
|
317
|
+
await tool_ctx.error(f"Error generating directory tree: {str(e)}")
|
|
318
|
+
return {"error": f"Error generating directory tree: {str(e)}"}
|
|
319
|
+
|
|
320
|
+
@override
|
|
321
|
+
def register(self, mcp_server: FastMCP) -> None:
|
|
322
|
+
"""Register this paginated directory tree tool with the MCP server."""
|
|
323
|
+
tool_self = self
|
|
324
|
+
|
|
325
|
+
@mcp_server.tool(name=self.name, description=self.description)
|
|
326
|
+
async def directory_tree_paginated(
|
|
327
|
+
path: DirectoryPath,
|
|
328
|
+
depth: Depth = 3,
|
|
329
|
+
include_filtered: IncludeFiltered = False,
|
|
330
|
+
page_size: PageSize = 100,
|
|
331
|
+
cursor: Cursor = None,
|
|
332
|
+
ctx: MCPContext = None,
|
|
333
|
+
) -> Dict[str, Any]:
|
|
334
|
+
return await tool_self.call(
|
|
335
|
+
ctx,
|
|
336
|
+
path=path,
|
|
337
|
+
depth=depth,
|
|
338
|
+
include_filtered=include_filtered,
|
|
339
|
+
page_size=page_size,
|
|
340
|
+
cursor=cursor,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
# Create the tool instance
|
|
345
|
+
directory_tree_paginated_tool = DirectoryTreePaginatedTool()
|
|
@@ -9,6 +9,8 @@ from pathlib import Path
|
|
|
9
9
|
|
|
10
10
|
from pydantic import Field
|
|
11
11
|
from mcp.server import FastMCP
|
|
12
|
+
|
|
13
|
+
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
|
|
12
14
|
from mcp.server.fastmcp import Context as MCPContext
|
|
13
15
|
|
|
14
16
|
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
|
|
@@ -88,6 +90,9 @@ Usage:
|
|
|
88
90
|
- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required."""
|
|
89
91
|
|
|
90
92
|
@override
|
|
93
|
+
@auto_timeout("edit")
|
|
94
|
+
|
|
95
|
+
|
|
91
96
|
async def call(
|
|
92
97
|
self,
|
|
93
98
|
ctx: MCPContext,
|
|
@@ -103,7 +108,7 @@ Usage:
|
|
|
103
108
|
Tool result
|
|
104
109
|
"""
|
|
105
110
|
tool_ctx = self.create_tool_context(ctx)
|
|
106
|
-
self.set_tool_context_info(tool_ctx)
|
|
111
|
+
await self.set_tool_context_info(tool_ctx)
|
|
107
112
|
|
|
108
113
|
# Extract parameters
|
|
109
114
|
file_path: FilePath = params["file_path"]
|
|
@@ -117,10 +122,6 @@ Usage:
|
|
|
117
122
|
await tool_ctx.error(path_validation.error_message)
|
|
118
123
|
return f"Error: {path_validation.error_message}"
|
|
119
124
|
|
|
120
|
-
# Expand path first (handles ~, $HOME, etc.)
|
|
121
|
-
expanded_path = self.expand_path(file_path)
|
|
122
|
-
file_path = expanded_path # Use expanded path for all operations
|
|
123
|
-
|
|
124
125
|
# Only validate old_string for non-empty if we're not creating a new file
|
|
125
126
|
# Empty old_string is valid when creating a new file
|
|
126
127
|
file_exists = Path(file_path).exists()
|