#!/usr/bin/env bash set -euo pipefail # MCP Agent Mail — TL;DR installer # - Installs uv (if missing) # - Sets up Python 3.14 venv with uv # - Syncs dependencies # - Runs auto-detect integration and starts the HTTP server on port 8765 # # Usage examples: # ./scripts/install.sh --yes # ./scripts/install.sh --dir "$HOME/mcp_agent_mail" --yes # curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/mcp_agent_mail/main/scripts/install.sh | bash -s -- --yes REPO_URL="https://github.com/Dicklesworthstone/mcp_agent_mail" REPO_NAME="mcp_agent_mail" BRANCH="main" DEFAULT_CLONE_DIR="$PWD/${REPO_NAME}" CLONE_DIR="" YES=0 NO_START=0 START_ONLY=0 PROJECT_DIR="" INTEGRATION_TOKEN="${INTEGRATION_BEARER_TOKEN:-}" HTTP_PORT_OVERRIDE="" usage() { cat <&2; usage; exit 2 ;; esac shift || true done # Validate port if provided if [[ -n "${HTTP_PORT_OVERRIDE}" ]]; then if ! [[ "${HTTP_PORT_OVERRIDE}" =~ ^[0-9]+$ ]]; then err "Port must be a number (got: ${HTTP_PORT_OVERRIDE})" exit 1 fi if [[ "${HTTP_PORT_OVERRIDE}" -lt 1 || "${HTTP_PORT_OVERRIDE}" -gt 65535 ]]; then err "Port must be between 1 and 65535 (got: ${HTTP_PORT_OVERRIDE})" exit 1 fi fi info() { printf "\033[1;36m[INFO]\033[0m %s\n" "$*"; } ok() { printf "\033[1;32m[ OK ]\033[0m %s\n" "$*"; } warn() { printf "\033[1;33m[WARN]\033[0m %s\n" "$*"; } err() { printf "\033[1;31m[ERR ]\033[0m %s\n" "$*"; } need_cmd() { command -v "$1" >/dev/null 2>&1 || return 1; } ensure_uv() { if need_cmd uv; then ok "uv is already installed" return 0 fi info "Installing uv (Astral)" if ! need_cmd curl; then err "curl is required to install uv"; exit 1; fi curl -LsSf https://astral.sh/uv/install.sh | sh export PATH="${HOME}/.local/bin:${PATH}" if need_cmd uv; then ok "uv installed"; else err "uv install failed"; exit 1; fi } ensure_repo() { # Determine target directory if [[ -z "${CLONE_DIR}" ]]; then CLONE_DIR="${DEFAULT_CLONE_DIR}"; fi # If we're already in the repo (local run), use it if [[ -f "pyproject.toml" ]] && grep -q '^name\s*=\s*"mcp-agent-mail"' pyproject.toml 2>/dev/null; then REPO_DIR="$PWD" ok "Using existing repo at: ${REPO_DIR}" return 0 fi # If directory exists and looks like the repo, use it if [[ -d "${CLONE_DIR}" ]] && [[ -f "${CLONE_DIR}/pyproject.toml" ]] && grep -q '^name\s*=\s*"mcp-agent-mail"' "${CLONE_DIR}/pyproject.toml" 2>/dev/null; then REPO_DIR="${CLONE_DIR}" ok "Using existing repo at: ${REPO_DIR}" return 0 fi # Otherwise clone info "Cloning ${REPO_URL} (branch=${BRANCH}) to ${CLONE_DIR}" need_cmd git || { err "git is required to clone"; exit 1; } git clone --depth 1 --branch "${BRANCH}" "${REPO_URL}" "${CLONE_DIR}" REPO_DIR="${CLONE_DIR}" ok "Cloned repo" } ensure_python_and_venv() { info "Ensuring Python 3.14 and project venv (.venv)" uv python install 3.14 if [[ ! -d "${REPO_DIR}/.venv" ]]; then (cd "${REPO_DIR}" && uv venv -p 3.14) ok "Created venv at ${REPO_DIR}/.venv" else ok "Found existing venv at ${REPO_DIR}/.venv" fi } sync_deps() { info "Syncing dependencies with uv" ( cd "${REPO_DIR}" # shellcheck disable=SC1091 source .venv/bin/activate uv sync ) ok "Dependencies installed" } configure_port() { if [[ -z "${HTTP_PORT_OVERRIDE}" ]]; then return 0 fi local env_file="${REPO_DIR}/.env" local tmp="${env_file}.tmp.$$" info "Configuring HTTP_PORT=${HTTP_PORT_OVERRIDE} in .env" # Set trap to cleanup temp file trap "rm -f \"${tmp}\" 2>/dev/null" EXIT INT TERM # Set secure umask for all file operations umask 077 if [[ -f "${env_file}" ]]; then # File exists - update or append if grep -q '^HTTP_PORT=' "${env_file}"; then # Replace existing value if ! sed "s/^HTTP_PORT=.*/HTTP_PORT=${HTTP_PORT_OVERRIDE}/" "${env_file}" > "${tmp}"; then err "Failed to update HTTP_PORT in .env" rm -f "${tmp}" 2>/dev/null trap - EXIT INT TERM return 1 fi else # Append new value if ! { cat "${env_file}"; echo "HTTP_PORT=${HTTP_PORT_OVERRIDE}"; } > "${tmp}"; then err "Failed to append HTTP_PORT to .env" rm -f "${tmp}" 2>/dev/null trap - EXIT INT TERM return 1 fi fi # Atomic move if ! mv "${tmp}" "${env_file}"; then err "Failed to write .env file" rm -f "${tmp}" 2>/dev/null trap - EXIT INT TERM return 1 fi else # Create new file if ! echo "HTTP_PORT=${HTTP_PORT_OVERRIDE}" > "${tmp}"; then err "Failed to create .env file" rm -f "${tmp}" 2>/dev/null trap - EXIT INT TERM return 1 fi if ! mv "${tmp}" "${env_file}"; then err "Failed to write .env file" rm -f "${tmp}" 2>/dev/null trap - EXIT INT TERM return 1 fi fi # Ensure secure permissions (in case file existed with wrong perms) chmod 600 "${env_file}" 2>/dev/null || warn "Could not set .env permissions to 600" trap - EXIT INT TERM ok "HTTP_PORT set to ${HTTP_PORT_OVERRIDE}" } run_integration_and_start() { if [[ "${NO_START}" -eq 1 ]]; then warn "--no-start specified; skipping integration/start" return 0 fi info "Running auto-detect integration and starting server" ( cd "${REPO_DIR}" # shellcheck disable=SC1091 source .venv/bin/activate export INTEGRATION_BEARER_TOKEN="${INTEGRATION_TOKEN}" args=() if [[ "${YES}" -eq 1 ]]; then args+=("--yes"); fi if [[ -n "${PROJECT_DIR}" ]]; then args+=("--project-dir" "${PROJECT_DIR}"); fi bash scripts/automatically_detect_all_installed_coding_agents_and_install_mcp_agent_mail_in_all.sh "${args[@]}" ) } main() { if [[ "${START_ONLY}" -eq 1 ]]; then info "--start-only specified: skipping clone/setup; starting integration" REPO_DIR="$PWD" configure_port run_integration_and_start exit 0 fi ensure_uv ensure_repo ensure_python_and_venv sync_deps configure_port run_integration_and_start echo ok "All set!" echo "Next runs:" echo " cd \"${REPO_DIR}\"" echo " source .venv/bin/activate" echo " bash scripts/run_server_with_token.sh" } main "$@"