Open Models MCP for Blender Using Ollama
SaferSkills independently audited blender-open-mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Local-first Model Context Protocol (MCP) server for controlling a live Blender session from AI agents, with a provider-agnostic LLM backend:
Groq, Together, Azure AI Foundry via its OpenAI-compatible surface, …)
POST /chat/completions with an optional base URLand 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)| 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) |
Requires Python ≥ 3.10.
python -m venv .venv
.venv/Scripts/pip install -e ".[dev]" # Windows (bash/PowerShell)
# or: source .venv/bin/activate && pip install -e ".[dev]" # macOS/LinuxEdit → Preferences → Add-ons → Install…, choose addon.py.N, open the Blender MCP tab and clickStart MCP Server (listens on localhost:9876 by default).
Default LLM backend is Ollama:
blender-mcp # Ollama at http://localhost:11434, model llama3.2Or pick a different backend at startup:
# 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.
Point your MCP client at http://localhost:8000/mcp (streamable HTTP) or run blender-mcp --transport stdio.
Example Claude/Cursor-style config:
{
"mcpServers": {
"blender": {
"url": "http://localhost:8000/mcp"
}
}
}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-callprovider/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 orOpenAI-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.
# 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:
Azure OpenAI or AI Foundry project) and take the short name from its endpoint URL: https://<resource>.openai.azure.com/....
gpt-4o-mini.This is what you pass as model (it is not the base model name).
2024-06-01 (the adapter defaults to it).Switch to Azure at runtime with a single call (CLI form):
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:
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:
base_url entirely (the placeholderhttps://RESOURCE.openai.azure.com is filled in from extra.resource) or pass the full base URL explicitly.
*.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".
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
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:
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
.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.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.