testsAndMisc/linux_configuration/features/lib/unreal_mcp_repo.sh
Krzysztof kuhy Rudnicki 8be1fac8f9
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / lib-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
linux_configuration: track ~/.local/bin wrapper scripts in repo, symlink instead of generating
Moves 7 loose ~/.local/bin scripts into linux_configuration/scripts/ and
tracks the flutter/dart git-env wrapper; fix_flutter_git_env.sh and
unreal_mcp_repo.sh now symlink to the tracked files instead of writing
heredocs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014yhTkYV6ZeuAbQYvykFGFc

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 18:49:56 +02:00

251 lines
8.4 KiB
Bash
Executable File

#!/bin/bash
# Repo checkout, launcher install and MCP client registration.
#
# Sourced by install_unreal_mcp.sh; split out to keep it under the 250-line cap.
# Sourced rather than run, so it inherits the caller's strict mode and
# the helper functions and variables defined above the source line.
# ---------- 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 launcher="$bin_dir/unreal-mcp-server"
# The wrapper is tracked in the repo; ~/.local/bin only holds a symlink.
local target
target="$(readlink -f "$SCRIPT_DIR/../scripts/unreal-mcp-server")"
[[ -x $target ]] || fail "Tracked launcher missing: $target"
mkdir -p "$bin_dir"
ln -sfn "$target" "$launcher"
if [[ $EUID -eq 0 ]]; then chown -h "$ACTUAL_USER:$ACTUAL_USER" "$launcher"; fi
if [[ $INSTALL_ROOT != "$INSTALL_ROOT_DEFAULT" ]]; then
log "WARNING: non-default --install-dir; export UNREAL_MCP_PYTHON_DIR=$REPO_DIR/Python for the launcher"
fi
log "Installed launcher: $launcher -> $target"
}
# ---------- 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
}