mirror of
https://github.com/hanzoai/mcp.git
synced 2026-10-30 08:02:50 +00:00
- Add docker-publish.yml workflow for ghcr.io - Add production-ready Dockerfile (if missing) - Enable automated builds on push to main - Support for semantic versioning tags
212 lines
6.7 KiB
Python
212 lines
6.7 KiB
Python
"""Configuration tool for Hanzo AI.
|
|
|
|
Git-style config tool for managing settings.
|
|
"""
|
|
|
|
from typing import Annotated, TypedDict, Unpack, final, override, Optional, Dict, Any
|
|
from pathlib import Path
|
|
import json
|
|
|
|
from mcp.server.fastmcp import Context as MCPContext
|
|
from pydantic import Field
|
|
|
|
from hanzo_mcp.tools.common.base import BaseTool
|
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
from hanzo_mcp.tools.config.index_config import IndexConfig, IndexScope
|
|
|
|
|
|
# Parameter types
|
|
Action = Annotated[
|
|
str,
|
|
Field(
|
|
description="Action: get (default), set, list, toggle",
|
|
default="get",
|
|
),
|
|
]
|
|
|
|
Key = Annotated[
|
|
Optional[str],
|
|
Field(
|
|
description="Configuration key (e.g., index.scope, vector.enabled)",
|
|
default=None,
|
|
),
|
|
]
|
|
|
|
Value = Annotated[
|
|
Optional[str],
|
|
Field(
|
|
description="Configuration value",
|
|
default=None,
|
|
),
|
|
]
|
|
|
|
Scope = Annotated[
|
|
str,
|
|
Field(
|
|
description="Config scope: local (project) or global",
|
|
default="local",
|
|
),
|
|
]
|
|
|
|
ConfigPath = Annotated[
|
|
Optional[str],
|
|
Field(
|
|
description="Path for project-specific config",
|
|
default=None,
|
|
),
|
|
]
|
|
|
|
|
|
class ConfigParams(TypedDict, total=False):
|
|
"""Parameters for config tool."""
|
|
action: str
|
|
key: Optional[str]
|
|
value: Optional[str]
|
|
scope: str
|
|
path: Optional[str]
|
|
|
|
|
|
@final
|
|
class ConfigTool(BaseTool):
|
|
"""Git-style configuration management tool."""
|
|
|
|
def __init__(self, permission_manager: PermissionManager):
|
|
"""Initialize config tool."""
|
|
super().__init__(permission_manager)
|
|
self.index_config = IndexConfig()
|
|
|
|
@property
|
|
@override
|
|
def name(self) -> str:
|
|
"""Get the tool name."""
|
|
return "config"
|
|
|
|
@property
|
|
@override
|
|
def description(self) -> str:
|
|
"""Get the tool description."""
|
|
return """Git-style configuration. Actions: get (default), set, list, toggle.
|
|
|
|
Usage:
|
|
config index.scope
|
|
config --action set index.scope project
|
|
config --action list
|
|
config --action toggle index.scope --path ./project"""
|
|
|
|
@override
|
|
async def call(
|
|
self,
|
|
ctx: MCPContext,
|
|
**params: Unpack[ConfigParams],
|
|
) -> str:
|
|
"""Execute config operation."""
|
|
tool_ctx = self.create_tool_context(ctx)
|
|
|
|
# Extract parameters
|
|
action = params.get("action", "get")
|
|
key = params.get("key")
|
|
value = params.get("value")
|
|
scope = params.get("scope", "local")
|
|
path = params.get("path")
|
|
|
|
# Route to handler
|
|
if action == "get":
|
|
return await self._handle_get(key, scope, path, tool_ctx)
|
|
elif action == "set":
|
|
return await self._handle_set(key, value, scope, path, tool_ctx)
|
|
elif action == "list":
|
|
return await self._handle_list(scope, path, tool_ctx)
|
|
elif action == "toggle":
|
|
return await self._handle_toggle(key, scope, path, tool_ctx)
|
|
else:
|
|
return f"Error: Unknown action '{action}'. Valid actions: get, set, list, toggle"
|
|
|
|
async def _handle_get(self, key: Optional[str], scope: str, path: Optional[str], tool_ctx) -> str:
|
|
"""Get configuration value."""
|
|
if not key:
|
|
return "Error: key required for get action"
|
|
|
|
# Handle index scope
|
|
if key == "index.scope":
|
|
current_scope = self.index_config.get_scope(path)
|
|
return f"index.scope={current_scope.value}"
|
|
|
|
# Handle tool-specific settings
|
|
if "." in key:
|
|
tool, setting = key.split(".", 1)
|
|
if setting == "enabled":
|
|
enabled = self.index_config.is_indexing_enabled(tool)
|
|
return f"{key}={enabled}"
|
|
|
|
return f"Unknown key: {key}"
|
|
|
|
async def _handle_set(self, key: Optional[str], value: Optional[str], scope: str, path: Optional[str], tool_ctx) -> str:
|
|
"""Set configuration value."""
|
|
if not key:
|
|
return "Error: key required for set action"
|
|
if not value:
|
|
return "Error: value required for set action"
|
|
|
|
# Handle index scope
|
|
if key == "index.scope":
|
|
try:
|
|
new_scope = IndexScope(value)
|
|
self.index_config.set_scope(new_scope, path if scope == "local" else None)
|
|
return f"Set {key}={value} ({'project' if path else 'global'})"
|
|
except ValueError:
|
|
return f"Error: Invalid scope value '{value}'. Valid: project, global, auto"
|
|
|
|
# Handle tool-specific settings
|
|
if "." in key:
|
|
tool, setting = key.split(".", 1)
|
|
if setting == "enabled":
|
|
enabled = value.lower() in ["true", "yes", "1", "on"]
|
|
self.index_config.set_indexing_enabled(tool, enabled)
|
|
return f"Set {key}={enabled}"
|
|
|
|
return f"Unknown key: {key}"
|
|
|
|
async def _handle_list(self, scope: str, path: Optional[str], tool_ctx) -> str:
|
|
"""List all configuration."""
|
|
status = self.index_config.get_status()
|
|
|
|
output = ["=== Configuration ==="]
|
|
output.append(f"\nDefault scope: {status['default_scope']}")
|
|
|
|
if path:
|
|
current_scope = self.index_config.get_scope(path)
|
|
output.append(f"Current path scope: {current_scope.value}")
|
|
|
|
output.append(f"\nProjects with custom config: {status['project_count']}")
|
|
|
|
output.append("\nTool settings:")
|
|
for tool, settings in status["tools"].items():
|
|
output.append(f" {tool}:")
|
|
output.append(f" enabled: {settings['enabled']}")
|
|
output.append(f" per_project: {settings['per_project']}")
|
|
|
|
return "\n".join(output)
|
|
|
|
async def _handle_toggle(self, key: Optional[str], scope: str, path: Optional[str], tool_ctx) -> str:
|
|
"""Toggle configuration value."""
|
|
if not key:
|
|
return "Error: key required for toggle action"
|
|
|
|
# Handle index scope toggle
|
|
if key == "index.scope":
|
|
new_scope = self.index_config.toggle_scope(path if scope == "local" else None)
|
|
return f"Toggled index.scope to {new_scope.value}"
|
|
|
|
# Handle tool enable/disable toggle
|
|
if "." in key:
|
|
tool, setting = key.split(".", 1)
|
|
if setting == "enabled":
|
|
current = self.index_config.is_indexing_enabled(tool)
|
|
self.index_config.set_indexing_enabled(tool, not current)
|
|
return f"Toggled {key} to {not current}"
|
|
|
|
return f"Cannot toggle key: {key}"
|
|
|
|
def register(self, mcp_server) -> None:
|
|
"""Register this tool with the MCP server."""
|
|
pass |