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.

Files changed (87) hide show
  1. hanzo_mcp/__init__.py +1 -1
  2. hanzo_mcp/cli.py +118 -170
  3. hanzo_mcp/cli_enhanced.py +438 -0
  4. hanzo_mcp/config/__init__.py +19 -0
  5. hanzo_mcp/config/settings.py +388 -0
  6. hanzo_mcp/config/tool_config.py +197 -0
  7. hanzo_mcp/prompts/__init__.py +117 -0
  8. hanzo_mcp/prompts/compact_conversation.py +77 -0
  9. hanzo_mcp/prompts/create_release.py +38 -0
  10. hanzo_mcp/prompts/project_system.py +120 -0
  11. hanzo_mcp/prompts/project_todo_reminder.py +111 -0
  12. hanzo_mcp/prompts/utils.py +286 -0
  13. hanzo_mcp/server.py +117 -99
  14. hanzo_mcp/tools/__init__.py +105 -32
  15. hanzo_mcp/tools/agent/__init__.py +8 -11
  16. hanzo_mcp/tools/agent/agent_tool.py +290 -224
  17. hanzo_mcp/tools/agent/prompt.py +16 -13
  18. hanzo_mcp/tools/agent/tool_adapter.py +9 -9
  19. hanzo_mcp/tools/common/__init__.py +17 -16
  20. hanzo_mcp/tools/common/base.py +79 -110
  21. hanzo_mcp/tools/common/batch_tool.py +330 -0
  22. hanzo_mcp/tools/common/context.py +26 -292
  23. hanzo_mcp/tools/common/permissions.py +12 -12
  24. hanzo_mcp/tools/common/thinking_tool.py +153 -0
  25. hanzo_mcp/tools/common/validation.py +1 -63
  26. hanzo_mcp/tools/filesystem/__init__.py +88 -57
  27. hanzo_mcp/tools/filesystem/base.py +32 -24
  28. hanzo_mcp/tools/filesystem/content_replace.py +114 -107
  29. hanzo_mcp/tools/filesystem/directory_tree.py +129 -105
  30. hanzo_mcp/tools/filesystem/edit.py +279 -0
  31. hanzo_mcp/tools/filesystem/grep.py +458 -0
  32. hanzo_mcp/tools/filesystem/grep_ast_tool.py +250 -0
  33. hanzo_mcp/tools/filesystem/multi_edit.py +362 -0
  34. hanzo_mcp/tools/filesystem/read.py +255 -0
  35. hanzo_mcp/tools/filesystem/write.py +156 -0
  36. hanzo_mcp/tools/jupyter/__init__.py +41 -29
  37. hanzo_mcp/tools/jupyter/base.py +66 -57
  38. hanzo_mcp/tools/jupyter/{edit_notebook.py → notebook_edit.py} +162 -139
  39. hanzo_mcp/tools/jupyter/notebook_read.py +152 -0
  40. hanzo_mcp/tools/shell/__init__.py +29 -20
  41. hanzo_mcp/tools/shell/base.py +87 -45
  42. hanzo_mcp/tools/shell/bash_session.py +731 -0
  43. hanzo_mcp/tools/shell/bash_session_executor.py +295 -0
  44. hanzo_mcp/tools/shell/command_executor.py +435 -384
  45. hanzo_mcp/tools/shell/run_command.py +284 -131
  46. hanzo_mcp/tools/shell/run_command_windows.py +328 -0
  47. hanzo_mcp/tools/shell/session_manager.py +196 -0
  48. hanzo_mcp/tools/shell/session_storage.py +325 -0
  49. hanzo_mcp/tools/todo/__init__.py +66 -0
  50. hanzo_mcp/tools/todo/base.py +319 -0
  51. hanzo_mcp/tools/todo/todo_read.py +148 -0
  52. hanzo_mcp/tools/todo/todo_write.py +378 -0
  53. hanzo_mcp/tools/vector/__init__.py +95 -0
  54. hanzo_mcp/tools/vector/infinity_store.py +365 -0
  55. hanzo_mcp/tools/vector/project_manager.py +361 -0
  56. hanzo_mcp/tools/vector/vector_index.py +115 -0
  57. hanzo_mcp/tools/vector/vector_search.py +215 -0
  58. {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/METADATA +33 -1
  59. hanzo_mcp-0.5.0.dist-info/RECORD +63 -0
  60. {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/WHEEL +1 -1
  61. hanzo_mcp/tools/agent/base_provider.py +0 -73
  62. hanzo_mcp/tools/agent/litellm_provider.py +0 -45
  63. hanzo_mcp/tools/agent/lmstudio_agent.py +0 -385
  64. hanzo_mcp/tools/agent/lmstudio_provider.py +0 -219
  65. hanzo_mcp/tools/agent/provider_registry.py +0 -120
  66. hanzo_mcp/tools/common/error_handling.py +0 -86
  67. hanzo_mcp/tools/common/logging_config.py +0 -115
  68. hanzo_mcp/tools/common/session.py +0 -91
  69. hanzo_mcp/tools/common/think_tool.py +0 -123
  70. hanzo_mcp/tools/common/version_tool.py +0 -120
  71. hanzo_mcp/tools/filesystem/edit_file.py +0 -287
  72. hanzo_mcp/tools/filesystem/get_file_info.py +0 -170
  73. hanzo_mcp/tools/filesystem/read_files.py +0 -199
  74. hanzo_mcp/tools/filesystem/search_content.py +0 -275
  75. hanzo_mcp/tools/filesystem/write_file.py +0 -162
  76. hanzo_mcp/tools/jupyter/notebook_operations.py +0 -514
  77. hanzo_mcp/tools/jupyter/read_notebook.py +0 -165
  78. hanzo_mcp/tools/project/__init__.py +0 -64
  79. hanzo_mcp/tools/project/analysis.py +0 -886
  80. hanzo_mcp/tools/project/base.py +0 -66
  81. hanzo_mcp/tools/project/project_analyze.py +0 -173
  82. hanzo_mcp/tools/shell/run_script.py +0 -215
  83. hanzo_mcp/tools/shell/script_tool.py +0 -244
  84. hanzo_mcp-0.3.8.dist-info/RECORD +0 -53
  85. {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/entry_points.txt +0 -0
  86. {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/licenses/LICENSE +0 -0
  87. {hanzo_mcp-0.3.8.dist-info → hanzo_mcp-0.5.0.dist-info}/top_level.txt +0 -0
@@ -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)