loading…
Search for a command to run...
loading…
MCP server embedded in TouchDesigner with 45 tools for creating operators, setting parameters, wiring connections, managing externalizations, and building real-
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.
Create at the speed of thought.
Full Documentation | Manifesto | Changelog
Embody puts your ideas on screen as fast as you can describe them. Operators, connections, parameters, the works. Want to try a different direction? Spin up a new approach in seconds. Compare attempts side by side. Branch off the one that works. The tool keeps up with you, instead of the other way around.
Envoy — forward velocity. An embedded MCP server lets Claude Code, Cursor, and Windsurf talk directly to your live TouchDesigner session. Create operators, wire them up, set parameters, write extensions, debug errors — by saying what you want. No copy-pasting code. No describing your network in chat. Idea → operators in seconds.
Embody — lateral velocity. Tag any operator and Embody externalizes it to files on disk that mirror your network hierarchy. Try a new direction, branch off a good one, restore the state from yesterday — all in seconds. Your externalized files are the source of truth, so every project opens already in flow.
TDN — the substrate that makes both possible. TouchDesigner networks exported as human-readable JSON. The format is what lets your AI agent understand what's on the screen, what lets you diff one attempt against another, and what lets a network reconstruct itself from text on the next project open. TDN is what makes the rest of this possible.

| What | Why it matters | |
|---|---|---|
| 🤖 | Envoy MCP Server | 49 tools let your AI assistant build, wire, parameterize, and debug live networks. The first time you watch it happen, you stop typing operator names by hand for good. |
| 📄 | TDN Network Format | Networks become text. Diff two versions, revisit any version, hand an LLM a complete picture of what's on screen — all from a single .tdn file. |
| 📦 | Automatic Restoration | Externalized operators rebuild themselves from disk on every project open. The .toe is no longer the source of truth — your files are. |
| 📤 | Portable Tox Export | Pull any COMP out as a self-contained .tox with external references stripped. Ship a piece of your project anywhere. |
Embody writes externalized files relative to your .toe location — no special folder structure required. Embody works in any project folder; if you happen to use git, every change is also a clean diff for free.
my-project/ ← project folder (optionally a git repo)
├── my-project.toe ← your TouchDesigner project
├── base1/ ← externalized operators
│ ├── base2.tox ← COMP (TOX strategy)
│ ├── base3.tdn ← COMP (TDN strategy — diffable JSON)
│ └── text1.py ← DAT
└── ...
.tox from /release and drag it into your TouchDesigner projectlctrl twice to tag and externalize itctrl + shift + u to update all externalizations, or ctrl + alt + u to update only the current COMP. On project open, Embody restores everything from disk automaticallyTip: If no operators are tagged, Embody will externalize all eligible COMPs and DATs, which may slow down complex projects. Tagging selectively is recommended.
| Shortcut | Action |
|---|---|
lctrl + lctrl |
Tag or manage the operator under the cursor |
ctrl + shift + u |
Update all externalizations |
ctrl + alt + u |
Update only the current COMP |
ctrl + shift + r |
Refresh tracking state |
ctrl + shift + o |
Open the Manager UI |
ctrl + shift + e |
Export entire project to .tdn file |
ctrl + alt + e |
Export current COMP to .tdn file |
For supported formats, folder configuration, duplicate handling, Manager UI, and more — see the Embody docs.
Embody includes Envoy, an embedded MCP server that gives AI coding assistants direct access to your live TouchDesigner session.
Envoyenable parameter on the Embody COMPlocalhost:9870 (configurable via Envoyport).mcp.json in your git repo root.mcp.json automaticallyIf your project isn't in a git repo, add .mcp.json manually to your project root:
{
"mcpServers": {
"envoy": {
"type": "http",
"url": "http://localhost:9870/mcp"
}
}
}
| Tool | What It Does |
|---|---|
create_op |
Create any operator type in any network |
set_parameter |
Set values, expressions, or bind modes on any parameter |
connect_ops |
Wire operators together |
execute_python |
Run arbitrary Python in TD's main thread |
export_network |
Export networks to diffable .tdn JSON |
create_extension |
Scaffold a full extension (COMP + DAT + wiring) |
get_op_errors |
Inspect errors on any operator and its children |
...and 37 more. See the full tools reference.
When Envoy starts, it generates a CLAUDE.md file in your project root with TD development patterns, the complete MCP tool reference, and project-specific guidance.
TDN (TouchDesigner Network) is the file format that makes the rest of Embody possible. It exports an entire operator network — operators, connections, parameters, layout, annotations, DAT content — as a single human-readable JSON file. Your AI agent can read it. You can read it. Any text tool can diff it. The network can rebuild itself from it on the next project open.
This is the substrate. Every other capability — AI-driven building, version control, automatic restoration — builds on top of it.
ctrl + shift + ectrl + alt + eexport_network / import_network MCP toolsSee the full TDN specification for format details, import process, and round-trip guarantees.
Embody provides a multi-destination logging system:
dev/logs/<project_name>_YYMMDD.log, auto-rotates at 10 MBPrint parameter to echo logsget_logs MCP toolop.Embody.Log('Something happened', 'INFO')
op.Embody.Warn('Check this out')
op.Embody.Error('Something broke')
Embody includes 61 test suites (1,469 tests) covering core externalization, MCP tools, TDN format, the Envoy server/bridge, and palette catalogs. Tests run inside TouchDesigner using a custom test runner with sandbox isolation.
op.unit_tests.RunTests() # All tests (non-blocking)
op.unit_tests.RunTests(suite_name='test_path_utils') # Single suite
op.unit_tests.RunTestsSync() # All in one frame (blocks TD)
Via Envoy MCP: use the run_tests tool. See the full testing docs for coverage details and how to write new tests.
For more, see Troubleshooting.
See the full changelog for detailed version history.
Recent releases:
.py/.glsl/.json) use TouchDesigner's bidirectional syncfile, so they're never "unsaved" the way TOX/TDN COMPs are -- their only meaningful "changed" state is git-relative (on disk but not committed). Embody now surfaces that as a distinct orange Strategy badge for any externalized file that's saved-but-uncommitted -- TOX, TDN, and DAT alike -- keeping the two axes visually separate: red = unsaved (live network vs on-disk file, which git can't see), orange = saved but uncommitted, green/blue = clean and committed. The git status scan runs on a worker thread (off the main thread, so it never stalls a Refresh frame) with a pure string-based result-to-operator mapping -- the previous synchronous version cost ~121ms per Refresh (a git status subprocess plus a Path.resolve() per tracked file); it's now a ~6ms async enqueue. Plus a changed filter keyword (shows only unsaved-or-uncommitted rows, with ancestors so the tree stays intact), lowercase Strategy labels (tox/tdn/tag, matching the file-extension labels), a themeable Uncommittedcolor parameter, and a shipped refresh-after-commit rule that has AI agents pulse an Embody refresh after committing so the orange -> clean transition shows immediately. Runtime-only (never written to externalizations.tsv) and self-disabling outside a git repo -- zero behavior change for non-git users. Hardened by a 3-agent codex review (no regressions found): the background scan runs git --no-optional-locks status --untracked-files=all (no .git/index.lock contention with concurrent commits; new files in fresh folders enumerated), and async completion is generation-guarded so a stale scan can't clobber a newer one. Release .tox verified by a fresh-install smoke test. Test suite 61 suites / 1,469 tests (+20).diff_tdn MCP tool -- the UNSAVED view git can't give: it diffs the live in-memory TDN network against its on-disk .tdn, answering "what have I changed but not saved?" Pass a COMP path or a .tdn file path/bare filename ("tooltip.tdn", resolved to its COMP) for one COMP in full per-field detail (old=disk, new=live), or omit the target for a whole-project summary across every live TDN COMP. The comparison is semantic, not byte-level (same type_defaults/par_templates normalization the format uses; volatile export header ignored), matched by name per level so reorders are clean and a deep child edit marks only that child; truncation is honest. Engine in TDNExt (DiffLiveVsDisk/DiffAllLiveVsDisk + pure _diff_normalized), reusing existing expanders with zero duplication, verified by a 5-reviewer panel. A companion .tdn git diff driver (a textconv auto-installed by Embody: *.tdn diff=tdn + .embody/tdn_textconv.py + git config) strips the volatile export header so git diff/log/show on a .tdn are clean for committed/history diffs -- the two paths are complementary (diff_tdn for unsaved, git for committed). get_externalizations/get_externalization_status surface strategy, absolute_path, and a recommended_tool: diff_tdn hint so "what changed in comp.tdn?" routes correctly; validated with a hint-free agent that called diff_tdn (not git diff) for both single-COMP and project-wide questions. Also: a project-wide hard rule against caching extension references (cached op.X.ext.Y points at a dead instance after TD reinitializes the extension) -- the whole codebase swept inline and enforced by test_no_ext_caching.py; and a fix for the recurring "TouchDesigner is not responding" error during saves -- the STDIO bridge now retries a failed forward for up to 30s while the TD process is alive (forward_with_busy_retry) instead of surfacing a transient save-time stall as a dead server. Docs-site nav pills made readable over content (frosted-glass). Test suite 60 suites / 1,449 tests (+36), all green; release .tox verified by a fresh-install smoke test.Template Master Name parameter (Templatemaster, default __template__): when a group of operators sharing one external path has exactly one whose path contains that name as a whole segment (e.g. a __template__ parent COMP), it's auto-selected as master and the rest tagged clone with no prompt — the durable fix for the common template-plus-runtime-copies pattern (a scene_<id> chain each carrying the template's externalized DATs). Opt-in by convention (projects not using the name are unaffected; set your own like _master, or clear to always choose manually); matches a whole segment not a substring, and only on an unambiguous single match. The manual prompt no longer shows N identical same-named buttons — each is now labeled by the differing path segment, numbered to the dialog body (1: __template__, 2: scene_1exalohf, …) — and groups larger than 5 operators get a Keep first as master / Dismiss strategy prompt instead of an unreadable button row. Test suite 57 suites / 1,413 tests (+12), all green.tdn_exclude — a Tdnexcludetag parameter (default tdn_exclude) that makes a COMP invisible to the TDN system (never exported, stripped, or reconstructed), the durable opt-out for app-managed children under cascade-autotag (runtime op.copy() content like Moonshine's proj_<id> chains). Exclusion is honored at a TDN boundary's direct children; a COMP tagged but nested under a non-excluded intermediate is serialized as normal content and preserved (with a warning) rather than silently lost. TDN dirty detection rebuilt: the fingerprint captures each operator's non-default authored parameter values (expr/bindExpr/val, never par.eval()), so a parameter edit flags a TDN COMP dirty while a dependency-driven change to a live expression's evaluated value (animation, audio, a moving CHOP) does not — no more perpetual re-export churn on animated COMPs. The sweep is consolidated to one fingerprint per Refresh (was two) for a real frame-time win on large networks; DirtyCount reads the fingerprint result instead of the always-True oper.dirty; a reverted edit clears the dirty flag. Envoy resilience: startup status waits for a real bind handshake before reading "Running" (no optimistic lie, including uvicorn SystemExit); restart_td validates real non-zombie TouchDesigner PIDs via ps; the status moved into the window header, prefixed "Envoy". Three issue #21 crash fixes hardened across the whole table-read surface: captureParameters can't crash on broken expressions (authored read, no eval), _cellVal guards every externalizations-table read (and warns on genuine row-level corruption), and onProjectPreSave is fail-safe end-to-end so a hook exception can't truncate the .toe. Plus: the "TDN Content at Risk" dialog gains a persistent Always Skip; externalizations.tsv no longer churns phantom timestamp rows per save; a calmer first-launch palette scan; the manager expand/collapse glyph on top-level rows; a duplicate-BOM fix in WindowHeaderExt; an AI-first Quickstart page; MCP tool count reconciled to 48. A final 7-angle regression review of the branch caught and fixed the live-expression churn, the nested-exclude data loss, the always-dirty count, the stuck dirty flag, the duplicate BOM, and the double sweep — each with a regression test. Verified by a fresh-install smoke test of the release .tox. Test suite 57 suites / 1,401 tests, all green.Custom for AI Project Root (follow-up to Ten0's feedback on issue #19) — paired with a new AI Project Root (Custom) Folder parameter that's greyed out unless the menu is set to Custom. The custom path can be absolute or relative to the .toe directory (e.g. ../). For monorepos where multiple .toe files share a parent directory, each .toe sets the same relative path and they all converge on one AGENTS.md / .claude/ / .mcp.json / .embody/envoy.json — which lets the multi-instance MCP feature work naturally across sibling projects. Flipping the menu or changing the path migrates Embody state and AI config to the new location, same atomic move + marker-aware cleanup as the gitroot↔projectfolder flip. Plus two defense fixes from earlier in the cycle: _messageBox no longer falls back to a real modal dialog when seeded test responses are exhausted (would freeze TD with stacked dialogs after a test run); Verify() won't re-queue the Envoy opt-in prompt while one is already pending (idempotent flag — multiple Verify() calls during a test's Disable/Enable cycle can no longer stack N prompts). _findSettingsFile got a walk-up fallback to handle the Custom-mode chicken-and-egg (saved custom path lives in config.json which can't be read until settings are restored — so walk up from the .toe looking for any .embody/config.json)..tdn files no longer embed the contents of TOX-externalized child COMPs — emits a tox_ref pointer instead, mirroring the existing tdn_ref pattern; round-trip restoration handled by a new Phase 8.5 (_restoreTOXShells) that sets externaltox and calls _reloadTox after import. Plus _validateTOXRefs for cross-validation parity, the _stripNestedTOXChildren backward-compat path for pre-v1.4 files, and TDN format version bumped to 1.4 with the schema updated accordingly. Envoy-toggle frame-drop fix surfaced during diagnosis — _findAvailablePort was paying a 1.5s time.sleep on the main thread (108 frames at 60fps) whenever the preferred port was held by any listener, including foreign zombie TD processes the wait could never free. Now skips the wait when force-close has nothing of ours to close (caps at 500ms when it does), and branches directly to range-scan when the holder is a foreign live instance in our registry. Measured: Start time 1797ms → 346ms. (2) AI Project Root parameter (gitroot default / projectfolder) for monorepo setups where the TouchDesigner project lives in a subdirectory of a larger repo — flips Embody's AI/MCP config target between the git root and the .toe directory, with full migration of persistent state and marker-aware cleanup of the old root that preserves user-authored files. Issue #19 fix — the Path.home() length comparison in _findProjectRoot/_findGitRoot/_checkOrInitGitRepo bailed before searching when the project lived on a non-home drive (Windows D:\ with home on C:), so subsequent runs failed to find .git, duplicated .mcp.json config, and broke the MCP connection. Also: registry I/O (RefreshRegistry, _removeFromRegistry, port-conflict detection) routes through _findProjectRoot() for consistency under projectfolder mode; _atomicMove helper (copy-to-tmp + os.replace + unlink) makes cross-filesystem catalog moves safe against partial writes; settings-restore checks both Aiprojectroot candidate roots; orphan-handling renames any leftover critical files after a failed migration to .orphan so fallback lookups don't pick up stale data; legacy file sweep removes old .envoy.json, .embody.json, .envoy-tools-cache.json, and .claude/envoy-bridge.py from previous Embody versions. 6 new tests; test_tdn_file_io 66 → 92._isPidAlive(pid) was built on os.kill(pid, 0) — on Windows, CPython implements that as OpenProcess(PROCESS_ALL_ACCESS) + TerminateProcess(handle, sig) for all sig values, so the "liveness check" literally told the OS to kill the process being checked. Any time _writeEnvoyConfig's GC loop iterated instances and the row contained the running TD's own PID (which it does any time the project has been saved with Envoy enabled), the loop called TerminateProcess(self_handle, 0) and TD exited with code 0 — silent, no traceback, repro fingerprint matched perfectly. Replaced with safe OpenProcess(SYNCHRONIZE) pattern via ctypes (already in use by envoy_bridge.is_process_alive); SYNCHRONIZE access doesn't include termination rights. Also rewired the duplicate os.kill inside _findAvailablePort that would have killed any foreign live TD whose registry entry shared the port. Plus: CatalogManager palette scan now snapshots and restores time.play/time.rate/cookRate/realTime around each chunk so a misbehaving palette component (e.g. the refactored Palette:logger v2.7.0 on TD 2025.32820) can't leave the timeline paused; _verifyMcpImportable short-circuits when mcp.server is already loaded instead of tearing down 82 submodules every toggle; bridge find_all_td_pids() filters CEF/Web Render helper subprocesses that were generating ~214k phantom "TD process detected" log lines per session; _osLabel() disambiguates Windows 11 from Windows 10 (both report NT 10.0 — discriminator is build ≥22000); execute_src_ctrl reads/writes README as UTF-8 so emoji don't crash the bumper on Windows non-UTF-8 code pages. 19 new tests across 3 new files. Test file count 50 → 53.EmbodyExt.Update() rename-detection used self.ownerComp (an EnvoyExt-only attribute) instead of self.my. Every Update tick during a save threw AttributeError, which got caught and logged at WARNING but meant the rename-detect path never actually fired. Layer 2 walk-forward in the bridge masked the symptom (lookups still resolved correctly), but the registry would have stayed perpetually keyed to the previous version. One-character fix._writeEnvoyConfig now garbage-collects dead-PID rows on every write — registries that previously accumulated dead entries across sessions (hard kills, force-quits, crashes) collapse to live-only on the next save (verified: 28 rows → 1 in one cycle). EmbodyExt.Update() caches _last_toe_name and triggers RefreshRegistry() on basename mismatch — defensive backstop for execute.py's postSave hook in case it didn't reload. Bridge handle_launch_td adds a PID-aware slow-path scan after the fast-path key lookup: walks-forward each alive instance's registered toe_path and refuses if any resolves to the same target — fixes the stale-key + walk-forward composition that could otherwise spawn a duplicate TD. 6 new tests across test_envoy_registry and test_envoy_bridge.envoy.json registry walks forward across TD's save-time .toe version bump (Foo-5.398.toe → Foo-5.399.toe). Two-layer fix: EnvoyExt._instanceKey and _writeEnvoyConfig detect a PID's existing row under a different basename and rename + prune in one write; new RefreshRegistry() is called from onProjectPostSave so the registry walks forward even when Envoy doesn't restart. Bridge-side defensive layer: find_latest_versioned_toe() strips <digits>.toe and finds the highest-versioned sibling on disk; resolve_toe_path() now reads from instances[active] (was legacy-flat-only) and routes through the walk-forward helper. Plus a hotfix for the postSave's 'EnvoyExt' object has no attribute 'port' crash — RefreshRegistry() now reads the running port from envoy.json by PID instead of a nonexistent instance attribute. 20 new tests across test_envoy_registry (7) and test_envoy_bridge (13), one updated for the v5.0.399 instance-specific guard.edit_dat_content MCP tool for token-efficient surgical text edits — mirrors Claude Code's Edit tool (old_string/new_string, unique match by default, opt-in replace_all), so a 2-line edit in a 500-line DAT no longer pays for the whole DAT's content on the wire. Bridge multi-launch fix: Envoy can now launch a TD instance alongside an unrelated TD project — handle_launch_td swapped the blanket "any TD running" guard for an instance-specific check, macOS open -n flag forces a new process instead of reusing an existing window, and PID identification diffs against a pre-launch snapshot instead of returning the first TD pid found. Plus 11 new tests for the new tool and a one-line fix for test_set_dat_content_clear that had been failing since v5.0.397's wipe guardrail.Update() raced with EnsureCatalogs(), which sets Status='Scanning defaults (X/N)' to show progress. The old gate if Status != 'Enabled': return returned early on that transient value, so _pending_envoy_prompt was never consumed and the Envoy opt-in dialog never appeared. Both gates (Update, ReconcileMetadata) now short-circuit only when Status is explicitly 'Disabled'. Plus 2 regression tests that fail without the fix.confirm_wipe guardrail on set_dat_content MCP tool blocks accidental DAT wipes from malformed agent calls (refuses text="", rows=[], or clear=True with no replacement unless explicitly confirmed); TDN at-risk dialog skips TD-managed read-only DAT types (Info, WebRTC, Folder, Monitors, device-discovery, etc.) so the warning only fires for content the user actually authored; .embody/config.json now byte-stable across saves via sorted iteration of _PERSISTED_PARAMS + sort_keys=True (issue #18); test debt cleanup of 28 orphan .txt files, 3 stale envoy_bridge stubs replaced with 6 real tests, ancestor_rename tearDown leak fixed, palette tdn_reconstruction tests aligned with current production contractNo module named 'mcp.server' — _setupEnvironment now returns bool, four previously-silent failure paths log explicit errors, Start() aborts before _runServer if deps aren't ready, and a final import mcp.server gate catches partial installs (issue #17)subprocess.run from inside TD raised [WinError 50] The request is not supported because TD's GUI process stdin handle isn't duplicatable, causing Embody's venv-verify to falsely flag healthy venvs as corrupt and shutil.rmtree them on every TD restart. Fixed by passing stdin=subprocess.DEVNULL on the 5 affected subprocess.run sites in the bootstrap and verify-venv paths.embody/project.json + Envoy bridge auto-discovers the matching install on fresh clones), thread-conflict fix in the MCP update checker, and a 21-assertion cleanup of bridge tests that had been silently broken since the bridge v2 refactor — bridge tests now 148/151 passing, zero failuresAuto-resolve all / Review individually / Dismiss instead of a separate modal per groupiop.* expressions), and a cleaner list UI moving the tree expand/collapse indicator into a dedicated columnMove no longer fails with "source folder not found" (Issue #16), new render-coordinate-system rules for TD's bottom-left origin convention (Issue #14)read_tdn MCP tool (~20-90× fewer tokens than get_op walks), combined DAT+storage Content Safety dialog with "Never Ask" footgun removed, palette detection fix for native buttonCOMP operators, migration nudge for upgrading users, docs + landing page rewrite.embody/ folder, fix bridge path resolution/local path prohibition, TD connectivity recoveryswitch_instance tool, auto-suffix collision avoidancerestart_td meta-tool, local MCP handshake, operator overlap warnings/validate command, test runner dialog fixtag_for_externalization → externalize_op, clarify single-step workflowOriginally derived from External Tox Saver by Tim Franklin. Refactored entirely by Dylan Roscover, with inspiration and guidance from Elburz Sorkhabi, Matthew Ragan and Wieland Hilker.
Run in your terminal:
claude mcp add embody -- npx CSA PROJECT - FZCO © 2026 IFZA Business Park, DDP, Premises Number 31174 - 001
Security
Low riskAutomated heuristic from public metadata — not a security guarantee.