#!/usr/bin/env bash set -euo pipefail MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}" MODE="apply" RUNTIME="all" STRICT_CHECK=0 PKG="@modelcontextprotocol/server-sequential-thinking" err() { echo "[mosaic-seq] ERROR: $*" >&2; } log() { echo "[mosaic-seq] $*"; } while [[ $# -gt 0 ]]; do case "$1" in --check) MODE="check" shift ;; --runtime) if [[ $# -lt 2 ]]; then err "--runtime requires a value: claude|codex|opencode|all" exit 2 fi RUNTIME="$2" shift 2 ;; --strict) STRICT_CHECK=1 shift ;; *) err "Unknown argument: $1" exit 2 ;; esac done case "$RUNTIME" in all|claude|codex|opencode) ;; *) err "Invalid runtime: $RUNTIME (expected claude|codex|opencode|all)" exit 2 ;; esac require_binary() { local name="$1" if ! command -v "$name" >/dev/null 2>&1; then err "Required binary missing: $name" return 1 fi } check_software() { require_binary node require_binary npx } warm_package() { local timeout_sec="${MOSAIC_SEQ_WARM_TIMEOUT_SEC:-15}" if command -v timeout >/dev/null 2>&1; then timeout "$timeout_sec" npx -y "$PKG" --help >/dev/null 2>&1 else npx -y "$PKG" --help >/dev/null 2>&1 fi } check_claude_config() { python3 - <<'PY' import json from pathlib import Path p = Path.home() / ".claude" / "settings.json" if not p.exists(): raise SystemExit(1) try: data = json.loads(p.read_text(encoding="utf-8")) except Exception: raise SystemExit(1) mcp = data.get("mcpServers") if not isinstance(mcp, dict): raise SystemExit(1) entry = mcp.get("sequential-thinking") if not isinstance(entry, dict): raise SystemExit(1) if entry.get("command") != "npx": raise SystemExit(1) args = entry.get("args") if args != ["-y", "@modelcontextprotocol/server-sequential-thinking"]: raise SystemExit(1) PY } apply_claude_config() { python3 - <<'PY' import json from pathlib import Path p = Path.home() / ".claude" / "settings.json" p.parent.mkdir(parents=True, exist_ok=True) if p.exists(): try: data = json.loads(p.read_text(encoding="utf-8")) except Exception: data = {} else: data = {} mcp = data.get("mcpServers") if not isinstance(mcp, dict): mcp = {} mcp["sequential-thinking"] = { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-sequential-thinking"] } data["mcpServers"] = mcp p.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") PY } check_codex_config() { local cfg="$HOME/.codex/config.toml" [[ -f "$cfg" ]] || return 1 grep -Eq '^\[mcp_servers\.(sequential-thinking|sequential_thinking)\]' "$cfg" && \ grep -q '^command = "npx"' "$cfg" && \ grep -q '@modelcontextprotocol/server-sequential-thinking' "$cfg" } apply_codex_config() { local cfg="$HOME/.codex/config.toml" mkdir -p "$(dirname "$cfg")" [[ -f "$cfg" ]] || touch "$cfg" local tmp tmp="$(mktemp)" awk ' BEGIN { skip = 0 } /^\[mcp_servers\.(sequential-thinking|sequential_thinking)\]/ { skip = 1; next } skip && /^\[/ { skip = 0 } !skip { print } ' "$cfg" > "$tmp" mv "$tmp" "$cfg" { echo "" echo "[mcp_servers.sequential-thinking]" echo "command = \"npx\"" echo "args = [\"-y\", \"@modelcontextprotocol/server-sequential-thinking\"]" } >> "$cfg" } check_opencode_config() { python3 - <<'PY' import json from pathlib import Path p = Path.home() / ".config" / "opencode" / "config.json" if not p.exists(): raise SystemExit(1) try: data = json.loads(p.read_text(encoding="utf-8")) except Exception: raise SystemExit(1) mcp = data.get("mcp") if not isinstance(mcp, dict): raise SystemExit(1) entry = mcp.get("sequential-thinking") if not isinstance(entry, dict): raise SystemExit(1) if entry.get("type") != "local": raise SystemExit(1) if entry.get("command") != ["npx", "-y", "@modelcontextprotocol/server-sequential-thinking"]: raise SystemExit(1) if entry.get("enabled") is not True: raise SystemExit(1) PY } apply_opencode_config() { python3 - <<'PY' import json from pathlib import Path p = Path.home() / ".config" / "opencode" / "config.json" p.parent.mkdir(parents=True, exist_ok=True) if p.exists(): try: data = json.loads(p.read_text(encoding="utf-8")) except Exception: data = {} else: data = {} mcp = data.get("mcp") if not isinstance(mcp, dict): mcp = {} mcp["sequential-thinking"] = { "type": "local", "command": ["npx", "-y", "@modelcontextprotocol/server-sequential-thinking"], "enabled": True } data["mcp"] = mcp p.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") PY } check_runtime_config() { case "$RUNTIME" in all) check_claude_config check_codex_config check_opencode_config ;; claude) check_claude_config ;; codex) check_codex_config ;; opencode) check_opencode_config ;; esac } apply_runtime_config() { case "$RUNTIME" in all) apply_claude_config apply_codex_config apply_opencode_config ;; claude) apply_claude_config ;; codex) apply_codex_config ;; opencode) apply_opencode_config ;; esac } if [[ "$MODE" == "check" ]]; then check_software check_runtime_config # Runtime launch checks should be local/fast by default. if [[ "$STRICT_CHECK" -eq 1 || "${MOSAIC_SEQ_CHECK_WARM:-0}" == "1" ]]; then if ! warm_package; then err "sequential-thinking package warm-up failed in strict mode" exit 1 fi fi log "sequential-thinking MCP is configured and available (${RUNTIME})" exit 0 fi check_software if ! warm_package; then err "Unable to warm sequential-thinking package (npx timeout/failure)" exit 1 fi apply_runtime_config log "sequential-thinking MCP configured (${RUNTIME})"