#!/usr/bin/env python3 """ Cauldron Cloud MCP — Setup wizard Creates a local virtual environment, installs dependencies, and writes Claude Desktop config automatically. """ import json import os import platform import shutil import subprocess import sys import webbrowser from pathlib import Path # ── Colours ────────────────────────────────────────────────────────────────── def _supports_colour() -> bool: return sys.stdout.isatty() and platform.system() != "Windows" if _supports_colour(): RESET = "\033[0m" BOLD = "\033[1m" GREEN = "\033[32m" YELLOW = "\033[33m" RED = "\033[31m" CYAN = "\033[36m" else: RESET = BOLD = GREEN = YELLOW = RED = CYAN = "" def ok(msg: str) -> None: print(f"{GREEN} ✓ {RESET}{msg}") def info(msg: str) -> None: print(f"{CYAN} → {RESET}{msg}") def warn(msg: str) -> None: print(f"{YELLOW} ⚠ {RESET}{msg}") def err(msg: str) -> None: print(f"{RED} ✗ {RESET}{msg}") def bold(msg: str) -> str: return f"{BOLD}{msg}{RESET}" # ── Paths ───────────────────────────────────────────────────────────────────── REPO_DIR = Path(__file__).resolve().parent VENV_DIR = REPO_DIR / ".venv" SERVER_PY = REPO_DIR / "server.py" PACKAGES = ["mcp>=1.0.0", "httpx>=0.27.0"] def _venv_python() -> Path: """Path to the Python executable inside the local venv.""" if platform.system() == "Windows": return VENV_DIR / "Scripts" / "python.exe" return VENV_DIR / "bin" / "python" def _config_path() -> Path: system = platform.system() if system == "Windows": base = Path(os.environ.get("APPDATA", "~")).expanduser() return base / "Claude" / "claude_desktop_config.json" if system == "Darwin": return Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json" xdg = os.environ.get("XDG_CONFIG_HOME", "") base = Path(xdg) if xdg else Path.home() / ".config" return base / "Claude" / "claude_desktop_config.json" def _read_config(path: Path) -> dict: if path.exists(): try: return json.loads(path.read_text(encoding="utf-8")) except json.JSONDecodeError: warn("Existing config file contains invalid JSON — backing it up.") backup = path.with_suffix(".json.bak") shutil.copy(path, backup) info(f"Backup saved to {backup}") return {} def _write_config(path: Path, data: dict) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8") def _validate_key(key: str) -> bool: key = key.strip() return key.startswith("cldrn_") and len(key) >= 20 # ── Venv + deps ─────────────────────────────────────────────────────────────── def _create_venv() -> bool: info(f"Creating virtual environment in {VENV_DIR} ...") result = subprocess.run( [sys.executable, "-m", "venv", str(VENV_DIR)], capture_output=True, text=True, ) if result.returncode != 0: err("Could not create virtual environment:") print(result.stderr.strip()) return False return True def _install_deps() -> bool: python = _venv_python() info("Installing dependencies into virtual environment...") result = subprocess.run( [str(python), "-m", "pip", "install", "--quiet", "--upgrade", *PACKAGES], capture_output=True, text=True, ) if result.returncode != 0: err("pip install failed:") print(result.stderr.strip()) return False return True def _deps_ok() -> bool: python = _venv_python() if not python.exists(): return False result = subprocess.run( [str(python), "-c", "import mcp, httpx"], capture_output=True, ) return result.returncode == 0 # ── Main ────────────────────────────────────────────────────────────────────── def main() -> None: print() print(bold("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")) print(bold(" Cauldron Cloud — MCP Server Setup")) print(bold("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")) print() # ── 1. Python version ───────────────────────────────────────────────────── if sys.version_info < (3, 10): err(f"Python 3.10+ required (you have {platform.python_version()}).") sys.exit(1) ok(f"Python {platform.python_version()}") # ── 2. server.py present ────────────────────────────────────────────────── if not SERVER_PY.exists(): err(f"server.py not found at {SERVER_PY}") sys.exit(1) ok(f"Server script: {SERVER_PY}") # ── 3. Virtual environment + dependencies ───────────────────────────────── if _deps_ok(): ok("Virtual environment already set up") else: if VENV_DIR.exists(): info("Refreshing existing virtual environment...") elif not _create_venv(): sys.exit(1) if not _install_deps() or not _deps_ok(): err("Could not install dependencies. Check your Python installation.") sys.exit(1) ok("Dependencies installed (mcp, httpx)") python_cmd = str(_venv_python()) # ── 4. API key ──────────────────────────────────────────────────────────── print() print(bold(" Step: Generate your API key")) print() print(" A browser window will open to the Cauldron portal.") print(" Log in, click Generate , copy the key and paste it below.") print() input(" Press ENTER to open the browser...") webbrowser.open("https://cauldron.cloud/mcpKeys") info("Opened: https://cauldron.cloud/mcpKeys") print() api_key = "" for _ in range(3): raw = input(" Paste your API key here: ").strip() if _validate_key(raw): api_key = raw break err("Key must start with cldrn_ and be at least 20 characters. Try again.") else: err("No valid key provided. Run setup.py again when you have a key.") sys.exit(1) ok(f"Key accepted: {api_key[:14]}…") # ── 5. Write Claude Desktop config ──────────────────────────────────────── config_path = _config_path() print() info(f"Config file: {config_path}") config = _read_config(config_path) existing = config.get("mcpServers", {}).get("cauldron") config.setdefault("mcpServers", {})["cauldron"] = { "command": python_cmd, "args": [str(SERVER_PY)], "env": {"CAULDRON_API_KEY": api_key}, } _write_config(config_path, config) if existing: ok("Updated existing cauldron entry in Claude Desktop config") else: ok("Added cauldron entry to Claude Desktop config") # ── 6. Done ─────────────────────────────────────────────────────────────── print() print(bold("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")) print(bold(" Setup complete!")) print(bold("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")) print() print(" Next steps:") print(f" 1. {bold('Restart Claude Desktop')}") print(" 2. Open a new Chat") print(" 3. Look for the 🔌 icon — cauldron should appear in the tool list") print() print(" Try asking Claude:") print(f" {CYAN}\"Show me all open Sell Side deals in the Automotive sector\"{RESET}") print(f" {CYAN}\"Are there any incoming Requests for Help we haven't answered?\"{RESET}") print() print(f" To update in the future: {bold('git pull')} (then restart Claude Desktop)") print() if __name__ == "__main__": main()