hanzo-mcp 0.7.7__py3-none-any.whl → 0.8.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 (178) hide show
  1. hanzo_mcp/__init__.py +6 -0
  2. hanzo_mcp/__main__.py +1 -1
  3. hanzo_mcp/analytics/__init__.py +2 -2
  4. hanzo_mcp/analytics/posthog_analytics.py +76 -82
  5. hanzo_mcp/cli.py +31 -36
  6. hanzo_mcp/cli_enhanced.py +94 -72
  7. hanzo_mcp/cli_plugin.py +27 -17
  8. hanzo_mcp/config/__init__.py +2 -2
  9. hanzo_mcp/config/settings.py +112 -88
  10. hanzo_mcp/config/tool_config.py +32 -34
  11. hanzo_mcp/dev_server.py +66 -67
  12. hanzo_mcp/prompts/__init__.py +94 -12
  13. hanzo_mcp/prompts/enhanced_prompts.py +809 -0
  14. hanzo_mcp/prompts/example_custom_prompt.py +6 -5
  15. hanzo_mcp/prompts/project_todo_reminder.py +0 -1
  16. hanzo_mcp/prompts/tool_explorer.py +10 -7
  17. hanzo_mcp/server.py +17 -21
  18. hanzo_mcp/server_enhanced.py +15 -22
  19. hanzo_mcp/tools/__init__.py +56 -28
  20. hanzo_mcp/tools/agent/__init__.py +16 -19
  21. hanzo_mcp/tools/agent/agent.py +82 -65
  22. hanzo_mcp/tools/agent/agent_tool.py +152 -122
  23. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
  24. hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
  25. hanzo_mcp/tools/agent/clarification_tool.py +11 -10
  26. hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
  27. hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
  28. hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
  29. hanzo_mcp/tools/agent/code_auth.py +102 -107
  30. hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
  31. hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
  32. hanzo_mcp/tools/agent/critic_tool.py +86 -73
  33. hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
  34. hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
  35. hanzo_mcp/tools/agent/iching_tool.py +404 -139
  36. hanzo_mcp/tools/agent/network_tool.py +89 -73
  37. hanzo_mcp/tools/agent/prompt.py +2 -1
  38. hanzo_mcp/tools/agent/review_tool.py +101 -98
  39. hanzo_mcp/tools/agent/swarm_alias.py +87 -0
  40. hanzo_mcp/tools/agent/swarm_tool.py +246 -161
  41. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
  42. hanzo_mcp/tools/agent/tool_adapter.py +21 -11
  43. hanzo_mcp/tools/common/__init__.py +1 -1
  44. hanzo_mcp/tools/common/base.py +3 -5
  45. hanzo_mcp/tools/common/batch_tool.py +46 -39
  46. hanzo_mcp/tools/common/config_tool.py +120 -84
  47. hanzo_mcp/tools/common/context.py +1 -5
  48. hanzo_mcp/tools/common/context_fix.py +5 -3
  49. hanzo_mcp/tools/common/critic_tool.py +4 -8
  50. hanzo_mcp/tools/common/decorators.py +58 -56
  51. hanzo_mcp/tools/common/enhanced_base.py +29 -32
  52. hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
  53. hanzo_mcp/tools/common/forgiving_edit.py +91 -87
  54. hanzo_mcp/tools/common/mode.py +15 -17
  55. hanzo_mcp/tools/common/mode_loader.py +27 -24
  56. hanzo_mcp/tools/common/paginated_base.py +61 -53
  57. hanzo_mcp/tools/common/paginated_response.py +72 -79
  58. hanzo_mcp/tools/common/pagination.py +50 -53
  59. hanzo_mcp/tools/common/permissions.py +4 -4
  60. hanzo_mcp/tools/common/personality.py +186 -138
  61. hanzo_mcp/tools/common/plugin_loader.py +54 -54
  62. hanzo_mcp/tools/common/stats.py +65 -47
  63. hanzo_mcp/tools/common/test_helpers.py +31 -0
  64. hanzo_mcp/tools/common/thinking_tool.py +4 -8
  65. hanzo_mcp/tools/common/tool_disable.py +17 -12
  66. hanzo_mcp/tools/common/tool_enable.py +13 -14
  67. hanzo_mcp/tools/common/tool_list.py +36 -28
  68. hanzo_mcp/tools/common/truncate.py +23 -23
  69. hanzo_mcp/tools/config/__init__.py +4 -4
  70. hanzo_mcp/tools/config/config_tool.py +42 -29
  71. hanzo_mcp/tools/config/index_config.py +37 -34
  72. hanzo_mcp/tools/config/mode_tool.py +175 -55
  73. hanzo_mcp/tools/database/__init__.py +15 -12
  74. hanzo_mcp/tools/database/database_manager.py +77 -75
  75. hanzo_mcp/tools/database/graph.py +137 -91
  76. hanzo_mcp/tools/database/graph_add.py +30 -18
  77. hanzo_mcp/tools/database/graph_query.py +178 -102
  78. hanzo_mcp/tools/database/graph_remove.py +33 -28
  79. hanzo_mcp/tools/database/graph_search.py +97 -75
  80. hanzo_mcp/tools/database/graph_stats.py +91 -59
  81. hanzo_mcp/tools/database/sql.py +107 -79
  82. hanzo_mcp/tools/database/sql_query.py +30 -24
  83. hanzo_mcp/tools/database/sql_search.py +29 -25
  84. hanzo_mcp/tools/database/sql_stats.py +47 -35
  85. hanzo_mcp/tools/editor/neovim_command.py +25 -28
  86. hanzo_mcp/tools/editor/neovim_edit.py +21 -23
  87. hanzo_mcp/tools/editor/neovim_session.py +60 -54
  88. hanzo_mcp/tools/filesystem/__init__.py +31 -30
  89. hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
  90. hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
  91. hanzo_mcp/tools/filesystem/base.py +1 -1
  92. hanzo_mcp/tools/filesystem/batch_search.py +316 -224
  93. hanzo_mcp/tools/filesystem/content_replace.py +4 -4
  94. hanzo_mcp/tools/filesystem/diff.py +71 -59
  95. hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
  96. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
  97. hanzo_mcp/tools/filesystem/edit.py +4 -4
  98. hanzo_mcp/tools/filesystem/find.py +173 -80
  99. hanzo_mcp/tools/filesystem/find_files.py +73 -52
  100. hanzo_mcp/tools/filesystem/git_search.py +157 -104
  101. hanzo_mcp/tools/filesystem/grep.py +8 -8
  102. hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
  103. hanzo_mcp/tools/filesystem/read.py +12 -10
  104. hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
  105. hanzo_mcp/tools/filesystem/search_tool.py +263 -207
  106. hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
  107. hanzo_mcp/tools/filesystem/tree.py +35 -33
  108. hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
  109. hanzo_mcp/tools/filesystem/watch.py +37 -36
  110. hanzo_mcp/tools/filesystem/write.py +4 -8
  111. hanzo_mcp/tools/jupyter/__init__.py +4 -4
  112. hanzo_mcp/tools/jupyter/base.py +4 -5
  113. hanzo_mcp/tools/jupyter/jupyter.py +67 -47
  114. hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
  115. hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
  116. hanzo_mcp/tools/llm/__init__.py +5 -7
  117. hanzo_mcp/tools/llm/consensus_tool.py +72 -52
  118. hanzo_mcp/tools/llm/llm_manage.py +101 -60
  119. hanzo_mcp/tools/llm/llm_tool.py +226 -166
  120. hanzo_mcp/tools/llm/provider_tools.py +25 -26
  121. hanzo_mcp/tools/lsp/__init__.py +1 -1
  122. hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
  123. hanzo_mcp/tools/mcp/__init__.py +2 -3
  124. hanzo_mcp/tools/mcp/mcp_add.py +27 -25
  125. hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
  126. hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
  127. hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
  128. hanzo_mcp/tools/memory/__init__.py +39 -21
  129. hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
  130. hanzo_mcp/tools/memory/memory_tools.py +90 -108
  131. hanzo_mcp/tools/search/__init__.py +7 -2
  132. hanzo_mcp/tools/search/find_tool.py +297 -212
  133. hanzo_mcp/tools/search/unified_search.py +366 -314
  134. hanzo_mcp/tools/shell/__init__.py +8 -7
  135. hanzo_mcp/tools/shell/auto_background.py +56 -49
  136. hanzo_mcp/tools/shell/base.py +1 -1
  137. hanzo_mcp/tools/shell/base_process.py +75 -75
  138. hanzo_mcp/tools/shell/bash_session.py +2 -2
  139. hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
  140. hanzo_mcp/tools/shell/bash_tool.py +24 -31
  141. hanzo_mcp/tools/shell/command_executor.py +12 -12
  142. hanzo_mcp/tools/shell/logs.py +43 -33
  143. hanzo_mcp/tools/shell/npx.py +13 -13
  144. hanzo_mcp/tools/shell/npx_background.py +24 -21
  145. hanzo_mcp/tools/shell/npx_tool.py +18 -22
  146. hanzo_mcp/tools/shell/open.py +19 -21
  147. hanzo_mcp/tools/shell/pkill.py +31 -26
  148. hanzo_mcp/tools/shell/process_tool.py +32 -32
  149. hanzo_mcp/tools/shell/processes.py +57 -58
  150. hanzo_mcp/tools/shell/run_background.py +24 -25
  151. hanzo_mcp/tools/shell/run_command.py +5 -5
  152. hanzo_mcp/tools/shell/run_command_windows.py +5 -5
  153. hanzo_mcp/tools/shell/session_storage.py +3 -3
  154. hanzo_mcp/tools/shell/streaming_command.py +141 -126
  155. hanzo_mcp/tools/shell/uvx.py +24 -25
  156. hanzo_mcp/tools/shell/uvx_background.py +35 -33
  157. hanzo_mcp/tools/shell/uvx_tool.py +18 -22
  158. hanzo_mcp/tools/todo/__init__.py +6 -2
  159. hanzo_mcp/tools/todo/todo.py +50 -37
  160. hanzo_mcp/tools/todo/todo_read.py +5 -8
  161. hanzo_mcp/tools/todo/todo_write.py +5 -7
  162. hanzo_mcp/tools/vector/__init__.py +40 -28
  163. hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
  164. hanzo_mcp/tools/vector/git_ingester.py +170 -179
  165. hanzo_mcp/tools/vector/index_tool.py +96 -44
  166. hanzo_mcp/tools/vector/infinity_store.py +283 -228
  167. hanzo_mcp/tools/vector/mock_infinity.py +39 -40
  168. hanzo_mcp/tools/vector/project_manager.py +88 -78
  169. hanzo_mcp/tools/vector/vector.py +59 -42
  170. hanzo_mcp/tools/vector/vector_index.py +30 -27
  171. hanzo_mcp/tools/vector/vector_search.py +64 -45
  172. hanzo_mcp/types.py +6 -4
  173. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/METADATA +1 -1
  174. hanzo_mcp-0.8.1.dist-info/RECORD +185 -0
  175. hanzo_mcp-0.7.7.dist-info/RECORD +0 -182
  176. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/WHEEL +0 -0
  177. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/entry_points.txt +0 -0
  178. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/top_level.txt +0 -0
@@ -6,17 +6,25 @@ and other code structures with full context.
6
6
  """
7
7
 
8
8
  import os
9
+ from typing import (
10
+ Any,
11
+ Dict,
12
+ List,
13
+ Unpack,
14
+ Optional,
15
+ Annotated,
16
+ TypedDict,
17
+ final,
18
+ override,
19
+ )
9
20
  from pathlib import Path
10
- from typing import Annotated, TypedDict, Unpack, final, override, Optional, List, Dict, Any
11
- import json
12
21
 
13
- from mcp.server.fastmcp import Context as MCPContext
14
- from grep_ast.grep_ast import TreeContext
15
22
  from pydantic import Field
23
+ from grep_ast.grep_ast import TreeContext
24
+ from mcp.server.fastmcp import Context as MCPContext
16
25
 
17
26
  from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
18
27
 
19
-
20
28
  # Parameter types
21
29
  Action = Annotated[
22
30
  str,
@@ -77,6 +85,7 @@ Limit = Annotated[
77
85
 
78
86
  class SymbolsParams(TypedDict, total=False):
79
87
  """Parameters for symbols tool."""
88
+
80
89
  action: str
81
90
  pattern: Optional[str]
82
91
  path: str
@@ -89,7 +98,7 @@ class SymbolsParams(TypedDict, total=False):
89
98
  @final
90
99
  class SymbolsTool(FilesystemBaseTool):
91
100
  """Tool for code symbol operations using tree-sitter."""
92
-
101
+
93
102
  def __init__(self, permission_manager):
94
103
  """Initialize the symbols tool."""
95
104
  super().__init__(permission_manager)
@@ -128,11 +137,13 @@ Finds code structures (functions, classes, methods) with full context."""
128
137
 
129
138
  # Extract action
130
139
  action = params.get("action", "search")
131
-
140
+
132
141
  # Route to appropriate handler
133
142
  if action == "search":
134
143
  return await self._handle_search(params, tool_ctx)
135
- elif action == "ast" or action == "grep_ast": # Support both for backward compatibility
144
+ elif (
145
+ action == "ast" or action == "grep_ast"
146
+ ): # Support both for backward compatibility
136
147
  return await self._handle_ast(params, tool_ctx)
137
148
  elif action == "index":
138
149
  return await self._handle_index(params, tool_ctx)
@@ -148,12 +159,12 @@ Finds code structures (functions, classes, methods) with full context."""
148
159
  pattern = params.get("pattern")
149
160
  if not pattern:
150
161
  return "Error: pattern required for search action"
151
-
162
+
152
163
  path = params.get("path", ".")
153
164
  ignore_case = params.get("ignore_case", False)
154
165
  show_context = params.get("show_context", True)
155
166
  limit = params.get("limit", 50)
156
-
167
+
157
168
  # Validate path
158
169
  path_validation = self.validate_path(path)
159
170
  if not path_validation.is_valid:
@@ -180,11 +191,11 @@ Finds code structures (functions, classes, methods) with full context."""
180
191
  # Process files
181
192
  results = []
182
193
  match_count = 0
183
-
194
+
184
195
  for file_path in files_to_process:
185
196
  if match_count >= limit:
186
197
  break
187
-
198
+
188
199
  try:
189
200
  with open(file_path, "r", encoding="utf-8") as f:
190
201
  code = f.read()
@@ -199,7 +210,7 @@ Finds code structures (functions, classes, methods) with full context."""
199
210
 
200
211
  # Find matches
201
212
  loi = tc.grep(pattern, ignore_case)
202
-
213
+
203
214
  if loi:
204
215
  if show_context:
205
216
  tc.add_lines_of_interest(loi)
@@ -207,11 +218,13 @@ Finds code structures (functions, classes, methods) with full context."""
207
218
  output = tc.format()
208
219
  else:
209
220
  # Just show matching lines
210
- output = "\n".join([f"{line}: {code.splitlines()[line-1]}" for line in loi])
211
-
221
+ output = "\n".join(
222
+ [f"{line}: {code.splitlines()[line - 1]}" for line in loi]
223
+ )
224
+
212
225
  results.append(f"\n{file_path}:\n{output}\n")
213
226
  match_count += len(loi)
214
-
227
+
215
228
  except Exception as e:
216
229
  await tool_ctx.warning(f"Could not parse {file_path}: {str(e)}")
217
230
 
@@ -221,10 +234,10 @@ Finds code structures (functions, classes, methods) with full context."""
221
234
  output = [f"=== Symbol Search Results for '{pattern}' ==="]
222
235
  output.append(f"Found {match_count} matches in {len(results)} files\n")
223
236
  output.extend(results)
224
-
237
+
225
238
  if match_count >= limit:
226
239
  output.append(f"\n(Results limited to {limit} matches)")
227
-
240
+
228
241
  return "\n".join(output)
229
242
 
230
243
  async def _handle_ast(self, params: Dict[str, Any], tool_ctx) -> str:
@@ -264,7 +277,7 @@ Finds code structures (functions, classes, methods) with full context."""
264
277
  # Process files
265
278
  results = []
266
279
  match_count = 0
267
-
280
+
268
281
  for file_path in files_to_process:
269
282
  if match_count >= limit:
270
283
  break
@@ -284,7 +297,6 @@ Finds code structures (functions, classes, methods) with full context."""
284
297
 
285
298
  # Find matches with case sensitivity option
286
299
  if ignore_case:
287
- import re
288
300
  loi = tc.grep(pattern, ignore_case=True)
289
301
  else:
290
302
  loi = tc.grep(pattern, ignore_case=False)
@@ -293,17 +305,17 @@ Finds code structures (functions, classes, methods) with full context."""
293
305
  # Always show AST context for grep_ast
294
306
  tc.add_lines_of_interest(loi)
295
307
  tc.add_context()
296
-
308
+
297
309
  # Get the formatted output with structure
298
310
  output = tc.format()
299
-
311
+
300
312
  # Add section separator and file info
301
- results.append(f"\n{'='*60}")
313
+ results.append(f"\n{'=' * 60}")
302
314
  results.append(f"File: {file_path}")
303
315
  results.append(f"Matches: {len(loi)}")
304
- results.append(f"{'='*60}\n")
316
+ results.append(f"{'=' * 60}\n")
305
317
  results.append(output)
306
-
318
+
307
319
  match_count += len(loi)
308
320
 
309
321
  except Exception as e:
@@ -313,7 +325,9 @@ Finds code structures (functions, classes, methods) with full context."""
313
325
  return f"No matches found for '{pattern}' in {path}"
314
326
 
315
327
  output = [f"=== AST-aware Grep Results for '{pattern}' ==="]
316
- output.append(f"Total matches: {match_count} in {len([r for r in results if '===' in str(r)])//4} files\n")
328
+ output.append(
329
+ f"Total matches: {match_count} in {len([r for r in results if '===' in str(r)]) // 4} files\n"
330
+ )
317
331
  output.extend(results)
318
332
 
319
333
  if match_count >= limit:
@@ -324,14 +338,14 @@ Finds code structures (functions, classes, methods) with full context."""
324
338
  async def _handle_index(self, params: Dict[str, Any], tool_ctx) -> str:
325
339
  """Index symbols in a codebase."""
326
340
  path = params.get("path", ".")
327
-
341
+
328
342
  # Validate path
329
343
  is_allowed, error_message = await self.check_path_allowed(path, tool_ctx)
330
344
  if not is_allowed:
331
345
  return error_message
332
346
 
333
347
  await tool_ctx.info(f"Indexing symbols in {path}...")
334
-
348
+
335
349
  files_to_process = self._get_source_files(path)
336
350
  if not files_to_process:
337
351
  return f"No source code files found in {path}"
@@ -343,38 +357,38 @@ Finds code structures (functions, classes, methods) with full context."""
343
357
  "methods": [],
344
358
  "variables": [],
345
359
  }
346
-
360
+
347
361
  indexed_count = 0
348
362
  symbol_count = 0
349
-
363
+
350
364
  for file_path in files_to_process:
351
365
  try:
352
366
  with open(file_path, "r", encoding="utf-8") as f:
353
367
  code = f.read()
354
368
 
355
369
  tc = TreeContext(file_path, code, color=False, verbose=False)
356
-
370
+
357
371
  # Extract symbols (simplified - would need proper tree-sitter queries)
358
372
  # This is a placeholder for actual symbol extraction
359
373
  symbols = self._extract_symbols(tc, file_path)
360
-
374
+
361
375
  for symbol_type, syms in symbols.items():
362
376
  self._symbol_cache[path][symbol_type].extend(syms)
363
377
  symbol_count += len(syms)
364
-
378
+
365
379
  indexed_count += 1
366
-
380
+
367
381
  except Exception as e:
368
382
  await tool_ctx.warning(f"Could not index {file_path}: {str(e)}")
369
383
 
370
384
  output = [f"=== Symbol Indexing Complete ==="]
371
385
  output.append(f"Indexed {indexed_count} files")
372
386
  output.append(f"Found {symbol_count} total symbols:")
373
-
387
+
374
388
  for symbol_type, symbols in self._symbol_cache[path].items():
375
389
  if symbols:
376
390
  output.append(f" {symbol_type}: {len(symbols)}")
377
-
391
+
378
392
  return "\n".join(output)
379
393
 
380
394
  async def _handle_query(self, params: Dict[str, Any], tool_ctx) -> str:
@@ -383,14 +397,14 @@ Finds code structures (functions, classes, methods) with full context."""
383
397
  symbol_type = params.get("symbol_type")
384
398
  pattern = params.get("pattern")
385
399
  limit = params.get("limit", 50)
386
-
400
+
387
401
  # Check if we have indexed this path
388
402
  if path not in self._symbol_cache:
389
403
  return f"No symbols indexed for {path}. Run 'symbols --action index --path {path}' first."
390
-
404
+
391
405
  symbols = self._symbol_cache[path]
392
406
  results = []
393
-
407
+
394
408
  # Filter by type if specified
395
409
  if symbol_type:
396
410
  if symbol_type in symbols:
@@ -402,7 +416,7 @@ Finds code structures (functions, classes, methods) with full context."""
402
416
  candidates = []
403
417
  for syms in symbols.values():
404
418
  candidates.extend(syms)
405
-
419
+
406
420
  # Filter by pattern if specified
407
421
  if pattern:
408
422
  filtered = []
@@ -410,23 +424,23 @@ Finds code structures (functions, classes, methods) with full context."""
410
424
  if pattern.lower() in sym["name"].lower():
411
425
  filtered.append(sym)
412
426
  candidates = filtered
413
-
427
+
414
428
  # Limit results
415
429
  candidates = candidates[:limit]
416
-
430
+
417
431
  if not candidates:
418
432
  return "No symbols found matching criteria"
419
-
433
+
420
434
  output = [f"=== Symbol Query Results ==="]
421
435
  output.append(f"Found {len(candidates)} symbols\n")
422
-
436
+
423
437
  for sym in candidates:
424
438
  output.append(f"{sym['type']}: {sym['name']}")
425
439
  output.append(f" File: {sym['file']}:{sym['line']}")
426
440
  if sym.get("signature"):
427
441
  output.append(f" Signature: {sym['signature']}")
428
442
  output.append("")
429
-
443
+
430
444
  return "\n".join(output)
431
445
 
432
446
  async def _handle_list(self, params: Dict[str, Any], tool_ctx) -> str:
@@ -439,14 +453,36 @@ Finds code structures (functions, classes, methods) with full context."""
439
453
  """Get all source code files in a path."""
440
454
  path_obj = Path(path)
441
455
  files_to_process = []
442
-
456
+
443
457
  # Common source file extensions
444
458
  extensions = {
445
- ".py", ".js", ".ts", ".jsx", ".tsx", ".java", ".cpp", ".c", ".h",
446
- ".hpp", ".cs", ".rb", ".go", ".rs", ".swift", ".kt", ".scala",
447
- ".php", ".lua", ".r", ".jl", ".ex", ".exs", ".clj", ".cljs"
459
+ ".py",
460
+ ".js",
461
+ ".ts",
462
+ ".jsx",
463
+ ".tsx",
464
+ ".java",
465
+ ".cpp",
466
+ ".c",
467
+ ".h",
468
+ ".hpp",
469
+ ".cs",
470
+ ".rb",
471
+ ".go",
472
+ ".rs",
473
+ ".swift",
474
+ ".kt",
475
+ ".scala",
476
+ ".php",
477
+ ".lua",
478
+ ".r",
479
+ ".jl",
480
+ ".ex",
481
+ ".exs",
482
+ ".clj",
483
+ ".cljs",
448
484
  }
449
-
485
+
450
486
  if path_obj.is_file():
451
487
  if path_obj.suffix in extensions:
452
488
  files_to_process.append(str(path_obj))
@@ -454,12 +490,16 @@ Finds code structures (functions, classes, methods) with full context."""
454
490
  for root, _, files in os.walk(path_obj):
455
491
  for file in files:
456
492
  file_path = Path(root) / file
457
- if file_path.suffix in extensions and self.is_path_allowed(str(file_path)):
493
+ if file_path.suffix in extensions and self.is_path_allowed(
494
+ str(file_path)
495
+ ):
458
496
  files_to_process.append(str(file_path))
459
-
497
+
460
498
  return files_to_process
461
499
 
462
- def _extract_symbols(self, tc: TreeContext, file_path: str) -> Dict[str, List[Dict[str, Any]]]:
500
+ def _extract_symbols(
501
+ self, tc: TreeContext, file_path: str
502
+ ) -> Dict[str, List[Dict[str, Any]]]:
463
503
  """Extract symbols from a TreeContext (placeholder implementation)."""
464
504
  # This would need proper tree-sitter queries to extract symbols
465
505
  # For now, return empty structure
@@ -472,4 +512,4 @@ Finds code structures (functions, classes, methods) with full context."""
472
512
 
473
513
  def register(self, mcp_server) -> None:
474
514
  """Register this tool with the MCP server."""
475
- pass
515
+ pass
@@ -3,16 +3,14 @@
3
3
  Unix-style tree command for directory visualization.
4
4
  """
5
5
 
6
- import os
6
+ from typing import List, Unpack, Optional, Annotated, TypedDict, final, override
7
7
  from pathlib import Path
8
- from typing import Annotated, TypedDict, Unpack, final, override, Optional, List
9
8
 
10
- from mcp.server.fastmcp import Context as MCPContext
11
9
  from pydantic import Field
10
+ from mcp.server.fastmcp import Context as MCPContext
12
11
 
13
12
  from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
14
13
 
15
-
16
14
  # Parameter types
17
15
  TreePath = Annotated[
18
16
  str,
@@ -65,6 +63,7 @@ Pattern = Annotated[
65
63
 
66
64
  class TreeParams(TypedDict, total=False):
67
65
  """Parameters for tree tool."""
66
+
68
67
  path: str
69
68
  depth: Optional[int]
70
69
  show_hidden: bool
@@ -103,7 +102,7 @@ tree --pattern "*.py" --show-size"""
103
102
  ) -> str:
104
103
  """Execute tree command."""
105
104
  tool_ctx = self.create_tool_context(ctx)
106
-
105
+
107
106
  # Extract parameters
108
107
  path = params.get("path", ".")
109
108
  max_depth = params.get("depth")
@@ -111,33 +110,33 @@ tree --pattern "*.py" --show-size"""
111
110
  dirs_only = params.get("dirs_only", False)
112
111
  show_size = params.get("show_size", False)
113
112
  pattern = params.get("pattern")
114
-
113
+
115
114
  # Validate path
116
115
  path_validation = self.validate_path(path)
117
116
  if path_validation.is_error:
118
117
  return f"Error: {path_validation.error_message}"
119
-
118
+
120
119
  # Check permissions
121
120
  allowed, error_msg = await self.check_path_allowed(path, tool_ctx)
122
121
  if not allowed:
123
122
  return error_msg
124
-
123
+
125
124
  # Check existence
126
125
  exists, error_msg = await self.check_path_exists(path, tool_ctx)
127
126
  if not exists:
128
127
  return error_msg
129
-
128
+
130
129
  path_obj = Path(path)
131
130
  if not path_obj.is_dir():
132
131
  return f"Error: {path} is not a directory"
133
-
132
+
134
133
  # Build tree
135
134
  output = [str(path_obj)]
136
135
  stats = {"dirs": 0, "files": 0}
137
-
136
+
138
137
  self._build_tree(
139
- path_obj,
140
- output,
138
+ path_obj,
139
+ output,
141
140
  stats,
142
141
  prefix="",
143
142
  is_last=True,
@@ -146,16 +145,16 @@ tree --pattern "*.py" --show-size"""
146
145
  show_hidden=show_hidden,
147
146
  dirs_only=dirs_only,
148
147
  show_size=show_size,
149
- pattern=pattern
148
+ pattern=pattern,
150
149
  )
151
-
150
+
152
151
  # Add summary
153
152
  output.append("")
154
153
  if dirs_only:
155
154
  output.append(f"{stats['dirs']} directories")
156
155
  else:
157
156
  output.append(f"{stats['dirs']} directories, {stats['files']} files")
158
-
157
+
159
158
  return "\n".join(output)
160
159
 
161
160
  def _build_tree(
@@ -176,35 +175,38 @@ tree --pattern "*.py" --show-size"""
176
175
  # Check depth limit
177
176
  if max_depth is not None and current_depth >= max_depth:
178
177
  return
179
-
178
+
180
179
  try:
181
180
  # Get entries
182
181
  entries = list(path.iterdir())
183
-
182
+
184
183
  # Filter hidden files
185
184
  if not show_hidden:
186
185
  entries = [e for e in entries if not e.name.startswith(".")]
187
-
186
+
188
187
  # Filter by pattern
189
188
  if pattern:
190
189
  import fnmatch
191
- entries = [e for e in entries if fnmatch.fnmatch(e.name, pattern) or e.is_dir()]
192
-
190
+
191
+ entries = [
192
+ e for e in entries if fnmatch.fnmatch(e.name, pattern) or e.is_dir()
193
+ ]
194
+
193
195
  # Filter dirs only
194
196
  if dirs_only:
195
197
  entries = [e for e in entries if e.is_dir()]
196
-
198
+
197
199
  # Sort entries (dirs first, then alphabetically)
198
200
  entries.sort(key=lambda e: (not e.is_dir(), e.name.lower()))
199
-
201
+
200
202
  # Process each entry
201
203
  for i, entry in enumerate(entries):
202
204
  is_last_entry = i == len(entries) - 1
203
-
205
+
204
206
  # Skip if not allowed
205
207
  if not self.is_path_allowed(str(entry)):
206
208
  continue
207
-
209
+
208
210
  # Build the tree branch
209
211
  if prefix:
210
212
  if is_last_entry:
@@ -216,20 +218,20 @@ tree --pattern "*.py" --show-size"""
216
218
  else:
217
219
  branch = ""
218
220
  extension = ""
219
-
221
+
220
222
  # Build entry line
221
223
  line = branch + entry.name
222
-
224
+
223
225
  # Add size if requested
224
226
  if show_size and entry.is_file():
225
227
  try:
226
228
  size = entry.stat().st_size
227
229
  line += f" ({self._format_size(size)})"
228
- except:
230
+ except Exception:
229
231
  pass
230
-
232
+
231
233
  output.append(line)
232
-
234
+
233
235
  # Update stats
234
236
  if entry.is_dir():
235
237
  stats["dirs"] += 1
@@ -245,11 +247,11 @@ tree --pattern "*.py" --show-size"""
245
247
  show_hidden=show_hidden,
246
248
  dirs_only=dirs_only,
247
249
  show_size=show_size,
248
- pattern=pattern
250
+ pattern=pattern,
249
251
  )
250
252
  else:
251
253
  stats["files"] += 1
252
-
254
+
253
255
  except PermissionError:
254
256
  output.append(prefix + "[Permission Denied]")
255
257
  except Exception as e:
@@ -265,4 +267,4 @@ tree --pattern "*.py" --show-size"""
265
267
 
266
268
  def register(self, mcp_server) -> None:
267
269
  """Register this tool with the MCP server."""
268
- pass
270
+ pass
@@ -3,20 +3,21 @@
3
3
  Provides familiar Unix command names that map to MCP tools.
4
4
  """
5
5
 
6
- from typing import Dict, Type
6
+ from typing import Dict
7
+
7
8
  from hanzo_mcp.tools.common.base import BaseTool
8
9
 
9
10
 
10
11
  def get_unix_aliases() -> Dict[str, str]:
11
12
  """Get mapping of Unix commands to MCP tool names.
12
-
13
+
13
14
  Returns:
14
15
  Dictionary mapping Unix command names to MCP tool names
15
16
  """
16
17
  return {
17
18
  # File operations
18
19
  "ls": "list_directory",
19
- "cat": "read_file",
20
+ "cat": "read_file",
20
21
  "head": "read_file", # With line limit
21
22
  "tail": "read_file", # With offset
22
23
  "cp": "copy_path",
@@ -24,7 +25,6 @@ def get_unix_aliases() -> Dict[str, str]:
24
25
  "rm": "delete_path",
25
26
  "mkdir": "create_directory",
26
27
  "touch": "write_file", # Create empty file
27
-
28
28
  # Search operations
29
29
  "grep": "find", # Our unified find tool
30
30
  "find": "glob", # For finding files by name
@@ -32,23 +32,18 @@ def get_unix_aliases() -> Dict[str, str]:
32
32
  "rg": "find", # Ripgrep alias
33
33
  "ag": "find", # Silver searcher alias
34
34
  "ack": "find", # Ack alias
35
-
36
35
  # Directory operations
37
36
  "tree": "tree", # Already named correctly
38
37
  "pwd": "get_working_directory",
39
38
  "cd": "change_directory",
40
-
41
39
  # Git operations (if git tools enabled)
42
40
  "git": "git_command",
43
-
44
41
  # Process operations
45
42
  "ps": "list_processes",
46
43
  "kill": "kill_process",
47
-
48
44
  # Archive operations
49
45
  "tar": "archive",
50
46
  "unzip": "extract",
51
-
52
47
  # Network operations
53
48
  "curl": "http_request",
54
49
  "wget": "download_file",
@@ -57,13 +52,13 @@ def get_unix_aliases() -> Dict[str, str]:
57
52
 
58
53
  class UnixAliasRegistry:
59
54
  """Registry for Unix command aliases."""
60
-
55
+
61
56
  def __init__(self):
62
57
  self.aliases = get_unix_aliases()
63
-
58
+
64
59
  def register_aliases(self, mcp_server, tools: Dict[str, BaseTool]) -> None:
65
60
  """Register Unix aliases for tools.
66
-
61
+
67
62
  Args:
68
63
  mcp_server: The MCP server instance
69
64
  tools: Dictionary of tool name to tool instance
@@ -73,10 +68,10 @@ class UnixAliasRegistry:
73
68
  tool = tools[tool_name]
74
69
  # Register the tool under its alias name
75
70
  self._register_alias(mcp_server, alias, tool)
76
-
71
+
77
72
  def _register_alias(self, mcp_server, alias: str, tool: BaseTool) -> None:
78
73
  """Register a single alias for a tool.
79
-
74
+
80
75
  Args:
81
76
  mcp_server: The MCP server instance
82
77
  alias: The Unix command alias
@@ -86,14 +81,14 @@ class UnixAliasRegistry:
86
81
  # but registers under the alias name
87
82
  original_name = tool.name
88
83
  original_description = tool.description
89
-
84
+
90
85
  # Temporarily change the tool's name for registration
91
86
  tool.name = alias
92
87
  tool.description = f"{original_description}\n\n(Unix alias for {original_name})"
93
-
88
+
94
89
  # Register the tool
95
90
  tool.register(mcp_server)
96
-
91
+
97
92
  # Restore original name
98
93
  tool.name = original_name
99
- tool.description = original_description
94
+ tool.description = original_description