Skip to main content
Glama
README.md
# Blender Open MCP

Local-first **Model Context Protocol (MCP)** server for controlling a live
Blender session from AI agents, with a **provider-agnostic LLM backend**:

- **Ollama**
- **LM Studio**
- **llama.cpp** server
- **OpenAI** (and every OpenAI-compatible endpoint: vLLM, TGI, OpenRouter,
  Groq, Together, Azure AI Foundry via its OpenAI-compatible surface, …)
- any server that speaks `POST /chat/completions` with an optional base URL
  and API key

Providers are configured at startup (CLI flags or environment variables) and
can be **switched at runtime** through MCP tools, so an agent can hop between
backends without restarting the server.

```
MCP Client ──▶ FastMCP Server ──▶ (TCP 9876) ──▶ Blender add-on (addon.py) ──▶ bpy
                  │
                  └──────────────▶ (HTTP) ──▶ LLM provider (Ollama / LM Studio /
                                               llama.cpp / OpenAI-compatible / Azure)
```

---

## Repository layout

| Path | Purpose |
| --- | --- |
| `addon.py` | Single-file Blender add-on: TCP server + scene/render/PolyHaven handlers + sidebar panel |
| `src/blender_open_mcp/server.py` | MCP server: Blender tools, PolyHaven tools, LLM prompt + provider tools |
| `src/blender_open_mcp/llm.py` | Provider-agnostic LLM layer (registry, adapters, model listing) |
| `src/blender_open_mcp/client/` | Canonical async MCP client + CLI |
| `client/` | Thin compat wrapper so `from client import …` keeps working from source |
| `tests/` | pytest suites (server, client, addon) |

---

## Quick start

### 1. Install

Requires Python ≥ 3.10.

```bash
python -m venv .venv
.venv/Scripts/pip install -e ".[dev]"   # Windows (bash/PowerShell)
# or: source .venv/bin/activate && pip install -e ".[dev]"   # macOS/Linux
```

### 2. Enable the Blender add-on

1. Open Blender.
2. `Edit → Preferences → Add-ons → Install…`, choose `addon.py`.
3. Enable **Blender MCP**.
4. In the 3D Viewport press `N`, open the **Blender MCP** tab and click
   **Start MCP Server** (listens on `localhost:9876` by default).

### 3. Start the MCP server

Default LLM backend is Ollama:

```bash
blender-mcp                          # Ollama at http://localhost:11434, model llama3.2
```

Or pick a different backend at startup:

```bash
# LM Studio (OpenAI-compatible, default localhost:1234/v1)
blender-mcp --llm-provider lmstudio --llm-model "local-model"

# llama.cpp server (default localhost:8080/v1)
blender-mcp --llm-provider llamacpp --llm-model qwen2.5-coder

# OpenAI-compatible generic endpoint
blender-mcp --llm-provider openai_compat --llm-base-url http://my-server:8000/v1 \
            --llm-api-key sk-... --llm-model my-model

# OpenAI
blender-mcp --llm-provider openai --llm-api-key "$OPENAI_API_KEY" --llm-model gpt-4o-mini

# Azure AI Foundry / Azure OpenAI
blender-mcp --llm-provider azure --llm-api-key "$AZURE_API_KEY" \
            --llm-base-url https://my-resource.openai.azure.com \
            --llm-model my-deployment \
            --llm-extra '{"resource":"my-resource","deployment":"my-deployment","api_version":"2024-06-01"}'
```

Environment variables are honored: `BLENDER_OPEN_MCP_PROVIDER`,
`BLENDER_OPEN_MCP_BASE_URL`, `BLENDER_OPEN_MCP_MODEL`,
`BLENDER_OPEN_MCP_API_KEY`.

Other useful flags: `--host`, `--port` (MCP endpoint, default `0.0.0.0:8000`),
`--blender-host`, `--blender-port`, `--transport streamable_http|http|stdio`.

### 4. Register with an MCP client

Point your MCP client at `http://localhost:8000/mcp` (streamable HTTP) or run
`blender-mcp --transport stdio`.

Example Claude/Cursor-style config:

```json
{
  "mcpServers": {
    "blender": {
      "url": "http://localhost:8000/mcp"
    }
  }
}
```

---

## Available MCP tools

**Scene / object control** (forwarded to the Blender add-on over TCP):
`blender_get_scene_info`, `blender_get_object_info`, `blender_create_object`,
`blender_modify_object`, `blender_delete_object`, `blender_set_material`,
`blender_render_image`, `blender_execute_code`.

**PolyHaven assets:** `blender_get_polyhaven_categories`,
`blender_search_polyhaven_assets`, `blender_download_polyhaven_asset`,
`blender_set_texture`.

**LLM / provider control:**
- `blender_ai_prompt` – send a prompt to the active backend (per-call
  `provider`/`base_url`/`model`/`api_key` overrides supported).
- `blender_get_llm_provider` – show active provider config (API key masked).
- `blender_set_llm_provider` – switch/configure the backend at runtime.
- `blender_list_llm_models` – list models (Ollama `/api/tags` or
  OpenAI-compatible `/models`).

**Legacy aliases:** `blender_set_ollama_model`, `blender_set_ollama_url`,
`blender_get_ollama_models` keep old Ollama-only clients working.

**MCP Prompts** (`prompts/list` / `prompts/get`):
- `blender_build_scene` – guided plan for building a scene from a description.
- `blender_review_scene` – read-only inspection workflow.
- `blender_configure_llm` – provider-switching instructions with examples.

Prompts are registered in `src/blender_open_mcp/prompts.py`.

### Runtime provider switching (examples)

```text
# Switch to LM Studio
tool blender_set_llm_provider {"provider":"lmstudio","base_url":"http://localhost:1234/v1","model":"local-model"}

# Switch to llama.cpp
tool blender_set_llm_provider {"provider":"llamacpp","base_url":"http://localhost:8080/v1"}

# Back to Ollama
tool blender_set_llm_provider {"provider":"ollama","base_url":"http://localhost:11434","model":"llama3.2"}
```

#### Azure AI Foundry (worked example)

Azure's OpenAI-compatible endpoint is deployment-scoped, so three values from
your Azure AI Foundry project are required — all of them go into the `extra`
parameter, and the model you name in `model` must match the deployment name:

1. **Resource name** — in the Azure portal, open your resource (e.g.
   *Azure OpenAI* or *AI Foundry project*) and take the short name from its
   endpoint URL: `https://<resource>.openai.azure.com/...`.
2. **Deployment name** — on the *Deployments* page, e.g. `gpt-4o-mini`.
   This is what you pass as `model` (it is *not* the base model name).
3. **API key** — on the resource's *Keys and Endpoint* page.
4. **API version** (optional) — e.g. `2024-06-01` (the adapter defaults to it).

Switch to Azure at runtime with a single call (CLI form):

```bash
blender-mcp-client --host http://localhost:8000 tool blender_set_llm_provider \
  '{"provider":"azure","api_key":"YOUR_AZURE_API_KEY","model":"gpt-4o-mini",' \
  '"extra":{"resource":"my-openai-resource","deployment":"gpt-4o-mini","api_version":"2024-06-01"}}'
```

The same call through the Python API:

```python
import asyncio
from blender_open_mcp.client.client import BlenderMCPClient

async def main():
    async with BlenderMCPClient("http://localhost:8000") as c:
        # Switch to Azure AI Foundry
        print(await c.set_llm_provider(
            provider="azure",
            api_key="YOUR_AZURE_API_KEY",
            model="gpt-4o-mini",
            extra={
                "resource": "my-openai-resource",
                "deployment": "gpt-4o-mini",
                "api_version": "2024-06-01",
            },
        ))
        # Confirm the active config (API key is masked)
        print(await c.get_llm_provider())
        # Use it
        print(await c.ai_prompt("Create a red cube at the origin"))

asyncio.run(main())
```

What the adapter does with those values — it builds the deployment-scoped
request and sends the key in the `api-key` header:

```
POST https://my-openai-resource.openai.azure.com/openai/deployments/gpt-4o-mini/chat/completions?api-version=2024-06-01
api-key: YOUR_AZURE_API_KEY
{"model": "gpt-4o-mini", "messages": [...], "stream": false}
```

Notes:
- You may omit `base_url` entirely (the placeholder
  `https://RESOURCE.openai.azure.com` is filled in from `extra.resource`) or
  pass the full base URL explicitly.
- If you use an AI Foundry **serverless model endpoint** (the
  `*.services.ai.azure.com/models` surface) instead of a deployment-scoped
  resource, point `provider` at the generic OpenAI-compatible adapter with the
  serverless base URL: `provider="openai_compat"`,
  `base_url="https://<resource>.services.ai.azure.com/models"`.
- The same configuration can be applied at startup instead of at runtime:

```bash
blender-mcp --llm-provider azure \
  --llm-api-key "$AZURE_API_KEY" \
  --llm-model gpt-4o-mini \
  --llm-extra '{"resource":"my-openai-resource","deployment":"gpt-4o-mini","api_version":"2024-06-01"}'
```
```

---

## Client CLI

```bash
blender-mcp-client --host http://localhost:8000 tools
blender-mcp-client --host http://localhost:8000 tool blender_get_scene_info
blender-mcp-client --host http://localhost:8000 tool blender_set_llm_provider '{"provider":"lmstudio"}'
blender-mcp-client --host http://localhost:8000 prompt "Create a metallic sphere at 0,0,2"
blender-mcp-client --host http://localhost:8000 interactive
```

As a library:

```python
import asyncio
from blender_open_mcp.client.client import BlenderMCPClient

async def main():
    async with BlenderMCPClient("http://localhost:8000") as c:
        print(await c.get_scene_info())
        await c.create_object("SPHERE", location=(0, 0, 2))
        print(await c.ai_prompt("What should I build next?"))

asyncio.run(main())
```

---

## Provider layer internals

`src/blender_open_mcp/llm.py` keeps a registry of provider specs and routes
every request through one chat helper:

- **OpenAI-style** providers post to `<base_url>/chat/completions` with a
  `Bearer` token when an API key is set, and parse
  `choices[0].message.content`.
- **Ollama** posts to `/api/chat` natively (or to its `/v1/chat/completions`
  surface when the base URL ends in `/v1`).
- **Azure** posts to
  `https://<resource>.openai.azure.com/openai/deployments/<deployment>/chat/completions?api-version=…`
  using the `api-key` header.
- **Model listing**: Ollama `/api/tags`, others `/models` (Azure deployments
  are managed in the portal and not listed).

Add new backends by inserting an entry in `PROVIDERS` (plus an alias in
`PROVIDER_ALIASES`); nothing else changes.

---

## Development

```bash
.venv/Scripts/python -m pytest tests -q
```

The suites mock `bpy` (addon tests), `httpx` (provider/PolyHaven routing),
and the MCP client wire format, so they run without Blender or a live LLM.

`tests/test_integration.py` additionally proves the layers work together:

- an in-process `fastmcp.Client` session lists tools/prompts and calls
  provider tools over the real MCP protocol;
- the actual `addon.py` TCP server loop is started on a local port (bpy
  mocked) and driven through the MCP server's Blender bridge, so
  `blender_get_scene_info` and `blender_execute_code` round-trip over a real
  socket;
- a parity test pins every bridge command to a registered addon handler and
  every `blender_*` tool to a registered MCP tool.

## Notes / known gaps

- Tests exercise the server without a live Blender; run them against a real
  Blender session to validate `addon.py` end to end.
- See `ARCHITECTURE.md` for the component diagram and `AGENTS.md` for
  contributor conventions.