hanzo-mcp 0.6.10__py3-none-any.whl → 0.6.13__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 (78) hide show
  1. hanzo_mcp/__init__.py +11 -2
  2. hanzo_mcp/cli.py +69 -19
  3. hanzo_mcp/cli_enhanced.py +15 -12
  4. hanzo_mcp/cli_plugin.py +91 -0
  5. hanzo_mcp/config/__init__.py +1 -1
  6. hanzo_mcp/config/settings.py +75 -8
  7. hanzo_mcp/config/tool_config.py +2 -2
  8. hanzo_mcp/dev_server.py +20 -15
  9. hanzo_mcp/prompts/project_system.py +1 -1
  10. hanzo_mcp/server.py +18 -4
  11. hanzo_mcp/server_enhanced.py +69 -0
  12. hanzo_mcp/tools/__init__.py +78 -30
  13. hanzo_mcp/tools/agent/__init__.py +1 -1
  14. hanzo_mcp/tools/agent/agent_tool.py +2 -2
  15. hanzo_mcp/tools/common/__init__.py +15 -1
  16. hanzo_mcp/tools/common/base.py +4 -4
  17. hanzo_mcp/tools/common/batch_tool.py +1 -1
  18. hanzo_mcp/tools/common/config_tool.py +2 -2
  19. hanzo_mcp/tools/common/context.py +2 -2
  20. hanzo_mcp/tools/common/context_fix.py +26 -0
  21. hanzo_mcp/tools/common/critic_tool.py +196 -0
  22. hanzo_mcp/tools/common/decorators.py +208 -0
  23. hanzo_mcp/tools/common/enhanced_base.py +106 -0
  24. hanzo_mcp/tools/common/mode.py +116 -0
  25. hanzo_mcp/tools/common/mode_loader.py +105 -0
  26. hanzo_mcp/tools/common/permissions.py +1 -1
  27. hanzo_mcp/tools/common/personality.py +936 -0
  28. hanzo_mcp/tools/common/plugin_loader.py +287 -0
  29. hanzo_mcp/tools/common/stats.py +4 -4
  30. hanzo_mcp/tools/common/tool_list.py +1 -1
  31. hanzo_mcp/tools/common/validation.py +1 -1
  32. hanzo_mcp/tools/config/__init__.py +3 -1
  33. hanzo_mcp/tools/config/config_tool.py +1 -1
  34. hanzo_mcp/tools/config/mode_tool.py +209 -0
  35. hanzo_mcp/tools/database/__init__.py +1 -1
  36. hanzo_mcp/tools/editor/__init__.py +1 -1
  37. hanzo_mcp/tools/filesystem/__init__.py +19 -14
  38. hanzo_mcp/tools/filesystem/batch_search.py +3 -3
  39. hanzo_mcp/tools/filesystem/diff.py +2 -2
  40. hanzo_mcp/tools/filesystem/rules_tool.py +235 -0
  41. hanzo_mcp/tools/filesystem/{unified_search.py → search_tool.py} +12 -12
  42. hanzo_mcp/tools/filesystem/{symbols_unified.py → symbols_tool.py} +104 -5
  43. hanzo_mcp/tools/filesystem/watch.py +3 -2
  44. hanzo_mcp/tools/jupyter/__init__.py +2 -2
  45. hanzo_mcp/tools/jupyter/jupyter.py +1 -1
  46. hanzo_mcp/tools/llm/__init__.py +3 -3
  47. hanzo_mcp/tools/llm/llm_tool.py +648 -143
  48. hanzo_mcp/tools/mcp/__init__.py +2 -2
  49. hanzo_mcp/tools/mcp/{mcp_unified.py → mcp_tool.py} +3 -3
  50. hanzo_mcp/tools/shell/__init__.py +6 -6
  51. hanzo_mcp/tools/shell/base_process.py +4 -2
  52. hanzo_mcp/tools/shell/bash_session_executor.py +8 -5
  53. hanzo_mcp/tools/shell/{bash_unified.py → bash_tool.py} +1 -1
  54. hanzo_mcp/tools/shell/command_executor.py +8 -6
  55. hanzo_mcp/tools/shell/{npx_unified.py → npx_tool.py} +1 -1
  56. hanzo_mcp/tools/shell/open.py +2 -2
  57. hanzo_mcp/tools/shell/{process_unified.py → process_tool.py} +1 -1
  58. hanzo_mcp/tools/shell/run_command_windows.py +1 -1
  59. hanzo_mcp/tools/shell/uvx.py +47 -2
  60. hanzo_mcp/tools/shell/uvx_background.py +47 -2
  61. hanzo_mcp/tools/shell/{uvx_unified.py → uvx_tool.py} +1 -1
  62. hanzo_mcp/tools/todo/__init__.py +14 -19
  63. hanzo_mcp/tools/todo/todo.py +22 -1
  64. hanzo_mcp/tools/vector/__init__.py +7 -3
  65. hanzo_mcp/tools/vector/ast_analyzer.py +12 -4
  66. hanzo_mcp/tools/vector/infinity_store.py +11 -5
  67. hanzo_mcp/tools/vector/project_manager.py +4 -2
  68. hanzo_mcp-0.6.13.dist-info/METADATA +359 -0
  69. {hanzo_mcp-0.6.10.dist-info → hanzo_mcp-0.6.13.dist-info}/RECORD +73 -65
  70. {hanzo_mcp-0.6.10.dist-info → hanzo_mcp-0.6.13.dist-info}/entry_points.txt +1 -0
  71. hanzo_mcp/tools/common/palette.py +0 -344
  72. hanzo_mcp/tools/common/palette_loader.py +0 -108
  73. hanzo_mcp/tools/config/palette_tool.py +0 -179
  74. hanzo_mcp/tools/llm/llm_unified.py +0 -851
  75. hanzo_mcp-0.6.10.dist-info/METADATA +0 -339
  76. {hanzo_mcp-0.6.10.dist-info → hanzo_mcp-0.6.13.dist-info}/WHEEL +0 -0
  77. {hanzo_mcp-0.6.10.dist-info → hanzo_mcp-0.6.13.dist-info}/licenses/LICENSE +0 -0
  78. {hanzo_mcp-0.6.10.dist-info → hanzo_mcp-0.6.13.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,208 @@
1
+ """Decorators for MCP tools.
2
+
3
+ This module provides decorators that handle common cross-cutting concerns
4
+ for MCP tools, such as context normalization and error handling.
5
+ """
6
+
7
+ import functools
8
+ import inspect
9
+ from typing import Any, Callable, TypeVar, cast
10
+ from collections.abc import Awaitable, Coroutine
11
+
12
+ from mcp.server.fastmcp import Context as MCPContext
13
+
14
+ F = TypeVar('F', bound=Callable[..., Any])
15
+
16
+
17
+ class MockContext:
18
+ """Mock context for when no real context is available.
19
+
20
+ This is used when tools are called externally through the MCP protocol
21
+ and the Context parameter is not properly serialized.
22
+ """
23
+
24
+ def __init__(self):
25
+ self.request_id = "external-request"
26
+ self.client_id = "external-client"
27
+
28
+ async def info(self, message: str) -> None:
29
+ """Mock info logging - no-op for external calls."""
30
+ pass
31
+
32
+ async def debug(self, message: str) -> None:
33
+ """Mock debug logging - no-op for external calls."""
34
+ pass
35
+
36
+ async def warning(self, message: str) -> None:
37
+ """Mock warning logging - no-op for external calls."""
38
+ pass
39
+
40
+ async def error(self, message: str) -> None:
41
+ """Mock error logging - no-op for external calls."""
42
+ pass
43
+
44
+ async def report_progress(self, current: int, total: int) -> None:
45
+ """Mock progress reporting - no-op for external calls."""
46
+ pass
47
+
48
+ async def read_resource(self, uri: str) -> Any:
49
+ """Mock resource reading - returns empty result."""
50
+ return []
51
+
52
+
53
+ def with_context_normalization(func: F) -> F:
54
+ """Decorator that normalizes the context parameter for MCP tools.
55
+
56
+ This decorator intercepts the ctx parameter and ensures it's a valid
57
+ MCPContext object, even when called externally where it might be
58
+ passed as a string, dict, or None.
59
+
60
+ Usage:
61
+ @server.tool()
62
+ @with_context_normalization
63
+ async def my_tool(ctx: MCPContext, param: str) -> str:
64
+ # ctx is guaranteed to be a valid context object
65
+ await ctx.info("Processing...")
66
+ return "result"
67
+
68
+ Args:
69
+ func: The async function to decorate
70
+
71
+ Returns:
72
+ The decorated function with context normalization
73
+ """
74
+ @functools.wraps(func)
75
+ async def wrapper(*args: Any, **kwargs: Any) -> Any:
76
+ # Get function signature to find ctx parameter
77
+ sig = inspect.signature(func)
78
+ params = list(sig.parameters.keys())
79
+
80
+ # Handle ctx in kwargs
81
+ if 'ctx' in kwargs:
82
+ ctx_value = kwargs['ctx']
83
+ if not _is_valid_context(ctx_value):
84
+ kwargs['ctx'] = MockContext()
85
+
86
+ # Handle ctx in args (positional)
87
+ elif 'ctx' in params:
88
+ ctx_index = params.index('ctx')
89
+ if ctx_index < len(args):
90
+ ctx_value = args[ctx_index]
91
+ if not _is_valid_context(ctx_value):
92
+ args_list = list(args)
93
+ args_list[ctx_index] = MockContext()
94
+ args = tuple(args_list)
95
+
96
+ # Call the original function
97
+ return await func(*args, **kwargs)
98
+
99
+ return cast(F, wrapper)
100
+
101
+
102
+ def _is_valid_context(ctx: Any) -> bool:
103
+ """Check if an object is a valid MCPContext.
104
+
105
+ Args:
106
+ ctx: The object to check
107
+
108
+ Returns:
109
+ True if ctx is a valid context object
110
+ """
111
+ # Check for required context methods
112
+ return (
113
+ hasattr(ctx, 'info') and
114
+ hasattr(ctx, 'debug') and
115
+ hasattr(ctx, 'warning') and
116
+ hasattr(ctx, 'error') and
117
+ hasattr(ctx, 'report_progress') and
118
+ # Ensure they're callable
119
+ callable(getattr(ctx, 'info', None)) and
120
+ callable(getattr(ctx, 'debug', None))
121
+ )
122
+
123
+
124
+ def mcp_tool(
125
+ server: Any,
126
+ name: str | None = None,
127
+ description: str | None = None
128
+ ) -> Callable[[F], F]:
129
+ """Enhanced MCP tool decorator that includes context normalization.
130
+
131
+ This decorator combines the standard MCP tool registration with
132
+ automatic context normalization, providing a single-point solution
133
+ for all tools.
134
+
135
+ Usage:
136
+ @mcp_tool(server, name="my_tool", description="Does something")
137
+ async def my_tool(ctx: MCPContext, param: str) -> str:
138
+ await ctx.info("Processing...")
139
+ return "result"
140
+
141
+ Args:
142
+ server: The MCP server instance
143
+ name: Optional tool name (defaults to function name)
144
+ description: Optional tool description
145
+
146
+ Returns:
147
+ Decorator function
148
+ """
149
+ def decorator(func: F) -> F:
150
+ # Apply context normalization first
151
+ normalized_func = with_context_normalization(func)
152
+
153
+ # Then apply the server's tool decorator
154
+ if hasattr(server, 'tool'):
155
+ # Use the server's tool decorator
156
+ server_decorator = server.tool(name=name, description=description)
157
+ return server_decorator(normalized_func)
158
+ else:
159
+ # Fallback if server doesn't have tool method
160
+ return normalized_func
161
+
162
+ return decorator
163
+
164
+
165
+ def create_tool_handler(server: Any, tool: Any) -> Callable[[], None]:
166
+ """Create a standardized tool registration handler.
167
+
168
+ This function creates a registration method that automatically applies
169
+ context normalization to any tool handler registered with the server.
170
+
171
+ Usage:
172
+ class MyTool(BaseTool):
173
+ def register(self, mcp_server):
174
+ register = create_tool_handler(mcp_server, self)
175
+ register()
176
+
177
+ Args:
178
+ server: The MCP server instance
179
+ tool: The tool instance with name, description, and handler
180
+
181
+ Returns:
182
+ A function that registers the tool with context normalization
183
+ """
184
+ def register_with_normalization():
185
+ # Get the original register method
186
+ original_register = tool.__class__.register
187
+
188
+ # Temporarily replace server.tool to wrap with normalization
189
+ original_tool_decorator = server.tool
190
+
191
+ def normalized_tool_decorator(name=None, description=None):
192
+ def decorator(func):
193
+ # Apply context normalization
194
+ normalized = with_context_normalization(func)
195
+ # Apply original decorator
196
+ return original_tool_decorator(name=name, description=description)(normalized)
197
+ return decorator
198
+
199
+ # Monkey-patch temporarily
200
+ server.tool = normalized_tool_decorator
201
+ try:
202
+ # Call original register
203
+ original_register(tool, server)
204
+ finally:
205
+ # Restore original
206
+ server.tool = original_tool_decorator
207
+
208
+ return register_with_normalization
@@ -0,0 +1,106 @@
1
+ """Enhanced base classes for MCP tools with automatic context handling.
2
+
3
+ This module provides enhanced base classes that automatically handle
4
+ context normalization and other cross-cutting concerns, ensuring
5
+ consistent behavior across all tools.
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+ from typing import Any, get_type_hints, get_args, get_origin
10
+ import inspect
11
+
12
+ from mcp.server import FastMCP
13
+ from mcp.server.fastmcp import Context as MCPContext
14
+
15
+ from hanzo_mcp.tools.common.base import BaseTool
16
+ from hanzo_mcp.tools.common.decorators import with_context_normalization
17
+
18
+
19
+ class EnhancedBaseTool(BaseTool, ABC):
20
+ """Enhanced base class for MCP tools with automatic context normalization.
21
+
22
+ This base class automatically wraps the tool registration to include
23
+ context normalization, ensuring that all tools handle external calls
24
+ properly without requiring manual decoration or copy-pasted code.
25
+ """
26
+
27
+ def register(self, mcp_server: FastMCP) -> None:
28
+ """Register this tool with automatic context normalization.
29
+
30
+ This method automatically applies context normalization to the tool
31
+ handler, ensuring it works properly when called externally.
32
+
33
+ Args:
34
+ mcp_server: The FastMCP server instance
35
+ """
36
+ # Get the tool method from the subclass
37
+ tool_method = self._create_tool_handler()
38
+
39
+ # Apply context normalization decorator
40
+ normalized_method = with_context_normalization(tool_method)
41
+
42
+ # Register with the server
43
+ mcp_server.tool(
44
+ name=self.name,
45
+ description=self.description
46
+ )(normalized_method)
47
+
48
+ @abstractmethod
49
+ def _create_tool_handler(self) -> Any:
50
+ """Create the tool handler function.
51
+
52
+ Subclasses must implement this to return an async function
53
+ that will be registered as the tool handler.
54
+
55
+ Returns:
56
+ An async function that handles tool calls
57
+ """
58
+ pass
59
+
60
+
61
+ class AutoRegisterTool(BaseTool, ABC):
62
+ """Base class that automatically generates tool handlers from the call method.
63
+
64
+ This base class inspects the call method signature and automatically
65
+ creates a properly typed tool handler with context normalization.
66
+ """
67
+
68
+ def register(self, mcp_server: FastMCP) -> None:
69
+ """Register this tool with automatic handler generation.
70
+
71
+ This method inspects the call method signature and automatically
72
+ creates a tool handler with the correct parameters and types.
73
+
74
+ Args:
75
+ mcp_server: The FastMCP server instance
76
+ """
77
+ # Get the call method signature
78
+ call_method = self.call
79
+ sig = inspect.signature(call_method)
80
+
81
+ # Get type hints for proper typing
82
+ hints = get_type_hints(call_method)
83
+
84
+ # Create a dynamic handler function
85
+ tool_self = self
86
+
87
+ # Build the handler dynamically based on the call signature
88
+ params = list(sig.parameters.items())
89
+
90
+ # Skip 'self' and 'ctx' parameters
91
+ tool_params = [(name, param) for name, param in params
92
+ if name not in ('self', 'ctx')]
93
+
94
+ # Create the handler function dynamically
95
+ async def handler(ctx: MCPContext, **kwargs: Any) -> Any:
96
+ # Call the tool's call method with the context and parameters
97
+ return await tool_self.call(ctx, **kwargs)
98
+
99
+ # Apply context normalization
100
+ normalized_handler = with_context_normalization(handler)
101
+
102
+ # Register with the server
103
+ mcp_server.tool(
104
+ name=self.name,
105
+ description=self.description
106
+ )(normalized_handler)
@@ -0,0 +1,116 @@
1
+ """Mode system for organizing development tools based on programmer personalities."""
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+ from typing import Dict, List, Optional, Set
6
+
7
+ from hanzo_mcp.tools.common.personality import (
8
+ ToolPersonality,
9
+ register_default_personalities,
10
+ ensure_agent_enabled,
11
+ personalities
12
+ )
13
+
14
+
15
+ @dataclass
16
+ class Mode(ToolPersonality):
17
+ """Development mode combining tool preferences and environment settings."""
18
+ # Inherits all fields from ToolPersonality
19
+ # Adds mode-specific functionality
20
+
21
+ @property
22
+ def is_active(self) -> bool:
23
+ """Check if this mode is currently active."""
24
+ return ModeRegistry.get_active() == self
25
+
26
+
27
+ class ModeRegistry:
28
+ """Registry for development modes."""
29
+
30
+ _modes: Dict[str, Mode] = {}
31
+ _active_mode: Optional[str] = None
32
+
33
+ @classmethod
34
+ def register(cls, mode: Mode) -> None:
35
+ """Register a development mode."""
36
+ # Ensure agent is enabled if API keys present
37
+ mode = ensure_agent_enabled(mode)
38
+ cls._modes[mode.name] = mode
39
+
40
+ @classmethod
41
+ def get(cls, name: str) -> Optional[Mode]:
42
+ """Get a mode by name."""
43
+ return cls._modes.get(name)
44
+
45
+ @classmethod
46
+ def list(cls) -> List[Mode]:
47
+ """List all registered modes."""
48
+ return list(cls._modes.values())
49
+
50
+ @classmethod
51
+ def set_active(cls, name: str) -> None:
52
+ """Set the active mode."""
53
+ if name not in cls._modes:
54
+ raise ValueError(f"Mode '{name}' not found")
55
+ cls._active_mode = name
56
+
57
+ # Apply environment variables from the mode
58
+ mode = cls._modes[name]
59
+ if mode.environment:
60
+ for key, value in mode.environment.items():
61
+ os.environ[key] = value
62
+
63
+ @classmethod
64
+ def get_active(cls) -> Optional[Mode]:
65
+ """Get the active mode."""
66
+ if cls._active_mode:
67
+ return cls._modes.get(cls._active_mode)
68
+ return None
69
+
70
+ @classmethod
71
+ def get_active_tools(cls) -> Set[str]:
72
+ """Get the set of tools from the active mode."""
73
+ mode = cls.get_active()
74
+ if mode:
75
+ return set(mode.tools)
76
+ return set()
77
+
78
+
79
+ def register_default_modes():
80
+ """Register all default development modes."""
81
+ # Convert personalities to modes
82
+ for personality in personalities:
83
+ mode = Mode(
84
+ name=personality.name,
85
+ programmer=personality.programmer,
86
+ description=personality.description,
87
+ tools=personality.tools,
88
+ environment=personality.environment,
89
+ philosophy=personality.philosophy,
90
+ )
91
+ ModeRegistry.register(mode)
92
+
93
+
94
+ def get_mode_from_env() -> Optional[str]:
95
+ """Get mode name from environment variables."""
96
+ # Check for HANZO_MODE, PERSONALITY, or MODE env vars
97
+ return (
98
+ os.environ.get("HANZO_MODE") or
99
+ os.environ.get("PERSONALITY") or
100
+ os.environ.get("MODE")
101
+ )
102
+
103
+
104
+ def activate_mode_from_env():
105
+ """Activate mode based on environment variables."""
106
+ mode_name = get_mode_from_env()
107
+ if mode_name:
108
+ try:
109
+ ModeRegistry.set_active(mode_name)
110
+ return True
111
+ except ValueError:
112
+ # Mode not found, ignore
113
+ pass
114
+ return False
115
+
116
+
@@ -0,0 +1,105 @@
1
+ """Tool mode loader for dynamic tool configuration."""
2
+
3
+ import os
4
+ from typing import Dict, List, Optional, Set
5
+
6
+ from hanzo_mcp.tools.common.mode import ModeRegistry, register_default_modes, activate_mode_from_env
7
+
8
+
9
+ class ModeLoader:
10
+ """Loads and manages tool modes for dynamic configuration."""
11
+
12
+ @staticmethod
13
+ def initialize_modes() -> None:
14
+ """Initialize the mode system with defaults."""
15
+ # Initialize modes
16
+ register_default_modes()
17
+
18
+ # Check for mode from environment
19
+ activate_mode_from_env()
20
+
21
+ # If no mode set, use default
22
+ if not ModeRegistry.get_active():
23
+ default_mode = os.environ.get("HANZO_DEFAULT_MODE", "hanzo")
24
+ if ModeRegistry.get(default_mode):
25
+ ModeRegistry.set_active(default_mode)
26
+
27
+ @staticmethod
28
+ def get_enabled_tools_from_mode(
29
+ base_enabled_tools: Optional[Dict[str, bool]] = None,
30
+ force_mode: Optional[str] = None
31
+ ) -> Dict[str, bool]:
32
+ """Get enabled tools configuration from active mode.
33
+
34
+ Args:
35
+ base_enabled_tools: Base configuration to merge with
36
+ force_mode: Force a specific mode (overrides active)
37
+
38
+ Returns:
39
+ Dictionary of tool enable states
40
+ """
41
+ # Initialize if needed
42
+ if not ModeRegistry.list():
43
+ ModeLoader.initialize_modes()
44
+
45
+ # Get mode to use
46
+ tools_list = None
47
+
48
+ if force_mode:
49
+ # Set and get mode
50
+ if ModeRegistry.get(force_mode):
51
+ ModeRegistry.set_active(force_mode)
52
+ mode = ModeRegistry.get_active()
53
+ tools_list = mode.tools if mode else None
54
+ else:
55
+ # Check active mode
56
+ mode = ModeRegistry.get_active()
57
+ if mode:
58
+ tools_list = mode.tools
59
+
60
+ if not tools_list:
61
+ # No active mode, return base config
62
+ return base_enabled_tools or {}
63
+
64
+ # Start with base configuration
65
+ result = base_enabled_tools.copy() if base_enabled_tools else {}
66
+
67
+ # Get all possible tools from registry
68
+ from hanzo_mcp.config.tool_config import TOOL_REGISTRY
69
+ all_possible_tools = set(TOOL_REGISTRY.keys())
70
+
71
+ # Disable all tools first (clean slate for mode)
72
+ for tool in all_possible_tools:
73
+ result[tool] = False
74
+
75
+ # Enable tools from mode
76
+ for tool in tools_list:
77
+ result[tool] = True
78
+
79
+ # Always enable mode tool (meta)
80
+ result["mode"] = True
81
+
82
+ return result
83
+
84
+ @staticmethod
85
+ def get_environment_from_mode() -> Dict[str, str]:
86
+ """Get environment variables from active mode.
87
+
88
+ Returns:
89
+ Dictionary of environment variables
90
+ """
91
+ # Check mode
92
+ mode = ModeRegistry.get_active()
93
+ if mode and mode.environment:
94
+ return mode.environment.copy()
95
+
96
+ return {}
97
+
98
+ @staticmethod
99
+ def apply_environment_from_mode() -> None:
100
+ """Apply environment variables from active mode."""
101
+ env_vars = ModeLoader.get_environment_from_mode()
102
+ for key, value in env_vars.items():
103
+ os.environ[key] = value
104
+
105
+
@@ -1,4 +1,4 @@
1
- """Permission system for the Hanzo MCP server."""
1
+ """Permission system for the Hanzo AI server."""
2
2
 
3
3
  import json
4
4
  import os