substance-painter-mcp

substance-painter-mcp

Enables conversational control of Adobe Substance 3D Painter for look-dev iteration, including baking, smart materials, and generator parameter tuning.

Category
Visit Server

README

substance-painter-mcp

An MCP server for driving Adobe Substance 3D Painter conversationally — bake mesh maps, stack smart materials, and tune generator parameters by talking to the app instead of guessing weights and re-baking.

Built for look-dev iteration, where weathering is authored by taste against a reference rather than by fixed numbers.

Requirements

  • Substance 3D Painter 12.x (developed against 12.1.1 / Python API 0.3.5), running
  • Python 3.10+ for the MCP server

Windows, macOS and Linux. No launch flags and no --enable-remote-scripting — Painter can be started normally. If you have no Python installed, Painter bundles a complete CPython 3.13 at <install>/resources/pythonsdk/python.exe that works fine.

Install

1. Install the bridge plugin into Painter

python scripts/install_plugin.py

This copies painter_plugin/substance_painter_mcp_bridge into Painter's user plugin folder, <Documents>/Adobe/Adobe Substance 3D Painter/python/plugins/. On Windows the Documents location is read from the shell-folder registry, so OneDrive redirection and moved Documents folders are handled; pass --plugins-dir to override.

Use --link to symlink instead of copy while developing (needs Developer Mode or an elevated shell on Windows), and --uninstall to remove it.

2. Enable it in Painter

Python → Reload Plugins Folder, then tick substance_painter_mcp_bridge under Python → Plugins.

This step is required. Painter treats everything in python/plugins/ as an optional component: modules are discovered but not started until enabled. Only python/startup/ loads automatically. Painter remembers the choice, so this is one-time — but a fresh install that skips it will simply never open the port.

Prefer no UI step? python scripts/install_plugin.py --startup installs into startup/ instead, which always loads, at the cost of losing the on/off toggle.

Either way the log window should show:

[substance-painter-mcp] listening on 127.0.0.1:60043

3. Install the MCP server

python -m venv .venv
.venv/Scripts/python -m pip install -e .     # .venv/bin/python on macOS/Linux

4. Register it with your MCP client

{
  "mcpServers": {
    "substance-painter": {
      "command": "C:/Users/you/code/substance-painter-mcp/.venv/Scripts/python.exe",
      "args": ["-m", "substance_painter_mcp"]
    }
  }
}

Then call painter_status — it distinguishes "Painter isn't running" from "that operation was invalid", so it's the right first thing to try when something fails.

Configuration

Variable Default Meaning
SP_MCP_BRIDGE_HOST 127.0.0.1 Bind/connect address
SP_MCP_BRIDGE_PORT 60043 Bridge port
SP_MCP_BRIDGE_TIMEOUT 600 Client socket timeout, seconds

The bridge binds to loopback only. It executes arbitrary Python inside Painter by design (painter_exec), so do not expose the port beyond localhost.

Tools

Tool Purpose
painter_status Reachability and version check
painter_project_info / painter_create_project / painter_open_project / painter_save_project Project lifecycle
painter_search_resources Find smart materials, generators, masks, export presets
painter_get_baking_parameters / painter_set_baking_parameters Inspect and set bake settings, including high-poly path and cage distance
painter_bake / painter_baking_status Bake mesh maps (async, with optional wait)
painter_layer_tree Read the layer stack; every node has a uid
painter_insert_smart_material Instantiate a shelf smart material
painter_insert_layer / painter_insert_generator / painter_add_mask Build stacks
painter_set_fill_source / painter_import_resource Assign a bitmap or flat colour to a fill layer's channel
painter_set_layer / painter_delete_layer Name, visibility, opacity, blending mode; delete
painter_screenshot Capture the viewport — see the result instead of inferring it
painter_get_parameters / painter_set_parameters / painter_apply_preset Tune generators — the look-dev loop
painter_list_export_presets / painter_export_textures Export maps
painter_list_ops / painter_call / painter_exec Escape hatches

The tuning loop

painter_get_parameters is the tool that makes conversational iteration work. It returns each parameter's key, human label, widget type, current value and enum options, so the model discovers that a rust generator exposes Dirt Level rather than guessing at names:

painter_layer_tree()                        → find the generator's uid
painter_get_parameters(uid=42)              → see what knobs exist
painter_set_parameters(uid=42, '{"Dirt Level": 0.7}')
painter_screenshot()                        → look at what that did

painter_screenshot closes the loop. Without it the model is editing blind and has to trust that a parameter did what it claims; with it, each change can actually be checked.

A smart material is a group of layers, so tuning one means walking into the group and adjusting the generator effects inside it. painter_layer_tree returns the full hierarchy with uids in a single call for that reason.

Wear is driven by the baked mesh maps via generators — mg_metal_edge_wear for edges and bolts, mg_dirt for crevices, mg_position multiplied into a mask to bias wear vertically. Mask effects blend with each other, which is how "heavier down low, lighter up top" gets expressed.

How it works

Claude  ──stdio──>  MCP server (this repo, any Python)
                          │
                          │  newline-delimited JSON over TCP, loopback only
                          ▼
                    Bridge plugin (runs inside Painter, Qt server on the main thread)
                          │
                          ▼
                    substance_painter Python API

The bridge is a Painter plugin rather than the --enable-remote-scripting HTTP endpoint, for two reasons:

  1. No launch flag. Painter can be started normally, including from Creative Cloud.
  2. Thread affinity. The substance_painter API is only safe to call from the main/UI thread. A Qt QTcpServer created inside the plugin lives on Painter's own event loop, so every request handler already runs on the right thread — no cross-thread marshalling and no temp-file result passing.

Wire protocol

Newline-delimited JSON, one object per line:

→ {"id": 1, "op": "layers.tree", "params": {"deep": true}}
← {"id": 1, "ok": true, "result": {...}}
← {"id": 1, "ok": false, "error": {"kind": "no_project", "message": "..."}}

json.dumps escapes newlines inside strings, so a serialised message never contains a raw newline of its own and the framing is safe.

Development

.venv/Scripts/python -m pytest      # protocol tests, no Painter needed
.venv/Scripts/python -m ruff check .

tests/ runs the bridge client against a fake NDJSON server, so framing, error mapping and reconnect-after-plugin-reload are covered without the application installed. Anything touching the substance_painter API needs Painter running.

Against a running Painter:

python scripts/smoke_test.py                      # exercise the bridge end to end
python scripts/reload_plugin.py                   # hot-reload after editing the plugin
python scripts/smoke_test.py --mesh mesh.fbx --force   # also create a project

reload_plugin.py avoids a Painter restart on every edit. Install the plugin with --link so the files are already in place and only the reload is needed.

Notes and limitations

  • Baking is asynchronous. API 0.3.5 has no synchronous bake(), only bake_async. The bridge tracks progress through Painter's BakingProcessProgress / BakingProcessEnded events and painter_bake polls to completion by default.
  • Cage distance matters on meshes with overlapping shells. Cage distances are relative to the bounding box by default, and a generous cage samples interior surfaces and produces black patches. Read the real key names with painter_get_baking_parameters before setting them — they come from Painter, not from this server.
  • One bake at a time. The bridge rejects a second concurrent bake.

See docs/api-findings.md for what was verified against the installed SDK, including several silent-failure modes worth knowing about.

License

MIT — see LICENSE.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured
E2B

E2B

Using MCP to run code via e2b.

Official
Featured