在 Windows 沙盒中为计算机使用代理运行 Minecraft
摘要
本指南说明如何在 Windows 沙盒中设置并运行 Minecraft Java Edition,该沙盒由使用驱动程序的 MCP 服务器的 AI 代理控制,涵盖要求和逐步说明。
暂无内容
查看缓存全文
缓存时间: 2026/08/25 19:59
# 在Windows沙盒中运行Minecraft
来源:https://cua.ai/docs/how-to-guides/sandbox/minecraft
启动一个Windows沙盒,安装Minecraft Java版,并通过沙盒内运行的cua\-driver MCP服务器用智能体进行操控。Minecraft几乎能测试Windows沙盒的所有功能:它需要网络访问、Java运行时、正常的OpenGL支持,以及只有通过点击操作才能控制的图形界面。本指南将介绍如何启动一个Windows沙盒,安装Minecraft Java版,并将其交给通过**沙盒内部cua\-driver MCP服务器**进行通信的智能体——无论是针对本地沙盒还是Fleet平台,控制循环机制相同。
## 开始前准备#
(https://cua.ai/docs/how-to-guides/sandbox/minecraft#before-you-start)
- **cua\-sandbox 0.3.3或更新版本。** Fleet平台上的Windows需要0.3.0版本,本地QEMU运行时中`Image\.expose\(\)`功能在0.3.1版本引入,本指南读取转发端口的`sb\.exposed\_ports`属性在0.3.2版本加入,0.3.3版本同时加入了`Image\.from\_registry\(\.\.\., os\_type=\.\.\.\)`功能和允许Fleet从自身允许列表外的注册表启动镜像的拉取密钥修复——后者在下面的containerDisk部分会用到。
- **支持硬件虚拟化的主机**(针对本地路径)——具有`/dev/kvm`的Linux x86_64机器,或Intel Mac。本指南传递`-cpu host`参数,该参数只在KVM或HVF环境下被QEMU接受。Apple Silicon上的x86_64虚拟机在TCG模拟模式下运行时会直接拒绝`-cpu host`参数。Fleet路径在该环境下运行,包括游戏本身,只需额外设置下面Fleet部分描述的一个环境变量。
- **拥有Minecraft Java版的微软账户。** 登录使用微软设备授权流程,因此中间有一个手动步骤:沙盒内会显示一个验证码,您需要在自己的浏览器中批准它。
- **支持视觉能力的LLM端点**(用于智能体循环)。
## 启动Windows沙盒#
(https://cua.ai/docs/how-to-guides/sandbox/minecraft#boot-a-windows-sandbox)
`Image\.windows\(\)`会解析到一个固定版本的Windows Server 2022容器磁盘镜像。在默认配置基础上额外添加了三样东西:
- **`.expose(3000)`**:发布cua\-driver的MCP服务器,该服务器已在虚拟机内运行,以便智能体能够访问。
- **第二个网络接口。** 裸金属运行时使用`restrict=on`参数附加其网卡,这将隔离虚拟机。虽然`sb\.shell\.run\(\)`仍可通过转发端口工作,但Windows内部无法访问互联网——而Minecraft需要联网。
- **`-cpu host`参数。** 默认的`qemu64`CPU模型对软件OpenGL驱动程序而言配置过低:Minecraft会创建窗口,然后在加载资源时崩溃,没有Java异常,也没有崩溃日志。命令行中最后一个`-cpu`参数生效,因此只需追加即可。
```python
import asyncio
from cua import Image, QEMURuntime, Sandbox
EXTRA_ARGS = [
# 第二个非受限用户模式网卡(默认网卡设置了restrict=on)
'-netdev', 'user,id=net1,net=10.0.3.0/24,host=10.0.3.2,dns=10.0.3.3',
'-device', 'virtio-net-pci,netdev=net1,mac=52:55:00:d1:55:02',
# 软件OpenGL驱动程序实际可用的CPU模型
'-cpu', 'host',
]
async def main():
sb = await Sandbox.create(
Image.windows().expose(3000),
name='mc-win',
local=True,
runtime=QEMURuntime(
mode='bare-metal',
cpu_count=12,
memory_mb=16384,
extra_args=EXTRA_ARGS,
),
)
mcp_port = sb.exposed_ports[3000]
print(f'cua-driver MCP 运行在 http://127.0.0.1:{mcp_port}/mcp')
await sb.disconnect() # 沙盒会继续运行
asyncio.run(main())
```
热启动大约需要30秒。`exposed_ports`属性将每个暴露的虚拟机端口映射到宿主机端口,当cua\-driver启动完成后,该端口的`GET /healthz`请求会返回`ok`响应。
**从`sb\.exposed_ports`读取端口,而非使用隧道。** `sb\.tunnel\.forward(3000)`——通常用于获取转发端口的方法,也是下面Fleet部分使用的方法——在本地传输时会抛出`NotImplementedError: HTTPTransport does not support port forwarding`错误。`exposed_ports`是本地的等效方案:运行时在启动时选择一个空闲的宿主机端口,因此映射关系仅在运行时可知,并且会与沙盒状态一同保存,以便后续的`Sandbox\.connect()`可以读回。在Fleet上该属性为空,因为Fleet发布的是服务——在那里需要使用`tunnel\.forward()`方法。
为第二个网卡分配独立的子网。两个用户模式网络默认使用`10\.0\.2\.0/24`网段,且都向虚拟机分配`10\.0\.2\.15`地址,这会导致Windows将其中一个接口降级为`169\.254\.x\.x`链路本地地址,没有网关,DNS也无法工作。安装任何软件前,请确认虚拟机确实能访问互联网。
```python
async with Sandbox.connect('mc-win', local=True) as sb:
check = await sb.shell.run(
'powershell -Command "(Invoke-WebRequest -UseBasicParsing '
'https://piston-meta.mojang.com/mc/game/version_manifest.json).StatusCode"'
)
print(check.stdout) # 输出 200
```
## 安装启动器和软件OpenGL驱动程序#
(https://cua.ai/docs/how-to-guides/sandbox/minecraft#install-a-launcher-and-a-software-opengl-driver)
沙盒的GPU是*Microsoft Basic Display Adapter*,提供OpenGL 1.1支持。Minecraft 1.17及更高版本需要OpenGL 3.2,因此游戏需要Mesa3D的`opengl32\.dll`(llvmpipe),它用软件方式实现OpenGL。下面两个下载文件都特意选择**MinGW**构建版本。Prism Launcher和Mesa的MSVC构建都依赖Visual C\+\+可再发行组件,而Windows Server 2022并未预装:Prism会静默退出,Mesa的DLL加载失败导致Windows静默回退到系统自带的`opengl32\.dll`。
```powershell
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
New-Item -ItemType Directory -Force -Path C:\mc | Out-Null
# Prism Launcher — 使用微软设备授权登录,无需浏览器
Invoke-WebRequest -UseBasicParsing -OutFile C:\mc\prism.zip `
'https://github.com/PrismLauncher/PrismLauncher/releases/download/11.0.3/PrismLauncher-Windows-MinGW-w64-Portable-11.0.3.zip'
Expand-Archive C:\mc\prism.zip -DestinationPath C:\mc\prismw -Force
# 7-Zip,因为Mesa以.7z格式分发
Invoke-WebRequest -UseBasicParsing -OutFile C:\mc\7z.msi 'https://www.7-zip.org/a/7z2408-x64.msi'
Start-Process msiexec.exe -ArgumentList '/i','C:\mc\7z.msi','/qn' -Wait
# Mesa3D软件OpenGL
Invoke-WebRequest -UseBasicParsing -OutFile C:\mc\mesa.7z `
'https://github.com/pal1000/mesa-dist-win/releases/download/26.1.6/mesa3d-26.1.6-release-mingw.7z'
& 'C:\Program Files\7-Zip\7z.exe' x C:\mc\mesa.7z -oC:\mc\mesamw -y | Out-Null
Start-Process -FilePath C:\mc\prismw\prismlauncher.exe -WorkingDirectory C:\mc\prismw
```
将此脚本保存为`setup\.ps1`,推送到沙盒中并运行。它会下载约100MB内容,请设置足够的超时时间。
```python
from pathlib import Path
async with Sandbox.connect('mc-win', local=True) as sb:
await sb.shell.run('if not exist C:\\mc mkdir C:\\mc')
await sb.files.write_text('C:\\mc\\setup.ps1', Path('setup.ps1').read_text())
result = await sb.shell.run(
'powershell -NoProfile -ExecutionPolicy Bypass -File C:\\mc\\setup.ps1',
timeout=1800,
)
print(result.stdout)
```
## 登录并创建实例#
(https://cua.ai/docs/how-to-guides/sandbox/minecraft#sign-in-and-create-an-instance)
Prism首次运行时会打开**快速设置**向导。截取沙盒屏幕截图,点击向导,在账户页面停下。
```python
async with Sandbox.connect('mc-win', local=True) as sb:
Path('sandbox.png').write_bytes(await sb.screenshot()) # 查看截图
await sb.mouse.click(888, 678) # 点击"下一步"
```
1. 通过向导进入**账户 → 添加微软账户**。Prism会显示二维码和八位设备代码。
2. 从截图中读取代码,在您自己的浏览器中打开`https://www\.microsoft\.com/link`,输入代码并批准登录。账户随后会以*就绪*状态显示。
3. 点击**添加实例**,搜索如`1\.20\.1`的版本,点击**确定**。Prism会下载客户端jar和资源文件。设备代码约十五分钟后过期,但Prism会自动发布新代码并持续轮询,因此对话框可以保持打开。截取新屏幕截图以读取当前代码,而不是重复使用旧截图。
## 将软件驱动程序指向启动器的Java#
(https://cua.ai/docs/how-to-guides/sandbox/minecraft#point-the-software-driver-at-the-launchers-java)
点击一次**启动**。Prism会下载自己的Java运行时,游戏会失败并显示`GLFW error 65542: WGL: The driver does not appear to support OpenGL`——这是预期行为,因为Mesa尚未就位。即使你在配置中设置`JavaPath`,Prism可能仍会使用它下载的运行时,因此接下来需要将Mesa的DLL复制到安装根目录下*所有*`javaw\.exe`文件所在的目录。Windows会从运行中可执行文件的目录加载`opengl32\.dll`,优先于系统目录,这正是此方法有效的原因。
```powershell
$dirs = Get-ChildItem C:\mc -Recurse -Filter javaw.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty DirectoryName -Unique
foreach ($d in $dirs) {
Copy-Item C:\mc\mesamw\x64\opengl32.dll, C:\mc\mesamw\x64\libgallium_wgl.dll $d -Force
Write-Output "mesa -> $d"
}
```
用与第一个脚本相同的方式传送此脚本。
```python
async with Sandbox.connect('mc-win', local=True) as sb:
await sb.files.write_text('C:\\mc\\mesa.ps1', Path('mesa.ps1').read_text())
result = await sb.shell.run(
'powershell -NoProfile -ExecutionPolicy Bypass -File C:\\mc\\mesa.ps1',
timeout=600,
)
print(result.stdout) # 输出类似:mesa -> C:\mc\prismw\java\java-runtime-gamma\bin
```
再次点击**启动**。一两分钟后,Minecraft标题画面将会出现。
## 通过MCP用智能体控制游戏#
(https://cua.ai/docs/how-to-guides/sandbox/minecraft#drive-it-with-an-agent-over-mcp)
沙盒内已运行**cua\-driver**,在虚拟机端口3000提供MCP端点——这就是`.expose(3000)`发布的内容。智能体是一个简单的循环:列出MCP工具,将其作为普通函数工具提供给模型,调用它选择的工具,将结果反馈回去。关于cua\-driver工具有三点塑造了这个循环:
- **YAML策略控制哪些工具实际可运行,而`list\_tools()`不会反映该策略。** 迄今为止每个cua\-driver版本都会公告完整工具集,只有在你发起调用时才拒绝策略外的调用,并返回`Permission denied: user policy: tool 'X' is not allowed by the YAML policy`。因此列表是可用工具的菜单,而非可调用工具的菜单。在这里该工具集包含55个工具,在本地和Fleet传输方式下完全相同:`get\_desktop\_state`、`list\_apps`、`list\_windows`、`get\_window\_state`、`click`、`double\_click`、`type\_text`、`press\_key`、`hotkey`、`launch\_app`、`bring\_to\_front`、`scroll`和`drag`可正常运行,而`get\_screen\_size`、`get\_accessibility\_tree`、`get\_config`、`check\_permissions`、`get\_cursor\_position`和`zoom`被拒绝。应将这种区分视为需要在您自己的镜像上探索的内容,而非固定列表——拒绝会在工具执行前返回,因此探索成本很低。后续驱动程序会通过策略过滤列表,届时两者最终将保持一致。
- **点击操作是针对应用程序寻址的,而非屏幕。** `click(pid=\.\.\., x=\.\.\., y=\.\.\.)`针对属于该pid的窗口,可通过`list\_windows`找到。当后台传递的点击未生效时,可添加`delivery\_mode='foreground'`参数。
- **没有等待工具。** 循环通过再次调用`get\_desktop\_state`来等待,因此请在系统提示词中说明这一点,否则模型会发明更差的方法。
```python
import asyncio, json, os
import litellm
from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport
SYSTEM = """你通过提供的工具操作计算机。桌面是1280x800的Windows系统。循环工作:使用get_desktop_state查看屏幕,决定一个动作,调用一个工具,然后再次查看。
* click / type_text / press_key 作用于特定应用程序,通过`pid`寻址。使用list_windows查找pid,然后在调用时传递pid和x/y坐标。
* 没有等待工具。如果某些内容仍在加载,再次调用get_desktop_state——重复查看就是等待的方式。
每次回合只调用一个工具。当任务完成时,回复DONE。"""
async def complete(**kwargs):
"""此处使用的端点有两个变通方法——您的端点可能不需要。
它仅支持流式传输(普通请求返回空输出),且拒绝role=system,因此系统提示词作为第一个用户回合传输。"""
messages, system = [], []
for m in kwargs['messages']:
(system if m.get('role') == 'system' else messages).append(m)
if system:
text = '\n\n'.join(m['content'] for m in system)
messages = [{'role': 'user', 'content': text}] + messages
kwargs['messages'] = messages
stream = await litellm.acompletion(**kwargs, stream=True)
chunks = [c async for c in stream]
return litellm.stream_chunk_builder(chunks, messages=messages)
def prune_images(messages, keep=3):
"""每次get_desktop_state都返回完整屏幕截图;只保留最新的。"""
seen = 0
for msg in reversed(messages):
if not isinstance(msg.get('content'), list):
continue
for part in msg['content']:
if part.get('type') == 'image_url':
seen += 1
if seen > keep:
part.clear()
part.update({'type': 'text', 'text': '[已丢弃较旧的屏幕截图]'})
return messages
async def run(mcp_url, task, model, max_steps=60, headers=None):
client = Client(StreamableHttpTransport(mcp_url, headers=headers))
async with client:
# list_tools()在本文使用的镜像上返回了55个工具,其中大多数是
# 本任务永远不需要的浏览器和录制管道组件。只给模型提供任务所需的
# 工具:几十个模式会占用大量上下文,较短的菜单意味着较少的出错方式。
# 无论驱动程序是否已过滤被拒绝的工具,都值得这样做。
wanted = {
'get_desktop_state', 'list_windows', 'list_apps',
'click', 'double_click', 'type_text', 'press_key',
'hotkey', 'launch_app', 'bring_to_front', 'scroll',
}
mcp_tools = [t for t in await client.list_tools() if t.name in wanted]
tools = [{
'type': 'function',
'function': {
'name': t.name,
'description': (t.description or '')[:800],
'parameters': t.inputSchema or {'type': 'object', 'properties': {}},
},
} for t in mcp_tools]
messages = [{'role': 'system', 'content': SYSTEM}, {'role': 'user', 'content': task}]
for _ in range(max_steps):
resp = await complete(
model=model,
messages=prune_images(messages),
tools=tools,
tool_choice='auto',
temperature=0.0,
)
msg = resp.choices[0].message
messages.append(msg.model_dump())
if not msg.tool_calls:
break # 模型表示DONE
for call in msg.tool_calls:
args = json.loads(call.function.arguments or '{}')
result = await client.call_tool(call.function.name, args, raise_on_error=False)
text = ''.join(getattr(b, 'text', '') for b in (result.content or []))
messages.append({'role': 'tool', 'tool_call_id': call.id, 'name': call.function.name, 'content': text[:1500]})
shot = next((b.data for b in (result.content or []) if getattr(b, 'data', None)), None)
if shot:
messages.append({'role': 'user', 'content': [
{'type': 'text', 'text': '该操作后的屏幕截图:'},
{'type': 'image_url', 'image_url': {'url': f'data:image/png;base64,{shot}'}},
]})
```
将其指向暴露的端口并给它任务。
```python
TASK = """Prism Launcher已打开,其中有一个Minecraft实例和一个已登录账户。选择该实例并点击启动。
Minecraft使用软件渲染器,因此窗口需要几分钟才会出现,并且重绘缓慢——持续调用get_desktop_state来观察它,不要重启任何内容。
在标题画面点击单人游戏,然后创建新世界,再次点击创建新世界。
一旦进入世界(第一人称视角看到地形,可见快捷栏和生命值)就停止,并回复DONE。
当Minecraft处于前台时,永远不要按Escape键。"""
asyncio.run(run(f'http://127.0.0.1:{mcp_port}/mcp', TASK, 'your-model'))
```
因为MCP工具是作为**普通函数工具**呈现的,这适用于拒绝提供商原生计算机使用工具类型的端点。这并非假设场景:在本文使用的网关上,相同模型在相同时间使用相同图像,对普通函数工具返回200,而对Anthropic的`computer_`
相似文章
你们都在用什么沙箱来运行AI代理?
作者在询问关于在个人电脑上安全运行AI代理的沙箱推荐,提到了Bubblewrap和Docker作为选项,但指出了易用性问题。
AI Agent 具有 Root 权限
本文警告了在AI代理中使用非沙盒化的MCP服务器的安全风险,这些服务器可以以用户级权限执行,并可能暴露敏感数据和系统。
我们如何构建安全、可扩展的代理沙箱基础设施(8分钟阅读)
Browser Use 描述了隔离执行代码的 AI 代理的两种模式:隔离工具与隔离代理。他们使用 AWS 上的 Unikraft 微虚拟机实现了代理隔离模式,获得了安全、可扩展且一次性的沙箱。
代理环境的安全与维护
一位开发者构建了 Terrarium,这是一个开源沙箱解决方案,用于安全运行多个AI代理,提供隔离世界、反向代理管理和状态回滚功能。
@HowToAI_: 中国刚刚向AI智能体社区免费提供了一个生产级沙箱。OpenSandbox是一个开源的沙箱运行时…
中国发布了OpenSandbox,这是一个面向AI智能体的开源沙箱运行时,支持多种SDK以及通过Docker/Kubernetes隔离的安全执行环境。