Files
server-deploy/wsl-dev-stack/deploy.ps1
Joywayer dd3eb24d0f refactor: 拆分 claude-dev-stack 为 windows-dev-stack 和 wsl-dev-stack
将原 claude-dev-stack 目录拆分为独立的 Windows 和 WSL 部署栈,便于分别维护和使用。

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-29 01:11:20 +08:00

1086 lines
55 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#Requires -Version 5.1
<#
.SYNOPSIS
WSL2 + Claude Code CLI + Unity MCP + Godot MCP Pro + Rust Token Killer 全栈一键部署脚本
.DESCRIPTION
自动完成以下步骤:
1. 启用 WSL2 功能 & 安装 Ubuntu 发行版
2. 检测/安装 Windows 本机 Node.jsAI 客户端 MCP 需要)
3. WSL2 内安装 Node.js LTSClaude Code CLI 需要)
4. 安装 Claude Code CLI (@anthropic-ai/claude-code) + GitHub Copilot CLI
5. 安装 Unity MCP Server + Godot MCP ProWindows + WSL2 双侧)& 写入各 AI 客户端配置
6. 配置 Windows 防火墙放行 MCP Bridge 端口
7. 安装 Rust 工具链 & Token Killer (rtk)
8. 写入 PowerShell Profile 快捷命令
.PARAMETER BridgePort
Unity MCP Bridge 端口,默认 7890
.PARAMETER InstallDir
Windows 侧 unity-mcp-server 安装目录
.PARAMETER UnityHubPath
Unity Hub 路径(写入 MCP 配置 env
.PARAMETER SkipFirewall
跳过防火墙规则配置
.PARAMETER SkipWSL
跳过 WSL2 安装步骤
.NOTES
首次安装 WSL2 需以管理员身份运行。
已有 WSL2 可普通终端运行,加 -SkipWSL 跳过 WSL2 安装检查。
#>
param(
[int] $BridgePort = 7890,
[string]$InstallDir = "$env:USERPROFILE\unity-mcp-server",
[string]$UnityHubPath = "C:\Program Files\Unity Hub\Unity Hub.exe",
[string]$GodotMcpVersion = "godot-mcp-pro-v1.14.1",
[switch]$SkipFirewall,
[switch]$SkipWSL
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
# ──────────────────────────────────────────────────────────────
# 颜色日志辅助
# ──────────────────────────────────────────────────────────────
function Write-Step { param($msg) Write-Host "`n==== $msg ====" -ForegroundColor Cyan }
function Write-OK { param($msg) Write-Host " [OK] $msg" -ForegroundColor Green }
function Write-Info { param($msg) Write-Host " [..] $msg" -ForegroundColor DarkGray }
function Write-Warn { param($msg) Write-Host " [!!] $msg" -ForegroundColor Yellow }
function Write-Fail { param($msg) Write-Host " [ERR] $msg" -ForegroundColor Red }
# ──────────────────────────────────────────────────────────────
# 加载 .env 配置
# ──────────────────────────────────────────────────────────────
$ScriptDir = $PSScriptRoot
$EnvFile = Join-Path $ScriptDir ".env"
$EnvExample = Join-Path $ScriptDir ".env.example"
if (-not (Test-Path $EnvFile)) {
if (Test-Path $EnvExample) {
Copy-Item $EnvExample $EnvFile
Write-Warn "已从 .env.example 创建 .env请按需编辑后重新运行"
Write-Warn " notepad $EnvFile"
exit 0
}
}
$Config = @{}
if (Test-Path $EnvFile) {
Get-Content $EnvFile | Where-Object { $_ -match "^\s*[^#].*=" } | ForEach-Object {
$parts = $_ -split "=", 2
$key = $parts[0].Trim()
$val = $parts[1].Trim().Trim('"').Trim("'")
$Config[$key] = $val
}
Write-Info "已加载配置:$EnvFile"
}
$ANTHROPIC_API_KEY = if ($Config["ANTHROPIC_API_KEY"]) { $Config["ANTHROPIC_API_KEY"] } else { "" }
$ANTHROPIC_AUTH_TOKEN = if ($Config["ANTHROPIC_AUTH_TOKEN"]) { $Config["ANTHROPIC_AUTH_TOKEN"] } else { "" }
$ANTHROPIC_BASE_URL = if ($Config["ANTHROPIC_BASE_URL"]) { $Config["ANTHROPIC_BASE_URL"] } else { "https://api.lmuai.com" }
$CLAUDE_MODEL = if ($Config["CLAUDE_MODEL"]) { $Config["CLAUDE_MODEL"] } else { "claude-sonnet-4-6" }
$WSL_DISTRO = if ($Config["WSL_DISTRO"]) { $Config["WSL_DISTRO"] } else { "Ubuntu" }
$SKIP_WSL_INSTALL = if ($Config["SKIP_WSL_INSTALL"]) { $Config["SKIP_WSL_INSTALL"] } else { "false" }
# 判断认证方式:灵眸/中转用 AUTH_TOKEN官方用 API_KEY
$UseLmuAuth = ($ANTHROPIC_AUTH_TOKEN -ne "" -and $ANTHROPIC_BASE_URL -ne "https://api.anthropic.com")
# ──────────────────────────────────────────────────────────────
# Banner
# ──────────────────────────────────────────────────────────────
Write-Host ""
Write-Host "╔══════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
Write-Host "║ WSL2 + Claude Code + Unity MCP + Godot MCP Pro + RTK ║" -ForegroundColor Cyan
Write-Host "║ AnkleBreaker Unity MCP · Godot MCP Pro · WSL2 Mirror ║" -ForegroundColor Cyan
Write-Host "╚══════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
Write-Host ""
# ══════════════════════════════════════════════════════════════
# Step 1: WSL2 安装
# ══════════════════════════════════════════════════════════════
Write-Step "1/8 WSL2 环境检测 & 安装"
$distroInstalled = $false
try {
$dl = wsl --list --quiet 2>&1 | ForEach-Object { $_ -replace "`0","" }
if ($dl -match [regex]::Escape($WSL_DISTRO)) { $distroInstalled = $true }
} catch {}
if ($distroInstalled) {
Write-OK "WSL2 + $WSL_DISTRO 已安装,跳过"
} elseif ($SkipWSL -or $SKIP_WSL_INSTALL -eq "true") {
Write-Warn "跳过 WSL2 安装"
} else {
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Fail "安装 WSL2 需要管理员权限,请以管理员身份运行 PowerShell"
Write-Info "如已安装 WSL2请加 -SkipWSL 参数或在 .env 设置 SKIP_WSL_INSTALL=true"
exit 1
}
try {
$f1 = Get-WindowsOptionalFeature -Online -FeatureName "Microsoft-Windows-Subsystem-Linux" -ErrorAction Stop
if ($f1.State -ne "Enabled") {
Enable-WindowsOptionalFeature -Online -FeatureName "Microsoft-Windows-Subsystem-Linux" -NoRestart | Out-Null
}
$f2 = Get-WindowsOptionalFeature -Online -FeatureName "VirtualMachinePlatform" -ErrorAction Stop
if ($f2.State -ne "Enabled") {
Enable-WindowsOptionalFeature -Online -FeatureName "VirtualMachinePlatform" -NoRestart | Out-Null
}
} catch {
# Fallback: Get-WindowsOptionalFeature 不可用WMI "没有注册类"),改用 dism.exe
Write-Warn "Get-WindowsOptionalFeature 不可用,回退至 dism.exe 启用 Windows 功能..."
$dismOut1 = (dism.exe /online /Get-FeatureInfo /FeatureName:Microsoft-Windows-Subsystem-Linux 2>&1) | Out-String
if ($dismOut1 -notmatch "State : Enabled") {
dism.exe /online /Enable-Feature /FeatureName:Microsoft-Windows-Subsystem-Linux /NoRestart | Out-Null
}
$dismOut2 = (dism.exe /online /Get-FeatureInfo /FeatureName:VirtualMachinePlatform 2>&1) | Out-String
if ($dismOut2 -notmatch "State : Enabled") {
dism.exe /online /Enable-Feature /FeatureName:VirtualMachinePlatform /NoRestart | Out-Null
}
}
wsl --update 2>&1 | Out-Null
wsl --set-default-version 2 2>&1 | Out-Null
wsl --install -d $WSL_DISTRO --no-launch 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Fail "WSL2 安装失败,系统可能需要重启后重新运行"
exit 1
}
Write-OK "WSL2 $WSL_DISTRO 安装完成"
Write-Warn "首次安装可能需要重启,重启后重新运行脚本"
}
# ── Step 1b: 首次 OOBE 处理 & 设置 root 为默认用户 ──────────
# Ubuntu OOBE 检测:若无 UID≥1000 的普通用户则弹交互提示
# 解决方案:创建占位用户满足检查 + wsl.conf 设 default=root
Write-Info "初始化 WSL2跳过 OOBE设置 root 为默认用户)..."
$initCmd = @'
# 创建占位用户,满足 Ubuntu OOBE "至少有一个真实用户" 的检查
id wsluser &>/dev/null || useradd -m -s /bin/bash -u 1000 wsluser
# 覆盖写入 /etc/wsl.conf单一 [user] 块,避免重复冲突)
printf '[boot]\nsystemd=false\n\n[user]\ndefault=root\n' > /etc/wsl.conf
# 禁用 Ubuntu OOBE 自动运行脚本(如存在)
rm -f /etc/profile.d/01-wsl-oobe.sh 2>/dev/null || true
rm -f /usr/lib/ubuntu-advantage/ua-messaging.service 2>/dev/null || true
true
'@
# 用 --user root 初始化 rootfs本身也会跳过 OOBE 交互)
wsl -d $WSL_DISTRO --user root -- bash -c "exit 0" 2>$null
Start-Sleep 2
# 写入配置
$b64Init = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($initCmd))
wsl -d $WSL_DISTRO --user root -- bash -c "echo '$b64Init' | base64 -d | bash" 2>$null
Write-OK "/etc/wsl.conf 已写入 (default=root),占位用户已创建"
# 重启使 wsl.conf 生效
wsl --terminate $WSL_DISTRO 2>$null
Start-Sleep 2
Write-OK "WSL2 已重启,后续命令将以 root 运行"
# ── WSL2 执行辅助函数 ─────────────────────────────────────────
function Invoke-WSL {
param([string]$Command, [switch]$IgnoreError)
if ($Command -match "`n") {
# 多行命令base64 编码后在 bash 内解码执行,完全规避 PowerShell 管道 CRLF 问题
$CleanCmd = $Command -replace "`r`n","`n" -replace "`r","`n"
$b64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($CleanCmd))
$result = wsl -d $WSL_DISTRO --user root -- bash -c "echo '$b64' | base64 -d | bash" 2>&1 |
ForEach-Object { ($_ -replace "`0","").ToString() }
} else {
$result = wsl -d $WSL_DISTRO --user root -- bash -c $Command 2>&1 |
ForEach-Object { ($_ -replace "`0","").ToString() }
}
if ($LASTEXITCODE -ne 0 -and -not $IgnoreError) {
Write-Fail "WSL 命令失败 (exit $LASTEXITCODE)"
Write-Fail ($result -join "`n")
exit 1
}
return ($result -join "`n")
}
# 用 exit code 方式测试连通性,避免输出编码干扰
wsl -d $WSL_DISTRO --user root -- bash -c "exit 0" 2>$null
if ($LASTEXITCODE -ne 0) {
Write-Fail "无法访问 WSL2 发行版 '$WSL_DISTRO'"
exit 1
}
Write-OK "WSL2 ($WSL_DISTRO) 连接正常 (root)"
# ── Step 1a: 配置 .wslconfig镜像网络模式仅 Windows 11 22H2+ 支持) ────
$wslCfgPath = "$env:USERPROFILE\.wslconfig"
$osBuild = [System.Environment]::OSVersion.Version.Build
$supportsmirror = ($osBuild -ge 22621) # Win11 22H2+
$needRestart = $false
if ($supportsmirror) {
$wslCfgContent = @"
[wsl2]
networkingMode=mirrored
dnsTunneling=true
firewall=true
autoProxy=true
"@
if (Test-Path $wslCfgPath) {
$existing = Get-Content $wslCfgPath -Raw
if ($existing -notmatch "networkingMode=mirrored") {
Set-Content $wslCfgPath $wslCfgContent -Encoding UTF8
$needRestart = $true
Write-OK ".wslconfig 已更新 -> networkingMode=mirrored"
}
} else {
Set-Content $wslCfgPath $wslCfgContent -Encoding UTF8
$needRestart = $true
Write-OK ".wslconfig 已创建 -> networkingMode=mirrored"
}
if ($needRestart) {
Write-Info "重启 WSL2 以应用镜像网络模式..."
wsl --shutdown 2>$null
Start-Sleep 3
}
} else {
Write-Warn "当前 Windows 版本Build $osBuild)不支持 WSL2 镜像网络模式(需要 Windows 11 22H2+ / Build 22621+),已跳过 .wslconfig 镜像配置"
Write-Info "WSL2 将使用默认 NAT 网络模式,代理请在 WSL2 内手动配置"
}
# ══════════════════════════════════════════════════════════════
# Step 2: Windows 本机 Node.js 检查AI 客户端 MCP 需要)
# ══════════════════════════════════════════════════════════════
Write-Step "2/8 Windows Node.js 检查"
$winNode = Get-Command node -ErrorAction SilentlyContinue
if ($winNode) {
$winNodeVer = & node --version
Write-OK "Windows Node.js 已安装: $winNodeVer"
} else {
Write-Warn "Windows 本机未检测到 Node.js"
Write-Info "尝试通过 winget 安装 Node.js LTS..."
$wingetCmd = Get-Command winget -ErrorAction SilentlyContinue
if ($wingetCmd) {
winget install --id OpenJS.NodeJS.LTS --silent --accept-source-agreements --accept-package-agreements 2>&1 | Out-Null
# 刷新 PATH
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" +
[System.Environment]::GetEnvironmentVariable("Path","User")
$winNode = Get-Command node -ErrorAction SilentlyContinue
if ($winNode) {
Write-OK "Node.js 安装成功: $(& node --version)"
} else {
Write-Warn "winget 安装后未找到 node请手动安装 Node.js 18+"
Write-Warn " https://nodejs.org/zh-cn/download"
Write-Warn " 或: winget install OpenJS.NodeJS.LTS"
}
} else {
Write-Warn "未找到 winget请手动安装 Node.js 18+"
Write-Warn " https://nodejs.org/zh-cn/download"
}
}
# ══════════════════════════════════════════════════════════════
# Step 3: WSL2 系统依赖 & Node.js
# ══════════════════════════════════════════════════════════════
Write-Step "3/8 WSL2 Node.js LTS"
# 检测 WSL2 原生 node排除通过 WSL interop 调用的 Windows node路径含 /mnt/
$nodeVer = Invoke-WSL @'
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
_n=$(which node 2>/dev/null)
if [ -n "$_n" ] && ! echo "$_n" | grep -q '/mnt/'; then
node --version
else
echo MISSING
fi
true
'@ -IgnoreError
if ($nodeVer -match "v\d+") {
Write-OK "WSL2 Node.js 已安装 (原生): $($nodeVer.Trim())"
} else {
Write-Info "安装 WSL2 Node.js LTS (via nvm)..."
$installNodeCmd = @'
export DEBIAN_FRONTEND=noninteractive
sudo apt-get install -y -qq curl ca-certificates wslu 2>/dev/null
# 安装 nvm
export NVM_DIR="$HOME/.nvm"
if [ ! -d "$NVM_DIR" ]; then
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
fi
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
# 安装 Node.js LTS
nvm install --lts
nvm use --lts
# 写入 .bashrc幂等
grep -q 'NVM_DIR' ~/.bashrc || cat >> ~/.bashrc << 'NVMEOF'
# nvm (Node Version Manager)
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
NVMEOF
node --version
npm --version
true
'@
Invoke-WSL $installNodeCmd
Write-OK "WSL2 Node.js 安装完成"
}
# ══════════════════════════════════════════════════════════════
# Step 4: Claude Code CLI + GitHub Copilot CLI (WSL2)
# ══════════════════════════════════════════════════════════════
Write-Step "4/8 Claude Code CLI + GitHub Copilot CLI"
# 检测 WSL2 原生 claude排除通过 WSL interop 调用的 Windows claude.exe路径含 /mnt/
$claudeVer = Invoke-WSL @'
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
_c=$(which claude 2>/dev/null)
if [ -n "$_c" ] && ! echo "$_c" | grep -q '/mnt/'; then
claude --version 2>/dev/null
else
echo MISSING
fi
true
'@ -IgnoreError
if ($claudeVer -notmatch "MISSING") {
Write-OK "Claude Code 已安装 (WSL2 原生): $($claudeVer.Trim())"
} else {
Write-Info "安装 @anthropic-ai/claude-code (WSL2 原生)..."
$installClaudeCmd = @'
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
npm install -g @anthropic-ai/claude-code --quiet
which claude
claude --version
true
'@
Invoke-WSL $installClaudeCmd
Write-OK "Claude Code 安装完成"
}
# 检测 WSL2 原生 GitHub CLI排除通过 WSL interop 调用的 Windows gh.exe路径含 /mnt/
$ghVer = Invoke-WSL @'
_g=$(which gh 2>/dev/null)
if [ -n "$_g" ] && ! echo "$_g" | grep -q '/mnt/'; then
gh --version 2>/dev/null | head -1
else
echo MISSING
fi
true
'@ -IgnoreError
$ghCopilotInstalled = Invoke-WSL @'
_g=$(which gh 2>/dev/null)
if [ -n "$_g" ] && ! echo "$_g" | grep -q '/mnt/'; then
gh extension list 2>/dev/null | grep -q 'github/gh-copilot'
echo $?
else
echo 1
fi
true
'@ -IgnoreError
if ($ghVer -notmatch "MISSING") {
Write-OK "GitHub CLI 已安装 (WSL2 原生): $($ghVer.Trim())"
} else {
Write-Info "安装 GitHub CLI (WSL2 原生)..."
$installGhCmd = @'
export DEBIAN_FRONTEND=noninteractive
sudo mkdir -p -m 755 /etc/apt/keyrings
if [ ! -f /etc/apt/keyrings/githubcli-archive-keyring.gpg ]; then
curl -fsSL --connect-timeout 20 --max-time 30 https://cli.github.com/packages/githubcli-archive-keyring.gpg \
| sudo dd of=/etc/apt/keyrings/githubcli-archive-keyring.gpg status=none
sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg
fi
if [ ! -f /etc/apt/sources.list.d/github-cli.list ]; then
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
| sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
fi
if sudo apt-get update -qq -o Acquire::http::Timeout=20 -o Acquire::https::Timeout=20 2>/dev/null && \
sudo apt-get install -y -qq -o Acquire::http::Timeout=30 gh 2>/dev/null; then
echo GH_INSTALL_OK
else
echo GH_INSTALL_FAIL
fi
true
'@
$ghInstallResult = Invoke-WSL $installGhCmd -IgnoreError
$ghVer = Invoke-WSL "gh --version 2>/dev/null | head -1 || echo MISSING" -IgnoreError
if ($ghInstallResult -match "GH_INSTALL_FAIL" -or $ghVer -match "MISSING") {
Write-Warn "GitHub CLI 安装失败网络超时跳过后续可手动安装wsl -d Ubuntu -- sudo apt install gh"
$ghVer = "MISSING"
} else {
Write-OK "GitHub CLI 安装完成: $($ghVer.Trim())"
}
}
if ($ghCopilotInstalled.Trim() -eq "0") {
Write-OK "GitHub Copilot CLI 扩展已安装 (WSL2 原生)"
} else {
Write-Info "安装 GitHub Copilot CLI 扩展 (gh-copilot)..."
$installGhCopilotCmd = @'
if gh extension list 2>/dev/null | grep -q 'github/gh-copilot'; then
gh extension upgrade github/gh-copilot 2>/dev/null || true
else
gh extension install github/gh-copilot
fi
gh extension list | grep 'github/gh-copilot' || true
true
'@
Invoke-WSL $installGhCopilotCmd
Write-OK "GitHub Copilot CLI 扩展已安装命令入口gh copilot"
}
# 写入 Claude 配置WSL2 侧)— 含 allowedTools 完整白名单(自动信任,无需每次确认)
$mcpAllowedTools = @(
# Claude 内置工具
"Bash","Read","Write","Edit","MultiEdit","Glob","Grep","LS","WebFetch","TodoRead","TodoWrite",
# Editor State
"mcp__unity-mcp__unity_editor_ping","mcp__unity-mcp__unity_editor_state","mcp__unity-mcp__unity_project_info",
# Scene
"mcp__unity-mcp__unity_scene_info","mcp__unity-mcp__unity_scene_open","mcp__unity-mcp__unity_scene_save",
"mcp__unity-mcp__unity_scene_new","mcp__unity-mcp__unity_scene_hierarchy","mcp__unity-mcp__unity_scene_stats",
# GameObject
"mcp__unity-mcp__unity_gameobject_create","mcp__unity-mcp__unity_gameobject_delete",
"mcp__unity-mcp__unity_gameobject_info","mcp__unity-mcp__unity_gameobject_set_transform",
"mcp__unity-mcp__unity_gameobject_duplicate","mcp__unity-mcp__unity_gameobject_set_active",
"mcp__unity-mcp__unity_gameobject_reparent",
# Component
"mcp__unity-mcp__unity_component_add","mcp__unity-mcp__unity_component_remove",
"mcp__unity-mcp__unity_component_get_properties","mcp__unity-mcp__unity_component_set_property",
"mcp__unity-mcp__unity_component_set_reference","mcp__unity-mcp__unity_component_batch_wire",
"mcp__unity-mcp__unity_component_get_referenceable",
# Asset
"mcp__unity-mcp__unity_asset_list","mcp__unity-mcp__unity_asset_import",
"mcp__unity-mcp__unity_asset_delete","mcp__unity-mcp__unity_asset_create_prefab",
"mcp__unity-mcp__unity_asset_instantiate_prefab",
# Script & Code
"mcp__unity-mcp__unity_script_create","mcp__unity-mcp__unity_script_read",
"mcp__unity-mcp__unity_script_update","mcp__unity-mcp__unity_execute_code",
# Material
"mcp__unity-mcp__unity_material_create","mcp__unity-mcp__unity_renderer_set_material",
# Build & Play Mode
"mcp__unity-mcp__unity_build","mcp__unity-mcp__unity_play_mode",
# Console & Compilation
"mcp__unity-mcp__unity_console_log","mcp__unity-mcp__unity_console_clear",
"mcp__unity-mcp__unity_get_compilation_errors",
# Editor Actions
"mcp__unity-mcp__unity_execute_menu_item","mcp__unity-mcp__unity_undo",
"mcp__unity-mcp__unity_redo","mcp__unity-mcp__unity_undo_history",
# Selection & Search
"mcp__unity-mcp__unity_selection_get","mcp__unity-mcp__unity_selection_set",
"mcp__unity-mcp__unity_selection_focus_scene_view","mcp__unity-mcp__unity_selection_find_by_type",
"mcp__unity-mcp__unity_search_by_component","mcp__unity-mcp__unity_search_by_tag",
"mcp__unity-mcp__unity_search_by_layer","mcp__unity-mcp__unity_search_by_name",
"mcp__unity-mcp__unity_search_assets","mcp__unity-mcp__unity_search_missing_references",
# Screenshots & Graphics
"mcp__unity-mcp__unity_screenshot_game","mcp__unity-mcp__unity_screenshot_scene",
"mcp__unity-mcp__unity_graphics_scene_capture","mcp__unity-mcp__unity_graphics_game_capture",
# Prefab
"mcp__unity-mcp__unity_prefab_info","mcp__unity-mcp__unity_set_object_reference",
# Packages
"mcp__unity-mcp__unity_packages_list","mcp__unity-mcp__unity_packages_add",
"mcp__unity-mcp__unity_packages_remove","mcp__unity-mcp__unity_packages_search",
"mcp__unity-mcp__unity_packages_info",
# Queue & Multi-Agent
"mcp__unity-mcp__unity_queue_info","mcp__unity-mcp__unity_agents_list","mcp__unity-mcp__unity_agent_log",
# Advanced Tools proxy200+ 工具通过此代理访问)
"mcp__unity-mcp__unity_list_advanced_tools","mcp__unity-mcp__unity_advanced_tool",
# Unity Hub
"mcp__unity-mcp__unity_hub_list_editors","mcp__unity-mcp__unity_hub_available_releases",
"mcp__unity-mcp__unity_hub_install_editor","mcp__unity-mcp__unity_hub_install_modules",
"mcp__unity-mcp__unity_hub_get_install_path","mcp__unity-mcp__unity_hub_set_install_path",
# Multi-Instance & Project Context
"mcp__unity-mcp__unity_list_instances","mcp__unity-mcp__unity_select_instance",
"mcp__unity-mcp__unity_get_project_context",
# ─── Godot MCP Pro ───────────────────────────────────────
# Project & Filesystem
"mcp__godot-mcp-pro__get_project_info","mcp__godot-mcp-pro__get_filesystem_tree",
"mcp__godot-mcp-pro__search_files","mcp__godot-mcp-pro__search_in_files",
"mcp__godot-mcp-pro__get_project_settings","mcp__godot-mcp-pro__get_project_statistics",
"mcp__godot-mcp-pro__uid_to_project_path","mcp__godot-mcp-pro__project_path_to_uid",
# Scene
"mcp__godot-mcp-pro__get_scene_tree","mcp__godot-mcp-pro__get_scene_file_content",
"mcp__godot-mcp-pro__get_scene_exports","mcp__godot-mcp-pro__get_scene_dependencies",
"mcp__godot-mcp-pro__create_scene","mcp__godot-mcp-pro__open_scene",
"mcp__godot-mcp-pro__save_scene","mcp__godot-mcp-pro__add_scene_instance",
# Node
"mcp__godot-mcp-pro__get_node_properties","mcp__godot-mcp-pro__get_node_groups",
"mcp__godot-mcp-pro__get_signals","mcp__godot-mcp-pro__find_nodes_in_group",
"mcp__godot-mcp-pro__find_nodes_by_type","mcp__godot-mcp-pro__find_signal_connections",
"mcp__godot-mcp-pro__find_node_references",
"mcp__godot-mcp-pro__add_node","mcp__godot-mcp-pro__duplicate_node",
"mcp__godot-mcp-pro__move_node","mcp__godot-mcp-pro__rename_node",
"mcp__godot-mcp-pro__update_property","mcp__godot-mcp-pro__add_resource",
"mcp__godot-mcp-pro__set_anchor_preset",
"mcp__godot-mcp-pro__connect_signal","mcp__godot-mcp-pro__disconnect_signal",
"mcp__godot-mcp-pro__set_node_groups",
"mcp__godot-mcp-pro__batch_set_property","mcp__godot-mcp-pro__cross_scene_set_property",
# Script
"mcp__godot-mcp-pro__list_scripts","mcp__godot-mcp-pro__read_script",
"mcp__godot-mcp-pro__get_open_scripts","mcp__godot-mcp-pro__validate_script",
"mcp__godot-mcp-pro__create_script","mcp__godot-mcp-pro__edit_script",
"mcp__godot-mcp-pro__attach_script",
# Editor & Output
"mcp__godot-mcp-pro__get_editor_errors","mcp__godot-mcp-pro__get_output_log",
"mcp__godot-mcp-pro__get_editor_screenshot","mcp__godot-mcp-pro__get_editor_performance",
"mcp__godot-mcp-pro__clear_output","mcp__godot-mcp-pro__reload_plugin",
"mcp__godot-mcp-pro__reload_project",
# Resource & Shader
"mcp__godot-mcp-pro__read_resource","mcp__godot-mcp-pro__get_resource_preview",
"mcp__godot-mcp-pro__create_resource","mcp__godot-mcp-pro__edit_resource",
"mcp__godot-mcp-pro__read_shader","mcp__godot-mcp-pro__get_shader_params",
"mcp__godot-mcp-pro__create_shader","mcp__godot-mcp-pro__edit_shader",
"mcp__godot-mcp-pro__assign_shader_material","mcp__godot-mcp-pro__set_shader_param",
# Animation
"mcp__godot-mcp-pro__list_animations","mcp__godot-mcp-pro__get_animation_info",
"mcp__godot-mcp-pro__create_animation","mcp__godot-mcp-pro__add_animation_track",
"mcp__godot-mcp-pro__set_animation_keyframe",
"mcp__godot-mcp-pro__get_animation_tree_structure",
"mcp__godot-mcp-pro__create_animation_tree","mcp__godot-mcp-pro__add_state_machine_state",
"mcp__godot-mcp-pro__add_state_machine_transition",
"mcp__godot-mcp-pro__set_blend_tree_node","mcp__godot-mcp-pro__set_tree_parameter",
# Audio
"mcp__godot-mcp-pro__get_audio_bus_layout","mcp__godot-mcp-pro__get_audio_info",
"mcp__godot-mcp-pro__add_audio_bus","mcp__godot-mcp-pro__set_audio_bus",
"mcp__godot-mcp-pro__add_audio_bus_effect","mcp__godot-mcp-pro__add_audio_player",
# Input
"mcp__godot-mcp-pro__get_input_actions","mcp__godot-mcp-pro__set_input_action",
# 3D & Physics
"mcp__godot-mcp-pro__get_physics_layers","mcp__godot-mcp-pro__get_collision_info",
"mcp__godot-mcp-pro__set_physics_layers",
"mcp__godot-mcp-pro__add_mesh_instance","mcp__godot-mcp-pro__setup_lighting",
"mcp__godot-mcp-pro__set_material_3d","mcp__godot-mcp-pro__setup_environment",
"mcp__godot-mcp-pro__setup_camera_3d","mcp__godot-mcp-pro__add_gridmap",
"mcp__godot-mcp-pro__setup_collision","mcp__godot-mcp-pro__add_raycast",
"mcp__godot-mcp-pro__setup_physics_body",
# Navigation
"mcp__godot-mcp-pro__get_navigation_info",
"mcp__godot-mcp-pro__setup_navigation_region","mcp__godot-mcp-pro__bake_navigation_mesh",
"mcp__godot-mcp-pro__setup_navigation_agent","mcp__godot-mcp-pro__set_navigation_layers",
# Particles
"mcp__godot-mcp-pro__get_particle_info",
"mcp__godot-mcp-pro__create_particles","mcp__godot-mcp-pro__set_particle_material",
"mcp__godot-mcp-pro__set_particle_color_gradient","mcp__godot-mcp-pro__apply_particle_preset",
# Theme
"mcp__godot-mcp-pro__get_theme_info",
"mcp__godot-mcp-pro__create_theme","mcp__godot-mcp-pro__set_theme_color",
"mcp__godot-mcp-pro__set_theme_constant","mcp__godot-mcp-pro__set_theme_font_size",
"mcp__godot-mcp-pro__set_theme_stylebox",
# Tilemap
"mcp__godot-mcp-pro__tilemap_get_cell","mcp__godot-mcp-pro__tilemap_get_info",
"mcp__godot-mcp-pro__tilemap_get_used_cells",
"mcp__godot-mcp-pro__tilemap_set_cell","mcp__godot-mcp-pro__tilemap_fill_rect",
# Autoload & Project Settings
"mcp__godot-mcp-pro__get_autoload","mcp__godot-mcp-pro__add_autoload",
"mcp__godot-mcp-pro__set_project_setting",
# Analysis
"mcp__godot-mcp-pro__find_unused_resources","mcp__godot-mcp-pro__analyze_signal_flow",
"mcp__godot-mcp-pro__analyze_scene_complexity","mcp__godot-mcp-pro__find_script_references",
"mcp__godot-mcp-pro__detect_circular_dependencies",
"mcp__godot-mcp-pro__get_performance_monitors",
# Export
"mcp__godot-mcp-pro__get_export_info","mcp__godot-mcp-pro__list_export_presets",
# Game Runtime (need play_scene first)
"mcp__godot-mcp-pro__play_scene","mcp__godot-mcp-pro__stop_scene",
"mcp__godot-mcp-pro__get_game_scene_tree","mcp__godot-mcp-pro__get_game_node_properties",
"mcp__godot-mcp-pro__get_game_screenshot","mcp__godot-mcp-pro__set_game_node_property",
"mcp__godot-mcp-pro__find_nodes_by_script",
# Input Simulation
"mcp__godot-mcp-pro__simulate_key","mcp__godot-mcp-pro__simulate_mouse_click",
"mcp__godot-mcp-pro__simulate_mouse_move","mcp__godot-mcp-pro__simulate_action",
"mcp__godot-mcp-pro__simulate_sequence",
# Capture & Recording
"mcp__godot-mcp-pro__capture_frames","mcp__godot-mcp-pro__record_frames",
"mcp__godot-mcp-pro__monitor_properties","mcp__godot-mcp-pro__batch_get_properties",
"mcp__godot-mcp-pro__start_recording","mcp__godot-mcp-pro__stop_recording",
"mcp__godot-mcp-pro__replay_recording",
# UI & Navigation (Runtime)
"mcp__godot-mcp-pro__find_ui_elements","mcp__godot-mcp-pro__click_button_by_text",
"mcp__godot-mcp-pro__wait_for_node","mcp__godot-mcp-pro__find_nearby_nodes",
"mcp__godot-mcp-pro__navigate_to","mcp__godot-mcp-pro__move_to",
# Testing
"mcp__godot-mcp-pro__run_test_scenario","mcp__godot-mcp-pro__assert_node_state",
"mcp__godot-mcp-pro__assert_screen_text","mcp__godot-mcp-pro__run_stress_test",
"mcp__godot-mcp-pro__get_test_report","mcp__godot-mcp-pro__compare_screenshots"
)
$claudeSettingsJson = if ($UseLmuAuth) {
# 灵眸 / 中转 API使用 env.ANTHROPIC_AUTH_TOKEN避免与 ANTHROPIC_API_KEY 冲突)
[ordered]@{
env = [ordered]@{
ANTHROPIC_BASE_URL = $ANTHROPIC_BASE_URL
ANTHROPIC_AUTH_TOKEN = $ANTHROPIC_AUTH_TOKEN
API_TIMEOUT_MS = "3000000"
CLAUDE_CODE_ATTRIBUTION_HEADER = "0"
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1"
}
model = $CLAUDE_MODEL
allowedTools = $mcpAllowedTools
} | ConvertTo-Json -Depth 4
} else {
# Anthropic 官方:使用 model + allowedToolsAPI Key 由环境变量注入
[ordered]@{ model = $CLAUDE_MODEL; allowedTools = $mcpAllowedTools } | ConvertTo-Json -Depth 3
}
$writeClaudeSettingsCmd = @"
mkdir -p ~/.claude
cat > ~/.claude/settings.json << 'SETTINGS'
$claudeSettingsJson
SETTINGS
true
"@
Invoke-WSL $writeClaudeSettingsCmd -IgnoreError | Out-Null
# 写入 bash 环境变量WSL2 侧)
# 灵眸模式auth token 已在 settings.json env 块中,清理旧 bashrc 变量避免冲突
# 官方模式:写入 ANTHROPIC_API_KEY 到 bashrc
$cleanOldVarsCmd = @'
sed -i '/ANTHROPIC_API_KEY/d' ~/.bashrc ~/.profile 2>/dev/null || true
sed -i '/ANTHROPIC_BASE_URL/d' ~/.bashrc ~/.profile 2>/dev/null || true
sed -i '/CLAUDE_MODEL/d' ~/.bashrc ~/.profile 2>/dev/null || true
sed -i '/# Claude Code CLI/d' ~/.bashrc ~/.profile 2>/dev/null || true
true
'@
Invoke-WSL $cleanOldVarsCmd -IgnoreError | Out-Null
if (-not $UseLmuAuth -and $ANTHROPIC_API_KEY) {
$profBlock = "export ANTHROPIC_API_KEY='$ANTHROPIC_API_KEY'\n"
$profBlock += "export ANTHROPIC_BASE_URL='$ANTHROPIC_BASE_URL'\n"
$profBlock += "export CLAUDE_MODEL='$CLAUDE_MODEL'\n"
$addEnvCmd = @"
printf '\n# Claude Code CLI\n$profBlock' >> ~/.bashrc
true
"@
Invoke-WSL $addEnvCmd -IgnoreError | Out-Null
}
# Windows 侧 Claude 配置
$claudeDir = "$env:USERPROFILE\.claude"
if (-not (Test-Path $claudeDir)) { New-Item -ItemType Directory -Path $claudeDir | Out-Null }
$claudeSettingsJson | Set-Content "$claudeDir\settings.json" -Encoding UTF8
if ($UseLmuAuth) {
# 灵眸token 已在 settings.json 中,清除可能冲突的 Windows 用户级变量
[System.Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", $null, "User")
[System.Environment]::SetEnvironmentVariable("ANTHROPIC_BASE_URL", $null, "User")
[System.Environment]::SetEnvironmentVariable("CLAUDE_MODEL", $null, "User")
} elseif ($ANTHROPIC_API_KEY) {
[System.Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", $ANTHROPIC_API_KEY, "User")
[System.Environment]::SetEnvironmentVariable("ANTHROPIC_BASE_URL", $ANTHROPIC_BASE_URL, "User")
[System.Environment]::SetEnvironmentVariable("CLAUDE_MODEL", $CLAUDE_MODEL, "User")
}
Write-OK "Claude Code 配置已写入Windows + WSL2"
# ══════════════════════════════════════════════════════════════
# Step 5: Unity MCP Server 安装
# ══════════════════════════════════════════════════════════════
Write-Step "5/8 MCP Servers (Unity + Godot MCP Pro)"
Write-Info "MCP Server: https://github.com/AnkleBreaker-Studio/unity-mcp-server"
Write-Info "Unity Plugin: https://github.com/AnkleBreaker-Studio/unity-mcp-plugin"
# ── 5a. Windows 侧安装(供 Claude Desktop / Cursor / Windsurf / VS Code────
Write-Info "安装 Windows 侧 MCP Server -> $InstallDir"
if (Test-Path "$InstallDir\.git") {
Write-Info "已存在,执行 git pull..."
Push-Location $InstallDir
try { git pull --quiet 2>&1 | Out-Null } finally { Pop-Location }
} else {
if (Test-Path $InstallDir) { Remove-Item $InstallDir -Recurse -Force }
git clone --quiet --depth 1 https://github.com/AnkleBreaker-Studio/unity-mcp-server.git $InstallDir
}
$winNodeExists = (Get-Command node -ErrorAction SilentlyContinue) -ne $null
if ($winNodeExists) {
Push-Location $InstallDir
try { & npm install --prefer-offline --quiet 2>&1 | Out-Null } finally { Pop-Location }
Write-OK "Windows MCP Server 就绪: $InstallDir"
} else {
Write-Warn "Windows Node.js 未就绪,跳过 Windows 侧 npm install请安装 Node.js 后重新运行)"
}
# ── 5b. WSL2 侧安装(供 Claude Code CLI─────────────────────
Write-Info "安装 WSL2 侧 MCP Server..."
$wslMcpDir = "~/.mcp-servers/unity-mcp-server"
$wslMcpCmd = @'
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
mkdir -p $HOME/.mcp-servers
if [ -d $HOME/.mcp-servers/unity-mcp-server/.git ]; then
git -C $HOME/.mcp-servers/unity-mcp-server pull --quiet 2>/dev/null || true
else
git clone --quiet --depth 1 https://github.com/AnkleBreaker-Studio/unity-mcp-server.git \
$HOME/.mcp-servers/unity-mcp-server 2>/dev/null \
|| git -c http.proxy="" -c https.proxy="" clone --quiet --depth 1 \
https://github.com/AnkleBreaker-Studio/unity-mcp-server.git \
$HOME/.mcp-servers/unity-mcp-server
fi
if [ -d $HOME/.mcp-servers/unity-mcp-server ]; then
cd $HOME/.mcp-servers/unity-mcp-server
# 修复可能由 root 遗留的权限问题
sudo chown -R $(whoami):$(whoami) . 2>/dev/null || true
npm install --prefer-offline --quiet 2>/dev/null || npm install --quiet
fi
echo MCP_WSL_OK
true
'@
Invoke-WSL $wslMcpCmd
$wslHome = (Invoke-WSL "echo `$HOME" -IgnoreError).Trim()
$wslScript = "$wslHome/.mcp-servers/unity-mcp-server/src/index.js"
Write-OK "WSL2 MCP Server 就绪: $wslScript"
# ── 5c. 写入各 AI 客户端 MCP 配置 ───────────────────────────
$winScript = ($InstallDir -replace "\\","/") + "/src/index.js"
$mcpEntry = @{
command = "node"
args = @($winScript)
env = @{
UNITY_HUB_PATH = $UnityHubPath
UNITY_BRIDGE_PORT = "$BridgePort"
}
}
$wslMcpEntry = @{
command = "wsl"
args = @("-d", $WSL_DISTRO, "--", "node", $wslScript)
env = @{
UNITY_BRIDGE_PORT = "$BridgePort"
}
}
function Merge-McpConfig {
param([string]$ConfigFile, [hashtable]$Entry, [string]$Label, [string]$ServerName = "unity-mcp")
$dir = Split-Path $ConfigFile
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
# PS5.1 兼容:手动将 PSCustomObject 转为普通 Hashtable
function ConvertTo-Hashtable($obj) {
if ($obj -is [System.Management.Automation.PSCustomObject]) {
$h = @{}
foreach ($p in $obj.PSObject.Properties) { $h[$p.Name] = ConvertTo-Hashtable $p.Value }
return $h
} elseif ($obj -is [System.Collections.IEnumerable] -and $obj -isnot [string]) {
return @($obj | ForEach-Object { ConvertTo-Hashtable $_ })
}
return $obj
}
$cfg = @{ mcpServers = @{} }
if (Test-Path $ConfigFile) {
try {
$raw = Get-Content $ConfigFile -Raw | ConvertFrom-Json
$converted = ConvertTo-Hashtable $raw
if ($converted -is [hashtable]) { $cfg = $converted }
} catch {}
}
if (-not $cfg.ContainsKey("mcpServers")) { $cfg["mcpServers"] = @{} }
$cfg["mcpServers"][$ServerName] = $Entry
$cfg | ConvertTo-Json -Depth 10 | Set-Content $ConfigFile -Encoding UTF8
Write-OK "$Label -> $ConfigFile"
}
# Claude DesktopWindows
Merge-McpConfig "$env:APPDATA\Claude\claude_desktop_config.json" $mcpEntry "Claude Desktop"
# Cursor
Merge-McpConfig "$env:USERPROFILE\.cursor\mcp.json" $mcpEntry "Cursor"
# Windsurf两种路径
$windsurfDir = if (Test-Path "$env:APPDATA\Windsurf") { "$env:APPDATA\Windsurf" } else { "$env:USERPROFILE\.codeium\windsurf" }
Merge-McpConfig "$windsurfDir\mcp_config.json" $mcpEntry "Windsurf"
# VS Code
Merge-McpConfig "$env:APPDATA\Code\User\mcp.json" $mcpEntry "VS Code"
# Claude Code CLIWSL2——用 `claude mcp add --scope user` 写入全局用户级 MCP 配置
$wslMcpCfgCmd = @"
export NVM_DIR="`$HOME/.nvm"
[ -s "`$NVM_DIR/nvm.sh" ] && . "`$NVM_DIR/nvm.sh"
#
claude mcp remove unity-mcp --scope user 2>/dev/null || true
claude mcp add --scope user unity-mcp node $wslScript -e UNITY_BRIDGE_PORT=$BridgePort
echo "Claude Code MCP configured"
true
"@
Invoke-WSL $wslMcpCfgCmd -IgnoreError | Out-Null
Write-OK "Claude Code CLI (WSL2) MCP -> unity-mcp (node $wslScript)"
# ══════════════════════════════════════════════════════════════# Step 5d: Godot MCP Pro 安装(本地 ZIP 包,无需联网)
# ════════════════════════════════════════════════════════════
Write-Step "5d Godot MCP Pro"
Write-Info "版本目录: $GodotMcpVersion"
Write-Info "源目录: $ScriptDir\$GodotMcpVersion"
$GodotMcpSrcDir = Join-Path $ScriptDir $GodotMcpVersion
$GodotMcpSrcSrv = Join-Path $GodotMcpSrcDir "server"
$GodotMcpWinDir = Join-Path $env:USERPROFILE "godot-mcp-pro"
# ── 5d-i. Windows 侧robocopy 同步 server/,排除 node_modules ─────────────
if (Test-Path $GodotMcpSrcSrv) {
Write-Info "同步 server/ -> $GodotMcpWinDir"
# robocopy 退出码 0-7 均为正常,需屏蔽错误级
$rcArgs = @($GodotMcpSrcSrv, $GodotMcpWinDir, "/E", "/XD", "node_modules",
"/NFL", "/NDL", "/NJH", "/NJS", "/nc", "/ns", "/np")
& robocopy @rcArgs 2>&1 | Out-Null
if ($winNodeExists) {
Push-Location $GodotMcpWinDir
try { & node build/setup.js install 2>&1 | Out-Null } finally { Pop-Location }
}
$godotWinEntry = @{
command = "node"
args = @(($GodotMcpWinDir -replace "\\", "/") + "/build/index.js")
}
Merge-McpConfig "$env:APPDATA\Claude\claude_desktop_config.json" $godotWinEntry "Claude Desktop (Godot MCP Pro)" "godot-mcp-pro"
Merge-McpConfig "$env:USERPROFILE\.cursor\mcp.json" $godotWinEntry "Cursor (Godot MCP Pro)" "godot-mcp-pro"
$windsurfDir2 = if (Test-Path "$env:APPDATA\Windsurf") { "$env:APPDATA\Windsurf" } else { "$env:USERPROFILE\.codeium\windsurf" }
Merge-McpConfig "$windsurfDir2\mcp_config.json" $godotWinEntry "Windsurf (Godot MCP Pro)" "godot-mcp-pro"
Merge-McpConfig "$env:APPDATA\Code\User\mcp.json" $godotWinEntry "VS Code (Godot MCP Pro)" "godot-mcp-pro"
Write-OK "Godot MCP Pro (Windows) 就绪: $GodotMcpWinDir"
} else {
$GodotMcpWinDir = ""
Write-Warn "未找到 $GodotMcpVersion\server\,跳过 Godot MCP Pro Windows 侧安装"
Write-Warn "请确认 $GodotMcpVersion/ 目录与 deploy.ps1 位于同一目录"
}
# ── 5d-ii. WSL2 侧:复制 server/ 到 ~/.mcp-servers/godot-mcp-pro/ ────────────
Write-Info "安装 Godot MCP Pro (WSL2)..."
$scriptDirFwd = $ScriptDir -replace "\\", "/"
$godotWslSrcBase = (Invoke-WSL "wslpath '$scriptDirFwd/$GodotMcpVersion/server'" -IgnoreError).Trim()
$godotWslDest = "$wslHome/.mcp-servers/godot-mcp-pro"
$godotWslScript = "$godotWslDest/build/index.js"
$installGodotWslCmd = @"
export NVM_DIR="`$HOME/.nvm"
[ -s "`$NVM_DIR/nvm.sh" ] && . "`$NVM_DIR/nvm.sh"
GODOT_SRC="$godotWslSrcBase"
GODOT_DST="$godotWslDest"
if [ ! -d "`$GODOT_SRC" ]; then echo "GODOT_SRC_MISSING: `$GODOT_SRC"; exit 0; fi
mkdir -p "`$GODOT_DST"
if command -v rsync >/dev/null 2>&1; then
rsync -a --delete --exclude='node_modules' "`$GODOT_SRC/" "`$GODOT_DST/"
else
find "`$GODOT_SRC" -mindepth 1 -maxdepth 1 ! -name 'node_modules' -exec cp -r {} "`$GODOT_DST/" \;
fi
cd "`$GODOT_DST"
node build/setup.js install
echo GODOT_WSL_OK
true
"@
$godotWslResult = Invoke-WSL $installGodotWslCmd -IgnoreError
if ($godotWslResult -match "GODOT_WSL_OK") {
Write-OK "Godot MCP Pro (WSL2) 就绪: $godotWslScript"
$godotWslMcpCmd = @"
export NVM_DIR="`$HOME/.nvm"
[ -s "`$NVM_DIR/nvm.sh" ] && . "`$NVM_DIR/nvm.sh"
claude mcp remove godot-mcp-pro --scope user 2>/dev/null || true
claude mcp add --scope user godot-mcp-pro node $godotWslScript
echo "Godot MCP Pro Claude Code configured"
true
"@
Invoke-WSL $godotWslMcpCmd -IgnoreError | Out-Null
Write-OK "Claude Code CLI (WSL2) MCP -> godot-mcp-pro (node $godotWslScript)"
} elseif ($godotWslResult -match "GODOT_SRC_MISSING") {
Write-Warn "WSL2 无法访问源目录,跳过 WSL2 侧 Godot MCP Pro 安装"
$godotWslScript = ""
} else {
Write-Warn "Godot MCP Pro WSL2 安装异常,跳过"
$godotWslScript = ""
}
# ════════════════════════════════════════════════════════════# Step 6: Windows 防火墙放行 MCP Bridge 端口
# ══════════════════════════════════════════════════════════════
Write-Step "6/8 Windows 防火墙规则"
if ($SkipFirewall) {
Write-Warn "已跳过防火墙配置(-SkipFirewall"
} else {
$portRange = "$BridgePort-$($BridgePort + 9)"
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if ($isAdmin) {
foreach ($dir in @("Inbound","Outbound")) {
$name = "Unity MCP Bridge $dir"
Get-NetFirewallRule -DisplayName $name -ErrorAction SilentlyContinue |
Remove-NetFirewallRule -ErrorAction SilentlyContinue
New-NetFirewallRule -DisplayName $name -Direction $dir `
-LocalPort $portRange -Protocol TCP -Action Allow -Profile Any | Out-Null
Write-OK "防火墙规则: $name (TCP $portRange)"
}
} else {
Write-Warn "非管理员权限,使用提升权限添加防火墙规则..."
$cmd = @"
`$p = '$portRange'
foreach (`$d in @('Inbound','Outbound')) {
`$n = "Unity MCP Bridge `$d"
Get-NetFirewallRule -DisplayName `$n -ErrorAction SilentlyContinue | Remove-NetFirewallRule -ErrorAction SilentlyContinue
New-NetFirewallRule -DisplayName `$n -Direction `$d -LocalPort `$p -Protocol TCP -Action Allow -Profile Any | Out-Null
Write-Host "OK: `$n"
}
Start-Sleep 1
"@
$enc = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cmd))
$proc = Start-Process powershell.exe -ArgumentList "-NoProfile -EncodedCommand $enc" `
-Verb RunAs -Wait -PassThru
if ($proc.ExitCode -eq 0) {
Write-OK "防火墙规则已添加 (TCP $portRange)"
} else {
Write-Warn "防火墙配置可能未成功,请手动放行 TCP $portRange"
}
}
}
# ══════════════════════════════════════════════════════════════
# Step 7: Rust 工具链 & Token Killer (WSL2)
# ══════════════════════════════════════════════════════════════
Write-Step "7/8 Rust & Token Killer"
$rustVer = Invoke-WSL ". ~/.cargo/env 2>/dev/null; rustc --version 2>/dev/null || echo MISSING" -IgnoreError
if ($rustVer -notmatch "MISSING") {
Write-OK "Rust 已安装: $($rustVer.Trim())"
} else {
Write-Info "安装 Rust 工具链..."
$installRustCmd = @"
sudo apt-get install -y -qq build-essential pkg-config libssl-dev 2>/dev/null || true
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o /tmp/rustup-init.sh
sh /tmp/rustup-init.sh -y --default-toolchain stable --no-modify-path
source ~/.cargo/env
rustc --version
"@
Invoke-WSL $installRustCmd
Invoke-WSL "grep -q 'cargo/env' ~/.bashrc || echo 'source ~/.cargo/env 2>/dev/null || true' >> ~/.bashrc" -IgnoreError | Out-Null
Invoke-WSL "grep -q 'cargo/env' ~/.profile || echo 'source ~/.cargo/env 2>/dev/null || true' >> ~/.profile" -IgnoreError | Out-Null
Write-OK "Rust 安装完成"
}
$rtkVer = Invoke-WSL ". ~/.cargo/env 2>/dev/null; rtk --version 2>/dev/null || echo MISSING" -IgnoreError
if ($rtkVer -notmatch "MISSING") {
Write-OK "rtk 已安装: $($rtkVer.Trim())"
} else {
Write-Info "安装 rtk (Rust Token Killer)..."
$installRtkCmd = @"
. ~/.cargo/env 2>/dev/null || true
CARGO_NET_GIT_FETCH_WITH_CLI=true cargo install --git https://github.com/rtk-ai/rtk 2>&1 | tail -5
"@
Invoke-WSL $installRtkCmd -IgnoreError
$rtkCheck = Invoke-WSL ". ~/.cargo/env 2>/dev/null; rtk --version 2>/dev/null || echo FAILED" -IgnoreError
if ($rtkCheck -match "FAILED") {
Write-Warn "rtk 安装失败,可手动运行: cargo install --git https://github.com/rtk-ai/rtk"
} else {
Write-OK "rtk 安装成功: $($rtkCheck.Trim())"
}
}
# rtk init -g安装 Claude Code PreToolUse hook幂等yes 管道自动确认所有提示)
Invoke-WSL ". ~/.cargo/env 2>/dev/null; yes | rtk init -g --auto-patch 2>/dev/null || true" -IgnoreError | Out-Null
# 将 settings.json 中的 hook command 改为绝对路径(避免 Claude Code 找不到 rtk
$fixRtkHookCmd = @'
. ~/.cargo/env 2>/dev/null || true
RTK_BIN=$(which rtk 2>/dev/null)
if [ -n "$RTK_BIN" ] && [ -f ~/.claude/settings.json ]; then
python3 -c "
import json, sys
p='/root/.claude/settings.json'
with open(p) as f: d=json.load(f)
hooks=d.get('hooks',{})
changed=False
for event in hooks:
for h in (hooks[event] if isinstance(hooks[event],list) else [hooks[event]]):
if isinstance(h,dict) and 'command' in h and 'rtk hook' in h['command']:
h['command']=h['command'].replace('rtk hook','$RTK_BIN hook')
changed=True
if changed:
with open(p,'w') as f: json.dump(d,f,indent=2)
print('hook path updated to $RTK_BIN')
" 2>/dev/null || true
fi
true
'@
Invoke-WSL $fixRtkHookCmd -IgnoreError | Out-Null
Write-OK "rtk hook 已注册(绝对路径),重启 Claude Code 后生效"
# ══════════════════════════════════════════════════════════════
# Step 8: PowerShell Profile 配置
# ══════════════════════════════════════════════════════════════
Write-Step "8/8 PowerShell Profile"
$pwshProfile = "$env:USERPROFILE\Documents\PowerShell\Microsoft.PowerShell_profile.ps1"
$profileDir = Split-Path $pwshProfile
if (-not (Test-Path $profileDir)) { New-Item -ItemType Directory -Path $profileDir | Out-Null }
$profileBlock = @"
# Claude Dev Stack (generated by deploy.ps1)
`$env:ANTHROPIC_BASE_URL = "$ANTHROPIC_BASE_URL"
$(if ($ANTHROPIC_API_KEY) { "`$env:ANTHROPIC_API_KEY = `"$ANTHROPIC_API_KEY`"" } else { "# ANTHROPIC_API_KEY= (配置 .env 后重新运行)" })
`$env:CLAUDE_MODEL = "$CLAUDE_MODEL"
function claude-wsl { wsl -d $WSL_DISTRO -- bash -ic 'claude' }
function gh-copilot-wsl { wsl -d $WSL_DISTRO -- gh copilot @args }
function unity-mcp-status { Invoke-RestMethod http://127.0.0.1:$BridgePort/api/ping -ErrorAction SilentlyContinue }
#
"@
$existing = if (Test-Path $pwshProfile) { Get-Content $pwshProfile -Raw } else { "" }
if ($existing -match "Claude Dev Stack") {
$existing = $existing -replace "(?ms)# ── Claude Dev Stack.*?# ─{60}", $profileBlock
Set-Content $pwshProfile $existing -Encoding UTF8
} else {
Add-Content $pwshProfile $profileBlock -Encoding UTF8
}
$policy = Get-ExecutionPolicy -Scope CurrentUser
if ($policy -in @("Restricted","Undefined")) {
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force
}
Write-OK "PowerShell Profile 已配置: $pwshProfile"
# ══════════════════════════════════════════════════════════════
# 安装总结
# ══════════════════════════════════════════════════════════════
Write-Host ""
Write-Host "╔══════════════════════════════════════════════════════════════╗" -ForegroundColor Green
Write-Host "║ ✅ 全栈部署完成 ║" -ForegroundColor Green
Write-Host "╚══════════════════════════════════════════════════════════════╝" -ForegroundColor Green
$items = @(
@("WSL2 内核", (Invoke-WSL "uname -r 2>/dev/null" -IgnoreError).Trim()),
@("Node.js (Win)", (& node --version 2>$null)),
@("Node.js (WSL)", $(if ($nodeVer -match "v\d+") { $nodeVer.Trim() } else { "" })),
@("Claude Code", $(if ($claudeVer -notmatch "MISSING") { $claudeVer.Trim() } else { "" })),
@("GitHub CLI", $(if ($ghVer -notmatch "MISSING") { $ghVer.Trim() } else { "" })),
@("Copilot CLI", $(if ($ghCopilotInstalled.Trim() -eq "0") { "gh extension github/gh-copilot" } else { "" })),
@("Rust", (Invoke-WSL ". ~/.cargo/env && rustc --version 2>/dev/null" -IgnoreError).Trim()),
@("rtk", (Invoke-WSL ". ~/.cargo/env && rtk --version 2>/dev/null" -IgnoreError).Trim()),
@("Unity MCP", $InstallDir),
@("Godot MCP Pro", $(if ($GodotMcpWinDir) { $GodotMcpWinDir } else { "(未安装)" })),
@("Bridge Port", "TCP $BridgePort (防火墙已放行 $BridgePort-$($BridgePort+9))")
)
foreach ($it in $items) {
Write-Host (" {0,-16} {1}" -f $it[0], $(if ($it[1]) { $it[1] } else { "(未安装)" })) -ForegroundColor White
}
Write-Host ""
Write-Host " ━━━━ 后续步骤 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan
Write-Host " 1) 在 Unity 项目中安装 MCP Plugin每个项目一次" -ForegroundColor White
Write-Host " Window > Package Manager > + > Add from git URL:" -ForegroundColor DarkGray
Write-Host " https://github.com/AnkleBreaker-Studio/unity-mcp-plugin.git" -ForegroundColor Yellow
Write-Host ""
Write-Host " 2) 打开 Unity 后确认 Bridge 在线(浏览器验证):" -ForegroundColor White
Write-Host " http://127.0.0.1:$BridgePort/api/ping" -ForegroundColor Yellow
Write-Host ""
Write-Host " 3) 重启 AI 客户端Claude Desktop / Cursor / Windsurf" -ForegroundColor White
Write-Host " MCP 配置已自动写入各客户端配置文件" -ForegroundColor DarkGray
Write-Host ""
Write-Host " 4) 在 Claude Code (WSL2) 中使用:" -ForegroundColor White
Write-Host " claude-wsl → claude → /mcp" -ForegroundColor DarkGray
Write-Host ""
Write-Host " 5) 在 WSL2 中使用 GitHub Copilot CLI" -ForegroundColor White
Write-Host " 先执行 gh auth login然后使用 gh-copilot-wsl suggest \"your prompt\"" -ForegroundColor DarkGray
Write-Host ""
Write-Host " 6) 在 Godot 项目中启用 MCP Pro 插件(每个项目一次):" -ForegroundColor White
Write-Host " 复制 addons/godot_mcp/ 到 Godot 项目根目录" -ForegroundColor DarkGray
Write-Host " 在 Project Settings → Plugins 中启用 Godot MCP Pro" -ForegroundColor DarkGray
Write-Host " 插件源目录: $GodotMcpSrcDir\addons\godot_mcp\" -ForegroundColor Yellow
Write-Host " 启用后 Godot 将在 6505-6509 端口监听 MCP 连接" -ForegroundColor DarkGray
Write-Host ""
if (-not $ANTHROPIC_API_KEY -and -not $ANTHROPIC_AUTH_TOKEN) {
Write-Warn " ⚠ 未设置 API Key请编辑 .env 后重新运行"
Write-Warn " 灵眸用户:填写 ANTHROPIC_AUTH_TOKEN=sk-xxx"
Write-Warn " 官方用户:填写 ANTHROPIC_API_KEY=sk-xxx"
}
Write-Host "╚══════════════════════════════════════════════════════════════╝" -ForegroundColor Green