hanzo-mcp 0.5.1__py3-none-any.whl → 0.6.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 (118) hide show
  1. hanzo_mcp/__init__.py +1 -1
  2. hanzo_mcp/cli.py +32 -0
  3. hanzo_mcp/dev_server.py +246 -0
  4. hanzo_mcp/prompts/__init__.py +1 -1
  5. hanzo_mcp/prompts/project_system.py +43 -7
  6. hanzo_mcp/server.py +5 -1
  7. hanzo_mcp/tools/__init__.py +168 -6
  8. hanzo_mcp/tools/agent/__init__.py +1 -1
  9. hanzo_mcp/tools/agent/agent.py +401 -0
  10. hanzo_mcp/tools/agent/agent_tool.py +3 -4
  11. hanzo_mcp/tools/common/__init__.py +1 -1
  12. hanzo_mcp/tools/common/base.py +9 -4
  13. hanzo_mcp/tools/common/batch_tool.py +3 -5
  14. hanzo_mcp/tools/common/config_tool.py +1 -1
  15. hanzo_mcp/tools/common/context.py +1 -1
  16. hanzo_mcp/tools/common/palette.py +344 -0
  17. hanzo_mcp/tools/common/palette_loader.py +108 -0
  18. hanzo_mcp/tools/common/stats.py +261 -0
  19. hanzo_mcp/tools/common/thinking_tool.py +3 -5
  20. hanzo_mcp/tools/common/tool_disable.py +144 -0
  21. hanzo_mcp/tools/common/tool_enable.py +182 -0
  22. hanzo_mcp/tools/common/tool_list.py +260 -0
  23. hanzo_mcp/tools/config/__init__.py +10 -0
  24. hanzo_mcp/tools/config/config_tool.py +212 -0
  25. hanzo_mcp/tools/config/index_config.py +176 -0
  26. hanzo_mcp/tools/config/palette_tool.py +166 -0
  27. hanzo_mcp/tools/database/__init__.py +71 -0
  28. hanzo_mcp/tools/database/database_manager.py +246 -0
  29. hanzo_mcp/tools/database/graph.py +482 -0
  30. hanzo_mcp/tools/database/graph_add.py +257 -0
  31. hanzo_mcp/tools/database/graph_query.py +536 -0
  32. hanzo_mcp/tools/database/graph_remove.py +267 -0
  33. hanzo_mcp/tools/database/graph_search.py +348 -0
  34. hanzo_mcp/tools/database/graph_stats.py +345 -0
  35. hanzo_mcp/tools/database/sql.py +411 -0
  36. hanzo_mcp/tools/database/sql_query.py +229 -0
  37. hanzo_mcp/tools/database/sql_search.py +296 -0
  38. hanzo_mcp/tools/database/sql_stats.py +254 -0
  39. hanzo_mcp/tools/editor/__init__.py +11 -0
  40. hanzo_mcp/tools/editor/neovim_command.py +272 -0
  41. hanzo_mcp/tools/editor/neovim_edit.py +290 -0
  42. hanzo_mcp/tools/editor/neovim_session.py +356 -0
  43. hanzo_mcp/tools/filesystem/__init__.py +52 -13
  44. hanzo_mcp/tools/filesystem/base.py +1 -1
  45. hanzo_mcp/tools/filesystem/batch_search.py +812 -0
  46. hanzo_mcp/tools/filesystem/content_replace.py +3 -5
  47. hanzo_mcp/tools/filesystem/diff.py +193 -0
  48. hanzo_mcp/tools/filesystem/directory_tree.py +3 -5
  49. hanzo_mcp/tools/filesystem/edit.py +3 -5
  50. hanzo_mcp/tools/filesystem/find.py +443 -0
  51. hanzo_mcp/tools/filesystem/find_files.py +348 -0
  52. hanzo_mcp/tools/filesystem/git_search.py +505 -0
  53. hanzo_mcp/tools/filesystem/grep.py +2 -2
  54. hanzo_mcp/tools/filesystem/multi_edit.py +3 -5
  55. hanzo_mcp/tools/filesystem/read.py +17 -5
  56. hanzo_mcp/tools/filesystem/{grep_ast_tool.py → symbols.py} +17 -27
  57. hanzo_mcp/tools/filesystem/symbols_unified.py +376 -0
  58. hanzo_mcp/tools/filesystem/tree.py +268 -0
  59. hanzo_mcp/tools/filesystem/unified_search.py +465 -443
  60. hanzo_mcp/tools/filesystem/unix_aliases.py +99 -0
  61. hanzo_mcp/tools/filesystem/watch.py +174 -0
  62. hanzo_mcp/tools/filesystem/write.py +3 -5
  63. hanzo_mcp/tools/jupyter/__init__.py +9 -12
  64. hanzo_mcp/tools/jupyter/base.py +1 -1
  65. hanzo_mcp/tools/jupyter/jupyter.py +326 -0
  66. hanzo_mcp/tools/jupyter/notebook_edit.py +3 -4
  67. hanzo_mcp/tools/jupyter/notebook_read.py +3 -5
  68. hanzo_mcp/tools/llm/__init__.py +31 -0
  69. hanzo_mcp/tools/llm/consensus_tool.py +351 -0
  70. hanzo_mcp/tools/llm/llm_manage.py +413 -0
  71. hanzo_mcp/tools/llm/llm_tool.py +346 -0
  72. hanzo_mcp/tools/llm/llm_unified.py +851 -0
  73. hanzo_mcp/tools/llm/provider_tools.py +412 -0
  74. hanzo_mcp/tools/mcp/__init__.py +15 -0
  75. hanzo_mcp/tools/mcp/mcp_add.py +263 -0
  76. hanzo_mcp/tools/mcp/mcp_remove.py +127 -0
  77. hanzo_mcp/tools/mcp/mcp_stats.py +165 -0
  78. hanzo_mcp/tools/mcp/mcp_unified.py +503 -0
  79. hanzo_mcp/tools/shell/__init__.py +21 -23
  80. hanzo_mcp/tools/shell/base.py +1 -1
  81. hanzo_mcp/tools/shell/base_process.py +303 -0
  82. hanzo_mcp/tools/shell/bash_unified.py +134 -0
  83. hanzo_mcp/tools/shell/logs.py +265 -0
  84. hanzo_mcp/tools/shell/npx.py +194 -0
  85. hanzo_mcp/tools/shell/npx_background.py +254 -0
  86. hanzo_mcp/tools/shell/npx_unified.py +101 -0
  87. hanzo_mcp/tools/shell/open.py +107 -0
  88. hanzo_mcp/tools/shell/pkill.py +262 -0
  89. hanzo_mcp/tools/shell/process_unified.py +131 -0
  90. hanzo_mcp/tools/shell/processes.py +279 -0
  91. hanzo_mcp/tools/shell/run_background.py +326 -0
  92. hanzo_mcp/tools/shell/run_command.py +3 -4
  93. hanzo_mcp/tools/shell/run_command_windows.py +3 -4
  94. hanzo_mcp/tools/shell/uvx.py +187 -0
  95. hanzo_mcp/tools/shell/uvx_background.py +249 -0
  96. hanzo_mcp/tools/shell/uvx_unified.py +101 -0
  97. hanzo_mcp/tools/todo/__init__.py +1 -1
  98. hanzo_mcp/tools/todo/base.py +1 -1
  99. hanzo_mcp/tools/todo/todo.py +265 -0
  100. hanzo_mcp/tools/todo/todo_read.py +3 -5
  101. hanzo_mcp/tools/todo/todo_write.py +3 -5
  102. hanzo_mcp/tools/vector/__init__.py +6 -1
  103. hanzo_mcp/tools/vector/git_ingester.py +3 -0
  104. hanzo_mcp/tools/vector/index_tool.py +358 -0
  105. hanzo_mcp/tools/vector/infinity_store.py +98 -0
  106. hanzo_mcp/tools/vector/project_manager.py +27 -5
  107. hanzo_mcp/tools/vector/vector.py +311 -0
  108. hanzo_mcp/tools/vector/vector_index.py +1 -1
  109. hanzo_mcp/tools/vector/vector_search.py +12 -7
  110. hanzo_mcp-0.6.1.dist-info/METADATA +336 -0
  111. hanzo_mcp-0.6.1.dist-info/RECORD +134 -0
  112. hanzo_mcp-0.6.1.dist-info/entry_points.txt +3 -0
  113. hanzo_mcp-0.5.1.dist-info/METADATA +0 -276
  114. hanzo_mcp-0.5.1.dist-info/RECORD +0 -68
  115. hanzo_mcp-0.5.1.dist-info/entry_points.txt +0 -2
  116. {hanzo_mcp-0.5.1.dist-info → hanzo_mcp-0.6.1.dist-info}/WHEEL +0 -0
  117. {hanzo_mcp-0.5.1.dist-info → hanzo_mcp-0.6.1.dist-info}/licenses/LICENSE +0 -0
  118. {hanzo_mcp-0.5.1.dist-info → hanzo_mcp-0.6.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,356 @@
1
+ """Manage Neovim sessions."""
2
+
3
+ import os
4
+ import subprocess
5
+ import shutil
6
+ import json
7
+ from typing import Annotated, Optional, TypedDict, Unpack, final, override, List
8
+ from pathlib import Path
9
+ from datetime import datetime
10
+
11
+ from mcp.server.fastmcp import Context as MCPContext
12
+ from pydantic import Field
13
+
14
+ from hanzo_mcp.tools.common.base import BaseTool
15
+ from hanzo_mcp.tools.common.context import create_tool_context
16
+
17
+
18
+ Action = Annotated[
19
+ str,
20
+ Field(
21
+ description="Action to perform: save, restore, list, delete",
22
+ min_length=1,
23
+ ),
24
+ ]
25
+
26
+ SessionName = Annotated[
27
+ Optional[str],
28
+ Field(
29
+ description="Name of the session",
30
+ default=None,
31
+ ),
32
+ ]
33
+
34
+ ProjectPath = Annotated[
35
+ Optional[str],
36
+ Field(
37
+ description="Project path (defaults to current directory)",
38
+ default=None,
39
+ ),
40
+ ]
41
+
42
+ AutoName = Annotated[
43
+ bool,
44
+ Field(
45
+ description="Auto-generate session name based on project and timestamp",
46
+ default=False,
47
+ ),
48
+ ]
49
+
50
+ Overwrite = Annotated[
51
+ bool,
52
+ Field(
53
+ description="Overwrite existing session",
54
+ default=False,
55
+ ),
56
+ ]
57
+
58
+
59
+ class NeovimSessionParams(TypedDict, total=False):
60
+ """Parameters for Neovim session tool."""
61
+
62
+ action: str
63
+ session_name: Optional[str]
64
+ project_path: Optional[str]
65
+ auto_name: bool
66
+ overwrite: bool
67
+
68
+
69
+ @final
70
+ class NeovimSessionTool(BaseTool):
71
+ """Tool for managing Neovim sessions."""
72
+
73
+ def __init__(self):
74
+ """Initialize the Neovim session tool."""
75
+ self.session_dir = Path.home() / ".hanzo" / "neovim" / "sessions"
76
+ self.session_dir.mkdir(parents=True, exist_ok=True)
77
+
78
+ @property
79
+ @override
80
+ def name(self) -> str:
81
+ """Get the tool name."""
82
+ return "neovim_session"
83
+
84
+ @property
85
+ @override
86
+ def description(self) -> str:
87
+ """Get the tool description."""
88
+ return """Save and restore Neovim editing sessions.
89
+
90
+ Manage Neovim sessions to save your workspace state including:
91
+ - Open files and buffers
92
+ - Window layouts and splits
93
+ - Cursor positions
94
+ - Marks and registers
95
+ - Local options and mappings
96
+
97
+ Actions:
98
+ - save: Save current Neovim session
99
+ - restore: Restore a saved session
100
+ - list: List all saved sessions
101
+ - delete: Delete a saved session
102
+
103
+ Examples:
104
+ - neovim_session --action save --session-name "feature-work"
105
+ - neovim_session --action save --auto-name # Auto-generate name
106
+ - neovim_session --action restore --session-name "feature-work"
107
+ - neovim_session --action list
108
+ - neovim_session --action list --project-path /path/to/project
109
+ - neovim_session --action delete --session-name "old-session"
110
+
111
+ Sessions are stored in ~/.hanzo/neovim/sessions/
112
+ Project-specific sessions are automatically organized by project path.
113
+
114
+ Note: Requires Neovim to be installed.
115
+ """
116
+
117
+ @override
118
+ async def call(
119
+ self,
120
+ ctx: MCPContext,
121
+ **params: Unpack[NeovimSessionParams],
122
+ ) -> str:
123
+ """Manage Neovim session.
124
+
125
+ Args:
126
+ ctx: MCP context
127
+ **params: Tool parameters
128
+
129
+ Returns:
130
+ Result of the session operation
131
+ """
132
+ tool_ctx = create_tool_context(ctx)
133
+ await tool_ctx.set_tool_info(self.name)
134
+
135
+ # Extract parameters
136
+ action = params.get("action")
137
+ if not action:
138
+ return "Error: action is required (save, restore, list, delete)"
139
+
140
+ session_name = params.get("session_name")
141
+ project_path = params.get("project_path") or os.getcwd()
142
+ auto_name = params.get("auto_name", False)
143
+ overwrite = params.get("overwrite", False)
144
+
145
+ # Validate action
146
+ valid_actions = ["save", "restore", "list", "delete"]
147
+ if action not in valid_actions:
148
+ return f"Error: Invalid action '{action}'. Must be one of: {', '.join(valid_actions)}"
149
+
150
+ # Check if Neovim is available
151
+ nvim_cmd = shutil.which("nvim")
152
+ if not nvim_cmd and action in ["save", "restore"]:
153
+ return "Error: Neovim (nvim) not found. Install it first."
154
+
155
+ # Get project-specific session directory
156
+ project_hash = str(hash(os.path.abspath(project_path)) % 10**8)
157
+ project_name = os.path.basename(project_path) or "root"
158
+ project_session_dir = self.session_dir / f"{project_name}_{project_hash}"
159
+ project_session_dir.mkdir(exist_ok=True)
160
+
161
+ # Handle different actions
162
+ if action == "save":
163
+ return await self._save_session(
164
+ tool_ctx, session_name, project_session_dir,
165
+ auto_name, overwrite, project_path
166
+ )
167
+ elif action == "restore":
168
+ return await self._restore_session(
169
+ tool_ctx, session_name, project_session_dir
170
+ )
171
+ elif action == "list":
172
+ return self._list_sessions(project_session_dir, project_path)
173
+ elif action == "delete":
174
+ return self._delete_session(session_name, project_session_dir)
175
+
176
+ async def _save_session(
177
+ self, tool_ctx, session_name: Optional[str],
178
+ project_dir: Path, auto_name: bool, overwrite: bool,
179
+ project_path: str
180
+ ) -> str:
181
+ """Save Neovim session."""
182
+ # Generate session name if needed
183
+ if auto_name or not session_name:
184
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
185
+ session_name = f"session_{timestamp}"
186
+
187
+ # Sanitize session name
188
+ session_name = session_name.replace("/", "_").replace(" ", "_")
189
+
190
+ session_file = project_dir / f"{session_name}.vim"
191
+ metadata_file = project_dir / f"{session_name}.json"
192
+
193
+ # Check if exists
194
+ if session_file.exists() and not overwrite:
195
+ return f"Error: Session '{session_name}' already exists. Use --overwrite to replace."
196
+
197
+ await tool_ctx.info(f"Saving Neovim session: {session_name}")
198
+
199
+ # Create temporary vim script to save session
200
+ vim_script = f'''
201
+ :mksession! {session_file}
202
+ :echo "Session saved to {session_file}"
203
+ :qa
204
+ '''
205
+
206
+ try:
207
+ # Run Neovim to save session
208
+ # First, check if Neovim is already running
209
+ # For now, we'll create a new instance
210
+ result = subprocess.run(
211
+ ["nvim", "-c", vim_script.strip()],
212
+ capture_output=True,
213
+ text=True
214
+ )
215
+
216
+ if result.returncode != 0 and result.stderr:
217
+ return f"Error saving session: {result.stderr}"
218
+
219
+ # Save metadata
220
+ metadata = {
221
+ "name": session_name,
222
+ "created_at": datetime.now().isoformat(),
223
+ "project_path": project_path,
224
+ "description": f"Neovim session for {project_path}"
225
+ }
226
+
227
+ with open(metadata_file, 'w') as f:
228
+ json.dump(metadata, f, indent=2)
229
+
230
+ return f"""Successfully saved Neovim session '{session_name}'
231
+
232
+ Session file: {session_file}
233
+ Project: {project_path}
234
+
235
+ To restore this session:
236
+ neovim_session --action restore --session-name "{session_name}"
237
+
238
+ Or manually in Neovim:
239
+ :source {session_file}"""
240
+
241
+ except Exception as e:
242
+ return f"Error saving session: {str(e)}"
243
+
244
+ async def _restore_session(
245
+ self, tool_ctx, session_name: Optional[str],
246
+ project_dir: Path
247
+ ) -> str:
248
+ """Restore Neovim session."""
249
+ if not session_name:
250
+ # List available sessions
251
+ sessions = list(project_dir.glob("*.vim"))
252
+ if not sessions:
253
+ return "Error: No sessions found for this project. Use 'neovim_session --action list' to see all sessions."
254
+
255
+ # Use most recent
256
+ sessions.sort(key=lambda x: x.stat().st_mtime, reverse=True)
257
+ session_file = sessions[0]
258
+ session_name = session_file.stem
259
+ else:
260
+ session_file = project_dir / f"{session_name}.vim"
261
+ if not session_file.exists():
262
+ return f"Error: Session '{session_name}' not found. Use 'neovim_session --action list' to see available sessions."
263
+
264
+ await tool_ctx.info(f"Restoring Neovim session: {session_name}")
265
+
266
+ try:
267
+ # Open Neovim with the session
268
+ subprocess.run(["nvim", "-S", str(session_file)])
269
+
270
+ return f"Restored Neovim session '{session_name}'"
271
+
272
+ except Exception as e:
273
+ return f"Error restoring session: {str(e)}"
274
+
275
+ def _list_sessions(self, project_dir: Path, project_path: str) -> str:
276
+ """List available sessions."""
277
+ output = ["=== Neovim Sessions ==="]
278
+ output.append(f"Project: {project_path}\n")
279
+
280
+ # List project-specific sessions
281
+ sessions = list(project_dir.glob("*.vim"))
282
+
283
+ if sessions:
284
+ output.append("Project Sessions:")
285
+ sessions.sort(key=lambda x: x.stat().st_mtime, reverse=True)
286
+
287
+ for session_file in sessions:
288
+ session_name = session_file.stem
289
+ metadata_file = project_dir / f"{session_name}.json"
290
+
291
+ # Get metadata if available
292
+ created_at = "Unknown"
293
+ if metadata_file.exists():
294
+ try:
295
+ with open(metadata_file, 'r') as f:
296
+ metadata = json.load(f)
297
+ created_at = metadata.get("created_at", "Unknown")
298
+ if created_at != "Unknown":
299
+ # Format date
300
+ dt = datetime.fromisoformat(created_at)
301
+ created_at = dt.strftime("%Y-%m-%d %H:%M:%S")
302
+ except:
303
+ pass
304
+
305
+ # Get file size
306
+ size = session_file.stat().st_size
307
+ size_kb = size / 1024
308
+
309
+ output.append(f" - {session_name}")
310
+ output.append(f" Created: {created_at}")
311
+ output.append(f" Size: {size_kb:.1f} KB")
312
+ else:
313
+ output.append("No sessions found for this project.")
314
+
315
+ # Also list all sessions
316
+ all_sessions = list(self.session_dir.rglob("*.vim"))
317
+ other_sessions = [s for s in all_sessions if s.parent != project_dir]
318
+
319
+ if other_sessions:
320
+ output.append("\nOther Projects' Sessions:")
321
+ for session_file in other_sessions[:10]: # Show max 10
322
+ project_name = session_file.parent.name
323
+ session_name = session_file.stem
324
+ output.append(f" - {project_name}/{session_name}")
325
+
326
+ if len(other_sessions) > 10:
327
+ output.append(f" ... and {len(other_sessions) - 10} more")
328
+
329
+ output.append("\nUse 'neovim_session --action restore --session-name <name>' to restore a session.")
330
+
331
+ return "\n".join(output)
332
+
333
+ def _delete_session(self, session_name: Optional[str], project_dir: Path) -> str:
334
+ """Delete a session."""
335
+ if not session_name:
336
+ return "Error: session_name is required for delete action"
337
+
338
+ session_file = project_dir / f"{session_name}.vim"
339
+ metadata_file = project_dir / f"{session_name}.json"
340
+
341
+ if not session_file.exists():
342
+ return f"Error: Session '{session_name}' not found"
343
+
344
+ try:
345
+ session_file.unlink()
346
+ if metadata_file.exists():
347
+ metadata_file.unlink()
348
+
349
+ return f"Successfully deleted session '{session_name}'"
350
+
351
+ except Exception as e:
352
+ return f"Error deleting session: {str(e)}"
353
+
354
+ def register(self, mcp_server) -> None:
355
+ """Register this tool with the MCP server."""
356
+ pass
@@ -4,7 +4,7 @@ This package provides tools for interacting with the filesystem, including readi
4
4
  and editing files, directory navigation, and content searching.
5
5
  """
6
6
 
7
- from fastmcp import FastMCP
7
+ from mcp.server import FastMCP
8
8
 
9
9
  from hanzo_mcp.tools.common.base import BaseTool, ToolRegistry
10
10
 
@@ -13,11 +13,16 @@ from hanzo_mcp.tools.filesystem.content_replace import ContentReplaceTool
13
13
  from hanzo_mcp.tools.filesystem.directory_tree import DirectoryTreeTool
14
14
  from hanzo_mcp.tools.filesystem.edit import Edit
15
15
  from hanzo_mcp.tools.filesystem.grep import Grep
16
- from hanzo_mcp.tools.filesystem.grep_ast_tool import GrepAstTool
16
+ from hanzo_mcp.tools.filesystem.symbols import SymbolsTool
17
+ from hanzo_mcp.tools.filesystem.git_search import GitSearchTool
17
18
  from hanzo_mcp.tools.filesystem.multi_edit import MultiEdit
18
19
  from hanzo_mcp.tools.filesystem.read import ReadTool
19
20
  from hanzo_mcp.tools.filesystem.write import Write
21
+ from hanzo_mcp.tools.filesystem.batch_search import BatchSearchTool
22
+ from hanzo_mcp.tools.filesystem.find_files import FindFilesTool
20
23
  from hanzo_mcp.tools.filesystem.unified_search import UnifiedSearchTool
24
+ from hanzo_mcp.tools.filesystem.watch import watch_tool
25
+ from hanzo_mcp.tools.filesystem.diff import create_diff_tool
21
26
 
22
27
  # Export all tool classes
23
28
  __all__ = [
@@ -28,7 +33,10 @@ __all__ = [
28
33
  "DirectoryTreeTool",
29
34
  "Grep",
30
35
  "ContentReplaceTool",
31
- "GrepAstTool",
36
+ "SymbolsTool",
37
+ "GitSearchTool",
38
+ "BatchSearchTool",
39
+ "FindFilesTool",
32
40
  "UnifiedSearchTool",
33
41
  "get_filesystem_tools",
34
42
  "register_filesystem_tools",
@@ -37,33 +45,46 @@ __all__ = [
37
45
 
38
46
  def get_read_only_filesystem_tools(
39
47
  permission_manager: PermissionManager,
48
+ project_manager=None,
40
49
  ) -> list[BaseTool]:
41
50
  """Create instances of read-only filesystem tools.
42
51
 
43
52
  Args:
44
53
  permission_manager: Permission manager for access control
54
+ project_manager: Optional project manager for unified search
45
55
 
46
56
  Returns:
47
57
  List of read-only filesystem tool instances
48
58
  """
49
- return [
59
+ tools = [
50
60
  ReadTool(permission_manager),
51
61
  DirectoryTreeTool(permission_manager),
52
62
  Grep(permission_manager),
53
- GrepAstTool(permission_manager),
63
+ SymbolsTool(permission_manager),
64
+ GitSearchTool(permission_manager),
65
+ FindFilesTool(permission_manager),
66
+ watch_tool,
67
+ create_diff_tool(permission_manager),
54
68
  ]
69
+
70
+ # Add unified search if project manager is available
71
+ if project_manager:
72
+ tools.append(UnifiedSearchTool(permission_manager, project_manager))
73
+
74
+ return tools
55
75
 
56
76
 
57
- def get_filesystem_tools(permission_manager: PermissionManager) -> list[BaseTool]:
77
+ def get_filesystem_tools(permission_manager: PermissionManager, project_manager=None) -> list[BaseTool]:
58
78
  """Create instances of all filesystem tools.
59
79
 
60
80
  Args:
61
81
  permission_manager: Permission manager for access control
82
+ project_manager: Optional project manager for unified search
62
83
 
63
84
  Returns:
64
85
  List of filesystem tool instances
65
86
  """
66
- return [
87
+ tools = [
67
88
  ReadTool(permission_manager),
68
89
  Write(permission_manager),
69
90
  Edit(permission_manager),
@@ -71,8 +92,18 @@ def get_filesystem_tools(permission_manager: PermissionManager) -> list[BaseTool
71
92
  DirectoryTreeTool(permission_manager),
72
93
  Grep(permission_manager),
73
94
  ContentReplaceTool(permission_manager),
74
- GrepAstTool(permission_manager),
95
+ SymbolsTool(permission_manager),
96
+ GitSearchTool(permission_manager),
97
+ FindFilesTool(permission_manager),
98
+ watch_tool,
99
+ create_diff_tool(permission_manager),
75
100
  ]
101
+
102
+ # Add unified search if project manager is available
103
+ if project_manager:
104
+ tools.append(UnifiedSearchTool(permission_manager, project_manager))
105
+
106
+ return tools
76
107
 
77
108
 
78
109
  def register_filesystem_tools(
@@ -104,9 +135,14 @@ def register_filesystem_tools(
104
135
  "multi_edit": MultiEdit,
105
136
  "directory_tree": DirectoryTreeTool,
106
137
  "grep": Grep,
107
- "grep_ast": GrepAstTool,
138
+ "grep_ast": SymbolsTool, # Using correct import name
139
+ "git_search": GitSearchTool,
108
140
  "content_replace": ContentReplaceTool,
141
+ "batch_search": BatchSearchTool,
142
+ "find_files": FindFilesTool,
109
143
  "unified_search": UnifiedSearchTool,
144
+ "watch": lambda pm: watch_tool, # Singleton instance
145
+ "diff": create_diff_tool,
110
146
  }
111
147
 
112
148
  tools = []
@@ -116,9 +152,12 @@ def register_filesystem_tools(
116
152
  for tool_name, enabled in enabled_tools.items():
117
153
  if enabled and tool_name in tool_classes:
118
154
  tool_class = tool_classes[tool_name]
119
- if tool_name == "unified_search":
120
- # Unified search requires project_manager
155
+ if tool_name in ["batch_search", "unified_search"]:
156
+ # Batch search and unified search require project_manager
121
157
  tools.append(tool_class(permission_manager, project_manager))
158
+ elif tool_name == "watch":
159
+ # Watch tool is a singleton
160
+ tools.append(tool_class(permission_manager))
122
161
  else:
123
162
  tools.append(tool_class(permission_manager))
124
163
  else:
@@ -131,7 +170,7 @@ def register_filesystem_tools(
131
170
  ]
132
171
  elif disable_write_tools:
133
172
  # Read-only tools including search
134
- tools = get_read_only_filesystem_tools(permission_manager)
173
+ tools = get_read_only_filesystem_tools(permission_manager, project_manager)
135
174
  elif disable_search_tools:
136
175
  # Write tools but no search
137
176
  tools = [
@@ -144,7 +183,7 @@ def register_filesystem_tools(
144
183
  ]
145
184
  else:
146
185
  # All tools
147
- tools = get_filesystem_tools(permission_manager)
186
+ tools = get_filesystem_tools(permission_manager, project_manager)
148
187
 
149
188
  ToolRegistry.register_tools(mcp_server, tools)
150
189
  return tools
@@ -8,7 +8,7 @@ from abc import ABC
8
8
  from pathlib import Path
9
9
  from typing import Any
10
10
 
11
- from fastmcp import Context as MCPContext
11
+ from mcp.server.fastmcp import Context as MCPContext
12
12
 
13
13
  from hanzo_mcp.tools.common.base import FileSystemTool
14
14
  from hanzo_mcp.tools.common.context import ToolContext, create_tool_context