Spaces:
Runtime error
Runtime error
ToolStore Agent
fix: client web UI displays online toolsets from flat-list format, default URL to HF Space, browse page shows function bindings
293585c | 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() | |