MCP Server
Give any MCP-compatible agent (Claude Code, Cursor, etc.) direct tool access to Sandal sandboxes, runners, and devices.
Sandal ships a Model Context Protocol server so agents can create and drive infrastructure as tools, without you writing SDK glue code. There are two levels:
- Product MCP server, one server exposing tools for sandboxes, runners, and devices across your whole account.
- Per-instance MCP server, a scoped server bound to a single device (simulator) or sandbox, useful when you want an agent to only touch one resource it already owns.
Product MCP server#
Claude Code#
claude mcp add sandal -- npx -y @sandal/mcpOr add it directly to .mcp.json:
{
"mcpServers": {
"sandal": {
"command": "npx",
"args": ["-y", "@sandal/mcp"],
"env": {
"SANDAL_API_KEY": "sk_live_..."
}
}
}
}Cursor#
Add to .cursor/mcp.json:
{
"mcpServers": {
"sandal": {
"command": "npx",
"args": ["-y", "@sandal/mcp"],
"env": {
"SANDAL_API_KEY": "sk_live_..."
}
}
}
}Once connected, the agent gets tools like:
sandal_sandbox_create, create a sandbox from a templatesandal_sandbox_exec, run a shell command and return stdout/stderr/exit codesandal_sandbox_fs_write/sandal_sandbox_fs_readsandal_sandbox_expose, get a public URL for a portsandal_sandbox_pause/sandal_sandbox_forksandal_runner_pool_create/sandal_runner_pool_listsandal_device_create/sandal_device_tap/sandal_device_screenshot
Each tool call maps 1:1 onto the same REST API the @sandal/sdk clients use, so behavior (billing, snapshotting, per-second metering) is identical whether you drive Sandal through code or through an agent.
Per-instance MCP for devices#
When you create an iOS/Android simulator, Sandal can mint a scoped MCP server for just that device, handy for handing a running simulator to an agent without giving it account-wide access.
const dev = await sandal.devices.create({ kind: "ios-simulator" });
const { mcpUrl, mcpToken } = await dev.mcp.create({ scope: ["tap", "screenshot", "installApp"] });mcpUrl is a DPoP-scoped endpoint good for the lifetime of the device session (or until revoked). Point any MCP client at it:
{
"mcpServers": {
"sandal-device": {
"url": "https://mcp.sandal.dev/devices/dev_7f21a",
"headers": {
"Authorization": "Bearer <mcpToken>"
}
}
}
}The per-instance server only exposes tools for that device, tap, screenshot, installApp, swipe, scoped to whatever you passed in scope. It cannot create new devices, list other sandboxes, or see billing.
Auth & scoping#
- The product MCP server authenticates with a normal Sandal API key (
SANDAL_API_KEY), same as the SDKs. Scope it withsandal api-key create --scope ...if you want to limit what the agent can do. - Per-instance MCP tokens are DPoP-bound and short-lived by default; call
dev.mcp.create({ ttl: "2h" })to extend. - All MCP tool calls are logged as normal API activity and show up in
sandal list/ the dashboard, so agent actions are auditable alongside everything else.
Discovery#
The MCP server also serves llms.txt and mirrors every docs page as Markdown at <url>.md, so agents that read docs directly (rather than calling tools) get clean, unstyled source.