Skip to content

Tools Reference

Envoy exposes 68 MCP tools for interacting with TouchDesigner, plus 23 bridge meta-tools: 6 TD-lifecycle tools and 17 convoy_* LAN work-relay tools (all listed below). All tools use the standard MCP protocol and can be called by any compatible client.

Two of the 62 (convoy_lifecycle_state, convoy_lifecycle_quit) are internal Convoy host-lifecycle tools: they refuse any session other than the Convoy host app's dedicated loopback session and are not for agent use.

Every mutating TD-authoring tool call is wrapped in a TouchDesigner undo block. Press Ctrl+Z in TD to revert an agent change; a batch_operations call is one undo step for the whole batch.

Responses are compact by default; opt-in flags such as include_defaults and details return full detail when needed.

Operator Management

Tool Parameters Description
create_op parent_path, op_type, name? Create a new operator (e.g., baseCOMP, noiseTOP, textDAT, gridPOP)
create_extension parent_path, class_name, name?, code?, promote?, ext_name?, ext_index?, existing_comp? Create a TD extension: baseCOMP + text DAT + extension wiring, initialized and ready to use
delete_op op_path, override? Delete an operator. Also purges its externalization tracking (any strategy) and the externalized file — unless the file is clone-owned or still referenced by another operator. Refused while another live session claims the scope or wrote it in the last minute; override=True bypasses that gate. Refuses the Embody COMP, its ancestors, / and Envoy's own extension DAT (envoy.embody.host_destroy_refused); override=True does not bypass that check
copy_op source_path, dest_parent, new_name? Copy operator to new location
rename_op op_path, new_name Rename an operator
get_op op_path, include_defaults? Get operator info. Parameters are NON-DEFAULT only by default; pass include_defaults=True for all parameters. Parameter-heavy COMPs are expensive in full detail, so prefer read_tdxn for structure reads. inputs has one entry per input connector, null for an empty one — entry position IS the real connector index
query_network parent_path?, recursive?, op_type?, include_utility? List operators in a container. Child rows are compact: path, type, family, depth (name is derivable from the last path segment). Set include_utility=True to include annotations
find_children op_path, name?, type?, depth?, tags?, text?, comment?, include_utility? Advanced search using TD's findChildren — filter by name pattern, type, depth, tags, text content, or comment
cook_op op_path, force?, recurse? Force-cook an operator

Parameter Control

Tool Parameters Description
set_parameter op_path, par_name, value?, mode?, expr?, bind_expr? Set a parameter's value, expression, bind expression, or mode (constant/expression/export/bind). Invalid Menu values are rejected with valid menuNames; sequence-block names auto-grow their sequence (const5name grows numBlocks to 6). A reload or clone pulse (enableexternaltoxpulse, reinitnet, enablecloningpulse) on the Embody COMP, an ancestor, / or Envoy's extension DAT is refused (envoy.embody.host_destroy_refused)
get_parameter op_path, par_name?, search?, search_in?, depth?, max_results?, details? Get one parameter compactly, or search parameters by glob/substring across a subtree. Search fields: name, value, expr, or any

Search mode omits par_name and passes search. It scans the target operator and children to depth (default 2) using fnmatch glob semantics; patterns without *?[ become contains searches. Results are {root, pattern, search_in, count, results, truncated?}, where each hit includes op, par, value, mode, and expr or bindExpr when present.

search_in='value' evaluates every parameter it scans (expressions included), so expression side effects and cooking cost are on the caller; search_in='any' only evaluates constant-mode values and matches expression/bind parameters by text.

Single-parameter mode returns path, parameter, value, mode, label, mode-specific refs (expression, bindExpr, bindMaster, exportOP), and menuNames for Menu parameters. Pass details=True to include defaults, custom/read-only/style metadata, numeric ranges, menuLabels, and menuIndex. Search mode ignores details.

Example -- find expressions under /project1 that reference absolute paths in that subtree:

{"op_path": "/project1", "search": "*/project1/*", "search_in": "expr", "depth": 10, "max_results": 100}

DAT Content

Tool Parameters Description
get_dat_content op_path, format? Get DAT text or table data ("text", "table", "auto"), or "stats" to reduce a table to per-column min/max/mean (numeric) or distinct counts (text) plus head/tail rows -- use it instead of dumping a large table into context
set_dat_content op_path, text?, rows?, clear?, confirm_wipe? Full-replace DAT content. Wipe guardrail refuses text="", rows=[], or clear=True with no content unless confirm_wipe=True is passed. For partial edits to text DATs, prefer edit_dat_content -- it sends only the changed substring.
edit_dat_content op_path, old_string, new_string, replace_all?, confirm_wipe? Surgical text edit on a DAT (mirrors Claude Code's Edit tool). Replaces old_string with new_string. By default old_string must appear exactly once -- pass replace_all=True to replace every occurrence. Token-efficient: only the changed substring crosses the wire. Text DATs only; use set_dat_content(rows=...) for tables.

CHOP / POP Data

Reduced reads -- never a blind dump. A 4x600 CHOP is 2,400 raw floats; these return the shape and range instead.

Tool Parameters Description
get_chop_data op_path, channels?, samples?, compare_to? Per-channel min/max/mean/std/first/last, capped at 32 channels. channels is a name glob; samples>0 adds head/tail raw values. compare_to another CHOP adds a diff of per-channel deltas -- the "what did this chain do to my data" read
get_pop_data op_path, attributes?, samples?, max_points? Point/prim/vert attribute metadata (name, size, type). Metadata costs ~0.02ms at ANY point count; reading point VALUES is a GPU->CPU readback (~9.5ms at 16k points, ~69ms at 160k -- about 4 frames at 60fps), so samples>0 is opt-in and refused above max_points (default 50,000)

Why the POP guard is on point count, not sample count: POP.points(attr, startIndex, count) accepts both slicing arguments and ignores them (verified on 2025.33070) -- the readback happens regardless. Asking for 4,096 points from a 160k POP measured slower than reading all of them. Reach for samples only when you actually need values, and prefer a smaller upstream POP for inspection.

The reduce-don't-dump contract for CHOP and DAT reads is adapted from the view tool in Marius Alwan Meyer's code-mode fork of Embody (sporqist/Embody, MIT). The POP reader and its readback ceiling are Embody's own.

Operator Flags

Tool Parameters Description
get_op_flags op_path Get all flags: bypass, lock, display, render, viewer, current, expose, selected, allowCooking
set_op_flags op_path, bypass?, lock?, display?, render?, viewer?, current?, expose?, allowCooking?, selected? Set one or more flags on an operator

Positioning & Layout

Tool Parameters Description
get_op_position op_path Get operator position, size, color, and comment
get_network_layout comp_path, include_annotations? Get compact positions of ALL operators (and annotations) in a COMP in one call. Operators include path, type, nodeX, nodeY, nodeWidth, nodeHeight; centers are derivable as nodeX+nodeWidth/2 and nodeY+nodeHeight/2. Annotation text is capped at 160 chars. Returns bounding_box
set_op_position op_path, x?, y?, width?, height?, color?, comment? Set operator position, size, color ([r,g,b] floats 0-1), or comment
layout_children op_path Auto-layout all children in a COMP

Annotations

Tool Parameters Description
create_annotation parent_path, mode?, text?, title?, x?, y?, width?, height?, color?, opacity?, name? Create an annotation. Modes: "annotate" (default, has title bar), "comment", "networkbox". Created utility=True (matching TD UI-drawn annotations): visible to get_annotations, hidden from query_network/find_children unless include_utility=True. Every op-path tool still resolves it by path; delete with delete_op (durable), never a raw .destroy()
get_annotations parent_path List all annotations in a COMP with their properties and enclosed operators
set_annotation op_path, text?, title?, color?, opacity?, width?, height?, x?, y? Modify properties of an existing annotation
get_enclosed_ops op_path Get operators enclosed by an annotation, or annotations enclosing an operator

Connections

Tool Parameters Description
connect_ops source_path, dest_path, source_index?, dest_index?, comp? Wire two operators together. Set comp=True for COMP connectors (top/bottom)
disconnect_op op_path, input_index?, comp? Disconnect an operator's input. Set comp=True for COMP connectors (top/bottom)
get_connections op_path Get all input/output connections (includes COMP connections for COMPs). Inputs are reported per connector: one entry per input connector carrying its real index, with connected_to: null for an empty connector — nothing is compacted away, so a wire on connector 2 reports as index 2. Dynamic multi-input ops (Switch, Composite) always show one trailing empty connector, the growth slot

Performance Monitoring

Tool Parameters Description
get_op_performance op_path, include_children? Get CPU/GPU cook times (milliseconds), memory usage (bytes), cook counts
get_project_performance include_hotspots? Get project-level FPS, frame time, GPU/CPU memory, dropped frames, active ops, GPU temp. Optional hotspot ranking of top N COMPs by cook time

Code Execution

Tool Parameters Description
execute_python code Execute Python in TD; set the result variable to return values. Auto-lints newly-created ops and emits a LAYOUT WARNING when they are left at (0,0) or overlapping (unlike create_op, raw comp.create() does not auto-position). Also statically lints the submitted source (as do set_dat_content / edit_dat_content for Python written to DATs) and emits a THREADING WARNING when a thread target calls TD's run() -- worker-side run() silently corrupts TD state and crashes later (Derivative-confirmed 2026-08-17). Refused before anything runs (envoy.embody.host_destroy_refused) when the code would destroy or reload the Embody COMP, an ancestor, / or Envoy's extension DAT in the same call -- me and parent() are the Embody COMP here; see Troubleshooting

Introspection & Diagnostics

Tool Parameters Description
get_td_info (none) Get TD version, build, OS, and Envoy version
get_op_errors op_path, recurse? Errors and warnings for an operator and its children. Covers all three surfaces TD reports red: cook errors, Python tracebacks from callbacks/DAT scripts/expressions (OP.scriptErrors, tagged kind: "script" in errors[]), and GLSL compile failures (separate shaderErrors key)
exec_op_method op_path, method, args?, kwargs? Call a method on an operator (e.g., appendRow, cook). destroy, reload, changeType and progressiveUnload on the Embody COMP, an ancestor, / or Envoy's extension DAT are refused (envoy.embody.host_destroy_refused)
get_td_classes (none) List all Python classes/modules in the td module
get_td_class_details class_name Get methods, properties, and docs for a TD class
get_module_help module_name Get Python help text for a module (supports dotted names like td.tdu)
get_docs query, section?, source?, max_chars? Look up official TouchDesigner docs. source is auto (offline then web), offline, or web; normal responses carry title, source, sections_available, content, and optional url/truncated; ambiguous offline lookups return source + matches only
get_guidance topic? Serve this project's checked-in TouchDesigner doctrine (.claude/rules/*.md and .claude/skills/*/SKILL.md) over MCP, so agents on a client with no skills folder (VS Code, Copilot, Windsurf) get the same rules Claude Code loads; Codex, Cursor, Gemini and Antigravity read .agents/skills/, OpenCode reads .claude/skills/. Bare call lists topics; topic returns that document. Answered worker-side (no TD round-trip)
get_focus (none) What the user is looking at: current pane network, selected operator(s), current op, and rollover. When the user says "this operator" they mean the SELECTED/current op -- rollover is incidental mouse position and must not be acted on

Embody Integration

Tool Parameters Description
externalize_op op_path, tag_type? Tag and externalize operator to disk (auto-detects type if omitted). Never shows the locked-content dialog: a TDXN export with locked TOP/CHOP/SOP/POP operators logs one WARNING (in _logs) naming each operator's source and the COMP to tag tox
remove_externalization_tag op_path, delete_file? Remove externalization tracking (tag + row + TDXN breadcrumb); delete_file=True also deletes the file (best-effort). Returns removed_tags, removed_rows, removed_anything, summary -- an operator can have a tracked row but NO tag, so check removed_anything, not removed_tags, to confirm cleanup
get_externalizations (none) List all externalized operators with status
save_externalization op_path Force save an externalized operator to disk. Refuses to overwrite a non-empty file with an operator-empty COMP and returns an error instead — untrack the operator or delete the file if the empty state is intended. Like externalize_op, it logs locked-content findings instead of showing the dialog
get_externalization_status op_path Get dirty state, build number, timestamp, file path

TDXN Format

Tool Parameters Description
read_tdxn comp_path?, include_dat_content?, max_depth?, embed_all? Preferred for reading ≥3 operators. Return the live network as a TDXN dict (in-memory, never written to disk). ~20-90× fewer tokens than a get_op walk thanks to default-omission, type_defaults, and par_templates compaction
export_network root_path?, include_dat_content?, output_file?, max_depth?, embed_all? Write a .tdxn file to disk. Same payload as read_tdxn plus file I/O and stale-file cleanup. Set embed_all=True to recurse into TDXN-tagged COMPs instead of skipping their children (self-contained export)
import_network target_path, tdn, clear_first?, override? Recreate a network from a .tdxn file. Nested externalized-TDXN children are rebuilt from their own .tdxn files in the same import, recursively, so no nested COMP is left an empty shell; the result's restored_tdn_shells lists what was restored. With clear_first=True, gated against live peer sessions like delete_op. clear_first into the Embody COMP, an ancestor or / is refused (envoy.embody.host_destroy_refused)
diff_tdxn target?, max_changed_ops?, max_bytes? What is UNSAVED in TDXN networks -- the live in-memory network vs the on-disk .tdxn, the view git cannot give. Omit target for a whole-project summary (every live TDXN COMP, which changed + counts); pass a COMP path OR a .tdxn file path/bare filename for one COMP in full per-field detail (old=disk, new=live). For committed/history diffs use plain git diff. Read-only, non-interactive

TOP Capture

Tool Parameters Description
capture_op op_path, format?, quality?, max_resolution?, inline? Capture any operator's current output as an image. A TOP is read natively; every other family (CHOP, SOP, POP, DAT, COMP, MAT) renders through a transient OP Viewer TOP -- what the network editor's viewer shows -- created inside the Embody COMP for the call and destroyed after (the capture waits a few frames for it to render, and the returned text says a viewer was used). Same temp-file return and Quality verdict as capture_top; max_resolution is the viewer's render width (16:9) for a non-TOP.
capture_top op_path, format?, quality?, max_resolution?, inline?, sample_grid? Capture a TOP's output as an image (TOP only; any other family -> capture_op). Saves to a temp file and returns the path -- Read that path to view it. Inline base64 previews are token-heavy, so they are off by default (inline=False); pass inline=True to also embed a small preview. Small images (<20 KB) include the inline MCP ImageContent preview when requested. Default: JPEG at 80% quality, max 640px long edge. Pass sample_grid>=2 to return a downsampled NxN RGBA grid instead of an image: row 0 is the top of the image, stats are computed over the full-resolution texture, the requested grid clamps to 2..32, the returned grid is further capped to the TOP's width/height and can drop below 2 on tiny textures, and image params are ignored. Channel padding: RG -> b=0/a=1, mono -> replicated/a=1, monoalpha -> replicated + real alpha; channels reports the raw plane count. Every capture also returns a Quality verdict from the raw float pixels (is_black / is_flat / fully_transparent / pass + fail_reasons), surfaced as a Quality: OK\|FAIL line so you can tell an empty/black/transparent render from a real one without reading the image (black and fully-transparent are failures; a uniform fill is advisory, flat_frame).

Multi-Session Awareness

Concurrent AI sessions (multiple Claude Code windows, other MCP clients) working on the same project are tracked, warned about each other, and gated away from destroying each other's work. See Multi-Session Coordination for the full picture.

Tool Parameters Description
get_sessions List connected AI sessions: label (repo@branch), idle time, recent_scopes it modified, claims it holds, plus you (the caller's own session id). May include worktrees: in-flight durable worktree tasks, visible even after their session ended
claim_scope scope, note?, ttl? Cooperative write lease on an op-path prefix, a file: path, or a project: scope. Peers' overlapping claims are refused while yours is live; their destructive operations on it are gated. Auto-renews on your own writes; expires on TTL or session silence
release_scope scope Release a lease you hold. Polite — expiry also handles it
announce_task title, scopes?, note? Announce a unit of work to the shared task ledger so parallel sessions see what is in progress and what is finished-but-uncommitted; active entries ride on get_sessions
update_task task_id, status?, note?, commit? Transition a ledger task (done_uncommitted / committed with sha / abandoned); any session may update any task, non-owner writes record updated_by
preflight_landing worktree_path Landing safety check for a git-worktree diff: intersects the files it would land with main-tree dirt, peer file: claims/touches, and unsaved live TDXN state. Run before porting any worktree diff; a conflicts verdict means reconcile first

Auto-piggybacked peer advisories

A _peers field rides on any response whose request touches territory another session modified in the last ~10 minutes — one entry per peer: {label, scope, tool, age_s, conflict}. conflict: true means a peer wrote an overlapping scope within the last minute and your operation is also a write — stop and coordinate.

Destructive-operation gate

delete_op, import_network with clear_first=True, run_tests, and batches containing them are refused with a MULTI-SESSION GATE error (naming the holder or recent writer) while a live peer session claims the scope or wrote it within the last minute. Pass override=True only when you are certain.

Logging

Tool Parameters Description
get_logs level?, count?, since_id?, source? Get recent log entries from ring buffer. Filter by level, source, or use since_id for incremental polling
run_tests suite_name?, test_name?, override?, background?, idempotency_key? Run test suites. background=True (recommended for full runs) returns a job id immediately and parks results in .embody/jobs/ -- poll get_job_status; the synchronous mode is severed by the watchdog suites' server restart. idempotency_key is background only -- a retry with the same key reconciles to the original run's job handle instead of starting a second run (passing it without background=True is refused). Gated while a peer session holds project:tests

Auto-piggybacked logs

When a tool call generates WARNING or ERROR entries since the previous call, the response carries a _logs field with up to the last 8 of them. INFO/DEBUG/SUCCESS history does not ride along — fetch it on demand with get_logs. Warning cursors are tracked per session, so concurrent AI sessions each receive their own copy — one session polling first no longer consumes a warning meant for everyone.

Unattended sessions

Nobody answers a dialog in an unattended session, so preset the Embody parameter that decides it: Tdxnpalettehandling (palette Black Box vs Full Export), Filecleanup (deleted-file prompt), Tdxnlockedwarn (quiet) and Tdxndatsafety (the save-time content report). Saves never prompt. Save through save_project and read the save's warnings in get_job_status(job_id)["warnings"]: the save reinitializes extensions, so they may never ride _logs.

Auto-attached recovery hints

When a tool returns an error, Envoy attaches a recovery_hints list — each entry {code, cause, action, next_tools}, matched to the real error string (path-not-found -> query_network/find_children, parameter-not-found -> get_op, wrong family, empty capture -> get_op_performance, thread conflict, timeout -> get_project_performance). Additive, never clobbers, never raises — follow the hint instead of retrying the same failing call.

Stable error codes

Every error envelope also carries error_code, a machine id of the form envoy.<area>.<condition>: envoy.op.not_found, envoy.par.not_found, envoy.parent.not_comp, envoy.op.wrong_family, envoy.top.empty, envoy.capture.failed, envoy.thread.violation, envoy.op.unknown_type, envoy.timeout, envoy.session.gated, envoy.dat.wipe_refused, envoy.project.unsaved, envoy.embody.unavailable, envoy.docs.lookup_failed, envoy.job.error, envoy.embody.host_destroy_refused (a destroy or reload of Envoy's own host, refused -- see Troubleshooting) — and envoy.error when no rule matches. Branch on the code, not the message: messages may be reworded, codes may not.

Write effects and shader lint

Every write tool's response may carry _effects: errors and warnings that appeared after your write, a meaningful fps drop, and — for DAT writes — the compile state of every GLSL operator that consumes that DAT, whether it is the DAT's dock host or references it by parameter from anywhere in the project. shaders_checked lists what was linted (a quiet footer means compiled clean, not unchecked), new_shader_errors the failures your write introduced, and shader_errors_persist a shader still failing after a later write, so silence never reads as fixed.

Background Jobs

Long operations that outlive the 30-second operation timeout run as disk-backed jobs: the starting tool returns a job_... handle immediately, results park in .embody/jobs/ (surviving server restarts and extension reinits), and get_job_status polls.

Tool Parameters Description
get_job_status job_id? One job record (status running/done/error, result when done, stale when a running record stopped updating), or the 16 newest records without job_id. A finished run_tests job carries the summary with failures listed first; a finished save_project job carries version_before/version_after and warnings (the WARNING/ERROR lines logged during the save: errors first, then oldest first, repeats collapsed, at most 8 plus a (+N more) entry; INFO lines stay in get_logs)
save_project idempotency_key? Save the project as a tracked job. Refused while a test run is active (a mid-run save bakes test-forced parameters into the export); idempotent -- a second call while a save is in flight returns the existing handle, and the same idempotency_key extends that dedupe to a retry of any age, reconciling it to the original save instead of queuing a second one. The next call after a save may fail once while the bridge reconnects. The finished record lists the save's warnings (warnings)
update_embody idempotency_key? Self-update Embody to the latest GitHub release as a tracked job -- bounded and non-interactive (sha256-pinned manifest, downgrade-refusing, TD-build-floor-gated), so it never needs the TD Python grant. Refused in Perform Mode and while a test run is active. A finished record carries version_before/version_after; an up-to-date node finishes done with them equal. The install restarts the MCP server -- expect one reconnect blip

Bridge Meta-Tools

These tools run locally on the STDIO bridge script, not inside TouchDesigner. They work even when TD is not running, or is frozen behind a modal dialog — this is how Claude Code can launch or restart TD without an active Envoy connection.

Tool Parameters Description
get_td_status (none) Check if TD is running, Envoy reachable, crash detection, process liveness, restart attempts remaining. envoy_unresponsive / unresponsive_since (process alive, port accepting, Envoy silent 60s or more) and main_thread_stalled / stalled_since (Envoy answers, but TD's main thread has not run its request loop for 60s or more) flag a frozen TouchDesigner
launch_td timeout?, project_path? Launch TD with the project's .toe file. Waits for Envoy to become reachable (default: 120s). Pass project_path (absolute, or relative to the git root) to open a different .toe
restart_td timeout?, project_path? Gracefully quit TD and relaunch. Waits for exit before relaunching (default: 120s). Pass project_path to relaunch with a different .toe. Targets only the active instance's verified process — other running TouchDesigner instances are never touched
list_dialogs instance?, screenshot? List the modal dialogs a TD instance shows (message boxes, missing-file and save-changes prompts, the license box, file pickers). Runs on the bridge, so it answers while TD's main thread is blocked. TouchDesigner draws its own dialogs, so their text is not readable through the OS: screenshot=true saves a PNG of each to the temp dir to Read. blocked=true when a dialog is up. macOS backend is unverified.
dismiss_dialog instance?, dialog?, action? Dismiss a blocking dialog and verify it is gone. action=auto (default) runs close (WM_CLOSE) -> escape -> enter until the window disappears; a single rung tries only that. dialog picks by title substring or window id (default: the first). The main TouchDesigner window is never a target. Failure is envoy.dialog.stuck with what was tried.
switch_instance instance?, all_sessions? List all registered TD instances (omit instance) or re-pin this session's bridge to a different running instance; other sessions are untouched unless all_sessions=true. For a single call, pass instance=<name> on any Envoy tool instead: the bridge routes that one call to the named instance and leaves this session's pin alone (an unknown or unreachable name fails only that call, with error_code envoy.instance.unknown / envoy.instance.unreachable). See Multiple Instances

Convoy Tools (LAN work relay)

The remaining 16 meta-tools drive Convoy, relaying work to Convoy-enabled Embody nodes on the trusted LAN through the local per-user host app. Status and inventory calls never wake TouchDesigner.

Tool Description
get_convoy_status Is the local Convoy host app available, and what does it know. host_status carries the host's advisories (each {kind, text}: realm_conflict, identity_reminted, handshake_refusals, peer_identity_changed, lan_bind), its lan posture (bound address, adapter, network category, warning or refusal reason), inbound_refusals (refused LAN handshakes in the last 10 minutes and which admitted peers sent them), identity_reminted and peers_mismatched
convoy_list_nodes Local and reachable remote nodes, with embody_version and host_app_version per node, offline_reason on an offline row (no_relay_port: the node heartbeats but its Envoy serves no relay port; heartbeat_stale; no_runtime: its TouchDesigner unregistered on exit), compatibility with compatibility_reason (protocol, shared-operation counts, the peer's host-app version), and a capabilities field (td_python, full_shell) on local nodes -- remote nodes carry none by design (grants are never advertised across the LAN; absent = unknown)
convoy_list_controllers Live client sessions, selected targets, leases, and active work
convoy_ping One node's liveness through its host app, without waking TouchDesigner
convoy_select_node Pin this session to one exact node so ordinary Envoy tools run there (clear=true to unpin)
convoy_call One-off registered operation on an explicit target, without changing the selection
convoy_batch The same ordered batch on one or more explicit targets, reported per target
convoy_get_job Check durable work that outlives the original call or reconnect
convoy_ack_job Acknowledge a finished delivery so the target can release its protected result artifacts
convoy_cancel_job Request cancellation from the exact owning host
convoy_forget_node Delete a stale node row on THIS machine's host app (refuses only while a delivery has not FINISHED, naming the blocking delivery ids; a finished result never holds a row); dead and long-unseen rows are also evicted automatically
convoy_get_artifact Retrieve and verify a large result into a temporary local file by artifact reference
convoy_save_artifact Verify an artifact and save it into the current project (overwrite=true required to replace)
convoy_start_node Reopen a previously registered, currently offline node
convoy_restart_node Safely replace one exact running TouchDesigner process (requires the current runtime id and an idempotency key)
convoy_update_embody Self-update Embody on Convoy nodes to the latest release -- one node (node=<id\|name\|hostname>) or the whole fleet (all=true) -- by dispatching the bounded update_embody operation as a durable per-node job (no TD Python grant involved). Skips offline, disabled, and Perform Mode nodes by name; poll the returned delivery handles with convoy_get_job. See Fleet Updates
convoy_owlette Optional read-mostly bridge to an Owlette site; fails closed without credentials

Bridge architecture

Claude Code connects to Envoy via a STDIO bridge script (.embody/envoy-bridge.py). The bridge translates between Claude Code's STDIO transport and Envoy's HTTP endpoint. It handles MCP protocol handshake locally when TD is down, so these meta-tools are always available. See Architecture for details.

Batch Operations

Tool Parameters Description
batch_operations operations Execute multiple operations in a single request. Reduces latency and token overhead

operations is a list of {"tool": str, "params": dict} objects. Each entry maps to an existing tool name and its parameters. Stops on first error.

When to use: 3+ calls to the same tool type (positioning, connecting, parameter setting, flags). Use execute_python instead when you need conditionals, loops, or computed values between operations.

Example — position 4 operators + connect them in one call:

{"operations": [
  {"tool": "set_op_position", "params": {"op_path": "/project1/noise1", "x": 400, "y": 0}},
  {"tool": "set_op_position", "params": {"op_path": "/project1/comp1", "x": 800, "y": 0}},
  {"tool": "set_op_position", "params": {"op_path": "/project1/level1", "x": 1200, "y": 0}},
  {"tool": "set_op_position", "params": {"op_path": "/project1/null1", "x": 1600, "y": 0}},
  {"tool": "connect_ops", "params": {"source_path": "/project1/noise1", "dest_path": "/project1/comp1"}},
  {"tool": "connect_ops", "params": {"source_path": "/project1/comp1", "dest_path": "/project1/level1"}},
  {"tool": "connect_ops", "params": {"source_path": "/project1/level1", "dest_path": "/project1/null1"}}
]}

MCP Prompts

Prompt Parameters Description
search_op op_name, op_type? Guide for searching operators by name
check_op_errors op_path Guide for inspecting and resolving operator errors
connect_ops (none) Guide for wiring operators together
create_extension_guide (none) Guide for creating TD extensions with proper patterns