Files
python-sdk/pkg/hanzo-mcp/install_hanzo_mcp.py
zeekay e450a908b1 Fix CI Quality Gate failures in hanzo-mcp
- Remove TODO comment in unified_backend.py (stub pattern detected)
- Replace bare except clauses with specific exception types:
  - OSError, FileNotFoundError for command availability checks
  - OSError, ImportError, ValueError, KeyError for TOML parsing
  - OSError, json.JSONDecodeError, KeyError for JSON config reading
  - OSError, PermissionError for file stat checks
- Fix blind exception assertion (Exception -> TypeError) in test
- Apply ruff format and import sorting fixes

All 14 anti-stub and CI tests now pass.
2026-01-17 21:46:04 -08:00

393 lines
13 KiB
Python

#!/usr/bin/env python3
"""
Hanzo MCP Installation Script
============================
Unified installer for Hanzo MCP tools that provides:
1. MCP server installation
2. VS Code extension setup
3. Browser extension setup (future)
4. CLI tools installation
5. Unified backend configuration
Usage:
python install_hanzo_mcp.py --all # Install everything
python install_hanzo_mcp.py --mcp # MCP server only
python install_hanzo_mcp.py --vscode # VS Code extension only
python install_hanzo_mcp.py --cli # CLI tools only
python install_hanzo_mcp.py --check # Check installation
"""
import os
import sys
import json
import shutil
import argparse
import subprocess
from typing import Dict, List, Optional
from pathlib import Path
class HanzoMCPInstaller:
"""Unified installer for all Hanzo MCP components"""
def __init__(self):
self.home = Path.home()
self.hanzo_dir = self.home / ".hanzo"
self.sessions_dir = self.hanzo_dir / "sessions"
self.config_file = self.hanzo_dir / "config.json"
# Create directories
self.hanzo_dir.mkdir(exist_ok=True)
self.sessions_dir.mkdir(exist_ok=True)
def install_all(self) -> bool:
"""Install all components"""
print("🚀 Installing Hanzo MCP - Complete Development Environment")
print("=" * 60)
success = True
success &= self.install_python_packages()
success &= self.setup_mcp_server()
success &= self.install_vscode_extension()
success &= self.install_cli_tools()
success &= self.create_config()
if success:
print("\n✅ Hanzo MCP installation completed successfully!")
print("\nNext steps:")
print("1. Restart VS Code to activate the extension")
print("2. Run `hanzo-mcp` to start the development server")
print("3. Check ~/.hanzo/config.json for configuration options")
else:
print("\n❌ Installation failed. Check errors above.")
return success
def install_python_packages(self) -> bool:
"""Install Python packages with uv"""
print("📦 Installing Python packages...")
try:
# Install hanzo-mcp and all tools
subprocess.run([
"uv", "pip", "install", "-e", ".", "--all-extras"
], check=True, cwd=Path(__file__).parent)
print("✓ Python packages installed")
return True
except subprocess.CalledProcessError as e:
print(f"❌ Failed to install Python packages: {e}")
return False
except FileNotFoundError:
print("❌ uv not found. Please install uv first: curl -LsSf https://astral.sh/uv/install.sh | sh")
return False
def setup_mcp_server(self) -> bool:
"""Setup MCP server for Claude/other AI tools"""
print("🔌 Setting up MCP server...")
# Create MCP server config
mcp_config = {
"mcpServers": {
"hanzo": {
"command": "hanzo-mcp",
"args": ["--server"],
"env": {
"HANZO_SESSION_DIR": str(self.sessions_dir),
"HANZO_CONFIG": str(self.config_file)
}
}
}
}
# Write to Claude MCP config location
claude_config_dir = self.home / ".config" / "claude-desktop"
claude_config_dir.mkdir(parents=True, exist_ok=True)
claude_config_file = claude_config_dir / "claude_desktop_config.json"
try:
if claude_config_file.exists():
# Merge with existing config
with open(claude_config_file) as f:
existing = json.load(f)
existing.update(mcp_config)
mcp_config = existing
with open(claude_config_file, "w") as f:
json.dump(mcp_config, f, indent=2)
print(f"✓ MCP server configured at {claude_config_file}")
return True
except Exception as e:
print(f"❌ Failed to setup MCP server: {e}")
return False
def install_vscode_extension(self) -> bool:
"""Install and build VS Code extension"""
print("🔧 Installing VS Code extension...")
extension_dir = Path(__file__).parent / "vscode-extension"
try:
# Install Node.js dependencies
subprocess.run(["npm", "install"], check=True, cwd=extension_dir)
# Compile TypeScript
subprocess.run(["npm", "run", "compile"], check=True, cwd=extension_dir)
# Package extension
subprocess.run(["npx", "vsce", "package"], check=True, cwd=extension_dir)
# Install extension
vsix_files = list(extension_dir.glob("*.vsix"))
if vsix_files:
subprocess.run([
"code", "--install-extension", str(vsix_files[0])
], check=True)
print("✓ VS Code extension installed")
return True
else:
print("❌ No .vsix file found")
return False
except subprocess.CalledProcessError as e:
print(f"❌ Failed to install VS Code extension: {e}")
print("Note: Make sure you have Node.js, npm, and VS Code installed")
return False
except FileNotFoundError as e:
print(f"❌ Required tool not found: {e}")
return False
def install_cli_tools(self) -> bool:
"""Install CLI tools and create shell aliases"""
print("💻 Installing CLI tools...")
try:
# Create CLI wrapper scripts
cli_script = """#!/usr/bin/env python3
import sys
import asyncio
from hanzo_mcp.dev_tools import DevToolsCore
async def main():
tools = DevToolsCore()
if len(sys.argv) < 2:
print("Usage: hanzo-dev <tool> <args>")
print("Tools: edit, fmt, test, build, lint, guard")
return 1
tool = sys.argv[1]
args = sys.argv[2:]
try:
if tool == "edit" and len(args) >= 2:
result = await tools.edit(target=args[0], op=args[1])
elif tool == "fmt" and len(args) >= 1:
result = await tools.fmt(target=args[0])
elif tool == "test" and len(args) >= 1:
result = await tools.test(target=args[0])
elif tool == "build" and len(args) >= 1:
result = await tools.build(target=args[0])
elif tool == "lint" and len(args) >= 1:
result = await tools.lint(target=args[0])
elif tool == "guard" and len(args) >= 1:
result = await tools.guard(target=args[0])
else:
print(f"Invalid usage for {tool}")
return 1
print(f"Result: {result.ok}")
if result.stdout:
print(f"Output: {result.stdout}")
if result.stderr:
print(f"Error: {result.stderr}")
if result.touched_files:
print(f"Modified files: {result.touched_files}")
return 0 if result.ok else 1
except Exception as e:
print(f"Error: {e}")
return 1
if __name__ == "__main__":
sys.exit(asyncio.run(main()))
"""
# Install CLI script
cli_bin = Path("/usr/local/bin/hanzo-dev")
with open(cli_bin, "w") as f:
f.write(cli_script)
cli_bin.chmod(0o755)
# Create shell aliases
aliases = """
# Hanzo MCP Development Tools
alias hedit='hanzo-dev edit'
alias hfmt='hanzo-dev fmt'
alias htest='hanzo-dev test'
alias hbuild='hanzo-dev build'
alias hlint='hanzo-dev lint'
alias hguard='hanzo-dev guard'
"""
# Add to shell profiles
for shell_profile in [".bashrc", ".zshrc", ".profile"]:
profile_path = self.home / shell_profile
if profile_path.exists():
with open(profile_path, "a") as f:
f.write(f"\n# Hanzo MCP aliases\n{aliases}\n")
print("✓ CLI tools installed")
return True
except Exception as e:
print(f"❌ Failed to install CLI tools: {e}")
return False
def create_config(self) -> bool:
"""Create default configuration"""
print("⚙️ Creating configuration...")
config = {
"version": "1.0.0",
"session_tracking": True,
"codebase_indexing": True,
"workspace_detection": "auto",
"default_language": "auto",
"backends": {
"go": {
"fmt": "goimports",
"local_prefix": "github.com/luxfi"
},
"ts": {
"fmt": "prettier",
"package_manager": "pnpm"
},
"py": {
"fmt": "ruff",
"test": "pytest"
}
},
"guard_rules": [
{
"id": "no_node_in_sdk",
"type": "import",
"glob": "sdk/**",
"forbid_import_prefix": "github.com/luxfi/node/"
},
{
"id": "no_generated_edits",
"type": "generated",
"glob": "api/pb/**",
"forbid_writes": True
}
]
}
try:
with open(self.config_file, "w") as f:
json.dump(config, f, indent=2)
print(f"✓ Configuration created at {self.config_file}")
return True
except Exception as e:
print(f"❌ Failed to create configuration: {e}")
return False
def check_installation(self) -> bool:
"""Check if installation is working"""
print("🔍 Checking Hanzo MCP installation...")
checks = [
("Python package", self.check_python_package),
("MCP server", self.check_mcp_server),
("CLI tools", self.check_cli_tools),
("Configuration", self.check_config),
("Session directory", self.check_session_dir)
]
all_good = True
for name, check_fn in checks:
try:
result = check_fn()
status = "✓" if result else "❌"
print(f"{status} {name}")
all_good &= result
except Exception as e:
print(f"❌ {name}: {e}")
all_good = False
return all_good
def check_python_package(self) -> bool:
"""Check if Python package is installed"""
try:
import hanzo_mcp
return True
except ImportError:
return False
def check_mcp_server(self) -> bool:
"""Check if MCP server is configured"""
claude_config_file = self.home / ".config" / "claude-desktop" / "claude_desktop_config.json"
if not claude_config_file.exists():
return False
try:
with open(claude_config_file) as f:
config = json.load(f)
return "hanzo" in config.get("mcpServers", {})
except (OSError, json.JSONDecodeError, KeyError):
return False
def check_cli_tools(self) -> bool:
"""Check if CLI tools are installed"""
return Path("/usr/local/bin/hanzo-dev").exists()
def check_config(self) -> bool:
"""Check if configuration exists"""
return self.config_file.exists()
def check_session_dir(self) -> bool:
"""Check if session directory exists"""
return self.sessions_dir.exists()
def main():
parser = argparse.ArgumentParser(description="Install Hanzo MCP Development Environment")
parser.add_argument("--all", action="store_true", help="Install all components")
parser.add_argument("--mcp", action="store_true", help="Install MCP server only")
parser.add_argument("--vscode", action="store_true", help="Install VS Code extension only")
parser.add_argument("--cli", action="store_true", help="Install CLI tools only")
parser.add_argument("--check", action="store_true", help="Check installation")
args = parser.parse_args()
if not any([args.all, args.mcp, args.vscode, args.cli, args.check]):
args.all = True # Default to installing everything
installer = HanzoMCPInstaller()
success = True
if args.check:
success = installer.check_installation()
else:
if args.all:
success = installer.install_all()
else:
if args.mcp:
success &= installer.setup_mcp_server()
if args.vscode:
success &= installer.install_vscode_extension()
if args.cli:
success &= installer.install_cli_tools()
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()