hanzo-mcp 0.3.4__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 +123 -160
- 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 +120 -98
- hanzo_mcp/tools/__init__.py +107 -31
- 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 -41
- 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.4.dist-info → hanzo_mcp-0.5.0.dist-info}/METADATA +35 -3
- hanzo_mcp-0.5.0.dist-info/RECORD +63 -0
- {hanzo_mcp-0.3.4.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 -198
- 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 -882
- 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.4.dist-info/RECORD +0 -53
- {hanzo_mcp-0.3.4.dist-info → hanzo_mcp-0.5.0.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.3.4.dist-info → hanzo_mcp-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {hanzo_mcp-0.3.4.dist-info → hanzo_mcp-0.5.0.dist-info}/top_level.txt +0 -0
hanzo_mcp/tools/project/base.py
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
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
|
|
@@ -1,173 +0,0 @@
|
|
|
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,215 +0,0 @@
|
|
|
1
|
-
"""Run script tool implementation.
|
|
2
|
-
|
|
3
|
-
This module provides the RunScriptTool for executing scripts with interpreters.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import os
|
|
7
|
-
from typing import Any, final, override
|
|
8
|
-
|
|
9
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
10
|
-
from mcp.server.fastmcp import FastMCP
|
|
11
|
-
|
|
12
|
-
from hanzo_mcp.tools.common.context import create_tool_context
|
|
13
|
-
from hanzo_mcp.tools.shell.base import ShellBaseTool
|
|
14
|
-
from hanzo_mcp.tools.shell.command_executor import CommandExecutor
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@final
|
|
18
|
-
class RunScriptTool(ShellBaseTool):
|
|
19
|
-
"""Tool for executing scripts with interpreters."""
|
|
20
|
-
|
|
21
|
-
def __init__(self, permission_manager: Any, command_executor: CommandExecutor) -> None:
|
|
22
|
-
"""Initialize the run script tool.
|
|
23
|
-
|
|
24
|
-
Args:
|
|
25
|
-
permission_manager: Permission manager for access control
|
|
26
|
-
command_executor: Command executor for running scripts
|
|
27
|
-
"""
|
|
28
|
-
super().__init__(permission_manager)
|
|
29
|
-
self.command_executor: CommandExecutor = command_executor
|
|
30
|
-
|
|
31
|
-
@property
|
|
32
|
-
@override
|
|
33
|
-
def name(self) -> str:
|
|
34
|
-
"""Get the tool name.
|
|
35
|
-
|
|
36
|
-
Returns:
|
|
37
|
-
Tool name
|
|
38
|
-
"""
|
|
39
|
-
return "run_script"
|
|
40
|
-
|
|
41
|
-
@property
|
|
42
|
-
@override
|
|
43
|
-
def description(self) -> str:
|
|
44
|
-
"""Get the tool description.
|
|
45
|
-
|
|
46
|
-
Returns:
|
|
47
|
-
Tool description
|
|
48
|
-
"""
|
|
49
|
-
return """Execute a script with the specified interpreter.
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
script: The script content to execute
|
|
53
|
-
cwd: Working directory for script execution. MUST be a subdirectory of one of the allowed paths, not a parent directory. Specify the most specific path possible.
|
|
54
|
-
|
|
55
|
-
interpreter: The interpreter to use (bash, python, etc.)
|
|
56
|
-
use_login_shell: Whether to use login shell (loads ~/.zshrc, ~/.bashrc, etc.)
|
|
57
|
-
|
|
58
|
-
Returns:
|
|
59
|
-
The output of the script
|
|
60
|
-
"""
|
|
61
|
-
|
|
62
|
-
@property
|
|
63
|
-
@override
|
|
64
|
-
def parameters(self) -> dict[str, Any]:
|
|
65
|
-
"""Get the parameter specifications for the tool.
|
|
66
|
-
|
|
67
|
-
Returns:
|
|
68
|
-
Parameter specifications
|
|
69
|
-
"""
|
|
70
|
-
return {
|
|
71
|
-
"properties": {
|
|
72
|
-
"script": {
|
|
73
|
-
"title": "Script",
|
|
74
|
-
"type": "string"
|
|
75
|
-
},
|
|
76
|
-
"cwd": {
|
|
77
|
-
"title": "Cwd",
|
|
78
|
-
"type": "string"
|
|
79
|
-
},
|
|
80
|
-
"interpreter": {
|
|
81
|
-
"default": "bash",
|
|
82
|
-
"title": "Interpreter",
|
|
83
|
-
"type": "string"
|
|
84
|
-
},
|
|
85
|
-
"use_login_shell": {
|
|
86
|
-
"default": True,
|
|
87
|
-
"title": "Use Login Shell",
|
|
88
|
-
"type": "boolean"
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
"required": ["script", "cwd"],
|
|
92
|
-
"title": "run_scriptArguments",
|
|
93
|
-
"type": "object"
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
@property
|
|
97
|
-
@override
|
|
98
|
-
def required(self) -> list[str]:
|
|
99
|
-
"""Get the list of required parameter names.
|
|
100
|
-
|
|
101
|
-
Returns:
|
|
102
|
-
List of required parameter names
|
|
103
|
-
"""
|
|
104
|
-
return ["script", "cwd"]
|
|
105
|
-
|
|
106
|
-
@override
|
|
107
|
-
async def prepare_tool_context(self, ctx: MCPContext) -> Any:
|
|
108
|
-
"""Create and prepare the tool context.
|
|
109
|
-
|
|
110
|
-
Args:
|
|
111
|
-
ctx: MCP context
|
|
112
|
-
|
|
113
|
-
Returns:
|
|
114
|
-
Prepared tool context
|
|
115
|
-
"""
|
|
116
|
-
tool_ctx = create_tool_context(ctx)
|
|
117
|
-
tool_ctx.set_tool_info(self.name)
|
|
118
|
-
return tool_ctx
|
|
119
|
-
|
|
120
|
-
@override
|
|
121
|
-
async def call(self, ctx: MCPContext, **params: Any) -> str:
|
|
122
|
-
"""Execute the tool with the given parameters.
|
|
123
|
-
|
|
124
|
-
Args:
|
|
125
|
-
ctx: MCP context
|
|
126
|
-
**params: Tool parameters
|
|
127
|
-
|
|
128
|
-
Returns:
|
|
129
|
-
Tool result
|
|
130
|
-
"""
|
|
131
|
-
tool_ctx = await self.prepare_tool_context(ctx)
|
|
132
|
-
|
|
133
|
-
# Extract parameters
|
|
134
|
-
script = params.get("script")
|
|
135
|
-
cwd = params.get("cwd")
|
|
136
|
-
interpreter = params.get("interpreter", "bash")
|
|
137
|
-
use_login_shell = params.get("use_login_shell", True)
|
|
138
|
-
|
|
139
|
-
# Validate required parameters
|
|
140
|
-
if not script:
|
|
141
|
-
await tool_ctx.error("Parameter 'script' is required but was None")
|
|
142
|
-
return "Error: Parameter 'script' is required but was None"
|
|
143
|
-
|
|
144
|
-
if script.strip() == "":
|
|
145
|
-
await tool_ctx.error("Parameter 'script' cannot be empty")
|
|
146
|
-
return "Error: Parameter 'script' cannot be empty"
|
|
147
|
-
|
|
148
|
-
# Validate interpreter
|
|
149
|
-
if not interpreter:
|
|
150
|
-
interpreter = "bash" # Use default if None
|
|
151
|
-
elif interpreter.strip() == "":
|
|
152
|
-
await tool_ctx.error("Parameter 'interpreter' cannot be empty")
|
|
153
|
-
return "Error: Parameter 'interpreter' cannot be empty"
|
|
154
|
-
|
|
155
|
-
# Validate required cwd parameter
|
|
156
|
-
if not cwd:
|
|
157
|
-
await tool_ctx.error("Parameter 'cwd' is required but was None")
|
|
158
|
-
return "Error: Parameter 'cwd' is required but was None"
|
|
159
|
-
|
|
160
|
-
if cwd.strip() == "":
|
|
161
|
-
await tool_ctx.error("Parameter 'cwd' cannot be empty")
|
|
162
|
-
return "Error: Parameter 'cwd' cannot be empty"
|
|
163
|
-
|
|
164
|
-
await tool_ctx.info(f"Executing script with interpreter: {interpreter}")
|
|
165
|
-
|
|
166
|
-
# Check if working directory is allowed
|
|
167
|
-
if not self.is_path_allowed(cwd):
|
|
168
|
-
await tool_ctx.error(f"Working directory not allowed: {cwd}")
|
|
169
|
-
return f"Error: Working directory not allowed: {cwd}"
|
|
170
|
-
|
|
171
|
-
# Check if working directory exists
|
|
172
|
-
if not os.path.isdir(cwd):
|
|
173
|
-
await tool_ctx.error(f"Working directory does not exist: {cwd}")
|
|
174
|
-
return f"Error: Working directory does not exist: {cwd}"
|
|
175
|
-
|
|
176
|
-
# Execute the script
|
|
177
|
-
result = await self.command_executor.execute_script(
|
|
178
|
-
script=script,
|
|
179
|
-
interpreter=interpreter,
|
|
180
|
-
cwd=cwd,
|
|
181
|
-
timeout=30.0,
|
|
182
|
-
use_login_shell=use_login_shell
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
# Report result
|
|
186
|
-
if result.is_success:
|
|
187
|
-
await tool_ctx.info("Script executed successfully")
|
|
188
|
-
else:
|
|
189
|
-
await tool_ctx.error(f"Script execution failed with exit code {result.return_code}")
|
|
190
|
-
|
|
191
|
-
# Format the result
|
|
192
|
-
if result.is_success:
|
|
193
|
-
# For successful scripts, just return stdout unless stderr has content
|
|
194
|
-
if result.stderr:
|
|
195
|
-
return f"Script executed successfully.\n\nSTDOUT:\n{result.stdout}\n\nSTDERR:\n{result.stderr}"
|
|
196
|
-
return result.stdout
|
|
197
|
-
else:
|
|
198
|
-
# For failed scripts, include all available information
|
|
199
|
-
return result.format_output()
|
|
200
|
-
|
|
201
|
-
@override
|
|
202
|
-
def register(self, mcp_server: FastMCP) -> None:
|
|
203
|
-
"""Register this run script tool with the MCP server.
|
|
204
|
-
|
|
205
|
-
Creates a wrapper function with explicitly defined parameters that match
|
|
206
|
-
the tool's parameter schema and registers it with the MCP server.
|
|
207
|
-
|
|
208
|
-
Args:
|
|
209
|
-
mcp_server: The FastMCP server instance
|
|
210
|
-
"""
|
|
211
|
-
tool_self = self # Create a reference to self for use in the closure
|
|
212
|
-
|
|
213
|
-
@mcp_server.tool(name=self.name, description=self.mcp_description)
|
|
214
|
-
async def run_script(ctx: MCPContext, script: str, cwd: str, interpreter: str = "bash", use_login_shell: bool = True) -> str:
|
|
215
|
-
return await tool_self.call(ctx, script=script, cwd=cwd, interpreter=interpreter, use_login_shell=use_login_shell)
|
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
"""Script tool implementation.
|
|
2
|
-
|
|
3
|
-
This module provides the ScriptTool for executing scripts in various languages.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import os
|
|
7
|
-
from typing import Any, final, override
|
|
8
|
-
|
|
9
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
10
|
-
from mcp.server.fastmcp import FastMCP
|
|
11
|
-
|
|
12
|
-
from hanzo_mcp.tools.common.context import create_tool_context
|
|
13
|
-
from hanzo_mcp.tools.shell.base import ShellBaseTool
|
|
14
|
-
from hanzo_mcp.tools.shell.command_executor import CommandExecutor
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@final
|
|
18
|
-
class ScriptTool(ShellBaseTool):
|
|
19
|
-
"""Tool for executing scripts in various languages."""
|
|
20
|
-
|
|
21
|
-
def __init__(self, permission_manager: Any, command_executor: CommandExecutor) -> None:
|
|
22
|
-
"""Initialize the script tool.
|
|
23
|
-
|
|
24
|
-
Args:
|
|
25
|
-
permission_manager: Permission manager for access control
|
|
26
|
-
command_executor: Command executor for running scripts
|
|
27
|
-
"""
|
|
28
|
-
super().__init__(permission_manager)
|
|
29
|
-
self.command_executor: CommandExecutor = command_executor
|
|
30
|
-
|
|
31
|
-
@property
|
|
32
|
-
@override
|
|
33
|
-
def name(self) -> str:
|
|
34
|
-
"""Get the tool name.
|
|
35
|
-
|
|
36
|
-
Returns:
|
|
37
|
-
Tool name
|
|
38
|
-
"""
|
|
39
|
-
return "script_tool"
|
|
40
|
-
|
|
41
|
-
@property
|
|
42
|
-
@override
|
|
43
|
-
def description(self) -> str:
|
|
44
|
-
"""Get the tool description.
|
|
45
|
-
|
|
46
|
-
Returns:
|
|
47
|
-
Tool description
|
|
48
|
-
"""
|
|
49
|
-
return """Execute a script in the specified language.
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
language: The programming language (python, javascript, etc.)
|
|
53
|
-
script: The script code to execute
|
|
54
|
-
cwd: Working directory for script execution. MUST be a subdirectory of one of the allowed paths, not a parent directory. Specify the most specific path possible.
|
|
55
|
-
|
|
56
|
-
args: Optional command-line arguments
|
|
57
|
-
use_login_shell: Whether to use login shell (loads ~/.zshrc, ~/.bashrc, etc.)
|
|
58
|
-
|
|
59
|
-
Returns:
|
|
60
|
-
Script execution results
|
|
61
|
-
"""
|
|
62
|
-
|
|
63
|
-
@property
|
|
64
|
-
@override
|
|
65
|
-
def parameters(self) -> dict[str, Any]:
|
|
66
|
-
"""Get the parameter specifications for the tool.
|
|
67
|
-
|
|
68
|
-
Returns:
|
|
69
|
-
Parameter specifications
|
|
70
|
-
"""
|
|
71
|
-
return {
|
|
72
|
-
"properties": {
|
|
73
|
-
"language": {
|
|
74
|
-
"title": "Language",
|
|
75
|
-
"type": "string"
|
|
76
|
-
},
|
|
77
|
-
"script": {
|
|
78
|
-
"title": "Script",
|
|
79
|
-
"type": "string"
|
|
80
|
-
},
|
|
81
|
-
"cwd": {
|
|
82
|
-
"title": "Cwd",
|
|
83
|
-
"type": "string"
|
|
84
|
-
},
|
|
85
|
-
"args": {
|
|
86
|
-
"anyOf": [
|
|
87
|
-
{"items": {"type": "string"}, "type": "array"},
|
|
88
|
-
{"type": "null"}
|
|
89
|
-
],
|
|
90
|
-
"default": None,
|
|
91
|
-
"title": "Args"
|
|
92
|
-
},
|
|
93
|
-
"use_login_shell": {
|
|
94
|
-
"default": True,
|
|
95
|
-
"title": "Use Login Shell",
|
|
96
|
-
"type": "boolean"
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
"required": ["language", "script", "cwd"],
|
|
100
|
-
"title": "script_toolArguments",
|
|
101
|
-
"type": "object"
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
@property
|
|
105
|
-
@override
|
|
106
|
-
def required(self) -> list[str]:
|
|
107
|
-
"""Get the list of required parameter names.
|
|
108
|
-
|
|
109
|
-
Returns:
|
|
110
|
-
List of required parameter names
|
|
111
|
-
"""
|
|
112
|
-
return ["language", "script", "cwd"]
|
|
113
|
-
|
|
114
|
-
@override
|
|
115
|
-
async def prepare_tool_context(self, ctx: MCPContext) -> Any:
|
|
116
|
-
"""Create and prepare the tool context.
|
|
117
|
-
|
|
118
|
-
Args:
|
|
119
|
-
ctx: MCP context
|
|
120
|
-
|
|
121
|
-
Returns:
|
|
122
|
-
Prepared tool context
|
|
123
|
-
"""
|
|
124
|
-
tool_ctx = create_tool_context(ctx)
|
|
125
|
-
tool_ctx.set_tool_info(self.name)
|
|
126
|
-
return tool_ctx
|
|
127
|
-
|
|
128
|
-
@override
|
|
129
|
-
async def call(self, ctx: MCPContext, **params: Any) -> str:
|
|
130
|
-
"""Execute the tool with the given parameters.
|
|
131
|
-
|
|
132
|
-
Args:
|
|
133
|
-
ctx: MCP context
|
|
134
|
-
**params: Tool parameters
|
|
135
|
-
|
|
136
|
-
Returns:
|
|
137
|
-
Tool result
|
|
138
|
-
"""
|
|
139
|
-
tool_ctx = await self.prepare_tool_context(ctx)
|
|
140
|
-
|
|
141
|
-
# Extract parameters
|
|
142
|
-
language = params.get("language")
|
|
143
|
-
script = params.get("script")
|
|
144
|
-
cwd = params.get("cwd")
|
|
145
|
-
args = params.get("args")
|
|
146
|
-
use_login_shell = params.get("use_login_shell", True)
|
|
147
|
-
|
|
148
|
-
# Validate required parameters
|
|
149
|
-
if not language:
|
|
150
|
-
await tool_ctx.error("Parameter 'language' is required but was None")
|
|
151
|
-
return "Error: Parameter 'language' is required but was None"
|
|
152
|
-
|
|
153
|
-
if language.strip() == "":
|
|
154
|
-
await tool_ctx.error("Parameter 'language' cannot be empty")
|
|
155
|
-
return "Error: Parameter 'language' cannot be empty"
|
|
156
|
-
|
|
157
|
-
if not script:
|
|
158
|
-
await tool_ctx.error("Parameter 'script' is required but was None")
|
|
159
|
-
return "Error: Parameter 'script' is required but was None"
|
|
160
|
-
|
|
161
|
-
if script.strip() == "":
|
|
162
|
-
await tool_ctx.error("Parameter 'script' cannot be empty")
|
|
163
|
-
return "Error: Parameter 'script' cannot be empty"
|
|
164
|
-
|
|
165
|
-
# args can be None as it's optional
|
|
166
|
-
# Check for empty list but still allow None
|
|
167
|
-
if args is not None and len(args) == 0:
|
|
168
|
-
await tool_ctx.warning("Parameter 'args' is an empty list")
|
|
169
|
-
# We don't return error for this as empty args is acceptable
|
|
170
|
-
|
|
171
|
-
# Validate required cwd parameter
|
|
172
|
-
if not cwd:
|
|
173
|
-
await tool_ctx.error("Parameter 'cwd' is required but was None")
|
|
174
|
-
return "Error: Parameter 'cwd' is required but was None"
|
|
175
|
-
|
|
176
|
-
if cwd.strip() == "":
|
|
177
|
-
await tool_ctx.error("Parameter 'cwd' cannot be empty")
|
|
178
|
-
return "Error: Parameter 'cwd' cannot be empty"
|
|
179
|
-
|
|
180
|
-
await tool_ctx.info(f"Executing {language} script")
|
|
181
|
-
|
|
182
|
-
# Check if the language is supported
|
|
183
|
-
if language not in self.command_executor.get_available_languages():
|
|
184
|
-
await tool_ctx.error(f"Unsupported language: {language}")
|
|
185
|
-
return f"Error: Unsupported language: {language}. Supported languages: {', '.join(self.command_executor.get_available_languages())}"
|
|
186
|
-
|
|
187
|
-
# Check if working directory is allowed
|
|
188
|
-
if not self.is_path_allowed(cwd):
|
|
189
|
-
await tool_ctx.error(f"Working directory not allowed: {cwd}")
|
|
190
|
-
return f"Error: Working directory not allowed: {cwd}"
|
|
191
|
-
|
|
192
|
-
# Check if working directory exists
|
|
193
|
-
if not os.path.isdir(cwd):
|
|
194
|
-
await tool_ctx.error(f"Working directory does not exist: {cwd}")
|
|
195
|
-
return f"Error: Working directory does not exist: {cwd}"
|
|
196
|
-
|
|
197
|
-
# Execute the script
|
|
198
|
-
result = await self.command_executor.execute_script_from_file(
|
|
199
|
-
script=script,
|
|
200
|
-
language=language,
|
|
201
|
-
cwd=cwd,
|
|
202
|
-
timeout=30.0,
|
|
203
|
-
args=args,
|
|
204
|
-
use_login_shell=use_login_shell
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
# Report result
|
|
208
|
-
if result.is_success:
|
|
209
|
-
await tool_ctx.info(f"{language} script executed successfully")
|
|
210
|
-
else:
|
|
211
|
-
await tool_ctx.error(
|
|
212
|
-
f"{language} script execution failed with exit code {result.return_code}"
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
# Format the result
|
|
216
|
-
if result.is_success:
|
|
217
|
-
# Format the successful result
|
|
218
|
-
output = f"{language} script executed successfully.\n\n"
|
|
219
|
-
if result.stdout:
|
|
220
|
-
output += f"STDOUT:\n{result.stdout}\n\n"
|
|
221
|
-
if result.stderr:
|
|
222
|
-
output += f"STDERR:\n{result.stderr}"
|
|
223
|
-
return output.strip()
|
|
224
|
-
else:
|
|
225
|
-
# For failed scripts, include all available information
|
|
226
|
-
return result.format_output()
|
|
227
|
-
|
|
228
|
-
@override
|
|
229
|
-
def register(self, mcp_server: FastMCP) -> None:
|
|
230
|
-
"""Register this script tool with the MCP server.
|
|
231
|
-
|
|
232
|
-
Creates a wrapper function with explicitly defined parameters that match
|
|
233
|
-
the tool's parameter schema and registers it with the MCP server.
|
|
234
|
-
|
|
235
|
-
Args:
|
|
236
|
-
mcp_server: The FastMCP server instance
|
|
237
|
-
"""
|
|
238
|
-
tool_self = self # Create a reference to self for use in the closure
|
|
239
|
-
|
|
240
|
-
@mcp_server.tool(name=self.name, description=self.mcp_description)
|
|
241
|
-
async def script_tool(ctx: MCPContext, language: str, script: str, cwd: str, args: list[str] | None = None,
|
|
242
|
-
use_login_shell: bool = True) -> str:
|
|
243
|
-
return await tool_self.call(ctx, language=language, script=script, cwd=cwd,
|
|
244
|
-
args=args, use_login_shell=use_login_shell)
|