hanzo-mcp 0.1.35__tar.gz → 0.2.0__tar.gz

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 (56) hide show
  1. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/PKG-INFO +1 -1
  2. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/__init__.py +1 -1
  3. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/cli.py +21 -3
  4. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/server.py +4 -0
  5. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/__init__.py +6 -4
  6. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/common/__init__.py +4 -4
  7. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/filesystem/__init__.py +6 -1
  8. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/jupyter/__init__.py +6 -1
  9. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp.egg-info/PKG-INFO +1 -1
  10. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp.egg-info/SOURCES.txt +2 -1
  11. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/pyproject.toml +1 -1
  12. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/tests/test_cli.py +94 -3
  13. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/tests/test_server.py +20 -0
  14. hanzo_mcp-0.2.0/tests/test_tools_registration.py +180 -0
  15. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/LICENSE +0 -0
  16. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/README.md +0 -0
  17. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/agent/__init__.py +0 -0
  18. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/agent/agent_tool.py +0 -0
  19. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/agent/prompt.py +0 -0
  20. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/agent/tool_adapter.py +0 -0
  21. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/common/base.py +0 -0
  22. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/common/context.py +0 -0
  23. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/common/permissions.py +0 -0
  24. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/common/session.py +0 -0
  25. /hanzo_mcp-0.1.35/hanzo_mcp/tools/common/thinking_tool.py → /hanzo_mcp-0.2.0/hanzo_mcp/tools/common/think_tool.py +0 -0
  26. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/common/validation.py +0 -0
  27. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/common/version_tool.py +0 -0
  28. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/filesystem/base.py +0 -0
  29. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/filesystem/content_replace.py +0 -0
  30. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/filesystem/directory_tree.py +0 -0
  31. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/filesystem/edit_file.py +0 -0
  32. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/filesystem/get_file_info.py +0 -0
  33. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/filesystem/read_files.py +0 -0
  34. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/filesystem/search_content.py +0 -0
  35. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/filesystem/write_file.py +0 -0
  36. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/jupyter/base.py +0 -0
  37. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/jupyter/edit_notebook.py +0 -0
  38. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/jupyter/notebook_operations.py +0 -0
  39. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/jupyter/read_notebook.py +0 -0
  40. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/project/__init__.py +0 -0
  41. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/project/analysis.py +0 -0
  42. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/project/base.py +0 -0
  43. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/project/project_analyze.py +0 -0
  44. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/shell/__init__.py +0 -0
  45. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/shell/base.py +0 -0
  46. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/shell/command_executor.py +0 -0
  47. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/shell/run_command.py +0 -0
  48. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/shell/run_script.py +0 -0
  49. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/shell/script_tool.py +0 -0
  50. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp.egg-info/dependency_links.txt +0 -0
  51. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp.egg-info/entry_points.txt +0 -0
  52. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp.egg-info/requires.txt +0 -0
  53. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp.egg-info/top_level.txt +0 -0
  54. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/setup.cfg +0 -0
  55. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/setup.py +0 -0
  56. {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/tests/test_validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hanzo-mcp
3
- Version: 0.1.35
3
+ Version: 0.2.0
4
4
  Summary: MCP implementation of Hanzo capabilities
5
5
  Author-email: Hanzo Industries Inc <dev@hanzo.ai>
6
6
  License: MIT
@@ -1,3 +1,3 @@
1
1
  """Hanzo MCP - Implementation of Hanzo capabilities using MCP."""
2
2
 
3
- __version__ = "0.1.35"
3
+ __version__ = "0.2.0"
@@ -82,6 +82,14 @@ def main() -> None:
82
82
  default=False,
83
83
  help="Enable the agent tool (disabled by default)"
84
84
  )
85
+
86
+ _ = parser.add_argument(
87
+ "--disable-write-tools",
88
+ dest="disable_write_tools",
89
+ action="store_true",
90
+ default=False,
91
+ help="Disable write/edit tools (file writing, editing, notebook editing) to use IDE tools instead. Note: Shell commands can still modify files."
92
+ )
85
93
 
86
94
  _ = parser.add_argument(
87
95
  "--install",
@@ -102,12 +110,13 @@ def main() -> None:
102
110
  agent_max_iterations: int = cast(int, args.agent_max_iterations)
103
111
  agent_max_tool_uses: int = cast(int, args.agent_max_tool_uses)
104
112
  enable_agent_tool: bool = cast(bool, args.enable_agent_tool)
113
+ disable_write_tools: bool = cast(bool, args.disable_write_tools)
105
114
  allowed_paths: list[str] = (
106
115
  cast(list[str], args.allowed_paths) if args.allowed_paths else []
107
116
  )
108
117
 
109
118
  if install:
110
- install_claude_desktop_config(name, allowed_paths)
119
+ install_claude_desktop_config(name, allowed_paths, disable_write_tools)
111
120
  return
112
121
 
113
122
  # If no allowed paths are specified, use the user's home directory
@@ -140,20 +149,25 @@ def main() -> None:
140
149
  agent_api_key=agent_api_key,
141
150
  agent_max_iterations=agent_max_iterations,
142
151
  agent_max_tool_uses=agent_max_tool_uses,
143
- enable_agent_tool=enable_agent_tool
152
+ enable_agent_tool=enable_agent_tool,
153
+ disable_write_tools=disable_write_tools
144
154
  )
145
155
  # Transport will be automatically cast to Literal['stdio', 'sse'] by the server
146
156
  server.run(transport=transport)
147
157
 
148
158
 
149
159
  def install_claude_desktop_config(
150
- name: str = "claude-code", allowed_paths: list[str] | None = None
160
+ name: str = "claude-code", allowed_paths: list[str] | None = None,
161
+ disable_write_tools: bool = False
151
162
  ) -> None:
152
163
  """Install the server configuration in Claude Desktop.
153
164
 
154
165
  Args:
155
166
  name: The name to use for the server in the config
156
167
  allowed_paths: Optional list of paths to allow
168
+ disable_write_tools: Whether to disable write/edit tools (file writing, editing, notebook editing)
169
+ to use IDE tools instead. Note: Shell commands can still modify files.
170
+ (default: False)
157
171
  """
158
172
  # Find the Claude Desktop config directory
159
173
  home: Path = Path.home()
@@ -183,6 +197,10 @@ def install_claude_desktop_config(
183
197
  else:
184
198
  # Allow home directory by default
185
199
  args.extend(["--allow-path", str(home)])
200
+
201
+ # Add disable_write_tools flag if specified
202
+ if disable_write_tools:
203
+ args.append("--disable-write-tools")
186
204
 
187
205
  # Create config object
188
206
  config: dict[str, Any] = {
@@ -27,6 +27,7 @@ class HanzoServer:
27
27
  agent_max_iterations: int = 10,
28
28
  agent_max_tool_uses: int = 30,
29
29
  enable_agent_tool: bool = False,
30
+ disable_write_tools: bool = False,
30
31
  ):
31
32
  """Initialize the Hanzo server.
32
33
 
@@ -41,6 +42,7 @@ class HanzoServer:
41
42
  agent_max_iterations: Maximum number of iterations for agent (default: 10)
42
43
  agent_max_tool_uses: Maximum number of total tool uses for agent (default: 30)
43
44
  enable_agent_tool: Whether to enable the agent tool (default: False)
45
+ disable_write_tools: Whether to disable write/edit tools (default: False)
44
46
  """
45
47
  self.mcp = mcp_instance if mcp_instance is not None else FastMCP(name)
46
48
 
@@ -80,6 +82,7 @@ class HanzoServer:
80
82
  self.agent_max_iterations = agent_max_iterations
81
83
  self.agent_max_tool_uses = agent_max_tool_uses
82
84
  self.enable_agent_tool = enable_agent_tool
85
+ self.disable_write_tools = disable_write_tools
83
86
 
84
87
  # Register all tools
85
88
  register_all_tools(
@@ -92,6 +95,7 @@ class HanzoServer:
92
95
  agent_max_iterations=self.agent_max_iterations,
93
96
  agent_max_tool_uses=self.agent_max_tool_uses,
94
97
  enable_agent_tool=self.enable_agent_tool,
98
+ disable_write_tools=self.disable_write_tools,
95
99
  )
96
100
 
97
101
  def run(self, transport: str = "stdio", allowed_paths: list[str] | None = None):
@@ -12,7 +12,7 @@ to delegate tasks to sub-agents for concurrent execution and specialized process
12
12
  from mcp.server.fastmcp import FastMCP
13
13
 
14
14
  from hanzo_mcp.tools.agent import register_agent_tools
15
- from hanzo_mcp.tools.common import register_thinking_tool, register_version_tool
15
+ from hanzo_mcp.tools.common import register_think_tool, register_version_tool
16
16
  from hanzo_mcp.tools.common.context import DocumentContext
17
17
  from hanzo_mcp.tools.common.permissions import PermissionManager
18
18
  from hanzo_mcp.tools.filesystem import register_filesystem_tools
@@ -32,6 +32,7 @@ def register_all_tools(
32
32
  agent_max_iterations: int = 10,
33
33
  agent_max_tool_uses: int = 30,
34
34
  enable_agent_tool: bool = False,
35
+ disable_write_tools: bool = False,
35
36
  ) -> None:
36
37
  """Register all Hanzo tools with the MCP server.
37
38
 
@@ -45,12 +46,13 @@ def register_all_tools(
45
46
  agent_max_iterations: Maximum number of iterations for agent (default: 10)
46
47
  agent_max_tool_uses: Maximum number of total tool uses for agent (default: 30)
47
48
  enable_agent_tool: Whether to enable the agent tool (default: False)
49
+ disable_write_tools: Whether to disable write/edit tools (default: False)
48
50
  """
49
51
  # Register all filesystem tools
50
- register_filesystem_tools(mcp_server, document_context, permission_manager)
52
+ register_filesystem_tools(mcp_server, document_context, permission_manager, disable_write_tools)
51
53
 
52
54
  # Register all jupyter tools
53
- register_jupyter_tools(mcp_server, document_context, permission_manager)
55
+ register_jupyter_tools(mcp_server, document_context, permission_manager, disable_write_tools)
54
56
 
55
57
  # Register shell tools
56
58
  register_shell_tools(mcp_server, permission_manager)
@@ -78,7 +80,7 @@ def register_all_tools(
78
80
  )
79
81
 
80
82
  # Initialize and register thinking tool
81
- register_thinking_tool(mcp_server)
83
+ register_think_tool(mcp_server)
82
84
 
83
85
  # Register version tool
84
86
  register_version_tool(mcp_server)
@@ -3,11 +3,11 @@
3
3
  from mcp.server.fastmcp import FastMCP
4
4
 
5
5
  from hanzo_mcp.tools.common.base import ToolRegistry
6
- from hanzo_mcp.tools.common.thinking_tool import ThinkingTool
6
+ from hanzo_mcp.tools.common.think_tool import ThinkingTool
7
7
  from hanzo_mcp.tools.common.version_tool import VersionTool
8
8
 
9
9
 
10
- def register_thinking_tool(
10
+ def register_think_tool(
11
11
  mcp_server: FastMCP,
12
12
  ) -> None:
13
13
  """Register all thinking tools with the MCP server.
@@ -15,8 +15,8 @@ def register_thinking_tool(
15
15
  Args:
16
16
  mcp_server: The FastMCP server instance
17
17
  """
18
- thinking_tool = ThinkingTool()
19
- ToolRegistry.register_tool(mcp_server, thinking_tool)
18
+ think_tool = ThinkingTool()
19
+ ToolRegistry.register_tool(mcp_server, think_tool)
20
20
 
21
21
 
22
22
  def register_version_tool(
@@ -77,6 +77,7 @@ def register_filesystem_tools(
77
77
  mcp_server: FastMCP,
78
78
  document_context: DocumentContext,
79
79
  permission_manager: PermissionManager,
80
+ disable_write_tools: bool = False,
80
81
  ) -> None:
81
82
  """Register all filesystem tools with the MCP server.
82
83
 
@@ -84,6 +85,10 @@ def register_filesystem_tools(
84
85
  mcp_server: The FastMCP server instance
85
86
  document_context: Document context for tracking file contents
86
87
  permission_manager: Permission manager for access control
88
+ disable_write_tools: Whether to disable write/edit tools (default: False)
87
89
  """
88
- tools = get_filesystem_tools(document_context, permission_manager)
90
+ if disable_write_tools:
91
+ tools = get_read_only_filesystem_tools(document_context, permission_manager)
92
+ else:
93
+ tools = get_filesystem_tools(document_context, permission_manager)
89
94
  ToolRegistry.register_tools(mcp_server, tools)
@@ -59,6 +59,7 @@ def register_jupyter_tools(
59
59
  mcp_server: FastMCP,
60
60
  document_context: DocumentContext,
61
61
  permission_manager: PermissionManager,
62
+ disable_write_tools: bool = False,
62
63
  ) -> None:
63
64
  """Register all Jupyter notebook tools with the MCP server.
64
65
 
@@ -66,6 +67,10 @@ def register_jupyter_tools(
66
67
  mcp_server: The FastMCP server instance
67
68
  document_context: Document context for tracking file contents
68
69
  permission_manager: Permission manager for access control
70
+ disable_write_tools: Whether to disable write/edit tools (default: False)
69
71
  """
70
- tools = get_jupyter_tools(document_context, permission_manager)
72
+ if disable_write_tools:
73
+ tools = get_read_only_jupyter_tools(document_context, permission_manager)
74
+ else:
75
+ tools = get_jupyter_tools(document_context, permission_manager)
71
76
  ToolRegistry.register_tools(mcp_server, tools)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hanzo-mcp
3
- Version: 0.1.35
3
+ Version: 0.2.0
4
4
  Summary: MCP implementation of Hanzo capabilities
5
5
  Author-email: Hanzo Industries Inc <dev@hanzo.ai>
6
6
  License: MIT
@@ -21,7 +21,7 @@ hanzo_mcp/tools/common/base.py
21
21
  hanzo_mcp/tools/common/context.py
22
22
  hanzo_mcp/tools/common/permissions.py
23
23
  hanzo_mcp/tools/common/session.py
24
- hanzo_mcp/tools/common/thinking_tool.py
24
+ hanzo_mcp/tools/common/think_tool.py
25
25
  hanzo_mcp/tools/common/validation.py
26
26
  hanzo_mcp/tools/common/version_tool.py
27
27
  hanzo_mcp/tools/filesystem/__init__.py
@@ -50,4 +50,5 @@ hanzo_mcp/tools/shell/run_script.py
50
50
  hanzo_mcp/tools/shell/script_tool.py
51
51
  tests/test_cli.py
52
52
  tests/test_server.py
53
+ tests/test_tools_registration.py
53
54
  tests/test_validation.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hanzo-mcp"
7
- version = "0.1.35"
7
+ version = "0.2.0"
8
8
  description = "MCP implementation of Hanzo capabilities"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -33,6 +33,7 @@ class TestCLI:
33
33
  mock_args.agent_max_iterations = 10
34
34
  mock_args.agent_max_tool_uses = 30
35
35
  mock_args.enable_agent_tool = False
36
+ mock_args.disable_write_tools = False
36
37
  mock_parse_args.return_value = mock_args
37
38
 
38
39
  # Mock server instance
@@ -53,7 +54,8 @@ class TestCLI:
53
54
  agent_api_key="test_api_key",
54
55
  agent_max_iterations=10,
55
56
  agent_max_tool_uses=30,
56
- enable_agent_tool=False
57
+ enable_agent_tool=False,
58
+ disable_write_tools=False
57
59
  )
58
60
  mock_server.run.assert_called_once_with(transport="stdio")
59
61
 
@@ -96,6 +98,7 @@ class TestCLI:
96
98
  mock_args.agent_max_iterations = 10
97
99
  mock_args.agent_max_tool_uses = 30
98
100
  mock_args.enable_agent_tool = False
101
+ mock_args.disable_write_tools = False
99
102
  mock_parse_args.return_value = mock_args
100
103
 
101
104
  # Mock server instance
@@ -114,7 +117,52 @@ class TestCLI:
114
117
  agent_api_key=None,
115
118
  agent_max_iterations=10,
116
119
  agent_max_tool_uses=30,
117
- enable_agent_tool=False
120
+ enable_agent_tool=False,
121
+ disable_write_tools=False
122
+ )
123
+ mock_server.run.assert_called_once_with(transport="stdio")
124
+
125
+ def test_main_with_disable_write_tools(self) -> None:
126
+ """Test the main function with disable_write_tools=True."""
127
+ with (
128
+ patch("argparse.ArgumentParser.parse_args") as mock_parse_args,
129
+ patch("hanzo_mcp.cli.HanzoServer") as mock_server_class,
130
+ ):
131
+ # Mock parsed arguments
132
+ mock_args = MagicMock()
133
+ mock_args.name = "test-server"
134
+ mock_args.transport = "stdio"
135
+ mock_args.allowed_paths = ["/test/path"]
136
+ mock_args.project_dir = "/test/project"
137
+ mock_args.install = False
138
+ mock_args.agent_model = None
139
+ mock_args.agent_max_tokens = None
140
+ mock_args.agent_api_key = None
141
+ mock_args.agent_max_iterations = 10
142
+ mock_args.agent_max_tool_uses = 30
143
+ mock_args.enable_agent_tool = False
144
+ mock_args.disable_write_tools = True
145
+ mock_parse_args.return_value = mock_args
146
+
147
+ # Mock server instance
148
+ mock_server = MagicMock()
149
+ mock_server_class.return_value = mock_server
150
+
151
+ # Call main
152
+ main()
153
+
154
+ # Verify server was created with disable_write_tools=True
155
+ expected_paths = ["/test/path", "/test/project"]
156
+ mock_server_class.assert_called_once_with(
157
+ name="test-server",
158
+ allowed_paths=expected_paths,
159
+ agent_model=None,
160
+ agent_max_tokens=None,
161
+ agent_api_key=None,
162
+ agent_max_iterations=10,
163
+ agent_max_tool_uses=30,
164
+ enable_agent_tool=False,
165
+ disable_write_tools=True
118
166
  )
119
167
  mock_server.run.assert_called_once_with(transport="stdio")
120
168
 
@@ -313,4 +361,47 @@ class TestInstallClaudeDesktopConfig:
313
361
  # Verify home directory was added as an allowed path
314
362
  assert "--allow-path" in server_args
315
363
  home_path_index = server_args.index("--allow-path") + 1
316
- assert str(tmp_path) in server_args[home_path_index]
364
+ assert str(tmp_path) in server_args[home_path_index]
365
+
366
+ # Verify --disable-write-tools flag is not present
367
+ assert "--disable-write-tools" not in server_args
368
+
369
+ def test_install_config_with_disable_write_tools(
370
+ self, mock_platform: Callable[[str], str], tmp_path: Path
371
+ ) -> None:
372
+ """Test installing config with disable_write_tools=True."""
373
+ # Set platform to macOS
374
+ mock_platform("darwin")
375
+
376
+ # Mock home directory and config path
377
+ with (
378
+ patch("pathlib.Path.home", return_value=Path(tmp_path)),
379
+ patch("sys.executable", "/usr/bin/python3"),
380
+ patch("json.dump") as mock_json_dump,
381
+ patch("builtins.open", create=True) as mock_open,
382
+ patch("pathlib.Path.exists", return_value=False),
383
+ patch("pathlib.Path.mkdir"),
384
+ ):
385
+ # Mock file opening
386
+ mock_file = MagicMock()
387
+ mock_open.return_value.__enter__.return_value = mock_file
388
+
389
+ # Call the install function with disable_write_tools=True
390
+ install_claude_desktop_config(
391
+ "test-server",
392
+ allowed_paths=["/test/path"],
393
+ disable_write_tools=True
394
+ )
395
+
396
+ # Verify correct config was written
397
+ mock_json_dump.assert_called_once()
398
+ config_data = mock_json_dump.call_args[0][0]
399
+ server_args = config_data["mcpServers"]["test-server"]["args"]
400
+
401
+ # Verify allowed path was added
402
+ assert "--allow-path" in server_args
403
+ path_index = server_args.index("--allow-path") + 1
404
+ assert "/test/path" in server_args[path_index]
405
+
406
+ # Verify --disable-write-tools flag is present
407
+ assert "--disable-write-tools" in server_args
@@ -35,6 +35,26 @@ class TestHanzoServer:
35
35
  assert server_instance.command_executor is not None
36
36
  assert server_instance.project_analyzer is not None
37
37
  assert server_instance.project_manager is not None
38
+
39
+ def test_initialization_with_disable_write_tools(self) -> None:
40
+ """Test initializing HanzoServer with disable_write_tools=True."""
41
+ with patch("mcp.server.fastmcp.FastMCP") as mock_fastmcp, \
42
+ patch("hanzo_mcp.tools.register_all_tools") as mock_register_all_tools:
43
+ # Create a mock FastMCP instance
44
+ mock_mcp = MagicMock()
45
+ mock_fastmcp.return_value = mock_mcp
46
+
47
+ # Create the server with disable_write_tools=True
48
+ server = HanzoServer(
49
+ name="test-server",
50
+ mcp_instance=mock_mcp,
51
+ disable_write_tools=True
52
+ )
53
+
54
+ # Verify that the disable_write_tools flag was passed to register_all_tools
55
+ mock_register_all_tools.assert_called_once()
56
+ args, kwargs = mock_register_all_tools.call_args
57
+ assert kwargs.get("disable_write_tools") is True
38
58
 
39
59
  def test_initialization_with_allowed_paths(self) -> None:
40
60
  """Test initializing with allowed paths."""
@@ -0,0 +1,180 @@
1
+ """Tests for the tools registration process."""
2
+
3
+ from unittest.mock import MagicMock, patch
4
+
5
+ import pytest
6
+
7
+ from hanzo_mcp.tools import register_all_tools
8
+ from hanzo_mcp.tools.common.context import DocumentContext
9
+ from hanzo_mcp.tools.common.permissions import PermissionManager
10
+ from hanzo_mcp.tools.filesystem import (
11
+ ReadFilesTool,
12
+ WriteFileTool,
13
+ EditFileTool,
14
+ DirectoryTreeTool,
15
+ GetFileInfoTool,
16
+ SearchContentTool,
17
+ ContentReplaceTool
18
+ )
19
+ from hanzo_mcp.tools.jupyter import (
20
+ ReadNotebookTool,
21
+ EditNotebookTool
22
+ )
23
+
24
+
25
+ class TestToolsRegistration:
26
+ """Test the tools registration process."""
27
+
28
+ @pytest.fixture
29
+ def mcp_server(self):
30
+ """Create a mock MCP server."""
31
+ server = MagicMock()
32
+ return server
33
+
34
+ @pytest.fixture
35
+ def document_context(self):
36
+ """Create a document context."""
37
+ return DocumentContext()
38
+
39
+ @pytest.fixture
40
+ def permission_manager(self):
41
+ """Create a permission manager."""
42
+ return PermissionManager()
43
+
44
+ def test_register_all_tools_default(
45
+ self, mcp_server, document_context, permission_manager
46
+ ):
47
+ """Test registering all tools with default settings."""
48
+ # Mock the tool registry to capture registered tools
49
+ registered_tools = []
50
+
51
+ with patch("hanzo_mcp.tools.ToolRegistry.register_tools") as mock_register:
52
+ mock_register.side_effect = lambda _, tools: registered_tools.extend(tools)
53
+
54
+ # Register all tools with default settings
55
+ register_all_tools(
56
+ mcp_server=mcp_server,
57
+ document_context=document_context,
58
+ permission_manager=permission_manager
59
+ )
60
+
61
+ # Check that all filesystem tools are registered
62
+ fs_tool_types = [type(tool) for tool in registered_tools
63
+ if isinstance(tool, (ReadFilesTool, WriteFileTool, EditFileTool,
64
+ DirectoryTreeTool, GetFileInfoTool,
65
+ SearchContentTool, ContentReplaceTool))]
66
+
67
+ assert ReadFilesTool in fs_tool_types
68
+ assert WriteFileTool in fs_tool_types
69
+ assert EditFileTool in fs_tool_types
70
+
71
+ # Check that all Jupyter tools are registered
72
+ jupyter_tool_types = [type(tool) for tool in registered_tools
73
+ if isinstance(tool, (ReadNotebookTool, EditNotebookTool))]
74
+
75
+ assert ReadNotebookTool in jupyter_tool_types
76
+ assert EditNotebookTool in jupyter_tool_types
77
+
78
+ def test_register_all_tools_disable_write_tools(
79
+ self, mcp_server, document_context, permission_manager
80
+ ):
81
+ """Test registering all tools with disable_write_tools=True."""
82
+ # Mock the tool registry to capture registered tools
83
+ registered_tools = []
84
+
85
+ with patch("hanzo_mcp.tools.ToolRegistry.register_tools") as mock_register:
86
+ mock_register.side_effect = lambda _, tools: registered_tools.extend(tools)
87
+
88
+ # Register all tools with disable_write_tools=True
89
+ register_all_tools(
90
+ mcp_server=mcp_server,
91
+ document_context=document_context,
92
+ permission_manager=permission_manager,
93
+ disable_write_tools=True
94
+ )
95
+
96
+ # Check that only read-only filesystem tools are registered
97
+ fs_tool_types = [type(tool) for tool in registered_tools]
98
+
99
+ # Read-only tools should be present
100
+ assert ReadFilesTool in fs_tool_types
101
+ assert DirectoryTreeTool in fs_tool_types
102
+ assert GetFileInfoTool in fs_tool_types
103
+ assert SearchContentTool in fs_tool_types
104
+
105
+ # Write tools should not be present
106
+ assert WriteFileTool not in fs_tool_types
107
+ assert EditFileTool not in fs_tool_types
108
+ assert ContentReplaceTool not in fs_tool_types
109
+
110
+ # Check that only read-only Jupyter tools are registered
111
+ jupyter_tool_types = [type(tool) for tool in registered_tools]
112
+
113
+ # Read-only tools should be present
114
+ assert ReadNotebookTool in jupyter_tool_types
115
+
116
+ # Write tools should not be present
117
+ assert EditNotebookTool not in jupyter_tool_types
118
+
119
+ def test_register_filesystem_tools_with_disabled_write(
120
+ self, mcp_server, document_context, permission_manager
121
+ ):
122
+ """Test registering filesystem tools with disable_write_tools=True."""
123
+ from hanzo_mcp.tools.filesystem import register_filesystem_tools
124
+
125
+ # Mock the tool registry to capture registered tools
126
+ registered_tools = []
127
+
128
+ with patch("hanzo_mcp.tools.filesystem.ToolRegistry.register_tools") as mock_register:
129
+ mock_register.side_effect = lambda _, tools: registered_tools.extend(tools)
130
+
131
+ # Register filesystem tools with disable_write_tools=True
132
+ register_filesystem_tools(
133
+ mcp_server=mcp_server,
134
+ document_context=document_context,
135
+ permission_manager=permission_manager,
136
+ disable_write_tools=True
137
+ )
138
+
139
+ # Check that only read-only tools are registered
140
+ tool_types = [type(tool) for tool in registered_tools]
141
+
142
+ # Read-only tools should be present
143
+ assert ReadFilesTool in tool_types
144
+ assert DirectoryTreeTool in tool_types
145
+ assert GetFileInfoTool in tool_types
146
+ assert SearchContentTool in tool_types
147
+
148
+ # Write tools should not be present
149
+ assert WriteFileTool not in tool_types
150
+ assert EditFileTool not in tool_types
151
+ assert ContentReplaceTool not in tool_types
152
+
153
+ def test_register_jupyter_tools_with_disabled_write(
154
+ self, mcp_server, document_context, permission_manager
155
+ ):
156
+ """Test registering Jupyter tools with disable_write_tools=True."""
157
+ from hanzo_mcp.tools.jupyter import register_jupyter_tools
158
+
159
+ # Mock the tool registry to capture registered tools
160
+ registered_tools = []
161
+
162
+ with patch("hanzo_mcp.tools.jupyter.ToolRegistry.register_tools") as mock_register:
163
+ mock_register.side_effect = lambda _, tools: registered_tools.extend(tools)
164
+
165
+ # Register Jupyter tools with disable_write_tools=True
166
+ register_jupyter_tools(
167
+ mcp_server=mcp_server,
168
+ document_context=document_context,
169
+ permission_manager=permission_manager,
170
+ disable_write_tools=True
171
+ )
172
+
173
+ # Check that only read-only tools are registered
174
+ tool_types = [type(tool) for tool in registered_tools]
175
+
176
+ # Read-only tools should be present
177
+ assert ReadNotebookTool in tool_types
178
+
179
+ # Write tools should not be present
180
+ assert EditNotebookTool not in tool_types
File without changes
File without changes
File without changes
File without changes