Created
May 28, 2026 17:30
-
-
Save ardzz/3a2e1247bcb1b18aa95d9a32455e8f56 to your computer and use it in GitHub Desktop.
Reset opencode MCP OAuth state — fixes {"detail":"Unknown client_id"} and similar refresh errors. Backup-safe, idempotent, supports --list/--dry-run.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # opencode-mcp-reset — Reset OAuth state for a single MCP server in opencode. | |
| # | |
| # Use this when opencode shows: {"detail":"Unknown client_id"} or any OAuth | |
| # refresh error. It removes the broken entry from opencode's mcp-auth.json so | |
| # opencode triggers a fresh OAuth flow next time you use that MCP. | |
| # | |
| # Usage: | |
| # opencode-mcp-reset # reset 'consensus' (default) | |
| # opencode-mcp-reset <mcp-name> # reset a specific MCP | |
| # opencode-mcp-reset --list # show stored MCP auth entries | |
| # opencode-mcp-reset --dry-run NAME # preview only, no changes | |
| # opencode-mcp-reset --help | |
| # | |
| # After running: restart opencode and invoke the MCP — a browser-based OAuth | |
| # flow will start and a fresh client_id will be registered. | |
| set -euo pipefail | |
| # ─── Colors (only if stdout is a TTY) ────────────────────────────────────── | |
| if [[ -t 1 ]]; then | |
| R=$'\033[31m'; G=$'\033[32m'; Y=$'\033[33m'; B=$'\033[34m'; D=$'\033[2m'; N=$'\033[0m' | |
| else | |
| R=""; G=""; Y=""; B=""; D=""; N="" | |
| fi | |
| die() { echo "${R}error:${N} $*" >&2; exit 1; } | |
| info() { echo "${B}::${N} $*"; } | |
| ok() { echo "${G}✓${N} $*"; } | |
| warn() { echo "${Y}!${N} $*"; } | |
| # ─── Locate mcp-auth.json across platforms ───────────────────────────────── | |
| find_auth_file() { | |
| local candidates=( | |
| "${XDG_DATA_HOME:-$HOME/.local/share}/opencode/mcp-auth.json" | |
| "$HOME/Library/Application Support/opencode/mcp-auth.json" # macOS | |
| "${APPDATA:-$HOME/AppData/Roaming}/opencode/mcp-auth.json" # Windows (Git Bash/WSL) | |
| ) | |
| for f in "${candidates[@]}"; do | |
| [[ -f "$f" ]] && { echo "$f"; return 0; } | |
| done | |
| return 1 | |
| } | |
| # ─── Argument parsing ────────────────────────────────────────────────────── | |
| DRY_RUN=0 | |
| LIST=0 | |
| MCP="" | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| -h|--help) | |
| sed -n '2,17p' "$0" | sed 's/^# \{0,1\}//' | |
| exit 0 ;; | |
| -l|--list) LIST=1; shift ;; | |
| -n|--dry-run) DRY_RUN=1; shift ;; | |
| -*) die "unknown flag: $1" ;; | |
| *) MCP="$1"; shift ;; | |
| esac | |
| done | |
| [[ -z "$MCP" && $LIST -eq 0 ]] && MCP="consensus" | |
| # ─── Tool checks ─────────────────────────────────────────────────────────── | |
| command -v python3 >/dev/null 2>&1 \ | |
| || die "python3 is required (used for safe JSON editing)" | |
| AUTH_FILE="$(find_auth_file)" \ | |
| || die "mcp-auth.json not found in any known opencode data dir. | |
| Checked: | |
| \$XDG_DATA_HOME/opencode/mcp-auth.json | |
| ~/Library/Application Support/opencode/mcp-auth.json | |
| \$APPDATA/opencode/mcp-auth.json | |
| Is opencode installed and have you used MCP at least once?" | |
| info "auth file: ${D}${AUTH_FILE}${N}" | |
| # ─── --list mode ─────────────────────────────────────────────────────────── | |
| if [[ $LIST -eq 1 ]]; then | |
| python3 - "$AUTH_FILE" <<'PY' | |
| import json, sys, datetime | |
| with open(sys.argv[1]) as f: d = json.load(f) | |
| if not d: | |
| print(" (empty — no MCPs have completed OAuth yet)"); sys.exit() | |
| for name, cfg in d.items(): | |
| tok = cfg.get("tokens", {}) if isinstance(cfg, dict) else {} | |
| exp = tok.get("expiresAt") | |
| when = "" | |
| if isinstance(exp, (int, float)): | |
| # opencode stores seconds (sometimes float) | |
| ts = exp if exp < 1e12 else exp / 1000 | |
| try: | |
| when = " (expires " + datetime.datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M") + ")" | |
| except Exception: | |
| pass | |
| print(f" - {name}{when}") | |
| PY | |
| exit 0 | |
| fi | |
| # ─── Reset mode ──────────────────────────────────────────────────────────── | |
| EXISTS=$(python3 -c " | |
| import json, sys | |
| with open('$AUTH_FILE') as f: d = json.load(f) | |
| print('yes' if '$MCP' in d else 'no') | |
| ") | |
| if [[ "$EXISTS" != "yes" ]]; then | |
| warn "MCP '${MCP}' tidak ada di ${AUTH_FILE}. Tidak ada yang dihapus." | |
| info "Gunakan ${D}--list${N} untuk melihat entry yang tersedia." | |
| exit 0 | |
| fi | |
| if [[ $DRY_RUN -eq 1 ]]; then | |
| warn "DRY-RUN: entry '${MCP}' AKAN dihapus. Tidak ada perubahan dilakukan." | |
| exit 0 | |
| fi | |
| # Backup with timestamp | |
| BACKUP="${AUTH_FILE}.bak.$(date +%s)" | |
| cp "$AUTH_FILE" "$BACKUP" | |
| ok "backup: ${D}${BACKUP}${N}" | |
| # Remove the entry (atomic via temp file → rename) | |
| TMP="$(mktemp "${AUTH_FILE}.tmp.XXXXXX")" | |
| python3 - "$AUTH_FILE" "$MCP" "$TMP" <<'PY' | |
| import json, sys | |
| src, mcp, dst = sys.argv[1:4] | |
| with open(src) as f: d = json.load(f) | |
| d.pop(mcp, None) | |
| with open(dst, "w") as f: json.dump(d, f, indent=2) | |
| PY | |
| mv "$TMP" "$AUTH_FILE" | |
| ok "entry '${MCP}' dihapus dari mcp-auth.json" | |
| # ─── Next steps ──────────────────────────────────────────────────────────── | |
| cat <<EOF | |
| ${G}Selesai.${N} Langkah selanjutnya: | |
| 1. Restart opencode (tutup & buka lagi sesinya). | |
| 2. Panggil tool dari MCP '${MCP}' — opencode akan menampilkan URL OAuth. | |
| 3. Buka URL itu di browser, login, beri izin. | |
| 4. Selesai — clientId baru akan tersimpan otomatis di mcp-auth.json. | |
| Restore backup jika perlu: | |
| ${D}cp "${BACKUP}" "${AUTH_FILE}"${N} | |
| EOF |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment