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.

Files changed (135) hide show
  1. hanzo_mcp/__init__.py +1 -1
  2. hanzo_mcp/analytics/posthog_analytics.py +14 -1
  3. hanzo_mcp/cli.py +108 -4
  4. hanzo_mcp/server.py +11 -0
  5. hanzo_mcp/tools/__init__.py +3 -16
  6. hanzo_mcp/tools/agent/__init__.py +5 -0
  7. hanzo_mcp/tools/agent/agent.py +5 -0
  8. hanzo_mcp/tools/agent/agent_tool.py +3 -17
  9. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +623 -0
  10. hanzo_mcp/tools/agent/clarification_tool.py +7 -1
  11. hanzo_mcp/tools/agent/claude_desktop_auth.py +16 -6
  12. hanzo_mcp/tools/agent/cli_agent_base.py +5 -0
  13. hanzo_mcp/tools/agent/cli_tools.py +26 -0
  14. hanzo_mcp/tools/agent/code_auth_tool.py +5 -0
  15. hanzo_mcp/tools/agent/critic_tool.py +7 -1
  16. hanzo_mcp/tools/agent/iching_tool.py +5 -0
  17. hanzo_mcp/tools/agent/network_tool.py +5 -0
  18. hanzo_mcp/tools/agent/review_tool.py +7 -1
  19. hanzo_mcp/tools/agent/swarm_alias.py +5 -0
  20. hanzo_mcp/tools/agent/swarm_tool.py +701 -0
  21. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +554 -0
  22. hanzo_mcp/tools/agent/unified_cli_tools.py +5 -0
  23. hanzo_mcp/tools/common/auto_timeout.py +254 -0
  24. hanzo_mcp/tools/common/base.py +4 -0
  25. hanzo_mcp/tools/common/batch_tool.py +5 -0
  26. hanzo_mcp/tools/common/config_tool.py +5 -0
  27. hanzo_mcp/tools/common/critic_tool.py +5 -0
  28. hanzo_mcp/tools/common/paginated_base.py +4 -0
  29. hanzo_mcp/tools/common/permissions.py +38 -12
  30. hanzo_mcp/tools/common/personality.py +673 -980
  31. hanzo_mcp/tools/common/stats.py +5 -0
  32. hanzo_mcp/tools/common/thinking_tool.py +5 -0
  33. hanzo_mcp/tools/common/timeout_parser.py +103 -0
  34. hanzo_mcp/tools/common/tool_disable.py +5 -0
  35. hanzo_mcp/tools/common/tool_enable.py +5 -0
  36. hanzo_mcp/tools/common/tool_list.py +5 -0
  37. hanzo_mcp/tools/config/config_tool.py +5 -0
  38. hanzo_mcp/tools/config/mode_tool.py +5 -0
  39. hanzo_mcp/tools/database/graph.py +5 -0
  40. hanzo_mcp/tools/database/graph_add.py +5 -0
  41. hanzo_mcp/tools/database/graph_query.py +5 -0
  42. hanzo_mcp/tools/database/graph_remove.py +5 -0
  43. hanzo_mcp/tools/database/graph_search.py +5 -0
  44. hanzo_mcp/tools/database/graph_stats.py +5 -0
  45. hanzo_mcp/tools/database/sql.py +5 -0
  46. hanzo_mcp/tools/database/sql_query.py +2 -0
  47. hanzo_mcp/tools/database/sql_search.py +5 -0
  48. hanzo_mcp/tools/database/sql_stats.py +5 -0
  49. hanzo_mcp/tools/editor/neovim_command.py +5 -0
  50. hanzo_mcp/tools/editor/neovim_edit.py +7 -2
  51. hanzo_mcp/tools/editor/neovim_session.py +5 -0
  52. hanzo_mcp/tools/filesystem/__init__.py +23 -26
  53. hanzo_mcp/tools/filesystem/ast_tool.py +3 -4
  54. hanzo_mcp/tools/filesystem/base.py +2 -18
  55. hanzo_mcp/tools/filesystem/batch_search.py +825 -0
  56. hanzo_mcp/tools/filesystem/content_replace.py +5 -3
  57. hanzo_mcp/tools/filesystem/diff.py +5 -0
  58. hanzo_mcp/tools/filesystem/directory_tree.py +34 -281
  59. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +345 -0
  60. hanzo_mcp/tools/filesystem/edit.py +6 -5
  61. hanzo_mcp/tools/filesystem/find.py +177 -311
  62. hanzo_mcp/tools/filesystem/find_files.py +370 -0
  63. hanzo_mcp/tools/filesystem/git_search.py +5 -3
  64. hanzo_mcp/tools/filesystem/grep.py +454 -0
  65. hanzo_mcp/tools/filesystem/multi_edit.py +6 -5
  66. hanzo_mcp/tools/filesystem/read.py +10 -9
  67. hanzo_mcp/tools/filesystem/rules_tool.py +6 -4
  68. hanzo_mcp/tools/filesystem/search_tool.py +728 -0
  69. hanzo_mcp/tools/filesystem/symbols_tool.py +510 -0
  70. hanzo_mcp/tools/filesystem/tree.py +273 -0
  71. hanzo_mcp/tools/filesystem/watch.py +6 -1
  72. hanzo_mcp/tools/filesystem/write.py +13 -7
  73. hanzo_mcp/tools/jupyter/jupyter.py +30 -2
  74. hanzo_mcp/tools/jupyter/notebook_edit.py +298 -0
  75. hanzo_mcp/tools/jupyter/notebook_read.py +148 -0
  76. hanzo_mcp/tools/llm/consensus_tool.py +8 -6
  77. hanzo_mcp/tools/llm/llm_manage.py +5 -0
  78. hanzo_mcp/tools/llm/llm_tool.py +2 -0
  79. hanzo_mcp/tools/llm/llm_unified.py +5 -0
  80. hanzo_mcp/tools/llm/provider_tools.py +5 -0
  81. hanzo_mcp/tools/lsp/lsp_tool.py +475 -622
  82. hanzo_mcp/tools/mcp/mcp_add.py +7 -2
  83. hanzo_mcp/tools/mcp/mcp_remove.py +15 -2
  84. hanzo_mcp/tools/mcp/mcp_stats.py +5 -0
  85. hanzo_mcp/tools/mcp/mcp_tool.py +5 -0
  86. hanzo_mcp/tools/memory/knowledge_tools.py +14 -0
  87. hanzo_mcp/tools/memory/memory_tools.py +17 -0
  88. hanzo_mcp/tools/search/find_tool.py +5 -3
  89. hanzo_mcp/tools/search/unified_search.py +3 -1
  90. hanzo_mcp/tools/shell/__init__.py +2 -14
  91. hanzo_mcp/tools/shell/base_process.py +4 -2
  92. hanzo_mcp/tools/shell/bash_tool.py +2 -0
  93. hanzo_mcp/tools/shell/command_executor.py +7 -7
  94. hanzo_mcp/tools/shell/logs.py +5 -0
  95. hanzo_mcp/tools/shell/npx.py +5 -0
  96. hanzo_mcp/tools/shell/npx_background.py +5 -0
  97. hanzo_mcp/tools/shell/npx_tool.py +5 -0
  98. hanzo_mcp/tools/shell/open.py +5 -0
  99. hanzo_mcp/tools/shell/pkill.py +5 -0
  100. hanzo_mcp/tools/shell/process_tool.py +5 -0
  101. hanzo_mcp/tools/shell/processes.py +5 -0
  102. hanzo_mcp/tools/shell/run_background.py +5 -0
  103. hanzo_mcp/tools/shell/run_command.py +2 -0
  104. hanzo_mcp/tools/shell/run_command_windows.py +5 -0
  105. hanzo_mcp/tools/shell/streaming_command.py +5 -0
  106. hanzo_mcp/tools/shell/uvx.py +5 -0
  107. hanzo_mcp/tools/shell/uvx_background.py +5 -0
  108. hanzo_mcp/tools/shell/uvx_tool.py +5 -0
  109. hanzo_mcp/tools/shell/zsh_tool.py +3 -0
  110. hanzo_mcp/tools/todo/todo.py +5 -0
  111. hanzo_mcp/tools/todo/todo_read.py +142 -0
  112. hanzo_mcp/tools/todo/todo_write.py +367 -0
  113. hanzo_mcp/tools/vector/__init__.py +42 -95
  114. hanzo_mcp/tools/vector/index_tool.py +5 -0
  115. hanzo_mcp/tools/vector/vector.py +5 -0
  116. hanzo_mcp/tools/vector/vector_index.py +5 -0
  117. hanzo_mcp/tools/vector/vector_search.py +5 -0
  118. {hanzo_mcp-0.9.0.dist-info → hanzo_mcp-0.9.2.dist-info}/METADATA +1 -1
  119. hanzo_mcp-0.9.2.dist-info/RECORD +195 -0
  120. hanzo_mcp/tools/common/path_utils.py +0 -34
  121. hanzo_mcp/tools/compiler/__init__.py +0 -8
  122. hanzo_mcp/tools/compiler/sandboxed_compiler.py +0 -681
  123. hanzo_mcp/tools/environment/__init__.py +0 -8
  124. hanzo_mcp/tools/environment/environment_detector.py +0 -594
  125. hanzo_mcp/tools/filesystem/search.py +0 -1160
  126. hanzo_mcp/tools/framework/__init__.py +0 -8
  127. hanzo_mcp/tools/framework/framework_modes.py +0 -714
  128. hanzo_mcp/tools/memory/conversation_memory.py +0 -636
  129. hanzo_mcp/tools/shell/run_tool.py +0 -56
  130. hanzo_mcp/tools/vector/node_tool.py +0 -538
  131. hanzo_mcp/tools/vector/unified_vector.py +0 -384
  132. hanzo_mcp-0.9.0.dist-info/RECORD +0 -191
  133. {hanzo_mcp-0.9.0.dist-info → hanzo_mcp-0.9.2.dist-info}/WHEEL +0 -0
  134. {hanzo_mcp-0.9.0.dist-info → hanzo_mcp-0.9.2.dist-info}/entry_points.txt +0 -0
  135. {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()