Skip to content

Latest commit

 

History

History
119 lines (98 loc) · 6.5 KB

File metadata and controls

119 lines (98 loc) · 6.5 KB

sandbox_script

Run a single shell command in a fresh sandbox and return its stdout and stderr.

Parameters

Name Type Required Default Description
command string yes Shell command to run inside the sandbox. Executed with /bin/sh -c. Working directory is /out.
image string no anaconda Container image. One of: node (node:22), python (python:3.12), go (golang:1), anaconda (continuumio/anaconda3:latest, default), browser (demesne-built; Playwright JS + Chromium/Firefox/WebKit + Node, headless rendering at egress=none, built lazily on first use), media (demesne-built; ffmpeg + ImageMagick + libvips + audio tooling for video/audio/image conversion, built lazily on first use), twine (demesne-built; Tweego + Twine story formats + Chromium for offline interactive-fiction build/playtest, built lazily on first use), webgamedev (demesne-built; warm Phaser + Vite + TypeScript template + Chromium for offline HTML5-game build/playtest, built lazily on first use).
egress string no package-managers Outbound network policy. package-managers allows npm, PyPI, and conda registries; none denies all egress.
files array of strings no Host file paths to mount read-only into /in/<basename>. Each path must be absolute and inside DEMESNE_ALLOWED_PATHS. The live MCP input schema's description for this parameter is populated at registration time with the configured DEMESNE_ALLOWED_PATHS roots (or a no-host-inputs warning when none are configured).
directories array of strings no Host directory paths to mount read-only into /in/<basename>. Each path must be absolute and inside DEMESNE_ALLOWED_PATHS. The live MCP input schema's description for this parameter is populated at registration time with the configured DEMESNE_ALLOWED_PATHS roots (or a no-host-inputs warning when none are configured).
background boolean no false When true, returns immediately and automatically attempts one advisory terminal MCP logging notification. Client display/wake is not guaranteed; poll with sandbox_status / sandbox_wait, cancel with sandbox_cancel.

Async usage

Synchronous commands may run to completion (subject to the explicit 48h runtime limit) and remain cancellable. Pass background: true for concurrent work, detachment, status/progress polling, or deliberate job control. The response is {job_id, status: "running"}; inspect it with sandbox_status or block (30-minute default, up to 48 hours) with sandbox_wait, and cancel its descendant subtree with sandbox_cancel.

Annotations

Hint Value Rationale
readOnlyHint false The tool creates a sandbox, writes to /out, and tears the sandbox down.
destructiveHint false The sandbox is created and destroyed as a unit; from the caller's perspective no persistent state is mutated.
idempotentHint false Running the same command twice can re-fetch packages or produce different side effects.
openWorldHint true With egress=package-managers (the default) the sandbox can reach npm/PyPI/conda registries on the open internet.

Sample request

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "sandbox_script",
    "arguments": {
      "command": "python -c 'import sys; print(sys.version)'",
      "image": "python",
      "egress": "none",
      "files": ["/home/user/data.csv"],
      "directories": []
    }
  }
}

Sample result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "exit_code: 0\noutput_dir: /var/demesne/out/3f2a1b4c-...\njob_id: 3f2a1b4c-...\n---\n3.12.0 (main, ...)\n---stderr---\n\n"
      }
    ],
    "structuredContent": {
      "exit_code": 0,
      "output_dir": "/var/demesne/out/3f2a1b4c-...",
      "job_id": "3f2a1b4c-...",
      "stdout": "3.12.0 (main, ...)\n",
      "stderr": ""
    },
    "isError": false
  }
}

The text payload format (from internal/server/format.go):

exit_code: <int>
output_dir: <host path of /out>
job_id: <UUID>
---
<stdout from the command>
---stderr---
<stderr from the command>

Returned as structuredContent against the declared output schema — see Structured output for the cross-tool conventions. Fields for this tool:

Field Type
exit_code integer
output_dir string
job_id string
stdout string
stderr string

The output_dir is preserved on the host after the sandbox is destroyed; any files written to /out inside the sandbox are available there.

Files written: stdout.log (full stdout) and stderr.log (full stderr). The MCP stderr field is the last 16 KiB; the file is the complete stream.

Errors

Error When it occurs
image "<name>" is not in the allowlist (node, python, anaconda, go, browser, media, twine, webgamedev) image parameter names an unknown container image.
egress mode "<mode>" is not in the allowlist (none, package-managers, open) egress parameter is not one of the three valid modes.
mount path must be absolute: <path> A path in files or directories is relative.
mount path <path> is not within DEMESNE_ALLOWED_PATHS A path in files or directories is outside every configured DEMESNE_ALLOWED_PATHS entry.
resolve mount path <path>: <OS error> Symlink resolution failed for a path in files or directories (e.g. dangling symlink).
mount path is empty An empty string was passed in files or directories.
mount basename "<base>" would collide: <p1> and <p2> Two input paths share the same basename; they would both map to /in/<basename>.
<path> is not a regular file A path supplied in files is a directory or special file.
<path> is not a directory A path supplied in directories is a regular file.
DOCKER::SANDBOX_EXECD_DISTRIBUTION_FAILED … passing bulk input to subprocess Transient buildah-copier race. Demesne retries up to 3 times; surfaces only if all attempts fail.
VOLUME::HOST_PATH_NOT_ALLOWED OpenSandbox server rejected the bind mount because the host path is not in the server's allowed_host_paths list. Check ~/.sandbox.toml.
create sandbox: <error> OpenSandbox SDK returned an error during sandbox creation.

JSON Schema

See sandbox_script.schema.json.