Compare commits
5 Commits
18e24d40f0
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| be82a4eb70 | |||
| b094cb85cd | |||
| dd3eb24d0f | |||
| e8693dad2a | |||
| 8ceccd75f1 |
27
.github/copilot-instructions.md
vendored
Normal file
27
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<!-- rtk-instructions v2 -->
|
||||
# RTK — Token-Optimized CLI
|
||||
|
||||
**rtk** is a CLI proxy that filters and compresses command outputs, saving 60-90% tokens.
|
||||
|
||||
## Rule
|
||||
|
||||
Always prefix shell commands with `rtk`:
|
||||
|
||||
```bash
|
||||
# Instead of: Use:
|
||||
git status rtk git status
|
||||
git log -10 rtk git log -10
|
||||
cargo test rtk cargo test
|
||||
docker ps rtk docker ps
|
||||
kubectl get pods rtk kubectl pods
|
||||
```
|
||||
|
||||
## Meta commands (use directly)
|
||||
|
||||
```bash
|
||||
rtk gain # Token savings dashboard
|
||||
rtk gain --history # Per-command savings history
|
||||
rtk discover # Find missed rtk opportunities
|
||||
rtk proxy <cmd> # Run raw (no filtering) but track usage
|
||||
```
|
||||
<!-- /rtk-instructions -->
|
||||
12
.github/hooks/rtk-rewrite.json
vendored
Normal file
12
.github/hooks/rtk-rewrite.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "rtk hook copilot",
|
||||
"cwd": ".",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
43
claude-dev-stack/.env
Normal file
43
claude-dev-stack/.env
Normal file
@@ -0,0 +1,43 @@
|
||||
# =============================================================
|
||||
# Claude Dev Stack 配置文件
|
||||
# 复制为 .env 并按需填写
|
||||
# =============================================================
|
||||
|
||||
# ── Claude / API 配置 ────────────────────────────────────────
|
||||
# 推荐:灵眸 AI(国内直连,无需代理)https://docs.lmuai.com/docs/tools/claude-code
|
||||
ANTHROPIC_AUTH_TOKEN=sk-ccaf8169bce7c735dfa1f021ec326b3904842dfd50e1ae817c3417017e0d8ee0
|
||||
ANTHROPIC_BASE_URL=https://api.lmuai.com
|
||||
|
||||
# 备选:Anthropic 官方 API Key(海外直连)
|
||||
# ANTHROPIC_API_KEY=
|
||||
# ANTHROPIC_BASE_URL=https://api.anthropic.com
|
||||
|
||||
# 备选:DeepSeek 兼容接口
|
||||
# ANTHROPIC_API_KEY=
|
||||
# ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
|
||||
|
||||
# 默认使用的模型
|
||||
# 灵眸可选:claude-opus-4-7 | claude-sonnet-4-6 | claude-sonnet-4-5 | claude-haiku-4-5
|
||||
# DeepSeek可选:deepseek-v4-pro
|
||||
CLAUDE_MODEL=claude-sonnet-4-6
|
||||
|
||||
# ── WSL2 ────────────────────────────────────────────────────
|
||||
# WSL2 发行版名称(wsl --list 查看已安装发行版)
|
||||
WSL_DISTRO=Ubuntu
|
||||
|
||||
# 设为 true 可跳过 WSL2 安装步骤(已安装时使用)
|
||||
SKIP_WSL_INSTALL=false
|
||||
|
||||
# ── Unity MCP ───────────────────────────────────────────────
|
||||
# unity-mcp-server 自动克隆至 WSL2 ~/.mcp-servers/unity-mcp-server/
|
||||
# Unity Plugin 需手动通过 Package Manager 安装:
|
||||
# https://github.com/AnkleBreaker-Studio/unity-mcp-plugin.git
|
||||
# (无需额外配置)
|
||||
|
||||
# ── Rust / Token Killer ──────────────────────────────────────
|
||||
# (暂无需配置,预留扩展用)
|
||||
# CARGO_REGISTRY_MIRROR=https://rsproxy.cn/
|
||||
|
||||
# ── Docker 镜像加速(可选)──────────────────────────────────
|
||||
# 若 WSL2 内需要 Docker,可配置国内加速镜像(逗号分隔)
|
||||
# DOCKER_REGISTRY_MIRRORS=https://docker.m.daocloud.io,https://hub-mirror.c.163.com
|
||||
42
windows-dev-stack/.env.example
Normal file
42
windows-dev-stack/.env.example
Normal file
@@ -0,0 +1,42 @@
|
||||
# =============================================================
|
||||
# Windows Dev Stack 配置文件
|
||||
# 复制为 .env 并按需填写
|
||||
# =============================================================
|
||||
|
||||
# ── Claude / API 配置 ────────────────────────────────────────
|
||||
# 推荐:灵眸 AI(国内直连,无需代理)https://docs.lmuai.com/docs/tools/claude-code
|
||||
ANTHROPIC_AUTH_TOKEN=sk-你的灵眸API密钥
|
||||
ANTHROPIC_BASE_URL=https://api.lmuai.com
|
||||
|
||||
# 备选:Anthropic 官方 API Key(海外直连)
|
||||
# ANTHROPIC_API_KEY=
|
||||
# ANTHROPIC_BASE_URL=https://api.anthropic.com
|
||||
|
||||
# 备选:DeepSeek 兼容接口
|
||||
# ANTHROPIC_API_KEY=
|
||||
# ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
|
||||
|
||||
# 默认使用的模型
|
||||
# 灵眸可选:claude-opus-4-7 | claude-sonnet-4-6 | claude-sonnet-4-5 | claude-haiku-4-5
|
||||
CLAUDE_MODEL=claude-sonnet-4-6
|
||||
|
||||
# ── Godot MCP Pro ─────────────────────────────────────────────
|
||||
# 本地包相对路径(相对于本脚本目录),默认引用 claude-dev-stack 中的包
|
||||
# 通常无需修改
|
||||
GODOT_MCP_VERSION=godot-mcp-pro-v1.14.1
|
||||
GODOT_MCP_PACKAGE_DIR=.
|
||||
|
||||
# ── Unity MCP ─────────────────────────────────────────────────
|
||||
# unity-mcp-server 安装目录(Windows 侧)
|
||||
# UNITY_MCP_INSTALL_DIR=%USERPROFILE%\unity-mcp-server
|
||||
|
||||
# Unity Hub 路径(写入 MCP 配置 env 供 unity-mcp 使用)
|
||||
# UNITY_HUB_PATH=C:\Program Files\Unity Hub\Unity Hub.exe
|
||||
|
||||
# Unity MCP Bridge 端口
|
||||
BRIDGE_PORT=7890
|
||||
|
||||
# ── Rust / Token Killer ──────────────────────────────────────
|
||||
# 国内加速镜像(安装 Rust 缓慢时启用)
|
||||
# RUSTUP_DIST_SERVER=https://rsproxy.cn
|
||||
# RUSTUP_UPDATE_ROOT=https://rsproxy.cn/rustup
|
||||
155
windows-dev-stack/README.md
Normal file
155
windows-dev-stack/README.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# Windows Dev Stack
|
||||
|
||||
**Windows 原生** Claude Code CLI + GitHub Copilot CLI + MCP + Rust Token Killer 一键部署方案。
|
||||
|
||||
不依赖 WSL2,所有组件直接运行在 Windows 本机。
|
||||
|
||||
## 组件清单
|
||||
|
||||
| 组件 | 说明 | 安装位置 |
|
||||
|------|------|---------|
|
||||
| **Node.js LTS** | JS 运行时 | winget → 系统 |
|
||||
| **Claude Code CLI** | `@anthropic-ai/claude-code` | npm global |
|
||||
| **GitHub CLI + Copilot CLI** | `gh` + `github/gh-copilot` 扩展 | winget + gh extension |
|
||||
| **Unity MCP Server** | AnkleBreaker-Studio — Claude ↔ Unity Editor | `%USERPROFILE%\unity-mcp-server\` |
|
||||
| **Godot MCP Pro** | 本地包 v1.14.1 — Claude ↔ Godot Editor | `%USERPROFILE%\godot-mcp-pro\` |
|
||||
| **Rust** | rustup stable 工具链 | winget → `%USERPROFILE%\.cargo\` |
|
||||
| **RTK** | Rust Token Killer — LLM token 优化 | cargo install → `%USERPROFILE%\.cargo\bin\` |
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
windows-dev-stack/
|
||||
├── deploy.ps1 # Windows 一键部署(PowerShell 5.1+)
|
||||
├── .env.example # 配置模板
|
||||
└── README.md
|
||||
```
|
||||
|
||||
> Godot MCP Pro 本地包位于 `../claude-dev-stack/godot-mcp-pro-v1.14.1/`,
|
||||
> 两个 stack 共用同一份本地包,无需重复存储。
|
||||
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
|
||||
```powershell
|
||||
# 1. 复制并填写配置
|
||||
cd path\to\windows-dev-stack
|
||||
cp .env.example .env
|
||||
notepad .env
|
||||
|
||||
# 2. 运行部署(普通用户即可;防火墙步骤会弹 UAC 提权)
|
||||
pwsh .\deploy.ps1
|
||||
|
||||
# 3. GitHub CLI 登录(首次使用前)
|
||||
gh auth login
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 配置说明(`.env`)
|
||||
|
||||
### 推荐:灵眸 AI(国内直连,无需代理)
|
||||
|
||||
| 变量 | 说明 |
|
||||
|------|------|
|
||||
| `ANTHROPIC_AUTH_TOKEN` | 灵眸 API Key,从 [lmuai.com](https://lmuai.com) 获取 |
|
||||
| `ANTHROPIC_BASE_URL` | `https://api.lmuai.com`(默认) |
|
||||
| `CLAUDE_MODEL` | `claude-sonnet-4-6`(默认) |
|
||||
|
||||
### 备选:Anthropic 官方 API
|
||||
|
||||
| 变量 | 说明 |
|
||||
|------|------|
|
||||
| `ANTHROPIC_API_KEY` | 从 [console.anthropic.com](https://console.anthropic.com) 获取 |
|
||||
| `ANTHROPIC_BASE_URL` | `https://api.anthropic.com` |
|
||||
|
||||
---
|
||||
|
||||
## 与 claude-dev-stack 的对比
|
||||
|
||||
| 特性 | `claude-dev-stack` | `windows-dev-stack` |
|
||||
|------|--------------------|---------------------|
|
||||
| 运行环境 | WSL2(Linux) | Windows 原生 |
|
||||
| Claude Code CLI | WSL2 内 npm global | Windows npm global |
|
||||
| GitHub Copilot CLI | WSL2 gh extension | Windows gh extension |
|
||||
| Rust / RTK | WSL2 cargo | Windows cargo |
|
||||
| MCP Servers | WSL2 node 进程 | Windows node 进程 |
|
||||
| 适合场景 | Linux 工具链 / 命令行重度用户 | 纯 Windows 开发者 / 不想装 WSL2 |
|
||||
|
||||
两套 stack **可以共存**,各自写入独立配置,互不干扰。
|
||||
|
||||
---
|
||||
|
||||
## MCP 配置覆盖范围
|
||||
|
||||
`deploy.ps1` 自动写入以下客户端的 MCP 配置:
|
||||
|
||||
| 客户端 | 配置文件 / 方式 |
|
||||
|--------|----------------|
|
||||
| Claude Desktop | `%APPDATA%\Claude\claude_desktop_config.json`(`mcpServers` 格式) |
|
||||
| Cursor | `%USERPROFILE%\.cursor\mcp.json`(`mcpServers` 格式) |
|
||||
| Windsurf | `%APPDATA%\Windsurf\mcp_config.json`(`mcpServers` 格式) |
|
||||
| **VS Code Copilot Agent** | `%APPDATA%\Code\User\settings.json` → `mcp.servers`(VS Code 专用格式) |
|
||||
| Claude Code CLI | `claude mcp add --scope user`(写入用户级 MCP) |
|
||||
|
||||
---
|
||||
|
||||
## RTK 使用
|
||||
|
||||
RTK 通过 Claude Code 的 PreToolUse hook 自动生效(`deploy.ps1` 运行 `rtk init -g` 注册)。
|
||||
|
||||
```powershell
|
||||
# 查看 token 节省统计
|
||||
rtk gain
|
||||
rtk gain --history
|
||||
|
||||
# 手动使用
|
||||
rtk git status
|
||||
rtk cargo test
|
||||
```
|
||||
|
||||
`.github/copilot-instructions.md` 已在仓库根目录配置,GitHub Copilot (VS Code) 会自动读取并在终端命令前加 `rtk` 前缀。
|
||||
|
||||
---
|
||||
|
||||
## 故障排查
|
||||
|
||||
### Node.js 安装后找不到命令
|
||||
```powershell
|
||||
# 刷新当前会话 PATH
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" +
|
||||
[System.Environment]::GetEnvironmentVariable("Path","User")
|
||||
# 或直接重开 PowerShell
|
||||
```
|
||||
|
||||
### Rust / cargo 安装后找不到命令
|
||||
```powershell
|
||||
# 手动加入 PATH(当前会话)
|
||||
$env:Path = "$env:USERPROFILE\.cargo\bin;$env:Path"
|
||||
# 或重开终端(rustup installer 已写入用户 PATH)
|
||||
```
|
||||
|
||||
### rtk 安装失败(需要 Build Tools)
|
||||
```powershell
|
||||
# 安装 Visual C++ Build Tools(Rust Windows 编译器后端依赖)
|
||||
winget install Microsoft.VisualStudio.2022.BuildTools
|
||||
# 安装后重开终端,再运行:
|
||||
cargo install --git https://github.com/rtk-ai/rtk
|
||||
```
|
||||
|
||||
### gh copilot 找不到扩展
|
||||
```powershell
|
||||
# 确认已登录
|
||||
gh auth status
|
||||
# 重新安装扩展
|
||||
gh extension install github/gh-copilot
|
||||
```
|
||||
|
||||
### Unity MCP Bridge 连接失败
|
||||
```powershell
|
||||
# 检查 Bridge 端口
|
||||
Invoke-WebRequest http://127.0.0.1:7890/api/ping
|
||||
# 确认防火墙已放行
|
||||
Get-NetFirewallRule -DisplayName "Unity MCP Bridge*" | Select-Object DisplayName, Enabled
|
||||
```
|
||||
797
windows-dev-stack/deploy.ps1
Normal file
797
windows-dev-stack/deploy.ps1
Normal file
@@ -0,0 +1,797 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Windows 原生 Claude Code CLI + GitHub Copilot CLI + MCP + Rust Token Killer 一键部署脚本
|
||||
.DESCRIPTION
|
||||
不依赖 WSL2,全部组件原生运行在 Windows 上。完成以下步骤:
|
||||
1. Node.js LTS 检测 / 安装(winget)
|
||||
2. Claude Code CLI (@anthropic-ai/claude-code) 安装
|
||||
3. GitHub CLI + Copilot CLI 扩展安装(winget + gh extension)
|
||||
4. Unity MCP Server 安装 & 写入各 AI 客户端 MCP 配置
|
||||
5. Godot MCP Pro 安装(本地包)& 写入各 AI 客户端 MCP 配置
|
||||
6. Rust 工具链 + RTK (Rust Token Killer) 安装
|
||||
7. RTK hook 注册到 Claude Code + Copilot Instructions 生成
|
||||
8. PowerShell Profile 快捷命令写入
|
||||
.PARAMETER BridgePort
|
||||
Unity MCP Bridge 端口,默认 7890
|
||||
.PARAMETER SkipFirewall
|
||||
跳过防火墙规则配置
|
||||
.NOTES
|
||||
防火墙规则需要管理员权限,其余步骤普通用户即可运行。
|
||||
脚本可重复运行(幂等),已安装的组件自动跳过。
|
||||
#>
|
||||
param(
|
||||
[int] $BridgePort = 7890,
|
||||
[string]$GodotMcpVersion = "godot-mcp-pro-v1.14.1",
|
||||
[switch]$SkipFirewall
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
$Cfg = @{}
|
||||
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("'")
|
||||
$Cfg[$key] = $val
|
||||
}
|
||||
Write-Info "已加载配置:$EnvFile"
|
||||
}
|
||||
|
||||
$ANTHROPIC_API_KEY = $Cfg["ANTHROPIC_API_KEY"] ?? ""
|
||||
$ANTHROPIC_AUTH_TOKEN = $Cfg["ANTHROPIC_AUTH_TOKEN"] ?? ""
|
||||
$ANTHROPIC_BASE_URL = $Cfg["ANTHROPIC_BASE_URL"] ?? "https://api.lmuai.com"
|
||||
$CLAUDE_MODEL = $Cfg["CLAUDE_MODEL"] ?? "claude-sonnet-4-6"
|
||||
$BRIDGE_PORT_CFG = $Cfg["BRIDGE_PORT"] ?? "$BridgePort"
|
||||
$UnityHubPath = $Cfg["UNITY_HUB_PATH"] ?? "C:\Program Files\Unity Hub\Unity Hub.exe"
|
||||
$UnityMcpInstallDir = $Cfg["UNITY_MCP_INSTALL_DIR"] ?? "$env:USERPROFILE\unity-mcp-server"
|
||||
$GodotMcpPackageDir = $Cfg["GODOT_MCP_PACKAGE_DIR"] ?? "."
|
||||
|
||||
# 参数 BridgePort 优先于 .env
|
||||
if ($BridgePort -eq 7890 -and $BRIDGE_PORT_CFG -ne "7890") { $BridgePort = [int]$BRIDGE_PORT_CFG }
|
||||
|
||||
# 判断认证方式
|
||||
$UseLmuAuth = ($ANTHROPIC_AUTH_TOKEN -ne "" -and $ANTHROPIC_BASE_URL -ne "https://api.anthropic.com")
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Banner
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
Write-Host ""
|
||||
Write-Host "╔══════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
|
||||
Write-Host "║ Windows Native: Claude Code + Copilot CLI + MCP + RTK ║" -ForegroundColor Cyan
|
||||
Write-Host "║ Unity MCP · Godot MCP Pro · Rust Token Killer ║" -ForegroundColor Cyan
|
||||
Write-Host "╚══════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# 辅助:刷新当前进程 PATH
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
function Refresh-Path {
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" +
|
||||
[System.Environment]::GetEnvironmentVariable("Path", "User")
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# 辅助:winget 安装(静默)
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
function Install-Winget {
|
||||
param([string]$Id, [string]$DisplayName)
|
||||
# pwsh (Core) 里 winget 不在 PATH,尝试固定路径
|
||||
$wgPath = (Get-Command winget -ErrorAction SilentlyContinue)?.Source
|
||||
if (-not $wgPath) {
|
||||
$wgPath = "$env:LOCALAPPDATA\Microsoft\WindowsApps\winget.exe"
|
||||
if (-not (Test-Path $wgPath)) { $wgPath = $null }
|
||||
}
|
||||
if (-not $wgPath) {
|
||||
Write-Warn "未找到 winget,请手动安装 $DisplayName"
|
||||
return $false
|
||||
}
|
||||
Write-Info "winget install $Id ..."
|
||||
& $wgPath install --id $Id --silent --accept-source-agreements --accept-package-agreements 2>&1 | Out-Null
|
||||
Refresh-Path
|
||||
return $true
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# 辅助:写入 / 合并 MCP 配置文件(JSON)
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
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 }
|
||||
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"
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# 辅助:写入 VS Code settings.json 中的 mcp.servers(VS Code 专用格式)
|
||||
# VS Code Copilot Agent 读取 settings.json -> "mcp" -> "servers"
|
||||
# 而非 mcp.json 中的 "mcpServers"(那是 Claude Desktop / Cursor 格式)
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
function Merge-VsCodeMcpSettings {
|
||||
param(
|
||||
[string] $ServerName,
|
||||
[hashtable] $Entry,
|
||||
[string] $Label
|
||||
)
|
||||
$settingsFile = "$env:APPDATA\Code\User\settings.json"
|
||||
$dir = Split-Path $settingsFile
|
||||
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
|
||||
|
||||
function ConvertTo-OrderedHashtable($obj) {
|
||||
if ($obj -is [System.Management.Automation.PSCustomObject]) {
|
||||
$h = [ordered]@{}
|
||||
foreach ($p in $obj.PSObject.Properties) { $h[$p.Name] = ConvertTo-OrderedHashtable $p.Value }
|
||||
return $h
|
||||
} elseif ($obj -is [System.Collections.IEnumerable] -and $obj -isnot [string]) {
|
||||
return @($obj | ForEach-Object { ConvertTo-OrderedHashtable $_ })
|
||||
}
|
||||
return $obj
|
||||
}
|
||||
|
||||
$cfg = [ordered]@{}
|
||||
if (Test-Path $settingsFile) {
|
||||
try {
|
||||
$raw = Get-Content $settingsFile -Raw | ConvertFrom-Json
|
||||
$cfg = ConvertTo-OrderedHashtable $raw
|
||||
} catch {
|
||||
Write-Warn "settings.json 解析失败,将新建 mcp 节点:$_"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $cfg.ContainsKey("mcp")) { $cfg["mcp"] = [ordered]@{} }
|
||||
if (-not $cfg["mcp"].ContainsKey("servers")) { $cfg["mcp"]["servers"] = [ordered]@{} }
|
||||
$cfg["mcp"]["servers"][$ServerName] = $Entry
|
||||
|
||||
$cfg | ConvertTo-Json -Depth 15 | Set-Content $settingsFile -Encoding UTF8
|
||||
Write-OK "$Label -> $settingsFile [mcp.servers.$ServerName]"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# Step 1: Node.js LTS
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
Write-Step "1/8 Node.js LTS"
|
||||
|
||||
$nodeCmd = Get-Command node -ErrorAction SilentlyContinue
|
||||
if ($nodeCmd) {
|
||||
Write-OK "Node.js 已安装: $(& node --version)"
|
||||
} else {
|
||||
Write-Info "通过 winget 安装 Node.js LTS..."
|
||||
$ok = Install-Winget "OpenJS.NodeJS.LTS" "Node.js LTS"
|
||||
$nodeCmd = Get-Command node -ErrorAction SilentlyContinue
|
||||
if ($nodeCmd) {
|
||||
Write-OK "Node.js 安装完成: $(& node --version)"
|
||||
} else {
|
||||
Write-Warn "未能自动安装 Node.js,请手动安装后重新运行:"
|
||||
Write-Warn " https://nodejs.org/zh-cn/download 或 winget install OpenJS.NodeJS.LTS"
|
||||
}
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# Step 2: Claude Code CLI
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
Write-Step "2/8 Claude Code CLI"
|
||||
|
||||
$claudeCmd = Get-Command claude -ErrorAction SilentlyContinue
|
||||
if ($claudeCmd) {
|
||||
Write-OK "Claude Code 已安装: $(& claude --version 2>&1 | Select-Object -First 1)"
|
||||
} else {
|
||||
if (Get-Command node -ErrorAction SilentlyContinue) {
|
||||
Write-Info "安装 @anthropic-ai/claude-code ..."
|
||||
& npm install -g @anthropic-ai/claude-code --quiet 2>&1 | Out-Null
|
||||
Refresh-Path
|
||||
$claudeCmd = Get-Command claude -ErrorAction SilentlyContinue
|
||||
if ($claudeCmd) {
|
||||
Write-OK "Claude Code 安装完成: $(& claude --version 2>&1 | Select-Object -First 1)"
|
||||
} else {
|
||||
Write-Warn "安装后未找到 claude 命令,请重开终端后重试"
|
||||
}
|
||||
} else {
|
||||
Write-Warn "Node.js 未就绪,跳过 Claude Code 安装"
|
||||
}
|
||||
}
|
||||
|
||||
# ── 写入 Claude Code 配置(settings.json)──────────────────
|
||||
$mcpAllowedTools = @(
|
||||
"Bash","Read","Write","Edit","MultiEdit","Glob","Grep","LS","WebFetch","TodoRead","TodoWrite",
|
||||
# Unity MCP
|
||||
"mcp__unity-mcp__unity_editor_ping","mcp__unity-mcp__unity_editor_state","mcp__unity-mcp__unity_project_info",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"mcp__unity-mcp__unity_script_create","mcp__unity-mcp__unity_script_read",
|
||||
"mcp__unity-mcp__unity_script_update","mcp__unity-mcp__unity_execute_code",
|
||||
"mcp__unity-mcp__unity_material_create","mcp__unity-mcp__unity_renderer_set_material",
|
||||
"mcp__unity-mcp__unity_build","mcp__unity-mcp__unity_play_mode",
|
||||
"mcp__unity-mcp__unity_console_log","mcp__unity-mcp__unity_console_clear",
|
||||
"mcp__unity-mcp__unity_get_compilation_errors",
|
||||
"mcp__unity-mcp__unity_execute_menu_item","mcp__unity-mcp__unity_undo",
|
||||
"mcp__unity-mcp__unity_redo","mcp__unity-mcp__unity_undo_history",
|
||||
"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",
|
||||
"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",
|
||||
"mcp__unity-mcp__unity_prefab_info","mcp__unity-mcp__unity_set_object_reference",
|
||||
"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",
|
||||
"mcp__unity-mcp__unity_queue_info","mcp__unity-mcp__unity_agents_list","mcp__unity-mcp__unity_agent_log",
|
||||
"mcp__unity-mcp__unity_list_advanced_tools","mcp__unity-mcp__unity_advanced_tool",
|
||||
"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",
|
||||
"mcp__unity-mcp__unity_list_instances","mcp__unity-mcp__unity_select_instance",
|
||||
"mcp__unity-mcp__unity_get_project_context",
|
||||
# Godot MCP Pro
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"mcp__godot-mcp-pro__get_input_actions","mcp__godot-mcp-pro__set_input_action",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"mcp__godot-mcp-pro__get_autoload","mcp__godot-mcp-pro__add_autoload",
|
||||
"mcp__godot-mcp-pro__set_project_setting",
|
||||
"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",
|
||||
"mcp__godot-mcp-pro__get_export_info","mcp__godot-mcp-pro__list_export_presets",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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) {
|
||||
[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 {
|
||||
[ordered]@{ model = $CLAUDE_MODEL; allowedTools = $mcpAllowedTools } | ConvertTo-Json -Depth 3
|
||||
}
|
||||
|
||||
$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) {
|
||||
[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 配置已写入 $claudeDir\settings.json"
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# Step 3: GitHub CLI + Copilot CLI 扩展
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
Write-Step "3/8 GitHub CLI + Copilot CLI"
|
||||
|
||||
$ghCmd = Get-Command gh -ErrorAction SilentlyContinue
|
||||
if ($ghCmd) {
|
||||
Write-OK "GitHub CLI 已安装: $(& gh --version 2>&1 | Select-Object -First 1)"
|
||||
} else {
|
||||
Write-Info "通过 winget 安装 GitHub CLI..."
|
||||
$ok = Install-Winget "GitHub.cli" "GitHub CLI"
|
||||
$ghCmd = Get-Command gh -ErrorAction SilentlyContinue
|
||||
if ($ghCmd) {
|
||||
Write-OK "GitHub CLI 安装完成: $(& gh --version 2>&1 | Select-Object -First 1)"
|
||||
} else {
|
||||
Write-Warn "GitHub CLI 安装后未找到,请重开终端或手动安装:winget install GitHub.cli"
|
||||
}
|
||||
}
|
||||
|
||||
if ($ghCmd) {
|
||||
$extList = & gh extension list 2>&1 | Out-String
|
||||
if ($extList -match "github/gh-copilot") {
|
||||
Write-OK "GitHub Copilot CLI 扩展已安装"
|
||||
} else {
|
||||
Write-Info "安装 GitHub Copilot CLI 扩展..."
|
||||
try {
|
||||
& gh extension install github/gh-copilot 2>&1 | Out-Null
|
||||
Write-OK "GitHub Copilot CLI 扩展安装完成(入口:gh copilot)"
|
||||
} catch {
|
||||
Write-Warn "安装失败(可能需要先 gh auth login),请手动运行:gh extension install github/gh-copilot"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# Step 4: Unity MCP Server
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
Write-Step "4/8 Unity MCP Server"
|
||||
Write-Info "安装目录: $UnityMcpInstallDir"
|
||||
|
||||
$gitCmd = Get-Command git -ErrorAction SilentlyContinue
|
||||
if (-not $gitCmd) {
|
||||
Write-Warn "未找到 git,跳过 Unity MCP Server 安装"
|
||||
Write-Warn "请安装 Git for Windows:winget install Git.Git"
|
||||
$UnityMcpReady = $false
|
||||
} else {
|
||||
if (Test-Path "$UnityMcpInstallDir\.git") {
|
||||
Write-Info "已存在,更新至最新..."
|
||||
Push-Location $UnityMcpInstallDir
|
||||
try { & git pull --quiet 2>&1 | Out-Null } finally { Pop-Location }
|
||||
} else {
|
||||
if (Test-Path $UnityMcpInstallDir) { Remove-Item $UnityMcpInstallDir -Recurse -Force }
|
||||
& git clone --quiet --depth 1 https://github.com/AnkleBreaker-Studio/unity-mcp-server.git $UnityMcpInstallDir
|
||||
}
|
||||
|
||||
if (Get-Command node -ErrorAction SilentlyContinue) {
|
||||
Push-Location $UnityMcpInstallDir
|
||||
try { & npm install --prefer-offline --quiet 2>&1 | Out-Null } finally { Pop-Location }
|
||||
Write-OK "Unity MCP Server 就绪"
|
||||
$UnityMcpReady = $true
|
||||
} else {
|
||||
Write-Warn "Node.js 未就绪,跳过 npm install"
|
||||
$UnityMcpReady = $false
|
||||
}
|
||||
}
|
||||
|
||||
# ── 确定入口文件 ──────────────────────────────────────────────
|
||||
$unityEntry = ""
|
||||
if ($UnityMcpReady) {
|
||||
foreach ($f in @("src/index.js","build/index.js","index.js")) {
|
||||
$fp = Join-Path $UnityMcpInstallDir $f
|
||||
if (Test-Path $fp) { $unityEntry = $fp -replace "\\","/"; break }
|
||||
}
|
||||
}
|
||||
|
||||
# ── 写入 MCP 配置(四大 AI 客户端 + Claude Code CLI)─────────
|
||||
if ($unityEntry) {
|
||||
$unityMcpEntry = @{
|
||||
command = "node"
|
||||
args = @($unityEntry)
|
||||
env = @{ UNITY_HUB_PATH = $UnityHubPath; UNITY_BRIDGE_PORT = "$BridgePort" }
|
||||
}
|
||||
Merge-McpConfig "$env:APPDATA\Claude\claude_desktop_config.json" $unityMcpEntry "Claude Desktop"
|
||||
Merge-McpConfig "$env:USERPROFILE\.cursor\mcp.json" $unityMcpEntry "Cursor"
|
||||
$wsDirC = if (Test-Path "$env:APPDATA\Windsurf") { "$env:APPDATA\Windsurf" } else { "$env:USERPROFILE\.codeium\windsurf" }
|
||||
Merge-McpConfig "$wsDirC\mcp_config.json" $unityMcpEntry "Windsurf"
|
||||
Merge-VsCodeMcpSettings "unity-mcp" $unityMcpEntry "VS Code Copilot Agent (Unity MCP)"
|
||||
# Claude Code CLI(Windows)
|
||||
if (Get-Command claude -ErrorAction SilentlyContinue) {
|
||||
& claude mcp remove unity-mcp --scope user 2>&1 | Out-Null
|
||||
& claude mcp add --scope user unity-mcp node $unityEntry -e "UNITY_BRIDGE_PORT=$BridgePort" 2>&1 | Out-Null
|
||||
Write-OK "Claude Code CLI MCP -> unity-mcp"
|
||||
}
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# Step 5: Godot MCP Pro
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
Write-Step "5/8 Godot MCP Pro"
|
||||
|
||||
# 解析 Godot MCP Pro 本地包路径(相对于本脚本目录)
|
||||
$godotPkgBase = if ([System.IO.Path]::IsPathRooted($GodotMcpPackageDir)) {
|
||||
$GodotMcpPackageDir
|
||||
} else {
|
||||
[System.IO.Path]::GetFullPath((Join-Path $ScriptDir $GodotMcpPackageDir))
|
||||
}
|
||||
$godotSrcDir = Join-Path $godotPkgBase "$GodotMcpVersion\server"
|
||||
$godotWinDir = "$env:USERPROFILE\godot-mcp-pro"
|
||||
|
||||
if (-not (Test-Path $godotSrcDir)) {
|
||||
Write-Warn "未找到 Godot MCP Pro 源目录: $godotSrcDir"
|
||||
Write-Warn "请确认 .env 中 GODOT_MCP_PACKAGE_DIR 指向包含 $GodotMcpVersion\ 的目录"
|
||||
$godotEntry = ""
|
||||
} else {
|
||||
Write-Info "同步 server/ -> $godotWinDir"
|
||||
$rcArgs = @($godotSrcDir, $godotWinDir, "/E", "/XD", "node_modules",
|
||||
"/NFL", "/NDL", "/NJH", "/NJS", "/nc", "/ns", "/np")
|
||||
& robocopy @rcArgs 2>&1 | Out-Null
|
||||
|
||||
if (Get-Command node -ErrorAction SilentlyContinue) {
|
||||
Push-Location $godotWinDir
|
||||
try { & node build/setup.js install 2>&1 | Out-Null } finally { Pop-Location }
|
||||
Write-OK "Godot MCP Pro 就绪: $godotWinDir"
|
||||
} else {
|
||||
Write-Warn "Node.js 未就绪,跳过 Godot MCP Pro setup"
|
||||
}
|
||||
|
||||
$godotEntry = ($godotWinDir -replace "\\","/") + "/build/index.js"
|
||||
$godotMcpEntry = @{ command = "node"; args = @($godotEntry) }
|
||||
|
||||
Merge-McpConfig "$env:APPDATA\Claude\claude_desktop_config.json" $godotMcpEntry "Claude Desktop (Godot)" "godot-mcp-pro"
|
||||
Merge-McpConfig "$env:USERPROFILE\.cursor\mcp.json" $godotMcpEntry "Cursor (Godot)" "godot-mcp-pro"
|
||||
$wsDirG = if (Test-Path "$env:APPDATA\Windsurf") { "$env:APPDATA\Windsurf" } else { "$env:USERPROFILE\.codeium\windsurf" }
|
||||
Merge-McpConfig "$wsDirG\mcp_config.json" $godotMcpEntry "Windsurf (Godot)" "godot-mcp-pro"
|
||||
Merge-VsCodeMcpSettings "godot-mcp-pro" $godotMcpEntry "VS Code Copilot Agent (Godot MCP Pro)"
|
||||
if (Get-Command claude -ErrorAction SilentlyContinue) {
|
||||
& claude mcp remove godot-mcp-pro --scope user 2>&1 | Out-Null
|
||||
& claude mcp add --scope user godot-mcp-pro node $godotEntry 2>&1 | Out-Null
|
||||
Write-OK "Claude Code CLI MCP -> godot-mcp-pro"
|
||||
}
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# Step 6: 防火墙放行 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 "非管理员权限,尝试以提升权限添加防火墙规则..."
|
||||
$fwCmd = @"
|
||||
`$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($fwCmd))
|
||||
$proc = Start-Process powershell.exe -ArgumentList "-NoProfile -EncodedCommand $enc" `
|
||||
-Verb RunAs -Wait -PassThru -ErrorAction SilentlyContinue
|
||||
if ($proc -and $proc.ExitCode -eq 0) {
|
||||
Write-OK "防火墙规则已添加 (TCP $portRange)"
|
||||
} else {
|
||||
Write-Warn "防火墙配置未成功,请手动以管理员运行:New-NetFirewallRule ... -LocalPort $portRange"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# Step 7: Rust 工具链 & RTK
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
Write-Step "7/8 Rust 工具链 & Token Killer"
|
||||
|
||||
# 若 .env 配置了国内镜像,提前注入环境变量
|
||||
$rustupMirror = $Cfg["RUSTUP_DIST_SERVER"] ?? ""
|
||||
$rustupRoot = $Cfg["RUSTUP_UPDATE_ROOT"] ?? ""
|
||||
if ($rustupMirror) { $env:RUSTUP_DIST_SERVER = $rustupMirror }
|
||||
if ($rustupRoot) { $env:RUSTUP_UPDATE_ROOT = $rustupRoot }
|
||||
|
||||
# 先把 cargo\bin 加入 PATH(rustup 通过 winget 安装后 PATH 需刷新)
|
||||
$cargoBin = "$env:USERPROFILE\.cargo\bin"
|
||||
if (Test-Path $cargoBin) { $env:Path = "$cargoBin;$env:Path" }
|
||||
Refresh-Path
|
||||
|
||||
$cargoCmd = Get-Command cargo -ErrorAction SilentlyContinue
|
||||
|
||||
if ($cargoCmd) {
|
||||
# 检查是否有默认 toolchain,若没有则安装 stable
|
||||
$rustcVer = & rustc --version 2>&1 | Out-String
|
||||
if ($rustcVer -match "no default is configured" -or $rustcVer -match "error:") {
|
||||
Write-Info "rustup 无默认 toolchain,安装 stable..."
|
||||
& rustup toolchain install stable 2>&1 | Select-Object -Last 3
|
||||
& rustup default stable 2>&1 | Out-Null
|
||||
Refresh-Path
|
||||
if (Test-Path $cargoBin) { $env:Path = "$cargoBin;$env:Path" }
|
||||
$rustcVer = & rustc --version 2>&1 | Out-String
|
||||
}
|
||||
Write-OK "Rust 已安装: $($rustcVer.Trim())"
|
||||
} else {
|
||||
# 检查 winget 是否已记录安装(避免重复安装时阻塞)
|
||||
$wgPath = (Get-Command winget -ErrorAction SilentlyContinue)?.Source
|
||||
if (-not $wgPath) {
|
||||
$fallback = "$env:LOCALAPPDATA\Microsoft\WindowsApps\winget.exe"
|
||||
if (Test-Path $fallback) { $wgPath = $fallback }
|
||||
}
|
||||
$wgList = if ($wgPath) { & $wgPath list --id Rustlang.Rustup --accept-source-agreements 2>&1 | Out-String } else { "" }
|
||||
if ($wgList -match "Rustlang.Rustup") {
|
||||
Write-Info "rustup 已安装,但当前会话 PATH 未更新。已将 $cargoBin 加入 PATH"
|
||||
# rustup 首次运行会初始化 stable 工具链
|
||||
if (Test-Path "$cargoBin\rustup.exe") { & "$cargoBin\rustup.exe" show 2>&1 | Out-Null }
|
||||
$cargoCmd = Get-Command cargo -ErrorAction SilentlyContinue
|
||||
if ($cargoCmd) { Write-OK "Rust: $(& rustc --version 2>&1)" }
|
||||
} else {
|
||||
Write-Info "通过 winget 安装 Rust (rustup)..."
|
||||
$ok = Install-Winget "Rustlang.Rustup" "Rustup"
|
||||
if ($ok) {
|
||||
if (Test-Path $cargoBin) { $env:Path = "$cargoBin;$env:Path" }
|
||||
Refresh-Path
|
||||
$cargoCmd = Get-Command cargo -ErrorAction SilentlyContinue
|
||||
if ($cargoCmd) {
|
||||
Write-OK "Rust 安装完成: $(& rustc --version 2>&1)"
|
||||
} else {
|
||||
Write-Warn "Rust 安装后未找到 cargo,请关闭并重开终端后继续"
|
||||
Write-Warn " 或手动安装:https://rustup.rs"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 先把 ~/.local/bin 加入 PATH(预编译包安装目录)
|
||||
$localBin = "$env:USERPROFILE\.local\bin"
|
||||
if (Test-Path $localBin) { $env:Path = "$localBin;$env:Path" }
|
||||
|
||||
$rtkCmd = Get-Command rtk -ErrorAction SilentlyContinue
|
||||
if ($rtkCmd) {
|
||||
Write-OK "rtk 已安装: $(& rtk --version 2>&1)"
|
||||
} else {
|
||||
# 从 GitHub Releases 下载预编译包(比 cargo install 快得多,且无需编译环境)
|
||||
Write-Info "安装 rtk (Rust Token Killer) - 下载预编译包..."
|
||||
try {
|
||||
$rtkVer = "v0.42.0"
|
||||
$rtkUrl = "https://github.com/rtk-ai/rtk/releases/download/$rtkVer/rtk-x86_64-pc-windows-msvc.zip"
|
||||
New-Item -ItemType Directory -Force -Path $localBin | Out-Null
|
||||
$rtkZip = "$env:TEMP\rtk-windows.zip"
|
||||
Invoke-WebRequest -Uri $rtkUrl -OutFile $rtkZip -UseBasicParsing
|
||||
Expand-Archive -Path $rtkZip -DestinationPath $localBin -Force
|
||||
Remove-Item $rtkZip -ErrorAction SilentlyContinue
|
||||
$env:Path = "$localBin;$env:Path"
|
||||
$rtkCmd = Get-Command rtk -ErrorAction SilentlyContinue
|
||||
if ($rtkCmd) {
|
||||
Write-OK "rtk 安装完成: $(& rtk --version 2>&1)"
|
||||
} else {
|
||||
Write-Warn "rtk 安装失败,可手动运行:cargo install --git https://github.com/rtk-ai/rtk"
|
||||
}
|
||||
} catch {
|
||||
Write-Warn "rtk 下载失败: $_"
|
||||
Write-Warn "可手动运行:cargo install --git https://github.com/rtk-ai/rtk"
|
||||
}
|
||||
}
|
||||
|
||||
# ── rtk init -g:注册 Claude Code PreToolUse hook ─────────────
|
||||
if (Get-Command rtk -ErrorAction SilentlyContinue) {
|
||||
Write-Info "注册 rtk hook 到 Claude Code..."
|
||||
# 先禁用遥测,避免交互式确认提示阻塞管道
|
||||
$prevEAP = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
& rtk telemetry disable 2>&1 | Out-Null
|
||||
try {
|
||||
& rtk init -g --auto-patch 2>&1 | Out-Null
|
||||
Write-OK "rtk hook 已注册(重启 Claude Code 后生效)"
|
||||
} catch {
|
||||
Write-Warn "rtk init 异常(hook 可能已注册): $_"
|
||||
} finally {
|
||||
$ErrorActionPreference = $prevEAP
|
||||
}
|
||||
|
||||
# ── rtk Copilot Instructions:写入 .github/copilot-instructions.md ──
|
||||
# 使用已有的(位于仓库根 .github/),仅在缺失时生成
|
||||
$copilotDir = Join-Path $ScriptDir ".." ".github"
|
||||
$copilotInst = Join-Path $copilotDir "copilot-instructions.md"
|
||||
if (-not (Test-Path $copilotInst)) {
|
||||
Write-Info "生成 .github/copilot-instructions.md ..."
|
||||
if (-not (Test-Path $copilotDir)) { New-Item -ItemType Directory -Path $copilotDir | Out-Null }
|
||||
& rtk init -g --copilot 2>&1 | Out-Null
|
||||
Write-OK "Copilot Instructions 已生成"
|
||||
} else {
|
||||
Write-OK "Copilot Instructions 已存在,跳过生成"
|
||||
}
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
# 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 = @"
|
||||
|
||||
# ── Windows Dev Stack (generated by windows-dev-stack\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"
|
||||
# 将 rtk / cargo 加入 PATH(如尚未在系统 PATH 中)
|
||||
`$_localBin = "`$env:USERPROFILE\.local\bin"
|
||||
if ((Test-Path `$_localBin) -and `$env:Path -notlike "*.local\bin*") { `$env:Path = "`$_localBin;`$env:Path" }
|
||||
`$_cargoBin = "`$env:USERPROFILE\.cargo\bin"
|
||||
if ((Test-Path `$_cargoBin) -and `$env:Path -notlike "*cargo\bin*") { `$env:Path = "`$_cargoBin;`$env:Path" }
|
||||
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 "Windows Dev Stack") {
|
||||
# 注意:直接用 -replace 会将 $_ 等解释为正则反向引用导致内容爆炸
|
||||
# 使用 MatchEvaluator scriptblock 让替换字符串按字面量处理
|
||||
$blockCapture = $profileBlock
|
||||
$existing = [regex]::Replace($existing, '(?ms)# ── Windows Dev Stack.*?# ─+', { $blockCapture })
|
||||
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 "║ ✅ Windows 原生全栈部署完成 ║" -ForegroundColor Green
|
||||
Write-Host "╚══════════════════════════════════════════════════════════════╝" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
$nodeVer = if (Get-Command node -ErrorAction SilentlyContinue) { & node --version 2>&1 } else { "(未安装)" }
|
||||
$claudeVer = if (Get-Command claude -ErrorAction SilentlyContinue) { & claude --version 2>&1 | Select-Object -First 1 } else { "(未安装)" }
|
||||
$ghVer = if (Get-Command gh -ErrorAction SilentlyContinue) { & gh --version 2>&1 | Select-Object -First 1 } else { "(未安装)" }
|
||||
$rustVer = if (Get-Command rustc -ErrorAction SilentlyContinue) { & rustc --version 2>&1 } else { "(未安装)" }
|
||||
$rtkVer = if (Get-Command rtk -ErrorAction SilentlyContinue) { & rtk --version 2>&1 } else { "(未安装)" }
|
||||
|
||||
$items = @(
|
||||
@("Node.js", $nodeVer),
|
||||
@("Claude Code", $claudeVer),
|
||||
@("GitHub CLI", $ghVer),
|
||||
@("Rust", $rustVer),
|
||||
@("rtk", $rtkVer),
|
||||
@("Unity MCP", $(if ($unityEntry) { $unityEntry } else { "(未安装)" })),
|
||||
@("Godot MCP Pro", $(if ($godotEntry) { $godotEntry } else { "(未安装)" })),
|
||||
@("Bridge Port", "TCP $BridgePort (防火墙已放行 $BridgePort-$($BridgePort+9))")
|
||||
)
|
||||
foreach ($item in $items) {
|
||||
$label = $item[0].PadRight(14)
|
||||
Write-Host " $label $($item[1])" -ForegroundColor White
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host " 后续手动步骤:" -ForegroundColor Yellow
|
||||
Write-Host " 1. gh auth login # GitHub CLI 登录(首次)" -ForegroundColor DarkGray
|
||||
Write-Host " 2. Unity Editor 安装 unity-mcp-plugin:" -ForegroundColor DarkGray
|
||||
Write-Host " https://github.com/AnkleBreaker-Studio/unity-mcp-plugin.git" -ForegroundColor DarkGray
|
||||
Write-Host " 3. Godot 项目:复制 addons/godot_mcp/ 并在 Plugins 中启用" -ForegroundColor DarkGray
|
||||
Write-Host " 源:$(Join-Path $godotPkgBase "$GodotMcpVersion\addons\godot_mcp")" -ForegroundColor DarkGray
|
||||
Write-Host " 4. 重启 AI 客户端(Claude Desktop / Cursor / Windsurf / VS Code)" -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user