#!/bin/bash # Install Unreal MCP and connect it to VS Code (via Continue MCP) on Arch Linux # - Installs deps: git, jq, uv, python # - Clones https://github.com/chongdashu/unreal-mcp # - Creates a launcher: ~/.local/bin/unreal-mcp-server # - Configures VS Code Continue MCP: ~/.continue/config.json # - Optional: copies UnrealMCP plugin into a specified .uproject's Plugins/ set -euo pipefail SCRIPT_NAME="$(basename "$0")" # ---------- User/paths ---------- if [[ -n ${SUDO_USER:-} ]]; then ACTUAL_USER="$SUDO_USER" USER_HOME="/home/$SUDO_USER" else ACTUAL_USER="$USER" USER_HOME="$HOME" fi INSTALL_ROOT_DEFAULT="$USER_HOME/.local/share/unreal-mcp" INSTALL_ROOT="$INSTALL_ROOT_DEFAULT" REPO_URL="https://github.com/chongdashu/unreal-mcp.git" REPO_DIR="" # will be set after INSTALL_ROOT known PROJECT_UPROJECT="" # optional: path to .uproject RESOLVED_PROJECT_DIR="" # directory containing the resolved .uproject CONFIGURE_CONTINUE=true CONFIGURE_VSCODE_USER=true FORCE_UPDATE=false log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } fail() { echo "[ERROR] $*" >&2 exit 1 } usage() { cat </dev/null 2>&1; } ensure_packages_arch() { # Install with pacman using sudo when needed; keep idempotent with --needed local pkgs=(git jq uv python rsync) local to_install=() for p in "${pkgs[@]}"; do if ! pacman -Qi "$p" >/dev/null 2>&1; then to_install+=("$p") fi done if [[ ${#to_install[@]} -gt 0 ]]; then log "Installing packages: ${to_install[*]}" if [[ $EUID -eq 0 ]]; then pacman -S --noconfirm --needed "${to_install[@]}" else sudo pacman -S --noconfirm --needed "${to_install[@]}" fi else log "All required packages already installed" fi } check_python_version() { if require_cmd python; then local v v=$(python -V 2>&1 | awk '{print $2}') elif require_cmd python3; then local v v=$(python3 -V 2>&1 | awk '{print $2}') else log "python not found; pacman install will provide it" return 0 fi # Require >= 3.12 (Unreal MCP docs) local major minor major=$(echo "$v" | cut -d. -f1) minor=$(echo "$v" | cut -d. -f2) if ((major < 3 || (major == 3 && minor < 12))); then log "Python $v detected; installing newer python via pacman" if [[ $EUID -eq 0 ]]; then pacman -S --noconfirm --needed python else sudo pacman -S --noconfirm --needed python fi fi } # ---------- Git clone/update ---------- setup_repo() { mkdir -p "$INSTALL_ROOT" if [[ ! -d "$REPO_DIR/.git" ]]; then log "Cloning unreal-mcp into $REPO_DIR" if require_cmd git; then git clone "$REPO_URL" "$REPO_DIR" else fail "git is required but not found after install" fi else log "Repo exists at $REPO_DIR" if [[ $FORCE_UPDATE == true ]]; then log "Updating repo with --force-update" git -C "$REPO_DIR" fetch origin git -C "$REPO_DIR" reset --hard origin/main git -C "$REPO_DIR" pull --rebase --autostash else log "Pulling latest changes" git -C "$REPO_DIR" pull --rebase --autostash fi fi # Ensure ownership for the real user when script ran via sudo if [[ $EUID -eq 0 ]]; then chown -R "$ACTUAL_USER:$ACTUAL_USER" "$INSTALL_ROOT" fi } # ---------- Launcher ---------- install_launcher() { local bin_dir="$USER_HOME/.local/bin" local python_dir="$REPO_DIR/Python" local launcher="$bin_dir/unreal-mcp-server" mkdir -p "$bin_dir" cat >"$launcher" <"$tmp_file" <"$tmp_file" && mv "$tmp_file" "$cont_cfg" fi if [[ $EUID -eq 0 ]]; then chown "$ACTUAL_USER:$ACTUAL_USER" "$cont_cfg"; fi log "Configured Continue MCP at: $cont_cfg" } # ---------- VS Code user MCP (native) ---------- configure_vscode_user_mcp() { if [[ $CONFIGURE_VSCODE_USER != true ]]; then log "Skipping VS Code user MCP config (--no-vscode)" return 0 fi if ! require_cmd jq; then fail "jq is required to compose VS Code --add-mcp JSON and to parse profiles" fi local python_dir="$REPO_DIR/Python" local json json=$(jq -n --arg dir "$python_dir" '{name:"unrealMCP", command:"uv", args:["--directory", $dir, "run", "unreal_mcp_server.py"]}') # Handle multiple VS Code variants if present local candidates=(code code-insiders codium) local found_any=false for cli in "${candidates[@]}"; do if ! command -v "$cli" >/dev/null 2>&1; then continue fi found_any=true log "Registering MCP server in VS Code user profile via: $cli --add-mcp" if "$cli" --add-mcp "$json" >"/tmp/${cli}-add-mcp.log" 2>&1; then log "[$cli] user profile: unrealMCP added/updated" else sed -n '1,200p' "/tmp/${cli}-add-mcp.log" || true fail "[$cli] --add-mcp failed for user profile. Ensure your VS Code supports MCP or rerun with --no-vscode." fi # Detect profiles with 'unreal' (case-insensitive) and add there too local data_dir="" case "$cli" in code) data_dir="$USER_HOME/.config/Code" ;; code-insiders) data_dir="$USER_HOME/.config/Code - Insiders" ;; codium) data_dir="$USER_HOME/.config/VSCodium" ;; esac local profiles_json="$data_dir/User/profiles/profiles.json" if [[ -f $profiles_json ]]; then # Extract profile names matching /unreal/i mapfile -t unreal_profiles < <(jq -r '.profiles // [] | .[] | .name // empty | select(test("unreal"; "i"))' "$profiles_json") if [[ ${#unreal_profiles[@]} -gt 0 ]]; then log "[$cli] Found profiles with 'unreal': ${unreal_profiles[*]}" local name for name in "${unreal_profiles[@]}"; do log "[$cli] Adding unrealMCP to profile: $name" if "$cli" --profile "$name" --add-mcp "$json" >"/tmp/${cli}-add-mcp-${name// /_}.log" 2>&1; then log "[$cli] profile '$name': unrealMCP added/updated" else sed -n '1,200p' "/tmp/${cli}-add-mcp-${name// /_}.log" || true fail "[$cli] --add-mcp failed for profile '$name'." fi done else log "[$cli] No VS Code profiles with 'unreal' in name" fi else log "[$cli] Profiles file not found: $profiles_json (skipping profile-specific adds)" fi done if [[ $found_any == false ]]; then fail "VS Code CLI not found (code/code-insiders/codium). Install VS Code and ensure 'code' CLI is available, or run with --no-vscode to skip." fi } # ---------- Unreal Plugin copy (optional) ---------- install_plugin_into_project() { [[ -n $PROJECT_UPROJECT ]] || return 0 local upath="$PROJECT_UPROJECT" if [[ -d $upath ]]; then # Resolve .uproject in the provided directory mapfile -t _uprojects < <(find "$upath" -maxdepth 1 -type f -name "*.uproject" 2>/dev/null || true) if [[ ${#_uprojects[@]} -eq 0 ]]; then fail "--project directory '$upath' contains no .uproject files" elif [[ ${#_uprojects[@]} -gt 1 ]]; then printf '[ERROR] Multiple .uproject files found in %s:\n' "$upath" >&2 printf ' - %s\n' "${_uprojects[@]}" >&2 fail "Please pass the specific .uproject path to --project" else upath="${_uprojects[0]}" log "Resolved .uproject: $upath" fi elif [[ -f $upath ]]; then true else fail "--project path does not exist: $upath" fi if [[ ${upath##*.} != "uproject" ]]; then fail "--project must point to a .uproject file (got: $upath)" fi local proj_dir proj_dir="$(cd "$(dirname "$upath")" && pwd)" RESOLVED_PROJECT_DIR="$proj_dir" local src_plugin="$REPO_DIR/MCPGameProject/Plugins/UnrealMCP" local dst_plugin="$proj_dir/Plugins/UnrealMCP" if [[ ! -d $src_plugin ]]; then fail "Source plugin not found at $src_plugin (did repo layout change?)" fi mkdir -p "$proj_dir/Plugins" log "Copying UnrealMCP plugin to project: $dst_plugin" rsync -a --delete "$src_plugin/" "$dst_plugin/" # Set ownership back to actual user if run as root if [[ $EUID -eq 0 ]]; then chown -R "$ACTUAL_USER:$ACTUAL_USER" "$proj_dir/Plugins"; fi log "Plugin installed. Enable it from Unreal Editor (Edit > Plugins) if needed." } # ---------- Summary ---------- print_summary() { local python_dir="$REPO_DIR/Python" local plugin_dest="N/A" if [[ -n $RESOLVED_PROJECT_DIR ]]; then plugin_dest="$RESOLVED_PROJECT_DIR/Plugins/UnrealMCP" fi cat < Plugins > search "UnrealMCP" and enable. Restart when prompted. Notes: - Ensure you have Unreal Engine 5.5+ installed. - The Python server listens to the Unreal plugin on TCP port 55557 by default. - For other MCP clients (Claude Desktop, Cursor, Windsurf), copy the JSON snippet from the repo README to their config locations. EOF } main() { log "Installing prerequisites (Arch Linux)" ensure_packages_arch check_python_version setup_repo install_launcher configure_continue install_plugin_into_project configure_vscode_user_mcp print_summary } main "$@"