touchdesigner-mcp
by mrinalghosh
README.md
# touchdesigner-mcp
Model Context Protocol (MCP) server that lets Claude drive a running TouchDesigner instance — create operators, wire them together, set parameters, run arbitrary Python, and introspect the `td` API.
## Architecture
```
Claude (MCP client) ──stdio──► touchdesigner-mcp (Python) ──HTTP POST──► Web Server DAT ──► td.run() ──► main-thread eval/exec
```
The MCP server is a thin stdio bridge. All TD mutation happens on TouchDesigner's main thread via a Web Server DAT callback that `exec`s or `eval`s the code you send.
## Prerequisites
- TouchDesigner (any recent 2022+ build — tools introspect the live API)
- Python 3.10+
- [`uv`](https://docs.astral.sh/uv/) or plain `pip`
## Install via .mcpb (recommended)
The `.mcpb` bundle is a single-file install for Claude Desktop. It vendors the Python deps and ships the TD-side `.tox` alongside, so both halves come from one file.
1. Build the bundle from the repo root:
```bash
bash scripts/build_mcpb.sh
# → built dist/touchdesigner-mcp.mcpb
```
2. Double-click [dist/touchdesigner-mcp.mcpb](dist/touchdesigner-mcp.mcpb). Claude Desktop opens a config form — defaults are fine for local TD (`TD_HOST=127.0.0.1`, `TD_PORT=9980`, `TD_PATH=/mcp`). Leave the multi-instance fields blank unless you're targeting more than one TD process. Click Install.
3. Continue with the TouchDesigner-side setup below, then skip the "MCP client configuration" section (the bundle wires that for you).
Requires `python3` on PATH (default on most macOS/Linux installs; install from python.org if missing).
## Install from source
```bash
git clone https://github.com/mrinalghosh/touchdesigner-mcp
cd touchdesigner-mcp
uv venv
source .venv/bin/activate
uv pip install -e .
```
This exposes a `touchdesigner-mcp` console script inside `.venv/bin/`.
## TouchDesigner-side setup (one-time, per .toe)
1. Open your project in TouchDesigner.
2. Drag [td_component/touchdesigner_mcp.tox](td_component/touchdesigner_mcp.tox) into `/project1` (or any persistent COMP). The component ships a pre-wired Web Server DAT on port `9980` with the callbacks already attached.
3. Save the .toe.
<details>
<summary>Manual setup (if you'd rather not use the .tox)</summary>
1. Inside `/project1` create a **Web Server DAT** (suggested name: `mcp_webserver`).
2. Set **Port** = `9980`, **Active** = On.
3. Replace the auto-created Callbacks DAT contents with [td_component/webserver_callbacks.py](td_component/webserver_callbacks.py).
4. Save the .toe.
</details>
Verify from a terminal:
```bash
curl -s -X POST http://127.0.0.1:9980/mcp \
-H 'content-type: application/json' \
-d '{"code": "_result = app.version", "mode": "exec"}'
# → {"ok": true, "result": "2023.xxxxx"}
```
Or run the packaged smoke test with TD open:
```bash
uv run python scripts/smoke_test.py
```
## MCP client configuration
### Claude Desktop
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) — single-instance form:
```json
{
"mcpServers": {
"touchdesigner": {
"command": "/absolute/path/to/touchdesigner-mcp/.venv/bin/touchdesigner-mcp",
"env": {
"TD_HOST": "127.0.0.1",
"TD_PORT": "9980"
}
}
}
}
```
Multi-instance form (target several TD processes from one Claude session):
```json
{
"mcpServers": {
"touchdesigner": {
"command": "/absolute/path/to/touchdesigner-mcp/.venv/bin/touchdesigner-mcp",
"env": {
"TD_INSTANCES": "main=127.0.0.1:9980,fx=127.0.0.1:9981,stage=192.168.1.40:9980",
"TD_DEFAULT_INSTANCE": "main"
}
}
}
}
```
Restart Claude Desktop after editing.
### Claude Code CLI
```bash
claude mcp add touchdesigner /absolute/path/to/touchdesigner-mcp/.venv/bin/touchdesigner-mcp \
--env TD_INSTANCES=main=127.0.0.1:9980,fx=127.0.0.1:9981 \
--env TD_DEFAULT_INSTANCE=main
```
See [claude_desktop_config.example.json](claude_desktop_config.example.json) for both forms in one place.
## Environment variables
| Var | Default | Purpose |
| --------------------- | ------------ | ------------------------------------------------------------------------------------ |
| `TD_HOST` | `127.0.0.1` | Single-instance host |
| `TD_PORT` | `9980` | Single-instance port |
| `TD_PATH` | `/mcp` | HTTP path the Web Server DAT answers on |
| `TD_INSTANCES` | — | Multi-instance map: `name=host:port[/path],...` (overrides the single-instance vars) |
| `TD_DEFAULT_INSTANCE` | first in map | Which instance unqualified tool calls target |
| `TD_TIMEOUT` | `10.0` | HTTP timeout in seconds |
Per-instance path override: `TD_INSTANCES="main=127.0.0.1:9980/mcp,dev=127.0.0.1:9981/mcp-dev"`.
## Tools
Every tool accepts an optional `instance` argument to target a specific TD process. Omit it to hit `TD_DEFAULT_INSTANCE`.
**Meta**
- `list_instances` — show configured TD processes
- `ping` / `ping_all` — health check
**Arbitrary code**
- `exec_python(code)` — runs on TD's main thread; assign to `_result` to return a value
- `eval_python(expression)` — single-expression eval
**Node lifecycle**
- `create_operator(parent_path, op_type, name)`
- `delete_operator(path)`
- `rename_operator(path, new_name)`
- `move_operator(path, x, y)`
**Parameters**
- `set_parameter(path, param, value)`
- `get_parameter(path, param)`
- `list_parameters(path)`
- `pulse_parameter(path, param)`
- `bind_parameter_expression(path, param, expression)` — switch a parameter to Expression mode with verification (reports any eval exception or new op error)
**Wiring**
- `connect_operators(source_path, target_path, source_output=0, target_input=0)`
- `disconnect_input(path, input_index=0)`
**Query**
- `list_children(comp_path)`
- `find_operators(root_path='/', op_type=None, name_pattern=None, depth=4)`
- `get_errors(path='/', recurse=True)`
**Templates** (multi-op recipes that encode known gotchas)
- `list_templates` — discover available templates and their options
- `create_from_template(template, parent_path, name_prefix, options=None)` — instantiate one. Ships with `chop_source_with_null`, `glsl_top_vec4_uniform`, `audio_in_with_analyze`, `feedback_loop_top`, `render_pipeline`.
**Introspection** (lets the model discover the live `td` API rather than guessing)
- `get_td_info` — version, project, Python, platform
- `get_td_classes(name_contains=None)`
- `get_td_class_details(class_name)`
- `get_module_help(name)`
## Example prompts
> "Under `/project1`, create a Noise TOP called `n1` and a Level TOP called `lvl1`, wire n1 → lvl1, then set `lvl1.Brightness` to 0.6."
> "List every TOP under `/project1` and report which ones have errors."
> "Show me `td.noiseTOP`'s parameters so I know what I can tweak."
## Security
The Web Server DAT callback executes **arbitrary Python** against your live project. Bind it to `127.0.0.1` only, never expose the port to the internet, and don't run untrusted prompts against a TD instance with valuable state open.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues