@@ -295,6 +295,25 @@ Create `~/.config/pixels/config.toml`:
295295#
296296# Session-only literal — passed to console/exec but NOT written to /etc/environment:
297297# MY_VAR = { value = "some-value", session_only = true }
298+
299+ [mcp ]
300+ # prefix = "mcp-" # name prefix for MCP-spawned sandboxes (final: px-mcp-<hex>)
301+ # base_prefix = "base-" # name prefix for bases (final: px-base-<name>)
302+ # default_image = "" # falls back to defaults.image when empty
303+ # listen_addr = "127.0.0.1:8765"
304+ # endpoint_path = "/mcp"
305+ # idle_stop_after = "1h" # stop sandboxes idle for this long
306+ # hard_destroy_after = "24h" # destroy sandboxes older than this
307+ # reap_interval = "1m" # how often the reaper checks lifetimes
308+ # exec_timeout_max = "10m" # ceiling for any single MCP exec call
309+ # state_file = "" # default: $XDG_CACHE_HOME/pixels/mcp-state.json
310+ # pid_file = "" # default: $XDG_CACHE_HOME/pixels/mcp.pid
311+
312+ # Declare extra bases (dev/python/node ship built in):
313+ # [mcp.bases.rust]
314+ # parent_image = "ubuntu/24.04"
315+ # setup_script = "~/.config/pixels/bases/rust.sh"
316+ # description = "Rust toolchain"
298317```
299318
300319### Priority Order
@@ -327,9 +346,28 @@ Create `~/.config/pixels/config.toml`:
327346| ` PIXELS_PROVISION_ENABLED ` | ` provision.enabled ` |
328347| ` PIXELS_PROVISION_DEVTOOLS ` | ` provision.devtools ` |
329348| ` PIXELS_NETWORK_EGRESS ` | ` network.egress ` |
349+ | ` PIXELS_MCP_PREFIX ` | ` mcp.prefix ` |
350+ | ` PIXELS_MCP_BASE_PREFIX ` | ` mcp.base_prefix ` |
351+ | ` PIXELS_MCP_DEFAULT_IMAGE ` | ` mcp.default_image ` |
352+ | ` PIXELS_MCP_LISTEN_ADDR ` | ` mcp.listen_addr ` |
353+ | ` PIXELS_MCP_ENDPOINT_PATH ` | ` mcp.endpoint_path ` |
354+ | ` PIXELS_MCP_IDLE_STOP_AFTER ` | ` mcp.idle_stop_after ` |
355+ | ` PIXELS_MCP_HARD_DESTROY_AFTER ` | ` mcp.hard_destroy_after ` |
356+ | ` PIXELS_MCP_REAP_INTERVAL ` | ` mcp.reap_interval ` |
357+ | ` PIXELS_MCP_EXEC_TIMEOUT_MAX ` | ` mcp.exec_timeout_max ` |
358+ | ` PIXELS_MCP_STATE_FILE ` | ` mcp.state_file ` |
359+ | ` PIXELS_MCP_PID_FILE ` | ` mcp.pid_file ` |
330360
331361## Using ` pixels ` as an MCP code-sandbox server
332362
363+ > ** Alpha.** Lifecycle and the tool surface are stable enough to build
364+ > against. Sandbox security for the MCP path is not yet aligned with
365+ > ` pixels create ` : sandboxes don't get per-call egress policies, and
366+ > base clones inherit whatever was baked in at build time. Egress
367+ > and the rest of the ` pixels create ` hardening are coming soon.
368+ > For now, I'd treat an MCP-spawned sandbox as if you'd run
369+ > ` pixels create --egress unrestricted ` .
370+
333371` pixels mcp ` runs a streamable-HTTP MCP server that exposes container
334372lifecycle, exec, and file CRUD as MCP tools. Run it once on your
335373machine, then point any number of MCP clients at it.
@@ -342,6 +380,12 @@ By default it binds to `http://127.0.0.1:8765/mcp` and refuses to start
342380if another instance is already running (PID file at
343381` ~/.cache/pixels/mcp.pid ` ).
344382
383+ The server has no auth. Keep it on loopback, or put it behind a
384+ reverse proxy (Caddy, nginx, Traefik) that handles auth for you.
385+ If you bind it to a non-loopback address with no proxy in front,
386+ anything that can reach the port can run ` exec ` in any of your
387+ sandboxes.
388+
345389### Configure your client
346390
347391Claude Code MCP entry:
@@ -370,6 +414,20 @@ Claude Code MCP entry:
370414| ` delete_file ` | Remove a file |
371415| ` list_files ` | List directory contents (optionally recursive) |
372416
417+ ### Container names
418+
419+ Both backends prepend ` px- ` to every instance. The MCP daemon
420+ prepends its own prefix on top of that, so:
421+
422+ - MCP-spawned sandboxes land at ` px-mcp-<hex> ` (` mcp.prefix ` default
423+ is ` mcp- ` ).
424+ - Bases land at ` px-base-<name> ` (` mcp.base_prefix ` default is
425+ ` base- ` ).
426+
427+ That's why CLI examples like ` pixels checkpoint create px-base-python `
428+ use the full on-disk name. ` create_sandbox ` returns the full name in
429+ its response.
430+
373431### Base pixels
374432
375433A base is a container that sandboxes clone from. Bases are declared in
@@ -402,6 +460,11 @@ description = "Rust toolchain + dev tools"
402460Bases form a DAG via ` from ` . Cycle / missing-dep / both-set / neither-set
403461are rejected at config load.
404462
463+ ** Setup scripts run as root with the ` pixels create ` hardening absent.**
464+ I treat them like Docker ` RUN ` lines: only build a base from a script
465+ you wrote or reviewed. The egress, ` safe-apt ` , and restricted-sudoers
466+ wiring from ` pixels create ` isn't applied during base build.
467+
405468Customise a base by mutating its container:
406469
407470``` bash
@@ -414,11 +477,13 @@ pixels checkpoint create px-base-python
414477The next ` create_sandbox(base="python") ` call clones from the new
415478checkpoint. Existing sandboxes are unaffected (independent containers).
416479
417- ** Checkpoint-advances-clone-source.** Any ` pixels checkpoint create px-base-X `
418- immediately advances the snapshot that future sandboxes clone from. If you take
419- a * safety* checkpoint before mutating, new sandboxes will clone from that
420- pre-mutation state until you take another checkpoint * after* the changes. Always
421- re-checkpoint after mutating to ensure new clones pick up your changes.
480+ ** Checkpoint-advances-clone-source.** Future sandboxes clone from the
481+ most recent checkpoint by creation time, not by label. So any
482+ ` pixels checkpoint create px-base-X ` immediately advances the clone
483+ source. If you take a * safety* checkpoint before mutating, new sandboxes
484+ clone from that pre-mutation state until you take another checkpoint
485+ * after* the changes. Always re-checkpoint after mutating to ensure new
486+ clones pick up your changes.
422487
423488** Mutation-propagation gotcha.** Changes to ` dev ` do NOT auto-flow into
424489` python ` or ` node ` . Both are independent containers built when ` dev ` was
0 commit comments