Skip to main content
Glama
amichae2

Modal Sandbox MCP

by amichae2
README.md
# Modal Sandbox MCP

Give your AI agent **on-demand cloud compute** — CPU, RAM, and even GPU — through a
[Model Context Protocol](https://modelcontextprotocol.io) (MCP) server backed by
[Modal](https://modal.com) sandboxes.

`run_script` executes arbitrary Python on Modal's serverless infrastructure with
per-second billing, so your agent can crunch through heavy jobs that would crawl
(or OOM) on a small VPS — without you renting an always-on GPU box.

Works with **Open WebUI** (native MCP or via the mcpo OpenAPI bridge), Claude, and
any other MCP-capable client.

---

## Why

Your Open WebUI / agent host (e.g. a cheap VPS) usually has:

- limited CPU and RAM
- no GPU
- no burst capacity

Modal provides:

- containers with **up to 64+ cores, 100+ GB RAM**
- **GPUs** (T4, A10G, A100, ...) on demand
- scale-to-zero: **you pay nothing while idle**, only per-second while a script runs
- a free monthly credit (~$30/mo) — plenty for light/medium usage

This server is the glue: a thin MCP wrapper that turns "run this script" into a
Modal sandbox with whatever specs the agent asks for.

---

## Architecture

```
┌────────────┐    MCP (stdio or HTTP)    ┌──────────────────┐
│  Client    │ ────────────────────────▶ │  FastMCP server  │
│ (Open WebUI│                           │  (this repo)     │
│  / Claude) │ ◀──────────────────────── │                  │
└────────────┘                           └────────┬─────────┘
                                                  │ modal.Sandbox.create(
                                                  │   cpu=..., memory=...,
                                                  │   gpu=..., timeout=...)
                                                  ▼
                                        ┌──────────────────┐
                                        │  Modal cloud     │
                                        │  (ephemeral      │
                                        │   sandbox)       │
                                        └──────────────────┘
```

Two deployment flavors are included:

| File | Where it runs | Best for |
|---|---|---|
| `sandbox_mcp.py` | **On Modal** (`modal deploy`) | No server to babysit; public URL |
| `sandbox_mcp_local.py` | **On your own host** (systemd / mcpo) | Private (bind to Docker bridge), matches mcpo-style Open WebUI setups |

The heavy lifting always happens on Modal either way — the wrapper is just glue.

---

## Setup

### 1. Prerequisites

- A [Modal](https://modal.com) account (free tier: no payment method required)
- Python 3.10+ (for the local flavor)

### 2. Install & authenticate

```bash
pip install modal fastmcp uvicorn   # or: uv pip install ...
modal token new                     # opens browser; stores creds in ~/.modal.toml
```

### 3a. Deploy on Modal (hosted flavor)

```bash
modal deploy sandbox_mcp.py
```

Note the printed URL — it serves MCP over streamable HTTP at `<url>/mcp`.

### 3b. Run locally (glue flavor)

```bash
python sandbox_mcp_local.py --http --host 127.0.0.1 --port 8020
# stdio mode (for mcpo / MCP stdio clients):
python sandbox_mcp_local.py
```

Recommended for Open WebUI: wrap it with **mcpo** so it appears as an OpenAPI
tool server, exactly like the official Open WebUI MCP servers:

```bash
uvx mcpo --port 8021 --name sandbox-mcp \
  --description "Run Python scripts on Modal hardware via Modal Sandboxes." \
  -- /path/to/venv/bin/python /path/to/sandbox_mcp_local.py
```

A ready-made systemd user unit is in [`deploy/sandbox-mcpo.service`](deploy/sandbox-mcpo.service).

---

## Connecting Open WebUI

**Via OpenAPI tool server (mcpo):**
Admin Panel → Settings → Connections → **OpenAPI Tool Servers** → add:

```
http://<host>:8021/openapi.json
```

Then in a chat: **+ → Tools** → enable `sandbox-mcp`.

**Via native MCP:**
Admin Panel → Settings → Connections → **MCP Servers** → add:

```
http://<host>:8020/mcp     (streamable HTTP)
```

> 💡 Open WebUI's MCP connection test runs **from your browser** — a server bound to
> a private address (e.g. Docker bridge `172.17.0.1`) will fail the browser-side
> check even though the backend can reach it. The **OpenAPI/mcpo route** is fetched
> server-side and is the reliable choice for containerized Open WebUI.

---

## The tool: `run_script`

| Param | Type | Default | Meaning |
|---|---|---|---|
| `code` | string | *required* | Python source to execute (use `print()` for output) |
| `cpu` | number | 2.0 | CPU cores (e.g. 1.0, 4.0, 8.0) |
| `memory_mb` | integer | 2048 | RAM in MB (e.g. 8192, 65536) |
| `gpu` | string | `""` | GPU type: `T4`, `A10G`, `A100`; empty = CPU-only |
| `timeout` | integer | 600 | Max seconds before the sandbox is force-killed |

Returns `exit code`, stdout, and stderr (truncated at 100 KB). Each run is a fresh,
isolated, ephemeral sandbox — no persistent state, no access to your host's files.

Sandboxes have network access and come with Python + `requests`; scripts can
`pip install` extra packages at runtime (adds a little time).

---

## The tool: `list_hardware`

`list_hardware()` has no arguments and returns the static hardware catalog — CPU/RAM
tiers, GPU types (T4 → B300, including Hopper and Blackwell), approximate hourly prices,
and guidance on when to use each. Call it first when you need to choose `cpu` /
`memory_mb` / `gpu` values deliberately instead of guessing. Prices are approximate;
verify at modal.com/pricing.

## Tuning timeouts (Open WebUI gotcha)

Open WebUI caps tool-server calls with the aiohttp client timeout. If your scripts
run longer than 5 minutes, set this env var **on the open-webui container**
(default fallback is only **300s**):

```
AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER=900
```

(Requires recreating the container; keep it above your sandbox `timeout`.)

Full stack for reference: Open WebUI `900s` → mcpo `900s` → sandbox `timeout` (the
real backstop).

---

## Cost notes

- CPU sandbox runs cost **fractions of a cent** — effectively free inside Modal's
  monthly credit.
- GPUs are the expensive option (~$0.60+/hr). The agent should default to CPU and
  only request a GPU when the workload genuinely needs one (see the example system
  prompt).
- Scale-to-zero means an idle server costs **nothing**.

---

## Security notes

- The `run_script` tool is **arbitrary code execution** by design. It's for *your*
  agent, not strangers.
- **Hosted flavor:** the Modal URL is unauthenticated — anyone with it can run code
  on your Modal account. Add auth (e.g. a bearer-token middleware) before exposing
  it publicly.
- **Local flavor:** bind to `127.0.0.1` or the Docker bridge gateway
  (`172.17.0.1`) so only your container/host can reach it.
- Modal tokens stay in `~/.modal.toml` (or `MODAL_TOKEN_ID` / `MODAL_TOKEN_SECRET`
  env vars) — never commit them.

---

## Example agent system prompt

See [`examples/system-prompt.md`](examples/system-prompt.md) — copy it into your
model's system prompt (Open WebUI: Workspace → Models → edit → System Prompt) to
teach the agent *when* and *how* to use the tool.

---

## Files

```
sandbox_mcp.py          # Modal-hosted flavor (modal deploy)
sandbox_mcp_local.py    # local glue flavor (HTTP or stdio/mcpo)
deploy/
  sandbox-mcpo.service  # systemd user unit for mcpo wrapping
examples/
  system-prompt.md      # ready-to-paste agent instructions
```

## License

MIT — see [LICENSE](LICENSE).