Skip to main content
Glama
BATTLEHAWK00

blender-server-mcp-docker

by BATTLEHAWK00
README.md
# Blender Server — Native Blender MCP Container

**English** | [简体中文](README.zh-CN.md)

Run Blender 5.2.1, an Xvfb virtual display, and the native MCP server and addon from [ahujasid/blender-mcp](https://github.com/ahujasid/blender-mcp) in one container. Client machines do not need Blender or a manually started addon.

The request path is: HTTP MCP client → native MCP server → in-container addon → persistent Blender process. Xvfb provides the GUI and viewport environment, while Cycles renders on an NVIDIA GPU. The native addon requires a GUI event loop, so the container does not use `blender -b`.

## Quick start

Requirements: Linux x86_64, Docker Compose v2, an NVIDIA driver, and NVIDIA Container Toolkit.

```bash
cp .env.example .env
mkdir -p work
# Edit .env: set API_TOKEN and GPU_ID, and set APP_UID / APP_GID to id -u / id -g
docker compose up -d --build
curl --fail http://127.0.0.1:21849/health
```

The MCP endpoint is `http://127.0.0.1:21849/mcp`. For remote access, configure `BIND_HOST`, `MCP_ALLOWED_HOSTS`, and a strong random `API_TOKEN`, then expose the service through an HTTPS reverse proxy. Addon port 9876 listens only inside the container. `.env`, `work/`, and `mcp-client.local.json` are excluded from the repository and image.

## Client setup

Generic HTTP MCP client configuration (exact fields depend on the client):

```json
{
  "mcpServers": {
    "blender-server": {
      "url": "http://127.0.0.1:21849/mcp",
      "headers": {"Authorization": "Bearer <API_TOKEN from .env>"}
    }
  }
}
```

Remove or disable any previous `uvx blender-mcp` stdio configuration, use the remote HTTP configuration above, reconnect, and refresh the tool list. Otherwise, the old service will continue trying to connect to Blender on the client machine and report `Could not connect to Blender`.

## Native tools

Use MCP `tools/list` to retrieve the complete tool list. It includes `get_addon_status`, `get_scene_info`, `get_object_info`, `execute_blender_code`, `get_viewport_screenshot`, and upstream asset search, import, and generation tools. Third-party services require separate integrations and API keys. Upstream telemetry is disabled through environment variables.

For example, call `execute_blender_code` to create an object:

```json
{"code":"import bpy\nbpy.ops.mesh.primitive_cube_add(location=(2, 0, 0))"}
```

The persistent Blender process uses Cycles and `AUTO` GPU selection by default: OptiX first, then CUDA, with no automatic CPU fallback. The selected backend depends on the host driver and GPU. Save scenes and rendered images under `/work/...`, which maps to `./work/...` on the host. In-memory scenes reset when the container restarts. Persist one with:

```python
bpy.ops.wm.save_as_mainfile(filepath="/work/scene.blend")
```

Native execution tools are synchronous, and the upstream socket timeout is 180 seconds. Use a saved `.blend` file and a separate batch process for long renders. All clients share one scene, so avoid concurrent modifications. Arbitrary Python execution has the container user's permissions; expose this service only to trusted clients.

## MCP file upload and download

The native MCP endpoint registers three additional tools. They use the same Bearer authentication as `/mcp`; no separate REST request is required.

| Tool | Purpose |
| --- | --- |
| `upload_file(path, data_base64, offset=0, overwrite=false)` | Upload a file or append a chunk; each decoded chunk may be up to 1 MiB |
| `download_file(path, offset=0, length=65536)` | Download Base64 data; chunks default to 64 KiB and may be up to 1 MiB |
| `list_files(path=".", offset=0, limit=100)` | Browse a directory with pagination, including path, type, and size |

Paths are relative to `/work/sessions/<session-id>/files/`. Absolute paths inside that session directory are also accepted. Cross-session paths and symbolic links are rejected. Uploads create parent directories automatically. Set `overwrite=true` explicitly to replace an existing file from offset zero.

The service uses stateful Streamable HTTP. The client sends `initialize`, saves the `Mcp-Session-Id` response header, then sends `notifications/initialized`. Every later request carries that session ID and the original Bearer token. Standard MCP clients handle this automatically. Session IDs are generated by the server and should be treated as credentials because file isolation under one token depends on their secrecy.

Each session has independent storage. `list_files` returns an `absolute_path` that Blender can use for scene and render output; the client can then retrieve the file with `download_file`. Files directly under `/work` are outside the file tools' session scope.

At startup, and then every **4 hours**, the service removes session directories that have received no requests for **4 consecutive hours** and have no active request. Cleanup therefore usually occurs after 4–8 idle hours. It only handles valid server-generated directories under `sessions`, does not remove legacy `/work` files, and does not follow session-directory symlinks. Download important results promptly because deleted files cannot be recovered.

After a session expires or the service restarts, clients must initialize again. A new session cannot access files from an old session. Detached background Blender work does not count as an active MCP request; use persistent storage for long detached jobs.

File-tool isolation is not operating-system isolation. `execute_blender_code` can still execute arbitrary Python, and all clients share the same Blender scene.

Upload `hello.txt` through MCP `tools/call`:

```json
{"name":"upload_file","arguments":{"path":"hello.txt","data_base64":"aGVsbG8K"}}
```

Download it again:

```json
{"name":"download_file","arguments":{"path":"hello.txt"}}
```

The result contains `data_base64`; the client decodes it and writes the bytes locally. For large uploads, split the source into chunks of at most 1 MiB, use `offset=0` for the first chunk, and use each response's `next_offset` for the next chunk. For downloads, continue with `next_offset` until `eof=true`, decoding chunks in order. `.blend` files, textures, rendered images, and archives are transferred as raw bytes.

## Build and run

The host does not need Blender or Python dependencies. To build directly:

```bash
docker build --build-arg APP_UID="$(id -u)" --build-arg APP_GID="$(id -g)" -t blender-server:local .
```

Debian uses its official package source by default; pass the `APT_MIRROR` build argument to select a mirror. The Python lockfile currently uses the USTC index. The official Blender archive is SHA256-verified, and the base image comes from GHCR. Existing download proxies can be passed with `--build-arg ALL_PROXY`; the proxy is not preserved at runtime. Upstream sources and local patches live in `vendor/blender-mcp`, with the pinned commit and license recorded there.

| Setting | Description |
| --- | --- |
| `GPU_ID` | Host GPU index; defaults to 0 |
| `APP_UID` / `APP_GID` | Container user IDs; they must be able to write to `work` |
| `BIND_HOST` / `PORT` | Bind address and port; defaults to `127.0.0.1:21849` |
| `API_TOKEN` | HTTP MCP Bearer authentication; the health endpoint is exempt |
| `MCP_ALLOWED_HOSTS` | Allowed external IP addresses, hostnames, and ports |
| Origin / CORS | All origins are allowed; requests still require the Bearer token |

The entrypoint supervises Xvfb, Blender, and the HTTP server. If any process exits, the container stops and Compose restarts it. Inspect logs with `docker compose logs -f`.

To update a deployment, save any in-memory scene, then run:

```bash
docker compose up -d --build
```

Reconnect MCP clients and refresh their tool lists afterward.

## Testing

Unit tests and code checks do not require a GPU:

```bash
uv sync --locked
uv run --locked ruff check blender_server tests scripts
uv run --locked pytest -q
```

To test the native tools, addon, scene operations, screenshots, and GPU rendering against a configured deployment:

```bash
python3 scripts/native_smoke.py
```

The smoke test reads the local client configuration, creates `MCP_E2E_Cube`, and writes `work/native-viewport.png`, `work/native-render.png`, and `work/native-e2e.blend`. It modifies the active scene, so use a test instance.

The previous custom rendering API remains in `blender_server/app.py`, but the current image starts the native MCP service. The old `/jobs` endpoint and `scripts/smoke.py` are not used by the current deployment.

## Prebuilt image and publishing

The image is available from GHCR:

```text
ghcr.io/battlehawk00/blender-server-mcp-docker:edge
```

Set it in `.env`, then start without a local build:

```dotenv
BLENDER_SERVER_IMAGE=ghcr.io/battlehawk00/blender-server-mcp-docker:edge
```

```bash
docker compose pull
docker compose up -d --no-build
```

The prebuilt image uses UID/GID 1000. Make sure `work/` is writable by that user. `APP_UID` and `APP_GID` affect local builds only.

The [container image workflow](.github/workflows/image.yml) runs for pushes to `main`, `v*` tags, and manual dispatches. Pull requests build the image for validation without logging in or pushing. Unit tests and lint must pass before publication. Only `linux/amd64` is currently supported.

| Trigger | Image tags |
| --- | --- |
| Push to `main` | `main`, `edge`, `sha-<commit>` |
| Tag `v1.2.3` | `1.2.3`, `1.2`, `latest`, `sha-<commit>` |
| Tag `v1.2.3-rc.1` | `1.2.3-rc.1`, `sha-<commit>`; does not update `latest` |
| Manual dispatch | Current branch or version tag, plus `sha-<commit>` |

The workflow uses the repository's `GITHUB_TOKEN`; no registry secret is required. Pulling a private image requires GHCR authentication. Use `edge` before the first stable release; only stable version tags produce `latest`.

## Repository layout and contributing

- `blender_server/`: service entrypoint, file/session management, and retained REST rendering implementation.
- `vendor/blender-mcp/`: pinned upstream MCP server and addon, patch notes, and license.
- `tests/`: unit tests that do not require Blender or a GPU.
- `scripts/`: smoke tests for a live deployment.
- `.github/`: CI, image build, and repository maintenance configuration.

See [CONTRIBUTING.md](CONTRIBUTING.md) for development and release instructions, [SECURITY.md](SECURITY.md) for the security model, and [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for third-party licenses.