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
|
@@ -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
|
|
@@ -101,6 +103,9 @@ Can be run in dry-run mode to preview changes without applying them.
|
|
|
101
103
|
Only works within allowed directories."""
|
|
102
104
|
|
|
103
105
|
@override
|
|
106
|
+
@auto_timeout("content_replace")
|
|
107
|
+
|
|
108
|
+
|
|
104
109
|
async def call(
|
|
105
110
|
self,
|
|
106
111
|
ctx: MCPContext,
|
|
@@ -121,9 +126,6 @@ Only works within allowed directories."""
|
|
|
121
126
|
pattern: Pattern = params["pattern"]
|
|
122
127
|
replacement: Replacement = params["replacement"]
|
|
123
128
|
path: SearchPath = params["path"]
|
|
124
|
-
|
|
125
|
-
# Expand path (handles ~, $HOME, etc.)
|
|
126
|
-
path = self.expand_path(path)
|
|
127
129
|
file_pattern = params.get("file_pattern", "*") # Default to all files
|
|
128
130
|
dry_run = params.get("dry_run", False) # Default to False
|
|
129
131
|
|
|
@@ -5,6 +5,8 @@ from typing import override
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
from mcp.server import FastMCP
|
|
8
|
+
|
|
9
|
+
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
|
|
8
10
|
from mcp.server.fastmcp import Context as MCPContext
|
|
9
11
|
|
|
10
12
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
@@ -200,6 +202,9 @@ diff a.json b.json --ignore-whitespace"""
|
|
|
200
202
|
show_line_numbers=show_line_numbers,
|
|
201
203
|
)
|
|
202
204
|
|
|
205
|
+
@auto_timeout("diff")
|
|
206
|
+
|
|
207
|
+
|
|
203
208
|
async def call(self, ctx: MCPContext, **params) -> str:
|
|
204
209
|
"""Call the tool with arguments."""
|
|
205
210
|
return await self.run(
|
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
"""Directory tree tool implementation.
|
|
2
2
|
|
|
3
|
-
This module provides the DirectoryTreeTool for viewing file and directory structures
|
|
4
|
-
with optional pagination and different display styles.
|
|
3
|
+
This module provides the DirectoryTreeTool for viewing file and directory structures.
|
|
5
4
|
"""
|
|
6
5
|
|
|
7
|
-
from typing import Any,
|
|
6
|
+
from typing import Any, Unpack, Annotated, TypedDict, final, override
|
|
8
7
|
from pathlib import Path
|
|
9
|
-
import fnmatch
|
|
10
8
|
|
|
11
9
|
from pydantic import Field
|
|
12
10
|
from mcp.server import FastMCP
|
|
11
|
+
|
|
12
|
+
from hanzo_mcp.tools.common.auto_timeout import auto_timeout
|
|
13
13
|
from mcp.server.fastmcp import Context as MCPContext
|
|
14
14
|
|
|
15
15
|
from hanzo_mcp.tools.common.truncate import truncate_response
|
|
16
16
|
from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
|
|
17
|
-
from hanzo_mcp.tools.common.pagination import (
|
|
18
|
-
CursorManager,
|
|
19
|
-
paginate_list,
|
|
20
|
-
)
|
|
21
17
|
|
|
22
18
|
DirectoryPath = Annotated[
|
|
23
19
|
str,
|
|
@@ -45,52 +41,19 @@ IncludeFiltered = Annotated[
|
|
|
45
41
|
),
|
|
46
42
|
]
|
|
47
43
|
|
|
48
|
-
PageSize = Annotated[
|
|
49
|
-
Optional[int],
|
|
50
|
-
Field(
|
|
51
|
-
default=None,
|
|
52
|
-
description="Number of entries per page (enables pagination when set)",
|
|
53
|
-
title="Page Size",
|
|
54
|
-
),
|
|
55
|
-
]
|
|
56
|
-
|
|
57
|
-
Page = Annotated[
|
|
58
|
-
int,
|
|
59
|
-
Field(
|
|
60
|
-
default=1,
|
|
61
|
-
description="Page number for pagination",
|
|
62
|
-
title="Page",
|
|
63
|
-
),
|
|
64
|
-
]
|
|
65
|
-
|
|
66
|
-
Style = Annotated[
|
|
67
|
-
Literal["compact", "detailed", "unix"],
|
|
68
|
-
Field(
|
|
69
|
-
default="compact",
|
|
70
|
-
description="Display style: compact (default), detailed (with sizes), or unix (tree-like)",
|
|
71
|
-
title="Style",
|
|
72
|
-
),
|
|
73
|
-
]
|
|
74
|
-
|
|
75
44
|
|
|
76
|
-
class DirectoryTreeToolParams(TypedDict
|
|
45
|
+
class DirectoryTreeToolParams(TypedDict):
|
|
77
46
|
"""Parameters for the DirectoryTreeTool.
|
|
78
47
|
|
|
79
48
|
Attributes:
|
|
80
49
|
path: The path to the directory to view
|
|
81
50
|
depth: The maximum depth to traverse (0 for unlimited)
|
|
82
51
|
include_filtered: Include directories that are normally filtered
|
|
83
|
-
page_size: Number of entries per page (enables pagination when set)
|
|
84
|
-
page: Page number for pagination
|
|
85
|
-
style: Display style (compact, detailed, unix)
|
|
86
52
|
"""
|
|
87
53
|
|
|
88
|
-
path:
|
|
89
|
-
depth:
|
|
90
|
-
include_filtered:
|
|
91
|
-
page_size: Optional[int]
|
|
92
|
-
page: int
|
|
93
|
-
style: Literal["compact", "detailed", "unix"]
|
|
54
|
+
path: DirectoryPath
|
|
55
|
+
depth: Depth
|
|
56
|
+
include_filtered: IncludeFiltered
|
|
94
57
|
|
|
95
58
|
|
|
96
59
|
@final
|
|
@@ -121,21 +84,17 @@ Returns a structured view of the directory tree with files and subdirectories.
|
|
|
121
84
|
Directories are marked with trailing slashes. The output is formatted as an
|
|
122
85
|
indented list for readability. By default, common development directories like
|
|
123
86
|
.git, node_modules, and venv are noted but not traversed unless explicitly
|
|
124
|
-
requested. Only works within allowed directories.
|
|
87
|
+
requested. Only works within allowed directories."""
|
|
125
88
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
- detailed: Includes file sizes and additional metadata
|
|
129
|
-
- unix: Traditional unix tree command style with ASCII art
|
|
89
|
+
@override
|
|
90
|
+
@auto_timeout("directory_tree")
|
|
130
91
|
|
|
131
|
-
Optional pagination is available by setting page_size parameter."""
|
|
132
92
|
|
|
133
|
-
@override
|
|
134
93
|
async def call(
|
|
135
94
|
self,
|
|
136
95
|
ctx: MCPContext,
|
|
137
96
|
**params: Unpack[DirectoryTreeToolParams],
|
|
138
|
-
) ->
|
|
97
|
+
) -> str:
|
|
139
98
|
"""Execute the tool with the given parameters.
|
|
140
99
|
|
|
141
100
|
Args:
|
|
@@ -148,18 +107,9 @@ Optional pagination is available by setting page_size parameter."""
|
|
|
148
107
|
tool_ctx = self.create_tool_context(ctx)
|
|
149
108
|
|
|
150
109
|
# Extract parameters
|
|
151
|
-
path:
|
|
110
|
+
path: DirectoryPath = params["path"]
|
|
152
111
|
depth = params.get("depth", 3) # Default depth is 3
|
|
153
112
|
include_filtered = params.get("include_filtered", False) # Default to False
|
|
154
|
-
page_size = params.get("page_size") # Optional pagination
|
|
155
|
-
page = params.get("page", 1)
|
|
156
|
-
style = params.get("style", "compact")
|
|
157
|
-
|
|
158
|
-
# Expand path (handles ~, $HOME, etc.)
|
|
159
|
-
path = self.expand_path(path)
|
|
160
|
-
|
|
161
|
-
# For pagination, we need to use offset-based pagination
|
|
162
|
-
offset = (page - 1) * page_size if page_size else None
|
|
163
113
|
|
|
164
114
|
# Validate path parameter
|
|
165
115
|
path_validation = self.validate_path(path)
|
|
@@ -167,8 +117,7 @@ Optional pagination is available by setting page_size parameter."""
|
|
|
167
117
|
await tool_ctx.error(path_validation.error_message)
|
|
168
118
|
return f"Error: {path_validation.error_message}"
|
|
169
119
|
|
|
170
|
-
|
|
171
|
-
await tool_ctx.info(f"Getting directory tree: {path} (depth: {depth}, include_filtered: {include_filtered}, style: {style}){pagination_info}")
|
|
120
|
+
await tool_ctx.info(f"Getting directory tree: {path} (depth: {depth}, include_filtered: {include_filtered})")
|
|
172
121
|
|
|
173
122
|
# Check if path is allowed
|
|
174
123
|
allowed, error_msg = await self.check_path_allowed(path, tool_ctx)
|
|
@@ -227,115 +176,7 @@ Optional pagination is available by setting page_size parameter."""
|
|
|
227
176
|
"skipped_filtered": 0,
|
|
228
177
|
}
|
|
229
178
|
|
|
230
|
-
#
|
|
231
|
-
if page_size:
|
|
232
|
-
all_entries: List[Dict[str, Any]] = []
|
|
233
|
-
|
|
234
|
-
async def collect_entries(current_path: Path, current_depth: int = 0, parent_path: str = "") -> None:
|
|
235
|
-
"""Collect entries in a flat list for pagination."""
|
|
236
|
-
if not self.is_path_allowed(str(current_path)):
|
|
237
|
-
return
|
|
238
|
-
|
|
239
|
-
try:
|
|
240
|
-
# Sort entries: directories first, then files alphabetically
|
|
241
|
-
entries = sorted(current_path.iterdir(), key=lambda x: (not x.is_dir(), x.name))
|
|
242
|
-
|
|
243
|
-
for entry in entries:
|
|
244
|
-
if not self.is_path_allowed(str(entry)):
|
|
245
|
-
continue
|
|
246
|
-
|
|
247
|
-
# Calculate relative path for display
|
|
248
|
-
relative_path = f"{parent_path}/{entry.name}" if parent_path else entry.name
|
|
249
|
-
|
|
250
|
-
if entry.is_dir():
|
|
251
|
-
stats["directories"] += 1
|
|
252
|
-
entry_data: Dict[str, Any] = {
|
|
253
|
-
"path": relative_path,
|
|
254
|
-
"name": entry.name,
|
|
255
|
-
"type": "directory",
|
|
256
|
-
"depth": current_depth,
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
# Add size info for detailed style
|
|
260
|
-
if style == "detailed":
|
|
261
|
-
try:
|
|
262
|
-
entry_data["size"] = sum(f.stat().st_size for f in entry.rglob('*') if f.is_file())
|
|
263
|
-
except Exception:
|
|
264
|
-
entry_data["size"] = 0
|
|
265
|
-
|
|
266
|
-
# Check if we should filter this directory
|
|
267
|
-
if should_filter(entry):
|
|
268
|
-
entry_data["skipped"] = "filtered-directory"
|
|
269
|
-
stats["skipped_filtered"] += 1
|
|
270
|
-
all_entries.append(entry_data)
|
|
271
|
-
continue
|
|
272
|
-
|
|
273
|
-
# Check depth limit
|
|
274
|
-
if depth > 0 and current_depth >= depth:
|
|
275
|
-
entry_data["skipped"] = "depth-limit"
|
|
276
|
-
stats["skipped_depth"] += 1
|
|
277
|
-
all_entries.append(entry_data)
|
|
278
|
-
continue
|
|
279
|
-
|
|
280
|
-
# Add directory entry
|
|
281
|
-
all_entries.append(entry_data)
|
|
282
|
-
|
|
283
|
-
# Process children recursively
|
|
284
|
-
await collect_entries(entry, current_depth + 1, relative_path)
|
|
285
|
-
else:
|
|
286
|
-
# Add file entry
|
|
287
|
-
if depth <= 0 or current_depth < depth:
|
|
288
|
-
stats["files"] += 1
|
|
289
|
-
file_data = {
|
|
290
|
-
"path": relative_path,
|
|
291
|
-
"name": entry.name,
|
|
292
|
-
"type": "file",
|
|
293
|
-
"depth": current_depth,
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
# Add size info for detailed style
|
|
297
|
-
if style == "detailed":
|
|
298
|
-
try:
|
|
299
|
-
file_data["size"] = entry.stat().st_size
|
|
300
|
-
except Exception:
|
|
301
|
-
file_data["size"] = 0
|
|
302
|
-
|
|
303
|
-
all_entries.append(file_data)
|
|
304
|
-
|
|
305
|
-
except Exception as e:
|
|
306
|
-
await tool_ctx.warning(f"Error processing {current_path}: {str(e)}")
|
|
307
|
-
|
|
308
|
-
# Collect all entries
|
|
309
|
-
await tool_ctx.info("Collecting directory entries for pagination...")
|
|
310
|
-
await collect_entries(dir_path)
|
|
311
|
-
|
|
312
|
-
# Apply pagination using offset
|
|
313
|
-
start_idx = offset if offset else 0
|
|
314
|
-
end_idx = start_idx + page_size
|
|
315
|
-
paginated_entries = all_entries[start_idx:end_idx]
|
|
316
|
-
|
|
317
|
-
# Format entries based on style
|
|
318
|
-
formatted_entries = self._format_entries(paginated_entries, style)
|
|
319
|
-
|
|
320
|
-
# Build paginated response
|
|
321
|
-
response = {
|
|
322
|
-
"entries": formatted_entries,
|
|
323
|
-
"total_entries": len(all_entries),
|
|
324
|
-
"page": page,
|
|
325
|
-
"page_size": page_size,
|
|
326
|
-
"total_pages": (len(all_entries) + page_size - 1) // page_size,
|
|
327
|
-
"has_next": end_idx < len(all_entries),
|
|
328
|
-
"stats": {
|
|
329
|
-
"directories": stats["directories"],
|
|
330
|
-
"files": stats["files"],
|
|
331
|
-
"skipped_depth": stats["skipped_depth"],
|
|
332
|
-
"skipped_filtered": stats["skipped_filtered"],
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
return response
|
|
337
|
-
|
|
338
|
-
# Non-paginated: Build the tree recursively
|
|
179
|
+
# Build the tree recursively
|
|
339
180
|
async def build_tree(current_path: Path, current_depth: int = 0) -> list[dict[str, Any]]:
|
|
340
181
|
result: list[dict[str, Any]] = []
|
|
341
182
|
|
|
@@ -358,13 +199,6 @@ Optional pagination is available by setting page_size parameter."""
|
|
|
358
199
|
"name": entry.name,
|
|
359
200
|
"type": "directory",
|
|
360
201
|
}
|
|
361
|
-
|
|
362
|
-
# Add size info for detailed style
|
|
363
|
-
if style == "detailed":
|
|
364
|
-
try:
|
|
365
|
-
entry_data["size"] = sum(f.stat().st_size for f in entry.rglob('*') if f.is_file())
|
|
366
|
-
except Exception:
|
|
367
|
-
entry_data["size"] = 0
|
|
368
202
|
|
|
369
203
|
# Check if we should filter this directory
|
|
370
204
|
if should_filter(entry):
|
|
@@ -387,80 +221,42 @@ Optional pagination is available by setting page_size parameter."""
|
|
|
387
221
|
# Files should be at the same level check as directories
|
|
388
222
|
if depth <= 0 or current_depth < depth:
|
|
389
223
|
stats["files"] += 1
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
# Add size info for detailed style
|
|
393
|
-
if style == "detailed":
|
|
394
|
-
try:
|
|
395
|
-
file_data["size"] = entry.stat().st_size
|
|
396
|
-
except Exception:
|
|
397
|
-
file_data["size"] = 0
|
|
398
|
-
|
|
399
|
-
result.append(file_data)
|
|
224
|
+
# Add file entry
|
|
225
|
+
result.append({"name": entry.name, "type": "file"})
|
|
400
226
|
|
|
401
227
|
except Exception as e:
|
|
402
228
|
await tool_ctx.warning(f"Error processing {current_path}: {str(e)}")
|
|
403
229
|
|
|
404
230
|
return result
|
|
405
231
|
|
|
406
|
-
# Format the tree
|
|
407
|
-
def format_tree(tree_data: list[dict[str, Any]], level: int = 0
|
|
232
|
+
# Format the tree as a simple indented structure
|
|
233
|
+
def format_tree(tree_data: list[dict[str, Any]], level: int = 0) -> list[str]:
|
|
408
234
|
lines = []
|
|
409
235
|
|
|
410
|
-
for
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
if style == "unix":
|
|
414
|
-
# Unix tree style with ASCII art
|
|
415
|
-
if level == 0:
|
|
416
|
-
current_prefix = ""
|
|
417
|
-
next_prefix = ""
|
|
418
|
-
else:
|
|
419
|
-
if is_last_item:
|
|
420
|
-
current_prefix = prefix + "└── "
|
|
421
|
-
next_prefix = prefix + " "
|
|
422
|
-
else:
|
|
423
|
-
current_prefix = prefix + "├── "
|
|
424
|
-
next_prefix = prefix + "│ "
|
|
425
|
-
else:
|
|
426
|
-
# Compact or detailed style with simple indentation
|
|
427
|
-
current_prefix = " " * level
|
|
428
|
-
next_prefix = " " * (level + 1)
|
|
236
|
+
for item in tree_data:
|
|
237
|
+
# Indentation based on level
|
|
238
|
+
indent = " " * level
|
|
429
239
|
|
|
430
240
|
# Format based on type
|
|
431
241
|
if item["type"] == "directory":
|
|
432
242
|
if "skipped" in item:
|
|
433
|
-
|
|
243
|
+
lines.append(f"{indent}{item['name']}/ [skipped - {item['skipped']}]")
|
|
434
244
|
else:
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
# Add children with increased indentation if present
|
|
441
|
-
if "children" in item and "skipped" not in item:
|
|
442
|
-
lines.extend(format_tree(item["children"], level + 1, next_prefix, is_last_item))
|
|
245
|
+
lines.append(f"{indent}{item['name']}/")
|
|
246
|
+
# Add children with increased indentation if present
|
|
247
|
+
if "children" in item:
|
|
248
|
+
lines.extend(format_tree(item["children"], level + 1))
|
|
443
249
|
else:
|
|
444
250
|
# File
|
|
445
|
-
|
|
446
|
-
if style == "detailed" and "size" in item:
|
|
447
|
-
line += f" ({self._format_size(item['size'])})"
|
|
448
|
-
lines.append(line)
|
|
251
|
+
lines.append(f"{indent}{item['name']}")
|
|
449
252
|
|
|
450
253
|
return lines
|
|
451
254
|
|
|
452
255
|
# Build tree starting from the requested directory
|
|
453
256
|
tree_data = await build_tree(dir_path)
|
|
454
257
|
|
|
455
|
-
# Format
|
|
456
|
-
|
|
457
|
-
# Start with the root directory name
|
|
458
|
-
formatted_lines = [str(dir_path)]
|
|
459
|
-
formatted_lines.extend(format_tree(tree_data))
|
|
460
|
-
else:
|
|
461
|
-
formatted_lines = format_tree(tree_data)
|
|
462
|
-
|
|
463
|
-
formatted_output = "\n".join(formatted_lines)
|
|
258
|
+
# Format as simple text
|
|
259
|
+
formatted_output = "\n".join(format_tree(tree_data))
|
|
464
260
|
|
|
465
261
|
# Add stats summary
|
|
466
262
|
summary = (
|
|
@@ -470,7 +266,7 @@ Optional pagination is available by setting page_size parameter."""
|
|
|
470
266
|
)
|
|
471
267
|
|
|
472
268
|
await tool_ctx.info(
|
|
473
|
-
f"Generated directory tree for {path} (depth: {depth}, include_filtered: {include_filtered}
|
|
269
|
+
f"Generated directory tree for {path} (depth: {depth}, include_filtered: {include_filtered})"
|
|
474
270
|
)
|
|
475
271
|
|
|
476
272
|
# Truncate response to stay within token limits
|
|
@@ -478,43 +274,11 @@ Optional pagination is available by setting page_size parameter."""
|
|
|
478
274
|
return truncate_response(
|
|
479
275
|
full_response,
|
|
480
276
|
max_tokens=25000,
|
|
481
|
-
truncation_message="\n\n[Response truncated due to token limit. Please use
|
|
277
|
+
truncation_message="\n\n[Response truncated due to token limit. Please use a smaller depth, specific subdirectory, or the paginated version of this tool.]",
|
|
482
278
|
)
|
|
483
279
|
except Exception as e:
|
|
484
280
|
await tool_ctx.error(f"Error generating directory tree: {str(e)}")
|
|
485
|
-
if page_size:
|
|
486
|
-
return {"error": f"Error generating directory tree: {str(e)}"}
|
|
487
281
|
return f"Error generating directory tree: {str(e)}"
|
|
488
|
-
|
|
489
|
-
def _format_entries(self, entries: List[Dict[str, Any]], style: str) -> List[str]:
|
|
490
|
-
"""Format entries for paginated output."""
|
|
491
|
-
formatted = []
|
|
492
|
-
for entry in entries:
|
|
493
|
-
indent = " " * entry["depth"]
|
|
494
|
-
name = entry["name"]
|
|
495
|
-
|
|
496
|
-
if entry["type"] == "directory":
|
|
497
|
-
if "skipped" in entry:
|
|
498
|
-
line = f"{indent}{name}/ [skipped - {entry['skipped']}]"
|
|
499
|
-
else:
|
|
500
|
-
line = f"{indent}{name}/"
|
|
501
|
-
if style == "detailed" and "size" in entry:
|
|
502
|
-
line += f" ({self._format_size(entry['size'])})"
|
|
503
|
-
else:
|
|
504
|
-
line = f"{indent}{name}"
|
|
505
|
-
if style == "detailed" and "size" in entry:
|
|
506
|
-
line += f" ({self._format_size(entry['size'])})"
|
|
507
|
-
|
|
508
|
-
formatted.append(line)
|
|
509
|
-
return formatted
|
|
510
|
-
|
|
511
|
-
def _format_size(self, size: int) -> str:
|
|
512
|
-
"""Format file size in human-readable format."""
|
|
513
|
-
for unit in ["B", "KB", "MB", "GB", "TB"]:
|
|
514
|
-
if size < 1024.0:
|
|
515
|
-
return f"{size:.1f}{unit}"
|
|
516
|
-
size /= 1024.0
|
|
517
|
-
return f"{size:.1f}PB"
|
|
518
282
|
|
|
519
283
|
@override
|
|
520
284
|
def register(self, mcp_server: FastMCP) -> None:
|
|
@@ -534,16 +298,5 @@ Optional pagination is available by setting page_size parameter."""
|
|
|
534
298
|
path: DirectoryPath,
|
|
535
299
|
depth: Depth = 3,
|
|
536
300
|
include_filtered: IncludeFiltered = False,
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
style: Style = "compact",
|
|
540
|
-
) -> Union[str, Dict[str, Any]]:
|
|
541
|
-
return await tool_self.call(
|
|
542
|
-
ctx,
|
|
543
|
-
path=path,
|
|
544
|
-
depth=depth,
|
|
545
|
-
include_filtered=include_filtered,
|
|
546
|
-
page_size=page_size,
|
|
547
|
-
page=page,
|
|
548
|
-
style=style
|
|
549
|
-
)
|
|
301
|
+
) -> str:
|
|
302
|
+
return await tool_self.call(ctx, path=path, depth=depth, include_filtered=include_filtered)
|