Docs: document --automation-read-root and --automation-write-root for mcp

This commit is contained in:
Isaac Levine committed 2026-10-12 10:50:05 -04:00
1 parent a9fa027528
commit 719fd909ee
1 file changed
+68
+68
View File
@@ -6,6 +6,7 @@ Claude and other agents can make music with it.
```sh
soundcraft-cli mcp # headless engine (no window, no audio device)
soundcraft-cli mcp --demo # headless, starting from the demo session
soundcraft-cli mcp --automation-read-root DIR --automation-write-root DIR # headless, confined to folders
soundcraft --control 7801 & # the desktop app…
soundcraft-cli mcp --connect 7801 # …driven over MCP: same tools plus screenshots and clicks
```
@@ -16,6 +17,73 @@ Claude Code:
claude mcp add soundcraft -- soundcraft-cli mcp --demo
```
## Confining file access
```sh
soundcraft-cli mcp --automation-read-root /work/project --automation-write-root /work/project/out
```
```json
{"mcpServers": {"soundcraft": {"command": "soundcraft-cli",
"args": ["mcp", "--automation-read-root", "/work/project", "--automation-write-root", "/work/project/out"]}}}
```
The two flags (VectorCraft and the other Craft apps take the same ones, so one client config
works across the suite) keep the agent to the folders you give it:
- Every file a command reads must lie inside the **read root**: `open_session`, `session.open`,
`import_audio` and `file.import_audio`, `file.import_midi`, `file.import_video`,
`file.import_clip_groups`, `file.import_session_data`, `track.load_preset` and
`mix.sampler_load`. The media a session file names is read too, so an opened session whose
audio paths lead outside the read root loads without that audio and lists it under `missing`.
- Every file a command writes must lie inside the **write root**: `save_session`, `session.save`,
`session.save_as`, `session.save_copy`, `session.save_template`, `bounce_mix` and
`file.bounce_mix`, `file.bounce_stems`, `file.export_clips`, `file.export_midi`,
`file.export_session_text`, `file.export_selected_tracks_as_session`, `file.export_sibelius`,
`file.send_to_sibelius`, `file.print_score`, `file.export_clip_groups` and
`track.save_preset`, together with what a session save writes beside the session file
(`Audio Files/`, `Backups/`) and its temporary file. A score command given no `path` writes to
the temp folder, which is outside the write root unless you made it the root, so it is refused.
- Read and write access are separate. Give the same folder twice for both; a root left out grants
none of its access (with only `--automation-read-root`, nothing can be written to disk). Without
either flag nothing changes.
- A path is made absolute against the server's working directory, then its links are followed: a
file (or folder) link that leads out of a root is outside it. For a file that doesn't exist yet,
its nearest existing folder's links are followed and the rest must be plain names, so `..` below a
folder that doesn't exist is refused, as is a link that leads nowhere (writing through it would
create its target). The result is compared with the root by whole folder names: `/work2/x` is not
inside `/work`. On Windows case is ignored, UNC paths (`\\server\share\...`) are resolved like
drive-letter ones, and device names (`CON`, `NUL`, `COM1`...) are refused.
- A refused path is an ordinary tool error the agent can read and correct, for example
`automation path rejected: outside the write root /work/project/out: /etc/x.wav` or
`automation filesystem access is not granted: write authority is absent: /work/project/out/x.wav`.
A refused save path is reported as refused, never as "already exists".
- The roots must be existing folders; a missing one stops the server with an error. Each flag is
given once (`--flag <dir>` or `--flag=<dir>`), and a misspelled `--automation-...` flag is an
error rather than an unconfined server.
The flags confine the in-process server, so `--connect` together with them is an error: the server
would otherwise talk to an app that isn't confined. SoundCraft's desktop app has no equivalent flags
yet, so a running app (`soundcraft --control`) is not confined. `--demo` is the operator's own
choice and is built before the roots apply.
Playback and bounce need no check of their own. SoundCraft decodes audio into the session when it
enters it (import, open, sampler load), so rendering reads nothing from disk afterwards. Movies are
the exception: a session only references them by path and the desktop app decodes pictures from
that file, but the headless server never does, and the movie's path is checked when it is imported
and when a session naming it is opened.
What the roots do not cover: commands that take no path still read and change the open session,
and `inspect_session` returns it. Plug-in discovery scans the system's plug-in folders and loads
plug-in binaries, which are fixed locations and native code, not agent paths. Recording is only
reachable in the desktop app. Anything that reads or writes a path goes through one place in the
engine (`io::read_file`, `io::write_file` and the helpers beside them in `crates/engine/src/io.rs`);
a test keeps the rest of the engine from touching the file system on its own.
The checks hold for every thread of the server process. A path is checked, then opened: another
program that swaps a link in between can still win that race, so the roots keep an agent to the
files it was given rather than guarding against other software on the computer.
## Tools
| Tool | Purpose |