hanzo-mcp 0.1.21__py3-none-any.whl → 0.1.30__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 +81 -10
- hanzo_mcp/server.py +42 -11
- hanzo_mcp/tools/__init__.py +51 -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 +18 -1
- hanzo_mcp/tools/common/base.py +216 -0
- hanzo_mcp/tools/common/context.py +9 -5
- hanzo_mcp/tools/common/permissions.py +7 -3
- hanzo_mcp/tools/common/session.py +91 -0
- hanzo_mcp/tools/common/thinking_tool.py +123 -0
- hanzo_mcp/tools/common/validation.py +1 -1
- 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 +73 -113
- hanzo_mcp/tools/jupyter/read_notebook.py +165 -0
- hanzo_mcp/tools/project/__init__.py +64 -1
- hanzo_mcp/tools/project/analysis.py +8 -5
- 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 +198 -317
- 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.21.dist-info → hanzo_mcp-0.1.30.dist-info}/METADATA +72 -77
- hanzo_mcp-0.1.30.dist-info/RECORD +45 -0
- {hanzo_mcp-0.1.21.dist-info → hanzo_mcp-0.1.30.dist-info}/WHEEL +1 -1
- {hanzo_mcp-0.1.21.dist-info → hanzo_mcp-0.1.30.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.21.dist-info/RECORD +0 -23
- {hanzo_mcp-0.1.21.dist-info → hanzo_mcp-0.1.30.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.1.21.dist-info → hanzo_mcp-0.1.30.dist-info}/top_level.txt +0 -0
|
@@ -1 +1,18 @@
|
|
|
1
|
-
"""Common utilities for Hanzo
|
|
1
|
+
"""Common utilities for Hanzo MCP tools."""
|
|
2
|
+
|
|
3
|
+
from mcp.server.fastmcp import FastMCP
|
|
4
|
+
|
|
5
|
+
from hanzo_mcp.tools.common.base import ToolRegistry
|
|
6
|
+
from hanzo_mcp.tools.common.thinking_tool import ThinkingTool
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register_thinking_tool(
|
|
10
|
+
mcp_server: FastMCP,
|
|
11
|
+
) -> None:
|
|
12
|
+
"""Register all thinking tools with the MCP server.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
mcp_server: The FastMCP server instance
|
|
16
|
+
"""
|
|
17
|
+
thinking_tool = ThinkingTool()
|
|
18
|
+
ToolRegistry.register_tool(mcp_server, thinking_tool)
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Base classes for Hanzo MCP tools.
|
|
2
|
+
|
|
3
|
+
This module provides abstract base classes that define interfaces and common functionality
|
|
4
|
+
for all tools used in Hanzo MCP. These abstractions help ensure consistent tool
|
|
5
|
+
behavior and provide a foundation for tool registration and management.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from typing import Any, final
|
|
10
|
+
|
|
11
|
+
from mcp.server.fastmcp import Context as MCPContext
|
|
12
|
+
from mcp.server.fastmcp import FastMCP
|
|
13
|
+
|
|
14
|
+
from hanzo_mcp.tools.common.context import DocumentContext
|
|
15
|
+
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
16
|
+
from hanzo_mcp.tools.common.validation import ValidationResult, validate_path_parameter
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BaseTool(ABC):
|
|
20
|
+
"""Abstract base class for all Hanzo MCP tools.
|
|
21
|
+
|
|
22
|
+
This class defines the core interface that all tools must implement, ensuring
|
|
23
|
+
consistency in how tools are registered, documented, and called.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def name(self) -> str:
|
|
29
|
+
"""Get the tool name.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
The tool name as it will appear in the MCP server
|
|
33
|
+
"""
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def description(self) -> str:
|
|
39
|
+
"""Get the tool description.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Detailed description of the tool's purpose and usage
|
|
43
|
+
"""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def mcp_description(self) -> str:
|
|
48
|
+
"""Get the complete tool description for MCP.
|
|
49
|
+
|
|
50
|
+
This method combines the tool description with parameter descriptions.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Complete tool description including parameter details
|
|
54
|
+
"""
|
|
55
|
+
# Start with the base description
|
|
56
|
+
desc = self.description.strip()
|
|
57
|
+
|
|
58
|
+
# Add parameter descriptions section if there are parameters
|
|
59
|
+
if self.parameters and "properties" in self.parameters:
|
|
60
|
+
# Add Args section header
|
|
61
|
+
desc += "\n\nArgs:"
|
|
62
|
+
|
|
63
|
+
# Get the properties dictionary
|
|
64
|
+
properties = self.parameters["properties"]
|
|
65
|
+
|
|
66
|
+
# Add each parameter description
|
|
67
|
+
for param_name, param_info in properties.items():
|
|
68
|
+
# Get the title if available, otherwise use the parameter name and capitalize it
|
|
69
|
+
if "title" in param_info:
|
|
70
|
+
title = param_info["title"]
|
|
71
|
+
else:
|
|
72
|
+
# Convert snake_case to Title Case
|
|
73
|
+
title = " ".join(word.capitalize() for word in param_name.split("_"))
|
|
74
|
+
|
|
75
|
+
# Check if the parameter is required
|
|
76
|
+
required = param_name in self.required
|
|
77
|
+
required_text = "" if required else " (optional)"
|
|
78
|
+
|
|
79
|
+
# Add the parameter description line
|
|
80
|
+
desc += f"\n {param_name}: {title}{required_text}"
|
|
81
|
+
|
|
82
|
+
# Add Returns section
|
|
83
|
+
desc += "\n\nReturns:\n "
|
|
84
|
+
|
|
85
|
+
# Add a generic return description based on the tool's purpose
|
|
86
|
+
# This could be enhanced with more specific return descriptions
|
|
87
|
+
if "read" in self.name or "get" in self.name or "search" in self.name:
|
|
88
|
+
desc += f"{self.name.replace('_', ' ').capitalize()} results"
|
|
89
|
+
elif "write" in self.name or "edit" in self.name or "create" in self.name:
|
|
90
|
+
desc += "Result of the operation"
|
|
91
|
+
else:
|
|
92
|
+
desc += "Tool execution results"
|
|
93
|
+
|
|
94
|
+
return desc
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
@abstractmethod
|
|
98
|
+
def parameters(self) -> dict[str, Any]:
|
|
99
|
+
"""Get the parameter specifications for the tool.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Dictionary containing parameter specifications in JSON Schema format
|
|
103
|
+
"""
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
@abstractmethod
|
|
108
|
+
def required(self) -> list[str]:
|
|
109
|
+
"""Get the list of required parameter names.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
List of parameter names that are required for the tool
|
|
113
|
+
"""
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
@abstractmethod
|
|
117
|
+
async def call(self, ctx: MCPContext, **params: Any) -> str:
|
|
118
|
+
"""Execute the tool with the given parameters.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
ctx: MCP context for the tool call
|
|
122
|
+
**params: Tool parameters provided by the caller
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Tool execution result as a string
|
|
126
|
+
"""
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
@abstractmethod
|
|
130
|
+
def register(self, mcp_server: FastMCP) -> None:
|
|
131
|
+
"""Register this tool with the MCP server.
|
|
132
|
+
|
|
133
|
+
This method must be implemented by each tool class to create a wrapper function
|
|
134
|
+
with explicitly defined parameters that calls this tool's call method.
|
|
135
|
+
The wrapper function is then registered with the MCP server.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
mcp_server: The FastMCP server instance
|
|
139
|
+
"""
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class FileSystemTool(BaseTool,ABC):
|
|
144
|
+
"""Base class for filesystem-related tools.
|
|
145
|
+
|
|
146
|
+
Provides common functionality for working with files and directories,
|
|
147
|
+
including permission checking and path validation.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
def __init__(
|
|
151
|
+
self,
|
|
152
|
+
document_context: DocumentContext,
|
|
153
|
+
permission_manager: PermissionManager
|
|
154
|
+
) -> None:
|
|
155
|
+
"""Initialize filesystem tool.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
document_context: Document context for tracking file contents
|
|
159
|
+
permission_manager: Permission manager for access control
|
|
160
|
+
"""
|
|
161
|
+
self.document_context:DocumentContext = document_context
|
|
162
|
+
self.permission_manager:PermissionManager = permission_manager
|
|
163
|
+
|
|
164
|
+
def validate_path(self, path: str, param_name: str = "path") -> ValidationResult:
|
|
165
|
+
"""Validate a path parameter.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
path: Path to validate
|
|
169
|
+
param_name: Name of the parameter (for error messages)
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Validation result containing validation status and error message if any
|
|
173
|
+
"""
|
|
174
|
+
return validate_path_parameter(path, param_name)
|
|
175
|
+
|
|
176
|
+
def is_path_allowed(self, path: str) -> bool:
|
|
177
|
+
"""Check if a path is allowed according to permission settings.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
path: Path to check
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
True if the path is allowed, False otherwise
|
|
184
|
+
"""
|
|
185
|
+
return self.permission_manager.is_path_allowed(path)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@final
|
|
189
|
+
class ToolRegistry:
|
|
190
|
+
"""Registry for Hanzo MCP tools.
|
|
191
|
+
|
|
192
|
+
Provides functionality for registering tool implementations with an MCP server,
|
|
193
|
+
handling the conversion between tool classes and MCP tool functions.
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def register_tool(mcp_server: FastMCP, tool: BaseTool) -> None:
|
|
198
|
+
"""Register a tool with the MCP server.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
mcp_server: The FastMCP server instance
|
|
202
|
+
tool: The tool to register
|
|
203
|
+
"""
|
|
204
|
+
# Use the tool's register method which handles all the details
|
|
205
|
+
tool.register(mcp_server)
|
|
206
|
+
|
|
207
|
+
@staticmethod
|
|
208
|
+
def register_tools(mcp_server: FastMCP, tools: list[BaseTool]) -> None:
|
|
209
|
+
"""Register multiple tools with the MCP server.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
mcp_server: The FastMCP server instance
|
|
213
|
+
tools: List of tools to register
|
|
214
|
+
"""
|
|
215
|
+
for tool in tools:
|
|
216
|
+
ToolRegistry.register_tool(mcp_server, tool)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"""Enhanced Context for Hanzo
|
|
1
|
+
"""Enhanced Context for Hanzo MCP tools.
|
|
2
2
|
|
|
3
3
|
This module provides an enhanced Context class that wraps the MCP Context
|
|
4
|
-
and adds additional functionality specific to
|
|
4
|
+
and adds additional functionality specific to Hanzo tools.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import json
|
|
@@ -17,7 +17,7 @@ from mcp.server.lowlevel.helper_types import ReadResourceContents
|
|
|
17
17
|
|
|
18
18
|
@final
|
|
19
19
|
class ToolContext:
|
|
20
|
-
"""Enhanced context for Hanzo
|
|
20
|
+
"""Enhanced context for Hanzo MCP tools.
|
|
21
21
|
|
|
22
22
|
This class wraps the MCP Context and adds additional functionality
|
|
23
23
|
for tracking tool execution, progress reporting, and resource access.
|
|
@@ -179,7 +179,9 @@ class DocumentContext:
|
|
|
179
179
|
Args:
|
|
180
180
|
path: The path to allow
|
|
181
181
|
"""
|
|
182
|
-
|
|
182
|
+
# Expand user path (e.g., ~/ or $HOME)
|
|
183
|
+
expanded_path = os.path.expanduser(path)
|
|
184
|
+
resolved_path: Path = Path(expanded_path).resolve()
|
|
183
185
|
self.allowed_paths.add(resolved_path)
|
|
184
186
|
|
|
185
187
|
def is_path_allowed(self, path: str) -> bool:
|
|
@@ -191,7 +193,9 @@ class DocumentContext:
|
|
|
191
193
|
Returns:
|
|
192
194
|
True if the path is allowed, False otherwise
|
|
193
195
|
"""
|
|
194
|
-
|
|
196
|
+
# Expand user path (e.g., ~/ or $HOME)
|
|
197
|
+
expanded_path = os.path.expanduser(path)
|
|
198
|
+
resolved_path: Path = Path(expanded_path).resolve()
|
|
195
199
|
|
|
196
200
|
# Check if the path is within any allowed path
|
|
197
201
|
for allowed_path in self.allowed_paths:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Permission system for the Hanzo
|
|
1
|
+
"""Permission system for the Hanzo MCP server."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
@@ -69,7 +69,9 @@ class PermissionManager:
|
|
|
69
69
|
Args:
|
|
70
70
|
path: The path to allow
|
|
71
71
|
"""
|
|
72
|
-
|
|
72
|
+
# Expand user path (e.g., ~/ or $HOME)
|
|
73
|
+
expanded_path = os.path.expanduser(path)
|
|
74
|
+
resolved_path: Path = Path(expanded_path).resolve()
|
|
73
75
|
self.allowed_paths.add(resolved_path)
|
|
74
76
|
|
|
75
77
|
def remove_allowed_path(self, path: str) -> None:
|
|
@@ -108,7 +110,9 @@ class PermissionManager:
|
|
|
108
110
|
Returns:
|
|
109
111
|
True if the path is allowed, False otherwise
|
|
110
112
|
"""
|
|
111
|
-
|
|
113
|
+
# Expand user path (e.g., ~/ or $HOME)
|
|
114
|
+
expanded_path = os.path.expanduser(path)
|
|
115
|
+
resolved_path: Path = Path(expanded_path).resolve()
|
|
112
116
|
|
|
113
117
|
# Check exclusions first
|
|
114
118
|
if self._is_path_excluded(resolved_path):
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Session management for maintaining state across tool executions."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, Optional, final
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@final
|
|
9
|
+
class SessionManager:
|
|
10
|
+
"""Manages session state across tool executions."""
|
|
11
|
+
|
|
12
|
+
_instances: Dict[str, "SessionManager"] = {}
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def get_instance(cls, session_id: str) -> "SessionManager":
|
|
16
|
+
"""Get or create a session manager instance for the given session ID.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
session_id: The session ID
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
The session manager instance
|
|
23
|
+
"""
|
|
24
|
+
if session_id not in cls._instances:
|
|
25
|
+
cls._instances[session_id] = cls(session_id)
|
|
26
|
+
return cls._instances[session_id]
|
|
27
|
+
|
|
28
|
+
def __init__(self, session_id: str):
|
|
29
|
+
"""Initialize the session manager.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
session_id: The session ID
|
|
33
|
+
"""
|
|
34
|
+
self.session_id = session_id
|
|
35
|
+
self._current_working_dir: Optional[Path] = None
|
|
36
|
+
self._initial_working_dir: Optional[Path] = None
|
|
37
|
+
self._environment_vars: Dict[str, str] = {}
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def current_working_dir(self) -> Path:
|
|
41
|
+
"""Get the current working directory.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
The current working directory
|
|
45
|
+
"""
|
|
46
|
+
if self._current_working_dir is None:
|
|
47
|
+
# Default to project directory if set, otherwise use current directory
|
|
48
|
+
self._current_working_dir = Path(os.getcwd())
|
|
49
|
+
self._initial_working_dir = self._current_working_dir
|
|
50
|
+
return self._current_working_dir
|
|
51
|
+
|
|
52
|
+
def set_working_dir(self, path: Path) -> None:
|
|
53
|
+
"""Set the current working directory.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
path: The path to set as the current working directory
|
|
57
|
+
"""
|
|
58
|
+
self._current_working_dir = path
|
|
59
|
+
|
|
60
|
+
def reset_working_dir(self) -> None:
|
|
61
|
+
"""Reset the working directory to the initial directory."""
|
|
62
|
+
if self._initial_working_dir is not None:
|
|
63
|
+
self._current_working_dir = self._initial_working_dir
|
|
64
|
+
|
|
65
|
+
def set_env_var(self, key: str, value: str) -> None:
|
|
66
|
+
"""Set an environment variable.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
key: The environment variable name
|
|
70
|
+
value: The environment variable value
|
|
71
|
+
"""
|
|
72
|
+
self._environment_vars[key] = value
|
|
73
|
+
|
|
74
|
+
def get_env_var(self, key: str) -> Optional[str]:
|
|
75
|
+
"""Get an environment variable.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
key: The environment variable name
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
The environment variable value, or None if not set
|
|
82
|
+
"""
|
|
83
|
+
return self._environment_vars.get(key)
|
|
84
|
+
|
|
85
|
+
def get_env_vars(self) -> Dict[str, str]:
|
|
86
|
+
"""Get all environment variables.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
A dictionary of environment variables
|
|
90
|
+
"""
|
|
91
|
+
return self._environment_vars.copy()
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Thinking tool implementation.
|
|
2
|
+
|
|
3
|
+
This module provides the ThinkingTool for Claude to engage in structured thinking.
|
|
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.base import BaseTool
|
|
12
|
+
from hanzo_mcp.tools.common.context import create_tool_context
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@final
|
|
16
|
+
class ThinkingTool(BaseTool):
|
|
17
|
+
"""Tool for Claude to engage in structured thinking."""
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
@override
|
|
21
|
+
def name(self) -> str:
|
|
22
|
+
"""Get the tool name.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Tool name
|
|
26
|
+
"""
|
|
27
|
+
return "think"
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
@override
|
|
31
|
+
def description(self) -> str:
|
|
32
|
+
"""Get the tool description.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Tool description
|
|
36
|
+
"""
|
|
37
|
+
return """Use the tool to think about something.
|
|
38
|
+
|
|
39
|
+
It will not obtain new information or make any changes to the repository, but just log the thought. Use it when complex reasoning or brainstorming is needed. For example, if you explore the repo and discover the source of a bug, call this tool to brainstorm several unique ways of fixing the bug, and assess which change(s) are likely to be simplest and most effective. Alternatively, if you receive some test results, call this tool to brainstorm ways to fix the failing tests."""
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
@override
|
|
43
|
+
def parameters(self) -> dict[str, Any]:
|
|
44
|
+
"""Get the parameter specifications for the tool.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Parameter specifications
|
|
48
|
+
"""
|
|
49
|
+
return {
|
|
50
|
+
"properties": {
|
|
51
|
+
"thought": {
|
|
52
|
+
"title": "Thought",
|
|
53
|
+
"type": "string"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"required": ["thought"],
|
|
57
|
+
"title": "thinkArguments",
|
|
58
|
+
"type": "object"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
@override
|
|
63
|
+
def required(self) -> list[str]:
|
|
64
|
+
"""Get the list of required parameter names.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
List of required parameter names
|
|
68
|
+
"""
|
|
69
|
+
return ["thought"]
|
|
70
|
+
|
|
71
|
+
def __init__(self) -> None:
|
|
72
|
+
"""Initialize the thinking tool."""
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
@override
|
|
76
|
+
async def call(self, ctx: MCPContext, **params: Any) -> str:
|
|
77
|
+
"""Execute the tool with the given parameters.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
ctx: MCP context
|
|
81
|
+
**params: Tool parameters
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Tool result
|
|
85
|
+
"""
|
|
86
|
+
tool_ctx = create_tool_context(ctx)
|
|
87
|
+
tool_ctx.set_tool_info(self.name)
|
|
88
|
+
|
|
89
|
+
# Extract parameters
|
|
90
|
+
thought = params.get("thought")
|
|
91
|
+
|
|
92
|
+
# Validate required thought parameter
|
|
93
|
+
if not thought:
|
|
94
|
+
await tool_ctx.error(
|
|
95
|
+
"Parameter 'thought' is required but was None or empty"
|
|
96
|
+
)
|
|
97
|
+
return "Error: Parameter 'thought' is required but was None or empty"
|
|
98
|
+
|
|
99
|
+
if thought.strip() == "":
|
|
100
|
+
await tool_ctx.error("Parameter 'thought' cannot be empty")
|
|
101
|
+
return "Error: Parameter 'thought' cannot be empty"
|
|
102
|
+
|
|
103
|
+
# Log the thought but don't take action
|
|
104
|
+
await tool_ctx.info("Thinking process recorded")
|
|
105
|
+
|
|
106
|
+
# Return confirmation
|
|
107
|
+
return "I've recorded your thinking process. You can continue with your next action based on this analysis."
|
|
108
|
+
|
|
109
|
+
@override
|
|
110
|
+
def register(self, mcp_server: FastMCP) -> None:
|
|
111
|
+
"""Register this thinking tool with the MCP server.
|
|
112
|
+
|
|
113
|
+
Creates a wrapper function with explicitly defined parameters that match
|
|
114
|
+
the tool's parameter schema and registers it with the MCP server.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
mcp_server: The FastMCP server instance
|
|
118
|
+
"""
|
|
119
|
+
tool_self = self # Create a reference to self for use in the closure
|
|
120
|
+
|
|
121
|
+
@mcp_server.tool(name=self.name, description=self.mcp_description)
|
|
122
|
+
async def think(thought: str, ctx: MCPContext) -> str:
|
|
123
|
+
return await tool_self.call(ctx, thought=thought)
|
|
@@ -1,9 +1,89 @@
|
|
|
1
|
-
"""Filesystem tools for Hanzo
|
|
1
|
+
"""Filesystem tools package for Hanzo MCP.
|
|
2
2
|
|
|
3
|
-
This
|
|
4
|
-
|
|
3
|
+
This package provides tools for interacting with the filesystem, including reading, writing,
|
|
4
|
+
and editing files, directory navigation, and content searching.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from
|
|
7
|
+
from mcp.server.fastmcp import FastMCP
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
from hanzo_mcp.tools.common.base import BaseTool, ToolRegistry
|
|
10
|
+
from hanzo_mcp.tools.common.context import DocumentContext
|
|
11
|
+
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
12
|
+
from hanzo_mcp.tools.filesystem.content_replace import ContentReplaceTool
|
|
13
|
+
from hanzo_mcp.tools.filesystem.directory_tree import DirectoryTreeTool
|
|
14
|
+
from hanzo_mcp.tools.filesystem.edit_file import EditFileTool
|
|
15
|
+
from hanzo_mcp.tools.filesystem.get_file_info import GetFileInfoTool
|
|
16
|
+
from hanzo_mcp.tools.filesystem.read_files import ReadFilesTool
|
|
17
|
+
from hanzo_mcp.tools.filesystem.search_content import SearchContentTool
|
|
18
|
+
from hanzo_mcp.tools.filesystem.write_file import WriteFileTool
|
|
19
|
+
|
|
20
|
+
# Export all tool classes
|
|
21
|
+
__all__ = [
|
|
22
|
+
"ReadFilesTool",
|
|
23
|
+
"WriteFileTool",
|
|
24
|
+
"EditFileTool",
|
|
25
|
+
"DirectoryTreeTool",
|
|
26
|
+
"GetFileInfoTool",
|
|
27
|
+
"SearchContentTool",
|
|
28
|
+
"ContentReplaceTool",
|
|
29
|
+
"get_filesystem_tools",
|
|
30
|
+
"register_filesystem_tools",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
def get_read_only_filesystem_tools(
|
|
34
|
+
document_context: DocumentContext, permission_manager: PermissionManager
|
|
35
|
+
) -> list[BaseTool]:
|
|
36
|
+
"""Create instances of read-only filesystem tools.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
document_context: Document context for tracking file contents
|
|
40
|
+
permission_manager: Permission manager for access control
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
List of read-only filesystem tool instances
|
|
44
|
+
"""
|
|
45
|
+
return [
|
|
46
|
+
ReadFilesTool(document_context, permission_manager),
|
|
47
|
+
DirectoryTreeTool(document_context, permission_manager),
|
|
48
|
+
GetFileInfoTool(document_context, permission_manager),
|
|
49
|
+
SearchContentTool(document_context, permission_manager),
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_filesystem_tools(
|
|
54
|
+
document_context: DocumentContext, permission_manager: PermissionManager
|
|
55
|
+
) -> list[BaseTool]:
|
|
56
|
+
"""Create instances of all filesystem tools.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
document_context: Document context for tracking file contents
|
|
60
|
+
permission_manager: Permission manager for access control
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
List of filesystem tool instances
|
|
64
|
+
"""
|
|
65
|
+
return [
|
|
66
|
+
ReadFilesTool(document_context, permission_manager),
|
|
67
|
+
WriteFileTool(document_context, permission_manager),
|
|
68
|
+
EditFileTool(document_context, permission_manager),
|
|
69
|
+
DirectoryTreeTool(document_context, permission_manager),
|
|
70
|
+
GetFileInfoTool(document_context, permission_manager),
|
|
71
|
+
SearchContentTool(document_context, permission_manager),
|
|
72
|
+
ContentReplaceTool(document_context, permission_manager),
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def register_filesystem_tools(
|
|
77
|
+
mcp_server: FastMCP,
|
|
78
|
+
document_context: DocumentContext,
|
|
79
|
+
permission_manager: PermissionManager,
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Register all filesystem tools with the MCP server.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
mcp_server: The FastMCP server instance
|
|
85
|
+
document_context: Document context for tracking file contents
|
|
86
|
+
permission_manager: Permission manager for access control
|
|
87
|
+
"""
|
|
88
|
+
tools = get_filesystem_tools(document_context, permission_manager)
|
|
89
|
+
ToolRegistry.register_tools(mcp_server, tools)
|