Register MCP servers through the installer
An MCP server is a URL plus a bearer token, and the token is why this is a stage rather than a manifest line. Panama is public, so the tokens cannot live in it, and neither runtime keeps its server list in a file worth symlinking: Codex writes them into config.toml beside dozens of unrelated settings, and Claude Code into ~/.claude.json. link-mcp registers them through the runtime's own CLI instead. user/agents/mcp/servers is tracked and names which variable carries each token. user/agents/mcp/env holds the tokens and is ignored. A new machine gets the servers by dropping its own env file beside the tracked one. Only Claude Code is handled, and only where the interview enabled personal content. Rewriting a section of somebody's live Codex TOML is a worse failure mode than leaving two lines to paste once.
This commit is contained in:
Executable
+99
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# MCP servers, registered with the agent runtimes on this machine.
|
||||
#
|
||||
# An MCP server is a URL plus a bearer token, and the token is the whole reason
|
||||
# this is a stage rather than a manifest line. Panama is a public repository, so
|
||||
# the tokens cannot live in it, and neither runtime keeps its server list in a
|
||||
# file that could be symlinked anyway: Codex writes them into config.toml beside
|
||||
# dozens of unrelated settings, and Claude Code into ~/.claude.json. There is no
|
||||
# file to point at, so this registers them through the runtime's own CLI.
|
||||
#
|
||||
# What is tracked is user/agents/mcp/servers, which names each server and which
|
||||
# variable carries its token. What is not tracked is user/agents/mcp/env, which
|
||||
# holds the tokens. A new machine gets the servers by dropping its own env file
|
||||
# beside the tracked one and re-running ./install.
|
||||
#
|
||||
# This is personal content, so it obeys the same interview decision link-user
|
||||
# does. A machine that never said yes registers nothing.
|
||||
#
|
||||
# Only Claude Code is handled. Codex stores its servers inside config.toml, and
|
||||
# rewriting a section of somebody's live TOML is a worse failure mode than
|
||||
# leaving two lines for them to paste once.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
log() { echo -e "\033[1;34m[INFO]\033[0m $*"; }
|
||||
warn() { echo -e "\033[1;33m[WARN]\033[0m $*" >&2; }
|
||||
|
||||
PANAMA_PATH="${PANAMA_PATH:-$HOME/.local/share/Panama}"
|
||||
MCP_DIR="$PANAMA_PATH/user/agents/mcp"
|
||||
SERVERS="$MCP_DIR/servers"
|
||||
ENV_FILE="$MCP_DIR/env"
|
||||
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/panama"
|
||||
DECISION="$STATE_DIR/user-content"
|
||||
|
||||
[[ -r "$SERVERS" ]] || { log "No MCP server list; nothing to register."; exit 0; }
|
||||
|
||||
# The same gate link-user uses, read the same way, so one answer governs all
|
||||
# personal content rather than two stages disagreeing about it.
|
||||
decision="$([[ -r "$DECISION" ]] && cat "$DECISION" || printf 'no')"
|
||||
if [[ "$decision" != "yes" ]]; then
|
||||
log "Personal content is not enabled on this machine; no MCP servers registered."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! command -v claude >/dev/null 2>&1; then
|
||||
log "Claude Code is not installed; nothing to register."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Tokens are optional. A machine without the env file still registers any server
|
||||
# that needs no header, and says which ones it skipped rather than failing.
|
||||
if [[ -r "$ENV_FILE" ]]; then
|
||||
set -a
|
||||
# shellcheck source=/dev/null
|
||||
. "$ENV_FILE"
|
||||
set +a
|
||||
else
|
||||
warn "No $ENV_FILE; servers needing a token will be skipped."
|
||||
fi
|
||||
|
||||
registered=0
|
||||
skipped=0
|
||||
|
||||
while read -r name transport url token_var _rest; do
|
||||
case "${name:-}" in ''|'#'*) continue ;; esac
|
||||
if [[ -z "${transport:-}" || -z "${url:-}" ]]; then
|
||||
warn "Ignoring malformed row for '$name'."
|
||||
continue
|
||||
fi
|
||||
|
||||
header=()
|
||||
if [[ -n "${token_var:-}" ]]; then
|
||||
token="${!token_var:-}"
|
||||
if [[ -z "$token" ]]; then
|
||||
warn "Skipping $name: $token_var is not set in $ENV_FILE."
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
fi
|
||||
header=(-H "Authorization: $token")
|
||||
fi
|
||||
|
||||
# Re-registering is how this stays idempotent across upgrades, and how a
|
||||
# rotated token reaches the runtime. Removing first avoids the CLI refusing
|
||||
# a name it already knows. Neither call may print the token, so both are
|
||||
# quiet unless they fail.
|
||||
claude mcp remove "$name" -s user >/dev/null 2>&1 || true
|
||||
if claude mcp add --transport "$transport" "$name" "$url" "${header[@]}" \
|
||||
-s user >/dev/null 2>&1; then
|
||||
log "Registered $name."
|
||||
registered=$((registered + 1))
|
||||
else
|
||||
warn "Could not register $name."
|
||||
skipped=$((skipped + 1))
|
||||
fi
|
||||
done <"$SERVERS"
|
||||
|
||||
log "MCP servers: $registered registered, $skipped skipped."
|
||||
log "Claude Code loads them at start, so restart a running session to pick them up."
|
||||
Reference in New Issue
Block a user