Skip to main content
Glama
README.md
# nuke-mcp

An [MCP](https://modelcontextprotocol.io) server for **Foundry Nuke**. It lets any MCP-capable agent (Claude, ChatGPT / Codex, Gemini and others) read and edit Nuke scripts, render Write nodes and look at the output of any node. It works two ways, chosen per call:

- **live**: the Nuke you have open. The agent sees and edits the same script as you, and every change is one undo step.
- **headless**: a fresh `nuke -t` process on a `.nk` file. Nothing has to be open; it uses one Nuke licence while it runs.

```
Agent (Claude / Codex / Gemini)
        │  MCP (stdio)
        ▼
  nuke-mcp  (this package, Python ≥ 3.11, outside Nuke)
        │                                   │
        │ live: JSON over TCP               │ headless: Nuke -t headless_runner.py
        │ 127.0.0.1:7393 + shared token     │ (job and result as JSON files)
        ▼                                   ▼
  nuke_mcp_bridge plugin (inside Nuke,   nuke_mcp_bridge.commands
  on Nuke's main thread)                 (the same code, the same commands)
```

Both backends run the **same** commands, so every tool behaves the same live and headless.

## Requirements

- Nuke 16 or 17 (tested on Nuke 17.1v2, macOS).
- [uv](https://docs.astral.sh/uv/) (`curl -LsSf https://astral.sh/uv/install.sh | sh`).

## Installation

### 1. Nuke plugin (once per machine)

Only needed for the live mode. Headless works without it.

```bash
uvx --from git+https://github.com/hramirezedc/nuke-mcp nuke-mcp install-plugin
```

This copies the `nuke_mcp_bridge` package into `~/.nuke/`, adds a marked block to `~/.nuke/init.py` (your own lines are kept) and creates the shared token in `~/.nuke_mcp/token`. Restart Nuke, then check both backends:

```bash
uvx --from git+https://github.com/hramirezedc/nuke-mcp nuke-mcp status
```

The listener starts in GUI sessions only; `nuke -t` never opens a socket. To skip it for one session, launch Nuke with `NUKE_MCP_DISABLE=1`. To remove it: `nuke-mcp uninstall-plugin`.

### 2. Register the server with your agent

In every case the command is `uvx --from git+https://github.com/hramirezedc/nuke-mcp nuke-mcp`. Desktop apps don't always inherit your shell `PATH`: if they can't find `uvx`, use its full path (`which uvx`).

**Claude Desktop**: in `~/Library/Application Support/Claude/claude_desktop_config.json`

```json
{
  "mcpServers": {
    "nuke": {
      "command": "/Users/YOU/.local/bin/uvx",
      "args": ["--from", "git+https://github.com/hramirezedc/nuke-mcp", "nuke-mcp"]
    }
  }
}
```

**Claude Code**

```bash
claude mcp add nuke -- uvx --from git+https://github.com/hramirezedc/nuke-mcp nuke-mcp
```

**OpenAI Codex CLI**: in `~/.codex/config.toml`

```toml
[mcp_servers.nuke]
command = "uvx"
args = ["--from", "git+https://github.com/hramirezedc/nuke-mcp", "nuke-mcp"]
```

**Gemini CLI**: in `~/.gemini/settings.json`

```json
{
  "mcpServers": {
    "nuke": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/hramirezedc/nuke-mcp", "nuke-mcp"]
    }
  }
}
```

Restart the app after editing its configuration.

## Live or headless?

Every tool that works on a script takes `script` (a `.nk` path) and `mode`:

| `mode` | What happens |
|---|---|
| `auto` (default) | Live if Nuke is open and has `script` open (or no `script` was given). Otherwise headless on `script`. |
| `live` | Live only. Error if Nuke is not open, or has another script open. |
| `headless` | Always a new `nuke -t` process on `script`. |

- **Headless edits** are saved back to the file, and the previous version is kept as `<name>.nk.bak`.
- **`render`** runs headless by default, so the Nuke window never freezes. With no `script` it renders a snapshot of the live script, **including unsaved changes**, and deletes the snapshot afterwards. `mode="live"` renders inside the open Nuke instead. Either way it checks that every expected frame exists on disk.
- `open_script`, `save_script`, `undo`, `redo`, `get_selection` and `set_selection` only exist in a live session.
- Apart from `render`, a headless run does not see the unsaved changes of your open session.

Headless uses the newest Nuke it finds in `/Applications`. Choose a version with `NUKE_MCP_NUKE_VERSION=17.0`, or an executable with `NUKE_MCP_EXE`.

## Tools

| Area | Tools |
|---|---|
| Application and script | `get_app_info`, `get_script_info`, `open_script`, `save_script`, `undo`, `redo`, `execute_python` |
| Nodes | `list_nodes`, `get_node`, `create_node`, `set_knobs`, `connect_node`, `delete_nodes`, `get_selection`, `set_selection` |
| Output | `render`, `preview_frame` (returns an image of any node at any frame) |

Live changes are **one undo step** each, cancelled again if the command fails half way. `delete_nodes` deletes nothing if one name is wrong. Tools that delete or overwrite carry `destructiveHint`, so agents ask before using them.

Knob values: numbers, strings and booleans; lists for multi-value knobs (`[r, g, b, a]`); `"=expr"` or `{"expression": "…"}` for expressions; `{"keyframes": {"1": 0, "24": 1}}` for animation; `{"script": "…"}` for Nuke's own text form. A Read's `file` accepts `"name.####.exr 1001-1100"`.

## Configuration

| Variable | Used by | Default |
|---|---|---|
| `NUKE_MCP_PORT` / `NUKE_MCP_HOST` | Nuke and server | `7393` / `127.0.0.1` |
| `NUKE_MCP_TOKEN` | Nuke and server | contents of `~/.nuke_mcp/token` |
| `NUKE_MCP_HOME` | Nuke and server | `~/.nuke_mcp` |
| `NUKE_MCP_DISABLE` | Nuke | unset |
| `NUKE_MCP_EXE` / `NUKE_MCP_NUKE_VERSION` | server (headless) | newest installed Nuke |

The default port differs from [hiero-mcp](https://github.com/toolbox-design-ia/hiero-mcp) (7392), so both can run at once. Only one Nuke GUI can hold the port: a second one prints a message and runs without the listener.

## Development

```bash
uv sync
uv run pytest                          # offline tests, no Nuke needed
uv run python scripts/live_check.py    # with Nuke open: runs every tool in a throw-away script
```

The offline tests run the real listener, client, MCP server and headless runner against `tests/fake_nuke.py`, a small stand-in for the `nuke` module (and a fake `Nuke -t` executable). The live check needs Nuke open with no unsaved changes: it opens a script in a temporary folder and works only there.

`src/nuke_mcp/server.py` defines the MCP tools. `src/nuke_mcp/nuke_plugin/nuke_mcp_bridge/commands.py` implements them with the Nuke API. Each tool has a command with the same name and parameters, and the tests check that they stay in sync. After changing the plugin, run `nuke-mcp install-plugin` and restart Nuke.

## License

Freeware by Toolbox Design, all rights reserved: free to use, including commercially, but not to modify, redistribute or sell. See [LICENSE](LICENSE). Foundry and Nuke are trademarks of their respective owners; this project is not affiliated with Foundry.

TDQS

B3.4/5.0

Scored across 17 tools

Disambiguation4/5

Each tool maps to a distinct resource+action: node listing/inspection (list_nodes vs get_node) is differentiated by the knobs/dependents detail, and app vs script info are clearly scoped. The only real overlap is execute_python acting as a superset of every other tool, plus mild adjacency between set_knobs and create_node's knob arguments, but these are conventional escape hatches rather than true ambiguity.

Naming Consistency4/5

The dominant pattern is verb_noun (get_app_info, list_nodes, create_node, set_knobs, delete_nodes, set_selection), applied consistently with correct singular/plural semantics. Deviations are the bare verbs undo/redo and the noun-ish render/preview_frame, which are idiomatic for a DCC app and remain readable.

Tool Count4/5

17 tools is slightly above the comfortable band but each earns its place across session control, node graph editing, selection, and rendering. Nothing looks like a redundant wrapper, though consolidation (e.g. selection get/set) could trim it a little.

Completeness4/5

Coverage is strong: script lifecycle (open/save), undo/redo, node CRUD, knob edits, graph wiring, selection, render verification, and a Python escape hatch for anything missing. Minor gaps remain around creating a new script from scratch, cloning/grouping nodes, and closing/reverting the session, all of which execute_python can work around.