cauldron-mcp/setup.py

211 lines
8.3 KiB
Python

#!/usr/bin/env python3
"""
Cauldron Cloud MCP — Setup wizard
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}"
# ── Helpers ───────────────────────────────────────────────────────────────────
def _python_executable() -> str:
"""Return the Python executable that is running this script."""
return sys.executable
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"
# Linux / other
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 — it will be backed up and rewritten.")
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 _install_deps() -> bool:
packages = ["mcp>=1.0.0", "httpx>=0.27.0"]
info("Installing Python dependencies...")
result = subprocess.run(
[_python_executable(), "-m", "pip", "install", "--quiet", *packages],
capture_output=True,
text=True,
)
if result.returncode != 0:
err("pip install failed:")
print(result.stderr.strip())
return False
return True
def _check_deps() -> bool:
try:
import mcp # noqa: F401
import httpx # noqa: F401
return True
except ImportError:
return False
def _validate_key(key: str) -> bool:
key = key.strip()
return key.startswith("cldrn_") and len(key) >= 20
# ── Main ──────────────────────────────────────────────────────────────────────
def main() -> None:
print()
print(bold("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"))
print(bold(" Cauldron Cloud — MCP Server Setup"))
print(bold("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"))
print()
# ── 1. Python version check ───────────────────────────────────────────────
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. Dependencies ───────────────────────────────────────────────────────
if _check_deps():
ok("Dependencies already installed (mcp, httpx)")
else:
if not _install_deps():
err("Could not install dependencies. Try manually: pip install mcp httpx")
sys.exit(1)
if not _check_deps():
err("Dependencies installed but import still fails. Check your Python environment.")
sys.exit(1)
ok("Dependencies installed (mcp, httpx)")
# ── 3. Locate server.py ───────────────────────────────────────────────────
server_py = Path(__file__).resolve().parent / "server.py"
if not server_py.exists():
err(f"server.py not found at {server_py}")
sys.exit(1)
ok(f"Server script: {server_py}")
# ── 4. Get the 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...")
portal_url = "https://cauldron.cloud/mcpKeys"
webbrowser.open(portal_url)
info(f"Opened: {portal_url}")
print()
api_key = ""
for attempt 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. Detect Claude Desktop config ───────────────────────────────────────
config_path = _config_path()
print()
info(f"Config file: {config_path}")
config = _read_config(config_path)
# ── 6. Merge mcpServers entry ─────────────────────────────────────────────
python_cmd = _python_executable()
mcp_entry = {
"command": python_cmd,
"args": [str(server_py)],
"env": {"CAULDRON_API_KEY": api_key},
}
if "mcpServers" not in config:
config["mcpServers"] = {}
existing = config["mcpServers"].get("cauldron")
config["mcpServers"]["cauldron"] = mcp_entry
_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")
# ── 7. 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()
if __name__ == "__main__":
main()