#!/usr/bin/env bash set -e # Lcontext MCP Server Installation Script # Downloads and installs the Lcontext MCP server binary for Claude Code INSTALL_DIR="${HOME}/.local/bin" BINARY_NAME="lcontext" GITHUB_REPO="Lcontext/Lcontext" # Use latest release by default, or a specific version if LCONTEXT_VERSION is set if [ -n "${LCONTEXT_VERSION:-}" ]; then BASE_URL="${LCONTEXT_BASE_URL:-https://github.com/${GITHUB_REPO}/releases/download/${LCONTEXT_VERSION}}" else BASE_URL="${LCONTEXT_BASE_URL:-https://github.com/${GITHUB_REPO}/releases/latest/download}" fi INSTALL_VERSION="${LCONTEXT_VERSION:-latest}" DOWNLOAD_TOOL="unknown" DOWNLOAD_DURATION_MS=0 # Telemetry: report install metrics (fire-and-forget, never blocks or fails install) send_telemetry() { local success="$1" local api_key_configured="false" if [ -n "${API_KEY:-}" ]; then api_key_configured="true" fi local telemetry_url="https://lcontext.com/api/track-install" local json_payload="{\"platform\":\"${PLATFORM:-unknown}\",\"success\":${success},\"version\":\"${INSTALL_VERSION}\",\"apiKeyConfigured\":${api_key_configured},\"downloadTool\":\"${DOWNLOAD_TOOL}\",\"downloadDurationMs\":${DOWNLOAD_DURATION_MS:-0},\"binarySize\":${DOWNLOAD_SIZE:-0}}" if command -v curl &> /dev/null; then curl -sS -X POST "${telemetry_url}" \ -H "Content-Type: application/json" \ -d "${json_payload}" \ --max-time 5 \ > /dev/null 2>&1 & elif command -v wget &> /dev/null; then wget -q --timeout=5 -O /dev/null --post-data="${json_payload}" \ --header="Content-Type: application/json" \ "${telemetry_url}" \ > /dev/null 2>&1 & fi } # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color echo -e "${BLUE}" cat << "EOF" _ _ _ | | | | | | | | ___ ___ _ __ | |_ _____ _| |_ | |/ __/ _ \| '_ \| __/ _ \ \/ / __| | | (_| (_) | | | | || __/> <| |_ |_|\___\___/|_| |_|\__\___/_/\_\\__| MCP Server Installer EOF echo -e "${NC}" # Detect OS and architecture detect_platform() { local os="" local arch="" # Detect OS case "$(uname -s)" in Linux*) os="linux";; Darwin*) os="macos";; MINGW*|MSYS*|CYGWIN*) os="windows";; *) echo -e "${RED}Error: Unsupported operating system$(uname -s)${NC}" exit 1 ;; esac # Detect architecture case "$(uname -m)" in x86_64|amd64) arch="x64";; arm64|aarch64) arch="arm64";; *) echo -e "${RED}Error: Unsupported architecture $(uname -m)${NC}" exit 1 ;; esac # macOS arm64 check if [ "$os" = "macos" ] && [ "$arch" = "x64" ]; then # Check if running on Apple Silicon with Rosetta if [ "$(sysctl -n sysctl.proc_translated 2>/dev/null)" = "1" ]; then arch="arm64" fi fi echo "${os}-${arch}" } PLATFORM=$(detect_platform) echo "Detected platform: ${PLATFORM}" # Determine binary name based on platform if [[ "$PLATFORM" == "windows-"* ]]; then BINARY_NAME="lcontext.exe" DOWNLOAD_NAME="lcontext-windows-x64.exe" elif [[ "$PLATFORM" == "macos-arm64" ]]; then DOWNLOAD_NAME="lcontext-macos-arm64" elif [[ "$PLATFORM" == "macos-x64" ]]; then DOWNLOAD_NAME="lcontext-macos-x64" elif [[ "$PLATFORM" == "linux-arm64" ]]; then DOWNLOAD_NAME="lcontext-linux-arm64" elif [[ "$PLATFORM" == "linux-x64" ]]; then DOWNLOAD_NAME="lcontext-linux-x64" else echo -e "${RED}Error: Unsupported platform ${PLATFORM}${NC}" exit 1 fi # Create installation directory echo "Creating installation directory..." mkdir -p "${INSTALL_DIR}" # Download binary DOWNLOAD_URL="${BASE_URL}/${DOWNLOAD_NAME}" TEMP_FILE="/tmp/${DOWNLOAD_NAME}" echo "Downloading from ${DOWNLOAD_URL}..." DOWNLOAD_EXIT_CODE=0 DOWNLOAD_START=$(date +%s%N 2>/dev/null || echo "0") if command -v curl &> /dev/null; then DOWNLOAD_TOOL="curl" curl -fSL --progress-bar "${DOWNLOAD_URL}" -o "${TEMP_FILE}" || DOWNLOAD_EXIT_CODE=$? elif command -v wget &> /dev/null; then DOWNLOAD_TOOL="wget" wget -q --show-progress "${DOWNLOAD_URL}" -O "${TEMP_FILE}" || DOWNLOAD_EXIT_CODE=$? else echo -e "${RED}Error: Neither curl nor wget found. Please install one of them.${NC}" exit 1 fi DOWNLOAD_END=$(date +%s%N 2>/dev/null || echo "0") if [ "$DOWNLOAD_START" != "0" ] && [ "$DOWNLOAD_END" != "0" ]; then DOWNLOAD_DURATION_MS=$(( (DOWNLOAD_END - DOWNLOAD_START) / 1000000 )) fi if [ $DOWNLOAD_EXIT_CODE -ne 0 ]; then echo -e "${RED}Error: Download failed with exit code $DOWNLOAD_EXIT_CODE${NC}" send_telemetry "false" exit 1 fi # Verify download exists and has content if [ ! -f "${TEMP_FILE}" ]; then echo -e "${RED}Error: Download failed - file not created${NC}" send_telemetry "false" exit 1 fi DOWNLOAD_SIZE=$(stat -c%s "${TEMP_FILE}" 2>/dev/null || stat -f%z "${TEMP_FILE}" 2>/dev/null || echo "0") if [ "$DOWNLOAD_SIZE" -lt 1000000 ]; then echo -e "${RED}Error: Download appears incomplete (only ${DOWNLOAD_SIZE} bytes)${NC}" echo -e "${RED}Expected a binary file of ~100MB. The server may be down or returning an error.${NC}" send_telemetry "false" rm -f "${TEMP_FILE}" exit 1 fi echo "Downloaded ${DOWNLOAD_SIZE} bytes" # Install binary echo "Installing to ${INSTALL_DIR}/${BINARY_NAME}..." mv "${TEMP_FILE}" "${INSTALL_DIR}/${BINARY_NAME}" chmod +x "${INSTALL_DIR}/${BINARY_NAME}" # Verify the file was actually installed if [ ! -f "${INSTALL_DIR}/${BINARY_NAME}" ]; then echo -e "${RED}Error: Failed to install binary to ${INSTALL_DIR}/${BINARY_NAME}${NC}" exit 1 fi # Verify binary can execute VERIFY_OUTPUT=$("${INSTALL_DIR}/${BINARY_NAME}" --version 2>&1) || true if [ -z "$VERIFY_OUTPUT" ]; then echo -e "${YELLOW}Warning: Binary installed but verification failed${NC}" echo -e "${YELLOW}This might indicate an architecture mismatch. Your system is: $(uname -m)${NC}" # Show more diagnostic info echo -e "${YELLOW}Binary file type:${NC}" file "${INSTALL_DIR}/${BINARY_NAME}" 2>/dev/null || ls -la "${INSTALL_DIR}/${BINARY_NAME}" else echo -e "${GREEN}Binary verified: $VERIFY_OUTPUT${NC}" fi # Check if INSTALL_DIR is in PATH if [[ ":$PATH:" != *":${INSTALL_DIR}:"* ]]; then echo "" echo -e "${YELLOW}Warning: ${INSTALL_DIR} is not in your PATH${NC}" echo "Add this line to your shell configuration file:" echo -e "${GREEN}export PATH=\"\$HOME/.local/bin:\$PATH\"${NC}" echo "" echo "Then restart your shell or run:" echo -e "${GREEN}source ~/.bashrc${NC} # or ~/.zshrc, ~/.profile, etc." echo "" fi echo "" echo -e "${GREEN}✓ Lcontext MCP server installed successfully!${NC}" echo "" # Install Claude Code skill for automatic analytics guidance SKILL_DIR="${HOME}/.claude/skills/lcontext" SKILL_URL="${BASE_URL}/SKILL.md" echo "Installing Claude Code skill..." mkdir -p "${SKILL_DIR}" SKILL_INSTALLED=false if command -v curl &> /dev/null; then if curl -fsSL "${SKILL_URL}" -o "${SKILL_DIR}/SKILL.md" 2>/dev/null; then SKILL_INSTALLED=true fi elif command -v wget &> /dev/null; then if wget -q "${SKILL_URL}" -O "${SKILL_DIR}/SKILL.md" 2>/dev/null; then SKILL_INSTALLED=true fi fi if [ "$SKILL_INSTALLED" = true ] && [ -s "${SKILL_DIR}/SKILL.md" ]; then echo -e "${GREEN}✓ Claude Code skill installed${NC}" else echo -e "${YELLOW}Skill download failed, writing bundled version...${NC}" cat > "${SKILL_DIR}/SKILL.md" << 'SKILLEOF' --- name: lcontext description: Analyze user behavior using Lcontext MCP tools. Use when investigating UX issues, conversion drop-offs, page performance, funnel analysis, session recordings, visitor patterns, Web Vitals, or when the user asks about analytics, user behavior, sessions, visitors, or what's happening in their app. --- You have access to Lcontext MCP tools that provide raw user behavior data for the application you're working on. Work **top-down**: start with `get_app_context` for aggregate metrics, use `list_pages` to map the app, then `get_page_context` to deep-dive into problem pages. Use `get_sessions` and `get_session_detail` to investigate individual user journeys. Use `get_user_flows` to see common navigation patterns. **Key signals**: bounce rate >60% is concerning, LCP >2.5s or CLS >0.1 = performance problem, elements with 0 interactions on high-traffic pages = broken or invisible UI, rapid repeated clicks = rage clicking. **Connect findings to code**: high exit rate → check route handler; 0 element interactions → check CSS visibility and click handlers; form with low submits → check validation logic; slow LCP → check for large images and blocking JS. SKILLEOF echo -e "${GREEN}✓ Claude Code skill installed (bundled version)${NC}" fi echo "" # Determine config path for Claude Desktop (fallback) if [[ "$PLATFORM" == "macos-"* ]]; then CONFIG_DIR="$HOME/Library/Application Support/Claude" CONFIG_PATH="$CONFIG_DIR/claude_desktop_config.json" elif [[ "$PLATFORM" == "windows-"* ]]; then CONFIG_DIR="$APPDATA/Claude" CONFIG_PATH="$CONFIG_DIR/claude_desktop_config.json" else CONFIG_DIR="$HOME/.config/Claude" CONFIG_PATH="$CONFIG_DIR/claude_desktop_config.json" fi # ── Authentication via device flow ────────────────────────────────────────── echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${GREEN}Sign in to Lcontext${NC}" echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" LCONTEXT_URL="https://lcontext.com" API_KEY="" TAG="" # Allow skipping auth if API key is already set via environment if [ -n "${LCONTEXT_API_KEY:-}" ]; then echo -e "Using API key from environment variable." API_KEY="$LCONTEXT_API_KEY" else echo "Starting authentication..." echo "" # Start device auth flow AUTH_RESPONSE=$(curl -s -X POST "${LCONTEXT_URL}/api/cli/auth/start" \ -H "Content-Type: application/json" -d '{}' 2>/dev/null) || true if [ -z "$AUTH_RESPONSE" ]; then echo -e "${RED}Error: Could not reach ${LCONTEXT_URL}${NC}" echo "" echo "To configure manually with an existing API key:" echo -e " ${GREEN}claude mcp add lcontext -e LCONTEXT_API_KEY=your-key -- lcontext${NC}" send_telemetry "true" exit 1 fi # Extract token and url from JSON response AUTH_TOKEN=$(echo "$AUTH_RESPONSE" | grep -o '"token":"[^"]*"' | head -1 | cut -d'"' -f4) AUTH_URL=$(echo "$AUTH_RESPONSE" | grep -o '"url":"[^"]*"' | head -1 | cut -d'"' -f4) if [ -z "$AUTH_TOKEN" ] || [ -z "$AUTH_URL" ]; then echo -e "${RED}Error: Unexpected response from auth server${NC}" echo "" echo "To configure manually with an existing API key:" echo -e " ${GREEN}claude mcp add lcontext -e LCONTEXT_API_KEY=your-key -- lcontext${NC}" send_telemetry "true" exit 1 fi # Open the browser echo -e "Opening your browser to sign in..." echo -e "If it doesn't open, visit: ${BLUE}${AUTH_URL}${NC}" echo "" if command -v open &> /dev/null; then open "$AUTH_URL" 2>/dev/null || true elif command -v xdg-open &> /dev/null; then xdg-open "$AUTH_URL" 2>/dev/null || true elif command -v wslview &> /dev/null; then wslview "$AUTH_URL" 2>/dev/null || true fi # Poll for completion (every 3 seconds, up to 10 minutes) echo -n "Waiting for you to sign in" POLL_COUNT=0 MAX_POLLS=200 # 200 * 3s = 10 minutes while [ $POLL_COUNT -lt $MAX_POLLS ]; do sleep 3 echo -n "." POLL_RESPONSE=$(curl -s "${LCONTEXT_URL}/api/cli/auth/poll/${AUTH_TOKEN}" 2>/dev/null) || true POLL_STATUS=$(echo "$POLL_RESPONSE" | grep -o '"status":"[^"]*"' | head -1 | cut -d'"' -f4) if [ "$POLL_STATUS" = "completed" ]; then echo "" echo "" # Extract credentials from completed response API_KEY=$(echo "$POLL_RESPONSE" | grep -o '"apiKey":"[^"]*"' | head -1 | cut -d'"' -f4) TAG=$(echo "$POLL_RESPONSE" | grep -o '"tag":"[^"]*"' | head -1 | cut -d'"' -f4) WEBSITE_NAME=$(echo "$POLL_RESPONSE" | grep -o '"websiteName":"[^"]*"' | head -1 | cut -d'"' -f4) echo -e "${GREEN}✓ Signed in successfully!${NC}" if [ -n "$WEBSITE_NAME" ]; then echo -e " App: ${WEBSITE_NAME}" fi break fi POLL_COUNT=$((POLL_COUNT + 1)) done if [ -z "$API_KEY" ]; then echo "" echo -e "${RED}Error: Authentication timed out${NC}" echo "" echo "To configure manually with an existing API key:" echo -e " ${GREEN}claude mcp add lcontext -e LCONTEXT_API_KEY=your-key -- lcontext${NC}" send_telemetry "true" exit 1 fi fi # ── Configure Claude ──────────────────────────────────────────────────────── echo "" echo "Configuring Claude..." CONFIGURED=false API_URL="https://lcontext.com" # Try Claude Code CLI first (preferred method) if command -v claude &> /dev/null; then echo "Detected Claude Code, configuring MCP server..." if claude mcp add lcontext -s user -e "LCONTEXT_API_KEY=$API_KEY" -e "LCONTEXT_API_URL=$API_URL" -- lcontext 2>/dev/null; then echo -e "${GREEN}✓ Claude Code configured!${NC}" CONFIGURED=true else echo -e "${YELLOW}Claude Code configuration failed, trying Claude Desktop config...${NC}" fi fi # Fall back to Claude Desktop config file if [ "$CONFIGURED" = false ]; then mkdir -p "$CONFIG_DIR" if [ -f "$CONFIG_PATH" ] && [ -s "$CONFIG_PATH" ]; then if command -v python3 &> /dev/null; then python3 << PYEOF import json config_path = "$CONFIG_PATH" api_key = "$API_KEY" try: with open(config_path, 'r') as f: config = json.load(f) except: config = {} if 'mcpServers' not in config: config['mcpServers'] = {} config['mcpServers']['lcontext'] = { 'command': 'lcontext', 'env': { 'LCONTEXT_API_KEY': api_key, 'LCONTEXT_API_URL': 'https://lcontext.com' } } with open(config_path, 'w') as f: json.dump(config, f, indent=2) print("Configuration merged successfully!") PYEOF else cp "$CONFIG_PATH" "$CONFIG_PATH.backup" 2>/dev/null || true cat > "$CONFIG_PATH" << JSONEOF { "mcpServers": { "lcontext": { "command": "lcontext", "env": { "LCONTEXT_API_KEY": "$API_KEY", "LCONTEXT_API_URL": "$API_URL" } } } } JSONEOF fi else cat > "$CONFIG_PATH" << JSONEOF { "mcpServers": { "lcontext": { "command": "lcontext", "env": { "LCONTEXT_API_KEY": "$API_KEY", "LCONTEXT_API_URL": "$API_URL" } } } } JSONEOF fi echo -e "${GREEN}✓ Claude Desktop configured!${NC}" fi # Send installation telemetry (background, non-blocking) send_telemetry "true" # ── Done ──────────────────────────────────────────────────────────────────── echo "" echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${GREEN}Setup complete!${NC}" echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" # Show tracking snippet if we have the tag if [ -n "$TAG" ]; then echo "Add this tracking script to your app's HTML:" echo "" echo -e " ${GREEN}${NC}" echo "" fi echo -e "${YELLOW}Next steps:${NC}" echo " 1. Restart your coding agent for the MCP server to connect" if [ -n "$TAG" ]; then echo " 2. Add the tracking script above to your app" echo " 3. Once you have traffic, ask your agent to analyze user behavior" else echo " 2. Add the tracking script to your app (find it in your dashboard)" echo " 3. Once you have traffic, ask your agent to analyze user behavior" fi echo ""