在 Windows 沙盒中为计算机使用代理运行 Minecraft

Hacker News Top 工具

摘要

本指南说明如何在 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 Agent 具有 Root 权限

Hacker News Top

本文警告了在AI代理中使用非沙盒化的MCP服务器的安全风险,这些服务器可以以用户级权限执行,并可能暴露敏感数据和系统。

代理环境的安全与维护

Reddit r/openclaw

一位开发者构建了 Terrarium,这是一个开源沙箱解决方案,用于安全运行多个AI代理,提供隔离世界、反向代理管理和状态回滚功能。