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.
- hanzo_mcp/__init__.py +2 -2
- hanzo_mcp/cli.py +80 -9
- hanzo_mcp/server.py +41 -10
- hanzo_mcp/tools/__init__.py +54 -32
- hanzo_mcp/tools/agent/__init__.py +59 -0
- hanzo_mcp/tools/agent/agent_tool.py +474 -0
- hanzo_mcp/tools/agent/prompt.py +137 -0
- hanzo_mcp/tools/agent/tool_adapter.py +75 -0
- hanzo_mcp/tools/common/__init__.py +29 -0
- hanzo_mcp/tools/common/base.py +216 -0
- hanzo_mcp/tools/common/context.py +7 -3
- hanzo_mcp/tools/common/permissions.py +63 -119
- hanzo_mcp/tools/common/session.py +91 -0
- hanzo_mcp/tools/common/thinking_tool.py +123 -0
- hanzo_mcp/tools/common/version_tool.py +62 -0
- hanzo_mcp/tools/filesystem/__init__.py +85 -5
- hanzo_mcp/tools/filesystem/base.py +113 -0
- hanzo_mcp/tools/filesystem/content_replace.py +287 -0
- hanzo_mcp/tools/filesystem/directory_tree.py +286 -0
- hanzo_mcp/tools/filesystem/edit_file.py +287 -0
- hanzo_mcp/tools/filesystem/get_file_info.py +170 -0
- hanzo_mcp/tools/filesystem/read_files.py +198 -0
- hanzo_mcp/tools/filesystem/search_content.py +275 -0
- hanzo_mcp/tools/filesystem/write_file.py +162 -0
- hanzo_mcp/tools/jupyter/__init__.py +67 -4
- hanzo_mcp/tools/jupyter/base.py +284 -0
- hanzo_mcp/tools/jupyter/edit_notebook.py +295 -0
- hanzo_mcp/tools/jupyter/notebook_operations.py +72 -112
- hanzo_mcp/tools/jupyter/read_notebook.py +165 -0
- hanzo_mcp/tools/project/__init__.py +64 -1
- hanzo_mcp/tools/project/analysis.py +9 -6
- hanzo_mcp/tools/project/base.py +66 -0
- hanzo_mcp/tools/project/project_analyze.py +173 -0
- hanzo_mcp/tools/shell/__init__.py +58 -1
- hanzo_mcp/tools/shell/base.py +148 -0
- hanzo_mcp/tools/shell/command_executor.py +203 -322
- hanzo_mcp/tools/shell/run_command.py +204 -0
- hanzo_mcp/tools/shell/run_script.py +215 -0
- hanzo_mcp/tools/shell/script_tool.py +244 -0
- {hanzo_mcp-0.1.25.dist-info → hanzo_mcp-0.1.32.dist-info}/METADATA +83 -77
- hanzo_mcp-0.1.32.dist-info/RECORD +46 -0
- {hanzo_mcp-0.1.25.dist-info → hanzo_mcp-0.1.32.dist-info}/licenses/LICENSE +2 -2
- hanzo_mcp/tools/common/thinking.py +0 -65
- hanzo_mcp/tools/filesystem/file_operations.py +0 -1050
- hanzo_mcp-0.1.25.dist-info/RECORD +0 -24
- hanzo_mcp-0.1.25.dist-info/zip-safe +0 -1
- {hanzo_mcp-0.1.25.dist-info → hanzo_mcp-0.1.32.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.1.25.dist-info → hanzo_mcp-0.1.32.dist-info}/entry_points.txt +0 -0
- {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=
|
|
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=
|
|
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=
|
|
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
|
|
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
|
|
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)
|