Skip to content

Instantly share code, notes, and snippets.

@markusstrasser
Last active March 21, 2026 02:48
Show Gist options
  • Select an option

  • Save markusstrasser/5dfa86257e875c5906ec27526c1c3cd3 to your computer and use it in GitHub Desktop.

Select an option

Save markusstrasser/5dfa86257e875c5906ec27526c1c3cd3 to your computer and use it in GitHub Desktop.
AI coding setup — Claude Code, Gemini, Codex, llmx, emb, MCP servers, skills, daily sync + API keys in macOS Keychain
AI coding setup — Claude Code, Gemini, Codex, llmx, emb, MCP servers, skills, daily sync + API keys in macOS Keychain
#!/usr/bin/env bash
set -euo pipefail
# ============================================================================
# Claude Code + AI Tools + MCP Infrastructure Setup
# Run: curl -sL <gist-url>/setup-friend.sh | bash
# ============================================================================
BOLD='\033[1m'
DIM='\033[2m'
GREEN='\033[32m'
YELLOW='\033[33m'
BLUE='\033[34m'
RED='\033[31m'
RESET='\033[0m'
step() { echo -e "\n${BOLD}${BLUE}▸ $1${RESET}"; }
ok() { echo -e " ${GREEN}✓${RESET} $1"; }
skip() { echo -e " ${DIM}⊘ $1${RESET}"; }
warn() { echo -e " ${YELLOW}⚠ $1${RESET}"; }
fail() { echo -e " ${RED}✗ $1${RESET}"; }
PROJECTS_DIR="$HOME/Projects"
# ── Prerequisites ────────────────────────────────────────────
step "Checking prerequisites"
if ! command -v brew &>/dev/null; then
fail "Homebrew not found. Install: https://brew.sh"
exit 1
fi
ok "Homebrew"
if ! command -v node &>/dev/null; then
step "Installing Node.js via Homebrew"
brew install node
fi
ok "Node.js $(node --version)"
if ! command -v uv &>/dev/null; then
step "Installing uv"
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
fi
ok "uv $(uv --version 2>&1 | head -1)"
if ! command -v git &>/dev/null; then
fail "git not found"
exit 1
fi
ok "git"
if ! command -v just &>/dev/null; then
brew install just
ok "just installed"
else
ok "just"
fi
# ── AI CLI Tools ─────────────────────────────────────────────
step "Installing AI CLI tools"
# Claude Code
if ! command -v claude &>/dev/null; then
npm install -g @anthropic-ai/claude-code
ok "Claude Code installed"
else
ok "Claude Code $(claude --version 2>/dev/null | head -1 || echo 'installed')"
fi
# Gemini CLI
if ! command -v gemini &>/dev/null; then
npm install -g @google/gemini-cli
ok "Gemini CLI installed"
else
ok "Gemini CLI installed"
fi
# Codex CLI (OpenAI)
if ! command -v codex &>/dev/null; then
npm install -g @openai/codex
ok "Codex CLI installed"
else
ok "Codex CLI installed"
fi
# ── Infrastructure Repos ─────────────────────────────────────
step "Cloning infrastructure repos"
mkdir -p "$PROJECTS_DIR"
clone_repo() {
local repo="$1"
local dir="$PROJECTS_DIR/$repo"
if [ -d "$dir/.git" ]; then
ok "$repo (already cloned)"
else
git clone "git@github.com:markusstrasser/${repo}.git" "$dir"
ok "$repo cloned"
fi
}
clone_repo "papers-mcp" # research MCP server (paper search, full-text RAG, evidence prep)
clone_repo "meta" # agent infrastructure, orchestrator, measurement scripts
clone_repo "skills" # shared Claude Code skills and hooks
clone_repo "parsers" # platform export parsers (Twitter, iMessage, Gmail, etc.)
clone_repo "biomedical-mcp" # biomedical API server (genes, drugs, variants, pathways — 80 tools)
clone_repo "llmx" # unified LLM CLI (all providers)
clone_repo "emb" # embed, index, search text corpora
# ── Python Tools (uv) ───────────────────────────────────────
step "Installing Python tools via uv (editable from local clones)"
for tool in llmx emb parsers; do
dir="$PROJECTS_DIR/$tool"
if [ -d "$dir" ]; then
uv tool install --editable "$dir" 2>/dev/null && ok "$tool (editable)" \
|| ok "$tool (already installed)"
else
uv tool install "git+https://github.com/markusstrasser/${tool}.git" 2>/dev/null \
&& ok "$tool (from git)" || warn "$tool install failed"
fi
done
# ── Install MCP Dependencies ─────────────────────────────────
step "Installing MCP server dependencies"
(cd "$PROJECTS_DIR/papers-mcp" && uv sync --quiet)
ok "papers-mcp dependencies"
(cd "$PROJECTS_DIR/meta" && uv sync --quiet)
ok "meta dependencies"
(cd "$PROJECTS_DIR/biomedical-mcp" && uv sync --quiet)
ok "biomedical-mcp dependencies"
# ── API Keys ─────────────────────────────────────────────────
step "Setting up API keys (stored in macOS Keychain)"
echo -e " ${DIM}Keys are encrypted at rest. Manage later with: llmx keys list${RESET}"
KEYCHAIN_SERVICE="llmx"
store_key() {
local key_name="$1"
local display_name="$2"
local signup_url="$3"
local required="${4:-optional}"
if [ -n "${!key_name:-}" ]; then
ok "$display_name — already in environment"
security delete-generic-password -a "$KEYCHAIN_SERVICE" -s "$key_name" 2>/dev/null || true
security add-generic-password -a "$KEYCHAIN_SERVICE" -s "$key_name" -w "${!key_name}" 2>/dev/null
return
fi
existing=$(security find-generic-password -a "$KEYCHAIN_SERVICE" -s "$key_name" -w 2>/dev/null || true)
if [ -n "$existing" ]; then
ok "$display_name — already in Keychain"
return
fi
echo ""
if [ "$required" = "required" ]; then
echo -e " ${BOLD}$display_name${RESET} ${RED}(required)${RESET}"
else
echo -e " ${BOLD}$display_name${RESET} ${DIM}(optional — some tools degrade gracefully without it)${RESET}"
fi
echo -e " ${DIM}Get your key: ${signup_url}${RESET}"
read -rp " Enter $key_name (or press Enter to skip): " value
if [ -n "$value" ]; then
security delete-generic-password -a "$KEYCHAIN_SERVICE" -s "$key_name" 2>/dev/null || true
security add-generic-password -a "$KEYCHAIN_SERVICE" -s "$key_name" -w "$value"
ok "Stored $key_name in Keychain"
else
if [ "$required" = "required" ]; then
warn "Skipped $key_name — you'll need this to use $display_name"
else
skip "Skipped $key_name"
fi
fi
}
# Required
store_key "ANTHROPIC_API_KEY" "Anthropic (Claude API + Claude Code)" \
"https://console.anthropic.com/settings/keys" "required"
store_key "OPENAI_API_KEY" "OpenAI (GPT models + Codex CLI)" \
"https://platform.openai.com/api-keys" "required"
store_key "GEMINI_API_KEY" "Google Gemini (powers paper RAG in research MCP)" \
"https://aistudio.google.com/apikey" "required"
# Research infrastructure
store_key "EXA_API_KEY" "Exa (neural web search + claim verification)" \
"https://dashboard.exa.ai/api-keys" "required"
store_key "BRAVE_API_KEY" "Brave Search (independent web index, triangulation)" \
"https://brave.com/search/api/" "optional"
store_key "S2_API_KEY" "Semantic Scholar (paper search, higher rate limits)" \
"https://www.semanticscholar.org/product/api#api-key-form" "optional"
store_key "PERPLEXITY_API_KEY" "Perplexity (grounded search)" \
"https://www.perplexity.ai/settings/api" "optional"
store_key "FIRECRAWL_API_KEY" "Firecrawl (web scraping + structured extraction)" \
"https://www.firecrawl.dev/app/api-keys" "optional"
# Other providers
store_key "OPENROUTER_API_KEY" "OpenRouter (multi-model gateway)" \
"https://openrouter.ai/keys" "optional"
store_key "XAI_API_KEY" "xAI (Grok)" \
"https://console.x.ai/" "optional"
store_key "MOONSHOT_API_KEY" "Kimi / Moonshot" \
"https://platform.moonshot.cn/console/api-keys" "optional"
# ── Shell Integration ────────────────────────────────────────
step "Setting up shell integration"
SHELL_RC="$HOME/.zshrc"
if [ -n "${BASH_VERSION:-}" ]; then
SHELL_RC="$HOME/.bashrc"
fi
KEYCHAIN_LOADER='
# Load API keys from macOS Keychain (set by setup script / llmx keys)
_load_keychain_key() {
local key="$1"
if [ -z "${!key:-}" ]; then
local val
val=$(security find-generic-password -a "llmx" -s "$key" -w 2>/dev/null) && export "$key=$val"
fi
}
for _k in ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY OPENROUTER_API_KEY EXA_API_KEY BRAVE_API_KEY PERPLEXITY_API_KEY S2_API_KEY XAI_API_KEY MOONSHOT_API_KEY FIRECRAWL_API_KEY; do
_load_keychain_key "$_k"
done
unset _k
'
if ! grep -q '_load_keychain_key' "$SHELL_RC" 2>/dev/null; then
echo "$KEYCHAIN_LOADER" >> "$SHELL_RC"
ok "Added Keychain loader to $SHELL_RC"
else
skip "Keychain loader already in $SHELL_RC"
fi
# Source it now so MCP config commands below can use the keys
eval "$KEYCHAIN_LOADER"
# ── MCP Servers ──────────────────────────────────────────────
step "Configuring MCP servers for Claude Code"
echo -e " ${DIM}These give Claude research, web search, and code navigation tools${RESET}"
# User-scope MCPs (available in all projects)
# brave-search
claude mcp add brave-search --scope user \
-e BRAVE_API_KEY='${BRAVE_API_KEY}' \
-- npx -y @modelcontextprotocol/server-brave-search 2>/dev/null \
&& ok "brave-search MCP" || skip "brave-search MCP (already configured or failed)"
# perplexity
claude mcp add perplexity --scope user \
-- npx -y @perplexity-ai/mcp-server 2>/dev/null \
&& ok "perplexity MCP" || skip "perplexity MCP (already configured or failed)"
# context7 (library documentation)
claude mcp add context7 --scope user \
-- npx -y @upstash/context7-mcp 2>/dev/null \
&& ok "context7 MCP" || skip "context7 MCP (already configured or failed)"
# firecrawl (web scraping)
claude mcp add firecrawl --scope user \
-e FIRECRAWL_API_KEY='${FIRECRAWL_API_KEY}' \
-- npx -y firecrawl-mcp 2>/dev/null \
&& ok "firecrawl MCP" || skip "firecrawl MCP (already configured or failed)"
# exa (neural search — HTTP MCP, needs API key)
EXA_KEY=$(security find-generic-password -a "llmx" -s "EXA_API_KEY" -w 2>/dev/null || echo "")
if [ -n "$EXA_KEY" ]; then
claude mcp add exa --scope user \
--transport http \
--url "https://mcp.exa.ai/mcp?exaApiKey=${EXA_KEY}&tools=web_search_exa,web_search_advanced_exa,crawling_exa,company_research_exa,people_search_exa,deep_researcher_start,deep_researcher_check" \
2>/dev/null \
&& ok "exa MCP" || skip "exa MCP (already configured or failed)"
else
warn "exa MCP skipped — no EXA_API_KEY"
fi
# research (papers-mcp — paper search, full-text RAG, evidence prep)
claude mcp add research --scope user \
-- uv run --directory "$PROJECTS_DIR/papers-mcp" papers-mcp 2>/dev/null \
&& ok "research MCP (papers-mcp)" || skip "research MCP (already configured or failed)"
# biomedical (genes, drugs, variants, pathways — 80 tools across 15 domains)
claude mcp add biomedical --scope user \
-- uv run --directory "$PROJECTS_DIR/biomedical-mcp" biomedical-mcp 2>/dev/null \
&& ok "biomedical MCP" || skip "biomedical MCP (already configured or failed)"
# ── Skills ───────────────────────────────────────────────────
step "Linking shared skills"
SKILLS_DIR="$HOME/.claude/skills"
mkdir -p "$SKILLS_DIR"
# Symlink each skill directory
for skill_dir in "$PROJECTS_DIR/skills"/*/; do
skill_name=$(basename "$skill_dir")
# Skip non-skill directories
[[ "$skill_name" == "hooks" || "$skill_name" == "archive" || "$skill_name" == "goals" ]] && continue
target="$SKILLS_DIR/$skill_name"
if [ -L "$target" ] || [ -d "$target" ]; then
skip "$skill_name (already linked)"
else
ln -s "$skill_dir" "$target"
ok "$skill_name"
fi
done
# ── Plugins ──────────────────────────────────────────────────
step "Enabling recommended plugins"
claude plugin add frontend-design@claude-plugins-official 2>/dev/null && ok "frontend-design" || skip "frontend-design (already enabled or failed)"
claude plugin add playground@claude-plugins-official 2>/dev/null && ok "playground" || skip "playground (already enabled or failed)"
# ── Daily Update ─────────────────────────────────────────────
step "Setting up daily repo sync"
SYNC_SCRIPT="$HOME/.local/bin/sync-ai-infra.sh"
mkdir -p "$(dirname "$SYNC_SCRIPT")"
SYNC_URL="https://gist.githubusercontent.com/markusstrasser/c0590ee82d700fab4de10a0312e57cc0/raw/friend-sync.sh"
curl -sL "$SYNC_URL" -o "$SYNC_SCRIPT"
chmod +x "$SYNC_SCRIPT"
ok "Downloaded $SYNC_SCRIPT (auto-updates from gist)"
# launchd plist for daily sync at 06:00
PLIST_DIR="$HOME/Library/LaunchAgents"
PLIST_FILE="$PLIST_DIR/com.ai-infra.daily-sync.plist"
if [ ! -f "$PLIST_FILE" ]; then
mkdir -p "$PLIST_DIR"
cat > "$PLIST_FILE" << PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.ai-infra.daily-sync</string>
<key>ProgramArguments</key>
<array>
<string>${SYNC_SCRIPT}</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>6</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>${HOME}/.local/log/ai-infra-sync.log</string>
<key>StandardErrorPath</key>
<string>${HOME}/.local/log/ai-infra-sync.log</string>
</dict>
</plist>
PLIST
mkdir -p "$HOME/.local/log"
launchctl load "$PLIST_FILE" 2>/dev/null
ok "Daily sync at 06:00 (launchd)"
else
skip "Daily sync plist already exists"
fi
# ── Summary ──────────────────────────────────────────────────
step "Setup complete!"
echo ""
echo -e " ${BOLD}CLI tools:${RESET}"
echo " claude — Claude Code (AI coding agent)"
echo " gemini — Gemini CLI (free tier)"
echo " codex — Codex CLI (OpenAI coding agent)"
echo " llmx — Unified LLM CLI (all providers)"
echo " emb — Embed, index, search text corpora"
echo " parsers — Parse platform exports (Twitter, Gmail, iMessage, etc.)"
echo ""
echo -e " ${BOLD}MCP servers (available in Claude Code):${RESET}"
echo " research — Paper search, full-text RAG, evidence synthesis"
echo " biomedical — Genes, drugs, variants, pathways (80 tools)"
echo " exa — Neural web search, claim verification"
echo " brave-search — Independent web search index"
echo " perplexity — Grounded AI search"
echo " firecrawl — Web scraping + structured extraction"
echo " context7 — Library documentation lookup"
echo ""
echo -e " ${BOLD}Skills:${RESET} $(ls "$HOME/.claude/skills" 2>/dev/null | wc -l | tr -d ' ') skills linked from ~/Projects/skills/"
echo " researcher, model-review, brainstorm, causal-dag, and more"
echo ""
echo -e " ${BOLD}API keys:${RESET}"
echo " Manage: llmx keys list / set / get / delete"
echo " Loaded: automatically from Keychain on shell start"
echo ""
echo -e " ${BOLD}Daily sync:${RESET}"
echo " Auto: pulls repos + updates tools at 06:00 daily (launchd)"
echo " Manual: ~/.local/bin/sync-ai-infra.sh"
echo " Source: https://gist.github.com/markusstrasser/c0590ee82d700fab4de10a0312e57cc0"
echo ""
echo -e " ${BOLD}Next steps:${RESET}"
echo " 1. Open a new terminal (to load Keychain keys)"
echo " 2. Run: claude — start Claude Code"
echo " 3. Run: llmx 'hello' — test LLM access"
echo " 4. Check: claude mcp list — verify MCP servers are connected"
echo ""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment