mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 15:03:01 +02:00
* Initial plan * fix: format shell scripts with shfmt (convert tabs to 2 spaces) Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com> * feat: enhance shell-check workflow for PR pre-merge validation - Add pull_request_target trigger to check PRs from forks - Add explicit failure message with instructions - Create BRANCH_PROTECTION.md with setup guide - Ensure workflow runs on all PRs targeting main/master Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com> * refactor: improve workflow security and remove redundant exit code - Remove pull_request_target to avoid executing untrusted fork code - Remove redundant exit 1 from failure step - Update documentation to reflect changes - Standard pull_request trigger handles forks securely Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com>
396 lines
12 KiB
Bash
Executable File
396 lines
12 KiB
Bash
Executable File
#!/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")"
|
|
|
|
# Source common library for shared functions
|
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
|
# shellcheck source=../lib/common.sh
|
|
source "$SCRIPT_DIR/../lib/common.sh"
|
|
|
|
# ---------- User/paths ----------
|
|
set_actual_user_vars
|
|
|
|
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 << EOF
|
|
Usage: $SCRIPT_NAME [options]
|
|
|
|
Options:
|
|
--install-dir DIR Install root for repo (default: $INSTALL_ROOT_DEFAULT)
|
|
--project PATH Path to your Unreal project (.uproject file) or a directory containing one
|
|
Copies UnrealMCP plugin into this Unreal project
|
|
--no-continue Skip configuring VS Code Continue MCP
|
|
--no-vscode Skip adding MCP server to VS Code user profile via --add-mcp
|
|
--force-update If repo exists, fetch and reset to origin/main
|
|
-h, --help Show this help
|
|
|
|
Examples:
|
|
$SCRIPT_NAME --project ~/UnrealProjects/MyGame/MyGame.uproject
|
|
$SCRIPT_NAME --install-dir "$USER_HOME/dev/unreal-mcp"
|
|
EOF
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--install-dir)
|
|
shift
|
|
[[ $# -gt 0 ]] || fail "--install-dir requires a value"
|
|
INSTALL_ROOT="$1"
|
|
;;
|
|
--project)
|
|
shift
|
|
[[ $# -gt 0 ]] || fail "--project requires a path to .uproject"
|
|
PROJECT_UPROJECT="$1"
|
|
;;
|
|
--no-continue)
|
|
CONFIGURE_CONTINUE=false
|
|
;;
|
|
--no-vscode)
|
|
CONFIGURE_VSCODE_USER=false
|
|
;;
|
|
--force-update)
|
|
FORCE_UPDATE=true
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
fail "Unknown option: $1"
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
REPO_DIR="$INSTALL_ROOT/unreal-mcp"
|
|
|
|
# ---------- Dependencies ----------
|
|
require_cmd() { command -v "$1" > /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" << EOF
|
|
#!/bin/bash
|
|
set -euo pipefail
|
|
exec uv --directory "$python_dir" run unreal_mcp_server.py "\${1:-}" < /dev/null
|
|
EOF
|
|
chmod +x "$launcher"
|
|
if [[ $EUID -eq 0 ]]; then chown "$ACTUAL_USER:$ACTUAL_USER" "$launcher"; fi
|
|
log "Installed launcher: $launcher"
|
|
}
|
|
|
|
# ---------- VS Code: Continue MCP config ----------
|
|
configure_continue() {
|
|
if [[ $CONFIGURE_CONTINUE != true ]]; then
|
|
log "Skipping Continue config (--no-continue)"
|
|
return 0
|
|
fi
|
|
|
|
local cont_dir="$USER_HOME/.continue"
|
|
local cont_cfg="$cont_dir/config.json"
|
|
local python_dir="$REPO_DIR/Python"
|
|
mkdir -p "$cont_dir"
|
|
|
|
# Base JSON when no config exists
|
|
local tmp_file
|
|
tmp_file="$(mktemp)"
|
|
if [[ ! -f $cont_cfg ]]; then
|
|
cat > "$tmp_file" << JSON
|
|
{
|
|
"mcpServers": {
|
|
"unrealMCP": {
|
|
"command": "uv",
|
|
"args": ["--directory", "$python_dir", "run", "unreal_mcp_server.py"]
|
|
}
|
|
}
|
|
}
|
|
JSON
|
|
mv "$tmp_file" "$cont_cfg"
|
|
else
|
|
# Merge using jq: ensure .mcpServers exists, then set/overwrite unrealMCP
|
|
if ! require_cmd jq; then
|
|
fail "jq is required to merge ~/.continue/config.json"
|
|
fi
|
|
jq --arg dir "$python_dir" '
|
|
.mcpServers = (.mcpServers // {}) |
|
|
.mcpServers.unrealMCP = {
|
|
command: "uv",
|
|
args: ["--directory", $dir, "run", "unreal_mcp_server.py"]
|
|
}
|
|
' "$cont_cfg" > "$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 << EOF
|
|
============================================
|
|
Unreal MCP setup complete
|
|
============================================
|
|
|
|
Repo: $REPO_DIR
|
|
Python dir: $python_dir
|
|
Launcher: $USER_HOME/.local/bin/unreal-mcp-server
|
|
|
|
VS Code (Continue) MCP configured: ${CONFIGURE_CONTINUE}
|
|
- File: $USER_HOME/.continue/config.json
|
|
- Server ID: unrealMCP
|
|
|
|
VS Code (User profile) MCP configured: ${CONFIGURE_VSCODE_USER}
|
|
- Command used: code --add-mcp '{"name":"unrealMCP", "command":"uv", "args":["--directory","$python_dir","run","unreal_mcp_server.py"]}'
|
|
|
|
Optional usage:
|
|
- Run server manually: unreal-mcp-server
|
|
- In VS Code with Continue installed, the unrealMCP server will auto-start when needed.
|
|
|
|
Unreal plugin:
|
|
- Source: MCPGameProject/Plugins/UnrealMCP
|
|
- If you provided --project, the plugin was copied to: $plugin_dest
|
|
- In the Unreal Editor: Edit > 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 "$@"
|