Register the self-hosted Sentry MCP server and keep OAuth sign-ins across updates

link-mcp gains a stdio transport: the servers row names a launcher under user/agents/mcp/, run from the checkout so every machine runs the same script, and the token reaches it as MCP_TOKEN. sentry-gbrown is the first, pointing Sentry's MCP server at sentry.gbrown.org with a read-only token from the ignored env file.

A server already registered exactly as its row would register it is now left alone. Re-adding it every run threw away the sign-in of the OAuth servers, bill-tracker and atlassian, on every update.
This commit is contained in:
Gabriel Brown committed 2026-10-01 17:33:12 -04:00
1 parent 58a64d6cee
commit eb0056ac94
3 files changed
+79 -15

No files matched your search

+58 -14
View File
@@ -2,7 +2,8 @@
# 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
# An MCP server is a URL plus a bearer token (or, for a stdio server, a launcher
# script plus a 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
@@ -62,17 +63,14 @@ fi
registered=0
skipped=0
while read -r name transport url token_var _rest; do
while read -r name transport target token_var _rest; do
case "${name:-}" in ''|'#'*) continue ;; esac
if [[ -z "${transport:-}" || -z "${url:-}" ]]; then
if [[ -z "${transport:-}" || -z "${target:-}" ]]; then
warn "Ignoring malformed row for '$name'."
continue
fi
# The token normally travels as an Authorization header. A server that only
# reads it from the query string (Penpot's userToken) marks the spot with
# {TOKEN} in its URL instead, and gets no header.
header=()
token=""
if [[ -n "${token_var:-}" ]]; then
token="${!token_var:-}"
if [[ -z "$token" ]]; then
@@ -80,20 +78,66 @@ while read -r name transport url token_var _rest; do
skipped=$((skipped + 1))
continue
fi
fi
# An http server's token normally travels as an Authorization header. One
# that only reads it from the query string (Penpot's userToken) marks the
# spot with {TOKEN} in its URL instead, and gets no header. A stdio server's
# target is a launcher under user/agents/mcp/, run from this checkout so
# every machine runs the same script, and its token arrives as MCP_TOKEN.
url="$target"
args=()
if [[ "$transport" == stdio ]]; then
launcher="$MCP_DIR/$target"
if [[ ! -x "$launcher" ]]; then
warn "Skipping $name: $launcher is not an executable launcher."
skipped=$((skipped + 1))
continue
fi
[[ -n "$token" ]] && args=(-e "MCP_TOKEN=$token")
elif [[ -n "$token" ]]; then
if [[ "$url" == *'{TOKEN}'* ]]; then
url="${url//\{TOKEN\}/$token}"
else
header=(-H "Authorization: $token")
args=(-H "Authorization: $token")
fi
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.
# A server already registered exactly as this row would register it is left
# alone. Removing and re-adding it would throw away the OAuth session of a
# server that signs in on its own (bill-tracker, atlassian), so every
# upgrade would sign them out again.
if [[ "$transport" == stdio ]]; then
want="$(jq -cn --arg c "$launcher" --arg t "$token" \
'{command: $c, env: (if $t == "" then null else {MCP_TOKEN: $t} end)}')"
have="$(jq -c --arg n "$name" \
'.mcpServers[$n] // {} | {command, env: (if (.env // {}) == {} then null else .env end)}' \
"$HOME/.claude.json" 2>/dev/null || true)"
else
auth=""
[[ "${args[0]:-}" == -H ]] && auth="${args[1]#Authorization: }"
want="$(jq -cn --arg u "$url" --arg a "$auth" \
'{url: $u, auth: (if $a == "" then null else $a end)}')"
have="$(jq -c --arg n "$name" \
'.mcpServers[$n] // {} | {url, auth: (.headers.Authorization // null)}' \
"$HOME/.claude.json" 2>/dev/null || true)"
fi
if [[ -n "$have" && "$have" == "$want" ]]; then
log "Already registered $name."
registered=$((registered + 1))
continue
fi
# Re-registering is how a new server or 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
if [[ "$transport" == stdio ]]; then
add=(claude mcp add "$name" -s user "${args[@]}" -- "$launcher")
else
add=(claude mcp add --transport "$transport" "$name" "$url" "${args[@]}" -s user)
fi
if "${add[@]}" >/dev/null 2>&1; then
log "Registered $name."
registered=$((registered + 1))
else