Architecture
tdmcp is three programs that talk to each other on your machine:
AI assistant tdmcp server TouchDesigner
(Claude / Cursor) ──▶ (Node / TypeScript) ──▶ (the Python bridge inside TD)
"make a feedback MCP tools + the creates / connects /
tunnel from noise" operator knowledge base inspects / previews nodes- The AI assistant is any MCP-capable client — Claude Desktop, Claude Code, Codex, or Cursor. It's where you describe what you want.
- The tdmcp server is a small Node program. It exposes a set of TouchDesigner "tools" and an embedded operator knowledge base to the AI over the Model Context Protocol.
- The bridge is a Python package that runs inside TouchDesigner (behind a Web Server DAT). It's what actually drives TD. See Bridge & REST API.
MCP client ──stdio──▶ tdmcp server (Node/TS) ──HTTP──▶ TouchDesigner bridge (Python)
├── Layer 1 artist tools (create_visual_system, …)
├── Layer 2 building blocks (create_node_chain, …)
├── Layer 3 atomic ops (create_td_node, …)
├── Knowledge base (MCP resources)
├── Recipes (validated network templates)
└── Feedback engine (errors / preview / performance)The three tool layers
Tools are organized into layers so the AI can pick the right altitude for a task. See the full, always-current list in the Tools reference.
- Layer 1 — artist tools. Describe a result (
create_feedback_network,create_audio_reactive,create_generative_art, …) and get a whole network, wired and arranged, often with a control panel ready to perform. - Layer 2 — building blocks. Mid-level pieces (
create_node_chain,connect_nodes,create_control_panel,animate_parameter,create_external_io, …) for assembling and controlling networks by hand. - Layer 3 — atomic operations. Single-node CRUD plus inspection, analysis, rendering and the Python escape hatches (
create_td_node,find_td_nodes,get_td_node_errors,execute_python_script, …).
A separate group of vault tools bridges an Obsidian vault and TouchDesigner.
The create → verify → preview loop
Every high-level build follows the same loop so the AI can see and fix its own work instead of guessing:
- Create the network from a recipe, GLSL pattern, or generated Python.
- Verify by reading cook/compile errors (
get_td_node_errors,summarize_td_errors). - Preview by capturing the output TOP as an inline image (
get_preview).
Generated networks are auto-arranged into a readable left→right layout (arrange_network) instead of piling nodes on top of each other.
Knowledge base
The server ships with an embedded, offline reference so the AI uses real operators rather than inventing them: 629 operators, 68 Python classes, workflow patterns, GLSL techniques, tutorials, 7 TouchDesigner release profiles, 45 operator compatibility entries, 9 Python API compatibility classes, 6 experimental build series, 7 technique packs with 39 techniques, and 6 TouchDesigner class references. These are exposed as MCP resources the AI can read on demand:
tdmcp://operators/{category|name} · tdmcp://python-api/{class} · tdmcp://operator-connections/{operator} · tdmcp://operator-examples/{operator} · tdmcp://td-versions/{version} · tdmcp://td-experimental/{series_or_category} · tdmcp://compat/operators/{operator} · tdmcp://compat/python/{class_or_member} · tdmcp://techniques/{category} · tdmcp://td-classes/{family} · tdmcp://patterns/{name} · tdmcp://glsl/{name} · tdmcp://glsl-snippets · tdmcp://recipes/{name} · tdmcp://recipes/search/{query} · tdmcp://tutorials/{name} · tdmcp://commands · tdmcp://prompts · tdmcp://cheatsheets · tdmcp://learning/touchdesigner
The knowledge base is committed to the repo, so a fresh clone needs only npm install && npm run build. npm run import:bottobot regenerates it from @bottobot/td-mcp and is only needed to refresh it.
Creative RAG (optional, opt-in)
Creative RAG is a separate, opt-in subsystem that lives outside the TouchDesigner bridge. It is not a tool layer, it does not touch TD, DMX, fixtures or any Python exec path. It is a CLI subcommand (tdmcp creative-rag {sync|index|search}) plus two read-only MCP resources (tdmcp://creative/cards/{id} and tdmcp://creative/search), both registered only when TDMCP_RAG_ENABLED=1. Its only outbound calls are HTTPS to four open-data museum APIs (during explicit sync) and POST /api/embed to a local Ollama process (during index and search). Repertoire, not policy — search results never trigger an in-TD action.
Recipes
Recipes are validated network templates (JSON) the AI can instantiate with apply_recipe. They cover common starting points — feedback tunnels, reaction-diffusion, particle galaxies, audio spectrum bars, projection mapping and more. See the Recipe gallery for what each one builds, and the repository's CONTRIBUTING.md guide to add your own.
Transports & events
The server speaks two MCP transports:
- stdio (default) — for local clients like Claude Desktop and Claude Code.
- Streamable HTTP (
TDMCP_TRANSPORT=http) — serves MCP atPOST/GET/DELETE /mcpon loopback with stateful sessions, for remote/headless setups. See Deployment.
It can also subscribe to a WebSocket event stream from TD (node.created / node.deleted / node.error / project.saved / timeline.frame / node.cook) and forward events as MCP logging notifications. High-frequency events (timeline.frame, node.cook) are dropped unless explicitly opted in. Toggle with TDMCP_EVENTS.
Calling tdmcp from inside TouchDesigner (LOPs)
dotsimulate's LOPs "MCP Client" can run inside TouchDesigner and spawn this server over stdio, closing a loop: TD (LOPs MCP Client) → node dist/index.js (tdmcp) → HTTP → the TD bridge on 127.0.0.1:9980 (the same TD) → the network. No transport change is needed (stdio is the default). Because the client lives in TD with no documented env field, point its command at the scripts/tdmcp-lops.mjs wrapper, which injects the hardened profile (TDMCP_RAW_PYTHON=off, TDMCP_TOOL_PROFILE=safe). See the LOPs integration guide.
Security
The TouchDesigner bridge runs arbitrary Python inside your TD process — that is what lets the assistant build networks for you. Treat it like an open door to the machine TD runs on:
- The Web Server DAT binds its port (default
9980) on all interfaces, but the bridge is loopback-only by default. A request from a non-loopback peer address is rejected immediately (HTTP403), before routing, authentication, or any tool runs, so reaching the port from another machine is not enough to drive it. To expose the bridge to a LAN, setTDMCP_BRIDGE_ALLOW_LAN=1in TouchDesigner's environment (also acceptstrue/yes/on) — and pair it withTDMCP_BRIDGE_TOKEN. Even then, only run it on a trusted network and/or firewall the port. (Some TouchDesigner builds don't surface the peer address to the callback; where that's the case theOrigin/Hostguards below are the defense.) - Turn on bridge auth for untrusted networks: set
TDMCP_BRIDGE_TOKENto a shared secret in both the server's environment and TouchDesigner's environment. The bridge then rejects any request without a matchingAuthorization: Bearer <token>(HTTP401). Unset (default) keeps the zero-config local flow. TDMCP_RAW_PYTHON=offhides client-authored Python tools, includingexecute_python_script,exec_node_method,create_python_script, and persistent Script callbacks fromauthor_script_operator. It is not a code-execution kill-switch: many higher-level tools still send their own templated Python to the bridge. The bridge-side arbitrary-code endpoints (/api/exec, node-method) are default-closed unlessTDMCP_BRIDGE_ALLOW_EXEC=1is explicitly set in TouchDesigner's environment;TDMCP_BRIDGE_TOKENauthenticates requests but does not enable exec by itself. Structured endpoints keep working.- The MCP server binds to loopback (
127.0.0.1) for both transports and enables DNS-rebinding protection on HTTP. - The bridge refuses browser cross-origin requests. Any request carrying an
Originheader that isn't loopback is rejected (HTTP403), and aPOST/PATCH/PUTwhoseContent-Typeis present but notapplication/jsonis rejected too — that blocks the simple cross-site form/fetchPOSTs a malicious web page could aim at the bridge (CSRF / DNS-rebinding → drive-by code execution). The MCP server sends noOriginand always usesapplication/json, so normal use is unaffected. - The local copilot chat UI (
tdmcp chat) applies the same guard. It binds to loopback and rejects (HTTP403) any request whoseHostorOriginisn't a loopback name, so a page the artist visits can't drive node CRUD against the live project viahttp://127.0.0.1:<chat-port>/chat.
All of these are configured through environment variables.
Known limitations
- WebSocket events are forwarded as MCP logging notifications on both transports; high-frequency events are dropped unless opted in.
- Audio / particle / 3D builders and the exotic recipes (kinect, LED, projection) produce valid, connected networks but use best-effort TD parameter names — fine-tuning may be needed, and they emit warnings to that effect.
- Preview returns the TOP at its native resolution (the requested size is advisory).
- The bridge ships as Python modules plus a callbacks template (a binary
.toxcan't be generated from source); the one-line installer assembles it for you.