⌘K
Scripting MCPSafe scans from the shell — using the REST API directly today, with a dedicated mcpsafe binary on the roadmap.
There is no mcpsafe binary to install today. Scan packages from the shell by calling the REST API directly — the script below covers the common case (submit, poll, fail on bad grade).
mcpsafe.sh — submit, poll, fail on D/FDrop this into a CI pipeline or local shell script. Exits non-zero on a D or F grade so it can gate a deploy.
#!/usr/bin/env bash
# mcpsafe.sh — scan a package and exit non-zero on D/F.
# Usage: ./mcpsafe.sh "@modelcontextprotocol/server-github" [fast|deep]
set -euo pipefail
INPUT="${1:?package or repo required}"
MODE="${2:-fast}"
API="${MCPSAFE_API:-https://api.mcpsafe.io}"
KEY="${MCPSAFE_API_KEY:-}" # optional; raises rate limits
AUTH=()
[[ -n "$KEY" ]] && AUTH=(-H "Authorization: Bearer ${KEY}")
# 1. Submit
SCAN_ID=$(curl -fsS -X POST "${API}/api/v1/scan" \
"${AUTH[@]}" \
-H "Content-Type: application/json" \
-d "{\"input\":\"${INPUT}\",\"mode\":\"${MODE}\"}" \
| jq -r '.data.scan_id // .scan_id')
[[ -z "$SCAN_ID" || "$SCAN_ID" == "null" ]] && { echo "submit failed"; exit 2; }
echo "scan_id=${SCAN_ID}"
# 2. Poll until status=complete (3 s interval, 30 min cap)
DEADLINE=$(( $(date +%s) + 1800 ))
while [[ $(date +%s) -lt $DEADLINE ]]; do
RESULT=$(curl -fsS "${API}/scan/${SCAN_ID}")
STATUS=$(jq -r '.status // "in_progress"' <<<"$RESULT")
case "$STATUS" in
complete) break ;;
failed) echo "scan failed: $(jq -r '.error_message // "unknown"' <<<"$RESULT")"; exit 2 ;;
*) sleep 3 ;;
esac
done
[[ "$STATUS" != "complete" ]] && { echo "scan timed out"; exit 2; }
# 3. Print + gate
GRADE=$(jq -r '.safety_grade' <<<"$RESULT")
SCORE=$(jq -r '.safety_score' <<<"$RESULT")
echo "grade=${GRADE} score=${SCORE} url=https://mcpsafe.io/scan/${SCAN_ID}"
case "$GRADE" in
D|F) exit 1 ;;
*) exit 0 ;;
esacchmod +x mcpsafe.sh
# anonymous fast scan
./mcpsafe.sh "@modelcontextprotocol/server-github"
# authenticated deep scan
export MCPSAFE_API_KEY="mcpsafe_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
./mcpsafe.sh "github.com/owner/private-mcp" deepFor real-time feedback, swap the polling loop for the SSE stream — see Server-Sent Events. Browser EventSource callers can authenticate via the ?token= query parameter since custom headers aren't allowed.
A pre-built workflow is at GitHub Actions; a Bash equivalent is at Claude Code (the audit-mcp.sh example reads .mcp.json and scans every server).
A dedicated mcpsafe CLI is scoped but not committed to a release date. Planned commands include:
mcpsafe scan @modelcontextprotocol/server-github
mcpsafe scan --config ~/.cursor/mcp.json
mcpsafe watch .mcp.json
mcpsafe history
mcpsafe export <scan_id> --format json|sarifWe'll announce on the changelog when the first release ships. Until then, the script above does the same job for almost every CI use case.
Why no binary today?
Every CI integration we've seen so far is satisfied by curl + jq. Shipping a binary just to wrap that adds maintenance burden (signed releases, multi-arch builds, package-manager publishing) without expanding what's possible. We'll ship one when there's a feature it unlocks (offline scanning, local rule packs) — not before.