Skip to main content
Glama

TouchDesigner ↔ MCP bridge

Local bridge that lets an AI agent read and build real TouchDesigner networks: create operators, wire them, set parameters and expressions, lay the network out, validate it, look at the rendered result as an image, and iterate on what it sees.

Works with any MCP-compatible host — Claude Code, Claude Desktop, Cursor, Antigravity, VS Code Copilot, Windsurf, Cline, Zed, Gemini CLI, Codex CLI. Nothing in the server is vendor-specific; it imports only mcp and httpx.

Connecting an AI agent — per-host setup, verified.

python docs/verify_stdio.py     # proves the server works with any MCP host

1. What this is

your AI agent
    │  MCP over stdio
    ▼
C:\td-claude-mcp\server.py            ← separate Python 3.11 venv
    │  HTTP POST http://127.0.0.1:9980/mcp
    ▼
TouchDesigner Web Server DAT  (mcp_webserver)
    │
    ▼
mcp_callbacks  (td_webserver_callback.py)
    │
    ▼
TouchDesigner Python API → your live project

Two hard rules the design keeps:

  • The MCP server never imports TouchDesigner. It only speaks HTTP.

  • The TouchDesigner side never needs a pip package. Standard library only.

Only TouchDesigner itself ever mutates the network, so nothing can get out of sync.

No fake capabilities. Every mutating tool re-reads the object from the live network after the change and returns what TouchDesigner actually holds — not what was requested. A reported success is a verified success.


Related MCP server: tdmcp

2. Architecture

Piece

File

Runs in

MCP tool surface (32 tools)

server.py

venv Python 3.11

HTTP client

td_bridge.py

venv Python 3.11

Configuration

config.py, config.json

venv Python 3.11

HTTP API + TouchDesigner logic (35 actions)

td_webserver_callback.py

TouchDesigner

Pasteable Textport stub (4 statements)

td_bootstrap.py

TouchDesigner Textport

Bootstrap implementation it exec's

td_bootstrap_impl.py

TouchDesigner

Tests

tests/

venv Python 3.11

HTTP protocol

POST /mcp
{"id": "...", "action": "...", "args": {...}}

→ {"ok": true,  "id": "...", "result": {...}, "error": null}
→ {"ok": false, "id": "...", "result": null,
   "error": {"type": "...", "message": "...", "details": {...}, "traceback": "..."}}

GET /health · GET /info · GET /api · GET /version

Error types: OperatorNotFound, OperatorTypeInvalid, ParameterNotFound, ParameterInvalid, ConnectionInvalid, ConnectorNotFound, UnsupportedOperation, TouchDesignerError, ValidationError, ProjectNotSaved, PreviewError, BatchError, PythonExecutionError, ServerUnavailable, TimeoutError.


3. Requirements

  • Windows

  • TouchDesigner 2025.32460 (developed and tested against this build)

  • System Python 3.10+, 3.11 preferred — not TouchDesigner's Python

  • Claude Code

  • No Node.js. No Docker. No database. No cloud.

Python dependencies: mcp[cli]>=2.1.1 and httpx. That is all.

MCP SDK note. This bridge targets MCP Python SDK 2.x, where FastMCP was renamed to MCPServer (from mcp.server.mcpserver import MCPServer). Code copied from older FastMCP examples will not run here.


4. Installation

powershell -ExecutionPolicy Bypass -File C:\td-claude-mcp\install.ps1

Creates the directories, builds .venv, upgrades pip, installs requirements.txt, and verifies that every module imports and all 32 MCP tools register.

Rebuild the environment from scratch:

powershell -ExecutionPolicy Bypass -File C:\td-claude-mcp\install.ps1 -Recreate

5. TouchDesigner setup

One step:

Open TouchDesigner → press Alt+T for the Textport → paste the entire contents of C:\td-claude-mcp\td_bootstrap.py → press Enter.

Then press Ctrl+S to save the project, so the bridge operators persist.

The bootstrap creates, inside /project1:

Operator

Type

Purpose

mcp_callbacks

Text DAT

holds td_webserver_callback.py

mcp_webserver

Web Server DAT

listens on port 9980

It introspects the running build, sets only parameters that actually exist, and prints an exact manual instruction for anything it cannot set safely. It is idempotent — re-run it any time to refresh the callback code.

td_bootstrap.py is deliberately tiny: the Textport behaves like a Python REPL, where a blank line inside an indented block ends the block, so a large script cannot be pasted into it reliably. The stub reads and execs td_bootstrap_impl.py from disk instead, which also means re-running it always picks up the current bridge code.

Manual setup (if you prefer, or if the bootstrap reports a manual step)

  1. Open TouchDesigner and create/open a project.

  2. In /project1, create a Text DAT named mcp_callbacks.

  3. Paste the entire contents of C:\td-claude-mcp\td_webserver_callback.py into it.

  4. In /project1, create a Web Server DAT named mcp_webserver.

  5. Set Port = 9980.

  6. Set Callbacks DAT = mcp_callbacks.

  7. If the DAT has a Local Address parameter, set it to 127.0.0.1.

  8. Turn Active On.

  9. Save the project (Ctrl+S).


6. Bootstrap verification

Invoke-RestMethod http://127.0.0.1:9980/health

Expected:

ok    id   result                                              error
--    --   ------                                              -----
True       @{status=ok; bridge_version=1.0.0; project=...}

Full self-test:

powershell -ExecutionPolicy Bypass -File C:\td-claude-mcp\start_test.ps1

7. Connect your AI agent

Full per-host instructions, including the hosts whose config shape differs: docs/agents.md.

Most hosts take this block — Claude Desktop, Cursor, Antigravity, Windsurf, Cline, Gemini CLI, JetBrains AI Assistant:

{
  "mcpServers": {
    "touchdesigner": {
      "command": "C:\\td-claude-mcp\\.venv\\Scripts\\python.exe",
      "args": ["C:\\td-claude-mcp\\server.py"]
    }
  }
}

Claude Code, from the CLI:

claude mcp add touchdesigner --scope user -- "C:\td-claude-mcp\.venv\Scripts\python.exe" "C:\td-claude-mcp\server.py"

Two hosts want a different shape — VS Code uses servers instead of mcpServers, Zed uses context_servers, and Codex CLI uses TOML. Those are written out in docs/agents.md.

Whatever the host: use the absolute path to .venv\Scripts\python.exe. The host does not inherit your shell's PATH or your active virtualenv, and it must never be pointed at TouchDesigner's own Python.

Restart the host afterwards — most read MCP config only at startup — then ask it: "Check the connection to TouchDesigner." It should call td_status.

To check the server independently of any host:

python docs\verify_stdio.py

8. First test

Ask Claude Code:

Build a simple visual system: Noise → Blur → Level. Configure it, lay it out neatly, check for errors and show me a preview.

Claude should inspect, create, connect, configure, lay out, validate, preview, fix anything broken, and report what it changed.


9. Available tools

Connectiontd_status, td_get_project_info

Inspectiontd_list_operators, td_find_operators, td_inspect_operator, td_get_network_graph, td_list_operator_types

Parameterstd_get_parameters, td_get_parameter, td_set_parameter, td_set_expression, td_reset_parameter, td_pulse_parameter

DAT texttd_get_text, td_set_text

Creationtd_create_operator, td_delete_operator, td_rename_operator, td_move_operator, td_set_node_properties

Connectionstd_connect, td_disconnect

Copytd_copy_operator, td_copy_operators

Layouttd_layout (auto / horizontal / vertical / grid / native)

Validation & cooktd_validate_network, td_cook

Projecttd_backup_project, td_save_project

Previewtd_preview

Escape hatchtd_execute_python

Batchtd_batch


10. Preview

td_preview(operator_path) force-cooks a TOP and returns a real MCP ImageContent block, so Claude can see the render and judge it, not just read a file path.

  • TOP.saveByteArray() is used when available; otherwise the tool falls back to TOP.save() into previews\, reads the bytes back and deletes the file. The response reports which path was taken in method.

  • Formats: png (default), jpg/jpeg, exr.

  • Only TOPs can be previewed. For a CHOP/SOP/COMP result, wire it into a TOP (a Null TOP works) and preview that.


11. Backup

td_backup_project() writes a timestamped restore point into C:\td-claude-mcp\backups\ and never moves the live project.

Why it does not use project.save(path). TouchDesigner's own docstring for it is "Save the current session to disk", and project.name / project.folder are read-only properties that follow the save. Saving to a backup path therefore makes the backup the current project — the user's next Ctrl+S would land in the backups folder. This build offers no save-a-copy call, so backups are made two other ways instead.

Artifact

How

Includes unsaved edits?

Restore

<project>_<root>_<stamp>.tox

COMP.save() on the root component

yes

Load into a COMP's Load Tox parameter, or right-click a network → Import .tox

<project>_<stamp>.toe

byte copy of the last saved .toe

no

open directly in TouchDesigner

mode selects one (snapshot / copy); the default auto does both and reports whatever succeeded. Existing backups are never overwritten. The response carries project_has_unsaved_changes plus a per-artifact includes_unsaved_changes, and warns when unsaved work is not covered.

Note that the true root / refuses COMP.save() ("Error creating file."), so the snapshot targets the configured root component (/project1) instead.

td_batch takes a backup automatically before any batch containing a delete.

If the project has never been saved there is no project.folder, so backup returns ProjectNotSaved and you must save the project once from the TouchDesigner UI first.

Saving

td_save_project() with no path saves in place. Passing a path is not a save-a-copy — it repoints the live project, so it is refused unless you also pass confirm_repoint=true. The response reports project_path_before, project_path_after and project_repointed.


12. Security

  • The Web Server DAT in 2025.32460 has no bind-address parameter. Its parameters are pageindex, active, restart, port, callbacks, secure, privatekey, certificate, password, verifyclient, minprotocol plus the common DAT ones — there is no Local Address. The socket is therefore opened on 0.0.0.0 and that cannot be changed from the DAT.

    The bridge enforces loopback-only access itself instead: onHTTPRequest reads the request's clientAddress and refuses anything that is not on the loopback interface with 403 Forbidden, before the request reaches the dispatcher. Verified live — a request to the machine's LAN address is refused while 127.0.0.1 is served.

    GET /health reports the posture under security: enforced, client_address_key, rejected_requests and the request keys observed. If enforced is ever false, the client address could not be identified and requests are not being filtered — do not open port 9980 in the firewall.

  • Do not open port 9980 in the Windows firewall.

  • No password is stored in the source. Authentication is not needed for a loopback-only endpoint; if you add it later, put it in config.json.

  • td_execute_python runs arbitrary Python inside TouchDesigner. It is reachable only over loopback, and it is documented to the model as an escape hatch of last resort.

  • Protected paths (/, /project1, /local, /sys, /ui, /perform) and the bridge's own operators cannot be deleted without an explicit force.

  • Deletion always requires confirm=true.


13. Troubleshooting

Symptom

Cause / fix

Port 9980 already in use

Get-NetTCPConnection -LocalPort 9980 to find the owner. Either stop it, or change the port in config.json and on the mcp_webserver DAT.

TouchDesigner not running

td_status returns ServerUnavailable. Start TouchDesigner and open the project containing the bridge.

Web Server DAT inactive

Select mcp_webserver, turn Active on. Re-run td_bootstrap.py to fix it automatically.

Wrong Local Address

This build's Web Server DAT has no such parameter — see Security above. The bridge filters by client address instead; check security.enforced in GET /health.

Project ended up in the backups folder

Something called project.save(<path>), which repoints the live project. Fix with File → Save As back to your intended .toe. td_backup_project no longer does this, and td_save_project refuses a new path without confirm_repoint=true.

OP.type looks wrong

OP.type is the SHORT name (noise, webserver); the full class name used by create() is OP.opType (noiseTOP, webserverDAT). The bridge returns the full name as type and the short one as short_type.

Callbacks DAT not assigned

The bridge answers with HTML instead of JSON → ProtocolError mentioning the Callbacks DAT. Set Callbacks DAT = mcp_callbacks.

MCP server not registered

claude mcp list does not show touchdesigner. Re-run the claude mcp add command with absolute paths.

Python not found

install.ps1 aborts. Install Python 3.11 from python.org and re-run. Do not point it at TouchDesigner's Python.

mcp import error

No module named 'mcp.server.fastmcp' means you are on mcp 2.x — that is expected, this project uses mcp.server.mcpserver. No module named 'mcp' means the venv is not installed: re-run install.ps1.

httpx import error

& C:\td-claude-mcp\.venv\Scripts\python.exe -m pip install -r C:\td-claude-mcp\requirements.txt

Claude does not see the tools

Restart Claude Code after registering. Check logs\mcp_server.log for a startup line. Confirm the command path points at .venv\Scripts\python.exe.

Preview unavailable

Only TOPs can be previewed. PreviewError naming a family means you pointed at a CHOP/SOP/COMP — wire it into a Null TOP first.

Operator type invalid

OperatorTypeInvalid includes similar valid names. Call td_list_operator_types instead of guessing.

Parameter not found

ParameterNotFound includes similar parameter names. Call td_get_parameters for the real list. Multi-value parameters are per-component: resolutionw, not resolution.

TouchDesigner errors after a change

td_validate_network lists them per node with the parameter that broke. Fix, re-validate, then save.

Project not saved

ProjectNotSaved from backup/save means the .toe has no folder yet. Save it once from the TouchDesigner UI.

Windows path escaping

In JSON use C:/td-claude-mcp/... or C:\\td-claude-mcp\\.... In PowerShell quote every path containing spaces.

UTF-8 problems

The TD-side callback is pure ASCII and all JSON is emitted with ensure_ascii=True, so it survives any console codepage. If PowerShell mangles output, set $env:PYTHONIOENCODING='utf-8'.

Timeout on a heavy cook

Raise timeout on td_cook/td_batch, or TD_TIMEOUT for everything.

Logs: C:\td-claude-mcp\logs\mcp_server.log (stderr is mirrored there; stdout is reserved for the MCP protocol and stays clean).


14. Development

Run the tests:

cd C:\td-claude-mcp
& .\.venv\Scripts\python.exe -m unittest discover -s tests -t . -v

Static tests only (no TouchDesigner needed):

& .\.venv\Scripts\python.exe -m unittest discover -s tests -t . -p "test_[cehs]*.py"

Live integration test (creates and deletes __mcp_test_* operators only):

& .\.venv\Scripts\python.exe -m unittest tests.test_integration -v

Environment variables override config.json:

Variable

Default

TD_HOST

127.0.0.1

TD_PORT

9980

TD_PATH

/mcp

TD_TIMEOUT

10

TD_ROOT

/project1

TD_PREVIEW_FORMAT

png

TD_LOG_LEVEL

INFO

TD_INSTANCE

main

Multi-instance is prepared but not enabled in v1: add an instances block to config.json and select one with TD_INSTANCE. WebSocket transport is deliberately not implemented in v1; the action dispatcher is transport-agnostic so it can be added later without touching the tools.


15. Updating

After editing td_webserver_callback.py, push it into the running project without restarting TouchDesigner:

$body = @{ id = "reload"; action = "reload_callbacks"; args = @{} } | ConvertTo-Json -Compress
Invoke-RestMethod -Uri http://127.0.0.1:9980/mcp -Method Post -Body $body -ContentType application/json

Or ask Claude to run td_execute_python with the same action. Re-running td_bootstrap.py in the Textport does the same thing and also re-checks the Web Server DAT configuration.

After editing server.py, td_bridge.py or config.py, restart Claude Code — the MCP server process is launched by Claude and is only reloaded on restart.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

No tool schema history has been recorded yet.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    MCP server embedded in TouchDesigner with 45 tools for creating operators, setting parameters, wiring connections, managing externalizations, and building real-time visual projects through natural conversation.
    169
    MIT
  • A
    license
    B
    quality
    A
    maintenance
    tdmcp is an open-source (MIT) Model Context Protocol server for TouchDesigner. You describe a visual in plain language and your AI assistant builds the real node network inside TouchDesigner, checks it for errors, and shows a preview — it ships an embedded operator knowledge base so the model uses real operators instead of guessing. TypeScript codebase, runs locally.
    2
    100
    253
    39
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    MCP server for controlling TouchDesigner from AI coding agents like Claude Code and Codex CLI, enabling operator manipulation, parameter control, and screenshot capture.
    12
    MIT
  • A
    license
    B
    quality
    A
    maintenance
    An MCP server for TouchDesigner that lets AI agents inspect, build, wire, optimize, and stabilize live TD networks with 106 tools, plus a technique memory system for reusable patterns.
    100
    8
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/XTNX/td-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server