@dpantani/tdmcp 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,9 +1,14 @@
1
- # tdmcp — build TouchDesigner from plain language
1
+ # tdmcp — TouchDesigner MCP server
2
2
 
3
- **tdmcp** lets you create real visual systems in
4
- [TouchDesigner](https://derivative.ca) just by describing them to an AI assistant
5
- (Claude, Cursor, …). You type what you want; the AI builds the actual network of
6
- nodes inside your project, checks it for errors, and shows you a preview.
3
+ [![CI](https://github.com/Pantani/tdmcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Pantani/tdmcp/actions/workflows/ci.yml)
4
+ [![Docs](https://github.com/Pantani/tdmcp/actions/workflows/docs.yml/badge.svg)](https://pantani.github.io/tdmcp/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+
7
+ **tdmcp is a [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server
8
+ for [TouchDesigner](https://derivative.ca)** — build TouchDesigner from plain
9
+ language. You describe a visual to an AI assistant (Claude, Claude Code, Cursor,
10
+ Codex); the AI builds the actual network of nodes inside your project, checks it
11
+ for errors, and shows you a preview.
7
12
 
8
13
  > *"Create a feedback tunnel from noise with blur and displace, then add bloom and
9
14
  > output it to a window."*
@@ -13,15 +18,27 @@ nodes inside your project, checks it for errors, and shows you a preview.
13
18
  It works because it pairs two things every other tool was missing:
14
19
 
15
20
  - **Real knowledge** — an embedded reference of 629 operators, 68 Python classes,
16
- 32 workflow patterns, GLSL techniques and tutorials, so the AI uses real
21
+ workflow patterns, GLSL techniques and tutorials, so the AI uses real
17
22
  TouchDesigner operators instead of guessing.
18
23
  - **Real execution** — a small **bridge** running inside TouchDesigner that
19
24
  actually creates, connects, inspects and previews nodes — with a
20
25
  create → verify → preview loop so the AI can see and fix its own work. Every
21
- generated network is auto-arranged into a readable left→right layout instead
22
- of piling nodes on top of each other.
26
+ generated network is auto-arranged into a readable left→right layout.
27
+
28
+ ## 📖 Documentation
29
+
30
+ Full guides and reference live on the **docs site → <https://pantani.github.io/tdmcp/>**
31
+
32
+ | For artists / musicians | For developers |
33
+ | --- | --- |
34
+ | [What is tdmcp?](https://pantani.github.io/tdmcp/guide/what-is-tdmcp) | [Architecture](https://pantani.github.io/tdmcp/reference/architecture) |
35
+ | [Install (no terminal)](https://pantani.github.io/tdmcp/guide/install) | [Tools reference](https://pantani.github.io/tdmcp/reference/tools) |
36
+ | [Your first visual](https://pantani.github.io/tdmcp/guide/first-visual) | [Environment variables](https://pantani.github.io/tdmcp/reference/environment) |
37
+ | [Prompt cookbook](https://pantani.github.io/tdmcp/guide/prompt-cookbook) | [CLI & local copilot](https://pantani.github.io/tdmcp/reference/cli) |
38
+ | [Recipe gallery](https://pantani.github.io/tdmcp/guide/recipes) | [Bridge & REST API](https://pantani.github.io/tdmcp/reference/bridge-api) |
39
+ | [Troubleshooting](https://pantani.github.io/tdmcp/guide/troubleshooting) | [Roadmap](docs/ROADMAP.md) · [Deployment](docs/DEPLOYMENT.md) |
23
40
 
24
- ---
41
+ 🇧🇷 **Documentação em português:** <https://pantani.github.io/tdmcp/pt/>
25
42
 
26
43
  ## How it works
27
44
 
@@ -40,11 +57,6 @@ Three pieces talk to each other on your computer:
40
57
  3. **The bridge** — a tiny piece that runs *inside* TouchDesigner so the server
41
58
  can actually drive it. You switch it on once per machine.
42
59
 
43
- The steps below wire these together — about 5 minutes (less with the one-click
44
- Claude Desktop extension, which bundles the server for you).
45
-
46
- ---
47
-
48
60
  ## What you'll need
49
61
 
50
62
  - **[TouchDesigner](https://derivative.ca/download)** — the free non-commercial
@@ -52,22 +64,17 @@ Claude Desktop extension, which bundles the server for you).
52
64
  - An MCP-capable AI assistant: **Claude Desktop** (easiest), **Claude Code**,
53
65
  **Codex**, or **Cursor**.
54
66
 
55
- **Do I need Node.js?** Only for the build-from-source path (Claude Code, Codex,
56
- or Cursor), which needs **[Node.js 20+](https://nodejs.org)**check with
57
- `node -v`. The
58
- one-click Claude Desktop extension needs **nothing extra**: Claude Desktop ships
59
- with its own Node, and the server is bundled inside the `.dxt`.
60
-
61
- ---
67
+ Node.js is only needed for the build-from-source path (**[Node 20+](https://nodejs.org)**).
68
+ The one-click Claude Desktop extension needs nothing extrathe server is bundled
69
+ inside the `.mcpb` (formerly `.dxt`; legacy `.dxt` files still install).
62
70
 
63
71
  ## Get started
64
72
 
65
73
  You set up **two sides**: your **AI** (so it gets the tdmcp tools) and
66
- **TouchDesigner** (so the AI can drive it). Pick whichever way is easiest for you.
74
+ **TouchDesigner** (so the AI can drive it).
67
75
 
68
- **🤖 The easiest way — let your AI install it for you.** If you already use
69
- **Claude Code**, **Codex**, or **Cursor**, you don't have to do any of the manual
70
- steps below. Just **paste this one message** into it:
76
+ **🤖 Easiest — let your AI install it.** Using **Claude Code**, **Codex**, or
77
+ **Cursor**? Paste this one message in:
71
78
 
72
79
  ```text
73
80
  Install and connect tdmcp for me by reading and following
@@ -75,345 +82,81 @@ https://raw.githubusercontent.com/Pantani/tdmcp/main/tdmcp-install-prompt.md
75
82
  Do every step yourself; only stop when you need me to paste one line into TouchDesigner.
76
83
  ```
77
84
 
78
- **That's the whole install.** Your AI clones, builds, and wires everything up on
79
- its own. The *only* thing it will ask you to do is paste one line into
80
- TouchDesigner's Textport when it's ready — nothing else. (It's the
81
- [`tdmcp-install-prompt.md`](tdmcp-install-prompt.md) runbook doing the work.)
82
-
83
- **Prefer to do it yourself, or using Claude Desktop?** Follow the three manual
84
- steps below — Step 1's one-click `.dxt` is the no-terminal, no-Node route for
85
- Claude Desktop.
86
-
87
- ### Step 1 — Connect tdmcp to your AI
88
-
89
- Pick the one tab that matches your client.
90
-
91
- <details open>
92
- <summary><b>🟢 Claude Desktop — one-click <code>.dxt</code> (easiest: no terminal, no Node)</b></summary>
93
-
94
- <br />
95
-
96
- A `.dxt` is **one file** Claude Desktop installs as an extension. The tdmcp server
97
- is **bundled inside it** — no terminal, no Node install, nothing to keep running
98
- yourself.
99
-
100
- 1. **Download** the bundle →
101
- **[⬇ tdmcp.dxt](https://github.com/Pantani/tdmcp/releases/latest/download/tdmcp.dxt)**
102
- (always the latest release).
103
- 2. **Install it:** in Claude Desktop open **Settings → Extensions**, then drag
104
- `tdmcp.dxt` onto the window (or click **Install from file**).
105
- 3. **Enable it.** Leave the TouchDesigner **host** / **port** at `127.0.0.1` /
106
- `9980` unless you changed them.
107
-
108
- Connected — **now do Step 2** to turn on the bridge. (Docker/HTTP options live in
109
- [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md).)
85
+ It clones, builds and wires everything up; the only manual step is pasting one
86
+ line into TouchDesigner (Step 2 below).
110
87
 
111
- </details>
88
+ **🟢 Claude Desktop — one-click `.mcpb` (no terminal, no Node).** Download
89
+ **[tdmcp.mcpb](https://github.com/Pantani/tdmcp/releases/latest/download/tdmcp.mcpb)**,
90
+ then in Claude Desktop open **Settings → Extensions** and install it (drag it in or
91
+ **Install from file**). Leave host/port at `127.0.0.1` / `9980`. Full walkthrough:
92
+ [the install guide](https://pantani.github.io/tdmcp/guide/install).
112
93
 
113
- <details>
114
- <summary><b>Claude Code, Codex, or Cursor — build from source (needs Node 20+)</b></summary>
115
-
116
- <br />
117
-
118
- This path builds the server locally, so you need
119
- **[Node.js 20+](https://nodejs.org)** (`node -v` to check).
94
+ **🛠️ Claude Code / Codex / Cursor — build from source.**
120
95
 
121
96
  ```bash
122
97
  git clone https://github.com/Pantani/tdmcp.git
123
98
  cd tdmcp
124
- npm run setup
99
+ npm run setup # installs, builds, and prints the exact line to connect your client
125
100
  ```
126
101
 
127
- `npm run setup` installs, builds, and then **prints the exact line to connect your
128
- AI**, with your real paths already filled in — paste it and you're done. The manual
129
- equivalents:
130
-
131
- - **Claude Code:**
132
-
133
- ```bash
134
- claude mcp add tdmcp -- node <project-path>/dist/index.js
135
- ```
136
-
137
- - **Codex CLI:**
138
-
139
- ```bash
140
- codex mcp add tdmcp -- node <project-path>/dist/index.js
141
- ```
142
-
143
- Prefer editing config by hand? Add this to `~/.codex/config.toml`:
144
-
145
- ```toml
146
- [mcp_servers.tdmcp]
147
- command = "node"
148
- args = ["<project-path>/dist/index.js"]
149
- ```
150
-
151
- - **Cursor** — create `.cursor/mcp.json` in your workspace:
152
-
153
- ```json
154
- {
155
- "mcpServers": {
156
- "tdmcp": { "command": "node", "args": ["<project-path>/dist/index.js"] }
157
- }
158
- }
159
- ```
160
-
161
- `<project-path>` is the folder you cloned — run `pwd` inside it for the full path.
162
- Restart your client afterward so it loads the server. **Now do Step 2.**
163
-
164
- </details>
165
-
166
- ### Step 2 — Turn on the bridge inside TouchDesigner (everyone)
167
-
168
- This is the **same one line** no matter which client you set up in Step 1 — it's
169
- what lets the server actually drive TouchDesigner.
170
-
171
- 1. **Open TouchDesigner.**
172
- 2. Open the **Textport** (`Dialogs → Textport and DATs`), paste this **one line**
173
- and press Enter:
102
+ ### Turn on the bridge inside TouchDesigner (everyone)
174
103
 
175
- ```python
176
- import urllib.request; exec(urllib.request.urlopen("https://raw.githubusercontent.com/Pantani/tdmcp/main/td/bootstrap.py").read().decode())
177
- ```
104
+ Open TouchDesigner, open the **Textport** (`Dialogs → Textport and DATs`), paste
105
+ this **one line** and press Enter:
178
106
 
179
- You should see:
180
-
181
- ```
182
- [tdmcp] bridge running on port 9980 (/project1/tdmcp_bridge)
107
+ ```python
108
+ import urllib.request; exec(urllib.request.urlopen("https://raw.githubusercontent.com/Pantani/tdmcp/main/td/bootstrap.py").read().decode())
183
109
  ```
184
110
 
185
- Done a `tdmcp_bridge` node now lives in your network and is listening. It's
186
- safe and reversible: it only adds that one tidy component, and re-running the line
187
- just reconfigures it.
188
-
189
- <details>
190
- <summary>Keep it on across restarts · other install methods · removing it</summary>
191
-
192
- <br />
111
+ You should see `[tdmcp] bridge running on port 9980 (/project1/tdmcp_bridge)`.
112
+ It's safe and reversible it adds one tidy component; remove it later with
113
+ `from mcp import install; install.uninstall()`. Other install methods (module
114
+ path, terminal, reusable `.tox`) are in the
115
+ [bridge docs](https://pantani.github.io/tdmcp/reference/bridge-api).
193
116
 
194
- - **Start it automatically in every project:** save your project as your
195
- **Default Project**, or use the Execute-DAT auto-start in
196
- [`td/README.md`](td/README.md).
197
- - **If you cloned the repo** and want a set-and-forget install: add
198
- `<project-path>/td/modules` to TouchDesigner's **Python 64-bit Module Path**
199
- (`Edit → Preferences → General`), then run
200
- `from mcp import install; install.run()` in the Textport.
201
- - **From a terminal:** `npx @dpantani/tdmcp install-bridge` (or
202
- `node <project-path>/dist/index.js install-bridge` from a clone) copies the
203
- bridge to `~/tdmcp-bridge` and prints the Textport line.
204
- - **Remove it later:** `from mcp import install; install.uninstall()`.
205
- - **Port 9980 taken?** Set it in both places — the bridge
206
- (`install.run(port=9981)`) and the client (`TDMCP_TD_PORT=9981`).
117
+ ### Make something
207
118
 
208
- </details>
209
-
210
- ### Step 3 — Make something
211
-
212
- With TouchDesigner open and your AI connected, just ask in plain language:
119
+ With TouchDesigner open and your AI connected, ask in plain language:
213
120
 
214
121
  > *"Create an audio-reactive particle galaxy and show me a preview."*
215
122
 
216
- The AI builds the network in your project, checks it for errors, and returns a
217
- thumbnail. Iterate from there: *"make it warmer,"* *"add a feedback trail,"*
218
- *"output it fullscreen."*
219
-
220
- > 💡 Once tdmcp is published to npm, the Claude Code wiring becomes path-free:
221
- > `claude mcp add tdmcp -- npx -y @dpantani/tdmcp`.
222
-
223
- ---
123
+ The AI builds the network, checks it for errors, and returns a thumbnail. Iterate:
124
+ *"make it warmer," "add a feedback trail," "output it fullscreen."* More ideas in
125
+ the [prompt cookbook](https://pantani.github.io/tdmcp/guide/prompt-cookbook).
224
126
 
225
- ## Troubleshooting
226
-
227
- | What you see | What to do |
228
- | --- | --- |
229
- | The AI says **"TouchDesigner isn't reachable."** | Make sure TD is open and the bridge is on (Step 2). Test it: `curl http://127.0.0.1:9980/api/info` should return JSON. |
230
- | `from mcp import install` → **`No module named 'mcp'`** | You're using the Module-Path method but it isn't set — point it at `<project-path>/td/modules` (see Step 2's collapsed options) and restart TouchDesigner. Or just use the one-line bootstrap in Step 2 instead. |
231
- | **`command not found: node` / `npm`** | Node isn't installed (or is too old). Install Node ≥ 20 from [nodejs.org](https://nodejs.org) and reopen the terminal. |
232
- | **Your AI client doesn't list any tdmcp tools** | Restart the client after adding the server, and double-check the path to `dist/index.js` is the full absolute path. |
233
- | **Port 9980 is already taken** | Set a different port in *both* places: the bridge (`install.run(port=9981)`) and the client (`TDMCP_TD_PORT=9981`). |
234
-
235
- The server runs fine even when TouchDesigner is closed — TD-dependent tools just
236
- return a friendly "not reachable" message instead of crashing.
237
-
238
- ---
127
+ > **Not connecting?** The two most common fixes: make sure the bridge is on
128
+ > (`curl http://127.0.0.1:9980/api/info` returns JSON), and **restart your AI
129
+ > client** after adding the server. Full
130
+ > [troubleshooting](https://pantani.github.io/tdmcp/guide/troubleshooting).
239
131
 
240
132
  ## What you can do
241
133
 
242
- **Artist tools** (describe the result, get a whole network):
243
- `create_visual_system`, `create_feedback_network`, `create_generative_art`,
244
- `create_audio_reactive`, `create_particle_system`, `create_data_visualization`,
245
- `apply_post_processing`, `setup_output`, `get_preview`, `plan_visual`. Feedback,
246
- particle, generative and audio-reactive systems arrive already playable they
247
- auto-expose a control panel (a Feedback knob, particle Drag/Turbulence/Gravity/Lifetime,
248
- an evolution-Speed knob, an audio Sensitivity knob) you can tweak, animate, preset, or
249
- map to a controller. Pass `expose_controls: false` to opt out.
250
-
251
- **Building blocks**: `create_node_chain`, `connect_nodes`, `create_glsl_shader`,
252
- `create_python_script`, `set_parameters_batch`, `create_container`,
253
- `duplicate_network`, `arrange_network` (tidy a messy network into a readable
254
- left→right layout).
255
-
256
- **Live control, animation & I/O** (make a generated system playable):
257
- `create_control_panel` (add knobs/sliders/toggles to a COMP and bind them to node
258
- parameters), `manage_presets` (store/recall/list named snapshots of those controls),
259
- `animate_parameter` (drive parameters with an LFO over time — no manual keyframing),
260
- `create_external_io` (OSC/MIDI input mapped straight to parameters, DMX/Art-Net out
261
- for lighting, NDI/Syphon-Spout video in), and `manage_component` (save/load reusable
262
- `.tox` components).
263
-
264
- **Atomic operations**: `create_td_node`, `delete_td_node`,
265
- `update_td_node_parameters`, `execute_python_script`, `exec_node_method`.
266
-
267
- **Inspect & analyze**: `get_td_info`, `get_td_nodes`, `get_td_node_parameters`,
268
- `get_td_node_errors`, `get_td_performance`, `get_td_topology`, `get_td_classes`,
269
- `get_td_class_details`, `get_module_help` (plus search / summary / compare /
270
- snapshot helpers).
271
-
272
- **Recipes** — 11 validated, ready-to-build templates: `feedback_tunnel`,
273
- `performable_feedback_tunnel` (the same tunnel pre-wired with live knobs for
274
- decay/zoom/spin/blur — ready to perform, animate with an LFO, or snapshot as
275
- presets), `reaction_diffusion`, `noise_landscape`, `audio_spectrum_bars`,
276
- `particle_galaxy`, `webcam_glitch`, `data_sonification`, `kinect_silhouette`,
277
- `led_strip_mapper`, `projection_mapping`.
278
-
279
- **Knowledge resources** the AI reads from:
280
- `tdmcp://operators/{category|name}`, `tdmcp://python-api/{class}`,
281
- `tdmcp://patterns/{name}`, `tdmcp://glsl/{name}`, `tdmcp://recipes/{name}`,
282
- `tdmcp://tutorials/{name}`.
283
-
284
- **Prompt modes**: `visual_artist_mode`, `debug_network`, `optimize_performance`,
285
- `explain_network`, `remix_visual`.
286
-
287
- ---
288
-
289
- ## Architecture (for developers)
290
-
291
- ```
292
- MCP client ──stdio──▶ tdmcp server (Node/TS) ──HTTP──▶ TouchDesigner bridge (Python)
293
- ├── Layer 1 artist tools (create_visual_system, …)
294
- ├── Layer 2 building blocks (create_node_chain, …)
295
- ├── Layer 3 atomic ops (create_td_node, …)
296
- ├── Knowledge base (MCP resources)
297
- ├── Recipes (validated network templates)
298
- └── Feedback engine (errors / preview / performance)
299
- ```
300
-
301
- ### Environment variables
302
-
303
- | Variable | Default | Description |
304
- | --- | --- | --- |
305
- | `TDMCP_TD_HOST` | `127.0.0.1` | TouchDesigner bridge host |
306
- | `TDMCP_TD_PORT` | `9980` | Web Server DAT port |
307
- | `TDMCP_TRANSPORT` | `stdio` | MCP transport: `stdio` (default) or `http` (Streamable HTTP) |
308
- | `TDMCP_HTTP_PORT` | `3939` | Port for the HTTP transport (when `TDMCP_TRANSPORT=http`) |
309
- | `TDMCP_EVENTS` | `on` | Subscribe to TD WebSocket events and forward them as MCP logging notifications (`on`/`off`) |
310
- | `TDMCP_RAW_PYTHON` | `on` | Whether to expose the raw-Python escape-hatch tools (`execute_python_script`, `exec_node_method`). Set to `off` to lock them out for restricted setups |
311
- | `TDMCP_BRIDGE_TOKEN` | _(unset)_ | Optional shared bearer token. When set, the server sends it and the bridge requires it — set the **same** value in TouchDesigner's environment to turn auth on |
312
- | `TDMCP_BRIDGE_ALLOW_EXEC` | `1` | **Set in TouchDesigner's environment.** Set to `0`/`false`/`off` to make the bridge refuse the arbitrary-code endpoints (`/api/exec`, node `method`) — enforced bridge-side, even for direct network callers |
313
- | `TDMCP_LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` / `silent` (stderr) |
314
- | `TDMCP_REQUEST_TIMEOUT_MS` | `10000` | Per-request timeout to the bridge |
315
-
316
- The HTTP transport (`TDMCP_TRANSPORT=http`) serves MCP at `POST/GET/DELETE /mcp`
317
- on `127.0.0.1:$TDMCP_HTTP_PORT` with stateful sessions — handy for remote/headless
318
- setups. See [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) for Docker and the Claude
319
- Desktop `.dxt` extension.
320
-
321
- ### Security
322
-
323
- The TouchDesigner bridge runs **arbitrary Python inside your TD process** (that is
324
- what lets the assistant build networks for you). Treat it like an open door to the
325
- machine TD runs on:
326
-
327
- - **The Web Server DAT listens on its port (default `9980`) on all network
328
- interfaces.** Anyone who can reach `http://<your-ip>:9980` can run code on that
329
- machine. Only run it on a trusted network, and/or firewall the port to localhost.
330
- - **Turn on bridge auth for untrusted networks:** set `TDMCP_BRIDGE_TOKEN` to a
331
- shared secret in **both** the MCP server's environment and TouchDesigner's
332
- environment. The bridge then rejects any request without a matching
333
- `Authorization: Bearer <token>` (HTTP `401`). Unset (default) keeps the
334
- zero-config local flow.
335
- - `TDMCP_RAW_PYTHON=off` hides the raw-Python MCP tools, but it only gates the
336
- **MCP-server side** — a direct network caller could still hit the bridge's
337
- `/api/exec` and node-`method` endpoints. To close them at the bridge itself, set
338
- `TDMCP_BRIDGE_ALLOW_EXEC=0` in TouchDesigner's environment (defense in depth that
339
- holds even without a token); the structured endpoints keep working.
340
- - The MCP server itself binds to loopback (`127.0.0.1`) for both stdio and HTTP
341
- transports and enables DNS-rebinding protection on HTTP.
342
- - **The bridge refuses browser cross-origin requests.** Any request carrying an
343
- `Origin` header that isn't loopback (`127.0.0.1`/`localhost`) is rejected (HTTP
344
- `401`), so a malicious web page open in your browser can't quietly POST to the
345
- bridge (CSRF / DNS-rebinding → drive-by code execution). The MCP server sends no
346
- `Origin`, so normal use is unaffected.
347
-
348
- ### Command-line agent (`tdmcp-agent`)
349
-
350
- The package also installs a second binary, `tdmcp-agent`, that drives the same
351
- tools from a shell with machine-readable output — useful for scripts and CI:
352
-
353
- ```bash
354
- tdmcp-agent --help # list commands
355
- tdmcp-agent info # health check + TD/bridge info
356
- tdmcp-agent nodes find --params '{"parent_path":"/project1","type":"TOP"}'
357
- tdmcp-agent nodes create --dry-run --params '{"parent_path":"/project1","type":"noiseTOP"}'
358
- tdmcp-agent schema "nodes create" # print a command's JSON Schema
359
- ```
360
-
361
- Output format is `--output json` (default) / `ndjson` / `text`. Mutating commands
362
- are tagged `mutates`; the Python escape hatches require `--allow-unsafe` and honour
363
- `TDMCP_RAW_PYTHON=off`.
364
-
365
- ### Scripts
366
-
367
- | Script | Purpose |
368
- | --- | --- |
369
- | `npm run setup` | guided install + build, then prints how to connect your client |
370
- | `npm run dev` | run the server from source (stdio) |
371
- | `npm run build` | typecheck + bundle + copy assets to `dist/` |
372
- | `npm test` | unit + integration tests (Vitest + MSW) |
373
- | `npm run typecheck` / `npm run lint` | TypeScript / Biome |
374
- | `npm run smoke:live` | end-to-end test against a running TD |
375
- | `npm run validate:recipes` | validate every recipe JSON |
376
- | `npm run import:bottobot` | (re)build the embedded knowledge base — only needed to refresh it |
377
- | `npm run build:dxt` | package a Claude Desktop `.dxt` extension (see `docs/DEPLOYMENT.md`) |
378
-
379
- > The knowledge base ships in the repo, so a fresh clone needs only
380
- > `npm install && npm run build`. `import:bottobot` is a maintenance command for
381
- > regenerating it from `@bottobot/td-mcp`.
382
-
383
- ### Verify end-to-end
384
-
385
- With TD open and the bridge running:
386
-
387
- ```bash
388
- npm run smoke:live # creates a Noise→Null chain in /project1 and grabs a preview
389
- ```
390
-
391
- ---
392
-
393
- ## Current state
394
-
395
- - ✅ 41 tools across 3 layers, 6 resource families, 5 prompts, 11 recipes, a
396
- feedback engine, and the TouchDesigner Python bridge.
397
- - ✅ Two transports: **stdio** (default) and **Streamable HTTP**; plus an optional
398
- **WebSocket event stream** (TD → MCP logging notifications).
399
- - ✅ `typecheck`, `build`, `lint`, and `test` all pass; the server boots over
400
- stdio with clean stdout.
401
- - 🔌 Verified end-to-end against a live TouchDesigner (CRUD, preview, batch, and
402
- `node.created`/`node.deleted` events).
403
-
404
- ## Known limitations
405
-
406
- - **WebSocket events** (`node.created` / `node.deleted` / `node.error` /
407
- `project.saved` / `timeline.frame` / `node.cook`) are forwarded as MCP logging
408
- notifications on both transports. High-frequency events (`timeline.frame`,
409
- `node.cook`) are dropped by the consumer unless explicitly opted in.
410
- - **Audio / particle / 3D builders and the exotic recipes** (kinect, LED,
411
- projection) produce valid, connected networks but use best-effort TD parameter
412
- names — fine-tuning may be needed, and they emit warnings to that effect.
413
- - **Preview** returns the TOP at its native resolution (the requested size is
414
- advisory).
415
- - The bridge ships as Python modules plus a callbacks template (a binary `.tox`
416
- can't be generated from source); the one-liner in Step 2 assembles it for you.
134
+ **102+ tools** across three layers, plus an Obsidian vault integration — from
135
+ one-line artist generators (`create_feedback_network`, `create_audio_reactive`,
136
+ `create_particle_system`, `create_generative_art`, …) to building blocks
137
+ (`create_control_panel`, `animate_parameter`, `create_external_io` for
138
+ OSC/MIDI/DMX/NDI, …) down to atomic node CRUD and inspection. Many systems arrive
139
+ **already playable**, with a control panel you can tweak, preset, or map to a
140
+ controller. See the full, always-current
141
+ [tools reference](https://pantani.github.io/tdmcp/reference/tools) and the
142
+ [recipe gallery](https://pantani.github.io/tdmcp/guide/recipes).
143
+
144
+ ## Security
145
+
146
+ The bridge runs **arbitrary Python inside your TD process** and listens on port
147
+ `9980` on all interfaces — treat it like an open door to that machine. Run it only
148
+ on a trusted network, and for untrusted networks turn on bridge auth
149
+ (`TDMCP_BRIDGE_TOKEN`) and/or disable the exec endpoints
150
+ (`TDMCP_BRIDGE_ALLOW_EXEC=0`). Details:
151
+ [Security](https://pantani.github.io/tdmcp/reference/architecture#security).
152
+
153
+ ## Contributing & development
154
+
155
+ Build with `npm install && npm run build`; run `npm test`, `npm run typecheck`,
156
+ `npm run lint`. Work on the docs with `npm run docs:dev` (the
157
+ [tools reference](https://pantani.github.io/tdmcp/reference/tools) is generated by
158
+ `scripts/gen-tool-docs.ts`). See [CONTRIBUTING.md](CONTRIBUTING.md),
159
+ [CHANGELOG.md](CHANGELOG.md), and the [roadmap](docs/ROADMAP.md).
417
160
 
418
161
  ## License
419
162
 
@@ -7,6 +7,12 @@ interface Logger {
7
7
  error(message: string, meta?: Record<string, unknown>): void;
8
8
  }
9
9
 
10
+ interface TdEvent {
11
+ event: string;
12
+ data?: unknown;
13
+ }
14
+ type TdEventHandler = (event: TdEvent) => void;
15
+
10
16
  interface OperatorParameter {
11
17
  name: string;
12
18
  label?: string;
@@ -201,6 +207,38 @@ declare class KnowledgeBase {
201
207
  stats(): KnowledgeStats;
202
208
  }
203
209
 
210
+ interface ParsedNote {
211
+ /** Parsed YAML frontmatter (empty object when the note has none). */
212
+ data: Record<string, unknown>;
213
+ /** Markdown body with the frontmatter stripped. */
214
+ body: string;
215
+ }
216
+
217
+ /**
218
+ * A thin, safe wrapper over an Obsidian vault (a folder of markdown files on the
219
+ * local filesystem). Every path is resolved relative to the vault root and is
220
+ * refused if it escapes that root, so user-supplied note names cannot reach
221
+ * outside the vault.
222
+ */
223
+ declare class Vault {
224
+ readonly root: string;
225
+ private readonly logger;
226
+ constructor(root: string, logger?: Logger);
227
+ /** Resolves a vault-relative path, throwing if it would escape the vault root. */
228
+ resolve(relPath: string): string;
229
+ exists(relPath: string): boolean;
230
+ read(relPath: string): string;
231
+ write(relPath: string, content: string): void;
232
+ writeBinary(relPath: string, data: Buffer): void;
233
+ ensureDir(relPath: string): void;
234
+ /** Lists file names directly inside `subdir`, optionally filtered by extension. */
235
+ list(subdir: string, ext?: string): string[];
236
+ /** Reads a markdown note and splits its frontmatter from its body. */
237
+ readNote(relPath: string): ParsedNote;
238
+ /** Writes a markdown note from frontmatter data + body. */
239
+ writeNote(relPath: string, data: Record<string, unknown>, body: string): void;
240
+ }
241
+
204
242
  declare const RecipeSchema: z.ZodObject<{
205
243
  id: z.ZodString;
206
244
  name: z.ZodString;
@@ -284,11 +322,14 @@ interface RecipeSummary {
284
322
  interface RecipeLibraryOptions {
285
323
  dir?: string;
286
324
  logger?: Logger;
325
+ /** Optional Obsidian vault; recipes in `<vault>/Recipes/*.md` are merged in (and override built-ins by id). */
326
+ vault?: Vault;
287
327
  }
288
- /** Loads and validates recipe JSON files from the recipes directory. */
328
+ /** Loads and validates recipe JSON files from the recipes directory, plus any vault recipes. */
289
329
  declare class RecipeLibrary {
290
330
  private readonly dir;
291
331
  private readonly logger;
332
+ private readonly vault?;
292
333
  private cache?;
293
334
  constructor(options?: RecipeLibraryOptions);
294
335
  private load;
@@ -338,6 +379,16 @@ interface TouchDesignerClientOptions {
338
379
  token?: string;
339
380
  /** Overridable for tests (defaults to global `fetch`). */
340
381
  fetchImpl?: typeof fetch;
382
+ /**
383
+ * Extra attempts for a transient connection failure on an **idempotent** (GET)
384
+ * request — e.g. TD briefly stalls mid-build. Default 2 (so up to 3 tries).
385
+ * Only `TdConnectionError` is retried; timeouts and bridge errors are not (a
386
+ * timeout may mean the request was received, and non-GET methods aren't safe
387
+ * to repeat). Set 0 to disable.
388
+ */
389
+ retries?: number;
390
+ /** Base backoff between retries, in ms (linear: delay × attempt). Default 150. */
391
+ retryDelayMs?: number;
341
392
  }
342
393
  /**
343
394
  * HTTP client for the TouchDesigner REST bridge. Every method maps to one of the
@@ -350,9 +401,12 @@ declare class TouchDesignerClient {
350
401
  private readonly logger;
351
402
  private readonly token;
352
403
  private readonly fetchImpl;
404
+ private readonly retries;
405
+ private readonly retryDelayMs;
353
406
  constructor(options: TouchDesignerClientOptions);
354
407
  get endpoint(): string;
355
408
  private request;
409
+ private attemptRequest;
356
410
  getInfo(): Promise<{
357
411
  td_version?: string | undefined;
358
412
  python_version?: string | undefined;
@@ -468,6 +522,8 @@ interface ToolContext {
468
522
  knowledge: KnowledgeBase;
469
523
  recipes: RecipeLibrary;
470
524
  logger: Logger;
525
+ /** Optional Obsidian vault (set via TDMCP_VAULT_PATH); undefined when not configured. */
526
+ vault?: Vault;
471
527
  /**
472
528
  * Whether the raw Python escape-hatch tools may be registered. Undefined means
473
529
  * allowed (the default); only an explicit `false` locks them out.
@@ -475,6 +531,39 @@ interface ToolContext {
475
531
  allowRawPython?: boolean;
476
532
  }
477
533
 
534
+ declare const ConfigSchema: z.ZodObject<{
535
+ tdHost: z.ZodDefault<z.ZodString>;
536
+ tdPort: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
537
+ transport: z.ZodDefault<z.ZodEnum<{
538
+ stdio: "stdio";
539
+ http: "http";
540
+ }>>;
541
+ logLevel: z.ZodDefault<z.ZodEnum<{
542
+ error: "error";
543
+ debug: "debug";
544
+ info: "info";
545
+ warn: "warn";
546
+ silent: "silent";
547
+ }>>;
548
+ requestTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
549
+ httpPort: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
550
+ events: z.ZodDefault<z.ZodEnum<{
551
+ on: "on";
552
+ off: "off";
553
+ }>>;
554
+ rawPython: z.ZodDefault<z.ZodEnum<{
555
+ on: "on";
556
+ off: "off";
557
+ }>>;
558
+ bridgeToken: z.ZodOptional<z.ZodString>;
559
+ llmBaseUrl: z.ZodDefault<z.ZodString>;
560
+ llmModel: z.ZodDefault<z.ZodString>;
561
+ llmApiKey: z.ZodOptional<z.ZodString>;
562
+ chatPort: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
563
+ vaultPath: z.ZodOptional<z.ZodString>;
564
+ }, z.core.$strip>;
565
+ type TdmcpConfig = z.infer<typeof ConfigSchema>;
566
+
478
567
  interface CliResult {
479
568
  stdout: string;
480
569
  stderr: string;
@@ -485,5 +574,29 @@ interface RunCliOptions {
485
574
  makeCtx?: () => ToolContext;
486
575
  }
487
576
  declare function runCli(argv: string[], opts?: RunCliOptions): Promise<CliResult>;
577
+ interface RunWatchOptions {
578
+ config?: TdmcpConfig;
579
+ includeHighFrequency?: boolean;
580
+ /** Where each event line goes; defaults to stdout. Overridable for tests. */
581
+ write?: (line: string) => void;
582
+ /** Inject a stream factory for tests; defaults to a real `TdEventStream`. */
583
+ makeStream?: (args: {
584
+ url: string;
585
+ onEvent: TdEventHandler;
586
+ includeHighFrequency: boolean;
587
+ }) => {
588
+ start: () => void;
589
+ close: () => void;
590
+ };
591
+ /** Resolve the returned promise when aborted; defaults to listening for SIGINT. */
592
+ signal?: AbortSignal;
593
+ }
594
+ /**
595
+ * Streams TouchDesigner bridge events to stdout as ndjson until interrupted.
596
+ * Runs outside `runCli` because it is a long-lived stream, not a request/response.
597
+ */
598
+ declare function runWatch(opts?: RunWatchOptions): Promise<void>;
599
+ /** Interactive read-eval-print loop: each line is tokenized and run through runCli. */
600
+ declare function runRepl(opts?: RunCliOptions): Promise<void>;
488
601
 
489
- export { type CliResult, type RunCliOptions, runCli };
602
+ export { type CliResult, type RunCliOptions, type RunWatchOptions, runCli, runRepl, runWatch };