mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-27 04:20:09 +00:00
464 lines
16 KiB
Python
464 lines
16 KiB
Python
"""Enhanced command-line interface for the Hanzo AI server with full tool configuration."""
|
|
|
|
import argparse
|
|
import logging
|
|
import os
|
|
from typing import Any, Dict
|
|
|
|
from hanzo_mcp.config import (
|
|
TOOL_REGISTRY,
|
|
HanzoMCPSettings,
|
|
load_settings,
|
|
save_settings,
|
|
)
|
|
from hanzo_mcp.server import HanzoMCPServer
|
|
|
|
|
|
def create_parser() -> argparse.ArgumentParser:
|
|
"""Create the argument parser with all tool configuration options."""
|
|
parser = argparse.ArgumentParser(
|
|
description="Hanzo AI server with comprehensive tool configuration",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
Tool Configuration:
|
|
Each tool can be individually enabled/disabled using CLI flags.
|
|
Use --list-tools to see all available tools and their current status.
|
|
|
|
Configuration Files:
|
|
Global: ~/.config/hanzo/mcp-settings.json
|
|
Project: ./.hanzo-mcp.json or ./.hanzo/mcp-settings.json
|
|
|
|
Examples:
|
|
# Start server with only read tools
|
|
hanzo-mcp --disable-write --disable-edit --disable-multi-edit
|
|
|
|
# Enable agent tool with custom model
|
|
hanzo-mcp --enable-dispatch-agent --agent-model anthropic/claude-3-sonnet
|
|
|
|
# Save current configuration
|
|
hanzo-mcp --save-config
|
|
""",
|
|
)
|
|
|
|
# Basic server options
|
|
server_group = parser.add_argument_group("Server Configuration")
|
|
server_group.add_argument(
|
|
"--name",
|
|
default="hanzo-mcp",
|
|
help="Name of the MCP server (default: hanzo-mcp)",
|
|
)
|
|
server_group.add_argument(
|
|
"--transport",
|
|
choices=["stdio", "sse"],
|
|
default="stdio",
|
|
help="Transport protocol to use (default: stdio)",
|
|
)
|
|
server_group.add_argument(
|
|
"--host",
|
|
default="127.0.0.1",
|
|
help="Host for SSE server (default: 127.0.0.1)",
|
|
)
|
|
server_group.add_argument(
|
|
"--port",
|
|
type=int,
|
|
default=8888,
|
|
help="Port for SSE server (default: 8888)",
|
|
)
|
|
server_group.add_argument(
|
|
"--log-level",
|
|
choices=["DEBUG", "INFO", "WARNING", "ERROR"],
|
|
default="INFO",
|
|
help="Logging level (default: INFO)",
|
|
)
|
|
server_group.add_argument(
|
|
"--command-timeout",
|
|
type=float,
|
|
default=120.0,
|
|
help="Default timeout for command execution in seconds (default: 120.0)",
|
|
)
|
|
|
|
# Path configuration
|
|
path_group = parser.add_argument_group("Path Configuration")
|
|
path_group.add_argument(
|
|
"--allow-path",
|
|
action="append",
|
|
dest="allowed_paths",
|
|
help="Add an allowed path (can be specified multiple times)",
|
|
)
|
|
path_group.add_argument(
|
|
"--project",
|
|
action="append",
|
|
dest="project_paths",
|
|
help="Add a project path (can be specified multiple times)",
|
|
)
|
|
path_group.add_argument(
|
|
"--project-dir",
|
|
help="Single project directory (added to both allowed and project paths)",
|
|
)
|
|
|
|
# Individual tool configuration
|
|
tool_group = parser.add_argument_group("Individual Tool Configuration")
|
|
|
|
# Add CLI flags for each tool
|
|
for tool_name, tool_config in TOOL_REGISTRY.items():
|
|
tool_config.cli_flag.lstrip("-")
|
|
help_text = f"{tool_config.description}"
|
|
|
|
if tool_config.enabled:
|
|
# Tool is enabled by default, add disable flag
|
|
tool_group.add_argument(
|
|
f"--disable-{tool_name.replace('_', '-')}",
|
|
action="store_true",
|
|
help=f"Disable {tool_name} tool: {help_text}",
|
|
)
|
|
else:
|
|
# Tool is disabled by default, add enable flag
|
|
tool_group.add_argument(
|
|
f"--enable-{tool_name.replace('_', '-')}",
|
|
action="store_true",
|
|
help=f"Enable {tool_name} tool: {help_text}",
|
|
)
|
|
|
|
# Category-level tool configuration (for backward compatibility)
|
|
category_group = parser.add_argument_group("Category-level Tool Configuration")
|
|
category_group.add_argument(
|
|
"--disable-write-tools",
|
|
action="store_true",
|
|
help="Disable all write tools (write, edit, multi_edit, content_replace)",
|
|
)
|
|
category_group.add_argument(
|
|
"--disable-search-tools",
|
|
action="store_true",
|
|
help="Disable all search tools (grep, grep_ast)",
|
|
)
|
|
category_group.add_argument(
|
|
"--disable-filesystem-tools",
|
|
action="store_true",
|
|
help="Disable all filesystem tools",
|
|
)
|
|
category_group.add_argument(
|
|
"--disable-jupyter-tools",
|
|
action="store_true",
|
|
help="Disable all Jupyter notebook tools",
|
|
)
|
|
category_group.add_argument(
|
|
"--disable-shell-tools",
|
|
action="store_true",
|
|
help="Disable shell command execution tools",
|
|
)
|
|
category_group.add_argument(
|
|
"--disable-todo-tools",
|
|
action="store_true",
|
|
help="Disable todo management tools",
|
|
)
|
|
|
|
# Agent configuration
|
|
agent_group = parser.add_argument_group("Agent Tool Configuration")
|
|
agent_group.add_argument(
|
|
"--agent-model",
|
|
help="Model name in LiteLLM format (e.g., 'openai/gpt-4o', 'anthropic/claude-3-sonnet')",
|
|
)
|
|
agent_group.add_argument(
|
|
"--agent-max-tokens",
|
|
type=int,
|
|
help="Maximum tokens for agent responses",
|
|
)
|
|
agent_group.add_argument(
|
|
"--agent-api-key",
|
|
help="API key for the LLM provider",
|
|
)
|
|
agent_group.add_argument(
|
|
"--agent-base-url",
|
|
help="Base URL for the LLM provider API endpoint",
|
|
)
|
|
agent_group.add_argument(
|
|
"--agent-max-iterations",
|
|
type=int,
|
|
default=10,
|
|
help="Maximum iterations for agent (default: 10)",
|
|
)
|
|
agent_group.add_argument(
|
|
"--agent-max-tool-uses",
|
|
type=int,
|
|
default=30,
|
|
help="Maximum tool uses for agent (default: 30)",
|
|
)
|
|
|
|
# Vector store configuration
|
|
vector_group = parser.add_argument_group("Vector Store Configuration")
|
|
vector_group.add_argument(
|
|
"--enable-vector-store",
|
|
action="store_true",
|
|
help="Enable local vector store (Infinity database)",
|
|
)
|
|
vector_group.add_argument(
|
|
"--vector-store-path",
|
|
help="Path for vector store data (default: ~/.config/hanzo/vector-store)",
|
|
)
|
|
vector_group.add_argument(
|
|
"--embedding-model",
|
|
default="text-embedding-3-small",
|
|
help="Embedding model for vector store (default: text-embedding-3-small)",
|
|
)
|
|
|
|
# Configuration management
|
|
config_group = parser.add_argument_group("Configuration Management")
|
|
config_group.add_argument(
|
|
"--config-file",
|
|
help="Load configuration from specific file",
|
|
)
|
|
config_group.add_argument(
|
|
"--save-config",
|
|
action="store_true",
|
|
help="Save current configuration to global config file",
|
|
)
|
|
config_group.add_argument(
|
|
"--save-project-config",
|
|
action="store_true",
|
|
help="Save current configuration to project config file",
|
|
)
|
|
config_group.add_argument(
|
|
"--list-tools",
|
|
action="store_true",
|
|
help="List all available tools and their status",
|
|
)
|
|
|
|
# Installation
|
|
install_group = parser.add_argument_group("Installation")
|
|
install_group.add_argument(
|
|
"--install",
|
|
action="store_true",
|
|
help="Install server configuration in Claude Desktop",
|
|
)
|
|
|
|
return parser
|
|
|
|
|
|
def apply_cli_overrides(args: argparse.Namespace) -> Dict[str, Any]:
|
|
"""Convert CLI arguments to configuration overrides."""
|
|
overrides = {}
|
|
|
|
# Server configuration
|
|
server_config = {}
|
|
if hasattr(args, "name") and args.name != "hanzo-mcp":
|
|
server_config["name"] = args.name
|
|
if hasattr(args, "host") and args.host != "127.0.0.1":
|
|
server_config["host"] = args.host
|
|
if hasattr(args, "port") and args.port != 8888:
|
|
server_config["port"] = args.port
|
|
if hasattr(args, "transport") and args.transport != "stdio":
|
|
server_config["transport"] = args.transport
|
|
if hasattr(args, "log_level") and args.log_level != "INFO":
|
|
server_config["log_level"] = args.log_level
|
|
if hasattr(args, "command_timeout") and args.command_timeout != 120.0:
|
|
server_config["command_timeout"] = args.command_timeout
|
|
|
|
if server_config:
|
|
overrides["server"] = server_config
|
|
|
|
# Path configuration
|
|
if hasattr(args, "allowed_paths") and args.allowed_paths:
|
|
overrides["allowed_paths"] = args.allowed_paths
|
|
if hasattr(args, "project_paths") and args.project_paths:
|
|
overrides["project_paths"] = args.project_paths
|
|
if hasattr(args, "project_dir") and args.project_dir:
|
|
overrides["project_dir"] = args.project_dir
|
|
|
|
# Tool configuration
|
|
enabled_tools = {}
|
|
|
|
# Handle individual tool flags
|
|
for tool_name, tool_config in TOOL_REGISTRY.items():
|
|
tool_name.replace("_", "-")
|
|
|
|
if tool_config.enabled:
|
|
# Check for disable flag
|
|
disable_flag = f"disable_{tool_name}"
|
|
if hasattr(args, disable_flag) and getattr(args, disable_flag):
|
|
enabled_tools[tool_name] = False
|
|
else:
|
|
# Check for enable flag
|
|
enable_flag = f"enable_{tool_name}"
|
|
if hasattr(args, enable_flag) and getattr(args, enable_flag):
|
|
enabled_tools[tool_name] = True
|
|
|
|
# Handle category-level disables
|
|
if hasattr(args, "disable_write_tools") and args.disable_write_tools:
|
|
for tool_name in ["write", "edit", "multi_edit", "content_replace"]:
|
|
enabled_tools[tool_name] = False
|
|
|
|
if hasattr(args, "disable_search_tools") and args.disable_search_tools:
|
|
for tool_name in ["grep", "grep_ast"]:
|
|
enabled_tools[tool_name] = False
|
|
|
|
if hasattr(args, "disable_filesystem_tools") and args.disable_filesystem_tools:
|
|
filesystem_tools = [
|
|
"read",
|
|
"write",
|
|
"edit",
|
|
"multi_edit",
|
|
"tree",
|
|
"grep",
|
|
"grep_ast",
|
|
"content_replace",
|
|
]
|
|
for tool_name in filesystem_tools:
|
|
enabled_tools[tool_name] = False
|
|
|
|
if hasattr(args, "disable_jupyter_tools") and args.disable_jupyter_tools:
|
|
for tool_name in ["notebook_read", "notebook_edit"]:
|
|
enabled_tools[tool_name] = False
|
|
|
|
if hasattr(args, "disable_shell_tools") and args.disable_shell_tools:
|
|
enabled_tools["run_command"] = False
|
|
|
|
if hasattr(args, "disable_todo_tools") and args.disable_todo_tools:
|
|
for tool_name in ["todo_read", "todo_write"]:
|
|
enabled_tools[tool_name] = False
|
|
|
|
if enabled_tools:
|
|
overrides["enabled_tools"] = enabled_tools
|
|
|
|
# Agent configuration
|
|
agent_config = {}
|
|
if hasattr(args, "agent_model") and args.agent_model:
|
|
agent_config["model"] = args.agent_model
|
|
agent_config["enabled"] = True
|
|
if hasattr(args, "agent_api_key") and args.agent_api_key:
|
|
agent_config["api_key"] = args.agent_api_key
|
|
if hasattr(args, "agent_base_url") and args.agent_base_url:
|
|
agent_config["base_url"] = args.agent_base_url
|
|
if hasattr(args, "agent_max_tokens") and args.agent_max_tokens:
|
|
agent_config["max_tokens"] = args.agent_max_tokens
|
|
if hasattr(args, "agent_max_iterations") and args.agent_max_iterations != 10:
|
|
agent_config["max_iterations"] = args.agent_max_iterations
|
|
if hasattr(args, "agent_max_tool_uses") and args.agent_max_tool_uses != 30:
|
|
agent_config["max_tool_uses"] = args.agent_max_tool_uses
|
|
|
|
if agent_config:
|
|
overrides["agent"] = agent_config
|
|
|
|
# Vector store configuration
|
|
vector_config = {}
|
|
if hasattr(args, "enable_vector_store") and args.enable_vector_store:
|
|
vector_config["enabled"] = True
|
|
if hasattr(args, "vector_store_path") and args.vector_store_path:
|
|
vector_config["data_path"] = args.vector_store_path
|
|
if (
|
|
hasattr(args, "embedding_model")
|
|
and args.embedding_model != "text-embedding-3-small"
|
|
):
|
|
vector_config["embedding_model"] = args.embedding_model
|
|
|
|
if vector_config:
|
|
overrides["vector_store"] = vector_config
|
|
|
|
return overrides
|
|
|
|
|
|
def list_tools(settings: HanzoMCPSettings) -> None:
|
|
"""List all tools and their current status."""
|
|
logger = logging.getLogger(__name__)
|
|
logger.info("Hanzo AI Tools Status:")
|
|
logger.info("=" * 50)
|
|
|
|
categories = {}
|
|
for tool_name, tool_config in TOOL_REGISTRY.items():
|
|
category = tool_config.category.value
|
|
if category not in categories:
|
|
categories[category] = []
|
|
|
|
enabled = settings.is_tool_enabled(tool_name)
|
|
status = "✅ ENABLED " if enabled else "❌ DISABLED"
|
|
categories[category].append((tool_name, status, tool_config.description))
|
|
|
|
for category, tools in categories.items():
|
|
logger.info(f"\n{category.upper()} TOOLS:")
|
|
logger.info("-" * 30)
|
|
for tool_name, status, description in tools:
|
|
logger.info(f" {status} {tool_name:<15} - {description}")
|
|
|
|
logger.info(f"\nTotal: {len(TOOL_REGISTRY)} tools")
|
|
enabled_count = len(settings.get_enabled_tools())
|
|
logger.info(
|
|
f"Enabled: {enabled_count}, Disabled: {len(TOOL_REGISTRY) - enabled_count}"
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
"""Run the enhanced CLI for the Hanzo AI server."""
|
|
parser = create_parser()
|
|
args = parser.parse_args()
|
|
|
|
# Handle list tools command
|
|
if hasattr(args, "list_tools") and args.list_tools:
|
|
settings = load_settings()
|
|
list_tools(settings)
|
|
return
|
|
|
|
# Load configuration with CLI overrides
|
|
config_overrides = apply_cli_overrides(args)
|
|
project_dir = getattr(args, "project_dir", None)
|
|
settings = load_settings(project_dir=project_dir, config_overrides=config_overrides)
|
|
|
|
# Handle configuration saving
|
|
logger = logging.getLogger(__name__)
|
|
if hasattr(args, "save_config") and args.save_config:
|
|
saved_path = save_settings(settings, global_config=True)
|
|
logger.info(f"Configuration saved to: {saved_path}")
|
|
return
|
|
|
|
if hasattr(args, "save_project_config") and args.save_project_config:
|
|
saved_path = save_settings(settings, global_config=False)
|
|
logger.info(f"Project configuration saved to: {saved_path}")
|
|
return
|
|
|
|
# Handle installation
|
|
if hasattr(args, "install") and args.install:
|
|
from hanzo_mcp.cli import install_claude_desktop_config
|
|
|
|
install_claude_desktop_config(
|
|
settings.server.name,
|
|
settings.allowed_paths,
|
|
)
|
|
return
|
|
|
|
# Set up allowed paths
|
|
allowed_paths = settings.allowed_paths[:]
|
|
if settings.project_dir and settings.project_dir not in allowed_paths:
|
|
allowed_paths.append(settings.project_dir)
|
|
|
|
if not allowed_paths:
|
|
allowed_paths = [os.getcwd()]
|
|
|
|
# Create and run server
|
|
server = HanzoMCPServer(
|
|
name=settings.server.name,
|
|
allowed_paths=allowed_paths,
|
|
project_dir=settings.project_dir,
|
|
agent_model=settings.agent.model,
|
|
agent_max_tokens=settings.agent.max_tokens,
|
|
agent_api_key=settings.agent.api_key,
|
|
agent_base_url=settings.agent.base_url,
|
|
agent_max_iterations=settings.agent.max_iterations,
|
|
agent_max_tool_uses=settings.agent.max_tool_uses,
|
|
enable_agent_tool=settings.agent.enabled
|
|
or settings.is_tool_enabled("dispatch_agent"),
|
|
disable_write_tools=not any(
|
|
settings.is_tool_enabled(t)
|
|
for t in ["write", "edit", "multi_edit", "content_replace"]
|
|
),
|
|
disable_search_tools=not any(
|
|
settings.is_tool_enabled(t) for t in ["grep", "grep_ast"]
|
|
),
|
|
host=settings.server.host,
|
|
port=settings.server.port,
|
|
enabled_tools=settings.enabled_tools, # Pass individual tool configuration
|
|
)
|
|
|
|
server.run(transport=settings.server.transport)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|