hanzo-mcp 0.3.8__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.
- hanzo_mcp/__init__.py +1 -1
- hanzo_mcp/cli.py +118 -170
- hanzo_mcp/cli_enhanced.py +438 -0
- hanzo_mcp/config/__init__.py +19 -0
- hanzo_mcp/config/settings.py +388 -0
- hanzo_mcp/config/tool_config.py +197 -0
- hanzo_mcp/prompts/__init__.py +117 -0
- hanzo_mcp/prompts/compact_conversation.py +77 -0
- hanzo_mcp/prompts/create_release.py +38 -0
- hanzo_mcp/prompts/project_system.py +120 -0
- hanzo_mcp/prompts/project_todo_reminder.py +111 -0
- hanzo_mcp/prompts/utils.py +286 -0
- hanzo_mcp/server.py +117 -99
- hanzo_mcp/tools/__init__.py +105 -32
- hanzo_mcp/tools/agent/__init__.py +8 -11
- hanzo_mcp/tools/agent/agent_tool.py +290 -224
- hanzo_mcp/tools/agent/prompt.py +16 -13
- hanzo_mcp/tools/agent/tool_adapter.py +9 -9
- hanzo_mcp/tools/common/__init__.py +17 -16
- hanzo_mcp/tools/common/base.py +79 -110
- hanzo_mcp/tools/common/batch_tool.py +330 -0
- hanzo_mcp/tools/common/context.py +26 -292
- hanzo_mcp/tools/common/permissions.py +12 -12
- hanzo_mcp/tools/common/thinking_tool.py +153 -0
- hanzo_mcp/tools/common/validation.py +1 -63
- hanzo_mcp/tools/filesystem/__init__.py +88 -57
- hanzo_mcp/tools/filesystem/base.py +32 -24
- hanzo_mcp/tools/filesystem/content_replace.py +114 -107
- hanzo_mcp/tools/filesystem/directory_tree.py +129 -105
- hanzo_mcp/tools/filesystem/edit.py +279 -0
- hanzo_mcp/tools/filesystem/grep.py +458 -0
- hanzo_mcp/tools/filesystem/grep_ast_tool.py +250 -0
- hanzo_mcp/tools/filesystem/multi_edit.py +362 -0
- hanzo_mcp/tools/filesystem/read.py +255 -0
- hanzo_mcp/tools/filesystem/write.py +156 -0
- hanzo_mcp/tools/jupyter/__init__.py +41 -29
- hanzo_mcp/tools/jupyter/base.py +66 -57
- hanzo_mcp/tools/jupyter/{edit_notebook.py → notebook_edit.py} +162 -139
- hanzo_mcp/tools/jupyter/notebook_read.py +152 -0
- hanzo_mcp/tools/shell/__init__.py +29 -20
- hanzo_mcp/tools/shell/base.py +87 -45
- hanzo_mcp/tools/shell/bash_session.py +731 -0
- hanzo_mcp/tools/shell/bash_session_executor.py +295 -0
- hanzo_mcp/tools/shell/command_executor.py +435 -384
- hanzo_mcp/tools/shell/run_command.py +284 -131
- hanzo_mcp/tools/shell/run_command_windows.py +328 -0
- hanzo_mcp/tools/shell/session_manager.py +196 -0
- hanzo_mcp/tools/shell/session_storage.py +325 -0
- hanzo_mcp/tools/todo/__init__.py +66 -0
- hanzo_mcp/tools/todo/base.py +319 -0
- hanzo_mcp/tools/todo/todo_read.py +148 -0
- hanzo_mcp/tools/todo/todo_write.py +378 -0
- hanzo_mcp/tools/vector/__init__.py +95 -0
- hanzo_mcp/tools/vector/infinity_store.py +365 -0
- hanzo_mcp/tools/vector/project_manager.py +361 -0
- hanzo_mcp/tools/vector/vector_index.py +115 -0
- hanzo_mcp/tools/vector/vector_search.py +215 -0
- {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/METADATA +33 -1
- hanzo_mcp-0.5.0.dist-info/RECORD +63 -0
- {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/WHEEL +1 -1
- hanzo_mcp/tools/agent/base_provider.py +0 -73
- hanzo_mcp/tools/agent/litellm_provider.py +0 -45
- hanzo_mcp/tools/agent/lmstudio_agent.py +0 -385
- hanzo_mcp/tools/agent/lmstudio_provider.py +0 -219
- hanzo_mcp/tools/agent/provider_registry.py +0 -120
- hanzo_mcp/tools/common/error_handling.py +0 -86
- hanzo_mcp/tools/common/logging_config.py +0 -115
- hanzo_mcp/tools/common/session.py +0 -91
- hanzo_mcp/tools/common/think_tool.py +0 -123
- hanzo_mcp/tools/common/version_tool.py +0 -120
- hanzo_mcp/tools/filesystem/edit_file.py +0 -287
- hanzo_mcp/tools/filesystem/get_file_info.py +0 -170
- hanzo_mcp/tools/filesystem/read_files.py +0 -199
- hanzo_mcp/tools/filesystem/search_content.py +0 -275
- hanzo_mcp/tools/filesystem/write_file.py +0 -162
- hanzo_mcp/tools/jupyter/notebook_operations.py +0 -514
- hanzo_mcp/tools/jupyter/read_notebook.py +0 -165
- hanzo_mcp/tools/project/__init__.py +0 -64
- hanzo_mcp/tools/project/analysis.py +0 -886
- hanzo_mcp/tools/project/base.py +0 -66
- hanzo_mcp/tools/project/project_analyze.py +0 -173
- hanzo_mcp/tools/shell/run_script.py +0 -215
- hanzo_mcp/tools/shell/script_tool.py +0 -244
- hanzo_mcp-0.3.8.dist-info/RECORD +0 -53
- {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/top_level.txt +0 -0
|
@@ -3,21 +3,17 @@
|
|
|
3
3
|
This package provides tools for executing shell commands and scripts.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import shutil
|
|
7
|
+
|
|
8
|
+
from fastmcp import FastMCP
|
|
7
9
|
|
|
8
10
|
from hanzo_mcp.tools.common.base import BaseTool, ToolRegistry
|
|
9
11
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
12
|
+
from hanzo_mcp.tools.shell.bash_session_executor import BashSessionExecutor
|
|
10
13
|
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
14
|
|
|
15
15
|
# Export all tool classes
|
|
16
16
|
__all__ = [
|
|
17
|
-
"RunCommandTool",
|
|
18
|
-
"RunScriptTool",
|
|
19
|
-
"ScriptTool",
|
|
20
|
-
"CommandExecutor",
|
|
21
17
|
"get_shell_tools",
|
|
22
18
|
"register_shell_tools",
|
|
23
19
|
]
|
|
@@ -27,32 +23,45 @@ def get_shell_tools(
|
|
|
27
23
|
permission_manager: PermissionManager,
|
|
28
24
|
) -> list[BaseTool]:
|
|
29
25
|
"""Create instances of all shell tools.
|
|
30
|
-
|
|
26
|
+
|
|
31
27
|
Args:
|
|
32
28
|
permission_manager: Permission manager for access control
|
|
33
|
-
|
|
29
|
+
|
|
34
30
|
Returns:
|
|
35
31
|
List of shell tool instances
|
|
36
32
|
"""
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
# Detect tmux availability and choose appropriate implementation
|
|
34
|
+
if shutil.which("tmux") is not None:
|
|
35
|
+
# Use tmux-based implementation for interactive sessions
|
|
36
|
+
from hanzo_mcp.tools.shell.run_command import RunCommandTool
|
|
37
|
+
|
|
38
|
+
command_executor = BashSessionExecutor(permission_manager)
|
|
39
|
+
return [
|
|
40
|
+
RunCommandTool(permission_manager, command_executor),
|
|
41
|
+
]
|
|
42
|
+
else:
|
|
43
|
+
# Use Windows-compatible implementation
|
|
44
|
+
from hanzo_mcp.tools.shell.run_command_windows import RunCommandTool
|
|
45
|
+
|
|
46
|
+
command_executor = CommandExecutor(permission_manager)
|
|
47
|
+
return [
|
|
48
|
+
RunCommandTool(permission_manager, command_executor),
|
|
49
|
+
]
|
|
45
50
|
|
|
46
51
|
|
|
47
52
|
def register_shell_tools(
|
|
48
53
|
mcp_server: FastMCP,
|
|
49
54
|
permission_manager: PermissionManager,
|
|
50
|
-
) ->
|
|
55
|
+
) -> list[BaseTool]:
|
|
51
56
|
"""Register all shell tools with the MCP server.
|
|
52
|
-
|
|
57
|
+
|
|
53
58
|
Args:
|
|
54
59
|
mcp_server: The FastMCP server instance
|
|
55
60
|
permission_manager: Permission manager for access control
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
List of registered tools
|
|
56
64
|
"""
|
|
57
65
|
tools = get_shell_tools(permission_manager)
|
|
58
66
|
ToolRegistry.register_tools(mcp_server, tools)
|
|
67
|
+
return tools
|
hanzo_mcp/tools/shell/base.py
CHANGED
|
@@ -5,19 +5,27 @@ including command execution, script running, and process management.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
|
-
from
|
|
9
|
-
from
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import Any, Self, final
|
|
10
10
|
|
|
11
|
-
from
|
|
12
|
-
from mcp.server.fastmcp import FastMCP
|
|
11
|
+
from fastmcp import Context as MCPContext
|
|
13
12
|
|
|
14
13
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
15
14
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
16
15
|
|
|
17
16
|
|
|
17
|
+
class BashCommandStatus(Enum):
|
|
18
|
+
"""Status of bash command execution."""
|
|
19
|
+
|
|
20
|
+
CONTINUE = "continue"
|
|
21
|
+
COMPLETED = "completed"
|
|
22
|
+
NO_CHANGE_TIMEOUT = "no_change_timeout"
|
|
23
|
+
HARD_TIMEOUT = "hard_timeout"
|
|
24
|
+
|
|
25
|
+
|
|
18
26
|
@final
|
|
19
27
|
class CommandResult:
|
|
20
|
-
"""Represents the result of a command execution."""
|
|
28
|
+
"""Represents the result of a command execution with rich metadata."""
|
|
21
29
|
|
|
22
30
|
def __init__(
|
|
23
31
|
self,
|
|
@@ -25,6 +33,9 @@ class CommandResult:
|
|
|
25
33
|
stdout: str = "",
|
|
26
34
|
stderr: str = "",
|
|
27
35
|
error_message: str | None = None,
|
|
36
|
+
session_id: str | None = None,
|
|
37
|
+
status: BashCommandStatus = BashCommandStatus.COMPLETED,
|
|
38
|
+
command: str = "",
|
|
28
39
|
):
|
|
29
40
|
"""Initialize a command result.
|
|
30
41
|
|
|
@@ -33,11 +44,17 @@ class CommandResult:
|
|
|
33
44
|
stdout: Standard output from the command
|
|
34
45
|
stderr: Standard error from the command
|
|
35
46
|
error_message: Optional error message for failure cases
|
|
47
|
+
session_id: Optional session ID used for the command execution
|
|
48
|
+
status: Command execution status
|
|
49
|
+
command: The original command that was executed
|
|
36
50
|
"""
|
|
37
51
|
self.return_code: int = return_code
|
|
38
52
|
self.stdout: str = stdout
|
|
39
53
|
self.stderr: str = stderr
|
|
40
54
|
self.error_message: str | None = error_message
|
|
55
|
+
self.session_id: str | None = session_id
|
|
56
|
+
self.status: BashCommandStatus = status
|
|
57
|
+
self.command: str = command
|
|
41
58
|
|
|
42
59
|
@property
|
|
43
60
|
def is_success(self) -> bool:
|
|
@@ -46,7 +63,41 @@ class CommandResult:
|
|
|
46
63
|
Returns:
|
|
47
64
|
True if the command succeeded, False otherwise
|
|
48
65
|
"""
|
|
49
|
-
return
|
|
66
|
+
return (
|
|
67
|
+
self.return_code == 0
|
|
68
|
+
and self.status == BashCommandStatus.COMPLETED
|
|
69
|
+
and not self.error_message
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def is_running(self) -> bool:
|
|
74
|
+
"""Check if the command is still running.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
True if the command is still running, False otherwise
|
|
78
|
+
"""
|
|
79
|
+
return self.status in {
|
|
80
|
+
BashCommandStatus.CONTINUE,
|
|
81
|
+
BashCommandStatus.NO_CHANGE_TIMEOUT,
|
|
82
|
+
BashCommandStatus.HARD_TIMEOUT,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def exit_code(self) -> int:
|
|
87
|
+
"""Get the exit code (alias for return_code for compatibility)."""
|
|
88
|
+
return self.return_code
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def error(self) -> bool:
|
|
92
|
+
"""Check if there was an error."""
|
|
93
|
+
return not self.is_success
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def message(self) -> str:
|
|
97
|
+
"""Get a human-readable message about the command result."""
|
|
98
|
+
if self.error_message:
|
|
99
|
+
return f"Command `{self.command}` failed: {self.error_message}"
|
|
100
|
+
return f"Command `{self.command}` executed with exit code {self.return_code}."
|
|
50
101
|
|
|
51
102
|
def format_output(self, include_exit_code: bool = True) -> str:
|
|
52
103
|
"""Format the command output as a string.
|
|
@@ -59,6 +110,14 @@ class CommandResult:
|
|
|
59
110
|
"""
|
|
60
111
|
result_parts: list[str] = []
|
|
61
112
|
|
|
113
|
+
# Add session ID if present
|
|
114
|
+
if self.session_id:
|
|
115
|
+
result_parts.append(f"Session ID: {self.session_id}")
|
|
116
|
+
|
|
117
|
+
# Add command status
|
|
118
|
+
if self.status != BashCommandStatus.COMPLETED:
|
|
119
|
+
result_parts.append(f"Status: {self.status.value}")
|
|
120
|
+
|
|
62
121
|
# Add error message if present
|
|
63
122
|
if self.error_message:
|
|
64
123
|
result_parts.append(f"Error: {self.error_message}")
|
|
@@ -78,71 +137,54 @@ class CommandResult:
|
|
|
78
137
|
# Join with newlines
|
|
79
138
|
return "\n\n".join(result_parts)
|
|
80
139
|
|
|
140
|
+
def to_agent_observation(self) -> str:
|
|
141
|
+
"""Format the result for agent consumption."""
|
|
142
|
+
content = self.stdout
|
|
143
|
+
|
|
144
|
+
additional_info: list[str] = []
|
|
145
|
+
if self.session_id:
|
|
146
|
+
additional_info.append(f"[Session ID: {self.session_id}]")
|
|
147
|
+
|
|
148
|
+
if additional_info:
|
|
149
|
+
content += "\n" + "\n".join(additional_info)
|
|
150
|
+
|
|
151
|
+
return content
|
|
152
|
+
|
|
81
153
|
|
|
82
154
|
class ShellBaseTool(BaseTool, ABC):
|
|
83
155
|
"""Base class for shell-related tools.
|
|
84
|
-
|
|
156
|
+
|
|
85
157
|
Provides common functionality for executing commands and scripts,
|
|
86
158
|
including permissions checking.
|
|
87
159
|
"""
|
|
88
|
-
|
|
160
|
+
|
|
89
161
|
def __init__(self, permission_manager: PermissionManager) -> None:
|
|
90
162
|
"""Initialize the shell base tool.
|
|
91
|
-
|
|
163
|
+
|
|
92
164
|
Args:
|
|
93
165
|
permission_manager: Permission manager for access control
|
|
94
166
|
"""
|
|
95
167
|
self.permission_manager: PermissionManager = permission_manager
|
|
96
|
-
|
|
168
|
+
|
|
97
169
|
def is_path_allowed(self, path: str) -> bool:
|
|
98
170
|
"""Check if a path is allowed according to permission settings.
|
|
99
|
-
|
|
171
|
+
|
|
100
172
|
Args:
|
|
101
173
|
path: Path to check
|
|
102
|
-
|
|
174
|
+
|
|
103
175
|
Returns:
|
|
104
176
|
True if the path is allowed, False otherwise
|
|
105
177
|
"""
|
|
106
178
|
return self.permission_manager.is_path_allowed(path)
|
|
107
|
-
|
|
179
|
+
|
|
108
180
|
@abstractmethod
|
|
109
181
|
async def prepare_tool_context(self, ctx: MCPContext) -> Any:
|
|
110
182
|
"""Create and prepare the tool context.
|
|
111
|
-
|
|
183
|
+
|
|
112
184
|
Args:
|
|
113
185
|
ctx: MCP context
|
|
114
|
-
|
|
186
|
+
|
|
115
187
|
Returns:
|
|
116
188
|
Prepared tool context
|
|
117
189
|
"""
|
|
118
190
|
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)
|