hanzo-mcp 0.9.0__py3-none-any.whl → 0.9.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.

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 +234 -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 +2 -3
  54. hanzo_mcp/tools/filesystem/base.py +0 -16
  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 +5 -4
  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 +5 -4
  66. hanzo_mcp/tools/filesystem/read.py +11 -8
  67. hanzo_mcp/tools/filesystem/rules_tool.py +5 -3
  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 +12 -6
  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.1.dist-info}/METADATA +1 -1
  119. hanzo_mcp-0.9.1.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.1.dist-info}/WHEEL +0 -0
  134. {hanzo_mcp-0.9.0.dist-info → hanzo_mcp-0.9.1.dist-info}/entry_points.txt +0 -0
  135. {hanzo_mcp-0.9.0.dist-info → hanzo_mcp-0.9.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,370 @@
1
+ """Find files using ffind library."""
2
+
3
+ import os
4
+ from typing import Unpack, Optional, Annotated, TypedDict, final, override
5
+
6
+ from pydantic import Field
7
+ from mcp.server.fastmcp import Context as MCPContext
8
+
9
+ from hanzo_mcp.tools.common.auto_timeout import auto_timeout
10
+
11
+ from hanzo_mcp.tools.common.base import BaseTool
12
+ from hanzo_mcp.tools.common.context import create_tool_context
13
+ from hanzo_mcp.tools.common.permissions import PermissionManager
14
+
15
+ try:
16
+ import ffind
17
+
18
+ FFIND_AVAILABLE = True
19
+ except ImportError:
20
+ FFIND_AVAILABLE = False
21
+
22
+
23
+ Pattern = Annotated[
24
+ str,
25
+ Field(
26
+ description="File name pattern to search for (supports wildcards)",
27
+ min_length=1,
28
+ ),
29
+ ]
30
+
31
+ Path_ = Annotated[
32
+ Optional[str],
33
+ Field(
34
+ description="Directory to search in (defaults to current directory)",
35
+ default=None,
36
+ ),
37
+ ]
38
+
39
+ Recursive = Annotated[
40
+ bool,
41
+ Field(
42
+ description="Search recursively in subdirectories",
43
+ default=True,
44
+ ),
45
+ ]
46
+
47
+ IgnoreCase = Annotated[
48
+ bool,
49
+ Field(
50
+ description="Case-insensitive search",
51
+ default=True,
52
+ ),
53
+ ]
54
+
55
+ Hidden = Annotated[
56
+ bool,
57
+ Field(
58
+ description="Include hidden files in search",
59
+ default=False,
60
+ ),
61
+ ]
62
+
63
+ DirsOnly = Annotated[
64
+ bool,
65
+ Field(
66
+ description="Only find directories",
67
+ default=False,
68
+ ),
69
+ ]
70
+
71
+ FilesOnly = Annotated[
72
+ bool,
73
+ Field(
74
+ description="Only find files (not directories)",
75
+ default=True,
76
+ ),
77
+ ]
78
+
79
+ MaxResults = Annotated[
80
+ int,
81
+ Field(
82
+ description="Maximum number of results",
83
+ default=100,
84
+ ),
85
+ ]
86
+
87
+
88
+ class FindFilesParams(TypedDict, total=False):
89
+ """Parameters for find files tool."""
90
+
91
+ pattern: str
92
+ path: Optional[str]
93
+ recursive: bool
94
+ ignore_case: bool
95
+ hidden: bool
96
+ dirs_only: bool
97
+ files_only: bool
98
+ max_results: int
99
+
100
+
101
+ @final
102
+ class FindFilesTool(BaseTool):
103
+ """Tool for finding files using ffind."""
104
+
105
+ def __init__(self, permission_manager: PermissionManager):
106
+ """Initialize the find files tool.
107
+
108
+ Args:
109
+ permission_manager: Permission manager for access control
110
+ """
111
+ self.permission_manager = permission_manager
112
+
113
+ @property
114
+ @override
115
+ def name(self) -> str:
116
+ """Get the tool name."""
117
+ return "find_files"
118
+
119
+ @property
120
+ @override
121
+ def description(self) -> str:
122
+ """Get the tool description."""
123
+ return """Find files by name pattern using efficient search.
124
+
125
+ Uses the ffind library for fast file searching with support for:
126
+ - Wildcards (* and ?)
127
+ - Case-insensitive search
128
+ - Hidden files
129
+ - Directory vs file filtering
130
+
131
+ Examples:
132
+ - find_files --pattern "*.py" # Find all Python files
133
+ - find_files --pattern "test_*" # Find files starting with test_
134
+ - find_files --pattern "README.*" # Find README files
135
+ - find_files --pattern "*config*" --hidden # Include hidden config files
136
+ - find_files --pattern "src" --dirs-only # Find directories named src
137
+
138
+ For content search, use 'grep' instead.
139
+ For database search, use 'sql_search' or 'vector_search'.
140
+ """
141
+
142
+ @override
143
+ @auto_timeout("find_files")
144
+
145
+
146
+ async def call(
147
+ self,
148
+ ctx: MCPContext,
149
+ **params: Unpack[FindFilesParams],
150
+ ) -> str:
151
+ """Find files matching pattern.
152
+
153
+ Args:
154
+ ctx: MCP context
155
+ **params: Tool parameters
156
+
157
+ Returns:
158
+ List of matching files
159
+ """
160
+ tool_ctx = create_tool_context(ctx)
161
+ await tool_ctx.set_tool_info(self.name)
162
+
163
+ # Extract parameters
164
+ pattern = params.get("pattern")
165
+ if not pattern:
166
+ return "Error: pattern is required"
167
+
168
+ search_path = params.get("path") or os.getcwd()
169
+ recursive = params.get("recursive", True)
170
+ ignore_case = params.get("ignore_case", True)
171
+ hidden = params.get("hidden", False)
172
+ dirs_only = params.get("dirs_only", False)
173
+ files_only = params.get("files_only", True)
174
+ max_results = params.get("max_results", 100)
175
+
176
+ # Validate path
177
+ search_path = os.path.abspath(search_path)
178
+ if not self.permission_manager.has_permission(search_path):
179
+ return f"Error: No permission to access {search_path}"
180
+
181
+ if not os.path.exists(search_path):
182
+ return f"Error: Path does not exist: {search_path}"
183
+
184
+ await tool_ctx.info(f"Searching for '{pattern}' in {search_path}")
185
+
186
+ # If ffind is not available, fall back to basic implementation
187
+ if not FFIND_AVAILABLE:
188
+ return await self._find_files_fallback(
189
+ pattern,
190
+ search_path,
191
+ recursive,
192
+ ignore_case,
193
+ hidden,
194
+ dirs_only,
195
+ files_only,
196
+ max_results,
197
+ )
198
+
199
+ try:
200
+ # Use ffind for efficient searching
201
+ results = []
202
+ count = 0
203
+
204
+ # Configure ffind options
205
+ options = {
206
+ "pattern": pattern,
207
+ "path": search_path,
208
+ "recursive": recursive,
209
+ "ignore_case": ignore_case,
210
+ "hidden": hidden,
211
+ }
212
+
213
+ # Search with ffind
214
+ for filepath in ffind.find(**options):
215
+ # Check if it matches our criteria
216
+ is_dir = os.path.isdir(filepath)
217
+
218
+ if dirs_only and not is_dir:
219
+ continue
220
+ if files_only and is_dir:
221
+ continue
222
+
223
+ # Make path relative for cleaner output
224
+ try:
225
+ rel_path = os.path.relpath(filepath, search_path)
226
+ except ValueError:
227
+ rel_path = filepath
228
+
229
+ results.append(rel_path)
230
+ count += 1
231
+
232
+ if count >= max_results:
233
+ break
234
+
235
+ if not results:
236
+ return f"No files found matching '{pattern}'"
237
+
238
+ # Format output
239
+ output = [f"Found {len(results)} file(s) matching '{pattern}':"]
240
+ output.append("")
241
+
242
+ for filepath in sorted(results):
243
+ output.append(filepath)
244
+
245
+ if count >= max_results:
246
+ output.append(f"\n... (showing first {max_results} results)")
247
+
248
+ return "\n".join(output)
249
+
250
+ except Exception as e:
251
+ await tool_ctx.error(f"Error during search: {str(e)}")
252
+ # Fall back to basic implementation
253
+ return await self._find_files_fallback(
254
+ pattern,
255
+ search_path,
256
+ recursive,
257
+ ignore_case,
258
+ hidden,
259
+ dirs_only,
260
+ files_only,
261
+ max_results,
262
+ )
263
+
264
+ async def _find_files_fallback(
265
+ self,
266
+ pattern: str,
267
+ search_path: str,
268
+ recursive: bool,
269
+ ignore_case: bool,
270
+ hidden: bool,
271
+ dirs_only: bool,
272
+ files_only: bool,
273
+ max_results: int,
274
+ ) -> str:
275
+ """Fallback implementation when ffind is not available."""
276
+
277
+ results = []
278
+ count = 0
279
+
280
+ # Convert pattern for case-insensitive matching
281
+ if ignore_case:
282
+ pattern = pattern.lower()
283
+
284
+ try:
285
+ if recursive:
286
+ # Walk directory tree
287
+ for root, dirs, files in os.walk(search_path):
288
+ # Skip hidden directories if not requested
289
+ if not hidden:
290
+ dirs[:] = [d for d in dirs if not d.startswith(".")]
291
+
292
+ # Check directories
293
+ if not files_only:
294
+ for dirname in dirs:
295
+ if self._match_pattern(dirname, pattern, ignore_case):
296
+ filepath = os.path.join(root, dirname)
297
+ rel_path = os.path.relpath(filepath, search_path)
298
+ results.append(rel_path + "/")
299
+ count += 1
300
+ if count >= max_results:
301
+ break
302
+
303
+ # Check files
304
+ if not dirs_only:
305
+ for filename in files:
306
+ if not hidden and filename.startswith("."):
307
+ continue
308
+
309
+ if self._match_pattern(filename, pattern, ignore_case):
310
+ filepath = os.path.join(root, filename)
311
+ rel_path = os.path.relpath(filepath, search_path)
312
+ results.append(rel_path)
313
+ count += 1
314
+ if count >= max_results:
315
+ break
316
+
317
+ if count >= max_results:
318
+ break
319
+ else:
320
+ # Only search in the specified directory
321
+ for entry in os.listdir(search_path):
322
+ if not hidden and entry.startswith("."):
323
+ continue
324
+
325
+ filepath = os.path.join(search_path, entry)
326
+ is_dir = os.path.isdir(filepath)
327
+
328
+ if dirs_only and not is_dir:
329
+ continue
330
+ if files_only and is_dir:
331
+ continue
332
+
333
+ if self._match_pattern(entry, pattern, ignore_case):
334
+ results.append(entry + "/" if is_dir else entry)
335
+ count += 1
336
+ if count >= max_results:
337
+ break
338
+
339
+ if not results:
340
+ return f"No files found matching '{pattern}' (using fallback search)"
341
+
342
+ # Format output
343
+ output = [f"Found {len(results)} file(s) matching '{pattern}' (using fallback search):"]
344
+ output.append("")
345
+
346
+ for filepath in sorted(results):
347
+ output.append(filepath)
348
+
349
+ if count >= max_results:
350
+ output.append(f"\n... (showing first {max_results} results)")
351
+
352
+ output.append("\nNote: Install 'ffind' for faster searching: pip install ffind")
353
+
354
+ return "\n".join(output)
355
+
356
+ except Exception as e:
357
+ return f"Error searching for files: {str(e)}"
358
+
359
+ def _match_pattern(self, filename: str, pattern: str, ignore_case: bool) -> bool:
360
+ """Check if filename matches pattern."""
361
+ import fnmatch
362
+
363
+ if ignore_case:
364
+ return fnmatch.fnmatch(filename.lower(), pattern)
365
+ else:
366
+ return fnmatch.fnmatch(filename, pattern)
367
+
368
+ def register(self, mcp_server) -> None:
369
+ """Register this tool with the MCP server."""
370
+ pass
@@ -8,6 +8,8 @@ from typing import Unpack, Annotated, TypedDict, final, override
8
8
  from pydantic import Field
9
9
  from mcp.server.fastmcp import Context as MCPContext
10
10
 
11
+ from hanzo_mcp.tools.common.auto_timeout import auto_timeout
12
+
11
13
  from hanzo_mcp.tools.common.base import BaseTool
12
14
  from hanzo_mcp.tools.common.context import create_tool_context
13
15
  from hanzo_mcp.tools.common.permissions import PermissionManager
@@ -153,6 +155,9 @@ Examples:
153
155
  """
154
156
 
155
157
  @override
158
+ @auto_timeout("git_search")
159
+
160
+
156
161
  async def call(
157
162
  self,
158
163
  ctx: MCPContext,
@@ -176,9 +181,6 @@ Examples:
176
181
  return "Error: pattern is required"
177
182
 
178
183
  path = params.get("path", os.getcwd())
179
-
180
- # Expand path (handles ~, $HOME, etc.)
181
- path = self.expand_path(path)
182
184
  search_type = params.get("search_type", "content")
183
185
  case_sensitive = params.get("case_sensitive", False)
184
186
  max_count = params.get("max_count", 100)