TouchDesigner Integration (twozero MCP)
CRITICAL RULES
- NEVER guess parameter names. Call
td_get_par_infofor the op type FIRST. Your training data is wrong for TD 2025.32. - If
tdAttributeErrorfires, STOP. Calltd_get_operator_infoon the failing node before continuing. - NEVER hardcode absolute paths in script callbacks. Use
me.parent()/scriptOp.parent(). - Prefer native MCP tools over td_execute_python. Use
td_create_operator,td_set_operator_pars,td_get_errorsetc. Only fall back totd_execute_pythonfor complex multi-step logic. - Call
td_get_hintsbefore building. It returns patterns specific to the op type you're working with.
Architecture
Hermes Agent -> MCP (Streamable HTTP) -> twozero.tox (port 40404) -> TD Python
36 native tools. Free plugin (no payment/license — confirmed April 2026).
Context-aware (knows selected OP, current network).
Hub health check: GET http://localhost:40404/mcp returns JSON with instance PID, project name, TD version.
Setup (Automated)
Run the setup script to handle everything:
bash "${HERMES_HOME:-$HOME/.hermes}/skills/creative/touchdesigner-mcp/scripts/setup.sh"
The script will:
- Check if TD is running
- Download twozero.tox if not already cached
- Add
twozero_tdMCP server to Hermes config (if missing) - Test the MCP connection on port 40404
- Report what manual steps remain (drag .tox into TD, enable MCP toggle)
Manual steps (one-time, cannot be automated)
- Drag
~/Downloads/twozero.toxinto the TD network editor → click Install - Enable MCP: click twozero icon → Settings → mcp → "auto start MCP" → Yes
- Restart Hermes session to pick up the new MCP server
After setup, verify:
nc -z 127.0.0.1 40404 && echo "twozero MCP: READY"
Environment Notes
- Non-Commercial TD caps resolution at 1280×1280. Use
outputresolution = 'custom'and set width/height explicitly. - Codecs:
prores(preferred on macOS) ormjpaas fallback. H.264/H.265/AV1 require a Commercial license. - Always call
td_get_par_infobefore setting params — names vary by TD version (see CRITICAL RULES #1).
Workflow
Step 0: Discover (before building anything)
Call td_get_par_info with op_type for each type you plan to use.
Call td_get_hints with the topic you're building (e.g. "glsl", "audio reactive", "feedback").
Call td_get_focus to see where the user is and what's selected.
Call td_get_network to see what already exists.
No temp nodes, no cleanup. This replaces the old discovery dance entirely.
Step 1: Clean + Build
IMPORTANT: Split cleanup and creation into SEPARATE MCP calls. Destroying and recreating same-named nodes in one td_execute_python script causes "Invalid OP object" errors. See pitfalls #11b.
Use td_create_operator for each node (handles viewport positioning automatically):
td_create_operator(type="noiseTOP", parent="/project1", name="bg", parameters={"resolutionw": 1280, "resolutionh": 720})
td_create_operator(type="levelTOP", parent="/project1", name="brightness")
td_create_operator(type="nullTOP", parent="/project1", name="out")
For bulk creation or wiring, use td_execute_python:
# td_execute_python script:
root = op('/project1')
nodes = []
for name, optype in [('bg', noiseTOP), ('fx', levelTOP), ('out', nullTOP)]:
n = root.create(optype, name)
nodes.append(n.path)
# Wire chain
for i in range(len(nodes)-1):
op(nodes[i]).outputConnectors[0].connect(op(nodes[i+1]).inputConnectors[0])
result = {'created': nodes}
Step 2: Set Parameters
Prefer the native tool (validates params, won't crash):
td_set_operator_pars(path="/project1/bg", parameters={"roughness": 0.6, "monochrome": true})
For expressions or modes, use td_execute_python:
op('/project1/time_driver').par.colorr.expr = "absTime.seconds % 1000.0"
Step 3: Wire
Use td_execute_python — no native wire tool exists:
op('/project1/bg').outputConnectors[0].connect(op('/project1/fx').inputConnectors[0])
Step 4: Verify
td_get_errors(path="/project1", recursive=true)
td_get_perf()
td_get_operator_info(path="/project1/out", detail="full")
Step 5: Display / Capture
td_get_screenshot(path="/project1/out")
Or open a window via script:
win = op('/project1').create(windowCOMP, 'display')
win.par.winop = op('/project1/out').path
win.par.winw = 1280; win.par.winh = 720
win.par.winopen.pulse()
MCP Tool Quick Reference
Core (use these most):
| Tool | What |
|---|---|
td_execute_python | Run arbitrary Python in TD. Full API access. |
td_create_operator | Create node with params + auto-positioning |
td_set_operator_pars | Set params safely (validates, won't crash) |
td_get_operator_info | Inspect one node: connections, params, errors |
td_get_operators_info | Inspect multiple nodes in one call |
td_get_network | See network structure at a path |
td_get_errors | Find errors/warnings recursively |
td_get_par_info | Get param names for an OP type (replaces discovery) |
td_get_hints | Get patterns/tips before building |
td_get_focus | What network is open, what's selected |
Read/Write:
| Tool | What |
|---|---|
td_read_dat | Read DAT text content |
td_write_dat | Write/patch DAT content |
td_read_chop | Read CHOP channel values |
td_read_textport | Read TD console output |
Visual:
| Tool | What |
|---|---|
td_get_screenshot | Capture one OP viewer to file |
td_get_screenshots | Capture multiple OPs at once |
td_get_screen_screenshot | Capture actual screen via TD |
td_navigate_to | Jump network editor to an OP |
Search:
| Tool | What |
|---|---|
td_find_op | Find ops by name/type across project |
td_search | Search code, expressions, string params |
System:
| Tool | What |
|---|---|
td_get_perf | Performance profiling (FPS, slow ops) |
td_list_instances | List all running TD instances |
td_get_docs | In-depth docs on a TD topic |
td_agents_md | Read/write per-COMP markdown docs |
td_reinit_extension | Reload extension after code edit |
td_clear_textport | Clear console before debug session |
Input Automation:
| Tool | What |
|---|---|
td_input_execute | Send mouse/keyboard to TD |
td_input_status | Poll input queue status |
td_input_clear | Stop input automation |
td_op_screen_rect | Get screen coords of a node |
td_click_screen_point | Click a point in a screenshot |
td_screen_point_to_global | Convert screenshot pixel to absolute screen coords |
The table above covers the 32 tools used in typical creative workflows. The remaining 4 tools (td_project_quit, td_test_session, td_dev_log, td_clear_dev_log) are admin/dev-mode utilities — see references/mcp-tools.md for the full 36-tool reference with complete parameter schemas.
Key Implementation Rules
GLSL time: No uTDCurrentTime in GLSL TOP. Use the Values page:
# Call td_get_par_info(op_type="glslTOP") first to confirm param names
td_set_operator_pars(path="/project1/shader", parameters={"value0name": "uTime"})
# Then set expression via script:
# op('/project1/shader').par.value0.expr = "absTime.seconds"
# In GLSL: uniform float uTime;
Fallback: Constant TOP in rgba32float format (8-bit clamps to 0-1, freezing the shader).
Feedback TOP: Use top parameter reference, not direct input wire. "Not enough sources" resolves after first cook. "Cook dependency loop" warning is expected.
Resolution: Non-Commercial caps at 1280×1280. Use outputresolution = 'custom'.
Large shaders: Write GLSL to /tmp/file.glsl, then use td_write_dat or td_execute_python to load.
Vertex/Point access (TD 2025.32): point.P[0], point.P[1], point.P[2] — NOT .x, .y, .z.
Extensions: ext0object