TouchDesigner MCP Skill: Control TD via Hermes Agent with twozero
Control TouchDesigner via twozero MCP.
Written by Neura Market from the official Hermes Agent documentation for Touchdesigner Mcp. Commands, paths, and version numbers are reproduced from the source unchanged.
Read the official documentationTwozero MCP for TouchDesigner: Reference Guide
This guide documents the integration between TouchDesigner and an AI agent through the twozero MCP plugin. The plugin exposes 36 native tools that allow AI-driven creation, manipulation, and inspection of TouchDesigner networks. The architecture follows this communication path:
Hermes Agent -> MCP (Streamable HTTP) -> twozero.tox (port 40404) -> TD Python
All MCP communication occurs over localhost only on port 40404. There is no authentication — any local process can send commands. The skill never sends data outside localhost.
When to Use This Integration
- Building or modifying TouchDesigner projects programmatically from an AI agent.
- Automating repetitive TD tasks like node creation, parameter setting, and wiring.
- Creating audio-reactive or generative GLSL shaders with precise control over signal chains.
- Recording or exporting video from TD without manual interaction.
- Debugging TD networks by inspecting errors, performance, and operator details.
- Setting up a new TD environment for AI-assisted creative workflows.
Prerequisites
- TouchDesigner installed (any edition, but Non-Commercial has a resolution cap).
- Hermes agent environment set up with
HERMES_HOMEvariable or default~/.hermes. twozero.toxplugin downloaded (automatically by the setup script or manually from 404zero.com).- MCP server configured in Hermes config (automatically by the setup script).
- Manual steps completed: drag
~/Downloads/twozero.toxinto TD, click Install, enable MCP toggle in twozero settings, restart Hermes session. - Port 40404 available on localhost.
- For video recording:
ffmpeginstalled for frame extraction (optional). - For audio-reactive: audio file available.
Setup
Automated Setup
Run the automated setup script:
bash "${HERMES_HOME:-$HOME/.hermes}/skills/creative/touchdesigner-mcp/scripts/setup.sh"
The script checks if TD is running, downloads twozero.tox if not cached, adds the MCP server to Hermes config, tests the MCP connection on port 40404, and reports remaining manual steps.
Manual Steps
- Drag
~/Downloads/twozero.toxinto the TD network editor and click Install. - Enable MCP: click the twozero icon → Settings → mcp → "auto start MCP" → Yes.
- Restart Hermes session to pick up the new MCP server.
Verify Readiness
nc -z 127.0.0.1 40404 && echo "twozero MCP: READY"
The MCP server listens at GET http://localhost:40404/mcp. The tool prefix is twozero_td.
Capabilities Overview
The integration provides tools for:
- Creating, deleting, and wiring operators (TOP, CHOP, SOP, DAT, MAT, COMP) with auto-positioning.
- Setting operator parameters safely with validation to prevent crashes.
- Reading and writing DAT text content, CHOP channel values, and TD console output.
- Capturing screenshots of operators or the full screen.
- Navigating the network editor to a specific operator.
- Searching for operators by name/type across the project.
- Searching code, expressions, and string parameters.
- Getting performance profiling (FPS, slow ops).
- Listing all running TD instances.
- Getting in-depth documentation on TD topics.
- Reading/writing per-COMP markdown documentation.
- Reloading extensions after code edits.
- Clearing the textport console.
- Automating mouse and keyboard input to TD.
- Getting screen coordinates of operators and click points.
- Converting screenshot pixels to absolute screen coordinates.
- Executing arbitrary Python in TD with full API access.
- Getting parameter info for an operator type before setting params.
- Getting hints/patterns for building specific topics.
- Getting focus info (current network, selected operator).
- Getting network structure at a path.
- Finding errors/warnings recursively.
- Inspecting one or multiple operators (connections, params, errors).
- Admin utilities: quit project, test session, dev log, clear dev log.
Tool Reference
Discovery and Inspection Tools
| Tool | Purpose |
|---|---|
td_get_par_info | Get parameter info for an operator type before setting params. |
td_get_operator_info | Inspect one operator (connections, params, errors). |
td_get_operators_info | Inspect multiple operators. |
td_get_network | Get network structure at a path. |
td_get_focus | Get focus info (current network, selected operator). |
td_get_errors | Find errors/warnings recursively. |
td_get_perf | Get performance profiling (FPS, slow ops). |
td_get_hints | Get hints/patterns for building specific topics. |
td_get_docs | Get in-depth docs on a TD topic. |
td_list_instances | List all running TD instances. |
td_find_op | Find operators by name/type across project. |
td_search | Search code, expressions, string params. |
Creation and Manipulation Tools
| Tool | Purpose |
|---|---|
td_create_operator | Create operators with auto-positioning. |
td_set_operator_pars | Set operator parameters safely with validation. |
td_execute_python | Execute arbitrary Python in TD with full API access. |
td_reinit_extension | Reload extension after code edit. |
Data Access Tools
| Tool | Purpose |
|---|---|
td_read_dat | Read DAT content. |
td_write_dat | Write DAT content. |
td_read_chop | Read CHOP channel values. |
td_read_textport | Read TD console output. |
td_clear_textport | Clear console. |
td_agents_md | Read/write per-COMP markdown docs. |
Capture and Navigation Tools
| Tool | Purpose |
|---|---|
td_get_screenshot | Capture one operator viewer to file. |
td_get_screenshots | Capture multiple operator viewers to files. |
td_get_screen_screenshot | Capture actual screen via TD. |
td_navigate_to | Navigate network editor to an operator. |
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. |
Input Automation Tools
| Tool | Purpose |
|---|---|
td_input_execute | Send mouse/keyboard to TD. |
td_input_status | Poll input queue status. |
td_input_clear | Stop input automation. |
Admin Tools
| Tool | Purpose |
|---|---|
td_project_quit | Quit project. |
td_test_session | Test session. |
td_dev_log | Dev log. |
td_clear_dev_log | Clear dev log. |
Parameters
Common Parameters
| Parameter | Meaning | Required |
|---|---|---|
op_type | The type of operator to create or get info for (e.g., noiseTOP, glslTOP, audiofileinCHOP). | Yes for td_create_operator and td_get_par_info. |
parent | The parent path where the operator will be created (e.g., /project1). | Yes for td_create_operator. |
name | The name for the new operator. | Yes for td_create_operator. |
parameters | A dictionary of parameter names and values to set on the operator. | Optional for td_create_operator and td_set_operator_pars. |
path | The absolute path to an operator (e.g., /project1/bg). | Yes for td_set_operator_pars, td_get_operator_info, td_get_errors, td_get_screenshot, etc. |
recursive | Whether to search recursively for errors or operators. | Optional for td_get_errors. |
detail | Level of detail for operator info (e.g., "full"). | Optional for td_get_operator_info. |
topic | The topic for which to get hints (e.g., "glsl", "audio reactive", "feedback"). | Yes for td_get_hints. |
Audio-Reactive Parameters
| Parameter | Meaning | Required |
|---|---|---|
timeslice | Whether timeslice mode is ON for AudioSpectrum. Must stay ON for audio-reactive. | Yes for AudioSpectrum in audio-reactive chains. |
outlength | Output length for AudioSpectrum. Set to 256 manually. | Yes for AudioSpectrum in audio-reactive chains. |
gain | Gain value for Math CHOP in audio-reactive chains. Use 10. | Yes for Math CHOP in audio-reactive chains. |
dataformat | Data format for CHOP to TOP. Use "r" for audio-reactive. | Yes for CHOP to TOP in audio-reactive chains. |
layout | Layout for CHOP to TOP. Use "rowscropped" for audio-reactive. | Yes for CHOP to TOP in audio-reactive chains. |
Recording Parameters
| Parameter | Meaning | Required |
|---|---|---|
codec | Video codec for recording (e.g., "prores", "mjpa"). H.264/H.265/AV1 require Commercial license. | Yes for MovieFileOut setup. |
outputresolution | Resolution setting for operators. Use "custom" for Non-Commercial TD and set width/height explicitly. | Optional, but needed for Non-Commercial TD to avoid 1280x1280 cap. |
Procedures
Build a TD Network (General Workflow)
Step 0 - Discover:
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.
Step 1 - Clean + Build: Split cleanup and creation into separate MCP calls to avoid "Invalid OP object" errors. Use td_create_operator for each node (handles viewport positioning). For bulk creation or wiring, use td_execute_python.
Example creating a simple TOP chain:
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 and wiring:
# 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 td_set_operator_pars (validates params). For expressions or modes, use td_execute_python.
td_set_operator_pars(path="/project1/bg", parameters={"roughness": 0.6, "monochrome": true})
Setting an expression on a parameter:
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: Use td_get_screenshot or open a window via script.
td_get_screenshot(path="/project1/out")
Opening a display window:
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()
Record/Export Video
Create a moviefileoutTOP via td_execute_python:
# 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)
To stop, set record parameter to False in a separate call.
Extract frames with ffmpeg:
ffmpeg -i /tmp/output.mov -vframes 120 /tmp/frames/frame_%06d.png
Build Audio-Reactive GLSL (Proven Recipe)
The signal chain:
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
In GLSL, sample spectrum at y=0.25 for first channel, use multiple samples per band averaged for stability:
// 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;
Do NOT use Lag CHOP or Filter CHOP for spectrum smoothing (causes expansion to 2400+ samples and near-zero values). Smooth in GLSL via temporal lerp if needed: mix(prevValue, newValue, 0.3).
GLSL Time Setup
There is no uTDCurrentTime in GLSL TOP. Use the Values page with a parameter named uTime and set expression via script:
# 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;
Constraints and Caveats
General Rules
- NEVER guess parameter names. Always call
td_get_par_infofor the op type first. Training data is wrong for TD 2025.32. - If
tdAttributeErrorfires, STOP and 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. Usetd_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.
Resolution and Codec Constraints
- Non-Commercial TD caps resolution at 1280x1280. Use
outputresolution = 'custom'and set width/height explicitly. - H.264/H.265/AV1 codecs require a Commercial license. Use
prores(preferred on macOS) ormjpaas fallback.
Node Management
- Split cleanup and creation into SEPARATE MCP calls. Destroying and recreating same-named nodes in one
td_execute_pythonscript causes "Invalid OP object" errors. - Cleaning nodes: Always
list(root.children)before iterating and checkchild.valid. TOP.save()is useless for animation — captures same GPU texture every time. Always use MovieFileOut.
Feedback TOP
- Use
topparameter reference, not direct input wire. "Not enough sources" resolves after first cook. "Cook dependency loop" warning is expected.
Vertex/Point Access
- In TD 2025.32, use
point.P[0],point.P[1],point.P[2]— NOT.x,.y,.z.
Extensions
ext0objectformat is"op('./datName').module.ClassName(me)"in CONSTANT mode.- After editing extension code with
td_write_dat, calltd_reinit_extension.
Recording
- Before recording: Verify FPS > 0 via
td_get_perf. If FPS=0 the recording will be empty. - Before recording: Verify shader output is not black via
td_get_screenshot. Black output = shader error or missing input. - If recording with audio: cue audio to start first, then delay recording by 3 frames.
- Set output path before starting record — setting both in the same script can race.
Audio-Reactive Specifics
- TimeSlice must stay ON for AudioSpectrum. OFF = processes entire audio file → 24000+ samples → CHOP to TOP overflow.
- Set Output Length manually to 256 via
outputmenu='setmanually'andoutlength=256. Default outputs 22050 samples. - DO NOT use Lag CHOP for spectrum smoothing. Lag CHOP operates in timeslice mode and expands 256 samples to 2400+, averaging all values to near-zero (~1e-06).
- DO NOT use Filter CHOP for spectrum smoothing — same timeslice expansion problem.
- Smoothing belongs in the GLSL shader if needed, via temporal lerp with a feedback texture:
mix(prevValue, newValue, 0.3). - CHOP to TOP
dataformat = 'r',layout = 'rowscropped'. Spectrum output is 256x2 (stereo). Sample at y=0.25 for first channel. - Math gain = 10 (not 5). Raw spectrum values are ~0.19 in bass range. Gain of 10 gives usable ~5.0 for the shader.
- No Resample CHOP needed. Control output size via AudioSpectrum's
outlengthparam directly.
Security
- MCP runs on localhost only (port 40404). No authentication — any local process can send commands.
td_execute_pythonhas unrestricted access to the TD Python environment and filesystem as the TD process user.- The skill never sends data outside localhost. All MCP communication is local.
Failure Modes
| Failure | Cause | Resolution |
|---|---|---|
tdAttributeError | Parameter name is wrong or operator doesn't exist. | STOP and call td_get_operator_info on the failing node. |
| Invalid OP object error | Destroying and recreating same-named nodes in one td_execute_python script. | Split cleanup and creation into separate MCP calls. |
| Empty recording | FPS=0. | Verify FPS > 0 via td_get_perf before recording. |
| Black output | Shader error or missing input. | Verify via td_get_screenshot. |
| Audio-reactive shader receives no usable data | Using Lag CHOP or Filter CHOP for spectrum smoothing. | Smooth in GLSL shader instead. |
| CHOP to TOP overflow | TimeSlice OFF on AudioSpectrum. | Keep TimeSlice ON. |
| AudioSpectrum outputs 22050 samples | Not setting outputmenu='setmanually' and outlength=256. | Set both parameters explicitly. |
| Shader freezes | Using Constant TOP in non-rgba32float format. | Use rgba32float format. |
| Recording race condition | Setting output path and starting record in the same script. | Set path first, start record in separate call. |
| Connection failure | MCP not running on port 40404. | Verify with nc -z 127.0.0.1 40404. |
| Extension not reloaded | After editing extension code with td_write_dat, forgot to call td_reinit_extension. | Always call td_reinit_extension after edits. |
Reference Documents
The following reference documents are available in the Hermes skills directory:
references/mcp-tools.md- Complete tool referencereferences/network-patterns.md- Network building patternsreferences/pitfalls.md- Common pitfallsreferences/operators.md- Operator referencereferences/python-api.md- Python API referencereferences/troubleshooting.md- Troubleshooting guidereferences/glsl.md- GLSL shader referencereferences/postfx.md- Post-processing effectsreferences/layout-compositor.md- Layout and compositingreferences/operator-tips.md- Operator tipsreferences/geometry-comp.md- Geometry COMP referencereferences/audio-reactive.md- Audio-reactive techniquesreferences/animation.md- Animation patternsreferences/midi-osc.md- MIDI and OSCreferences/particles.md- Particle systemsreferences/projection-mapping.md- Projection mappingreferences/external-data.md- External data integrationreferences/panel-ui.md- Panel UI componentsreferences/replicator.md- Replicator COMPreferences/dat-scripting.md- DAT scriptingreferences/3d-scene.md- 3D scene setupscripts/setup.sh- Setup script
Writing GLSL to File
Write to /tmp/file.glsl, then use td_write_dat or td_execute_python to load into a GLSL TOP.