hanzo-mcp 0.1.25__py3-none-any.whl → 0.1.32__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 (49) hide show
  1. hanzo_mcp/__init__.py +2 -2
  2. hanzo_mcp/cli.py +80 -9
  3. hanzo_mcp/server.py +41 -10
  4. hanzo_mcp/tools/__init__.py +54 -32
  5. hanzo_mcp/tools/agent/__init__.py +59 -0
  6. hanzo_mcp/tools/agent/agent_tool.py +474 -0
  7. hanzo_mcp/tools/agent/prompt.py +137 -0
  8. hanzo_mcp/tools/agent/tool_adapter.py +75 -0
  9. hanzo_mcp/tools/common/__init__.py +29 -0
  10. hanzo_mcp/tools/common/base.py +216 -0
  11. hanzo_mcp/tools/common/context.py +7 -3
  12. hanzo_mcp/tools/common/permissions.py +63 -119
  13. hanzo_mcp/tools/common/session.py +91 -0
  14. hanzo_mcp/tools/common/thinking_tool.py +123 -0
  15. hanzo_mcp/tools/common/version_tool.py +62 -0
  16. hanzo_mcp/tools/filesystem/__init__.py +85 -5
  17. hanzo_mcp/tools/filesystem/base.py +113 -0
  18. hanzo_mcp/tools/filesystem/content_replace.py +287 -0
  19. hanzo_mcp/tools/filesystem/directory_tree.py +286 -0
  20. hanzo_mcp/tools/filesystem/edit_file.py +287 -0
  21. hanzo_mcp/tools/filesystem/get_file_info.py +170 -0
  22. hanzo_mcp/tools/filesystem/read_files.py +198 -0
  23. hanzo_mcp/tools/filesystem/search_content.py +275 -0
  24. hanzo_mcp/tools/filesystem/write_file.py +162 -0
  25. hanzo_mcp/tools/jupyter/__init__.py +67 -4
  26. hanzo_mcp/tools/jupyter/base.py +284 -0
  27. hanzo_mcp/tools/jupyter/edit_notebook.py +295 -0
  28. hanzo_mcp/tools/jupyter/notebook_operations.py +72 -112
  29. hanzo_mcp/tools/jupyter/read_notebook.py +165 -0
  30. hanzo_mcp/tools/project/__init__.py +64 -1
  31. hanzo_mcp/tools/project/analysis.py +9 -6
  32. hanzo_mcp/tools/project/base.py +66 -0
  33. hanzo_mcp/tools/project/project_analyze.py +173 -0
  34. hanzo_mcp/tools/shell/__init__.py +58 -1
  35. hanzo_mcp/tools/shell/base.py +148 -0
  36. hanzo_mcp/tools/shell/command_executor.py +203 -322
  37. hanzo_mcp/tools/shell/run_command.py +204 -0
  38. hanzo_mcp/tools/shell/run_script.py +215 -0
  39. hanzo_mcp/tools/shell/script_tool.py +244 -0
  40. {hanzo_mcp-0.1.25.dist-info → hanzo_mcp-0.1.32.dist-info}/METADATA +83 -77
  41. hanzo_mcp-0.1.32.dist-info/RECORD +46 -0
  42. {hanzo_mcp-0.1.25.dist-info → hanzo_mcp-0.1.32.dist-info}/licenses/LICENSE +2 -2
  43. hanzo_mcp/tools/common/thinking.py +0 -65
  44. hanzo_mcp/tools/filesystem/file_operations.py +0 -1050
  45. hanzo_mcp-0.1.25.dist-info/RECORD +0 -24
  46. hanzo_mcp-0.1.25.dist-info/zip-safe +0 -1
  47. {hanzo_mcp-0.1.25.dist-info → hanzo_mcp-0.1.32.dist-info}/WHEEL +0 -0
  48. {hanzo_mcp-0.1.25.dist-info → hanzo_mcp-0.1.32.dist-info}/entry_points.txt +0 -0
  49. {hanzo_mcp-0.1.25.dist-info → hanzo_mcp-0.1.32.dist-info}/top_level.txt +0 -0
@@ -84,7 +84,7 @@ print(json.dumps(result))
84
84
 
85
85
  # Execute script
86
86
  result = await self.command_executor.execute_script_from_file(
87
- script=script, language="python", cwd=project_dir, timeout=600.0
87
+ script=script, language="python", cwd=project_dir, timeout=30.0
88
88
  )
89
89
  code, stdout, stderr = result.return_code, result.stdout, result.stderr
90
90
 
@@ -204,7 +204,7 @@ try {
204
204
 
205
205
  # Execute script
206
206
  result = await self.command_executor.execute_script_from_file(
207
- script=script, language="javascript", cwd=project_dir, timeout=600.0
207
+ script=script, language="javascript", cwd=project_dir, timeout=30.0
208
208
  )
209
209
  code, stdout, stderr = result.return_code, result.stdout, result.stderr
210
210
 
@@ -286,7 +286,7 @@ print(json.dumps(result))
286
286
 
287
287
  # Execute script
288
288
  result = await self.command_executor.execute_script_from_file(
289
- script=script, language="python", cwd=project_dir, timeout=600.0
289
+ script=script, language="python", cwd=project_dir, timeout=30.0
290
290
  )
291
291
  code, stdout, stderr = result.return_code, result.stdout, result.stderr
292
292
 
@@ -813,14 +813,17 @@ class ProjectAnalysis:
813
813
  self.project_manager: ProjectManager = project_manager
814
814
  self.project_analyzer: ProjectAnalyzer = project_analyzer
815
815
  self.permission_manager: PermissionManager = permission_manager
816
-
816
+
817
+ # Legacy method to keep backwards compatibility with tests
817
818
  def register_tools(self, mcp_server: FastMCP) -> None:
818
819
  """Register project analysis tools with the MCP server.
820
+
821
+ Legacy method for backwards compatibility with existing tests.
822
+ New code should use the modular tool classes instead.
819
823
 
820
824
  Args:
821
825
  mcp_server: The FastMCP server instance
822
826
  """
823
-
824
827
  # Project analysis tool
825
828
  @mcp_server.tool()
826
829
  async def project_analyze_tool(project_dir: str, ctx: MCPContext) -> str:
@@ -859,7 +862,7 @@ class ProjectAnalysis:
859
862
  await tool_ctx.report_progress(10, 100)
860
863
 
861
864
  # Analyze project
862
- analysis: dict[str, Any] = await self.project_manager.analyze_project()
865
+ analysis = await self.project_manager.analyze_project()
863
866
  if "error" in analysis:
864
867
  await tool_ctx.error(f"Error analyzing project: {analysis['error']}")
865
868
  return f"Error analyzing project: {analysis['error']}"
@@ -0,0 +1,66 @@
1
+ """Base classes for project tools.
2
+
3
+ This module provides abstract base classes for project analysis tools.
4
+ """
5
+
6
+ from abc import ABC, abstractmethod
7
+ from typing import Any
8
+
9
+ from mcp.server.fastmcp import Context as MCPContext
10
+
11
+ from hanzo_mcp.tools.common.base import BaseTool
12
+ from hanzo_mcp.tools.common.permissions import PermissionManager
13
+ from hanzo_mcp.tools.common.validation import validate_path_parameter
14
+
15
+
16
+ class ProjectBaseTool(BaseTool, ABC):
17
+ """Base class for project-related tools.
18
+
19
+ Provides common functionality for project analysis and scanning.
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ permission_manager: PermissionManager
25
+ ) -> None:
26
+ """Initialize project base tool.
27
+
28
+ Args:
29
+ permission_manager: Permission manager for access control
30
+ """
31
+ self.permission_manager: PermissionManager = permission_manager
32
+
33
+ def validate_path(self, path: str, param_name: str = "path") -> Any:
34
+ """Validate a path parameter.
35
+
36
+ Args:
37
+ path: Path to validate
38
+ param_name: Name of the parameter (for error messages)
39
+
40
+ Returns:
41
+ Validation result containing validation status and error message if any
42
+ """
43
+ return validate_path_parameter(path, param_name)
44
+
45
+ def is_path_allowed(self, path: str) -> bool:
46
+ """Check if a path is allowed according to permission settings.
47
+
48
+ Args:
49
+ path: Path to check
50
+
51
+ Returns:
52
+ True if the path is allowed, False otherwise
53
+ """
54
+ return self.permission_manager.is_path_allowed(path)
55
+
56
+ @abstractmethod
57
+ async def prepare_tool_context(self, ctx: MCPContext) -> Any:
58
+ """Create and prepare the tool context.
59
+
60
+ Args:
61
+ ctx: MCP context
62
+
63
+ Returns:
64
+ Prepared tool context
65
+ """
66
+ pass
@@ -0,0 +1,173 @@
1
+ """Project analyze tool implementation.
2
+
3
+ This module provides the ProjectAnalyzeTool for analyzing project structure and dependencies.
4
+ """
5
+
6
+ from typing import Any, final, override
7
+
8
+ from mcp.server.fastmcp import Context as MCPContext
9
+ from mcp.server.fastmcp import FastMCP
10
+
11
+ from hanzo_mcp.tools.common.context import create_tool_context
12
+ from hanzo_mcp.tools.project.analysis import ProjectAnalyzer, ProjectManager
13
+ from hanzo_mcp.tools.project.base import ProjectBaseTool
14
+
15
+
16
+ @final
17
+ class ProjectAnalyzeTool(ProjectBaseTool):
18
+ """Tool for analyzing project structure and dependencies."""
19
+
20
+ def __init__(
21
+ self,
22
+ permission_manager: Any,
23
+ project_manager: ProjectManager,
24
+ project_analyzer: ProjectAnalyzer
25
+ ) -> None:
26
+ """Initialize the project analyze tool.
27
+
28
+ Args:
29
+ permission_manager: Permission manager for access control
30
+ project_manager: Project manager for tracking projects
31
+ project_analyzer: Project analyzer for analyzing project structure
32
+ """
33
+ super().__init__(permission_manager)
34
+ self.project_manager: ProjectManager = project_manager
35
+ self.project_analyzer: ProjectAnalyzer = project_analyzer
36
+
37
+ @property
38
+ @override
39
+ def name(self) -> str:
40
+ """Get the tool name.
41
+
42
+ Returns:
43
+ Tool name
44
+ """
45
+ return "project_analyze_tool"
46
+
47
+ @property
48
+ @override
49
+ def description(self) -> str:
50
+ """Get the tool description.
51
+
52
+ Returns:
53
+ Tool description
54
+ """
55
+ return """Analyze a project directory structure and dependencies."""
56
+
57
+ @property
58
+ @override
59
+ def parameters(self) -> dict[str, Any]:
60
+ """Get the parameter specifications for the tool.
61
+
62
+ Returns:
63
+ Parameter specifications
64
+ """
65
+ return {
66
+ "properties": {
67
+ "project_dir": {
68
+ "type": "string",
69
+ "description": "path to the project directory to analyze"
70
+ }
71
+ },
72
+ "required": ["project_dir"],
73
+ "type": "object"
74
+ }
75
+
76
+ @property
77
+ @override
78
+ def required(self) -> list[str]:
79
+ """Get the list of required parameter names.
80
+
81
+ Returns:
82
+ List of required parameter names
83
+ """
84
+ return ["project_dir"]
85
+
86
+ @override
87
+ async def prepare_tool_context(self, ctx: MCPContext) -> Any:
88
+ """Create and prepare the tool context.
89
+
90
+ Args:
91
+ ctx: MCP context
92
+
93
+ Returns:
94
+ Prepared tool context
95
+ """
96
+ tool_ctx = create_tool_context(ctx)
97
+ tool_ctx.set_tool_info(self.name)
98
+ return tool_ctx
99
+
100
+ @override
101
+ async def call(self, ctx: MCPContext, **params: Any) -> str:
102
+ """Execute the tool with the given parameters.
103
+
104
+ Args:
105
+ ctx: MCP context
106
+ **params: Tool parameters
107
+
108
+ Returns:
109
+ Tool result
110
+ """
111
+ tool_ctx = await self.prepare_tool_context(ctx)
112
+
113
+ # Extract parameters
114
+ project_dir = params.get("project_dir")
115
+
116
+ # Validate project_dir parameter
117
+ path_validation = self.validate_path(project_dir, "project_dir")
118
+ if path_validation.is_error:
119
+ await tool_ctx.error(path_validation.error_message)
120
+ return f"Error: {path_validation.error_message}"
121
+
122
+ await tool_ctx.info(f"Analyzing project: {project_dir}")
123
+
124
+ # Check if directory is allowed
125
+ if not self.is_path_allowed(project_dir):
126
+ await tool_ctx.error(f"Directory not allowed: {project_dir}")
127
+ return f"Error: Directory not allowed: {project_dir}"
128
+
129
+ # Set project root
130
+ if not self.project_manager.set_project_root(project_dir):
131
+ await tool_ctx.error(f"Failed to set project root: {project_dir}")
132
+ return f"Error: Failed to set project root: {project_dir}"
133
+
134
+ await tool_ctx.info("Analyzing project structure...")
135
+
136
+ # Report intermediate progress
137
+ await tool_ctx.report_progress(10, 100)
138
+
139
+ # Analyze project
140
+ analysis = await self.project_manager.analyze_project()
141
+ if "error" in analysis:
142
+ await tool_ctx.error(f"Error analyzing project: {analysis['error']}")
143
+ return f"Error analyzing project: {analysis['error']}"
144
+
145
+ # Report more progress
146
+ await tool_ctx.report_progress(50, 100)
147
+
148
+ await tool_ctx.info("Generating project summary...")
149
+
150
+ # Generate summary
151
+ summary = self.project_manager.generate_project_summary()
152
+
153
+ # Complete progress
154
+ await tool_ctx.report_progress(100, 100)
155
+
156
+ await tool_ctx.info("Project analysis complete")
157
+ return summary
158
+
159
+ @override
160
+ def register(self, mcp_server: FastMCP) -> None:
161
+ """Register this project analyze tool with the MCP server.
162
+
163
+ Creates a wrapper function with explicitly defined parameters that match
164
+ the tool's parameter schema and registers it with the MCP server.
165
+
166
+ Args:
167
+ mcp_server: The FastMCP server instance
168
+ """
169
+ tool_self = self # Create a reference to self for use in the closure
170
+
171
+ @mcp_server.tool(name=self.name, description=self.mcp_description)
172
+ async def project_analyze_tool(project_dir: str, ctx: MCPContext) -> str:
173
+ return await tool_self.call(ctx, project_dir=project_dir)
@@ -1 +1,58 @@
1
- """Shell and command execution tools for Hanzo MCP."""
1
+ """Shell tools package for Hanzo MCP.
2
+
3
+ This package provides tools for executing shell commands and scripts.
4
+ """
5
+
6
+ from mcp.server.fastmcp import FastMCP
7
+
8
+ from hanzo_mcp.tools.common.base import BaseTool, ToolRegistry
9
+ from hanzo_mcp.tools.common.permissions import PermissionManager
10
+ from hanzo_mcp.tools.shell.command_executor import CommandExecutor
11
+ from hanzo_mcp.tools.shell.run_command import RunCommandTool
12
+ from hanzo_mcp.tools.shell.run_script import RunScriptTool
13
+ from hanzo_mcp.tools.shell.script_tool import ScriptTool
14
+
15
+ # Export all tool classes
16
+ __all__ = [
17
+ "RunCommandTool",
18
+ "RunScriptTool",
19
+ "ScriptTool",
20
+ "CommandExecutor",
21
+ "get_shell_tools",
22
+ "register_shell_tools",
23
+ ]
24
+
25
+
26
+ def get_shell_tools(
27
+ permission_manager: PermissionManager,
28
+ ) -> list[BaseTool]:
29
+ """Create instances of all shell tools.
30
+
31
+ Args:
32
+ permission_manager: Permission manager for access control
33
+
34
+ Returns:
35
+ List of shell tool instances
36
+ """
37
+ # Initialize the command executor
38
+ command_executor = CommandExecutor(permission_manager)
39
+
40
+ return [
41
+ RunCommandTool(permission_manager, command_executor),
42
+ RunScriptTool(permission_manager, command_executor),
43
+ ScriptTool(permission_manager, command_executor),
44
+ ]
45
+
46
+
47
+ def register_shell_tools(
48
+ mcp_server: FastMCP,
49
+ permission_manager: PermissionManager,
50
+ ) -> None:
51
+ """Register all shell tools with the MCP server.
52
+
53
+ Args:
54
+ mcp_server: The FastMCP server instance
55
+ permission_manager: Permission manager for access control
56
+ """
57
+ tools = get_shell_tools(permission_manager)
58
+ ToolRegistry.register_tools(mcp_server, tools)
@@ -0,0 +1,148 @@
1
+ """Base classes for shell tools.
2
+
3
+ This module provides abstract base classes and utilities for shell tools,
4
+ including command execution, script running, and process management.
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from typing import Any, final
9
+ from typing_extensions import override
10
+
11
+ from mcp.server.fastmcp import Context as MCPContext
12
+ from mcp.server.fastmcp import FastMCP
13
+
14
+ from hanzo_mcp.tools.common.base import BaseTool
15
+ from hanzo_mcp.tools.common.permissions import PermissionManager
16
+
17
+
18
+ @final
19
+ class CommandResult:
20
+ """Represents the result of a command execution."""
21
+
22
+ def __init__(
23
+ self,
24
+ return_code: int = 0,
25
+ stdout: str = "",
26
+ stderr: str = "",
27
+ error_message: str | None = None,
28
+ ):
29
+ """Initialize a command result.
30
+
31
+ Args:
32
+ return_code: The command's return code (0 for success)
33
+ stdout: Standard output from the command
34
+ stderr: Standard error from the command
35
+ error_message: Optional error message for failure cases
36
+ """
37
+ self.return_code: int = return_code
38
+ self.stdout: str = stdout
39
+ self.stderr: str = stderr
40
+ self.error_message: str | None = error_message
41
+
42
+ @property
43
+ def is_success(self) -> bool:
44
+ """Check if the command executed successfully.
45
+
46
+ Returns:
47
+ True if the command succeeded, False otherwise
48
+ """
49
+ return self.return_code == 0
50
+
51
+ def format_output(self, include_exit_code: bool = True) -> str:
52
+ """Format the command output as a string.
53
+
54
+ Args:
55
+ include_exit_code: Whether to include the exit code in the output
56
+
57
+ Returns:
58
+ Formatted output string
59
+ """
60
+ result_parts: list[str] = []
61
+
62
+ # Add error message if present
63
+ if self.error_message:
64
+ result_parts.append(f"Error: {self.error_message}")
65
+
66
+ # Add exit code if requested and not zero (for non-errors)
67
+ if include_exit_code and (self.return_code != 0 or not self.error_message):
68
+ result_parts.append(f"Exit code: {self.return_code}")
69
+
70
+ # Add stdout if present
71
+ if self.stdout:
72
+ result_parts.append(f"STDOUT:\n{self.stdout}")
73
+
74
+ # Add stderr if present
75
+ if self.stderr:
76
+ result_parts.append(f"STDERR:\n{self.stderr}")
77
+
78
+ # Join with newlines
79
+ return "\n\n".join(result_parts)
80
+
81
+
82
+ class ShellBaseTool(BaseTool, ABC):
83
+ """Base class for shell-related tools.
84
+
85
+ Provides common functionality for executing commands and scripts,
86
+ including permissions checking.
87
+ """
88
+
89
+ def __init__(self, permission_manager: PermissionManager) -> None:
90
+ """Initialize the shell base tool.
91
+
92
+ Args:
93
+ permission_manager: Permission manager for access control
94
+ """
95
+ self.permission_manager: PermissionManager = permission_manager
96
+
97
+ def is_path_allowed(self, path: str) -> bool:
98
+ """Check if a path is allowed according to permission settings.
99
+
100
+ Args:
101
+ path: Path to check
102
+
103
+ Returns:
104
+ True if the path is allowed, False otherwise
105
+ """
106
+ return self.permission_manager.is_path_allowed(path)
107
+
108
+ @abstractmethod
109
+ async def prepare_tool_context(self, ctx: MCPContext) -> Any:
110
+ """Create and prepare the tool context.
111
+
112
+ Args:
113
+ ctx: MCP context
114
+
115
+ Returns:
116
+ Prepared tool context
117
+ """
118
+ pass
119
+
120
+ @override
121
+ def register(self, mcp_server: FastMCP) -> None:
122
+ """Register this shell tool with the MCP server.
123
+
124
+ This provides a default implementation that derived classes should override
125
+ with more specific parameter definitions. This implementation uses generic
126
+ **kwargs which doesn't provide proper parameter definitions to MCP.
127
+
128
+ Args:
129
+ mcp_server: The FastMCP server instance
130
+ """
131
+ tool_self = self # Create a reference to self for use in the closure
132
+
133
+ # Each derived class should override this with a more specific signature
134
+ # that explicitly defines the parameters expected by the tool
135
+ @mcp_server.tool(name=self.name, description=self.mcp_description)
136
+ async def generic_wrapper(**kwargs: Any) -> str:
137
+ """Generic wrapper for shell tool.
138
+
139
+ This wrapper should be overridden by derived classes to provide
140
+ explicit parameter definitions.
141
+
142
+ Returns:
143
+ Tool execution result
144
+ """
145
+ # Extract context from kwargs
146
+ ctx = kwargs.pop("ctx")
147
+ # Call the actual tool implementation
148
+ return await tool_self.call(ctx, **kwargs)