Claude Code: connect to DevGrail (MCP + login)
Sets up Claude Code so it can manage DevGrail resources allowed by the supplied API key. Registers the `devgrail` MCP server at user scope using the container's `DEVGRAIL_MCP_URL`. It includes **Copy of Claude Code: sign in without a browser (credentials)** to install the Claude login; Context7 is not installed. The run form asks for the DevGrail API key and the Claude credentials — pasted, or a secret backed by `/home/dev/.claude/.credentials.json` in a workspace that is already signed in, which stays current on its own. An existing registration is left untouched unless **Replace existing registration** is `yes`. **Security:** the API key is stored in cleartext in `~/.claude.json` as an Authorization header. Prefer a narrowly scoped key. After it runs: start `claude` and ask it to list your DevGrail containers.
What it asks for
- DevGrail API keyrequired
- The key Claude Code will send to the DevGrail MCP server. Its grants decide what the agent can see and do — create a scoped key rather than reusing an admin one.
- MCP URL (override)
- Leave empty on current workspace images — the URL comes from the container's DEVGRAIL_MCP_URL. Older images don't set it; then enter it here, e.g. https://devgrail.example.com/mcp
- Replace existing registration
- Choose 'yes' to remove an existing 'devgrail' entry and re-add it — needed after rotating the API key or changing the URL. With 'no' an existing entry is left untouched.
What it runs
The whole script, exactly as it will execute in your workspace. Nothing is hidden — read it before you run it.
#!/usr/bin/env sh
set -eu
log() { printf '[devgrail-mcp] %s\n' "$*"; }
die() { printf '[devgrail-mcp] ERROR: %s\n' "$*" >&2; exit 1; }
MCP_NAME="devgrail"
API_KEY="${DEVGRAIL_API_KEY:-}"
# Workspace images export DEVGRAIL_MCP_URL; the param lets older images override.
MCP_URL="${DEVGRAIL_MCP_URL_OVERRIDE:-${DEVGRAIL_MCP_URL:-}}"
REPLACE="${REPLACE_EXISTING:-no}"
command -v claude >/dev/null 2>&1 \
|| die "the 'claude' CLI is not on PATH — run this in a workspace that has Claude Code installed"
[ -n "$API_KEY" ] || die "no DevGrail API key was supplied"
[ -n "$MCP_URL" ] || die "DEVGRAIL_MCP_URL is not set in this container (older workspace images do not provide it) — fill in the 'MCP URL (override)' parameter, e.g. https://your-devgrail-host/mcp"
if claude mcp get "$MCP_NAME" >/dev/null 2>&1; then
if [ "$REPLACE" = "yes" ]; then
claude mcp remove --scope user "$MCP_NAME" >/dev/null 2>&1 \
|| claude mcp remove "$MCP_NAME" >/dev/null 2>&1 \
|| die "could not remove the existing '$MCP_NAME' entry"
log "removed the existing '$MCP_NAME' entry so it can be re-added"
else
log "MCP server '$MCP_NAME' is already registered — nothing to do."
log "To point it at a new key or URL, re-run with 'Replace existing registration' = yes."
exit 0
fi
fi
# This script body is piped to `sh -s` on stdin, so stdin IS the remaining
# script: keep the CLI from reading it.
claude mcp add --transport http --scope user "$MCP_NAME" "$MCP_URL" \
--header "Authorization: Bearer $API_KEY" </dev/null \
|| die "claude mcp add failed"
claude mcp get "$MCP_NAME" >/dev/null 2>&1 \
|| die "'$MCP_NAME' was added but cannot be read back — check ~/.claude.json"
log "registered MCP server '$MCP_NAME' -> $MCP_URL"
log "Next: run 'claude' and ask it to list your DevGrail containers."
Included scripts
These come along automatically when you add this script.
Claude Code: sign in without a browser (credentials)
#!/usr/bin/env sh
set -eu
umask 077 # anything this script creates is owner-only by default
log() { printf '[claude-setup] %s\n' "$*"; }
warn() { printf '[claude-setup] WARNING: %s\n' "$*" >&2; }
die() { printf '[claude-setup] ERROR: %s\n' "$*" >&2; exit 1; }
CREDS_JSON="${CLAUDE_CODE_CREDENTIALS_JSON:-}"
OVERWRITE="${OVERWRITE_EXISTING_CREDENTIALS:-yes}"
[ -n "$CREDS_JSON" ] || die "no credentials JSON was supplied"
command -v claude >/dev/null 2>&1 \
|| die "the 'claude' CLI is not on PATH — run this in a workspace that has Claude Code installed"
if [ "$(id -u)" -eq 0 ]; then SUDO=""
elif command -v sudo >/dev/null 2>&1; then SUDO="sudo -n"
else SUDO=""
fi
# jq is optional: it only merges an existing ~/.claude.json and sanity-checks the
# credentials. Never fail the run over it — the login matters more.
# Note: this script body is piped to `sh -s` on stdin, so stdin IS the remaining
# script. Every installer gets </dev/null or a prompt would eat the script.
if ! command -v jq >/dev/null 2>&1; then
log "jq not found; attempting install (optional)"
export DEBIAN_FRONTEND=noninteractive
if command -v apt-get >/dev/null 2>&1; then $SUDO apt-get update -qq </dev/null && $SUDO apt-get install -y -qq jq </dev/null || true
elif command -v dnf >/dev/null 2>&1; then $SUDO dnf install -y jq </dev/null || true
elif command -v yum >/dev/null 2>&1; then $SUDO yum install -y jq </dev/null || true
elif command -v apk >/dev/null 2>&1; then $SUDO apk add --no-cache jq </dev/null || true
elif command -v pacman >/dev/null 2>&1; then $SUDO pacman -Sy --noconfirm jq </dev/null || true
elif command -v zypper >/dev/null 2>&1; then $SUDO zypper install -y jq </dev/null || true
elif command -v brew >/dev/null 2>&1; then brew install jq </dev/null || true
fi
command -v jq >/dev/null 2>&1 || warn "continuing without jq"
fi
# -- credentials ---------------------------------------------------------------
mkdir -p "$HOME/.claude"
CREDS="$HOME/.claude/.credentials.json"
if command -v jq >/dev/null 2>&1; then
printf '%s' "$CREDS_JSON" | jq empty >/dev/null 2>&1 \
|| die "the supplied value is not valid JSON — paste the whole file contents, from '{' to '}'"
fi
if [ -f "$CREDS" ] && [ "$OVERWRITE" != "yes" ]; then
log "$CREDS already exists and overwrite is off — leaving it alone"
else
if [ -f "$CREDS" ]; then
cp -p "$CREDS" "$CREDS.bak"
log "backed up previous credentials to $CREDS.bak"
fi
# printf, not a heredoc: the value must land verbatim, with no shell expansion.
printf '%s\n' "$CREDS_JSON" > "$CREDS"
chmod 600 "$CREDS"
log "wrote $CREDS (mode 600)"
fi
# -- skip the onboarding prompt ------------------------------------------------
CLAUDE_JSON="$HOME/.claude.json"
if [ ! -f "$CLAUDE_JSON" ]; then
printf '%s\n' '{"hasCompletedOnboarding":true}' > "$CLAUDE_JSON"
log "created $CLAUDE_JSON"
elif command -v jq >/dev/null 2>&1; then
tmp="$CLAUDE_JSON.devgrail.tmp" # same directory: atomic rename, no cross-device copy
trap 'rm -f "$tmp"' EXIT
jq '.hasCompletedOnboarding = true' "$CLAUDE_JSON" > "$tmp" \
|| die "jq failed to process $CLAUDE_JSON"
[ -s "$tmp" ] || die "jq produced empty output for $CLAUDE_JSON — aborting to avoid data loss"
cp -p "$CLAUDE_JSON" "$CLAUDE_JSON.bak"
mv "$tmp" "$CLAUDE_JSON"
trap - EXIT
log "updated $CLAUDE_JSON (backup: $CLAUDE_JSON.bak)"
else
warn "jq unavailable and $CLAUDE_JSON exists — left untouched; if Claude Code shows the onboarding prompt, set \"hasCompletedOnboarding\": true there by hand"
fi
log "done. Run 'claude' — it should start without asking you to log in."
How to get it
Open your DevGrail workspace, go to Scripts → Official scripts, and choose Download. It is copied into your own library, where you can edit it, run it, or set it as a workspace default — the copy is yours, and later changes here do not touch it.