#Requires -Version 5.1 <# .SYNOPSIS WSL2 + Claude Code CLI + Unity MCP + Godot MCP Pro + Rust Token Killer 全栈一键部署脚本 .DESCRIPTION 自动完成以下步骤: 1. 启用 WSL2 功能 & 安装 Ubuntu 发行版 2. 检测/安装 Windows 本机 Node.js(AI 客户端 MCP 需要) 3. WSL2 内安装 Node.js LTS(Claude Code CLI 需要) 4. 安装 Claude Code CLI (@anthropic-ai/claude-code) + GitHub Copilot CLI 5. 安装 Unity MCP Server + Godot MCP Pro(Windows + 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 proxy(200+ 工具通过此代理访问) "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 + allowedTools,API 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 Desktop(Windows) 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 CLI(WSL2)——用 `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