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.

Files changed (87) hide show
  1. hanzo_mcp/__init__.py +1 -1
  2. hanzo_mcp/cli.py +123 -160
  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 +120 -98
  14. hanzo_mcp/tools/__init__.py +107 -31
  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 -41
  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.4.dist-info → hanzo_mcp-0.5.0.dist-info}/METADATA +35 -3
  59. hanzo_mcp-0.5.0.dist-info/RECORD +63 -0
  60. {hanzo_mcp-0.3.4.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 -198
  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 -882
  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.4.dist-info/RECORD +0 -53
  85. {hanzo_mcp-0.3.4.dist-info → hanzo_mcp-0.5.0.dist-info}/entry_points.txt +0 -0
  86. {hanzo_mcp-0.3.4.dist-info → hanzo_mcp-0.5.0.dist-info}/licenses/LICENSE +0 -0
  87. {hanzo_mcp-0.3.4.dist-info → hanzo_mcp-0.5.0.dist-info}/top_level.txt +0 -0
@@ -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 model.startswith("test-model") or model == "gpt-4o" and "TEST_MODE" in os.environ:
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(tools: list[BaseTool]) -> list[ChatCompletionToolParam]:
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 mcp.server.fastmcp import FastMCP
3
+ from fastmcp import FastMCP
4
4
 
5
- from hanzo_mcp.tools.common.base import ToolRegistry
6
- from hanzo_mcp.tools.common.think_tool import ThinkingTool
7
- from hanzo_mcp.tools.common.version_tool import VersionTool
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 register_think_tool(
10
+ def register_thinking_tool(
11
11
  mcp_server: FastMCP,
12
- ) -> None:
13
- """Register all thinking tools with the MCP server.
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
- think_tool = ThinkingTool()
19
- ToolRegistry.register_tool(mcp_server, think_tool)
18
+ thinking_tool = ThinkingTool()
19
+ ToolRegistry.register_tool(mcp_server, thinking_tool)
20
+ return [thinking_tool]
20
21
 
21
22
 
22
- def register_version_tool(
23
- mcp_server: FastMCP,
24
- ) -> None:
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
- _ = VersionTool(mcp_server) # Tool registers itself in constructor
30
+ batch_tool = BatchTool(tools)
31
+ ToolRegistry.register_tool(mcp_server, batch_tool)
@@ -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 typing import Any, final
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 ValidationResult, validate_path_parameter
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) -> str:
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.document_context:DocumentContext = document_context
162
- self.permission_manager:PermissionManager = permission_manager
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