hanzo-mcp 0.6.13__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hanzo-mcp might be problematic. Click here for more details.

Files changed (62) hide show
  1. hanzo_mcp/analytics/__init__.py +5 -0
  2. hanzo_mcp/analytics/posthog_analytics.py +364 -0
  3. hanzo_mcp/cli.py +3 -3
  4. hanzo_mcp/cli_enhanced.py +3 -3
  5. hanzo_mcp/config/settings.py +1 -1
  6. hanzo_mcp/config/tool_config.py +18 -4
  7. hanzo_mcp/server.py +34 -1
  8. hanzo_mcp/tools/__init__.py +65 -2
  9. hanzo_mcp/tools/agent/__init__.py +84 -3
  10. hanzo_mcp/tools/agent/agent_tool.py +102 -4
  11. hanzo_mcp/tools/agent/agent_tool_v2.py +459 -0
  12. hanzo_mcp/tools/agent/clarification_protocol.py +220 -0
  13. hanzo_mcp/tools/agent/clarification_tool.py +68 -0
  14. hanzo_mcp/tools/agent/claude_cli_tool.py +125 -0
  15. hanzo_mcp/tools/agent/claude_desktop_auth.py +508 -0
  16. hanzo_mcp/tools/agent/cli_agent_base.py +191 -0
  17. hanzo_mcp/tools/agent/code_auth.py +436 -0
  18. hanzo_mcp/tools/agent/code_auth_tool.py +194 -0
  19. hanzo_mcp/tools/agent/codex_cli_tool.py +123 -0
  20. hanzo_mcp/tools/agent/critic_tool.py +376 -0
  21. hanzo_mcp/tools/agent/gemini_cli_tool.py +128 -0
  22. hanzo_mcp/tools/agent/grok_cli_tool.py +128 -0
  23. hanzo_mcp/tools/agent/iching_tool.py +380 -0
  24. hanzo_mcp/tools/agent/network_tool.py +273 -0
  25. hanzo_mcp/tools/agent/prompt.py +62 -20
  26. hanzo_mcp/tools/agent/review_tool.py +433 -0
  27. hanzo_mcp/tools/agent/swarm_tool.py +535 -0
  28. hanzo_mcp/tools/agent/swarm_tool_v2.py +594 -0
  29. hanzo_mcp/tools/common/base.py +1 -0
  30. hanzo_mcp/tools/common/batch_tool.py +102 -10
  31. hanzo_mcp/tools/common/fastmcp_pagination.py +369 -0
  32. hanzo_mcp/tools/common/forgiving_edit.py +243 -0
  33. hanzo_mcp/tools/common/paginated_base.py +230 -0
  34. hanzo_mcp/tools/common/paginated_response.py +307 -0
  35. hanzo_mcp/tools/common/pagination.py +226 -0
  36. hanzo_mcp/tools/common/tool_list.py +3 -0
  37. hanzo_mcp/tools/common/truncate.py +101 -0
  38. hanzo_mcp/tools/filesystem/__init__.py +29 -0
  39. hanzo_mcp/tools/filesystem/ast_multi_edit.py +562 -0
  40. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +338 -0
  41. hanzo_mcp/tools/lsp/__init__.py +5 -0
  42. hanzo_mcp/tools/lsp/lsp_tool.py +512 -0
  43. hanzo_mcp/tools/memory/__init__.py +76 -0
  44. hanzo_mcp/tools/memory/knowledge_tools.py +518 -0
  45. hanzo_mcp/tools/memory/memory_tools.py +456 -0
  46. hanzo_mcp/tools/search/__init__.py +6 -0
  47. hanzo_mcp/tools/search/find_tool.py +581 -0
  48. hanzo_mcp/tools/search/unified_search.py +953 -0
  49. hanzo_mcp/tools/shell/__init__.py +5 -0
  50. hanzo_mcp/tools/shell/auto_background.py +203 -0
  51. hanzo_mcp/tools/shell/base_process.py +53 -27
  52. hanzo_mcp/tools/shell/bash_tool.py +17 -33
  53. hanzo_mcp/tools/shell/npx_tool.py +15 -32
  54. hanzo_mcp/tools/shell/streaming_command.py +594 -0
  55. hanzo_mcp/tools/shell/uvx_tool.py +15 -32
  56. hanzo_mcp/types.py +23 -0
  57. {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/METADATA +228 -71
  58. {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/RECORD +61 -24
  59. hanzo_mcp-0.6.13.dist-info/licenses/LICENSE +0 -21
  60. {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/WHEEL +0 -0
  61. {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/entry_points.txt +0 -0
  62. {hanzo_mcp-0.6.13.dist-info → hanzo_mcp-0.7.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,338 @@
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 pathlib import Path
8
+ from typing import Annotated, Any, Dict, List, Optional, TypedDict, Unpack, final, override
9
+
10
+ from mcp.server.fastmcp import Context as MCPContext
11
+ from mcp.server import FastMCP
12
+ from pydantic import Field
13
+
14
+ from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
15
+ from hanzo_mcp.tools.common.pagination import (
16
+ CursorManager,
17
+ PaginatedResponse,
18
+ paginate_list
19
+ )
20
+
21
+ DirectoryPath = Annotated[
22
+ str,
23
+ Field(
24
+ description="The path to the directory to view",
25
+ title="Path",
26
+ ),
27
+ ]
28
+
29
+ Depth = Annotated[
30
+ int,
31
+ Field(
32
+ default=3,
33
+ description="The maximum depth to traverse (0 for unlimited)",
34
+ title="Depth",
35
+ ),
36
+ ]
37
+
38
+ IncludeFiltered = Annotated[
39
+ bool,
40
+ Field(
41
+ default=False,
42
+ description="Include directories that are normally filtered",
43
+ title="Include Filtered",
44
+ ),
45
+ ]
46
+
47
+ PageSize = Annotated[
48
+ int,
49
+ Field(
50
+ default=100,
51
+ description="Number of entries per page",
52
+ title="Page Size",
53
+ ),
54
+ ]
55
+
56
+ Cursor = Annotated[
57
+ Optional[str],
58
+ Field(
59
+ default=None,
60
+ description="Pagination cursor for continuing from previous request",
61
+ title="Cursor",
62
+ ),
63
+ ]
64
+
65
+
66
+ class DirectoryTreePaginatedParams(TypedDict):
67
+ """Parameters for the paginated DirectoryTreeTool.
68
+
69
+ Attributes:
70
+ path: The path to the directory to view
71
+ depth: The maximum depth to traverse (0 for unlimited)
72
+ include_filtered: Include directories that are normally filtered
73
+ page_size: Number of entries per page
74
+ cursor: Pagination cursor
75
+ """
76
+
77
+ path: DirectoryPath
78
+ depth: Depth
79
+ include_filtered: IncludeFiltered
80
+ page_size: PageSize
81
+ cursor: Cursor
82
+
83
+
84
+ @final
85
+ class DirectoryTreePaginatedTool(FilesystemBaseTool):
86
+ """Tool for viewing directory structure as a tree with pagination support."""
87
+
88
+ @property
89
+ @override
90
+ def name(self) -> str:
91
+ """Get the tool name."""
92
+ return "directory_tree_paginated"
93
+
94
+ @property
95
+ @override
96
+ def description(self) -> str:
97
+ """Get the tool description."""
98
+ return """Get a paginated recursive tree view of files and directories.
99
+
100
+ This is a paginated version of directory_tree that supports cursor-based pagination
101
+ for large directory structures. Returns a structured view with files and subdirectories.
102
+
103
+ Directories are marked with trailing slashes. Common development directories like
104
+ .git, node_modules, and venv are noted but not traversed unless explicitly requested.
105
+
106
+ Use the cursor field to continue from where the previous request left off.
107
+ Returns nextCursor if more entries are available."""
108
+
109
+ @override
110
+ async def call(
111
+ self,
112
+ ctx: MCPContext,
113
+ **params: Unpack[DirectoryTreePaginatedParams],
114
+ ) -> Dict[str, Any]:
115
+ """Execute the tool with the given parameters.
116
+
117
+ Args:
118
+ ctx: MCP context
119
+ **params: Tool parameters
120
+
121
+ Returns:
122
+ Dictionary with entries and optional nextCursor
123
+ """
124
+ tool_ctx = self.create_tool_context(ctx)
125
+
126
+ # Extract parameters
127
+ path: DirectoryPath = params["path"]
128
+ depth = params.get("depth", 3)
129
+ include_filtered = params.get("include_filtered", False)
130
+ page_size = params.get("page_size", 100)
131
+ cursor = params.get("cursor", None)
132
+
133
+ # Validate cursor if provided
134
+ if cursor:
135
+ cursor_data = CursorManager.parse_cursor(cursor)
136
+ if not cursor_data:
137
+ await tool_ctx.error("Invalid cursor provided")
138
+ return {"error": "Invalid cursor"}
139
+
140
+ # Validate path parameter
141
+ path_validation = self.validate_path(path)
142
+ if path_validation.is_error:
143
+ await tool_ctx.error(path_validation.error_message)
144
+ return {"error": path_validation.error_message}
145
+
146
+ await tool_ctx.info(
147
+ f"Getting paginated directory tree: {path} (depth: {depth}, page_size: {page_size})"
148
+ )
149
+
150
+ # Check if path is allowed
151
+ allowed, error_msg = await self.check_path_allowed(path, tool_ctx)
152
+ if not allowed:
153
+ return {"error": error_msg}
154
+
155
+ try:
156
+ dir_path = Path(path)
157
+
158
+ # Check if path exists
159
+ exists, error_msg = await self.check_path_exists(path, tool_ctx)
160
+ if not exists:
161
+ return {"error": error_msg}
162
+
163
+ # Check if path is a directory
164
+ is_dir, error_msg = await self.check_is_directory(path, tool_ctx)
165
+ if not is_dir:
166
+ return {"error": error_msg}
167
+
168
+ # Define filtered directories
169
+ FILTERED_DIRECTORIES = {
170
+ ".git",
171
+ "node_modules",
172
+ ".venv",
173
+ "venv",
174
+ "__pycache__",
175
+ ".pytest_cache",
176
+ ".idea",
177
+ ".vs",
178
+ ".vscode",
179
+ "dist",
180
+ "build",
181
+ "target",
182
+ ".ruff_cache",
183
+ ".llm-context",
184
+ }
185
+
186
+ # Check if a directory should be filtered
187
+ def should_filter(current_path: Path) -> bool:
188
+ if str(current_path.absolute()) == str(dir_path.absolute()):
189
+ return False
190
+ return (
191
+ current_path.name in FILTERED_DIRECTORIES and not include_filtered
192
+ )
193
+
194
+ # Collect all entries in a flat list for pagination
195
+ all_entries: List[Dict[str, Any]] = []
196
+
197
+ # Build the tree and collect entries
198
+ def collect_entries(
199
+ current_path: Path,
200
+ current_depth: int = 0,
201
+ parent_path: str = ""
202
+ ) -> None:
203
+ """Collect entries in a flat list for pagination."""
204
+ if not self.is_path_allowed(str(current_path)):
205
+ return
206
+
207
+ try:
208
+ # Sort entries: directories first, then files alphabetically
209
+ entries = sorted(
210
+ current_path.iterdir(),
211
+ key=lambda x: (not x.is_dir(), x.name)
212
+ )
213
+
214
+ for entry in entries:
215
+ if not self.is_path_allowed(str(entry)):
216
+ continue
217
+
218
+ # Calculate relative path for display
219
+ relative_path = f"{parent_path}/{entry.name}" if parent_path else entry.name
220
+
221
+ if entry.is_dir():
222
+ entry_data: Dict[str, Any] = {
223
+ "path": relative_path,
224
+ "type": "directory",
225
+ "depth": current_depth,
226
+ }
227
+
228
+ # Check if we should filter this directory
229
+ if should_filter(entry):
230
+ entry_data["skipped"] = "filtered-directory"
231
+ all_entries.append(entry_data)
232
+ continue
233
+
234
+ # Check depth limit
235
+ if depth > 0 and current_depth >= depth:
236
+ entry_data["skipped"] = "depth-limit"
237
+ all_entries.append(entry_data)
238
+ continue
239
+
240
+ # Add directory entry
241
+ all_entries.append(entry_data)
242
+
243
+ # Process children recursively
244
+ collect_entries(
245
+ entry,
246
+ current_depth + 1,
247
+ relative_path
248
+ )
249
+ else:
250
+ # Add file entry
251
+ if depth <= 0 or current_depth < depth:
252
+ all_entries.append({
253
+ "path": relative_path,
254
+ "type": "file",
255
+ "depth": current_depth,
256
+ })
257
+
258
+ except Exception as e:
259
+ await tool_ctx.warning(f"Error processing {current_path}: {str(e)}")
260
+
261
+ # Collect all entries
262
+ await tool_ctx.info("Collecting directory entries...")
263
+ collect_entries(dir_path)
264
+
265
+ # Paginate the results
266
+ paginated = paginate_list(all_entries, cursor, page_size)
267
+
268
+ # Format the paginated entries for display
269
+ formatted_entries = []
270
+ for entry in paginated.items:
271
+ indent = " " * entry["depth"]
272
+ if entry["type"] == "directory":
273
+ if "skipped" in entry:
274
+ formatted_entries.append({
275
+ "entry": f"{indent}{entry['path'].split('/')[-1]}/ [skipped - {entry['skipped']}]",
276
+ "type": "directory",
277
+ "skipped": entry.get("skipped")
278
+ })
279
+ else:
280
+ formatted_entries.append({
281
+ "entry": f"{indent}{entry['path'].split('/')[-1]}/",
282
+ "type": "directory"
283
+ })
284
+ else:
285
+ formatted_entries.append({
286
+ "entry": f"{indent}{entry['path'].split('/')[-1]}",
287
+ "type": "file"
288
+ })
289
+
290
+ # Build response
291
+ response = {
292
+ "entries": formatted_entries,
293
+ "total_collected": len(all_entries),
294
+ "page_size": page_size,
295
+ "current_page_count": len(formatted_entries)
296
+ }
297
+
298
+ # Add next cursor if available
299
+ if paginated.next_cursor:
300
+ response["nextCursor"] = paginated.next_cursor
301
+
302
+ await tool_ctx.info(
303
+ f"Returning page with {len(formatted_entries)} entries"
304
+ f"{' (more available)' if paginated.next_cursor else ' (end of results)'}"
305
+ )
306
+
307
+ return response
308
+
309
+ except Exception as e:
310
+ await tool_ctx.error(f"Error generating directory tree: {str(e)}")
311
+ return {"error": f"Error generating directory tree: {str(e)}"}
312
+
313
+ @override
314
+ def register(self, mcp_server: FastMCP) -> None:
315
+ """Register this paginated directory tree tool with the MCP server."""
316
+ tool_self = self
317
+
318
+ @mcp_server.tool(name=self.name, description=self.description)
319
+ async def directory_tree_paginated(
320
+ path: DirectoryPath,
321
+ depth: Depth = 3,
322
+ include_filtered: IncludeFiltered = False,
323
+ page_size: PageSize = 100,
324
+ cursor: Cursor = None,
325
+ ctx: MCPContext = None,
326
+ ) -> Dict[str, Any]:
327
+ return await tool_self.call(
328
+ ctx,
329
+ path=path,
330
+ depth=depth,
331
+ include_filtered=include_filtered,
332
+ page_size=page_size,
333
+ cursor=cursor,
334
+ )
335
+
336
+
337
+ # Create the tool instance
338
+ directory_tree_paginated_tool = DirectoryTreePaginatedTool()
@@ -0,0 +1,5 @@
1
+ """Language Server Protocol tools for code intelligence."""
2
+
3
+ from .lsp_tool import LSPTool, create_lsp_tool
4
+
5
+ __all__ = ["LSPTool", "create_lsp_tool"]