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.
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/PKG-INFO +1 -1
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/__init__.py +1 -1
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/cli.py +21 -3
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/server.py +4 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/__init__.py +6 -4
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/common/__init__.py +4 -4
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/filesystem/__init__.py +6 -1
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/jupyter/__init__.py +6 -1
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp.egg-info/PKG-INFO +1 -1
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp.egg-info/SOURCES.txt +2 -1
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/pyproject.toml +1 -1
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/tests/test_cli.py +94 -3
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/tests/test_server.py +20 -0
- hanzo_mcp-0.2.0/tests/test_tools_registration.py +180 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/LICENSE +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/README.md +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/agent/__init__.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/agent/agent_tool.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/agent/prompt.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/agent/tool_adapter.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/common/base.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/common/context.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/common/permissions.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/common/session.py +0 -0
- /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
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/common/validation.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/common/version_tool.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/filesystem/base.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/filesystem/content_replace.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/filesystem/directory_tree.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/filesystem/edit_file.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/filesystem/get_file_info.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/filesystem/read_files.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/filesystem/search_content.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/filesystem/write_file.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/jupyter/base.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/jupyter/edit_notebook.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/jupyter/notebook_operations.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/jupyter/read_notebook.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/project/__init__.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/project/analysis.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/project/base.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/project/project_analyze.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/shell/__init__.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/shell/base.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/shell/command_executor.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/shell/run_command.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/shell/run_script.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp/tools/shell/script_tool.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp.egg-info/dependency_links.txt +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp.egg-info/entry_points.txt +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp.egg-info/requires.txt +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/hanzo_mcp.egg-info/top_level.txt +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/setup.cfg +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/setup.py +0 -0
- {hanzo_mcp-0.1.35 → hanzo_mcp-0.2.0}/tests/test_validation.py +0 -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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
19
|
-
ToolRegistry.register_tool(mcp_server,
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -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/
|
|
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
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|