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
@@ -4,12 +4,12 @@ This module provides the ContentReplaceTool for replacing text patterns in files
4
4
  """
5
5
 
6
6
  import fnmatch
7
+ from typing import Unpack, Annotated, TypedDict, final, override
7
8
  from pathlib import Path
8
- from typing import Annotated, TypedDict, Unpack, final, override
9
9
 
10
- from mcp.server.fastmcp import Context as MCPContext
11
- from mcp.server import FastMCP
12
10
  from pydantic import Field
11
+ from mcp.server import FastMCP
12
+ from mcp.server.fastmcp import Context as MCPContext
13
13
 
14
14
  from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
15
15
 
@@ -280,7 +280,7 @@ Only works within allowed directories."""
280
280
  replacement: Replacement,
281
281
  path: SearchPath,
282
282
  file_pattern: FilePattern = "*",
283
- dry_run: DryRun = False
283
+ dry_run: DryRun = False,
284
284
  ) -> str:
285
285
  return await tool_self.call(
286
286
  ctx,
@@ -1,30 +1,30 @@
1
1
  """Diff tool for comparing files."""
2
2
 
3
3
  import difflib
4
+ from typing import override
4
5
  from pathlib import Path
5
- from typing import Optional, override
6
6
 
7
+ from mcp.server import FastMCP
7
8
  from mcp.server.fastmcp import Context as MCPContext
8
9
 
9
10
  from hanzo_mcp.tools.common.base import BaseTool
10
11
  from hanzo_mcp.tools.common.permissions import PermissionManager
11
- from mcp.server import FastMCP
12
12
 
13
13
 
14
14
  class DiffTool(BaseTool):
15
15
  """Tool for comparing files and showing differences."""
16
-
16
+
17
17
  name = "diff"
18
-
18
+
19
19
  def __init__(self, permission_manager: PermissionManager):
20
20
  """Initialize the diff tool.
21
-
21
+
22
22
  Args:
23
23
  permission_manager: Permission manager for access control
24
24
  """
25
25
  super().__init__()
26
26
  self.permission_manager = permission_manager
27
-
27
+
28
28
  @property
29
29
  @override
30
30
  def description(self) -> str:
@@ -36,7 +36,7 @@ diff file1.py file2.py
36
36
  diff old_version.js new_version.js --context 5
37
37
  diff before.txt after.txt --unified
38
38
  diff a.json b.json --ignore-whitespace"""
39
-
39
+
40
40
  @override
41
41
  async def run(
42
42
  self,
@@ -49,7 +49,7 @@ diff a.json b.json --ignore-whitespace"""
49
49
  show_line_numbers: bool = True,
50
50
  ) -> str:
51
51
  """Compare two files and show differences.
52
-
52
+
53
53
  Args:
54
54
  ctx: MCP context
55
55
  file1: Path to first file
@@ -58,100 +58,104 @@ diff a.json b.json --ignore-whitespace"""
58
58
  context: Number of context lines to show (default: 3)
59
59
  ignore_whitespace: Ignore whitespace differences (default: False)
60
60
  show_line_numbers: Show line numbers in output (default: True)
61
-
61
+
62
62
  Returns:
63
63
  Diff output showing differences between files
64
64
  """
65
65
  # Resolve file paths
66
66
  path1 = Path(file1).expanduser().resolve()
67
67
  path2 = Path(file2).expanduser().resolve()
68
-
68
+
69
69
  # Check permissions
70
70
  if not self.permission_manager.is_path_allowed(str(path1)):
71
71
  raise PermissionError(f"Access denied to path: {path1}")
72
72
  if not self.permission_manager.is_path_allowed(str(path2)):
73
73
  raise PermissionError(f"Access denied to path: {path2}")
74
-
74
+
75
75
  # Check if files exist
76
76
  if not path1.exists():
77
77
  raise ValueError(f"File not found: {path1}")
78
78
  if not path2.exists():
79
79
  raise ValueError(f"File not found: {path2}")
80
-
80
+
81
81
  # Read file contents
82
82
  try:
83
- with open(path1, 'r', encoding='utf-8') as f:
83
+ with open(path1, "r", encoding="utf-8") as f:
84
84
  lines1 = f.readlines()
85
85
  except Exception as e:
86
86
  raise RuntimeError(f"Error reading {path1}: {e}")
87
-
87
+
88
88
  try:
89
- with open(path2, 'r', encoding='utf-8') as f:
89
+ with open(path2, "r", encoding="utf-8") as f:
90
90
  lines2 = f.readlines()
91
91
  except Exception as e:
92
92
  raise RuntimeError(f"Error reading {path2}: {e}")
93
-
93
+
94
94
  # Optionally normalize whitespace
95
95
  if ignore_whitespace:
96
- lines1 = [line.strip() + '\n' for line in lines1]
97
- lines2 = [line.strip() + '\n' for line in lines2]
98
-
96
+ lines1 = [line.strip() + "\n" for line in lines1]
97
+ lines2 = [line.strip() + "\n" for line in lines2]
98
+
99
99
  # Generate diff
100
100
  if unified:
101
101
  # diff format
102
- diff_lines = list(difflib.unified_diff(
103
- lines1,
104
- lines2,
105
- fromfile=str(path1),
106
- tofile=str(path2),
107
- n=context,
108
- lineterm=''
109
- ))
102
+ diff_lines = list(
103
+ difflib.unified_diff(
104
+ lines1,
105
+ lines2,
106
+ fromfile=str(path1),
107
+ tofile=str(path2),
108
+ n=context,
109
+ lineterm="",
110
+ )
111
+ )
110
112
  else:
111
113
  # Context diff format
112
- diff_lines = list(difflib.context_diff(
113
- lines1,
114
- lines2,
115
- fromfile=str(path1),
116
- tofile=str(path2),
117
- n=context,
118
- lineterm=''
119
- ))
120
-
114
+ diff_lines = list(
115
+ difflib.context_diff(
116
+ lines1,
117
+ lines2,
118
+ fromfile=str(path1),
119
+ tofile=str(path2),
120
+ n=context,
121
+ lineterm="",
122
+ )
123
+ )
124
+
121
125
  if not diff_lines:
122
126
  return f"Files {path1.name} and {path2.name} are identical"
123
-
127
+
124
128
  # Format output
125
129
  output = []
126
-
130
+
127
131
  # Add header
128
132
  output.append(f"Comparing: {path1.name} vs {path2.name}")
129
133
  output.append("=" * 60)
130
-
134
+
131
135
  # Add diff with optional line numbers
132
136
  if show_line_numbers and unified:
133
137
  # Parse unified diff to add line numbers
134
138
  current_line1 = 0
135
139
  current_line2 = 0
136
-
140
+
137
141
  for line in diff_lines:
138
- if line.startswith('@@'):
142
+ if line.startswith("@@"):
139
143
  # Parse hunk header
140
144
  parts = line.split()
141
145
  if len(parts) >= 3:
142
146
  # Extract line numbers
143
- old_info = parts[1].strip('-').split(',')
144
- new_info = parts[2].strip('+').split(',')
147
+ old_info = parts[1].strip("-").split(",")
148
+ new_info = parts[2].strip("+").split(",")
145
149
  current_line1 = int(old_info[0]) - 1
146
150
  current_line2 = int(new_info[0]) - 1
147
151
  output.append(line)
148
- elif line.startswith('-'):
152
+ elif line.startswith("-"):
149
153
  current_line1 += 1
150
154
  output.append(f"{current_line1:4d}- {line[1:]}")
151
- elif line.startswith('+'):
155
+ elif line.startswith("+"):
152
156
  current_line2 += 1
153
157
  output.append(f"{current_line2:4d}+ {line[1:]}")
154
- elif line.startswith(' '):
158
+ elif line.startswith(" "):
155
159
  current_line1 += 1
156
160
  current_line2 += 1
157
161
  output.append(f"{current_line1:4d} {line[1:]}")
@@ -160,21 +164,29 @@ diff a.json b.json --ignore-whitespace"""
160
164
  else:
161
165
  # Standard diff output
162
166
  output.extend(diff_lines)
163
-
167
+
164
168
  # Add summary
165
- additions = sum(1 for line in diff_lines if line.startswith('+') and not line.startswith('+++'))
166
- deletions = sum(1 for line in diff_lines if line.startswith('-') and not line.startswith('---'))
167
-
169
+ additions = sum(
170
+ 1
171
+ for line in diff_lines
172
+ if line.startswith("+") and not line.startswith("+++")
173
+ )
174
+ deletions = sum(
175
+ 1
176
+ for line in diff_lines
177
+ if line.startswith("-") and not line.startswith("---")
178
+ )
179
+
168
180
  output.append("")
169
181
  output.append("=" * 60)
170
182
  output.append(f"Summary: {additions} additions, {deletions} deletions")
171
-
172
- return '\n'.join(output)
183
+
184
+ return "\n".join(output)
173
185
 
174
186
  def register(self, server: FastMCP) -> None:
175
187
  """Register the tool with the MCP server."""
176
188
  tool_self = self
177
-
189
+
178
190
  @server.tool(name=self.name, description=self.description)
179
191
  async def diff_handler(
180
192
  ctx: MCPContext,
@@ -183,7 +195,7 @@ diff a.json b.json --ignore-whitespace"""
183
195
  unified: bool = True,
184
196
  context: int = 3,
185
197
  ignore_whitespace: bool = False,
186
- show_line_numbers: bool = True
198
+ show_line_numbers: bool = True,
187
199
  ) -> str:
188
200
  """Handle diff tool calls."""
189
201
  return await tool_self.run(
@@ -195,7 +207,7 @@ diff a.json b.json --ignore-whitespace"""
195
207
  ignore_whitespace=ignore_whitespace,
196
208
  show_line_numbers=show_line_numbers,
197
209
  )
198
-
210
+
199
211
  async def call(self, ctx: MCPContext, **params) -> str:
200
212
  """Call the tool with arguments."""
201
213
  return await self.run(
@@ -205,18 +217,18 @@ diff a.json b.json --ignore-whitespace"""
205
217
  unified=params.get("unified", True),
206
218
  context=params.get("context", 3),
207
219
  ignore_whitespace=params.get("ignore_whitespace", False),
208
- show_line_numbers=params.get("show_line_numbers", True)
220
+ show_line_numbers=params.get("show_line_numbers", True),
209
221
  )
210
222
 
211
223
 
212
224
  # Create tool instance (requires permission manager to be set)
213
225
  def create_diff_tool(permission_manager: PermissionManager) -> DiffTool:
214
226
  """Create a diff tool instance.
215
-
227
+
216
228
  Args:
217
229
  permission_manager: Permission manager for access control
218
-
230
+
219
231
  Returns:
220
232
  Configured diff tool instance
221
233
  """
222
- return DiffTool(permission_manager)
234
+ return DiffTool(permission_manager)
@@ -3,15 +3,15 @@
3
3
  This module provides the DirectoryTreeTool for viewing file and directory structures.
4
4
  """
5
5
 
6
+ from typing import Any, Unpack, Annotated, TypedDict, final, override
6
7
  from pathlib import Path
7
- from typing import Annotated, Any, TypedDict, Unpack, final, override
8
8
 
9
- from mcp.server.fastmcp import Context as MCPContext
10
- from mcp.server import FastMCP
11
9
  from pydantic import Field
10
+ from mcp.server import FastMCP
11
+ from mcp.server.fastmcp import Context as MCPContext
12
12
 
13
- from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
14
13
  from hanzo_mcp.tools.common.truncate import truncate_response
14
+ from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
15
15
 
16
16
  DirectoryPath = Annotated[
17
17
  str,
@@ -283,9 +283,9 @@ requested. Only works within allowed directories."""
283
283
  # Truncate response to stay within token limits
284
284
  full_response = formatted_output + summary
285
285
  return truncate_response(
286
- full_response,
286
+ full_response,
287
287
  max_tokens=25000,
288
- truncation_message="\n\n[Response truncated due to token limit. Please use a smaller depth, specific subdirectory, or the paginated version of this tool.]"
288
+ truncation_message="\n\n[Response truncated due to token limit. Please use a smaller depth, specific subdirectory, or the paginated version of this tool.]",
289
289
  )
290
290
  except Exception as e:
291
291
  await tool_ctx.error(f"Error generating directory tree: {str(e)}")
@@ -308,7 +308,7 @@ requested. Only works within allowed directories."""
308
308
  ctx: MCPContext,
309
309
  path: DirectoryPath,
310
310
  depth: Depth = 3,
311
- include_filtered: IncludeFiltered = False
311
+ include_filtered: IncludeFiltered = False,
312
312
  ) -> str:
313
313
  return await tool_self.call(
314
314
  ctx, path=path, depth=depth, include_filtered=include_filtered
@@ -4,18 +4,27 @@ This module provides a paginated version of DirectoryTreeTool that supports
4
4
  MCP cursor-based pagination for large directory structures.
5
5
  """
6
6
 
7
+ from typing import (
8
+ Any,
9
+ Dict,
10
+ List,
11
+ Unpack,
12
+ Optional,
13
+ Annotated,
14
+ TypedDict,
15
+ final,
16
+ override,
17
+ )
7
18
  from pathlib import Path
8
- from typing import Annotated, Any, Dict, List, Optional, TypedDict, Unpack, final, override
9
19
 
10
- from mcp.server.fastmcp import Context as MCPContext
11
- from mcp.server import FastMCP
12
20
  from pydantic import Field
21
+ from mcp.server import FastMCP
22
+ from mcp.server.fastmcp import Context as MCPContext
13
23
 
14
24
  from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
15
25
  from hanzo_mcp.tools.common.pagination import (
16
26
  CursorManager,
17
- PaginatedResponse,
18
- paginate_list
27
+ paginate_list,
19
28
  )
20
29
 
21
30
  DirectoryPath = Annotated[
@@ -128,7 +137,7 @@ Returns nextCursor if more entries are available."""
128
137
  depth = params.get("depth", 3)
129
138
  include_filtered = params.get("include_filtered", False)
130
139
  page_size = params.get("page_size", 100)
131
- cursor = params.get("cursor", None)
140
+ cursor = params.get("cursor")
132
141
 
133
142
  # Validate cursor if provided
134
143
  if cursor:
@@ -196,9 +205,7 @@ Returns nextCursor if more entries are available."""
196
205
 
197
206
  # Build the tree and collect entries
198
207
  def collect_entries(
199
- current_path: Path,
200
- current_depth: int = 0,
201
- parent_path: str = ""
208
+ current_path: Path, current_depth: int = 0, parent_path: str = ""
202
209
  ) -> None:
203
210
  """Collect entries in a flat list for pagination."""
204
211
  if not self.is_path_allowed(str(current_path)):
@@ -207,8 +214,7 @@ Returns nextCursor if more entries are available."""
207
214
  try:
208
215
  # Sort entries: directories first, then files alphabetically
209
216
  entries = sorted(
210
- current_path.iterdir(),
211
- key=lambda x: (not x.is_dir(), x.name)
217
+ current_path.iterdir(), key=lambda x: (not x.is_dir(), x.name)
212
218
  )
213
219
 
214
220
  for entry in entries:
@@ -216,7 +222,9 @@ Returns nextCursor if more entries are available."""
216
222
  continue
217
223
 
218
224
  # Calculate relative path for display
219
- relative_path = f"{parent_path}/{entry.name}" if parent_path else entry.name
225
+ relative_path = (
226
+ f"{parent_path}/{entry.name}" if parent_path else entry.name
227
+ )
220
228
 
221
229
  if entry.is_dir():
222
230
  entry_data: Dict[str, Any] = {
@@ -241,19 +249,17 @@ Returns nextCursor if more entries are available."""
241
249
  all_entries.append(entry_data)
242
250
 
243
251
  # Process children recursively
244
- collect_entries(
245
- entry,
246
- current_depth + 1,
247
- relative_path
248
- )
252
+ collect_entries(entry, current_depth + 1, relative_path)
249
253
  else:
250
254
  # Add file entry
251
255
  if depth <= 0 or current_depth < depth:
252
- all_entries.append({
253
- "path": relative_path,
254
- "type": "file",
255
- "depth": current_depth,
256
- })
256
+ all_entries.append(
257
+ {
258
+ "path": relative_path,
259
+ "type": "file",
260
+ "depth": current_depth,
261
+ }
262
+ )
257
263
 
258
264
  except Exception as e:
259
265
  await tool_ctx.warning(f"Error processing {current_path}: {str(e)}")
@@ -271,28 +277,34 @@ Returns nextCursor if more entries are available."""
271
277
  indent = " " * entry["depth"]
272
278
  if entry["type"] == "directory":
273
279
  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
- })
280
+ formatted_entries.append(
281
+ {
282
+ "entry": f"{indent}{entry['path'].split('/')[-1]}/ [skipped - {entry['skipped']}]",
283
+ "type": "directory",
284
+ "skipped": entry.get("skipped"),
285
+ }
286
+ )
279
287
  else:
280
- formatted_entries.append({
281
- "entry": f"{indent}{entry['path'].split('/')[-1]}/",
282
- "type": "directory"
283
- })
288
+ formatted_entries.append(
289
+ {
290
+ "entry": f"{indent}{entry['path'].split('/')[-1]}/",
291
+ "type": "directory",
292
+ }
293
+ )
284
294
  else:
285
- formatted_entries.append({
286
- "entry": f"{indent}{entry['path'].split('/')[-1]}",
287
- "type": "file"
288
- })
295
+ formatted_entries.append(
296
+ {
297
+ "entry": f"{indent}{entry['path'].split('/')[-1]}",
298
+ "type": "file",
299
+ }
300
+ )
289
301
 
290
302
  # Build response
291
303
  response = {
292
304
  "entries": formatted_entries,
293
305
  "total_collected": len(all_entries),
294
306
  "page_size": page_size,
295
- "current_page_count": len(formatted_entries)
307
+ "current_page_count": len(formatted_entries),
296
308
  }
297
309
 
298
310
  # Add next cursor if available
@@ -335,4 +347,4 @@ Returns nextCursor if more entries are available."""
335
347
 
336
348
 
337
349
  # Create the tool instance
338
- directory_tree_paginated_tool = DirectoryTreePaginatedTool()
350
+ directory_tree_paginated_tool = DirectoryTreePaginatedTool()
@@ -3,13 +3,13 @@
3
3
  This module provides the Edit tool for making precise text replacements in files.
4
4
  """
5
5
 
6
+ from typing import Unpack, Annotated, TypedDict, final, override
6
7
  from difflib import unified_diff
7
8
  from pathlib import Path
8
- from typing import Annotated, TypedDict, Unpack, final, override
9
9
 
10
- from mcp.server.fastmcp import Context as MCPContext
11
- from mcp.server import FastMCP
12
10
  from pydantic import Field
11
+ from mcp.server import FastMCP
12
+ from mcp.server.fastmcp import Context as MCPContext
13
13
 
14
14
  from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
15
15
 
@@ -266,7 +266,7 @@ Usage:
266
266
  file_path: FilePath,
267
267
  old_string: OldString,
268
268
  new_string: NewString,
269
- expected_replacements: ExpectedReplacements = 1
269
+ expected_replacements: ExpectedReplacements = 1,
270
270
  ) -> str:
271
271
  return await tool_self.call(
272
272
  ctx,