Skip to main content
Glama
jwang47

substance-painter-mcp

by jwang47

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.

Related MCP server: Maya MCP Server

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.

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jwang47/substance-painter-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server