263 lines
5.8 KiB
Bash
Executable File
263 lines
5.8 KiB
Bash
Executable File
#!/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})"
|