hanzo-mcp 0.6.12__py3-none-any.whl → 0.7.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 +2 -2
- hanzo_mcp/analytics/__init__.py +5 -0
- hanzo_mcp/analytics/posthog_analytics.py +364 -0
- hanzo_mcp/cli.py +5 -5
- hanzo_mcp/cli_enhanced.py +7 -7
- hanzo_mcp/cli_plugin.py +91 -0
- hanzo_mcp/config/__init__.py +1 -1
- hanzo_mcp/config/settings.py +70 -7
- hanzo_mcp/config/tool_config.py +20 -6
- hanzo_mcp/dev_server.py +3 -3
- hanzo_mcp/prompts/project_system.py +1 -1
- hanzo_mcp/server.py +40 -3
- hanzo_mcp/server_enhanced.py +69 -0
- hanzo_mcp/tools/__init__.py +140 -31
- hanzo_mcp/tools/agent/__init__.py +85 -4
- hanzo_mcp/tools/agent/agent_tool.py +104 -6
- hanzo_mcp/tools/agent/agent_tool_v2.py +459 -0
- hanzo_mcp/tools/agent/clarification_protocol.py +220 -0
- hanzo_mcp/tools/agent/clarification_tool.py +68 -0
- hanzo_mcp/tools/agent/claude_cli_tool.py +125 -0
- hanzo_mcp/tools/agent/claude_desktop_auth.py +508 -0
- hanzo_mcp/tools/agent/cli_agent_base.py +191 -0
- hanzo_mcp/tools/agent/code_auth.py +436 -0
- hanzo_mcp/tools/agent/code_auth_tool.py +194 -0
- hanzo_mcp/tools/agent/codex_cli_tool.py +123 -0
- hanzo_mcp/tools/agent/critic_tool.py +376 -0
- hanzo_mcp/tools/agent/gemini_cli_tool.py +128 -0
- hanzo_mcp/tools/agent/grok_cli_tool.py +128 -0
- hanzo_mcp/tools/agent/iching_tool.py +380 -0
- hanzo_mcp/tools/agent/network_tool.py +273 -0
- hanzo_mcp/tools/agent/prompt.py +62 -20
- hanzo_mcp/tools/agent/review_tool.py +433 -0
- hanzo_mcp/tools/agent/swarm_tool.py +535 -0
- hanzo_mcp/tools/agent/swarm_tool_v2.py +594 -0
- hanzo_mcp/tools/common/__init__.py +15 -1
- hanzo_mcp/tools/common/base.py +5 -4
- hanzo_mcp/tools/common/batch_tool.py +103 -11
- hanzo_mcp/tools/common/config_tool.py +2 -2
- hanzo_mcp/tools/common/context.py +2 -2
- hanzo_mcp/tools/common/context_fix.py +26 -0
- hanzo_mcp/tools/common/critic_tool.py +196 -0
- hanzo_mcp/tools/common/decorators.py +208 -0
- hanzo_mcp/tools/common/enhanced_base.py +106 -0
- hanzo_mcp/tools/common/fastmcp_pagination.py +369 -0
- hanzo_mcp/tools/common/forgiving_edit.py +243 -0
- hanzo_mcp/tools/common/mode.py +116 -0
- hanzo_mcp/tools/common/mode_loader.py +105 -0
- hanzo_mcp/tools/common/paginated_base.py +230 -0
- hanzo_mcp/tools/common/paginated_response.py +307 -0
- hanzo_mcp/tools/common/pagination.py +226 -0
- hanzo_mcp/tools/common/permissions.py +1 -1
- hanzo_mcp/tools/common/personality.py +936 -0
- hanzo_mcp/tools/common/plugin_loader.py +287 -0
- hanzo_mcp/tools/common/stats.py +4 -4
- hanzo_mcp/tools/common/tool_list.py +4 -1
- hanzo_mcp/tools/common/truncate.py +101 -0
- hanzo_mcp/tools/common/validation.py +1 -1
- hanzo_mcp/tools/config/__init__.py +3 -1
- hanzo_mcp/tools/config/config_tool.py +1 -1
- hanzo_mcp/tools/config/mode_tool.py +209 -0
- hanzo_mcp/tools/database/__init__.py +1 -1
- hanzo_mcp/tools/editor/__init__.py +1 -1
- hanzo_mcp/tools/filesystem/__init__.py +48 -14
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +562 -0
- hanzo_mcp/tools/filesystem/batch_search.py +3 -3
- hanzo_mcp/tools/filesystem/diff.py +2 -2
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +338 -0
- hanzo_mcp/tools/filesystem/rules_tool.py +235 -0
- hanzo_mcp/tools/filesystem/{unified_search.py → search_tool.py} +12 -12
- hanzo_mcp/tools/filesystem/{symbols_unified.py → symbols_tool.py} +104 -5
- hanzo_mcp/tools/filesystem/watch.py +3 -2
- hanzo_mcp/tools/jupyter/__init__.py +2 -2
- hanzo_mcp/tools/jupyter/jupyter.py +1 -1
- hanzo_mcp/tools/llm/__init__.py +3 -3
- hanzo_mcp/tools/llm/llm_tool.py +648 -143
- hanzo_mcp/tools/lsp/__init__.py +5 -0
- hanzo_mcp/tools/lsp/lsp_tool.py +512 -0
- hanzo_mcp/tools/mcp/__init__.py +2 -2
- hanzo_mcp/tools/mcp/{mcp_unified.py → mcp_tool.py} +3 -3
- hanzo_mcp/tools/memory/__init__.py +76 -0
- hanzo_mcp/tools/memory/knowledge_tools.py +518 -0
- hanzo_mcp/tools/memory/memory_tools.py +456 -0
- hanzo_mcp/tools/search/__init__.py +6 -0
- hanzo_mcp/tools/search/find_tool.py +581 -0
- hanzo_mcp/tools/search/unified_search.py +953 -0
- hanzo_mcp/tools/shell/__init__.py +11 -6
- hanzo_mcp/tools/shell/auto_background.py +203 -0
- hanzo_mcp/tools/shell/base_process.py +57 -29
- hanzo_mcp/tools/shell/bash_session_executor.py +1 -1
- hanzo_mcp/tools/shell/{bash_unified.py → bash_tool.py} +18 -34
- hanzo_mcp/tools/shell/command_executor.py +2 -2
- hanzo_mcp/tools/shell/{npx_unified.py → npx_tool.py} +16 -33
- hanzo_mcp/tools/shell/open.py +2 -2
- hanzo_mcp/tools/shell/{process_unified.py → process_tool.py} +1 -1
- hanzo_mcp/tools/shell/run_command_windows.py +1 -1
- hanzo_mcp/tools/shell/streaming_command.py +594 -0
- hanzo_mcp/tools/shell/uvx.py +47 -2
- hanzo_mcp/tools/shell/uvx_background.py +47 -2
- hanzo_mcp/tools/shell/{uvx_unified.py → uvx_tool.py} +16 -33
- hanzo_mcp/tools/todo/__init__.py +14 -19
- hanzo_mcp/tools/todo/todo.py +22 -1
- hanzo_mcp/tools/vector/__init__.py +1 -1
- hanzo_mcp/tools/vector/infinity_store.py +2 -2
- hanzo_mcp/tools/vector/project_manager.py +1 -1
- hanzo_mcp/types.py +23 -0
- hanzo_mcp-0.7.0.dist-info/METADATA +516 -0
- hanzo_mcp-0.7.0.dist-info/RECORD +180 -0
- {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.7.0.dist-info}/entry_points.txt +1 -0
- hanzo_mcp/tools/common/palette.py +0 -344
- hanzo_mcp/tools/common/palette_loader.py +0 -108
- hanzo_mcp/tools/config/palette_tool.py +0 -179
- hanzo_mcp/tools/llm/llm_unified.py +0 -851
- hanzo_mcp-0.6.12.dist-info/METADATA +0 -339
- hanzo_mcp-0.6.12.dist-info/RECORD +0 -135
- hanzo_mcp-0.6.12.dist-info/licenses/LICENSE +0 -21
- {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.7.0.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.6.12.dist-info → hanzo_mcp-0.7.0.dist-info}/top_level.txt +0 -0
hanzo_mcp/config/settings.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Settings management for Hanzo
|
|
1
|
+
"""Settings management for Hanzo AI.
|
|
2
2
|
|
|
3
3
|
Handles loading and saving configuration from multiple sources:
|
|
4
4
|
1. Default settings
|
|
@@ -59,6 +59,7 @@ class AgentConfig:
|
|
|
59
59
|
enabled: bool = False
|
|
60
60
|
model: Optional[str] = None
|
|
61
61
|
api_key: Optional[str] = None
|
|
62
|
+
hanzo_api_key: Optional[str] = None # HANZO_API_KEY support
|
|
62
63
|
base_url: Optional[str] = None
|
|
63
64
|
max_tokens: Optional[int] = None
|
|
64
65
|
max_iterations: int = 10
|
|
@@ -70,7 +71,7 @@ class ServerConfig:
|
|
|
70
71
|
"""Configuration for the MCP server."""
|
|
71
72
|
name: str = "hanzo-mcp"
|
|
72
73
|
host: str = "127.0.0.1"
|
|
73
|
-
port: int =
|
|
74
|
+
port: int = 8888
|
|
74
75
|
transport: str = "stdio" # stdio or sse
|
|
75
76
|
log_level: str = "INFO"
|
|
76
77
|
command_timeout: float = 120.0
|
|
@@ -78,7 +79,7 @@ class ServerConfig:
|
|
|
78
79
|
|
|
79
80
|
@dataclass
|
|
80
81
|
class HanzoMCPSettings:
|
|
81
|
-
"""Complete configuration for Hanzo
|
|
82
|
+
"""Complete configuration for Hanzo AI."""
|
|
82
83
|
# Server settings
|
|
83
84
|
server: ServerConfig = field(default_factory=ServerConfig)
|
|
84
85
|
|
|
@@ -106,6 +107,9 @@ class HanzoMCPSettings:
|
|
|
106
107
|
projects: Dict[str, ProjectConfig] = field(default_factory=dict)
|
|
107
108
|
current_project: Optional[str] = None
|
|
108
109
|
|
|
110
|
+
# Mode configuration
|
|
111
|
+
active_mode: Optional[str] = None
|
|
112
|
+
|
|
109
113
|
def __post_init__(self):
|
|
110
114
|
"""Initialize default tool states if not specified."""
|
|
111
115
|
if not self.enabled_tools:
|
|
@@ -250,7 +254,7 @@ class HanzoMCPSettings:
|
|
|
250
254
|
|
|
251
255
|
|
|
252
256
|
def get_config_dir() -> Path:
|
|
253
|
-
"""Get the configuration directory for Hanzo
|
|
257
|
+
"""Get the configuration directory for Hanzo AI."""
|
|
254
258
|
if os.name == "nt": # Windows
|
|
255
259
|
config_dir = Path(os.environ.get("APPDATA", "")) / "hanzo"
|
|
256
260
|
else: # Unix/macOS
|
|
@@ -341,6 +345,59 @@ def detect_project_from_path(file_path: str) -> Optional[Dict[str, str]]:
|
|
|
341
345
|
return None
|
|
342
346
|
|
|
343
347
|
|
|
348
|
+
def _load_from_env() -> Dict[str, Any]:
|
|
349
|
+
"""Load configuration from environment variables."""
|
|
350
|
+
config = {}
|
|
351
|
+
|
|
352
|
+
# Check for agent API keys
|
|
353
|
+
has_api_keys = False
|
|
354
|
+
|
|
355
|
+
# HANZO_API_KEY
|
|
356
|
+
if hanzo_key := os.environ.get("HANZO_API_KEY"):
|
|
357
|
+
config.setdefault("agent", {})["hanzo_api_key"] = hanzo_key
|
|
358
|
+
config["agent"]["enabled"] = True
|
|
359
|
+
has_api_keys = True
|
|
360
|
+
|
|
361
|
+
# Check for other API keys
|
|
362
|
+
api_key_env_vars = [
|
|
363
|
+
("OPENAI_API_KEY", "openai"),
|
|
364
|
+
("ANTHROPIC_API_KEY", "anthropic"),
|
|
365
|
+
("GOOGLE_API_KEY", "google"),
|
|
366
|
+
("GROQ_API_KEY", "groq"),
|
|
367
|
+
("TOGETHER_API_KEY", "together"),
|
|
368
|
+
("MISTRAL_API_KEY", "mistral"),
|
|
369
|
+
("PERPLEXITY_API_KEY", "perplexity"),
|
|
370
|
+
]
|
|
371
|
+
|
|
372
|
+
for env_var, provider in api_key_env_vars:
|
|
373
|
+
if os.environ.get(env_var):
|
|
374
|
+
has_api_keys = True
|
|
375
|
+
break
|
|
376
|
+
|
|
377
|
+
# Auto-enable agent and consensus tools if API keys present
|
|
378
|
+
if has_api_keys:
|
|
379
|
+
config.setdefault("enabled_tools", {})
|
|
380
|
+
config["enabled_tools"]["agent"] = True
|
|
381
|
+
config["enabled_tools"]["consensus"] = True
|
|
382
|
+
config.setdefault("agent", {})["enabled"] = True
|
|
383
|
+
|
|
384
|
+
# Check for MODE/PERSONALITY/HANZO_MODE
|
|
385
|
+
if mode := os.environ.get("HANZO_MODE") or os.environ.get("PERSONALITY") or os.environ.get("MODE"):
|
|
386
|
+
config["active_mode"] = mode
|
|
387
|
+
|
|
388
|
+
# Check for other environment overrides
|
|
389
|
+
if project_dir := os.environ.get("HANZO_PROJECT_DIR"):
|
|
390
|
+
config["project_dir"] = project_dir
|
|
391
|
+
|
|
392
|
+
if log_level := os.environ.get("HANZO_LOG_LEVEL"):
|
|
393
|
+
config.setdefault("server", {})["log_level"] = log_level
|
|
394
|
+
|
|
395
|
+
if allowed_paths := os.environ.get("HANZO_ALLOWED_PATHS"):
|
|
396
|
+
config["allowed_paths"] = allowed_paths.split(":")
|
|
397
|
+
|
|
398
|
+
return config
|
|
399
|
+
|
|
400
|
+
|
|
344
401
|
def load_settings(
|
|
345
402
|
project_dir: Optional[str] = None,
|
|
346
403
|
config_overrides: Optional[Dict[str, Any]] = None
|
|
@@ -349,9 +406,10 @@ def load_settings(
|
|
|
349
406
|
|
|
350
407
|
Priority (highest to lowest):
|
|
351
408
|
1. config_overrides (usually from CLI)
|
|
352
|
-
2.
|
|
353
|
-
3.
|
|
354
|
-
4.
|
|
409
|
+
2. Environment variables
|
|
410
|
+
3. Project-specific config file
|
|
411
|
+
4. Global config file
|
|
412
|
+
5. Defaults
|
|
355
413
|
"""
|
|
356
414
|
# Start with defaults
|
|
357
415
|
settings = HanzoMCPSettings()
|
|
@@ -380,6 +438,11 @@ def load_settings(
|
|
|
380
438
|
logger = logging.getLogger(__name__)
|
|
381
439
|
logger.warning(f"Failed to load project config: {e}")
|
|
382
440
|
|
|
441
|
+
# Apply environment variables
|
|
442
|
+
env_config = _load_from_env()
|
|
443
|
+
if env_config:
|
|
444
|
+
settings = _merge_config(settings, env_config)
|
|
445
|
+
|
|
383
446
|
# Apply CLI overrides
|
|
384
447
|
if config_overrides:
|
|
385
448
|
settings = _merge_config(settings, config_overrides)
|
hanzo_mcp/config/tool_config.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Tool configuration definitions for Hanzo
|
|
1
|
+
"""Tool configuration definitions for Hanzo AI."""
|
|
2
2
|
|
|
3
3
|
from enum import Enum
|
|
4
4
|
from typing import Dict, List, Optional
|
|
@@ -6,7 +6,7 @@ from dataclasses import dataclass
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class ToolCategory(str, Enum):
|
|
9
|
-
"""Categories of tools available in Hanzo
|
|
9
|
+
"""Categories of tools available in Hanzo AI."""
|
|
10
10
|
FILESYSTEM = "filesystem"
|
|
11
11
|
SHELL = "shell"
|
|
12
12
|
JUPYTER = "jupyter"
|
|
@@ -71,11 +71,11 @@ TOOL_REGISTRY: Dict[str, ToolConfig] = {
|
|
|
71
71
|
description="Fast content search using ripgrep or fallback Python implementation",
|
|
72
72
|
cli_flag="--disable-grep"
|
|
73
73
|
),
|
|
74
|
-
"
|
|
75
|
-
name="
|
|
74
|
+
"ast": ToolConfig(
|
|
75
|
+
name="ast",
|
|
76
76
|
category=ToolCategory.FILESYSTEM,
|
|
77
77
|
description="Search source code with AST context using tree-sitter",
|
|
78
|
-
cli_flag="--disable-
|
|
78
|
+
cli_flag="--disable-ast"
|
|
79
79
|
),
|
|
80
80
|
"content_replace": ToolConfig(
|
|
81
81
|
name="content_replace",
|
|
@@ -120,7 +120,7 @@ TOOL_REGISTRY: Dict[str, ToolConfig] = {
|
|
|
120
120
|
cli_flag="--disable-todo-write"
|
|
121
121
|
),
|
|
122
122
|
|
|
123
|
-
# Agent Tools (
|
|
123
|
+
# Agent Tools (3)
|
|
124
124
|
"dispatch_agent": ToolConfig(
|
|
125
125
|
name="dispatch_agent",
|
|
126
126
|
category=ToolCategory.AGENT,
|
|
@@ -128,6 +128,20 @@ TOOL_REGISTRY: Dict[str, ToolConfig] = {
|
|
|
128
128
|
description="Delegate tasks to sub-agents for concurrent/specialized processing",
|
|
129
129
|
cli_flag="--enable-dispatch-agent"
|
|
130
130
|
),
|
|
131
|
+
"swarm": ToolConfig(
|
|
132
|
+
name="swarm",
|
|
133
|
+
category=ToolCategory.AGENT,
|
|
134
|
+
enabled=False, # Disabled by default
|
|
135
|
+
description="Execute multiple agent tasks in parallel across different files",
|
|
136
|
+
cli_flag="--enable-swarm"
|
|
137
|
+
),
|
|
138
|
+
"hierarchical_swarm": ToolConfig(
|
|
139
|
+
name="hierarchical_swarm",
|
|
140
|
+
category=ToolCategory.AGENT,
|
|
141
|
+
enabled=False, # Disabled by default
|
|
142
|
+
description="Execute hierarchical agent swarms with Claude Code integration",
|
|
143
|
+
cli_flag="--enable-hierarchical-swarm"
|
|
144
|
+
),
|
|
131
145
|
|
|
132
146
|
# Common Tools (3)
|
|
133
147
|
"think": ToolConfig(
|
hanzo_mcp/dev_server.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Development server with hot reload for Hanzo
|
|
1
|
+
"""Development server with hot reload for Hanzo AI."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
@@ -159,7 +159,7 @@ class DevServer:
|
|
|
159
159
|
self.running = True
|
|
160
160
|
|
|
161
161
|
logger = logging.getLogger(__name__)
|
|
162
|
-
logger.info(f"\n🚀 Starting Hanzo
|
|
162
|
+
logger.info(f"\n🚀 Starting Hanzo AI in development mode...")
|
|
163
163
|
logger.info(f"🔧 Hot reload enabled - watching for file changes")
|
|
164
164
|
logger.info(f"📁 Project: {self.project_dir or 'current directory'}")
|
|
165
165
|
logger.info(f"🌐 Transport: {transport}\n")
|
|
@@ -194,7 +194,7 @@ def run_dev_server():
|
|
|
194
194
|
"""Entry point for development server."""
|
|
195
195
|
import argparse
|
|
196
196
|
|
|
197
|
-
parser = argparse.ArgumentParser(description="Run Hanzo
|
|
197
|
+
parser = argparse.ArgumentParser(description="Run Hanzo AI in development mode with hot reload")
|
|
198
198
|
parser.add_argument(
|
|
199
199
|
"--name",
|
|
200
200
|
type=str,
|
|
@@ -30,7 +30,7 @@ Recent commits:
|
|
|
30
30
|
</project_info>
|
|
31
31
|
|
|
32
32
|
<available_tools>
|
|
33
|
-
Hanzo
|
|
33
|
+
Hanzo AI provides 65+ tools organized by category. Key tools include:
|
|
34
34
|
|
|
35
35
|
# File Operations
|
|
36
36
|
- read, write, edit, multi_edit: File manipulation
|
hanzo_mcp/server.py
CHANGED
|
@@ -16,6 +16,9 @@ except ImportError:
|
|
|
16
16
|
# Fallback for older MCP versions
|
|
17
17
|
from mcp.server import FastMCP
|
|
18
18
|
|
|
19
|
+
# Import our enhanced server
|
|
20
|
+
from hanzo_mcp.server_enhanced import EnhancedFastMCP
|
|
21
|
+
|
|
19
22
|
from hanzo_mcp.prompts import register_all_prompts
|
|
20
23
|
from hanzo_mcp.tools import register_all_tools
|
|
21
24
|
|
|
@@ -45,11 +48,11 @@ class HanzoMCPServer:
|
|
|
45
48
|
disable_write_tools: bool = False,
|
|
46
49
|
disable_search_tools: bool = False,
|
|
47
50
|
host: str = "127.0.0.1",
|
|
48
|
-
port: int =
|
|
51
|
+
port: int = 8888,
|
|
49
52
|
enabled_tools: dict[str, bool] | None = None,
|
|
50
53
|
disabled_tools: list[str] | None = None,
|
|
51
54
|
):
|
|
52
|
-
"""Initialize the Hanzo
|
|
55
|
+
"""Initialize the Hanzo AI server.
|
|
53
56
|
|
|
54
57
|
Args:
|
|
55
58
|
name: The name of the server
|
|
@@ -72,7 +75,8 @@ class HanzoMCPServer:
|
|
|
72
75
|
enabled_tools: Dictionary of individual tool enable states (default: None)
|
|
73
76
|
disabled_tools: List of tool names to disable (default: None)
|
|
74
77
|
"""
|
|
75
|
-
|
|
78
|
+
# Use enhanced server for automatic context normalization
|
|
79
|
+
self.mcp = mcp_instance if mcp_instance is not None else EnhancedFastMCP(name)
|
|
76
80
|
|
|
77
81
|
# Initialize permissions and command executor
|
|
78
82
|
self.permission_manager = PermissionManager()
|
|
@@ -222,3 +226,36 @@ class HanzoMCPServer:
|
|
|
222
226
|
# Run the server
|
|
223
227
|
transport_type = cast(Literal["stdio", "sse"], transport)
|
|
224
228
|
self.mcp.run(transport=transport_type)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def create_server(
|
|
232
|
+
name: str = "hanzo-mcp",
|
|
233
|
+
allowed_paths: list[str] | None = None,
|
|
234
|
+
enable_all_tools: bool = False,
|
|
235
|
+
**kwargs
|
|
236
|
+
) -> HanzoMCPServer:
|
|
237
|
+
"""Create a Hanzo MCP server instance.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
name: Server name
|
|
241
|
+
allowed_paths: List of allowed file paths
|
|
242
|
+
enable_all_tools: Enable all tools including agent tools
|
|
243
|
+
**kwargs: Additional server configuration
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
HanzoMCPServer instance
|
|
247
|
+
"""
|
|
248
|
+
if enable_all_tools:
|
|
249
|
+
kwargs['enable_agent_tool'] = True
|
|
250
|
+
|
|
251
|
+
return HanzoMCPServer(
|
|
252
|
+
name=name,
|
|
253
|
+
allowed_paths=allowed_paths,
|
|
254
|
+
**kwargs
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def main():
|
|
259
|
+
"""Main entry point for the server."""
|
|
260
|
+
from hanzo_mcp.cli import main as cli_main
|
|
261
|
+
cli_main()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Enhanced MCP server with automatic context normalization.
|
|
2
|
+
|
|
3
|
+
This module provides an enhanced FastMCP server that automatically
|
|
4
|
+
applies context normalization to all registered tools.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
from functools import wraps
|
|
9
|
+
|
|
10
|
+
from mcp.server import FastMCP
|
|
11
|
+
|
|
12
|
+
from hanzo_mcp.tools.common.decorators import with_context_normalization
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class EnhancedFastMCP(FastMCP):
|
|
16
|
+
"""Enhanced FastMCP server with automatic context normalization.
|
|
17
|
+
|
|
18
|
+
This server automatically wraps all tool registrations with context
|
|
19
|
+
normalization, ensuring that tools work properly when called externally
|
|
20
|
+
with serialized context parameters.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def tool(
|
|
24
|
+
self,
|
|
25
|
+
name: str | None = None,
|
|
26
|
+
description: str | None = None
|
|
27
|
+
) -> Callable:
|
|
28
|
+
"""Enhanced tool decorator that includes automatic context normalization.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
name: Tool name (defaults to function name)
|
|
32
|
+
description: Tool description
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Decorator function that registers the tool with context normalization
|
|
36
|
+
"""
|
|
37
|
+
# Get the original decorator from parent class
|
|
38
|
+
original_decorator = super().tool(
|
|
39
|
+
name=name,
|
|
40
|
+
description=description
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Create our enhanced decorator
|
|
44
|
+
def enhanced_decorator(func: Callable) -> Callable:
|
|
45
|
+
# Apply context normalization first
|
|
46
|
+
# Check if function has ctx parameter
|
|
47
|
+
import inspect
|
|
48
|
+
sig = inspect.signature(func)
|
|
49
|
+
if 'ctx' in sig.parameters:
|
|
50
|
+
normalized_func = with_context_normalization(func)
|
|
51
|
+
else:
|
|
52
|
+
normalized_func = func
|
|
53
|
+
|
|
54
|
+
# Then apply the original decorator
|
|
55
|
+
return original_decorator(normalized_func)
|
|
56
|
+
|
|
57
|
+
return enhanced_decorator
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def create_enhanced_server(name: str = "hanzo") -> EnhancedFastMCP:
|
|
61
|
+
"""Create an enhanced MCP server with automatic context normalization.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
name: Server name
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Enhanced FastMCP server instance
|
|
68
|
+
"""
|
|
69
|
+
return EnhancedFastMCP(name)
|
hanzo_mcp/tools/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"""Tools package for Hanzo
|
|
1
|
+
"""Tools package for Hanzo AI.
|
|
2
2
|
|
|
3
|
-
This package contains all the tools for the Hanzo
|
|
4
|
-
It provides a
|
|
3
|
+
This package contains all the tools for the Hanzo AI server.
|
|
4
|
+
It provides a interface for registering all tools with an MCP server.
|
|
5
5
|
|
|
6
6
|
This includes a "think" tool implementation based on Anthropic's research showing
|
|
7
7
|
improved performance for complex tool-based interactions when Claude has a dedicated
|
|
@@ -12,7 +12,7 @@ to delegate tasks to sub-agents for concurrent execution and specialized process
|
|
|
12
12
|
from mcp.server import FastMCP
|
|
13
13
|
|
|
14
14
|
from hanzo_mcp.tools.agent import register_agent_tools
|
|
15
|
-
from hanzo_mcp.tools.common import register_batch_tool, register_thinking_tool
|
|
15
|
+
from hanzo_mcp.tools.common import register_batch_tool, register_thinking_tool, register_critic_tool
|
|
16
16
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
17
17
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
18
18
|
from hanzo_mcp.tools.common.tool_enable import ToolEnableTool
|
|
@@ -25,11 +25,28 @@ from hanzo_mcp.tools.shell import register_shell_tools
|
|
|
25
25
|
from hanzo_mcp.tools.todo import register_todo_tools
|
|
26
26
|
from hanzo_mcp.tools.vector import register_vector_tools
|
|
27
27
|
from hanzo_mcp.tools.database import register_database_tools, DatabaseManager
|
|
28
|
-
|
|
28
|
+
|
|
29
|
+
# Try to import memory tools, but don't fail if hanzo-memory is not installed
|
|
30
|
+
try:
|
|
31
|
+
from hanzo_mcp.tools.memory import register_memory_tools
|
|
32
|
+
MEMORY_TOOLS_AVAILABLE = True
|
|
33
|
+
except ImportError:
|
|
34
|
+
MEMORY_TOOLS_AVAILABLE = False
|
|
35
|
+
register_memory_tools = None
|
|
36
|
+
from hanzo_mcp.tools.mcp import MCPTool, McpAddTool, McpRemoveTool, McpStatsTool
|
|
29
37
|
from hanzo_mcp.tools.editor import NeovimEditTool, NeovimCommandTool, NeovimSessionTool
|
|
30
|
-
from hanzo_mcp.tools.llm import
|
|
31
|
-
from hanzo_mcp.tools.config.
|
|
32
|
-
from hanzo_mcp.tools.common.
|
|
38
|
+
from hanzo_mcp.tools.llm import LLMTool, LLMTool, ConsensusTool, LLMManageTool, create_provider_tools
|
|
39
|
+
from hanzo_mcp.tools.config.mode_tool import mode_tool
|
|
40
|
+
from hanzo_mcp.tools.common.mode_loader import ModeLoader
|
|
41
|
+
from hanzo_mcp.tools.common.mode import activate_mode_from_env
|
|
42
|
+
from hanzo_mcp.tools.common.plugin_loader import load_user_plugins
|
|
43
|
+
|
|
44
|
+
# Try to import LSP tool
|
|
45
|
+
try:
|
|
46
|
+
from hanzo_mcp.tools.lsp import LSPTool, create_lsp_tool
|
|
47
|
+
LSP_TOOL_AVAILABLE = True
|
|
48
|
+
except ImportError:
|
|
49
|
+
LSP_TOOL_AVAILABLE = False
|
|
33
50
|
|
|
34
51
|
|
|
35
52
|
def register_all_tools(
|
|
@@ -46,8 +63,8 @@ def register_all_tools(
|
|
|
46
63
|
disable_search_tools: bool = False,
|
|
47
64
|
enabled_tools: dict[str, bool] | None = None,
|
|
48
65
|
vector_config: dict | None = None,
|
|
49
|
-
|
|
50
|
-
|
|
66
|
+
use_mode: bool = True,
|
|
67
|
+
force_mode: str | None = None,
|
|
51
68
|
) -> None:
|
|
52
69
|
"""Register all Hanzo tools with the MCP server.
|
|
53
70
|
|
|
@@ -65,20 +82,36 @@ def register_all_tools(
|
|
|
65
82
|
disable_search_tools: Whether to disable search tools (default: False)
|
|
66
83
|
enabled_tools: Dictionary of individual tool enable/disable states (default: None)
|
|
67
84
|
vector_config: Vector store configuration (default: None)
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
use_mode: Whether to use mode system for tool configuration (default: True)
|
|
86
|
+
force_mode: Force a specific mode to be active (default: None)
|
|
70
87
|
"""
|
|
71
88
|
# Dictionary to store all registered tools
|
|
72
89
|
all_tools: dict[str, BaseTool] = {}
|
|
73
90
|
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
|
|
91
|
+
# Load user plugins early
|
|
92
|
+
try:
|
|
93
|
+
plugins = load_user_plugins()
|
|
94
|
+
import logging
|
|
95
|
+
logger = logging.getLogger(__name__)
|
|
96
|
+
if plugins:
|
|
97
|
+
logger.info(f"Loaded {len(plugins)} user plugin tools: {', '.join(plugins.keys())}")
|
|
98
|
+
except Exception as e:
|
|
99
|
+
import logging
|
|
100
|
+
logger = logging.getLogger(__name__)
|
|
101
|
+
logger.warning(f"Failed to load user plugins: {e}")
|
|
102
|
+
plugins = {}
|
|
103
|
+
|
|
104
|
+
# Apply mode configuration if enabled
|
|
105
|
+
if use_mode:
|
|
106
|
+
# First check for mode activation from environment
|
|
107
|
+
activate_mode_from_env()
|
|
108
|
+
|
|
109
|
+
tool_config = ModeLoader.get_enabled_tools_from_mode(
|
|
77
110
|
base_enabled_tools=enabled_tools,
|
|
78
|
-
|
|
111
|
+
force_mode=force_mode
|
|
79
112
|
)
|
|
80
|
-
# Apply
|
|
81
|
-
|
|
113
|
+
# Apply mode environment variables
|
|
114
|
+
ModeLoader.apply_environment_from_mode()
|
|
82
115
|
else:
|
|
83
116
|
# Use individual tool configuration if provided, otherwise fall back to category-level flags
|
|
84
117
|
tool_config = enabled_tools or {}
|
|
@@ -97,15 +130,18 @@ def register_all_tools(
|
|
|
97
130
|
"multi_edit": is_tool_enabled("multi_edit", not disable_write_tools),
|
|
98
131
|
"directory_tree": is_tool_enabled("directory_tree", True),
|
|
99
132
|
"grep": is_tool_enabled("grep", not disable_search_tools),
|
|
100
|
-
"
|
|
133
|
+
"symbols": is_tool_enabled("symbols", not disable_search_tools),
|
|
101
134
|
"git_search": is_tool_enabled("git_search", not disable_search_tools),
|
|
102
135
|
"content_replace": is_tool_enabled("content_replace", not disable_write_tools),
|
|
103
136
|
"batch_search": is_tool_enabled("batch_search", not disable_search_tools),
|
|
104
137
|
"find_files": is_tool_enabled("find_files", True),
|
|
105
|
-
"
|
|
138
|
+
"rules": is_tool_enabled("rules", True),
|
|
139
|
+
"search": is_tool_enabled("search", not disable_search_tools),
|
|
140
|
+
"unified_search": is_tool_enabled("unified_search", True), # Primary search tool
|
|
141
|
+
"find": is_tool_enabled("find", True), # Fast file finder
|
|
106
142
|
}
|
|
107
143
|
|
|
108
|
-
# Vector tools setup (needed for
|
|
144
|
+
# Vector tools setup (needed for search)
|
|
109
145
|
project_manager = None
|
|
110
146
|
vector_enabled = {
|
|
111
147
|
"vector_index": is_tool_enabled("vector_index", False),
|
|
@@ -113,7 +149,7 @@ def register_all_tools(
|
|
|
113
149
|
}
|
|
114
150
|
|
|
115
151
|
# Create project manager if vector tools, batch_search, or unified_search are enabled
|
|
116
|
-
if any(vector_enabled.values()) or filesystem_enabled.get("batch_search", False) or filesystem_enabled.get("
|
|
152
|
+
if any(vector_enabled.values()) or filesystem_enabled.get("batch_search", False) or filesystem_enabled.get("search", False):
|
|
117
153
|
if vector_config:
|
|
118
154
|
from hanzo_mcp.tools.vector.project_manager import ProjectVectorManager
|
|
119
155
|
search_paths = [str(path) for path in permission_manager.allowed_paths]
|
|
@@ -155,8 +191,10 @@ def register_all_tools(
|
|
|
155
191
|
all_tools[tool.name] = tool
|
|
156
192
|
|
|
157
193
|
# Register agent tools if enabled
|
|
158
|
-
agent_enabled = enable_agent_tool or is_tool_enabled("dispatch_agent", False)
|
|
159
|
-
|
|
194
|
+
agent_enabled = enable_agent_tool or is_tool_enabled("agent", False) or is_tool_enabled("dispatch_agent", False)
|
|
195
|
+
swarm_enabled = is_tool_enabled("swarm", False)
|
|
196
|
+
|
|
197
|
+
if agent_enabled or swarm_enabled:
|
|
160
198
|
agent_tools = register_agent_tools(
|
|
161
199
|
mcp_server,
|
|
162
200
|
permission_manager,
|
|
@@ -167,17 +205,27 @@ def register_all_tools(
|
|
|
167
205
|
agent_max_iterations=agent_max_iterations,
|
|
168
206
|
agent_max_tool_uses=agent_max_tool_uses,
|
|
169
207
|
)
|
|
208
|
+
# Filter based on what's enabled
|
|
170
209
|
for tool in agent_tools:
|
|
171
|
-
|
|
210
|
+
if tool.name == "agent" and agent_enabled:
|
|
211
|
+
all_tools[tool.name] = tool
|
|
212
|
+
elif tool.name == "swarm" and swarm_enabled:
|
|
213
|
+
all_tools[tool.name] = tool
|
|
214
|
+
elif tool.name in ["claude", "codex", "gemini", "grok", "code_auth"]:
|
|
215
|
+
# CLI tools and auth are always included when agent tools are enabled
|
|
216
|
+
all_tools[tool.name] = tool
|
|
172
217
|
|
|
173
218
|
# Register todo tools if enabled
|
|
174
219
|
todo_enabled = {
|
|
220
|
+
"todo": is_tool_enabled("todo", True),
|
|
221
|
+
# Backward compatibility - if old names are used, enable the unified tool
|
|
175
222
|
"todo_read": is_tool_enabled("todo_read", True),
|
|
176
223
|
"todo_write": is_tool_enabled("todo_write", True),
|
|
177
224
|
}
|
|
178
225
|
|
|
226
|
+
# Enable unified todo if any of the todo tools are enabled
|
|
179
227
|
if any(todo_enabled.values()):
|
|
180
|
-
todo_tools = register_todo_tools(mcp_server, enabled_tools=
|
|
228
|
+
todo_tools = register_todo_tools(mcp_server, enabled_tools={"todo": True})
|
|
181
229
|
for tool in todo_tools:
|
|
182
230
|
all_tools[tool.name] = tool
|
|
183
231
|
|
|
@@ -186,6 +234,12 @@ def register_all_tools(
|
|
|
186
234
|
thinking_tool = register_thinking_tool(mcp_server)
|
|
187
235
|
for tool in thinking_tool:
|
|
188
236
|
all_tools[tool.name] = tool
|
|
237
|
+
|
|
238
|
+
# Register critic tool if enabled
|
|
239
|
+
if is_tool_enabled("critic", True):
|
|
240
|
+
critic_tools = register_critic_tool(mcp_server)
|
|
241
|
+
for tool in critic_tools:
|
|
242
|
+
all_tools[tool.name] = tool
|
|
189
243
|
|
|
190
244
|
# Register vector tools if enabled (reuse project_manager if available)
|
|
191
245
|
if any(vector_enabled.values()) and project_manager:
|
|
@@ -230,7 +284,7 @@ def register_all_tools(
|
|
|
230
284
|
|
|
231
285
|
# Register unified MCP tool if enabled
|
|
232
286
|
if is_tool_enabled("mcp", True):
|
|
233
|
-
tool =
|
|
287
|
+
tool = MCPTool()
|
|
234
288
|
tool.register(mcp_server)
|
|
235
289
|
all_tools[tool.name] = tool
|
|
236
290
|
|
|
@@ -275,9 +329,9 @@ def register_all_tools(
|
|
|
275
329
|
stats_tool.register(mcp_server)
|
|
276
330
|
all_tools[stats_tool.name] = stats_tool
|
|
277
331
|
|
|
278
|
-
#
|
|
279
|
-
|
|
280
|
-
all_tools[
|
|
332
|
+
# Mode tool (always enabled for managing tool sets)
|
|
333
|
+
mode_tool.register(mcp_server)
|
|
334
|
+
all_tools[mode_tool.name] = mode_tool
|
|
281
335
|
|
|
282
336
|
# Register editor tools if enabled
|
|
283
337
|
editor_enabled = {
|
|
@@ -303,11 +357,18 @@ def register_all_tools(
|
|
|
303
357
|
|
|
304
358
|
# Register unified LLM tool if enabled
|
|
305
359
|
if is_tool_enabled("llm", True):
|
|
306
|
-
tool =
|
|
360
|
+
tool = LLMTool()
|
|
307
361
|
if tool.available_providers: # Only register if API keys found
|
|
308
362
|
tool.register(mcp_server)
|
|
309
363
|
all_tools[tool.name] = tool
|
|
310
364
|
|
|
365
|
+
# Register consensus tool if enabled (enabled by default)
|
|
366
|
+
if is_tool_enabled("consensus", True):
|
|
367
|
+
tool = ConsensusTool()
|
|
368
|
+
if tool.llm_tool.available_providers:
|
|
369
|
+
tool.register(mcp_server)
|
|
370
|
+
all_tools[tool.name] = tool
|
|
371
|
+
|
|
311
372
|
# Register legacy LLM tools if explicitly enabled (disabled by default)
|
|
312
373
|
legacy_llm_enabled = {
|
|
313
374
|
"llm_legacy": is_tool_enabled("llm_legacy", False),
|
|
@@ -339,3 +400,51 @@ def register_all_tools(
|
|
|
339
400
|
if is_tool_enabled(tool.name, False):
|
|
340
401
|
tool.register(mcp_server)
|
|
341
402
|
all_tools[tool.name] = tool
|
|
403
|
+
|
|
404
|
+
# Register memory tools if enabled
|
|
405
|
+
memory_enabled = {
|
|
406
|
+
"recall_memories": is_tool_enabled("recall_memories", True),
|
|
407
|
+
"create_memories": is_tool_enabled("create_memories", True),
|
|
408
|
+
"update_memories": is_tool_enabled("update_memories", True),
|
|
409
|
+
"delete_memories": is_tool_enabled("delete_memories", True),
|
|
410
|
+
"manage_memories": is_tool_enabled("manage_memories", True),
|
|
411
|
+
"recall_facts": is_tool_enabled("recall_facts", True),
|
|
412
|
+
"store_facts": is_tool_enabled("store_facts", True),
|
|
413
|
+
"summarize_to_memory": is_tool_enabled("summarize_to_memory", True),
|
|
414
|
+
"manage_knowledge_bases": is_tool_enabled("manage_knowledge_bases", True),
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if any(memory_enabled.values()) and MEMORY_TOOLS_AVAILABLE:
|
|
418
|
+
try:
|
|
419
|
+
memory_tools = register_memory_tools(
|
|
420
|
+
mcp_server,
|
|
421
|
+
permission_manager,
|
|
422
|
+
user_id="default",
|
|
423
|
+
project_id="default"
|
|
424
|
+
)
|
|
425
|
+
# Filter based on enabled state
|
|
426
|
+
for tool in memory_tools:
|
|
427
|
+
if memory_enabled.get(tool.name, True):
|
|
428
|
+
all_tools[tool.name] = tool
|
|
429
|
+
except Exception as e:
|
|
430
|
+
logger.warning(f"Failed to register memory tools: {e}")
|
|
431
|
+
|
|
432
|
+
# Register LSP tool if enabled
|
|
433
|
+
if is_tool_enabled("lsp", True) and LSP_TOOL_AVAILABLE:
|
|
434
|
+
try:
|
|
435
|
+
tool = create_lsp_tool()
|
|
436
|
+
tool.register(mcp_server)
|
|
437
|
+
all_tools[tool.name] = tool
|
|
438
|
+
except Exception as e:
|
|
439
|
+
logger.warning(f"Failed to register LSP tool: {e}")
|
|
440
|
+
|
|
441
|
+
# Register user plugins last (so they can override built-in tools)
|
|
442
|
+
for plugin_name, plugin in plugins.items():
|
|
443
|
+
if is_tool_enabled(plugin_name, True):
|
|
444
|
+
try:
|
|
445
|
+
tool = plugin.tool_class()
|
|
446
|
+
tool.register(mcp_server)
|
|
447
|
+
all_tools[tool.name] = tool
|
|
448
|
+
logger.info(f"Registered plugin tool: {plugin_name}")
|
|
449
|
+
except Exception as e:
|
|
450
|
+
logger.error(f"Failed to register plugin tool {plugin_name}: {e}")
|