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.
- hanzo_mcp/__init__.py +11 -2
- hanzo_mcp/cli.py +69 -19
- hanzo_mcp/cli_enhanced.py +15 -12
- hanzo_mcp/cli_plugin.py +91 -0
- hanzo_mcp/config/__init__.py +1 -1
- hanzo_mcp/config/settings.py +75 -8
- hanzo_mcp/config/tool_config.py +2 -2
- hanzo_mcp/dev_server.py +20 -15
- hanzo_mcp/prompts/project_system.py +1 -1
- hanzo_mcp/server.py +18 -4
- hanzo_mcp/server_enhanced.py +69 -0
- hanzo_mcp/tools/__init__.py +78 -30
- hanzo_mcp/tools/agent/__init__.py +1 -1
- hanzo_mcp/tools/agent/agent_tool.py +2 -2
- hanzo_mcp/tools/common/__init__.py +15 -1
- hanzo_mcp/tools/common/base.py +4 -4
- hanzo_mcp/tools/common/batch_tool.py +1 -1
- 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/mode.py +116 -0
- hanzo_mcp/tools/common/mode_loader.py +105 -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 +1 -1
- 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 +19 -14
- hanzo_mcp/tools/filesystem/batch_search.py +3 -3
- hanzo_mcp/tools/filesystem/diff.py +2 -2
- 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/mcp/__init__.py +2 -2
- hanzo_mcp/tools/mcp/{mcp_unified.py → mcp_tool.py} +3 -3
- hanzo_mcp/tools/shell/__init__.py +6 -6
- hanzo_mcp/tools/shell/base_process.py +4 -2
- hanzo_mcp/tools/shell/bash_session_executor.py +8 -5
- hanzo_mcp/tools/shell/{bash_unified.py → bash_tool.py} +1 -1
- hanzo_mcp/tools/shell/command_executor.py +8 -6
- hanzo_mcp/tools/shell/{npx_unified.py → npx_tool.py} +1 -1
- 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/uvx.py +47 -2
- hanzo_mcp/tools/shell/uvx_background.py +47 -2
- hanzo_mcp/tools/shell/{uvx_unified.py → uvx_tool.py} +1 -1
- hanzo_mcp/tools/todo/__init__.py +14 -19
- hanzo_mcp/tools/todo/todo.py +22 -1
- hanzo_mcp/tools/vector/__init__.py +7 -3
- hanzo_mcp/tools/vector/ast_analyzer.py +12 -4
- hanzo_mcp/tools/vector/infinity_store.py +11 -5
- hanzo_mcp/tools/vector/project_manager.py +4 -2
- hanzo_mcp-0.6.13.dist-info/METADATA +359 -0
- {hanzo_mcp-0.6.10.dist-info → hanzo_mcp-0.6.13.dist-info}/RECORD +73 -65
- {hanzo_mcp-0.6.10.dist-info → hanzo_mcp-0.6.13.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.10.dist-info/METADATA +0 -339
- {hanzo_mcp-0.6.10.dist-info → hanzo_mcp-0.6.13.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.6.10.dist-info → hanzo_mcp-0.6.13.dist-info}/licenses/LICENSE +0 -0
- {hanzo_mcp-0.6.10.dist-info → hanzo_mcp-0.6.13.dist-info}/top_level.txt +0 -0
hanzo_mcp/__init__.py
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
-
"""Hanzo
|
|
1
|
+
"""Hanzo AI - Implementation of Hanzo capabilities using MCP."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# Configure FastMCP logging globally for stdio transport
|
|
4
|
+
import os
|
|
5
|
+
if os.environ.get("HANZO_MCP_TRANSPORT") == "stdio":
|
|
6
|
+
try:
|
|
7
|
+
from fastmcp.utilities.logging import configure_logging
|
|
8
|
+
configure_logging(level="ERROR")
|
|
9
|
+
except ImportError:
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
__version__ = "0.6.13"
|
hanzo_mcp/cli.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
"""Command-line interface for the Hanzo
|
|
1
|
+
"""Command-line interface for the Hanzo AI server."""
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import json
|
|
5
|
+
import logging
|
|
5
6
|
import os
|
|
6
7
|
import signal
|
|
7
8
|
import sys
|
|
@@ -12,14 +13,34 @@ from hanzo_mcp.server import HanzoMCPServer
|
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
def main() -> None:
|
|
15
|
-
"""Run the CLI for the Hanzo
|
|
16
|
-
# Set up signal handler to ensure clean exit
|
|
17
|
-
def signal_handler(signum, frame):
|
|
18
|
-
print("\nReceived interrupt signal, shutting down...")
|
|
19
|
-
sys.exit(0)
|
|
16
|
+
"""Run the CLI for the Hanzo AI server."""
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
# Pre-parse arguments to check transport type early
|
|
19
|
+
import sys
|
|
20
|
+
early_parser = argparse.ArgumentParser(add_help=False)
|
|
21
|
+
early_parser.add_argument("--transport", choices=["stdio", "sse"], default="stdio")
|
|
22
|
+
early_args, _ = early_parser.parse_known_args()
|
|
23
|
+
|
|
24
|
+
# Configure logging VERY early based on transport
|
|
25
|
+
if early_args.transport == "stdio":
|
|
26
|
+
# Set environment variable for server to detect stdio mode
|
|
27
|
+
import os
|
|
28
|
+
os.environ["HANZO_MCP_TRANSPORT"] = "stdio"
|
|
29
|
+
|
|
30
|
+
# For stdio transport, disable ALL logging immediately
|
|
31
|
+
from fastmcp.utilities.logging import configure_logging
|
|
32
|
+
# Set to ERROR to suppress INFO/WARNING messages from FastMCP
|
|
33
|
+
configure_logging(level="ERROR")
|
|
34
|
+
|
|
35
|
+
# Also configure standard logging to ERROR level
|
|
36
|
+
logging.basicConfig(
|
|
37
|
+
level=logging.ERROR, # Only show errors
|
|
38
|
+
handlers=[] # No handlers for stdio to prevent protocol corruption
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Redirect stderr to devnull for stdio transport to prevent any output
|
|
42
|
+
import sys
|
|
43
|
+
sys.stderr = open(os.devnull, 'w')
|
|
23
44
|
|
|
24
45
|
parser = argparse.ArgumentParser(
|
|
25
46
|
description="MCP server implementing Hanzo AI capabilities"
|
|
@@ -212,6 +233,32 @@ def main() -> None:
|
|
|
212
233
|
)
|
|
213
234
|
return
|
|
214
235
|
|
|
236
|
+
# Get logger
|
|
237
|
+
logger = logging.getLogger(__name__)
|
|
238
|
+
|
|
239
|
+
# Set up signal handler to ensure clean exit
|
|
240
|
+
def signal_handler(signum, frame):
|
|
241
|
+
if transport != "stdio":
|
|
242
|
+
logger.info("\nReceived interrupt signal, shutting down...")
|
|
243
|
+
sys.exit(0)
|
|
244
|
+
|
|
245
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
246
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
247
|
+
|
|
248
|
+
# Configure logging based on transport (stdio already configured early)
|
|
249
|
+
if transport != "stdio":
|
|
250
|
+
# For SSE transport, logging is fine
|
|
251
|
+
log_level_map = {
|
|
252
|
+
"DEBUG": logging.DEBUG,
|
|
253
|
+
"INFO": logging.INFO,
|
|
254
|
+
"WARNING": logging.WARNING,
|
|
255
|
+
"ERROR": logging.ERROR
|
|
256
|
+
}
|
|
257
|
+
logging.basicConfig(
|
|
258
|
+
level=log_level_map.get(log_level, logging.INFO),
|
|
259
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
260
|
+
)
|
|
261
|
+
|
|
215
262
|
# If no allowed paths are specified, use the current directory
|
|
216
263
|
if not allowed_paths:
|
|
217
264
|
allowed_paths = [os.getcwd()]
|
|
@@ -263,10 +310,11 @@ def main() -> None:
|
|
|
263
310
|
# Transport will be automatically cast to Literal['stdio', 'sse'] by the server
|
|
264
311
|
server.run(transport=transport)
|
|
265
312
|
except KeyboardInterrupt:
|
|
266
|
-
|
|
313
|
+
if transport != "stdio":
|
|
314
|
+
logger.info("\nShutting down...")
|
|
267
315
|
sys.exit(0)
|
|
268
316
|
except Exception as e:
|
|
269
|
-
|
|
317
|
+
logger.error(f"Server error: {e}")
|
|
270
318
|
sys.exit(1)
|
|
271
319
|
|
|
272
320
|
|
|
@@ -342,27 +390,29 @@ def install_claude_desktop_config(
|
|
|
342
390
|
existing_config["mcpServers"][name] = config["mcpServers"][name]
|
|
343
391
|
config = existing_config
|
|
344
392
|
except Exception as e:
|
|
345
|
-
|
|
346
|
-
|
|
393
|
+
logger = logging.getLogger(__name__)
|
|
394
|
+
logger.error(f"Error reading existing config: {e}")
|
|
395
|
+
logger.info("Creating new config file.")
|
|
347
396
|
|
|
348
397
|
# Write the config file
|
|
349
398
|
with open(config_file, mode="w") as f:
|
|
350
399
|
json.dump(config, f, indent=2)
|
|
351
400
|
|
|
352
|
-
|
|
353
|
-
|
|
401
|
+
logger = logging.getLogger(__name__)
|
|
402
|
+
logger.info(f"Successfully installed {name} in Claude Desktop configuration.")
|
|
403
|
+
logger.info(f"Config file: {config_file}")
|
|
354
404
|
|
|
355
405
|
if allowed_paths:
|
|
356
|
-
|
|
406
|
+
logger.info("\nAllowed paths:")
|
|
357
407
|
for path in allowed_paths:
|
|
358
|
-
|
|
408
|
+
logger.info(f"- {path}")
|
|
359
409
|
else:
|
|
360
|
-
|
|
410
|
+
logger.info(f"\nDefault allowed path: {home}")
|
|
361
411
|
|
|
362
|
-
|
|
412
|
+
logger.info(
|
|
363
413
|
"\nYou can modify allowed paths in the config file directly."
|
|
364
414
|
)
|
|
365
|
-
|
|
415
|
+
logger.info("Restart Claude Desktop for changes to take effect.")
|
|
366
416
|
|
|
367
417
|
|
|
368
418
|
if __name__ == "__main__":
|
hanzo_mcp/cli_enhanced.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
"""Enhanced command-line interface for the Hanzo
|
|
1
|
+
"""Enhanced command-line interface for the Hanzo AI server with full tool configuration."""
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import json
|
|
5
|
+
import logging
|
|
5
6
|
import os
|
|
6
7
|
import sys
|
|
7
8
|
from pathlib import Path
|
|
@@ -14,7 +15,7 @@ from hanzo_mcp.server import HanzoMCPServer
|
|
|
14
15
|
def create_parser() -> argparse.ArgumentParser:
|
|
15
16
|
"""Create the argument parser with all tool configuration options."""
|
|
16
17
|
parser = argparse.ArgumentParser(
|
|
17
|
-
description="Hanzo
|
|
18
|
+
description="Hanzo AI server with comprehensive tool configuration",
|
|
18
19
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
19
20
|
epilog="""
|
|
20
21
|
Tool Configuration:
|
|
@@ -344,8 +345,9 @@ def apply_cli_overrides(args: argparse.Namespace) -> Dict[str, Any]:
|
|
|
344
345
|
|
|
345
346
|
def list_tools(settings: HanzoMCPSettings) -> None:
|
|
346
347
|
"""List all tools and their current status."""
|
|
347
|
-
|
|
348
|
-
|
|
348
|
+
logger = logging.getLogger(__name__)
|
|
349
|
+
logger.info("Hanzo AI Tools Status:")
|
|
350
|
+
logger.info("=" * 50)
|
|
349
351
|
|
|
350
352
|
categories = {}
|
|
351
353
|
for tool_name, tool_config in TOOL_REGISTRY.items():
|
|
@@ -358,18 +360,18 @@ def list_tools(settings: HanzoMCPSettings) -> None:
|
|
|
358
360
|
categories[category].append((tool_name, status, tool_config.description))
|
|
359
361
|
|
|
360
362
|
for category, tools in categories.items():
|
|
361
|
-
|
|
362
|
-
|
|
363
|
+
logger.info(f"\n{category.upper()} TOOLS:")
|
|
364
|
+
logger.info("-" * 30)
|
|
363
365
|
for tool_name, status, description in tools:
|
|
364
|
-
|
|
366
|
+
logger.info(f" {status} {tool_name:<15} - {description}")
|
|
365
367
|
|
|
366
|
-
|
|
368
|
+
logger.info(f"\nTotal: {len(TOOL_REGISTRY)} tools")
|
|
367
369
|
enabled_count = len(settings.get_enabled_tools())
|
|
368
|
-
|
|
370
|
+
logger.info(f"Enabled: {enabled_count}, Disabled: {len(TOOL_REGISTRY) - enabled_count}")
|
|
369
371
|
|
|
370
372
|
|
|
371
373
|
def main() -> None:
|
|
372
|
-
"""Run the enhanced CLI for the Hanzo
|
|
374
|
+
"""Run the enhanced CLI for the Hanzo AI server."""
|
|
373
375
|
parser = create_parser()
|
|
374
376
|
args = parser.parse_args()
|
|
375
377
|
|
|
@@ -385,14 +387,15 @@ def main() -> None:
|
|
|
385
387
|
settings = load_settings(project_dir=project_dir, config_overrides=config_overrides)
|
|
386
388
|
|
|
387
389
|
# Handle configuration saving
|
|
390
|
+
logger = logging.getLogger(__name__)
|
|
388
391
|
if hasattr(args, 'save_config') and args.save_config:
|
|
389
392
|
saved_path = save_settings(settings, global_config=True)
|
|
390
|
-
|
|
393
|
+
logger.info(f"Configuration saved to: {saved_path}")
|
|
391
394
|
return
|
|
392
395
|
|
|
393
396
|
if hasattr(args, 'save_project_config') and args.save_project_config:
|
|
394
397
|
saved_path = save_settings(settings, global_config=False)
|
|
395
|
-
|
|
398
|
+
logger.info(f"Project configuration saved to: {saved_path}")
|
|
396
399
|
return
|
|
397
400
|
|
|
398
401
|
# Handle installation
|
hanzo_mcp/cli_plugin.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""CLI for managing Hanzo MCP plugins."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from hanzo_mcp.tools.common.plugin_loader import create_plugin_template, list_plugin_tools
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
"""Main CLI entry point."""
|
|
13
|
+
parser = argparse.ArgumentParser(
|
|
14
|
+
description="Hanzo MCP Plugin Manager",
|
|
15
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
16
|
+
epilog="""
|
|
17
|
+
Examples:
|
|
18
|
+
# Create a new plugin template
|
|
19
|
+
hanzo-plugin create mytool
|
|
20
|
+
|
|
21
|
+
# List installed plugins
|
|
22
|
+
hanzo-plugin list
|
|
23
|
+
|
|
24
|
+
# Create plugin in specific directory
|
|
25
|
+
hanzo-plugin create mytool --output /path/to/plugins
|
|
26
|
+
"""
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
subparsers = parser.add_subparsers(dest="command", help="Command to run")
|
|
30
|
+
|
|
31
|
+
# Create command
|
|
32
|
+
create_parser = subparsers.add_parser("create", help="Create a new plugin template")
|
|
33
|
+
create_parser.add_argument("name", help="Name of the tool (e.g., 'mytool')")
|
|
34
|
+
create_parser.add_argument(
|
|
35
|
+
"--output", "-o",
|
|
36
|
+
type=Path,
|
|
37
|
+
default=Path.home() / ".hanzo" / "plugins",
|
|
38
|
+
help="Output directory for the plugin (default: ~/.hanzo/plugins)"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# List command
|
|
42
|
+
list_parser = subparsers.add_parser("list", help="List installed plugins")
|
|
43
|
+
|
|
44
|
+
args = parser.parse_args()
|
|
45
|
+
|
|
46
|
+
if args.command == "create":
|
|
47
|
+
# Create plugin template
|
|
48
|
+
output_dir = args.output / args.name
|
|
49
|
+
try:
|
|
50
|
+
create_plugin_template(output_dir, args.name)
|
|
51
|
+
print(f"\n✅ Plugin template created successfully!")
|
|
52
|
+
print(f"\nTo use your plugin:")
|
|
53
|
+
print(f"1. Edit the tool implementation in {output_dir / f'{args.name}_tool.py'}")
|
|
54
|
+
print(f"2. Restart Hanzo MCP to load the plugin")
|
|
55
|
+
print(f"3. Add '{args.name}' to your mode's tool list")
|
|
56
|
+
except Exception as e:
|
|
57
|
+
print(f"❌ Error creating plugin: {e}", file=sys.stderr)
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
|
|
60
|
+
elif args.command == "list":
|
|
61
|
+
# List installed plugins
|
|
62
|
+
try:
|
|
63
|
+
from hanzo_mcp.tools.common.plugin_loader import load_user_plugins
|
|
64
|
+
plugins = load_user_plugins()
|
|
65
|
+
|
|
66
|
+
if not plugins:
|
|
67
|
+
print("No plugins installed.")
|
|
68
|
+
print("\nPlugin directories:")
|
|
69
|
+
print(" ~/.hanzo/plugins/")
|
|
70
|
+
print(" ./.hanzo/plugins/")
|
|
71
|
+
print(" $HANZO_PLUGIN_PATH")
|
|
72
|
+
else:
|
|
73
|
+
print(f"Installed plugins ({len(plugins)}):")
|
|
74
|
+
for name, plugin in plugins.items():
|
|
75
|
+
print(f"\n {name}:")
|
|
76
|
+
print(f" Source: {plugin.source_path}")
|
|
77
|
+
if plugin.metadata:
|
|
78
|
+
print(f" Version: {plugin.metadata.get('version', 'unknown')}")
|
|
79
|
+
print(f" Author: {plugin.metadata.get('author', 'unknown')}")
|
|
80
|
+
print(f" Description: {plugin.metadata.get('description', '')}")
|
|
81
|
+
except Exception as e:
|
|
82
|
+
print(f"❌ Error listing plugins: {e}", file=sys.stderr)
|
|
83
|
+
sys.exit(1)
|
|
84
|
+
|
|
85
|
+
else:
|
|
86
|
+
parser.print_help()
|
|
87
|
+
sys.exit(1)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
main()
|
hanzo_mcp/config/__init__.py
CHANGED
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
|
|
@@ -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()
|
|
@@ -364,7 +422,9 @@ def load_settings(
|
|
|
364
422
|
global_config = json.load(f)
|
|
365
423
|
settings = _merge_config(settings, global_config)
|
|
366
424
|
except Exception as e:
|
|
367
|
-
|
|
425
|
+
import logging
|
|
426
|
+
logger = logging.getLogger(__name__)
|
|
427
|
+
logger.warning(f"Failed to load global config: {e}")
|
|
368
428
|
|
|
369
429
|
# Load project config
|
|
370
430
|
project_config_path = get_project_config_path(project_dir)
|
|
@@ -374,7 +434,14 @@ def load_settings(
|
|
|
374
434
|
project_config = json.load(f)
|
|
375
435
|
settings = _merge_config(settings, project_config)
|
|
376
436
|
except Exception as e:
|
|
377
|
-
|
|
437
|
+
import logging
|
|
438
|
+
logger = logging.getLogger(__name__)
|
|
439
|
+
logger.warning(f"Failed to load project config: {e}")
|
|
440
|
+
|
|
441
|
+
# Apply environment variables
|
|
442
|
+
env_config = _load_from_env()
|
|
443
|
+
if env_config:
|
|
444
|
+
settings = _merge_config(settings, env_config)
|
|
378
445
|
|
|
379
446
|
# Apply CLI overrides
|
|
380
447
|
if 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"
|
hanzo_mcp/dev_server.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
"""Development server with hot reload for Hanzo
|
|
1
|
+
"""Development server with hot reload for Hanzo AI."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import logging
|
|
4
5
|
import os
|
|
5
6
|
import sys
|
|
6
7
|
import time
|
|
@@ -67,8 +68,9 @@ class MCPReloadHandler(FileSystemEventHandler):
|
|
|
67
68
|
|
|
68
69
|
self.last_reload = current_time
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
logger = logging.getLogger(__name__)
|
|
72
|
+
logger.info(f"\n🔄 File changed: {event.src_path}")
|
|
73
|
+
logger.info("🔄 Reloading MCP server...")
|
|
72
74
|
|
|
73
75
|
self.restart_callback()
|
|
74
76
|
|
|
@@ -133,9 +135,10 @@ class DevServer:
|
|
|
133
135
|
self.observer.schedule(handler, path, recursive=True)
|
|
134
136
|
|
|
135
137
|
self.observer.start()
|
|
136
|
-
|
|
138
|
+
logger = logging.getLogger(__name__)
|
|
139
|
+
logger.info(f"👀 Watching for changes in: {package_dir}")
|
|
137
140
|
if self.project_dir:
|
|
138
|
-
|
|
141
|
+
logger.info(f"👀 Also watching: {self.project_dir}")
|
|
139
142
|
|
|
140
143
|
def stop_file_watcher(self):
|
|
141
144
|
"""Stop the file watcher."""
|
|
@@ -146,18 +149,20 @@ class DevServer:
|
|
|
146
149
|
def restart_server(self):
|
|
147
150
|
"""Restart the MCP server."""
|
|
148
151
|
# Since MCP servers run in the same process, we need to handle this differently
|
|
149
|
-
# For now, we'll
|
|
150
|
-
|
|
151
|
-
|
|
152
|
+
# For now, we'll log a message indicating a restart is needed
|
|
153
|
+
logger = logging.getLogger(__name__)
|
|
154
|
+
logger.warning("\n⚠️ Server restart required. Please restart the MCP client to reload changes.")
|
|
155
|
+
logger.info("💡 Tip: In development, consider using the MCP test client for easier reloading.")
|
|
152
156
|
|
|
153
157
|
async def run_async(self, transport: str = "stdio"):
|
|
154
158
|
"""Run the development server asynchronously."""
|
|
155
159
|
self.running = True
|
|
156
160
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
logger = logging.getLogger(__name__)
|
|
162
|
+
logger.info(f"\n🚀 Starting Hanzo AI in development mode...")
|
|
163
|
+
logger.info(f"🔧 Hot reload enabled - watching for file changes")
|
|
164
|
+
logger.info(f"📁 Project: {self.project_dir or 'current directory'}")
|
|
165
|
+
logger.info(f"🌐 Transport: {transport}\n")
|
|
161
166
|
|
|
162
167
|
# Start file watcher
|
|
163
168
|
self.start_file_watcher()
|
|
@@ -170,11 +175,11 @@ class DevServer:
|
|
|
170
175
|
server.run(transport=transport)
|
|
171
176
|
|
|
172
177
|
except KeyboardInterrupt:
|
|
173
|
-
|
|
178
|
+
logger.info("\n\n🛑 Shutting down development server...")
|
|
174
179
|
finally:
|
|
175
180
|
self.running = False
|
|
176
181
|
self.stop_file_watcher()
|
|
177
|
-
|
|
182
|
+
logger.info("👋 Development server stopped")
|
|
178
183
|
|
|
179
184
|
def run(self, transport: str = "stdio"):
|
|
180
185
|
"""Run the development server."""
|
|
@@ -189,7 +194,7 @@ def run_dev_server():
|
|
|
189
194
|
"""Entry point for development server."""
|
|
190
195
|
import argparse
|
|
191
196
|
|
|
192
|
-
parser = argparse.ArgumentParser(description="Run Hanzo
|
|
197
|
+
parser = argparse.ArgumentParser(description="Run Hanzo AI in development mode with hot reload")
|
|
193
198
|
parser.add_argument(
|
|
194
199
|
"--name",
|
|
195
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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""MCP server implementing Hanzo capabilities."""
|
|
2
2
|
|
|
3
3
|
import atexit
|
|
4
|
+
import logging
|
|
4
5
|
import signal
|
|
5
6
|
import threading
|
|
6
7
|
import time
|
|
@@ -15,6 +16,9 @@ except ImportError:
|
|
|
15
16
|
# Fallback for older MCP versions
|
|
16
17
|
from mcp.server import FastMCP
|
|
17
18
|
|
|
19
|
+
# Import our enhanced server
|
|
20
|
+
from hanzo_mcp.server_enhanced import EnhancedFastMCP
|
|
21
|
+
|
|
18
22
|
from hanzo_mcp.prompts import register_all_prompts
|
|
19
23
|
from hanzo_mcp.tools import register_all_tools
|
|
20
24
|
|
|
@@ -48,7 +52,7 @@ class HanzoMCPServer:
|
|
|
48
52
|
enabled_tools: dict[str, bool] | None = None,
|
|
49
53
|
disabled_tools: list[str] | None = None,
|
|
50
54
|
):
|
|
51
|
-
"""Initialize the Hanzo
|
|
55
|
+
"""Initialize the Hanzo AI server.
|
|
52
56
|
|
|
53
57
|
Args:
|
|
54
58
|
name: The name of the server
|
|
@@ -71,7 +75,8 @@ class HanzoMCPServer:
|
|
|
71
75
|
enabled_tools: Dictionary of individual tool enable states (default: None)
|
|
72
76
|
disabled_tools: List of tool names to disable (default: None)
|
|
73
77
|
"""
|
|
74
|
-
|
|
78
|
+
# Use enhanced server for automatic context normalization
|
|
79
|
+
self.mcp = mcp_instance if mcp_instance is not None else EnhancedFastMCP(name)
|
|
75
80
|
|
|
76
81
|
# Initialize permissions and command executor
|
|
77
82
|
self.permission_manager = PermissionManager()
|
|
@@ -154,7 +159,10 @@ class HanzoMCPServer:
|
|
|
154
159
|
# Register signal handlers for graceful shutdown
|
|
155
160
|
def signal_handler(signum, frame):
|
|
156
161
|
import sys
|
|
157
|
-
|
|
162
|
+
# Only log if not stdio transport
|
|
163
|
+
if hasattr(self, '_transport') and self._transport != 'stdio':
|
|
164
|
+
logger = logging.getLogger(__name__)
|
|
165
|
+
logger.info("\nShutting down gracefully...")
|
|
158
166
|
self._cleanup_sessions()
|
|
159
167
|
self._shutdown_event.set()
|
|
160
168
|
sys.exit(0)
|
|
@@ -189,7 +197,10 @@ class HanzoMCPServer:
|
|
|
189
197
|
try:
|
|
190
198
|
cleared_count = SessionStorage.clear_all_sessions()
|
|
191
199
|
if cleared_count > 0:
|
|
192
|
-
|
|
200
|
+
# Only log if not stdio transport
|
|
201
|
+
if hasattr(self, '_transport') and self._transport != 'stdio':
|
|
202
|
+
logger = logging.getLogger(__name__)
|
|
203
|
+
logger.info(f"Cleaned up {cleared_count} tmux sessions on shutdown")
|
|
193
204
|
except Exception:
|
|
194
205
|
# Ignore cleanup errors during shutdown
|
|
195
206
|
pass
|
|
@@ -201,6 +212,9 @@ class HanzoMCPServer:
|
|
|
201
212
|
transport: The transport to use (stdio or sse)
|
|
202
213
|
allowed_paths: list of paths that the server is allowed to access
|
|
203
214
|
"""
|
|
215
|
+
# Store transport for later use
|
|
216
|
+
self._transport = transport
|
|
217
|
+
|
|
204
218
|
# Add allowed paths if provided
|
|
205
219
|
allowed_paths_list = allowed_paths or []
|
|
206
220
|
for path in allowed_paths_list:
|