touchdesigner-mcp
Control a running TouchDesigner instance via twozero MCP — create operators, set parameters, wire connections, execute Python, build real-time visuals. 36 native tools.
Control a running TouchDesigner instance via twozero MCP — create operators, set parameters, wire connections, execute Python, build real-time visuals. 36 native tools.
Real data. Real impact.
Emerging
Developers
Per week
Excellent
Skills give you superpowers. Install in 30 seconds.
td_get_par_info for the op type FIRST. Your training data is wrong for TD 2025.32.tdAttributeError fires, STOP. Call td_get_operator_info on the failing node before continuing.me.parent() / scriptOp.parent().td_create_operator, td_set_operator_pars, td_get_errors etc. Only fall back to td_execute_python for complex multi-step logic.td_get_hints before building. It returns patterns specific to the op type you're working with.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.
Run the setup script to handle everything:
bash "${HERMES_HOME:-$HOME/.hermes}/skills/creative/touchdesigner-mcp/scripts/setup.sh"
The script will:
twozero_td MCP server to Hermes config (if missing)~/Downloads/twozero.tox into the TD network editor → click InstallAfter setup, verify:
nc -z 127.0.0.1 40404 && echo "twozero MCP: READY"
outputresolution = 'custom' and set width/height explicitly.prores (preferred on macOS) or mjpa as fallback. H.264/H.265/AV1 require a Commercial license.td_get_par_info before setting params — names vary by TD version (see CRITICAL RULES #1).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.
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}
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"
Use
td_execute_python — no native wire tool exists:
op('/project1/bg').outputConnectors[0].connect(op('/project1/fx').inputConnectors[0])
td_get_errors(path="/project1", recursive=true) td_get_perf() td_get_operator_info(path="/project1/out", detail="full")
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()
Core (use these most):
| Tool | What |
|---|---|
| Run arbitrary Python in TD. Full API access. |
| Create node with params + auto-positioning |
| Set params safely (validates, won't crash) |
| Inspect one node: connections, params, errors |
| Inspect multiple nodes in one call |
| See network structure at a path |
| Find errors/warnings recursively |
| Get param names for an OP type (replaces discovery) |
| Get patterns/tips before building |
| What network is open, what's selected |
Read/Write:
| Tool | What |
|---|---|
| Read DAT text content |
| Write/patch DAT content |
| Read CHOP channel values |
| Read TD console output |
Visual:
| Tool | What |
|---|---|
| Capture one OP viewer to file |
| Capture multiple OPs at once |
| Capture actual screen via TD |
| Jump network editor to an OP |
Search:
| Tool | What |
|---|---|
| Find ops by name/type across project |
| Search code, expressions, string params |
System:
| Tool | What |
|---|---|
| Performance profiling (FPS, slow ops) |
| List all running TD instances |
| In-depth docs on a TD topic |
| Read/write per-COMP markdown docs |
| Reload extension after code edit |
| Clear console before debug session |
Input Automation:
| Tool | What |
|---|---|
| Send mouse/keyboard to TD |
| Poll input queue status |
| Stop input automation |
| Get screen coords of a node |
| Click a point in a screenshot |
| 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.
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 format is "op('./datName').module.ClassName(me)" in CONSTANT mode. After editing extension code with td_write_dat, call td_reinit_extension.
Script callbacks: ALWAYS use relative paths via
me.parent() / scriptOp.parent().
Cleaning nodes: Always
list(root.children) before iterating + child.valid check.
# via td_execute_python: root = op('/project1') rec = root.create(moviefileoutTOP, 'recorder') op('/project1/out').outputConnectors[0].connect(rec.inputConnectors[0]) rec.par.type = 'movie' rec.par.file = '/tmp/output.mov' rec.par.videocodec = 'prores' # Apple ProRes — NOT license-restricted on macOS rec.par.record = True # start # rec.par.record = False # stop (call separately later)
H.264/H.265/AV1 need Commercial license. Use
prores on macOS or mjpa as fallback.
Extract frames: ffmpeg -i /tmp/output.mov -vframes 120 /tmp/frames/frame_%06d.png
TOP.save() is useless for animation — captures same GPU texture every time. Always use MovieFileOut.
td_get_perf. If FPS=0 the recording will be empty. See pitfalls #38-39.td_get_screenshot. Black output = shader error or missing input. See pitfalls #8, #40.AudioFileIn CHOP (playmode=sequential) → AudioSpectrum CHOP (FFT=512, outputmenu=setmanually, outlength=256, timeslice=ON) → Math CHOP (gain=10) → CHOP to TOP (dataformat=r, layout=rowscropped) → GLSL TOP input 1 (spectrum texture, 256x2) Constant TOP (rgba32float, time) → GLSL TOP input 0 GLSL TOP → Null TOP → MovieFileOut
outputmenu='setmanually' and outlength=256. Default outputs 22050 samples.mix(prevValue, newValue, 0.3). This gives frame-perfect sync with zero pipeline latency.outlength param directly.// Input 0 = time (1x1 rgba32float), Input 1 = spectrum (256x2) float iTime = texture(sTD2DInputs[0], vec2(0.5)).r; // Sample multiple points per band and average for stability: // NOTE: y=0.25 for first channel (stereo texture is 256x2, first row center is 0.25) float bass = (texture(sTD2DInputs[1], vec2(0.02, 0.25)).r + texture(sTD2DInputs[1], vec2(0.05, 0.25)).r) / 2.0; float mid = (texture(sTD2DInputs[1], vec2(0.2, 0.25)).r + texture(sTD2DInputs[1], vec2(0.35, 0.25)).r) / 2.0; float hi = (texture(sTD2DInputs[1], vec2(0.6, 0.25)).r + texture(sTD2DInputs[1], vec2(0.8, 0.25)).r) / 2.0;
See
references/network-patterns.md for complete build scripts + shader code.
| Family | Color | Python class / MCP type | Suffix |
|---|---|---|---|
| TOP | Purple | noiseTOP, glslTOP, compositeTOP, levelTop, blurTOP, textTOP, nullTOP | TOP |
| CHOP | Green | audiofileinCHOP, audiospectrumCHOP, mathCHOP, lfoCHOP, constantCHOP | CHOP |
| SOP | Blue | gridSOP, sphereSOP, transformSOP, noiseSOP | SOP |
| DAT | White | textDAT, tableDAT, scriptDAT, webserverDAT | DAT |
| MAT | Yellow | phongMAT, pbrMAT, glslMAT, constMAT | MAT |
| COMP | Gray | geometryCOMP, containerCOMP, cameraCOMP, lightCOMP, windowCOMP | COMP |
td_execute_python has unrestricted access to the TD Python environment and filesystem as the TD process user.setup.sh downloads twozero.tox from the official 404zero.com URL. Verify the download if concerned.| File | What |
|---|---|
| Hard-won lessons from real sessions |
| All operator families with params and use cases |
| Recipes: audio-reactive, generative, GLSL, instancing |
| Full twozero MCP tool parameter schemas |
| TD Python: op(), scripting, extensions |
| Connection diagnostics, debugging |
| GLSL uniforms, built-in functions, shader templates |
| Post-FX: bloom, CRT, chromatic aberration, feedback glow |
| HUD layout patterns, panel grids, BSP-style layouts |
| Wireframe rendering, feedback TOP setup |
| Geometry COMP: instancing, POP vs SOP, morphing |
| Audio band extraction, beat detection, envelope following |
| LFOs, timers, keyframes, easing, expression-driven motion |
| MIDI/OSC controllers, TouchOSC, multi-machine sync |
| POPs and legacy particleSOP — emission, forces, collisions |
| Multi-window output, corner pin, mesh warp, edge blending |
| HTTP, WebSocket, MQTT, Serial, TCP, webserverDAT |
| Custom params, panel COMPs, button/slider/field, panelExecuteDAT |
| replicatorCOMP — data-driven cloning, layouts, callbacks |
| Execute DAT family — chop/dat/parameter/panel/op/executeDAT |
| Lighting rigs, shadows, IBL/cubemaps, multi-camera, PBR |
| Automated setup script |
You're not writing code. You're conducting light.
MIT
mkdir -p ~/.hermes/skills/creative/touchdesigner-mcp && curl -o ~/.hermes/skills/creative/touchdesigner-mcp/SKILL.md https://raw.githubusercontent.com/NousResearch/hermes-agent/main/skills/creative/touchdesigner-mcp/SKILL.md1,500+ AI skills, agents & workflows. Install in 30 seconds. Part of the Torly.ai family.
© 2026 Torly.ai. All rights reserved.