Skip to content
HN On Hacker News ↗

Run Minecraft in a Windows sandbox

▲ 23 points 8 comments by someguy101010 2w ago HN discussion ↗

Pangram verdict · v3.3

We believe that this entire text is AI.

100 %

AI likelihood · overall

AI
0% human-written 100% AI-generated
SEGMENTS · HUMAN 0 of 1
SEGMENTS · AI 1 of 1
WORD COUNT 1,387
PEAK AI % 100% · §1
Analyzed
Aug 25
backend: pangram/v3.3
Segments scanned
1 windows
avg 1387 words each
Distribution
0 / 100%
human / AI fraction
Verdict
AI
Pangram v3.3

Article text · 1,387 words · 1 segments analyzed

Human AI-generated
§1 AI · 100%

Boot a Windows sandbox, install Minecraft Java Edition, and drive it with an agent through the cua-driver MCP server running inside the sandbox.Minecraft exercises almost everything a Windows sandbox can do: it needs internet access, a Java runtime, working OpenGL, and a GUI that only clicks can drive. This guide boots a Windows sandbox, installs Minecraft Java Edition, and hands it to an agent that talks to cua-driver's MCP server inside the sandbox — the same loop against a local sandbox and against Fleet. Before you start# cua-sandbox 0.3.3 or newer. Windows on Fleet needs 0.3.0, Image.expose() on the local QEMU runtime landed in 0.3.1, the sb.exposed_ports this guide reads the forwarded port from landed in 0.3.2, and 0.3.3 brought both Image.from_registry(..., os_type=...) and the pull-secret fix that lets Fleet boot an image from a registry outside its own allowlist — which the containerDisk section below needs. A host with hardware virtualisation for the local path — a Linux x86_64 machine with /dev/kvm, or an Intel Mac. This guide passes -cpu host, which QEMU only accepts with KVM or HVF. An x86_64 guest on Apple Silicon runs under TCG emulation, where -cpu host is rejected outright. The Fleet path runs there instead, including the game, with the one extra environment variable described in the Fleet section below. A Microsoft account that owns Minecraft Java Edition. Signing in uses Microsoft device authorization, so one step in the middle is manual: a code appears inside the sandbox and you approve it in your own browser. A vision-capable LLM endpoint for the agent loop. Boot a Windows sandbox# Image.windows() resolves to a pinned Windows Server 2022 containerDisk. Three things get added on top of the defaults: .expose(3000) publishes cua-driver's MCP server, which already runs inside the guest, so the agent can reach it. A second network interface. The bare-metal runtime attaches its NIC with restrict=on, which isolates the guest. sb.shell.run() still works over the forwarded port, but nothing inside Windows can reach the internet — and Minecraft needs to. -cpu host. The default qemu64 model is too thin for a software OpenGL driver: Minecraft creates its window and then dies while loading resources, with no Java exception and no crash log. The last -cpu on the command line wins, so appending it is enough. import asyncio from cua import Image, QEMURuntime, Sandbox EXTRA_ARGS = [ # a second, unrestricted user-mode NIC — the default one is 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', # a CPU the software OpenGL driver can actually use '-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 on http://127.0.0.1:{mcp_port}/mcp') await sb.disconnect() # the sandbox keeps running asyncio.run(main()) A warm boot takes about 30 seconds. exposed_ports maps each exposed guest port to the host port it landed on, and GET /healthz on that port answers ok once cua-driver is up. Read the port from sb.exposed_ports, not from a tunnel. sb.tunnel.forward(3000) — the usual way to get a forwarded port, and the one the Fleet section below uses — raises NotImplementedError: HTTPTransport does not support port forwarding on the local transport. exposed_ports is the local equivalent: the runtime picks a free host port at boot, so the mapping is only knowable at runtime, and it is saved with the sandbox state so a later Sandbox.connect() can read it back. On Fleet the property is empty, because Fleet publishes services instead — use tunnel.forward() there. Give the second NIC its own subnet. Both user-mode networks default to 10.0.2.0/24 and both offer the guest 10.0.2.15, so Windows drops one interface to a 169.254.x.x link-local address with no gateway and no working DNS. Confirm the guest really has internet before installing anything. 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 Install a launcher and a software OpenGL driver# The sandbox GPU is the Microsoft Basic Display Adapter, which offers OpenGL 1.1. Minecraft 1.17 and later need OpenGL 3.2, so the game needs Mesa3D's opengl32.dll (llvmpipe), which implements OpenGL in software. Both downloads below are MinGW builds on purpose. The MSVC builds of Prism Launcher and Mesa both depend on the Visual C++ redistributable, which Windows Server 2022 does not ship: Prism then exits silently, and Mesa's DLL fails to load so Windows quietly falls back to the system opengl32.dll. $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' New-Item -ItemType Directory -Force -Path C:\mc | Out-Null # Prism Launcher — signs in with Microsoft device authorization, which needs no browser 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, because Mesa ships as .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 software 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 Save that as setup.ps1, push it into the sandbox, and run it. It downloads roughly 100 MB, so allow a generous timeout. 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) Sign in and create an instance# Prism opens a Quick Setup wizard on first run. Screenshot the sandbox, click through it, and stop at the account page. async with Sandbox.connect('mc-win', local=True) as sb: Path('sandbox.png').write_bytes(await sb.screenshot()) # look at it await sb.mouse.click(888, 678) # Next Work through the wizard to Accounts → Add Microsoft. Prism shows a QR code and an eight-character device code. Read the code off a screenshot, open https://www.microsoft.com/link in your own browser, enter it, and approve the sign-in. The account then appears with status Ready. Click Add Instance, search for a version such as 1.20.1, and click OK. Prism downloads the client jar and assets. Device codes expire after about fifteen minutes, but Prism issues a fresh one automatically and keeps polling, so the dialog can be left open. Take a new screenshot to read the current code rather than reusing an old one. Point the software driver at the launcher's Java# Click Launch once. Prism downloads its own Java runtime and the game fails with GLFW error 65542: WGL: The driver does not appear to support OpenGL — expected, because Mesa is not in place yet. Prism may keep using the runtime it downloaded even if you set JavaPath in its config, so copy the Mesa DLLs next to every javaw.exe under the install root. Windows loads opengl32.dll from the running executable's directory before the system directory, which is what makes this work. $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" } Deliver it the same way as the first script. 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 Click Launch again. The Minecraft title screen appears after a minute or two. Drive it with an agent over MCP# The sandbox already runs cua-driver, which serves an MCP endpoint on guest port 3000 — that is what .expose(3000) published. The agent is a small loop: list the MCP tools, hand them to a model as ordinary function tools, call whichever one it picks, feed the result back. Three things about cua-driver's tools shape the loop: A YAML policy governs which tools may actually run, and list_tools() does not reflect it. Every cua-driver release to date advertises the full surface and refuses out-of-policy calls only when you make them, with Permission denied: user policy: tool 'X' is not allowed by the YAML policy. So the listing is a menu of what exists, not of what you can call. Here that surface was 55 tools, identically over the local and Fleet transports: get_desktop_state, list_apps, list_windows, get_window_state, click, double_click, type_text, press_key, hotkey, launch_app, bring_to_front, scroll and drag ran, while get_screen_size, get_accessibility_tree, get_config, check_permissions, get_cursor_position and zoom were refused. Treat that split as something to probe on your own image rather than a fixed list — a denial arrives before the tool executes, so probing is cheap. Later drivers filter the listing through the policy, at which point the two finally agree.