Skip to main content
Glama
graysonchalmers

Material Maker MCP

README.md
# Material Maker MCP

> Part of [gProdDevKit](https://kit.graysonchalmers.com), Grayson Chalmers' game production / dev kit.

<p align="center">
  <img src="docs/images/hero.png" alt="Cobblestone, marble, and raw-crystal materials authored by the server and rendered in 3D" width="100%">
</p>

An [MCP](https://modelcontextprotocol.io) server that lets an AI assistant
author [Material Maker](https://github.com/RodZill4/material-maker) node graphs
from natural language, render them headlessly to PBR texture maps, and hand back
editable `.ptex` files you finish in the app.

You describe a material in a sentence. The assistant drafts the node graph,
the server validates it against Material Maker's own node catalog and renders it
with Godot, and you get back the maps plus an editable graph. The assistant gets
you most of the way there; you tweak the rest in Material Maker.

Why this project exists and what it's actually optimizing for is in
[docs/NORTH_STAR.md](docs/NORTH_STAR.md).

> ## ⚠️ Super, super, super alpha. Read this first.
>
> I am an artist and animator in the game industry, not a software engineer. I do
> not really know what I am doing on the code side. This project was built mostly
> by AI assistants with me steering, and it is at an extremely early, rough,
> experimental stage.
>
> What that means for you:
> - **Expect breakage.** Rough edges, sharp corners, things that only work on the
>   one machine they were built on. It has been verified on exactly one setup
>   (Windows, a specific Godot build, a specific Material Maker checkout).
> - **No stability promises.** Anything can change or break between versions. The
>   unit suite and CI only prove the code behaves on the setup above; nothing
>   here has been exercised by a second person or a second machine.
> - **Not production-ready.** Please do not rely on this for anything that matters.
>   Back up your work. Assume it will misbehave.
> - **The material "quality" bar is deliberately low.** The goal is "gets you 80%
>   of the way there so you finish in the app," not "photoreal." See the
>   [Phase-3 scorecard](docs/evidence/phase3/2026-08-26-iter1.md) for exactly how well (and badly) it does on 15 prompts:
>   it currently passes all 15 of them by a generous, artist's eyeball
>   standard (the ship gate was 11 of 15).
>
> I am sharing it in the open because it is a fun experiment and someone might find
> it useful or want to build on it, not because it is polished. Feedback, issues,
> and "you're doing this wrong" corrections from actual developers are very
> welcome.

## Gallery

Each material below was authored by the server, then its rendered maps were
composited onto a sphere, a rounded-bevel cube, and a lathed chess rook on a
lit ground plane, so the normal-map relief reads under real lighting instead of
as a flat swatch. Full graphs live in the cookbook below (`s07_cobblestone`,
`s09_ashlar_wall`, `s11_marble`, `gl04_raw_crystal_cluster`,
`sf02_hazard_stripe_panel`, `f07_herringbone_tweed`, `t05_cracked_ice`,
`t08_riverbed_pebbles`).

| | |
|:--:|:--:|
| ![irregular cobblestone](docs/images/gallery/s07_cobblestone.png) | ![coursed ashlar stone wall](docs/images/gallery/s09_ashlar_wall.png) |
| ![veined marble](docs/images/gallery/s11_marble.png) | ![raw purple crystal cluster](docs/images/gallery/gl04_raw_crystal_cluster.png) |
| ![yellow and black hazard stripe panel](docs/images/gallery/sf02_hazard_stripe_panel.png) | ![herringbone tweed fabric](docs/images/gallery/f07_herringbone_tweed.png) |
| ![cracked ice](docs/images/gallery/t05_cracked_ice.png) | ![riverbed pebbles](docs/images/gallery/t08_riverbed_pebbles.png) |

### In motion

The static frame hides how relief plays with light. These sweep the key light
across five materials so the normal-map depth reads as it moves.

| | |
|:--:|:--:|
| ![cobblestone under a moving light](docs/images/gallery/s07_cobblestone.gif) | ![raw crystal under a moving light](docs/images/gallery/gl04_raw_crystal_cluster.gif) |
| ![cracked ice under a moving light](docs/images/gallery/t05_cracked_ice.gif) | ![hazard stripe panel under a moving light](docs/images/gallery/sf02_hazard_stripe_panel.gif) |
| ![ashlar wall under a moving light](docs/images/gallery/s09_ashlar_wall.gif) | |

## Material cookbook

The cookbook is 74 materials across 12 categories (the gallery above is
drawn from it), each one a real graph this server authored and then locked
after a 3D-preview pass. Every one ships as a tracked `.ptex` under
[`cookbook/`](cookbook/): open `cookbook/<category>/<id>.ptex` in Material
Maker to see the node network, or start from it over MCP with
`load_example("f07_herringbone_tweed")`. The invariants that apply across
materials are in [docs/AUTHORING.md](docs/AUTHORING.md), also served as the
`guide://authoring` MCP resource; the recipe for each material lives next to
its graph as `cookbook/<category>/<id>.md`. The builders that regenerate the
graphs live in [`quality/`](quality/).

**The full cookbook (74 materials:** ceramic, fabrics, glass, leather, metal, organics, painted metal, plastics, sci-fi, stone, terrain, wood)

<p align="center">
  <img src="docs/images/cookbook-contact-sheet.png" alt="Contact sheet of all 74 cookbook materials across 12 categories" width="100%">
</p>

## Core toolbox

Below the finished cookbook materials sit the single-node building blocks
they are made from: two galleries that isolate ONE node at a time so you see
its raw, unmixed behavior before it gets composited into anything.

**Debug swatches.** 19 single-node debug swatches, each wiring exactly one
node straight into a Material so what you see IS that node's behavior, no
recipe, no blend, nothing to misread. Every swatch also doubles as a live
pixel-assertion regression test (`tests/test_debug_swatches.py` renders it
fresh and checks known-answer pixels), so a wiring regression fails a test
instead of waiting for a human to notice a material looks wrong. One swatch,
`slope_blur`, ships structure-only: it is a buffer/compute-shader node that
cannot render headless, so its tile is black by design, not broken. Legend
and known-answers for every swatch are in
[docs/DEBUG_SWATCHES.md](docs/DEBUG_SWATCHES.md).

<p align="center">
  <img src="docs/images/core-toolbox/swatches.png" alt="Contact sheet of the 19 debug swatches, one node isolated per tile" width="100%">
</p>

**Noise vocabulary.** A gallery of base noise/pattern nodes beyond the two
(`perlin`, `voronoi`) the cookbook leaned on early: `fbm`'s 8 base functions
side by side, plus a cross-family row (anisotropic, truchet, voronoi
triangle, wavelet, and more) showing how differently they read. Full
writeup, including the "the catalog carries 47 noise nodes, the cookbook
effectively used two" problem this was built to fix, is in the
[Noise vocabulary](docs/AUTHORING.md#noise-vocabulary-reach-past-voronoi--perlin)
section of `docs/AUTHORING.md`.

<p align="center">
  <img src="docs/images/noise-gallery/fbm-bases.png" alt="fbm noise node's 8 base functions rendered side by side" width="100%">
</p>

## How it works

Material Maker graphs are plain JSON (`.ptex`), and Material Maker ships a
headless CLI export mode. This server sits on top of an existing Material Maker
checkout:

1. A catalog builder reads Material Maker's node definitions
   (`addons/material_maker/nodes/*.mmg`) into a machine-readable catalog the
   assistant authors against.
2. The assistant drafts a graph as `.ptex` JSON. The server validates it against
   the catalog (returning errors as data so the assistant can self-correct), then
   renders it by driving Godot's `--export-material` mode.
3. Rendered maps come back as image files (albedo, normal, roughness/metallic,
   height), and the `.ptex` is saved for you to open in Material Maker.

## Requirements

- **Python 3.10+** (developed and verified on 3.13; older versions declared but
  not exercised)
- **Windows** is the only fully verified platform. The render runner falls back
  to the plain Godot binary on macOS/Linux, but that path is untested.
- **Godot 4.7.x** (the standard desktop binary; the server prefers the matching
  `_console.exe` build on Windows when present, to capture render logs)
- **A Material Maker project checkout** on disk. The server reads that checkout's
  node definitions and bundled examples and drives its headless export. Clone it
  from [github.com/RodZill4/material-maker](https://github.com/RodZill4/material-maker).
  Material Maker needs a `steam_appid.txt` (containing `4110830`) at the checkout
  root, or the app self-relaunches and exits on headless render. The upstream
  repo does not ship this file; create it yourself before your first render
  (`echo 4110830 > steam_appid.txt` at the checkout root).

## Install

Install from a clone (the supported path for now):

```bash
git clone https://github.com/graysonchalmers/Tool-MaterialMaker-MCP.git
cd Tool-MaterialMaker-MCP
python -m venv .venv
# Windows:  .\.venv\Scripts\activate
# macOS/Linux:  source .venv/bin/activate
pip install -e .
cp .env.example .env
```

Then edit `.env`:

```
MM_GODOT_BINARY=/path/to/Godot_v4.7.x_console.exe
MM_PROJECT_PATH=/path/to/material-maker
MM_OUTPUT_DIR=/path/to/where/rendered/maps/should/go
```

`.env` is gitignored and read from the current working directory (or from the
path in `MM_DOTENV` if set). Config can also be supplied via `MM_*` environment
variables, which take precedence over `.env`. `MM_OUTPUT_DIR` is optional and
defaults to an `output/` folder in the working directory.

`MM_ALLOWED_ROOTS` is optional. When set (an `os.pathsep`-separated list of
directories), the server refuses to read or write paths outside those roots.
When unset (the default), paths are unrestricted. Either way, node/example
`name` and `basename` arguments are always rejected if they contain a path
separator or `..`.

`MM_COOKBOOK_DIR` is also optional. It points the server at a cookbook of
authored graphs (see [cookbook/README.md](cookbook/README.md)) and defaults
to the checkout's own `cookbook/` folder, so a git clone needs nothing set.
Set it only if you want the server to serve a cookbook from somewhere else.

Either way you get an `mm-mcp` command on your PATH. (A `pip install mm-mcp`
from PyPI is packaged and ready but not yet published; the clone above is the
current route.)

## Check your setup

Before wiring it into a client, confirm every prerequisite is in place:

```bash
mm-mcp --check
```

It prints a green/red checklist (Godot binary, Material Maker checkout, node
definitions, examples, `steam_appid.txt`, output dir, and a catalog build) and
exits non-zero if anything is missing, so you find problems before your MCP
client does. `mm-mcp --version` prints the version.

## Verify

Two smoke scripts prove the render path is alive end to end:

```bash
# Render a bundled example headlessly and confirm PNGs appear
python smoke/smoke_mcp.py
```

On Windows there is also a PowerShell smoke that renders directly through Godot:

```
pwsh smoke/smoke.ps1
```

Run the test suite (the one Godot-launching test is marked `integration`):

```bash
pytest -q -m "not integration"   # fast unit + validation tests
pytest -q                        # everything, including a real render
```

## Connect it to an MCP client

The server speaks MCP over stdio. After installing it is on your PATH as
`mm-mcp`. Point your client at that command with the `MM_*` variables set.

Claude Desktop / Claude Code (`claude_desktop_config.json` or an equivalent MCP
config) example:

```json
{
  "mcpServers": {
    "material-maker": {
      "command": "mm-mcp",
      "env": {
        "MM_GODOT_BINARY": "C:\\path\\to\\Godot_v4.7.1-stable_win64_console.exe",
        "MM_PROJECT_PATH": "C:\\path\\to\\material-maker",
        "MM_OUTPUT_DIR": "C:\\path\\to\\output",
        "MM_IDLE_EXIT_MINUTES": "120"
      }
    }
  }
}
```

If `mm-mcp` is not on the client's PATH, use the venv's Python instead:
`"command": "/abs/path/.venv/bin/python"`, `"args": ["-m", "mm_mcp.server"]`.

Config is validated at startup, so a missing or wrong `MM_GODOT_BINARY` /
`MM_PROJECT_PATH` fails fast with an actionable message rather than partway
through a render.

Because the server is typically registered at user scope, every client
session spawns its own `mm-mcp` process, and abandoned sessions leave their
server running indefinitely. Set `MM_IDLE_EXIT_MINUTES` to have the server
exit on its own after that many minutes with no tool call (0, the default,
means never). This is opt-in: Claude Code does not restart an exited stdio
server, so the next tool call in a long-idle session fails until the session
reconnects. That trade-off is worth it for cleaning up abandoned sessions,
but is not the right default for every client.

## Tools

The server exposes 11 batch-mode tools and two resources (plus 7 more in Live mode, below):

| Tool | What it does |
|---|---|
| `list_node_types` | List catalog node types, optionally filtered by a name substring |
| `describe_node` | Full typed inputs/outputs/parameters for one node type |
| `validate` | Validate a `.ptex` graph against the catalog; returns problems as data. Descends into subgraph (`graph`-typed) nodes, path-prefixing inner problems (e.g. `sub/inner`) |
| `render_graph` | Render a `.ptex` to PBR maps at a given size |
| `render_node_output` | Render one node's output in isolation, without editing the real graph |
| `render_preview` | Composite already-rendered maps onto a sphere/cube/cutaway-ball preview scene |
| `render_preview_sweep` | Optional: same preview scene, but sweeps the key light through a full 360-degree rotation and returns a looping GIF, for when a static preview leaves relief/normal-map depth ambiguous |
| `save_graph` | Write a `.ptex` graph to a path |
| `list_examples` | List starting graphs from both sources: Material Maker's bundled examples and this repo's `cookbook/` (filter with `source`) |
| `load_example` | Load one starting graph by name as a `.ptex` (cookbook first, then bundled) |
| `inspect_project` | Read-only metrics for a `.ptex` on disk (hash, node/connection counts, type histogram, material outputs) |

Resource `catalog://nodes` exposes the full node catalog. Resource
`guide://authoring` exposes the authoring guide (the invariants; see
"Material cookbook" above for the per-material recipe cards).

## Live mode (optional)

Batch mode above (`render_graph` et al.) is the default, simplest path: no
Material Maker GUI involved. Live mode is a second, additive way to work --
open Material Maker yourself, and Claude can see the graph on your active
tab, build and edit it live, and trigger renders, so you watch it happen in
the GUI instead of copying files back and forth.

| Tool | What it does |
|---|---|
| `live_start` | Attach to an already-open Material Maker, or launch it against a disposable overlay if nothing's listening |
| `live_get_graph` | Fetch the active tab's current graph, `.ptex`-shaped |
| `live_apply` | Apply a batch of validated mutations (`add_node`/`connect_nodes`/`disconnect_nodes`/`set_param`) to the live graph |
| `live_render` | Trigger a render in the live window, same result shape as `render_graph` |
| `live_render_node_output` | Render one node's output in isolation on the live graph, previewing then restoring the original wiring |
| `live_clear` | Reset the live graph to a single default Material node, discarding everything else |
| `live_load` | Replace the shown graph in place (no new tab) with a caller-supplied graph dict or `.ptex` path, validated against the catalog first; Claude can push an authored graph live, and the play surface uses it to push the picked material into a live session |

No manual setup beyond what batch mode already needs -- the addon ships in
this repo and builds its own disposable working copy on first use. Live
mode is turn-based, not simultaneous: there's no conflict resolution for
edits from both sides at once. See
[docs/superpowers/specs/2026-08-26-live-control-addon-design.md](docs/superpowers/specs/2026-08-26-live-control-addon-design.md)
for the full design.

## Play surface (optional)

`mm-play` is a small local web page for a non-technical person who wants to
tweak a cookbook material without touching a node graph: a gallery of the 74
cookbook materials, each opening to friendly sliders (derived from the
material's author-chosen subgraph parameters) with a WebGL sphere preview
that re-renders as you drag. It deliberately hides the node graph; it is a
companion for the secondary audience described in
[docs/NORTH_STAR.md](docs/NORTH_STAR.md), not a replacement for Material
Maker's UI or the core round-trip loop.

Launch it with:

```bash
mm-play
```

This starts a local server (default `http://127.0.0.1:8788/`, `MM_PLAY_PORT`
to change it). It works two ways: standalone and headless, driving the same
Godot render path as the MCP tools, with no Material Maker GUI needed; or, if
a live Material Maker session is already up, it drives that live session
instead and you can watch the parameter changes land in the GUI. Downloading
a result includes the real editable `.ptex`, so the play surface still hands
you a graph you can open in Material Maker, not just a flattened image.

## Notes and gotchas

Learned while getting headless rendering to work reliably (all verified on this
project's setup):

- Use `--export-material`, not `--export`. Godot 4 reserved `--export` for its
  own build-export flag; the Material Maker app flag is `--export-material`.
- Use the `_console.exe` binary on Windows to capture stdout; the GUI exe returns
  empty logs.
- Do **not** pass `--headless`; texture rendering needs a real rendering context.
- The Material Maker checkout needs `steam_appid.txt` (`4110830`) or it
  self-relaunches and exits immediately.
- The `normal_map` node is a compound node: its real parameters are `param0`
  (buffer size), `param1` (strength), `param2`, `param4`, not `amount`/`size`.

## Project status

Very early alpha (see the warning up top). Phases 0 through 3 and 5 of my own
rough plan are done and verified on one machine; Phase 4 (public packaging) is
partway there. See [STATUS.md](STATUS.md) for the gate ledger and
[docs/PLAN.md](docs/PLAN.md) for the phase plan. Authoring quality is
measured against a frozen 15-case test set archived in [`docs/evidence/phase3/`](docs/evidence/phase3/); the current
scorecard is 15/15 usable by an artist's eyeball standard (see
`docs/evidence/phase3/`). "Verified" here means "worked when I ran it," not
"battle-tested."

## License and attribution

This project is MIT licensed (see [LICENSE](LICENSE)).

Material Maker is MIT licensed, Copyright (c) Rodolphe Suescun and contributors.
This project drives a separate Material Maker checkout and does not modify or
redistribute it.