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/agent/prompt.py
CHANGED
|
@@ -26,7 +26,7 @@ def get_allowed_agent_tools(
|
|
|
26
26
|
"""
|
|
27
27
|
# Get all tools except for the agent tool itself (avoid recursion)
|
|
28
28
|
filtered_tools = [tool for tool in tools if tool.name != "agent"]
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
return filtered_tools
|
|
31
31
|
|
|
32
32
|
|
|
@@ -44,9 +44,7 @@ def get_system_prompt(
|
|
|
44
44
|
System prompt for the sub-agent
|
|
45
45
|
"""
|
|
46
46
|
# Get filtered tools
|
|
47
|
-
filtered_tools = get_allowed_agent_tools(
|
|
48
|
-
tools, permission_manager
|
|
49
|
-
)
|
|
47
|
+
filtered_tools = get_allowed_agent_tools(tools, permission_manager)
|
|
50
48
|
|
|
51
49
|
# Extract tool names for display
|
|
52
50
|
tool_names = ", ".join(f"`{tool.name}`" for tool in filtered_tools)
|
|
@@ -61,6 +59,7 @@ GUIDELINES:
|
|
|
61
59
|
4. Be concise and focus on the specific task assigned
|
|
62
60
|
5. When relevant, share file names and code snippets relevant to the query
|
|
63
61
|
6. Any file paths you return in your final response MUST be absolute. DO NOT use relative paths.
|
|
62
|
+
7. CRITICAL: You can only work with the absolute paths provided in your task prompt. You cannot infer or guess other locations.
|
|
64
63
|
|
|
65
64
|
RESPONSE FORMAT:
|
|
66
65
|
- Begin with a summary of findings
|
|
@@ -86,24 +85,28 @@ def get_default_model(model_override: str | None = None) -> str:
|
|
|
86
85
|
# If in testing mode and using a test model, return as-is
|
|
87
86
|
if model_override.startswith("test-model") or "TEST_MODE" in os.environ:
|
|
88
87
|
return model_override
|
|
89
|
-
|
|
88
|
+
|
|
90
89
|
# If the model already has a provider prefix, return as-is
|
|
91
90
|
if "/" in model_override:
|
|
92
91
|
return model_override
|
|
93
|
-
|
|
92
|
+
|
|
94
93
|
# Otherwise, add the default provider prefix
|
|
95
94
|
provider = os.environ.get("AGENT_PROVIDER", "openai")
|
|
96
95
|
return f"{provider}/{model_override}"
|
|
97
|
-
|
|
96
|
+
|
|
98
97
|
# Fall back to environment variables
|
|
99
98
|
model = os.environ.get("AGENT_MODEL", "gpt-4o")
|
|
100
|
-
|
|
99
|
+
|
|
101
100
|
# Special cases for tests
|
|
102
|
-
if
|
|
101
|
+
if (
|
|
102
|
+
model.startswith("test-model")
|
|
103
|
+
or model == "gpt-4o"
|
|
104
|
+
and "TEST_MODE" in os.environ
|
|
105
|
+
):
|
|
103
106
|
return model
|
|
104
|
-
|
|
107
|
+
|
|
105
108
|
provider = os.environ.get("AGENT_PROVIDER", "openai")
|
|
106
|
-
|
|
109
|
+
|
|
107
110
|
# Only add provider prefix if it's not already in the model name
|
|
108
111
|
if "/" not in model and provider != "openai":
|
|
109
112
|
return f"{provider}/{model}"
|
|
@@ -127,11 +130,11 @@ def get_model_parameters(max_tokens: int | None = None) -> dict[str, Any]:
|
|
|
127
130
|
"temperature": float(os.environ.get("AGENT_TEMPERATURE", "0.7")),
|
|
128
131
|
"timeout": int(os.environ.get("AGENT_API_TIMEOUT", "60")),
|
|
129
132
|
}
|
|
130
|
-
|
|
133
|
+
|
|
131
134
|
# Add max_tokens if provided or if set in environment variable
|
|
132
135
|
if max_tokens is not None:
|
|
133
136
|
params["max_tokens"] = max_tokens
|
|
134
137
|
elif os.environ.get("AGENT_MAX_TOKENS"):
|
|
135
138
|
params["max_tokens"] = int(os.environ.get("AGENT_MAX_TOKENS", "1000"))
|
|
136
|
-
|
|
139
|
+
|
|
137
140
|
return params
|
|
@@ -5,7 +5,6 @@ formats, making MCP tools available to the OpenAI API, and processing tool input
|
|
|
5
5
|
and outputs for agent execution.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
|
|
9
8
|
from openai.types import FunctionParameters
|
|
10
9
|
from openai.types.chat import ChatCompletionToolParam
|
|
11
10
|
import litellm
|
|
@@ -13,7 +12,9 @@ import litellm
|
|
|
13
12
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
14
13
|
|
|
15
14
|
|
|
16
|
-
def convert_tools_to_openai_functions(
|
|
15
|
+
def convert_tools_to_openai_functions(
|
|
16
|
+
tools: list[BaseTool],
|
|
17
|
+
) -> list[ChatCompletionToolParam]:
|
|
17
18
|
"""Convert MCP tools to OpenAI function format.
|
|
18
19
|
|
|
19
20
|
Args:
|
|
@@ -22,9 +23,9 @@ def convert_tools_to_openai_functions(tools: list[BaseTool]) -> list[ChatComplet
|
|
|
22
23
|
Returns:
|
|
23
24
|
List of tools formatted for OpenAI API
|
|
24
25
|
"""
|
|
25
|
-
openai_tools:list[ChatCompletionToolParam] = []
|
|
26
|
+
openai_tools: list[ChatCompletionToolParam] = []
|
|
26
27
|
for tool in tools:
|
|
27
|
-
openai_tool:ChatCompletionToolParam = {
|
|
28
|
+
openai_tool: ChatCompletionToolParam = {
|
|
28
29
|
"type": "function",
|
|
29
30
|
"function": {
|
|
30
31
|
"name": tool.name,
|
|
@@ -47,19 +48,18 @@ def convert_tool_parameters(tool: BaseTool) -> FunctionParameters:
|
|
|
47
48
|
"""
|
|
48
49
|
# Start with a copy of the parameters
|
|
49
50
|
params = tool.parameters.copy()
|
|
50
|
-
|
|
51
|
+
|
|
51
52
|
# Ensure the schema has the right format for OpenAI
|
|
52
53
|
if "properties" not in params:
|
|
53
54
|
params["properties"] = {}
|
|
54
|
-
|
|
55
|
+
|
|
55
56
|
if "type" not in params:
|
|
56
57
|
params["type"] = "object"
|
|
57
|
-
|
|
58
|
+
|
|
58
59
|
if "required" not in params:
|
|
59
60
|
params["required"] = tool.required
|
|
60
|
-
|
|
61
|
-
return params
|
|
62
61
|
|
|
62
|
+
return params
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
def supports_parallel_function_calling(model: str) -> bool:
|
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
"""Common utilities for Hanzo MCP tools."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from fastmcp import FastMCP
|
|
4
4
|
|
|
5
|
-
from hanzo_mcp.tools.common.base import ToolRegistry
|
|
6
|
-
from hanzo_mcp.tools.common.
|
|
7
|
-
from hanzo_mcp.tools.common.
|
|
5
|
+
from hanzo_mcp.tools.common.base import BaseTool, ToolRegistry
|
|
6
|
+
from hanzo_mcp.tools.common.batch_tool import BatchTool
|
|
7
|
+
from hanzo_mcp.tools.common.thinking_tool import ThinkingTool
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def
|
|
10
|
+
def register_thinking_tool(
|
|
11
11
|
mcp_server: FastMCP,
|
|
12
|
-
) ->
|
|
13
|
-
"""Register
|
|
14
|
-
|
|
12
|
+
) -> list[BaseTool]:
|
|
13
|
+
"""Register thinking tools with the MCP server.
|
|
14
|
+
|
|
15
15
|
Args:
|
|
16
16
|
mcp_server: The FastMCP server instance
|
|
17
17
|
"""
|
|
18
|
-
|
|
19
|
-
ToolRegistry.register_tool(mcp_server,
|
|
18
|
+
thinking_tool = ThinkingTool()
|
|
19
|
+
ToolRegistry.register_tool(mcp_server, thinking_tool)
|
|
20
|
+
return [thinking_tool]
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
def
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"""Register the version tool with the MCP server.
|
|
26
|
-
|
|
23
|
+
def register_batch_tool(mcp_server: FastMCP, tools: dict[str, BaseTool]) -> None:
|
|
24
|
+
"""Register batch tool with the MCP server.
|
|
25
|
+
|
|
27
26
|
Args:
|
|
28
27
|
mcp_server: The FastMCP server instance
|
|
28
|
+
tools: Dictionary mapping tool names to tool instances
|
|
29
29
|
"""
|
|
30
|
-
|
|
30
|
+
batch_tool = BatchTool(tools)
|
|
31
|
+
ToolRegistry.register_tool(mcp_server, batch_tool)
|
hanzo_mcp/tools/common/base.py
CHANGED
|
@@ -5,180 +5,149 @@ for all tools used in Hanzo MCP. These abstractions help ensure consistent tool
|
|
|
5
5
|
behavior and provide a foundation for tool registration and management.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import functools
|
|
8
9
|
from abc import ABC, abstractmethod
|
|
9
|
-
from
|
|
10
|
+
from collections.abc import Awaitable
|
|
11
|
+
from typing import Any, Callable, final
|
|
12
|
+
|
|
13
|
+
from fastmcp import FastMCP
|
|
14
|
+
from fastmcp import Context as MCPContext
|
|
10
15
|
|
|
11
|
-
from mcp.server.fastmcp import Context as MCPContext
|
|
12
|
-
from mcp.server.fastmcp import FastMCP
|
|
13
16
|
|
|
14
|
-
from hanzo_mcp.tools.common.context import DocumentContext
|
|
15
17
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
16
|
-
from hanzo_mcp.tools.common.validation import
|
|
18
|
+
from hanzo_mcp.tools.common.validation import (
|
|
19
|
+
ValidationResult,
|
|
20
|
+
validate_path_parameter,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def handle_connection_errors(
|
|
25
|
+
func: Callable[..., Awaitable[str]],
|
|
26
|
+
) -> Callable[..., Awaitable[str]]:
|
|
27
|
+
"""Decorator to handle connection errors in MCP tool functions.
|
|
28
|
+
|
|
29
|
+
This decorator wraps tool functions to catch ClosedResourceError and other
|
|
30
|
+
connection-related exceptions that occur when the client disconnects.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
func: The async tool function to wrap
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Wrapped function that handles connection errors gracefully
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
@functools.wraps(func)
|
|
40
|
+
async def wrapper(*args: Any, **kwargs: Any) -> str:
|
|
41
|
+
try:
|
|
42
|
+
return await func(*args, **kwargs)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
# Check if this is a connection-related error
|
|
45
|
+
error_name = type(e).__name__
|
|
46
|
+
if any(
|
|
47
|
+
name in error_name
|
|
48
|
+
for name in [
|
|
49
|
+
"ClosedResourceError",
|
|
50
|
+
"ConnectionError",
|
|
51
|
+
"BrokenPipeError",
|
|
52
|
+
]
|
|
53
|
+
):
|
|
54
|
+
# Client has disconnected - log the error but don't crash
|
|
55
|
+
# Return a simple error message (though it likely won't be received)
|
|
56
|
+
return f"Client disconnected during operation: {error_name}"
|
|
57
|
+
else:
|
|
58
|
+
# Re-raise non-connection errors
|
|
59
|
+
raise
|
|
60
|
+
|
|
61
|
+
return wrapper
|
|
17
62
|
|
|
18
63
|
|
|
19
64
|
class BaseTool(ABC):
|
|
20
65
|
"""Abstract base class for all Hanzo MCP tools.
|
|
21
|
-
|
|
66
|
+
|
|
22
67
|
This class defines the core interface that all tools must implement, ensuring
|
|
23
68
|
consistency in how tools are registered, documented, and called.
|
|
24
69
|
"""
|
|
25
|
-
|
|
70
|
+
|
|
26
71
|
@property
|
|
27
72
|
@abstractmethod
|
|
28
73
|
def name(self) -> str:
|
|
29
74
|
"""Get the tool name.
|
|
30
|
-
|
|
75
|
+
|
|
31
76
|
Returns:
|
|
32
77
|
The tool name as it will appear in the MCP server
|
|
33
78
|
"""
|
|
34
79
|
pass
|
|
35
|
-
|
|
80
|
+
|
|
36
81
|
@property
|
|
37
82
|
@abstractmethod
|
|
38
83
|
def description(self) -> str:
|
|
39
84
|
"""Get the tool description.
|
|
40
|
-
|
|
85
|
+
|
|
41
86
|
Returns:
|
|
42
87
|
Detailed description of the tool's purpose and usage
|
|
43
88
|
"""
|
|
44
89
|
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
|
-
|
|
90
|
+
|
|
116
91
|
@abstractmethod
|
|
117
|
-
async def call(self, ctx: MCPContext, **params: Any) ->
|
|
92
|
+
async def call(self, ctx: MCPContext, **params: Any) -> Any:
|
|
118
93
|
"""Execute the tool with the given parameters.
|
|
119
|
-
|
|
94
|
+
|
|
120
95
|
Args:
|
|
121
96
|
ctx: MCP context for the tool call
|
|
122
97
|
**params: Tool parameters provided by the caller
|
|
123
|
-
|
|
98
|
+
|
|
124
99
|
Returns:
|
|
125
100
|
Tool execution result as a string
|
|
126
101
|
"""
|
|
127
102
|
pass
|
|
128
|
-
|
|
103
|
+
|
|
129
104
|
@abstractmethod
|
|
130
105
|
def register(self, mcp_server: FastMCP) -> None:
|
|
131
106
|
"""Register this tool with the MCP server.
|
|
132
|
-
|
|
107
|
+
|
|
133
108
|
This method must be implemented by each tool class to create a wrapper function
|
|
134
109
|
with explicitly defined parameters that calls this tool's call method.
|
|
135
110
|
The wrapper function is then registered with the MCP server.
|
|
136
|
-
|
|
111
|
+
|
|
137
112
|
Args:
|
|
138
113
|
mcp_server: The FastMCP server instance
|
|
139
114
|
"""
|
|
140
115
|
pass
|
|
141
116
|
|
|
142
117
|
|
|
143
|
-
class FileSystemTool(BaseTool,ABC):
|
|
118
|
+
class FileSystemTool(BaseTool, ABC):
|
|
144
119
|
"""Base class for filesystem-related tools.
|
|
145
|
-
|
|
120
|
+
|
|
146
121
|
Provides common functionality for working with files and directories,
|
|
147
122
|
including permission checking and path validation.
|
|
148
123
|
"""
|
|
149
|
-
|
|
150
|
-
def __init__(
|
|
151
|
-
self,
|
|
152
|
-
document_context: DocumentContext,
|
|
153
|
-
permission_manager: PermissionManager
|
|
154
|
-
) -> None:
|
|
124
|
+
|
|
125
|
+
def __init__(self, permission_manager: PermissionManager) -> None:
|
|
155
126
|
"""Initialize filesystem tool.
|
|
156
|
-
|
|
127
|
+
|
|
157
128
|
Args:
|
|
158
|
-
document_context: Document context for tracking file contents
|
|
159
129
|
permission_manager: Permission manager for access control
|
|
160
130
|
"""
|
|
161
|
-
self.
|
|
162
|
-
|
|
163
|
-
|
|
131
|
+
self.permission_manager: PermissionManager = permission_manager
|
|
132
|
+
|
|
164
133
|
def validate_path(self, path: str, param_name: str = "path") -> ValidationResult:
|
|
165
134
|
"""Validate a path parameter.
|
|
166
|
-
|
|
135
|
+
|
|
167
136
|
Args:
|
|
168
137
|
path: Path to validate
|
|
169
138
|
param_name: Name of the parameter (for error messages)
|
|
170
|
-
|
|
139
|
+
|
|
171
140
|
Returns:
|
|
172
141
|
Validation result containing validation status and error message if any
|
|
173
142
|
"""
|
|
174
143
|
return validate_path_parameter(path, param_name)
|
|
175
|
-
|
|
144
|
+
|
|
176
145
|
def is_path_allowed(self, path: str) -> bool:
|
|
177
146
|
"""Check if a path is allowed according to permission settings.
|
|
178
|
-
|
|
147
|
+
|
|
179
148
|
Args:
|
|
180
149
|
path: Path to check
|
|
181
|
-
|
|
150
|
+
|
|
182
151
|
Returns:
|
|
183
152
|
True if the path is allowed, False otherwise
|
|
184
153
|
"""
|
|
@@ -188,26 +157,26 @@ class FileSystemTool(BaseTool,ABC):
|
|
|
188
157
|
@final
|
|
189
158
|
class ToolRegistry:
|
|
190
159
|
"""Registry for Hanzo MCP tools.
|
|
191
|
-
|
|
160
|
+
|
|
192
161
|
Provides functionality for registering tool implementations with an MCP server,
|
|
193
162
|
handling the conversion between tool classes and MCP tool functions.
|
|
194
163
|
"""
|
|
195
|
-
|
|
164
|
+
|
|
196
165
|
@staticmethod
|
|
197
166
|
def register_tool(mcp_server: FastMCP, tool: BaseTool) -> None:
|
|
198
167
|
"""Register a tool with the MCP server.
|
|
199
|
-
|
|
168
|
+
|
|
200
169
|
Args:
|
|
201
170
|
mcp_server: The FastMCP server instance
|
|
202
171
|
tool: The tool to register
|
|
203
172
|
"""
|
|
204
173
|
# Use the tool's register method which handles all the details
|
|
205
174
|
tool.register(mcp_server)
|
|
206
|
-
|
|
175
|
+
|
|
207
176
|
@staticmethod
|
|
208
177
|
def register_tools(mcp_server: FastMCP, tools: list[BaseTool]) -> None:
|
|
209
178
|
"""Register multiple tools with the MCP server.
|
|
210
|
-
|
|
179
|
+
|
|
211
180
|
Args:
|
|
212
181
|
mcp_server: The FastMCP server instance
|
|
213
182
|
tools: List of tools to register
|