hanzo-mcp 0.3.4__py3-none-any.whl → 0.5.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (87) hide show
  1. hanzo_mcp/__init__.py +1 -1
  2. hanzo_mcp/cli.py +123 -160
  3. hanzo_mcp/cli_enhanced.py +438 -0
  4. hanzo_mcp/config/__init__.py +19 -0
  5. hanzo_mcp/config/settings.py +388 -0
  6. hanzo_mcp/config/tool_config.py +197 -0
  7. hanzo_mcp/prompts/__init__.py +117 -0
  8. hanzo_mcp/prompts/compact_conversation.py +77 -0
  9. hanzo_mcp/prompts/create_release.py +38 -0
  10. hanzo_mcp/prompts/project_system.py +120 -0
  11. hanzo_mcp/prompts/project_todo_reminder.py +111 -0
  12. hanzo_mcp/prompts/utils.py +286 -0
  13. hanzo_mcp/server.py +120 -98
  14. hanzo_mcp/tools/__init__.py +107 -31
  15. hanzo_mcp/tools/agent/__init__.py +8 -11
  16. hanzo_mcp/tools/agent/agent_tool.py +290 -224
  17. hanzo_mcp/tools/agent/prompt.py +16 -13
  18. hanzo_mcp/tools/agent/tool_adapter.py +9 -9
  19. hanzo_mcp/tools/common/__init__.py +17 -16
  20. hanzo_mcp/tools/common/base.py +79 -110
  21. hanzo_mcp/tools/common/batch_tool.py +330 -0
  22. hanzo_mcp/tools/common/context.py +26 -292
  23. hanzo_mcp/tools/common/permissions.py +12 -12
  24. hanzo_mcp/tools/common/thinking_tool.py +153 -0
  25. hanzo_mcp/tools/common/validation.py +1 -63
  26. hanzo_mcp/tools/filesystem/__init__.py +88 -41
  27. hanzo_mcp/tools/filesystem/base.py +32 -24
  28. hanzo_mcp/tools/filesystem/content_replace.py +114 -107
  29. hanzo_mcp/tools/filesystem/directory_tree.py +129 -105
  30. hanzo_mcp/tools/filesystem/edit.py +279 -0
  31. hanzo_mcp/tools/filesystem/grep.py +458 -0
  32. hanzo_mcp/tools/filesystem/grep_ast_tool.py +250 -0
  33. hanzo_mcp/tools/filesystem/multi_edit.py +362 -0
  34. hanzo_mcp/tools/filesystem/read.py +255 -0
  35. hanzo_mcp/tools/filesystem/write.py +156 -0
  36. hanzo_mcp/tools/jupyter/__init__.py +41 -29
  37. hanzo_mcp/tools/jupyter/base.py +66 -57
  38. hanzo_mcp/tools/jupyter/{edit_notebook.py → notebook_edit.py} +162 -139
  39. hanzo_mcp/tools/jupyter/notebook_read.py +152 -0
  40. hanzo_mcp/tools/shell/__init__.py +29 -20
  41. hanzo_mcp/tools/shell/base.py +87 -45
  42. hanzo_mcp/tools/shell/bash_session.py +731 -0
  43. hanzo_mcp/tools/shell/bash_session_executor.py +295 -0
  44. hanzo_mcp/tools/shell/command_executor.py +435 -384
  45. hanzo_mcp/tools/shell/run_command.py +284 -131
  46. hanzo_mcp/tools/shell/run_command_windows.py +328 -0
  47. hanzo_mcp/tools/shell/session_manager.py +196 -0
  48. hanzo_mcp/tools/shell/session_storage.py +325 -0
  49. hanzo_mcp/tools/todo/__init__.py +66 -0
  50. hanzo_mcp/tools/todo/base.py +319 -0
  51. hanzo_mcp/tools/todo/todo_read.py +148 -0
  52. hanzo_mcp/tools/todo/todo_write.py +378 -0
  53. hanzo_mcp/tools/vector/__init__.py +95 -0
  54. hanzo_mcp/tools/vector/infinity_store.py +365 -0
  55. hanzo_mcp/tools/vector/project_manager.py +361 -0
  56. hanzo_mcp/tools/vector/vector_index.py +115 -0
  57. hanzo_mcp/tools/vector/vector_search.py +215 -0
  58. {hanzo_mcp-0.3.4.dist-info → hanzo_mcp-0.5.0.dist-info}/METADATA +35 -3
  59. hanzo_mcp-0.5.0.dist-info/RECORD +63 -0
  60. {hanzo_mcp-0.3.4.dist-info → hanzo_mcp-0.5.0.dist-info}/WHEEL +1 -1
  61. hanzo_mcp/tools/agent/base_provider.py +0 -73
  62. hanzo_mcp/tools/agent/litellm_provider.py +0 -45
  63. hanzo_mcp/tools/agent/lmstudio_agent.py +0 -385
  64. hanzo_mcp/tools/agent/lmstudio_provider.py +0 -219
  65. hanzo_mcp/tools/agent/provider_registry.py +0 -120
  66. hanzo_mcp/tools/common/error_handling.py +0 -86
  67. hanzo_mcp/tools/common/logging_config.py +0 -115
  68. hanzo_mcp/tools/common/session.py +0 -91
  69. hanzo_mcp/tools/common/think_tool.py +0 -123
  70. hanzo_mcp/tools/common/version_tool.py +0 -120
  71. hanzo_mcp/tools/filesystem/edit_file.py +0 -287
  72. hanzo_mcp/tools/filesystem/get_file_info.py +0 -170
  73. hanzo_mcp/tools/filesystem/read_files.py +0 -198
  74. hanzo_mcp/tools/filesystem/search_content.py +0 -275
  75. hanzo_mcp/tools/filesystem/write_file.py +0 -162
  76. hanzo_mcp/tools/jupyter/notebook_operations.py +0 -514
  77. hanzo_mcp/tools/jupyter/read_notebook.py +0 -165
  78. hanzo_mcp/tools/project/__init__.py +0 -64
  79. hanzo_mcp/tools/project/analysis.py +0 -882
  80. hanzo_mcp/tools/project/base.py +0 -66
  81. hanzo_mcp/tools/project/project_analyze.py +0 -173
  82. hanzo_mcp/tools/shell/run_script.py +0 -215
  83. hanzo_mcp/tools/shell/script_tool.py +0 -244
  84. hanzo_mcp-0.3.4.dist-info/RECORD +0 -53
  85. {hanzo_mcp-0.3.4.dist-info → hanzo_mcp-0.5.0.dist-info}/entry_points.txt +0 -0
  86. {hanzo_mcp-0.3.4.dist-info → hanzo_mcp-0.5.0.dist-info}/licenses/LICENSE +0 -0
  87. {hanzo_mcp-0.3.4.dist-info → hanzo_mcp-0.5.0.dist-info}/top_level.txt +0 -0
@@ -2,6 +2,8 @@
2
2
 
3
3
  import json
4
4
  import os
5
+ import sys
6
+ import tempfile
5
7
  from collections.abc import Awaitable, Callable
6
8
  from pathlib import Path
7
9
  from typing import Any, TypeVar, final
@@ -18,9 +20,14 @@ class PermissionManager:
18
20
  def __init__(self) -> None:
19
21
  """Initialize the permission manager."""
20
22
  # Allowed paths
21
- self.allowed_paths: set[Path] = set(
22
- [Path("/tmp").resolve(), Path("/var").resolve()]
23
- )
23
+ self.allowed_paths: set[Path] = set()
24
+
25
+ # Allowed paths based on platform
26
+ if sys.platform == "win32": # Windows
27
+ self.allowed_paths.add(Path(tempfile.gettempdir()).resolve())
28
+ else: # Unix/Linux/Mac
29
+ self.allowed_paths.add(Path("/tmp").resolve())
30
+ self.allowed_paths.add(Path("/var").resolve())
24
31
 
25
32
  # Excluded paths
26
33
  self.excluded_paths: set[Path] = set()
@@ -33,17 +40,14 @@ class PermissionManager:
33
40
  """Add default exclusions for sensitive files and directories."""
34
41
  # Sensitive directories
35
42
  sensitive_dirs: list[str] = [
36
- # ".git" is now allowed by default
37
43
  ".ssh",
38
44
  ".gnupg",
39
- ".config",
40
45
  "node_modules",
41
46
  "__pycache__",
42
47
  ".venv",
43
48
  "venv",
44
49
  "env",
45
50
  ".idea",
46
- ".vscode",
47
51
  ".DS_Store",
48
52
  ]
49
53
  self.excluded_patterns.extend(sensitive_dirs)
@@ -69,9 +73,7 @@ class PermissionManager:
69
73
  Args:
70
74
  path: The path to allow
71
75
  """
72
- # Expand user path (e.g., ~/ or $HOME)
73
- expanded_path = os.path.expanduser(path)
74
- resolved_path: Path = Path(expanded_path).resolve()
76
+ resolved_path: Path = Path(path).resolve()
75
77
  self.allowed_paths.add(resolved_path)
76
78
 
77
79
  def remove_allowed_path(self, path: str) -> None:
@@ -110,9 +112,7 @@ class PermissionManager:
110
112
  Returns:
111
113
  True if the path is allowed, False otherwise
112
114
  """
113
- # Expand user path (e.g., ~/ or $HOME)
114
- expanded_path = os.path.expanduser(path)
115
- resolved_path: Path = Path(expanded_path).resolve()
115
+ resolved_path: Path = Path(path).resolve()
116
116
 
117
117
  # Check exclusions first
118
118
  if self._is_path_excluded(resolved_path):
@@ -0,0 +1,153 @@
1
+ """Thinking tool implementation.
2
+
3
+ This module provides the ThinkingTool for Claude to engage in structured thinking.
4
+ """
5
+
6
+ from typing import Annotated, TypedDict, Unpack, final, override
7
+
8
+ from fastmcp import Context as MCPContext
9
+ from fastmcp import FastMCP
10
+ from fastmcp.server.dependencies import get_context
11
+ from pydantic import Field
12
+
13
+ from hanzo_mcp.tools.common.base import BaseTool
14
+ from hanzo_mcp.tools.common.context import create_tool_context
15
+
16
+
17
+ Thought = Annotated[
18
+ str,
19
+ Field(
20
+ description="The detailed thought process to record",
21
+ min_length=1,
22
+ ),
23
+ ]
24
+
25
+
26
+ class ThinkingToolParams(TypedDict):
27
+ """Parameters for the ThinkingTool.
28
+
29
+ Attributes:
30
+ thought: The detailed thought process to record
31
+ """
32
+
33
+ thought: Thought
34
+
35
+
36
+ @final
37
+ class ThinkingTool(BaseTool):
38
+ """Tool for Claude to engage in structured thinking."""
39
+
40
+ @property
41
+ @override
42
+ def name(self) -> str:
43
+ """Get the tool name.
44
+
45
+ Returns:
46
+ Tool name
47
+ """
48
+ return "think"
49
+
50
+ @property
51
+ @override
52
+ def description(self) -> str:
53
+ """Get the tool description.
54
+
55
+ Returns:
56
+ Tool description
57
+ """
58
+ return """Use the tool to think about something. It will not obtain new information or make any changes to the repository, but just log the thought. Use it when complex reasoning or brainstorming is needed.
59
+ Ensure thinking content is concise and accurate, without needing to include code details
60
+
61
+ Common use cases:
62
+ 1. When exploring a repository and discovering the source of a bug, call this tool to brainstorm several unique ways of fixing the bug, and assess which change(s) are likely to be simplest and most effective
63
+ 2. After receiving test results, use this tool to brainstorm ways to fix failing tests
64
+ 3. When planning a complex refactoring, use this tool to outline different approaches and their tradeoffs
65
+ 4. When designing a new feature, use this tool to think through architecture decisions and implementation details
66
+ 5. When debugging a complex issue, use this tool to organize your thoughts and hypotheses
67
+ 6. When considering changes to the plan or shifts in thinking that the user has not previously mentioned, consider whether it is necessary to confirm with the user.
68
+
69
+ <think_example>
70
+ Feature Implementation Planning
71
+ - New code search feature requirements:
72
+ * Search for code patterns across multiple files
73
+ * Identify function usages and references
74
+ * Analyze import relationships
75
+ * Generate summary of matching patterns
76
+ - Implementation considerations:
77
+ * Need to leverage existing search mechanisms
78
+ * Should use regex for pattern matching
79
+ * Results need consistent format with other search methods
80
+ * Must handle large codebases efficiently
81
+ - Design approach:
82
+ 1. Create new CodeSearcher class that follows existing search patterns
83
+ 2. Implement core pattern matching algorithm
84
+ 3. Add result formatting methods
85
+ 4. Integrate with file traversal system
86
+ 5. Add caching for performance optimization
87
+ - Testing strategy:
88
+ * Unit tests for search accuracy
89
+ * Integration tests with existing components
90
+ * Performance tests with large codebases
91
+ </think_example>"""
92
+
93
+ def __init__(self) -> None:
94
+ """Initialize the thinking tool."""
95
+ pass
96
+
97
+ @override
98
+ async def call(
99
+ self,
100
+ ctx: MCPContext,
101
+ **params: Unpack[ThinkingToolParams],
102
+ ) -> str:
103
+ """Execute the tool with the given parameters.
104
+
105
+ Args:
106
+ ctx: MCP context
107
+ **params: Tool parameters
108
+
109
+ Returns:
110
+ Tool result
111
+ """
112
+ tool_ctx = create_tool_context(ctx)
113
+ tool_ctx.set_tool_info(self.name)
114
+
115
+ # Extract parameters
116
+ thought = params.get("thought")
117
+
118
+ # Validate required thought parameter
119
+ if not thought:
120
+ await tool_ctx.error(
121
+ "Parameter 'thought' is required but was None or empty"
122
+ )
123
+ return "Error: Parameter 'thought' is required but was None or empty"
124
+
125
+ if thought.strip() == "":
126
+ await tool_ctx.error("Parameter 'thought' cannot be empty")
127
+ return "Error: Parameter 'thought' cannot be empty"
128
+
129
+ # Log the thought but don't take action
130
+ await tool_ctx.info("Thinking process recorded")
131
+
132
+ # Return confirmation
133
+ return "I've recorded your thinking process. You can continue with your next action based on this analysis."
134
+
135
+ @override
136
+ def register(self, mcp_server: FastMCP) -> None:
137
+ """Register this thinking tool with the MCP server.
138
+
139
+ Creates a wrapper function with explicitly defined parameters that match
140
+ the tool's parameter schema and registers it with the MCP server.
141
+
142
+ Args:
143
+ mcp_server: The FastMCP server instance
144
+ """
145
+ tool_self = self # Create a reference to self for use in the closure
146
+
147
+ @mcp_server.tool(name=self.name, description=self.description)
148
+ async def think(
149
+ ctx: MCPContext,
150
+ thought: Thought,
151
+ ) -> str:
152
+ ctx = get_context()
153
+ return await tool_self.call(ctx, thought=thought)
@@ -3,7 +3,7 @@
3
3
  This module provides utilities for validating parameters in tool functions.
4
4
  """
5
5
 
6
- from typing import Any, TypeVar, final
6
+ from typing import TypeVar, final
7
7
 
8
8
  T = TypeVar("T")
9
9
 
@@ -32,48 +32,6 @@ class ValidationResult:
32
32
  return not self.is_valid
33
33
 
34
34
 
35
- def validate_parameter(
36
- parameter: Any, parameter_name: str, allow_empty: bool = False
37
- ) -> ValidationResult:
38
- """Validate a single parameter.
39
-
40
- Args:
41
- parameter: The parameter value to validate
42
- parameter_name: The name of the parameter (for error messages)
43
- allow_empty: Whether to allow empty strings, lists, etc.
44
-
45
- Returns:
46
- A ValidationResult indicating whether the parameter is valid
47
- """
48
- # Check for None
49
- if parameter is None:
50
- return ValidationResult(
51
- is_valid=False,
52
- error_message=f"Parameter '{parameter_name}' is required but was None",
53
- )
54
-
55
- # Check for empty strings
56
- if isinstance(parameter, str) and not allow_empty and parameter.strip() == "":
57
- return ValidationResult(
58
- is_valid=False,
59
- error_message=f"Parameter '{parameter_name}' is required but was empty string",
60
- )
61
-
62
- # Check for empty collections
63
- if (
64
- isinstance(parameter, (list, tuple, dict, set))
65
- and not allow_empty
66
- and len(parameter) == 0
67
- ):
68
- return ValidationResult(
69
- is_valid=False,
70
- error_message=f"Parameter '{parameter_name}' is required but was empty {type(parameter).__name__}",
71
- )
72
-
73
- # Parameter is valid
74
- return ValidationResult(is_valid=True)
75
-
76
-
77
35
  def validate_path_parameter(
78
36
  path: str | None, parameter_name: str = "path"
79
37
  ) -> ValidationResult:
@@ -102,23 +60,3 @@ def validate_path_parameter(
102
60
 
103
61
  # Path is valid
104
62
  return ValidationResult(is_valid=True)
105
-
106
-
107
- def validate_parameters(**kwargs: Any) -> ValidationResult:
108
- """Validate multiple parameters.
109
-
110
- Accepts keyword arguments where the key is the parameter name and the value is the parameter value.
111
-
112
- Args:
113
- **kwargs: Parameters to validate as name=value pairs
114
-
115
- Returns:
116
- A ValidationResult for the first invalid parameter, or a valid result if all are valid
117
- """
118
- for name, value in kwargs.items():
119
- result = validate_parameter(value, name)
120
- if result.is_error:
121
- return result
122
-
123
- # All parameters are valid
124
- return ValidationResult(is_valid=True)
@@ -4,91 +4,138 @@ 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 mcp.server.fastmcp import FastMCP
7
+ from fastmcp import FastMCP
8
8
 
9
9
  from hanzo_mcp.tools.common.base import BaseTool, ToolRegistry
10
- from hanzo_mcp.tools.common.context import DocumentContext
10
+
11
11
  from hanzo_mcp.tools.common.permissions import PermissionManager
12
12
  from hanzo_mcp.tools.filesystem.content_replace import ContentReplaceTool
13
13
  from hanzo_mcp.tools.filesystem.directory_tree import DirectoryTreeTool
14
- from hanzo_mcp.tools.filesystem.edit_file import EditFileTool
15
- from hanzo_mcp.tools.filesystem.get_file_info import GetFileInfoTool
16
- from hanzo_mcp.tools.filesystem.read_files import ReadFilesTool
17
- from hanzo_mcp.tools.filesystem.search_content import SearchContentTool
18
- from hanzo_mcp.tools.filesystem.write_file import WriteFileTool
14
+ from hanzo_mcp.tools.filesystem.edit import Edit
15
+ from hanzo_mcp.tools.filesystem.grep import Grep
16
+ from hanzo_mcp.tools.filesystem.grep_ast_tool import GrepAstTool
17
+ from hanzo_mcp.tools.filesystem.multi_edit import MultiEdit
18
+ from hanzo_mcp.tools.filesystem.read import ReadTool
19
+ from hanzo_mcp.tools.filesystem.write import Write
19
20
 
20
21
  # Export all tool classes
21
22
  __all__ = [
22
- "ReadFilesTool",
23
- "WriteFileTool",
24
- "EditFileTool",
23
+ "ReadTool",
24
+ "Write",
25
+ "Edit",
26
+ "MultiEdit",
25
27
  "DirectoryTreeTool",
26
- "GetFileInfoTool",
27
- "SearchContentTool",
28
+ "Grep",
28
29
  "ContentReplaceTool",
30
+ "GrepAstTool",
29
31
  "get_filesystem_tools",
30
32
  "register_filesystem_tools",
31
33
  ]
32
34
 
35
+
33
36
  def get_read_only_filesystem_tools(
34
- document_context: DocumentContext, permission_manager: PermissionManager
37
+ permission_manager: PermissionManager,
35
38
  ) -> list[BaseTool]:
36
39
  """Create instances of read-only filesystem tools.
37
-
40
+
38
41
  Args:
39
- document_context: Document context for tracking file contents
40
42
  permission_manager: Permission manager for access control
41
43
 
42
44
  Returns:
43
45
  List of read-only filesystem tool instances
44
46
  """
45
47
  return [
46
- ReadFilesTool(document_context, permission_manager),
47
- DirectoryTreeTool(document_context, permission_manager),
48
- GetFileInfoTool(document_context, permission_manager),
49
- SearchContentTool(document_context, permission_manager),
48
+ ReadTool(permission_manager),
49
+ DirectoryTreeTool(permission_manager),
50
+ Grep(permission_manager),
51
+ GrepAstTool(permission_manager),
50
52
  ]
51
53
 
52
54
 
53
- def get_filesystem_tools(
54
- document_context: DocumentContext, permission_manager: PermissionManager
55
- ) -> list[BaseTool]:
55
+ def get_filesystem_tools(permission_manager: PermissionManager) -> list[BaseTool]:
56
56
  """Create instances of all filesystem tools.
57
-
57
+
58
58
  Args:
59
- document_context: Document context for tracking file contents
60
59
  permission_manager: Permission manager for access control
61
-
60
+
62
61
  Returns:
63
62
  List of filesystem tool instances
64
63
  """
65
64
  return [
66
- ReadFilesTool(document_context, permission_manager),
67
- WriteFileTool(document_context, permission_manager),
68
- EditFileTool(document_context, permission_manager),
69
- DirectoryTreeTool(document_context, permission_manager),
70
- GetFileInfoTool(document_context, permission_manager),
71
- SearchContentTool(document_context, permission_manager),
72
- ContentReplaceTool(document_context, permission_manager),
65
+ ReadTool(permission_manager),
66
+ Write(permission_manager),
67
+ Edit(permission_manager),
68
+ MultiEdit(permission_manager),
69
+ DirectoryTreeTool(permission_manager),
70
+ Grep(permission_manager),
71
+ ContentReplaceTool(permission_manager),
72
+ GrepAstTool(permission_manager),
73
73
  ]
74
74
 
75
75
 
76
76
  def register_filesystem_tools(
77
77
  mcp_server: FastMCP,
78
- document_context: DocumentContext,
79
78
  permission_manager: PermissionManager,
80
79
  disable_write_tools: bool = False,
81
- ) -> None:
82
- """Register all filesystem tools with the MCP server.
83
-
80
+ disable_search_tools: bool = False,
81
+ enabled_tools: dict[str, bool] | None = None,
82
+ ) -> list[BaseTool]:
83
+ """Register filesystem tools with the MCP server.
84
+
84
85
  Args:
85
86
  mcp_server: The FastMCP server instance
86
- document_context: Document context for tracking file contents
87
87
  permission_manager: Permission manager for access control
88
- disable_write_tools: Whether to disable write/edit tools (default: False)
88
+ disable_write_tools: Whether to disable write tools (default: False)
89
+ disable_search_tools: Whether to disable search tools (default: False)
90
+ enabled_tools: Dictionary of individual tool enable states (default: None)
91
+
92
+ Returns:
93
+ List of registered tools
89
94
  """
90
- if disable_write_tools:
91
- tools = get_read_only_filesystem_tools(document_context, permission_manager)
95
+ # Define tool mapping
96
+ tool_classes = {
97
+ "read": ReadTool,
98
+ "write": Write,
99
+ "edit": Edit,
100
+ "multi_edit": MultiEdit,
101
+ "directory_tree": DirectoryTreeTool,
102
+ "grep": Grep,
103
+ "grep_ast": GrepAstTool,
104
+ "content_replace": ContentReplaceTool,
105
+ }
106
+
107
+ tools = []
108
+
109
+ if enabled_tools:
110
+ # Use individual tool configuration
111
+ for tool_name, enabled in enabled_tools.items():
112
+ if enabled and tool_name in tool_classes:
113
+ tool_class = tool_classes[tool_name]
114
+ tools.append(tool_class(permission_manager))
92
115
  else:
93
- tools = get_filesystem_tools(document_context, permission_manager)
116
+ # Use category-level configuration (backward compatibility)
117
+ if disable_write_tools and disable_search_tools:
118
+ # Only read and directory tools
119
+ tools = [
120
+ ReadTool(permission_manager),
121
+ DirectoryTreeTool(permission_manager),
122
+ ]
123
+ elif disable_write_tools:
124
+ # Read-only tools including search
125
+ tools = get_read_only_filesystem_tools(permission_manager)
126
+ elif disable_search_tools:
127
+ # Write tools but no search
128
+ tools = [
129
+ ReadTool(permission_manager),
130
+ Write(permission_manager),
131
+ Edit(permission_manager),
132
+ MultiEdit(permission_manager),
133
+ DirectoryTreeTool(permission_manager),
134
+ ContentReplaceTool(permission_manager),
135
+ ]
136
+ else:
137
+ # All tools
138
+ tools = get_filesystem_tools(permission_manager)
139
+
94
140
  ToolRegistry.register_tools(mcp_server, tools)
141
+ return tools
@@ -8,27 +8,29 @@ from abc import ABC
8
8
  from pathlib import Path
9
9
  from typing import Any
10
10
 
11
- from mcp.server.fastmcp import Context as MCPContext
11
+ from 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
15
15
 
16
16
 
17
- class FilesystemBaseTool(FileSystemTool,ABC):
17
+ class FilesystemBaseTool(FileSystemTool, ABC):
18
18
  """Enhanced base class for all filesystem tools.
19
-
19
+
20
20
  Provides additional utilities specific to filesystem operations beyond
21
21
  the base functionality in FileSystemTool.
22
22
  """
23
-
24
- async def check_path_allowed(self, path: str, tool_ctx: Any, error_prefix: str = "Error") -> tuple[bool, str]:
23
+
24
+ async def check_path_allowed(
25
+ self, path: str, tool_ctx: Any, error_prefix: str = "Error"
26
+ ) -> tuple[bool, str]:
25
27
  """Check if a path is allowed and log an error if not.
26
-
28
+
27
29
  Args:
28
30
  path: Path to check
29
31
  tool_ctx: Tool context for logging
30
32
  error_prefix: Prefix for error messages
31
-
33
+
32
34
  Returns:
33
35
  tuple of (is_allowed, error_message)
34
36
  """
@@ -37,15 +39,17 @@ class FilesystemBaseTool(FileSystemTool,ABC):
37
39
  await tool_ctx.error(message)
38
40
  return False, f"{error_prefix}: {message}"
39
41
  return True, ""
40
-
41
- async def check_path_exists(self, path: str, tool_ctx: Any, error_prefix: str = "Error") -> tuple[bool, str]:
42
+
43
+ async def check_path_exists(
44
+ self, path: str, tool_ctx: Any, error_prefix: str = "Error"
45
+ ) -> tuple[bool, str]:
42
46
  """Check if a path exists and log an error if not.
43
-
47
+
44
48
  Args:
45
49
  path: Path to check
46
50
  tool_ctx: Tool context for logging
47
51
  error_prefix: Prefix for error messages
48
-
52
+
49
53
  Returns:
50
54
  tuple of (exists, error_message)
51
55
  """
@@ -55,15 +59,17 @@ class FilesystemBaseTool(FileSystemTool,ABC):
55
59
  await tool_ctx.error(message)
56
60
  return False, f"{error_prefix}: {message}"
57
61
  return True, ""
58
-
59
- async def check_is_file(self, path: str, tool_ctx: Any, error_prefix: str = "Error") -> tuple[bool, str]:
62
+
63
+ async def check_is_file(
64
+ self, path: str, tool_ctx: Any, error_prefix: str = "Error"
65
+ ) -> tuple[bool, str]:
60
66
  """Check if a path is a file and log an error if not.
61
-
67
+
62
68
  Args:
63
69
  path: Path to check
64
70
  tool_ctx: Tool context for logging
65
71
  error_prefix: Prefix for error messages
66
-
72
+
67
73
  Returns:
68
74
  tuple of (is_file, error_message)
69
75
  """
@@ -73,15 +79,17 @@ class FilesystemBaseTool(FileSystemTool,ABC):
73
79
  await tool_ctx.error(message)
74
80
  return False, f"{error_prefix}: {message}"
75
81
  return True, ""
76
-
77
- async def check_is_directory(self, path: str, tool_ctx: Any, error_prefix: str = "Error") -> tuple[bool, str]:
82
+
83
+ async def check_is_directory(
84
+ self, path: str, tool_ctx: Any, error_prefix: str = "Error"
85
+ ) -> tuple[bool, str]:
78
86
  """Check if a path is a directory and log an error if not.
79
-
87
+
80
88
  Args:
81
89
  path: Path to check
82
90
  tool_ctx: Tool context for logging
83
91
  error_prefix: Prefix for error messages
84
-
92
+
85
93
  Returns:
86
94
  tuple of (is_directory, error_message)
87
95
  """
@@ -91,22 +99,22 @@ class FilesystemBaseTool(FileSystemTool,ABC):
91
99
  await tool_ctx.error(message)
92
100
  return False, f"{error_prefix}: {message}"
93
101
  return True, ""
94
-
102
+
95
103
  def create_tool_context(self, ctx: MCPContext) -> ToolContext:
96
104
  """Create a tool context with the tool name.
97
-
105
+
98
106
  Args:
99
107
  ctx: MCP context
100
-
108
+
101
109
  Returns:
102
110
  Tool context
103
111
  """
104
112
  tool_ctx = create_tool_context(ctx)
105
113
  return tool_ctx
106
-
114
+
107
115
  def set_tool_context_info(self, tool_ctx: ToolContext) -> None:
108
116
  """Set the tool info on the context.
109
-
117
+
110
118
  Args:
111
119
  tool_ctx: Tool context
112
120
  """