AgentToolStore / client /src /toolstore /config_manager.py
ToolStore Agent
fix: client web UI displays online toolsets from flat-list format, default URL to HF Space, browse page shows function bindings
293585c
Raw
History Blame Contribute Delete
7.26 kB
import json
import os
from pathlib import Path
from typing import Dict, Any, Optional, List
# Well-known persistent path inside the Docker container (mounted volume).
_DOCKER_PERSISTENT_DIR = Path("/app/data/toolstore")
class ConfigManager:
def __init__(self, config_dir: Optional[Path] = None):
if config_dir:
self.config_dir = Path(config_dir)
elif os.environ.get("TOOLSTORE_HOME"):
self.config_dir = Path(os.environ["TOOLSTORE_HOME"])
elif _DOCKER_PERSISTENT_DIR.exists() or _DOCKER_PERSISTENT_DIR.parent.exists():
# Docker container with persistent volume mount — use it
self.config_dir = _DOCKER_PERSISTENT_DIR
else:
self.config_dir = Path.home() / ".toolstore"
self.config_file = self.config_dir / "settings.json"
self._legacy_file = self.config_dir / "config.json" # pre-rename
self.config_dir.mkdir(parents=True, exist_ok=True)
self.config = self._load_defaults()
def _load_defaults(self) -> Dict[str, Any]:
defaults = {
"registry_url": "https://mrw33554432-agenttoolstore.hf.space/index.json",
"mcpServers": {},
"skill_dirs": [],
"toolset_dirs": [],
"server": {
"enabled": False,
"mode": "stdio", # stdio or sse
"sse_port": 9090,
"sse_host": "127.0.0.1",
},
"docker": {
"default_image": "quay.io/jupyter/scipy-notebook",
},
}
# When running in Docker, default skill dir to the persistent volume
# so installed skills survive container restarts.
if _DOCKER_PERSISTENT_DIR.parent.exists():
defaults["skill_dirs"] = [str(_DOCKER_PERSISTENT_DIR / "skills")]
return defaults
# ----------------------------------------------------------------
# Persistence
# ----------------------------------------------------------------
def load(self):
"""Load settings from disk. Auto-migrates from old 'config.json' name."""
# Migration: if settings.json doesn't exist but config.json does, rename it
if not self.config_file.exists() and self._legacy_file.exists():
self._legacy_file.rename(self.config_file)
if self.config_file.exists():
try:
with open(self.config_file, "r", encoding="utf-8") as f:
loaded = json.load(f)
self.config.update(loaded)
except json.JSONDecodeError:
pass
else:
self.save()
def save(self):
with open(self.config_file, "w", encoding="utf-8") as f:
json.dump(self.config, f, indent=2)
# ----------------------------------------------------------------
# Registry
# ----------------------------------------------------------------
def get_registry_url(self) -> str:
# Env var overrides everything — used in CI / ephemeral contexts
if os.environ.get("TOOLSTORE_REGISTRY_URL"):
return os.environ["TOOLSTORE_REGISTRY_URL"]
return self.config.get("registry_url", "http://localhost:8000/online_index")
# ----------------------------------------------------------------
# MCP servers
def get_mcp_servers(self) -> Dict[str, Any]:
return self.config.get("mcpServers", {})
def set_mcp_server(self, name: str, server_config: Dict[str, Any]) -> None:
self.config.setdefault("mcpServers", {})[name] = server_config
self.save()
def add_mcp_docker_server(
self, name: str, image: str,
entrypoint: list[str] | None = None,
) -> None:
"""Convenience: register a Docker-based MCP server."""
cfg: Dict[str, Any] = {
"type": "docker",
"image": image,
"entrypoint": entrypoint or ["python", "-m", "server"],
}
self.config.setdefault("mcpServers", {})[name] = cfg
self.save()
def remove_mcp_server(self, name: str) -> None:
self.config.get("mcpServers", {}).pop(name, None)
self.save()
# ----------------------------------------------------------------
# Skill directories
# ----------------------------------------------------------------
def get_skill_dirs(self) -> List[str]:
return self.config.get("skill_dirs", [])
def add_skill_dir(self, path: str) -> None:
dirs: list = self.config.setdefault("skill_dirs", [])
if path not in dirs:
dirs.append(path)
self.save()
def remove_skill_dir(self, path: str) -> None:
dirs: list = self.config.get("skill_dirs", [])
if path in dirs:
dirs.remove(path)
self.save()
# ----------------------------------------------------------------
# Server mode
# ----------------------------------------------------------------
def get_server_config(self) -> Dict[str, Any]:
return self.config.get("server", {})
def set_server_mode(self, enabled: bool, mode: str = "stdio",
port: int = 9090, host: str = "127.0.0.1") -> None:
self.config["server"] = {
"enabled": enabled,
"mode": mode,
"sse_port": port,
"sse_host": host,
}
self.save()
# ----------------------------------------------------------------
# Auth tokens
# ----------------------------------------------------------------
def save_token(self, token: str):
creds_file = self.config_dir / "credentials"
with open(creds_file, "w", encoding="utf-8") as f:
f.write(token)
def get_token(self) -> Optional[str]:
creds_file = self.config_dir / "credentials"
if creds_file.exists():
return creds_file.read_text(encoding="utf-8").strip()
return None
# ----------------------------------------------------------------
# Toolset directories
# ----------------------------------------------------------------
def get_toolset_dirs(self) -> List[str]:
return self.config.get("toolset_dirs", [])
def add_toolset_dir(self, path: str) -> None:
dirs: list = self.config.setdefault("toolset_dirs", [])
if path not in dirs:
dirs.append(path)
self.save()
def remove_toolset_dir(self, path: str) -> None:
dirs: list = self.config.get("toolset_dirs", [])
if path in dirs:
dirs.remove(path)
self.save()
# ----------------------------------------------------------------
# Docker worker image (still needed for toolset execution sandbox)
# ----------------------------------------------------------------
def get_default_docker_image(self) -> str:
"""Return the default Docker image for the warm worker sandbox."""
return self.config.get("docker", {}).get("default_image", "python:3.11-slim")
def set_default_docker_image(self, image: str) -> None:
"""Set the default Docker image for the warm worker sandbox."""
self.config.setdefault("docker", {})["default_image"] = image
self.save()