skills/creative/touchdesigner-mcp/references/troubleshooting.md
See
references/pitfalls.mdfor the comprehensive lessons-learned list.
Check these in order:
pgrep TouchDesigner
1b. Quick hub health check (no JSON-RPC needed): A plain GET to the MCP URL returns instance info:
curl -s http://localhost:40404/mcp
Returns: {"hub": true, "pid": ..., "instances": {"127.0.0.1_PID": {"project": "...", "tdVersion": "...", ...}}}
If this returns JSON but instances is empty, TD is running but twozero hasn't registered yet.
Is twozero installed in TD? Open TD Palette Browser > twozero should be listed. If not, install it.
Is MCP enabled in twozero settings? In TD, open twozero preferences and confirm MCP server is toggled ON.
Test the port directly:
nc -z 127.0.0.1 40404
Test the MCP endpoint:
curl -s http://localhost:40404/mcp
Should return JSON with hub info. If it does, the server is running.
The twozero MCP hub is running but TD hasn't registered. Causes:
Fix: Open/reload a TD project that contains the twozero COMP. Use td_list_instances to check which TD instances are registered.
twozero auto-assigns ports for multiple TD instances:
Use td_list_instances to discover all running instances and their ports.
The error message from td_execute_python often contains the Python traceback.
If it's unclear, use td_read_textport to see the full TD console output —
Python exceptions are always printed there.
Common causes:
Parameter name mismatch is the #1 cause. The tool validates param names and returns clear errors, but you must use exact names.
Fix: ALWAYS call td_get_par_info first to discover the real parameter names:
td_get_par_info(op_type='glslTOP')
td_get_par_info(op_type='noiseTOP')
Operator type names use camelCase with family suffix:
If unsure about any aspect of an operator (params, inputs, outputs, state):
td_get_operator_info(path='/project1/noise1', detail='full')
CRITICAL: ALWAYS use td_get_par_info to discover parameter names.
The agent's LLM training data contains WRONG parameter names for TouchDesigner. Do not trust them. Known wrong names include dat vs pixeldat, colora vs alpha, sizex vs size, and many more. See pitfalls.md for the full list.
Workflow:
Use td_get_perf to see which operators are slow. Look at cook times —
anything over 1ms per frame is worth investigating.
Common causes:
Always check effective resolution after creation:
n.cook(force=True)
actual = str(n.width) + 'x' + str(n.height)
$HERMES_HOME/config.yaml (defaults to ~/.hermes/config.yaml when HERMES_HOME is unset)
The twozero TD entry should look like:
mcpServers:
twozero_td:
url: http://localhost:40404/mcp
Restart the Hermes session for changes to take effect. The MCP connection is established at session startup.
After restarting, the session log should show twozero MCP tools registered. If tools show as registered but aren't callable, check:
Wrong type string. Use camelCase with family suffix:
Check parentPath — use absolute paths like /project1. The default project root is /project1. System nodes live at /, /ui, /sys, /local, /perform. Don't create user nodes outside /project1.
Only COMP operators (Container, Base, Geometry, etc.) can contain children. You cannot create nodes inside a TOP, CHOP, SOP, DAT, or MAT.
TOPs connect to TOPs, CHOPs to CHOPs, SOPs to SOPs, DATs to DATs. Use converter operators to bridge: choptoTOP, topToCHOP, soptoDAT, etc.
Note: choptoTOP has NO input connectors. Use par.chop reference instead:
spec_tex.par.chop = resample_node # correct
# NOT: resample.outputConnectors[0].connect(spec_tex.inputConnectors[0])
Never create A -> B -> A directly. Use a Feedback TOP:
fb = root.create(feedbackTOP, 'fb')
fb.par.top = comp.path # reference only, no wire to fb input
fb.outputConnectors[0].connect(next_node)
"Cook dependency loop detected" warning on the chain is expected and correct.
GLSL TOP shows a yellow warning in the UI but node.errors() may return empty. Check node.warnings() too. Create an Info DAT pointed at the GLSL TOP for full compiler output.
Use Apple ProRes on macOS (hardware accelerated, not license-restricted):
rec.par.videocodec = 'prores' # Preferred on macOS — lossless, Non-Commercial OK
# rec.par.videocodec = 'mjpa' # Fallback — lossy, works everywhere
Use the toggle parameter:
rec.par.record = True # start
rec.par.record = False # stop
TOP.save() captures same frame when called rapidly. Use MovieFileOut for real-time recording. Set project.realTime = False for frame-accurate output.