Skip to main content
Outcome: a stdio MCP server exposing a run_in_sandbox tool, verified end to end by a real MCP client. Level: intermediate · Time: ~20 minutes · Pattern: the box is a tool the model calls.

When to use this

The Model Context Protocol is becoming the common way to hand external capabilities to LLM clients. When several different agents, IDEs, or desktop clients all need “execute this in an isolated sandbox”, you do not want to reimplement the call path for each one. You want one standard tool that every client discovers and calls the same way. This guide builds that: SimpleBox.exec behind an MCP tool named run_in_sandbox, so any client gets “run a command in a hardware-isolated microVM and receive stdout, stderr, and the exit code”. The division of labor is worth stating once: the MCP transport is yours, the sandbox is BoxLite’s. You bring an MCP framework — this guide uses FastMCP from the official mcp package — and BoxLite provides the isolated execution behind each tool call.

Architecture

Protocol handling — handshake, tool discovery, JSON-RPC — runs in your process. Every call_tool starts a clean SimpleBox, runs the command in its own kernel, and destroys the box afterwards.

Prerequisites

  • BoxLite installed and a working virtualization host — see Installation.
  • The official MCP Python SDK, which BoxLite does not bundle.
No LLM credentials are needed. This guide exercises the protocol handshake and sandbox execution; credentials only matter when you connect a real client later.

Build it

Step 1: the MCP server

Save as sandbox_mcp_server.py. It runs directly as a stdio MCP server.

Step 2: a client that proves the handshake works

Save as mcp_client_test.py. It launches the server, initializes, discovers tools, and calls one.
The SimpleBox.exec parameter table is on MCP tool handler.

Run it

That stdout came from echo running inside the microVM, serialized back over JSON-RPC. Now confirm the behaviour that surprises people most — a failing command returns data rather than raising. Change the tool call to:
The client receives the exit code and stderr as a normal result. Nothing was raised, and nothing crossed into your process.

Trust and limits

Two layers, with different guarantees.
  • Execution layer — isolated. Every call_tool starts its own microVM with its own kernel, filesystem, memory, and resource budget. However untrusted the command an MCP client sends, rm -rf / damages only that box, which is destroyed on exit.
  • Protocol layer — yours to secure. FastMCP runs in your host process, outside any sandbox. Anything that can connect to this server can call run_in_sandbox. Authentication, rate limiting, and any command allowlist are your responsibility — add them in the tool function before the box is created.
  • Failures arrive as data or as exceptions, never as crashes. A non-zero exit is returned in the result. Missing virtualization or a failed image pull raises RuntimeError (not BoxliteError), which the handler above catches and serializes back to the client.
  • Fresh box per call is a deliberate trade. It gives maximum isolation and costs a start each time. Reusing one long-lived box is faster but lets state leak between calls — see Next steps.

Troubleshooting

Next steps

  • Connect a real client. Register sandbox_mcp_server.py in your client’s MCP configuration (command plus args); it performs the same stdio handshake verified above and discovers run_in_sandbox automatically.
  • Add more tools. Write additional @mcp.tool() functions — read_file / write_file over copy_in / copy_out, or run_python over CodeBox.run (see Build a code interpreter).
  • Add access control. A command allowlist and authentication in the tool function are the first things to add once the server is reachable — see Run untrusted tools safely.
  • Reuse one box per session. Hold a long-lived box on the server instead of creating one per call, and weigh the speed against the state that then persists across calls.