hanzo-mcp 0.1.36__py3-none-any.whl → 0.3.1__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.

@@ -0,0 +1,120 @@
1
+ """Provider registry for agent delegation.
2
+
3
+ Manages different model providers for agent delegation.
4
+ """
5
+
6
+ import logging
7
+ from typing import Any, Dict, List, Optional, Tuple, Type
8
+
9
+ from hanzo_mcp.tools.agent.base_provider import BaseModelProvider
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class ProviderRegistry:
15
+ """Registry for model providers."""
16
+
17
+ _instance = None
18
+
19
+ def __new__(cls):
20
+ """Singleton pattern to ensure only one registry instance exists."""
21
+ if cls._instance is None:
22
+ cls._instance = super(ProviderRegistry, cls).__new__(cls)
23
+ cls._instance._initialized = False
24
+ return cls._instance
25
+
26
+ def __init__(self):
27
+ """Initialize the provider registry."""
28
+ if self._initialized:
29
+ return
30
+
31
+ self.providers = {}
32
+ self.provider_classes = {}
33
+ self._initialized = True
34
+ logger.info("Provider registry initialized")
35
+
36
+ def register_provider_class(self, provider_type: str, provider_class: Type[BaseModelProvider]) -> None:
37
+ """Register a provider class with the registry.
38
+
39
+ Args:
40
+ provider_type: The type identifier for the provider
41
+ provider_class: The provider class to register
42
+ """
43
+ self.provider_classes[provider_type] = provider_class
44
+ logger.info(f"Registered provider class: {provider_type}")
45
+
46
+ async def get_provider(self, provider_type: str) -> BaseModelProvider:
47
+ """Get or create a provider instance for the given type.
48
+
49
+ Args:
50
+ provider_type: The type identifier for the provider
51
+
52
+ Returns:
53
+ A provider instance
54
+
55
+ Raises:
56
+ ValueError: If the provider type is not registered
57
+ """
58
+ # Check if we already have an instance
59
+ if provider_type in self.providers:
60
+ return self.providers[provider_type]
61
+
62
+ # Check if we have a class for this type
63
+ if provider_type not in self.provider_classes:
64
+ raise ValueError(f"Unknown provider type: {provider_type}")
65
+
66
+ # Create a new instance
67
+ provider_class = self.provider_classes[provider_type]
68
+ provider = provider_class()
69
+
70
+ # Initialize the provider
71
+ await provider.initialize()
72
+
73
+ # Store and return the provider
74
+ self.providers[provider_type] = provider
75
+ logger.info(f"Created and initialized provider: {provider_type}")
76
+ return provider
77
+
78
+ async def shutdown_all(self) -> None:
79
+ """Shutdown all providers."""
80
+ for provider_type, provider in self.providers.items():
81
+ try:
82
+ await provider.shutdown()
83
+ logger.info(f"Provider shut down: {provider_type}")
84
+ except Exception as e:
85
+ logger.error(f"Failed to shut down provider {provider_type}: {str(e)}")
86
+
87
+ self.providers = {}
88
+ logger.info("All providers shut down")
89
+
90
+ async def shutdown_provider(self, provider_type: str) -> None:
91
+ """Shutdown a specific provider.
92
+
93
+ Args:
94
+ provider_type: The type identifier for the provider
95
+ """
96
+ if provider_type not in self.providers:
97
+ logger.warning(f"Provider not found: {provider_type}")
98
+ return
99
+
100
+ try:
101
+ await self.providers[provider_type].shutdown()
102
+ del self.providers[provider_type]
103
+ logger.info(f"Provider shut down: {provider_type}")
104
+ except Exception as e:
105
+ logger.error(f"Failed to shut down provider {provider_type}: {str(e)}")
106
+
107
+
108
+ # Create a singleton instance
109
+ registry = ProviderRegistry()
110
+
111
+ # Register LiteLLM provider
112
+ from hanzo_mcp.tools.agent.litellm_provider import LiteLLMProvider
113
+ registry.register_provider_class("litellm", LiteLLMProvider)
114
+
115
+ # Try to register LM Studio provider if available
116
+ try:
117
+ from hanzo_mcp.tools.agent.lmstudio_provider import LMStudioProvider
118
+ registry.register_provider_class("lmstudio", LMStudioProvider)
119
+ except ImportError:
120
+ logger.warning("LM Studio provider not available. Install the package if needed.")
@@ -3,7 +3,6 @@
3
3
  from mcp.server.fastmcp import FastMCP
4
4
 
5
5
  from hanzo_mcp.tools.common.base import ToolRegistry
6
- from hanzo_mcp.tools.common.path_utils import PathUtils
7
6
  from hanzo_mcp.tools.common.think_tool import ThinkingTool
8
7
  from hanzo_mcp.tools.common.version_tool import VersionTool
9
8
 
@@ -14,8 +14,6 @@ from typing import Any, ClassVar, final
14
14
  from mcp.server.fastmcp import Context as MCPContext
15
15
  from mcp.server.lowlevel.helper_types import ReadResourceContents
16
16
 
17
- from hanzo_mcp.tools.common.path_utils import PathUtils
18
-
19
17
 
20
18
  @final
21
19
  class ToolContext:
@@ -181,9 +179,9 @@ class DocumentContext:
181
179
  Args:
182
180
  path: The path to allow
183
181
  """
184
- # Normalize path (expand user paths and make absolute)
185
- normalized_path = PathUtils.normalize_path(path)
186
- resolved_path: Path = Path(normalized_path).resolve()
182
+ # Expand user path (e.g., ~/ or $HOME)
183
+ expanded_path = os.path.expanduser(path)
184
+ resolved_path: Path = Path(expanded_path).resolve()
187
185
  self.allowed_paths.add(resolved_path)
188
186
 
189
187
  def is_path_allowed(self, path: str) -> bool:
@@ -195,9 +193,9 @@ class DocumentContext:
195
193
  Returns:
196
194
  True if the path is allowed, False otherwise
197
195
  """
198
- # Normalize path (expand user paths and make absolute)
199
- normalized_path = PathUtils.normalize_path(path)
200
- resolved_path: Path = Path(normalized_path).resolve()
196
+ # Expand user path (e.g., ~/ or $HOME)
197
+ expanded_path = os.path.expanduser(path)
198
+ resolved_path: Path = Path(expanded_path).resolve()
201
199
 
202
200
  # Check if the path is within any allowed path
203
201
  for allowed_path in self.allowed_paths:
@@ -0,0 +1,86 @@
1
+ """Error handling utilities for MCP tools.
2
+
3
+ This module provides utility functions for better error handling in MCP tools.
4
+ """
5
+
6
+ import logging
7
+ import traceback
8
+ from functools import wraps
9
+ from typing import Any, Callable, TypeVar, Awaitable, cast
10
+
11
+ from mcp.server.fastmcp import Context as MCPContext
12
+
13
+ # Setup logger
14
+ logger = logging.getLogger(__name__)
15
+
16
+ # Type variables for generic function signatures
17
+ T = TypeVar('T')
18
+ F = TypeVar('F', bound=Callable[..., Awaitable[Any]])
19
+
20
+
21
+ async def log_error(ctx: MCPContext, error: Exception, message: str) -> None:
22
+ """Log an error to both the logger and the MCP context.
23
+
24
+ Args:
25
+ ctx: The MCP context
26
+ error: The exception that occurred
27
+ message: A descriptive message about the error
28
+ """
29
+ error_message = f"{message}: {str(error)}"
30
+ stack_trace = "".join(traceback.format_exception(type(error), error, error.__traceback__))
31
+
32
+ # Log to system logger
33
+ logger.error(error_message)
34
+ logger.debug(stack_trace)
35
+
36
+ # Log to MCP context if available
37
+ try:
38
+ await ctx.error(error_message)
39
+ except Exception as e:
40
+ logger.error(f"Failed to log error to MCP context: {str(e)}")
41
+
42
+
43
+ def tool_error_handler(func: F) -> F:
44
+ """Decorator for handling errors in tool execution.
45
+
46
+ This decorator wraps a tool function to catch and properly handle exceptions,
47
+ ensuring they are logged and proper error messages are returned.
48
+
49
+ Args:
50
+ func: The async tool function to wrap
51
+
52
+ Returns:
53
+ Wrapped function with error handling
54
+ """
55
+ @wraps(func)
56
+ async def wrapper(*args: Any, **kwargs: Any) -> Any:
57
+ try:
58
+ # Extract the MCP context from arguments
59
+ ctx = None
60
+ for arg in args:
61
+ if isinstance(arg, MCPContext):
62
+ ctx = arg
63
+ break
64
+
65
+ if not ctx and 'ctx' in kwargs:
66
+ ctx = kwargs['ctx']
67
+
68
+ if not ctx:
69
+ logger.warning("No MCP context found in tool arguments, error handling will be limited")
70
+
71
+ # Call the original function
72
+ return await func(*args, **kwargs)
73
+ except Exception as e:
74
+ # Log the error
75
+ error_message = f"Error in tool execution: {func.__name__}"
76
+ if ctx:
77
+ await log_error(ctx, e, error_message)
78
+ else:
79
+ stack_trace = "".join(traceback.format_exception(type(e), e, e.__traceback__))
80
+ logger.error(f"{error_message}: {str(e)}")
81
+ logger.debug(stack_trace)
82
+
83
+ # Return a friendly error message
84
+ return f"Error executing {func.__name__}: {str(e)}"
85
+
86
+ return cast(F, wrapper)
@@ -0,0 +1,84 @@
1
+ """Logging configuration for Hanzo MCP.
2
+
3
+ This module sets up logging for the Hanzo MCP project.
4
+ """
5
+
6
+ import logging
7
+ import os
8
+ import sys
9
+ from datetime import datetime
10
+ from pathlib import Path
11
+
12
+
13
+ def setup_logging(log_level: str = "INFO", log_to_file: bool = True, testing: bool = False) -> None:
14
+ """Set up logging configuration.
15
+
16
+ Args:
17
+ log_level: The logging level ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
18
+ log_to_file: Whether to log to a file in addition to the console
19
+ testing: Set to True to disable file operations for testing
20
+ """
21
+ # Convert string log level to logging constant
22
+ numeric_level = getattr(logging, log_level.upper(), None)
23
+ if not isinstance(numeric_level, int):
24
+ raise ValueError(f"Invalid log level: {log_level}")
25
+
26
+ # Create logs directory if needed
27
+ log_dir = Path.home() / ".hanzo" / "logs"
28
+ if log_to_file and not testing:
29
+ log_dir.mkdir(parents=True, exist_ok=True)
30
+
31
+ # Generate log filename based on current date
32
+ current_time = datetime.now().strftime("%Y-%m-%d")
33
+ log_file = log_dir / f"hanzo-mcp-{current_time}.log"
34
+
35
+ # Base configuration
36
+ handlers = []
37
+
38
+ # Console handler
39
+ console = logging.StreamHandler(sys.stdout)
40
+ console.setLevel(numeric_level)
41
+ console_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
42
+ console.setFormatter(console_formatter)
43
+ handlers.append(console)
44
+
45
+ # File handler (if enabled)
46
+ if log_to_file and not testing:
47
+ file_handler = logging.FileHandler(log_file)
48
+ file_handler.setLevel(numeric_level)
49
+ file_formatter = logging.Formatter(
50
+ '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'
51
+ )
52
+ file_handler.setFormatter(file_formatter)
53
+ handlers.append(file_handler)
54
+
55
+ # Configure root logger
56
+ logging.basicConfig(
57
+ level=numeric_level,
58
+ handlers=handlers,
59
+ force=True # Overwrite any existing configuration
60
+ )
61
+
62
+ # Set specific log levels for third-party libraries
63
+ logging.getLogger("urllib3").setLevel(logging.WARNING)
64
+ logging.getLogger("asyncio").setLevel(logging.WARNING)
65
+
66
+ # Log startup message
67
+ root_logger = logging.getLogger()
68
+ root_logger.info(f"Logging initialized at level {log_level}")
69
+ if log_to_file and not testing:
70
+ root_logger.info(f"Log file: {log_file}")
71
+
72
+
73
+ def get_log_files() -> list[str]:
74
+ """Get a list of all log files.
75
+
76
+ Returns:
77
+ List of log file paths
78
+ """
79
+ log_dir = Path.home() / ".hanzo" / "logs"
80
+ if not log_dir.exists():
81
+ return []
82
+
83
+ log_files = [str(f) for f in log_dir.glob("hanzo-mcp-*.log")]
84
+ return sorted(log_files, reverse=True)
@@ -6,8 +6,6 @@ from collections.abc import Awaitable, Callable
6
6
  from pathlib import Path
7
7
  from typing import Any, TypeVar, final
8
8
 
9
- from hanzo_mcp.tools.common.path_utils import PathUtils
10
-
11
9
  # Define type variables for better type annotations
12
10
  T = TypeVar("T")
13
11
  P = TypeVar("P")
@@ -71,9 +69,9 @@ class PermissionManager:
71
69
  Args:
72
70
  path: The path to allow
73
71
  """
74
- # Normalize path (expand user paths and make absolute)
75
- normalized_path = PathUtils.normalize_path(path)
76
- resolved_path: Path = Path(normalized_path).resolve()
72
+ # Expand user path (e.g., ~/ or $HOME)
73
+ expanded_path = os.path.expanduser(path)
74
+ resolved_path: Path = Path(expanded_path).resolve()
77
75
  self.allowed_paths.add(resolved_path)
78
76
 
79
77
  def remove_allowed_path(self, path: str) -> None:
@@ -112,9 +110,9 @@ class PermissionManager:
112
110
  Returns:
113
111
  True if the path is allowed, False otherwise
114
112
  """
115
- # Normalize path (expand user paths and make absolute)
116
- normalized_path = PathUtils.normalize_path(path)
117
- resolved_path: Path = Path(normalized_path).resolve()
113
+ # Expand user path (e.g., ~/ or $HOME)
114
+ expanded_path = os.path.expanduser(path)
115
+ resolved_path: Path = Path(expanded_path).resolve()
118
116
 
119
117
  # Check exclusions first
120
118
  if self._is_path_excluded(resolved_path):
@@ -77,6 +77,7 @@ def register_filesystem_tools(
77
77
  mcp_server: FastMCP,
78
78
  document_context: DocumentContext,
79
79
  permission_manager: PermissionManager,
80
+ disable_write_tools: bool = False,
80
81
  ) -> None:
81
82
  """Register all filesystem tools with the MCP server.
82
83
 
@@ -84,6 +85,10 @@ def register_filesystem_tools(
84
85
  mcp_server: The FastMCP server instance
85
86
  document_context: Document context for tracking file contents
86
87
  permission_manager: Permission manager for access control
88
+ disable_write_tools: Whether to disable write/edit tools (default: False)
87
89
  """
88
- tools = get_filesystem_tools(document_context, permission_manager)
90
+ if disable_write_tools:
91
+ tools = get_read_only_filesystem_tools(document_context, permission_manager)
92
+ else:
93
+ tools = get_filesystem_tools(document_context, permission_manager)
89
94
  ToolRegistry.register_tools(mcp_server, tools)
@@ -3,14 +3,12 @@
3
3
  This module provides the DirectoryTreeTool for viewing file and directory structures.
4
4
  """
5
5
 
6
- import os
7
6
  from pathlib import Path
8
7
  from typing import Any, final, override
9
8
 
10
9
  from mcp.server.fastmcp import Context as MCPContext
11
10
  from mcp.server.fastmcp import FastMCP
12
11
 
13
- from hanzo_mcp.tools.common.path_utils import PathUtils
14
12
  from hanzo_mcp.tools.filesystem.base import FilesystemBaseTool
15
13
 
16
14
 
@@ -139,47 +137,17 @@ requested. Only works within allowed directories."""
139
137
  if not is_dir:
140
138
  return error_msg
141
139
 
142
- # Define filtered directories based on common patterns and .gitignore
140
+ # Define filtered directories
143
141
  FILTERED_DIRECTORIES = {
144
- # Hidden/dot directories
145
- ".git", ".github", ".gitignore", ".hg", ".svn", ".venv", ".env",
146
- ".idea", ".vscode", ".vs", ".cache", ".config", ".local",
147
- ".pytest_cache", ".ruff_cache", ".mypy_cache", ".pytype",
148
- ".coverage", ".tox", ".nox", ".circleci", ".llm-context",
149
- # Cache directories
150
- "__pycache__", ".ipynb_checkpoints", "htmlcov", ".eggs",
151
- # Build artifacts
152
- "dist", "build", "target", "out", "site", "coverage",
153
- # Dependency directories
154
- "node_modules", "venv", "env", "ENV", "lib", "libs", "vendor",
155
- "eggs", "sdist", "wheels", "share"
142
+ ".git", "node_modules", ".venv", "venv",
143
+ "__pycache__", ".pytest_cache", ".idea",
144
+ ".vs", ".vscode", "dist", "build", "target",
145
+ ".ruff_cache",".llm-context"
156
146
  }
157
147
 
158
148
  # Log filtering settings
159
149
  await tool_ctx.info(f"Directory tree filtering: include_filtered={include_filtered}")
160
150
 
161
- # Try to get additional patterns from .gitignore if it exists
162
- gitignore_patterns = set()
163
- gitignore_path = dir_path / ".gitignore"
164
- if gitignore_path.exists() and gitignore_path.is_file():
165
- try:
166
- with open(gitignore_path, "r") as f:
167
- for line in f:
168
- line = line.strip()
169
- if line and not line.startswith("#"):
170
- # Strip trailing slashes for directory patterns
171
- if line.endswith("/"):
172
- line = line[:-1]
173
- # Extract the actual pattern without path
174
- pattern = line.split("/")[-1]
175
- if pattern and "*" not in pattern and "?" not in pattern:
176
- gitignore_patterns.add(pattern)
177
- except Exception as e:
178
- await tool_ctx.warning(f"Error reading .gitignore: {str(e)}")
179
-
180
- # Add gitignore patterns to filtered directories
181
- FILTERED_DIRECTORIES.update(gitignore_patterns)
182
-
183
151
  # Check if a directory should be filtered
184
152
  def should_filter(current_path: Path) -> bool:
185
153
  # Don't filter if it's the explicitly requested path
@@ -187,15 +155,8 @@ requested. Only works within allowed directories."""
187
155
  # Don't filter explicitly requested paths
188
156
  return False
189
157
 
190
- # First check standard filtered directories
191
- if current_path.name in FILTERED_DIRECTORIES and not include_filtered:
192
- return True
193
-
194
- # Also filter hidden directories (dot directories) unless explicitly included
195
- if PathUtils.is_dot_directory(current_path) and not include_filtered:
196
- return True
197
-
198
- return False
158
+ # Filter based on directory name if filtering is enabled
159
+ return current_path.name in FILTERED_DIRECTORIES and not include_filtered
199
160
 
200
161
  # Track stats for summary
201
162
  stats = {
@@ -59,6 +59,7 @@ def register_jupyter_tools(
59
59
  mcp_server: FastMCP,
60
60
  document_context: DocumentContext,
61
61
  permission_manager: PermissionManager,
62
+ disable_write_tools: bool = False,
62
63
  ) -> None:
63
64
  """Register all Jupyter notebook tools with the MCP server.
64
65
 
@@ -66,6 +67,10 @@ def register_jupyter_tools(
66
67
  mcp_server: The FastMCP server instance
67
68
  document_context: Document context for tracking file contents
68
69
  permission_manager: Permission manager for access control
70
+ disable_write_tools: Whether to disable write/edit tools (default: False)
69
71
  """
70
- tools = get_jupyter_tools(document_context, permission_manager)
72
+ if disable_write_tools:
73
+ tools = get_read_only_jupyter_tools(document_context, permission_manager)
74
+ else:
75
+ tools = get_jupyter_tools(document_context, permission_manager)
71
76
  ToolRegistry.register_tools(mcp_server, tools)
@@ -14,8 +14,6 @@ from collections.abc import Awaitable, Callable
14
14
  from pathlib import Path
15
15
  from typing import Dict, Optional, final
16
16
 
17
- from hanzo_mcp.tools.common.path_utils import PathUtils
18
-
19
17
  from mcp.server.fastmcp import Context as MCPContext
20
18
  from mcp.server.fastmcp import FastMCP
21
19
 
@@ -136,8 +134,8 @@ class CommandExecutor:
136
134
  path: The path to set as the current working directory
137
135
  """
138
136
  session = self.get_session_manager(session_id)
139
- normalized_path = PathUtils.normalize_path(path)
140
- session.set_working_dir(Path(normalized_path))
137
+ expanded_path = os.path.expanduser(path)
138
+ session.set_working_dir(Path(expanded_path))
141
139
 
142
140
  def get_working_dir(self, session_id: str) -> str:
143
141
  """Get the current working directory for the session.
@@ -251,8 +249,11 @@ class CommandExecutor:
251
249
  session_cwd = self.get_working_dir(session_id)
252
250
  target_dir = os.path.join(session_cwd, target_dir)
253
251
 
254
- # Normalize path (expand user and make absolute)
255
- target_dir = PathUtils.normalize_path(target_dir)
252
+ # Expand user paths
253
+ target_dir = os.path.expanduser(target_dir)
254
+
255
+ # Normalize path
256
+ target_dir = os.path.normpath(target_dir)
256
257
 
257
258
  if os.path.isdir(target_dir):
258
259
  self.set_working_dir(session_id, target_dir)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hanzo-mcp
3
- Version: 0.1.36
3
+ Version: 0.3.1
4
4
  Summary: MCP implementation of Hanzo capabilities
5
5
  Author-email: Hanzo Industries Inc <dev@hanzo.ai>
6
6
  License: MIT
@@ -1,30 +1,36 @@
1
- hanzo_mcp/__init__.py,sha256=R1BsBtTu0Ll8ASecW2mPKCntuNwFh6Z8vY8U5tjQ6pE,90
2
- hanzo_mcp/cli.py,sha256=VPGky5w47_7PYYaXnryToaj7By7Sld0vn_bZ2Hxmpy8,7189
3
- hanzo_mcp/server.py,sha256=OyF-jmPCHXId7DaIk3QcGxnmkSgsnd0GLx3WAtiaOik,6032
4
- hanzo_mcp/tools/__init__.py,sha256=9bq0emNV_xQk3elWdfIkzgzRU2G1vIsV2k_neV43coc,3333
1
+ hanzo_mcp/__init__.py,sha256=roRFuV1ugS6Zf28PtFGKzOnpJMDFncOz6fR3TlG-LF0,89
2
+ hanzo_mcp/cli.py,sha256=ST428ys32fputErgrmLNJV9oYHbKGwkjrPBJfKh_K_Y,10820
3
+ hanzo_mcp/server.py,sha256=bV4ywWOgm7BwZrbZwv1qoRKbAsMYT08aCGQwuFS8raM,6678
4
+ hanzo_mcp/tools/__init__.py,sha256=9LbWAPfSntDwLaAex3dagsHO4BgZQHKj5E6UX-Gmyb4,3496
5
5
  hanzo_mcp/tools/agent/__init__.py,sha256=0eyQqqdAy7WCZEqUfV6xh66bDpQI9neB6iDjWf0_pJI,2189
6
6
  hanzo_mcp/tools/agent/agent_tool.py,sha256=qXu62ZRGM0o9mxOiRVFy-ABIZtEJ8z03DqAXshKAieI,19180
7
+ hanzo_mcp/tools/agent/base_provider.py,sha256=dd1J75Q0wl_t_Gcuwc8Ft3PWxeDsmf0B3ybhRsUEgmA,2080
8
+ hanzo_mcp/tools/agent/litellm_provider.py,sha256=6mVOLSNpfjAJKj6aBMflyYU6DRJpEqIxgu8wSzeRmxU,1334
9
+ hanzo_mcp/tools/agent/lmstudio_agent.py,sha256=c4-VIDRXksv39HYpXzqwyF1PisoNd73R3Txa8Mkzibg,14510
10
+ hanzo_mcp/tools/agent/lmstudio_provider.py,sha256=-gX8fxebyWaC7bqmZERR6QQc62Paj62IIDIu_rBu0wM,7876
7
11
  hanzo_mcp/tools/agent/prompt.py,sha256=jmYRI4Sm2D3LwEdC2qWakpqupgfGg8CT6grmx4NEDaA,4431
12
+ hanzo_mcp/tools/agent/provider_registry.py,sha256=A-Wd5B5hLBznQV4wWbxE-yyvBPnbuUL2OtSJUz2K_H0,4236
8
13
  hanzo_mcp/tools/agent/tool_adapter.py,sha256=g9NKfIET0WOsm0r28xEXsibsprpI1C6ripcM6QwU-rI,2172
9
- hanzo_mcp/tools/common/__init__.py,sha256=E0rOalOkyLKsjDcmZNjuvKY4Fu0NTr81rYC1G4r51UM,853
14
+ hanzo_mcp/tools/common/__init__.py,sha256=felrGAEqLWOa8fuHjTCS7khqtlA-izz8k865ky7R-f8,797
10
15
  hanzo_mcp/tools/common/base.py,sha256=O7Lgft0XiC9Iyi3fYsmoWWrvKDK2Aa-FJLxPgnNJRJY,7341
11
- hanzo_mcp/tools/common/context.py,sha256=rXK6FvHFnJ0OAYvAKfJUEjY6TKFn_Dj8ErgvCSvGHh4,13254
12
- hanzo_mcp/tools/common/path_utils.py,sha256=eLmHxvUQimCZceUNg5W9DbUvlA3uRzU6Stal-3XHRf8,1509
13
- hanzo_mcp/tools/common/permissions.py,sha256=w_D2bIX5E0PmctmNf0tFASYebU-cUuEFICjOOiAI_WM,7703
16
+ hanzo_mcp/tools/common/context.py,sha256=ReIfcm37j5qnLQ8G_-d88ad5uC1OKkjQZKG9HdJPybI,13145
17
+ hanzo_mcp/tools/common/error_handling.py,sha256=rluaHpXV89pjcf8JxgYB6EdqVkcxihb3pyEO2gN-u7w,2789
18
+ hanzo_mcp/tools/common/logging_config.py,sha256=b1swgWV3nuhpVT0TCcbxlOC0gmiRLmbey_t2LOWEMbU,2793
19
+ hanzo_mcp/tools/common/permissions.py,sha256=4YCfA2PJUOl-z_47n5uaRXO8gAZ_shMaPhpi1dilgRE,7594
14
20
  hanzo_mcp/tools/common/session.py,sha256=csX5ZhgBjO2bdXXXPpsUPzOCc7Tib-apYe01AP8sh8k,2774
15
21
  hanzo_mcp/tools/common/think_tool.py,sha256=I-O6ipM-PUofkNoMMzv37Y_2Yfx9mh7F1upTTsfRN4M,4046
16
22
  hanzo_mcp/tools/common/validation.py,sha256=gB3uM_cbPZsH4Ez0hnTgIcdP-AUlHZU02aRmZEpk_6I,3648
17
23
  hanzo_mcp/tools/common/version_tool.py,sha256=4bJZhqgtvwQMyVSSZ-xU-NQvr1xfnyDi_4FnOpZvuw0,3406
18
- hanzo_mcp/tools/filesystem/__init__.py,sha256=-wNhb0IjJgz05n_fRP0wDXfKgJ6fgBp4wrGo62Hpyvc,3265
24
+ hanzo_mcp/tools/filesystem/__init__.py,sha256=Jm5AmLIdRxzcZ4L8ajNJCQYObpmUeoBOzdYdFvDgQIA,3513
19
25
  hanzo_mcp/tools/filesystem/base.py,sha256=HAzuMCrS0dKOBZNoLr7y74tdbYyKpi0FGhStuRgkFTU,3917
20
26
  hanzo_mcp/tools/filesystem/content_replace.py,sha256=ZwzxyOTASUmzP-jtEnsSR-MKtNFC4R3kQunpV3KOCyg,11049
21
- hanzo_mcp/tools/filesystem/directory_tree.py,sha256=QgMjvBv9rKVNHEumHgrS0chP91IKo5xg3X5JzSiHbOY,13403
27
+ hanzo_mcp/tools/filesystem/directory_tree.py,sha256=cx-zpOeKP8DDuMt1ls3QhRk9h3RVmMhpPwpqn4wTfP4,11271
22
28
  hanzo_mcp/tools/filesystem/edit_file.py,sha256=03ku1_8X_uAUPfqGlR6fv55VEz-0Pifp9DJtkcOTFHY,10980
23
29
  hanzo_mcp/tools/filesystem/get_file_info.py,sha256=WR7uMqFcpKboS3FX3jF-MD-0-ROJJcppX7M_GtR0yLs,5392
24
30
  hanzo_mcp/tools/filesystem/read_files.py,sha256=0JYJ2kM8FIoksbnnO8V0uY3D2R1uWvR7zb7_oXV0sMM,6968
25
31
  hanzo_mcp/tools/filesystem/search_content.py,sha256=2zXke1YHYxg6GKQ_XHb0sXeaSkHI7Jx3P-YAqrpOTNM,10766
26
32
  hanzo_mcp/tools/filesystem/write_file.py,sha256=7ZNR1ygECBjT7m62QNkeIEt0OGxNZL2zijX-bASWj0Y,5303
27
- hanzo_mcp/tools/jupyter/__init__.py,sha256=xFYW8Idb4x1jLWbexN5sqFKSCd1aL820uENzJ7mb6rs,2282
33
+ hanzo_mcp/tools/jupyter/__init__.py,sha256=9mZ55Mq0JMmjOxCxbeFHS8vOqvezzh_uCst02EPB4Ng,2527
28
34
  hanzo_mcp/tools/jupyter/base.py,sha256=xtssHrkHx_u_nE12dqtZGcvuJe8kfsbSkMmq-6KQobQ,10412
29
35
  hanzo_mcp/tools/jupyter/edit_notebook.py,sha256=_ZNlsCYaO9_SbZouvrLYElvssL6nlElCc2JxNCeMdQo,11986
30
36
  hanzo_mcp/tools/jupyter/notebook_operations.py,sha256=PkZXk_PYPkBGxg2RWzqh-rN6VDHjFybImhdUm3xLLoY,23120
@@ -35,13 +41,13 @@ hanzo_mcp/tools/project/base.py,sha256=CniLAsjum5vC3cgvF9AqU-_ZY_0Nf9uaF2L_xV2ob
35
41
  hanzo_mcp/tools/project/project_analyze.py,sha256=6GLE_JcSiCy6kKdee0sMI5T2229A-Vpp98s2j_JD6yI,5711
36
42
  hanzo_mcp/tools/shell/__init__.py,sha256=lKgh0WXds4tZJ1tIL9MJbyMSzP6A9uZQALjGGBvyYc4,1679
37
43
  hanzo_mcp/tools/shell/base.py,sha256=OxKNWMp-fB-vozzWOE_hHvr5M_pFKSMIYfOX0dEOWzA,4836
38
- hanzo_mcp/tools/shell/command_executor.py,sha256=MqGxHoLB_cDOutdTv59BF5ooiLE-_2Xt-J9UOE-ReFo,32610
44
+ hanzo_mcp/tools/shell/command_executor.py,sha256=5GcJvg54uty9fl_tkGdWTBcHyjxuynQZ_Tv6qOyKgdk,32616
39
45
  hanzo_mcp/tools/shell/run_command.py,sha256=r7HBw0lqabgkGnVsDXmLnrTo0SU9g8gLvzpa-9n-cmM,6891
40
46
  hanzo_mcp/tools/shell/run_script.py,sha256=CLYnDc0Ze8plkXU6d98RgE4UrBg-fwaMVdcn9Fc6Ixw,7432
41
47
  hanzo_mcp/tools/shell/script_tool.py,sha256=s63tawIZBvwgm_kU9P7A3D4v2ulVw7j4l_rpsa_zGuc,8680
42
- hanzo_mcp-0.1.36.dist-info/licenses/LICENSE,sha256=mf1qZGFsPGskoPgytp9B-RsahfKvXsBpmaAbTLGTt8Y,1063
43
- hanzo_mcp-0.1.36.dist-info/METADATA,sha256=xVd9e-JGD2c0Rs5GdG7ZMoDu4LcsdS2jwgADGdegj30,7551
44
- hanzo_mcp-0.1.36.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
45
- hanzo_mcp-0.1.36.dist-info/entry_points.txt,sha256=aRKOKXtuQr-idSr-yH4efnRl2v8te94AcgN3ysqqSYs,49
46
- hanzo_mcp-0.1.36.dist-info/top_level.txt,sha256=eGFANatA0MHWiVlpS56fTYRIShtibrSom1uXI6XU0GU,10
47
- hanzo_mcp-0.1.36.dist-info/RECORD,,
48
+ hanzo_mcp-0.3.1.dist-info/licenses/LICENSE,sha256=mf1qZGFsPGskoPgytp9B-RsahfKvXsBpmaAbTLGTt8Y,1063
49
+ hanzo_mcp-0.3.1.dist-info/METADATA,sha256=ay2jJEkPi-ivKlvoCRyCNj57-M0tT36tL4w5nuK_SQ0,7550
50
+ hanzo_mcp-0.3.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
51
+ hanzo_mcp-0.3.1.dist-info/entry_points.txt,sha256=aRKOKXtuQr-idSr-yH4efnRl2v8te94AcgN3ysqqSYs,49
52
+ hanzo_mcp-0.3.1.dist-info/top_level.txt,sha256=eGFANatA0MHWiVlpS56fTYRIShtibrSom1uXI6XU0GU,10
53
+ hanzo_mcp-0.3.1.dist-info/RECORD,,
@@ -1,51 +0,0 @@
1
- """Path utilities for Hanzo MCP.
2
-
3
- This module provides path normalization and validation utilities.
4
- """
5
-
6
- import os
7
- from pathlib import Path
8
- from typing import final
9
-
10
-
11
- @final
12
- class PathUtils:
13
- """Utilities for path handling."""
14
-
15
- @staticmethod
16
- def normalize_path(path: str) -> str:
17
- """Normalize a path by expanding user paths and making it absolute.
18
-
19
- Args:
20
- path: The path to normalize
21
-
22
- Returns:
23
- The normalized path
24
- """
25
- # Expand user paths (e.g., ~/ or $HOME)
26
- expanded_path = os.path.expanduser(path)
27
-
28
- # Make the path absolute if it isn't already
29
- if not os.path.isabs(expanded_path):
30
- expanded_path = os.path.abspath(expanded_path)
31
-
32
- # Normalize the path (resolve symlinks, etc.)
33
- try:
34
- normalized_path = os.path.normpath(expanded_path)
35
- return normalized_path
36
- except Exception:
37
- # Return the expanded path if normalization fails
38
- return expanded_path
39
-
40
- @staticmethod
41
- def is_dot_directory(path: Path) -> bool:
42
- """Check if a path is a dot directory (e.g., .git, .vscode).
43
-
44
- Args:
45
- path: The path to check
46
-
47
- Returns:
48
- True if the path is a dot directory, False otherwise
49
- """
50
- # Consider any directory starting with "." to be a dot directory
51
- return path.is_dir() and path.name.startswith(".")