Complete macOS setup and teardown for MCP Gateway & Registry (AI-registry). Clones the repository, installs all services, configures Keycloak auth, registers the Cloudflare docs server, and verifies the full stack. Also supports complete teardown. Can be run directly from its GitHub URL without the repository already cloned. Uses an interactive or default-values mode chosen at startup.
Repository: https://github.com/agentic-community/mcp-gateway-registry This skill: https://github.com/agentic-community/mcp-gateway-registry/blob/main/.claude/skills/macos-setup/SKILL.md Full macOS guide: https://github.com/agentic-community/mcp-gateway-registry/blob/main/docs/macos-setup-guide.md
This skill is self-contained. You can invoke it from any directory in Claude Code using the GitHub URL. It will clone the repository for you.
/macos-setup
Or reference it remotely if you have not installed this repo:
@https://raw.githubusercontent.com/agentic-community/mcp-gateway-registry/main/.claude/skills/macos-setup/SKILL.md
/macos-setup setup — Full guided installation on a fresh macOS machine:
/macos-setup teardown — Removes all MCP Gateway components from your system
DO NOT run any Bash commands. DO NOT check prerequisites. DO NOT read any files.
The very first action when this skill is invoked MUST be using AskUserQuestion to complete Step 0.
Nothing else happens until the user has answered all three Step 0 questions.
Throughout the entire execution, Claude must maintain an internal step log. After every phase completes (success, skip, or failure), append an entry to this log. Display the full log as a formatted table in the Final Summary phase.
Step log format: { phase, name, status (DONE / SKIPPED / FAILED), notes }
STOP. Do not run any commands. Use AskUserQuestion right now to ask all three questions below before taking any other action.
Question 1 — Operation:
Which operation would you like to perform?
Setup - Install the MCP Gateway & Registry (AI-registry) from scratch.
Builds all services from source. Estimated time: 20-40 minutes.
Teardown - Stop all services and remove all components. Irreversible.
If the user invoked the skill with an argument (/macos-setup setup or /macos-setup teardown), skip this question.
Question 2 — Execution mode (Setup only):
How should I run the setup?
Default (recommended) - Use sensible defaults for all prompts. I will only
pause for decisions that truly require your input. Passwords will be
auto-generated and shown in the final summary.
Interactive - Ask for your confirmation and input before every phase.
You control each step individually.
Store the answer as EXECUTION_MODE = default or interactive.
Question 3 — Installation directory (Setup only):
Where should the AI-registry project be installed?
Default: ~/AI-registry
Enter a path, or press Enter / select the default option to use ~/AI-registry.
Store the answer as INSTALL_DIR. If the user provides no input or selects the default, set INSTALL_DIR=~/AI-registry and inform the user:
"Using default installation directory: ~/AI-registry"
Expand the path immediately:
INSTALL_DIR=$(eval echo "${INSTALL_DIR}")
echo "Installation directory: ${INSTALL_DIR}"
Log: { 0, "Mode & Directory Selection", DONE, "Mode: ${EXECUTION_MODE}, Dir: ${INSTALL_DIR}" }
For every phase below, apply this rule:
AskUserQuestion to confirm before executingAnnounce: "Checking prerequisites..."
echo "=== Docker ==="
docker --version 2>/dev/null && docker ps >/dev/null 2>&1 && echo "DOCKER_OK" || echo "DOCKER_FAIL"
echo "=== Python ==="
python3 --version 2>/dev/null && echo "PYTHON_OK" || echo "PYTHON_FAIL"
echo "=== uv ==="
uv --version 2>/dev/null && echo "UV_OK" || echo "UV_FAIL"
echo "=== Node.js (required for building from source) ==="
node --version 2>/dev/null && echo "NODE_OK" || echo "NODE_FAIL"
echo "=== git ==="
git --version 2>/dev/null && echo "GIT_OK" || echo "GIT_FAIL"
echo "=== jq ==="
jq --version 2>/dev/null && echo "JQ_OK" || echo "JQ_FAIL"
For any failed check, display the install instructions:
| Check | Install command |
|---|---|
| DOCKER_FAIL | "Install Docker Desktop from https://www.docker.com/products/docker-desktop/ then start it and wait for the whale icon in the menu bar" |
| PYTHON_FAIL | brew install [email protected] |
| UV_FAIL | curl -LsSf https://astral.sh/uv/install.sh | sh — then restart your terminal |
| NODE_FAIL | brew install node@20 or download from https://nodejs.org/ |
| GIT_FAIL | xcode-select --install |
| JQ_FAIL | brew install jq |
Do not proceed if Docker or git fail. Python, uv, Node.js, and jq must also be present before continuing. Ask the user to install missing tools and retry.
Log: { 1, "Prerequisites Check", DONE/FAILED, list of what passed/failed }
Announce: "Cloning the MCP Gateway & Registry repository to ${INSTALL_DIR}..."
First check if the directory already exists:
if [ -d "${INSTALL_DIR}" ]; then
echo "ALREADY_EXISTS"
ls "${INSTALL_DIR}/docker-compose.yml" 2>/dev/null && echo "REPO_OK" || echo "NOT_A_REPO"
else
echo "WILL_CLONE"
fi
If ALREADY_EXISTS and REPO_OK: Inform the user and ask (both modes):
The directory ${INSTALL_DIR} already contains the repository.
Use existing - Continue setup with the existing copy
Re-clone - Remove it and clone fresh (WARNING: deletes existing data)
If WILL_CLONE: Clone the repository:
# Create parent directory if needed
mkdir -p "$(dirname "${INSTALL_DIR}")"
# Clone
git clone https://github.com/agentic-community/mcp-gateway-registry.git "${INSTALL_DIR}"
echo "Clone exit code: $?"
After cloning or confirming existing, change into the directory:
cd "${INSTALL_DIR}"
echo "Working directory: $(pwd)"
ls docker-compose.yml build_and_run.sh 2>/dev/null && echo "REPO_VERIFIED" || echo "REPO_INVALID"
All subsequent phases run commands from within ${INSTALL_DIR}.
Key files now available locally (GitHub references for documentation):
Log: { 2, "Repository Clone", DONE/SKIPPED, "Cloned to ${INSTALL_DIR} / Used existing" }
Announce: "Configuring credentials..."
In default mode: Auto-generate both passwords using Python. Store them for the final summary.
KEYCLOAK_ADMIN_PASSWORD=$(python3 -c "import secrets, string; print(''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(20)))")
KEYCLOAK_DB_PASSWORD=$(python3 -c "import secrets, string; print(''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(20)))")
echo "Passwords auto-generated (will be shown in final summary)"
echo "Admin password length: ${#KEYCLOAK_ADMIN_PASSWORD}"
echo "DB password length: ${#KEYCLOAK_DB_PASSWORD}"
In interactive mode: Use AskUserQuestion to collect:
http://localhost:8080/admin.Validate: if either password is fewer than 8 characters or empty, re-prompt. Do not proceed with weak passwords.
Log: { 3, "Credentials Configuration", DONE, "default-generated / user-provided" }
Announce: "Creating .env configuration file..."
Check for existing .env:
ls -la "${INSTALL_DIR}/.env" 2>/dev/null && echo "ENV_EXISTS" || echo "ENV_MISSING"
In interactive mode with existing .env, ask to overwrite. In default mode, overwrite automatically and note it in the log.
cd "${INSTALL_DIR}"
# Copy template
cp .env.example .env
# Generate SECRET_KEY
SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_urlsafe(64))")
echo "SECRET_KEY generated: ${#SECRET_KEY} characters"
Update .env using Python to handle special characters safely:
cd "${INSTALL_DIR}"
python3 << 'PYEOF'
import re, os
env_path = '.env'
content = open(env_path).read()
updates = {
'AUTH_PROVIDER': 'keycloak',
'AUTH_SERVER_EXTERNAL_URL': 'http://localhost',
'KEYCLOAK_ADMIN_PASSWORD': os.environ.get('KEYCLOAK_ADMIN_PASSWORD', ''),
'KEYCLOAK_DB_PASSWORD': os.environ.get('KEYCLOAK_DB_PASSWORD', ''),
'SECRET_KEY': os.environ.get('SECRET_KEY', ''),
}
for key, value in updates.items():
pattern = rf'^{key}=.*'
replacement = f'{key}={value}'
if re.search(pattern, content, flags=re.MULTILINE):
content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
else:
content += f'\n{key}={value}'
open(env_path, 'w').write(content)
print('Environment file updated successfully')
PYEOF
Verify (without exposing values):
cd "${INSTALL_DIR}"
for KEY in AUTH_PROVIDER AUTH_SERVER_EXTERNAL_URL KEYCLOAK_ADMIN_PASSWORD KEYCLOAK_DB_PASSWORD SECRET_KEY; do
VALUE=$(grep "^${KEY}=" .env | cut -d'=' -f2)
if [ -n "$VALUE" ]; then
echo "${KEY}=[set]"
else
echo "${KEY}=[MISSING - ERROR]"
fi
done
The template is at: .env.example
Log: { 4, "Environment File Setup", DONE, ".env created and configured" }
Announce: "Installing Python dependencies via uv sync..."
First, enable native TLS so that uv uses the macOS system certificate store. This is required on enterprise Macs with corporate proxies or custom CA certificates, and is harmless on personal Macs.
export UV_NATIVE_TLS=true
cd "${INSTALL_DIR}"
uv sync
echo "uv sync exit code: $?"
ls -la .venv/bin/python 2>/dev/null && echo "VENV_OK" || echo "VENV_FAIL"
Log: { 5, "Python Virtual Environment", DONE/FAILED, "" }
Announce: "Downloading sentence-transformers embeddings model (~90MB) to ~/mcp-gateway/models/..."
This model powers intelligent tool discovery. It is downloaded from HuggingFace.
mkdir -p "${HOME}/mcp-gateway/models/all-MiniLM-L6-v2"
cd "${INSTALL_DIR}"
# Try huggingface-cli first, fall back to Python API
if command -v huggingface-cli >/dev/null 2>&1; then
huggingface-cli download sentence-transformers/all-MiniLM-L6-v2 \
--local-dir "${HOME}/mcp-gateway/models/all-MiniLM-L6-v2"
else
uv run python -c "
from huggingface_hub import snapshot_download
import os
path = snapshot_download(
'sentence-transformers/all-MiniLM-L6-v2',
local_dir=os.path.expanduser('~/mcp-gateway/models/all-MiniLM-L6-v2')
)
print(f'Downloaded to: {path}')
"
fi
echo "Model files: $(ls ${HOME}/mcp-gateway/models/all-MiniLM-L6-v2/ | wc -l | tr -d ' ') files"
Log: { 6, "Embeddings Model Download", DONE/FAILED, "~/mcp-gateway/models/all-MiniLM-L6-v2" }
Announce: "Creating Docker volume mount directories..."
mkdir -p "${HOME}/mcp-gateway/{servers,models,auth_server,secrets/fininfo,logs,ssl}"
ls -la "${HOME}/mcp-gateway/"
Log: { 7, "Directory Creation", DONE, "~/mcp-gateway/{servers,models,auth_server,secrets/fininfo,logs,ssl}" }
Announce: "Starting Keycloak authentication services (1-3 minute wait)..."
cd "${INSTALL_DIR}"
export KEYCLOAK_ADMIN_PASSWORD="${KEYCLOAK_ADMIN_PASSWORD}"
export KEYCLOAK_DB_PASSWORD="${KEYCLOAK_DB_PASSWORD}"
docker compose up -d keycloak-db keycloak
echo "Docker compose exit code: $?"
Poll until Keycloak responds (max 180 seconds):
echo "Waiting for Keycloak to be ready..."
TIMEOUT=180
ELAPSED=0
READY=false
while [ $ELAPSED -lt $TIMEOUT ]; do
HTTP=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/realms/master 2>/dev/null || echo "000")
if [ "$HTTP" = "200" ]; then
echo "Keycloak ready after ${ELAPSED}s"
READY=true
break
fi
echo " ${ELAPSED}s — HTTP ${HTTP}, still waiting..."
sleep 10
ELAPSED=$((ELAPSED + 10))
done
[ "$READY" = "false" ] && echo "ERROR: Keycloak did not start within ${TIMEOUT}s" && docker compose logs keycloak --tail 20
Verify:
curl -s http://localhost:8080/realms/master | python3 -c "
import sys, json