Files
Claude 8fb4281632 MCP: --automation-read-root / --automation-write-root confine the agent's file access (#500)
`effectcraft-cli mcp` had no way to keep an agent's file access to a folder;
the flags were unknown options. Both now take a folder (repeatable, as in
PhotoCraft): with either given, files are read only under a read or write root
and written only under a write root (none: no writes). Defaults are unchanged.

automation::confine::Confinement checks every engine command the backend runs
(execute_command, command_run, open/save_project, each engine.batch step), in
both headless and bridge mode, before it runs: the file parameters of the
commands that read or write files must be absolute paths inside the roots
(symbolic links resolved through the longest existing ancestor, `..` refused),
as must any other absolute path in a command's parameters. Commands that write
where they aren't told are checked against the target they would write: Save /
Increment and Save the open project's file, renderQueue.render every queued
output (renderQueue.list). Scripts, Execute File, Reveal in Finder, Open Recent
by index, `$N` file-path references in a batch, render_frame/screenshot paths
outside a write root and the bridge's UI-driving tools are refused while
confined. docs/mcp.md describes it.

Test: a confined headless server opens and saves inside its roots and is refused
outside them, for a relative path, `..`, a batch step, a `$N` path, Save to a
read-only root's project, the Render Queue's default output there, a symbolic
link out of the write root, and scripts; the unconfined server still writes
anywhere.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 22:54:57 +00:00

86 lines
5.3 KiB
Markdown

# MCP conventions
Run `effectcraft-cli mcp` for a headless session, or `effectcraft-cli mcp --bridge 9877`
to use the desktop app's control channel. Existing ports are unchanged.
## Confining file access
`--automation-read-root DIR` and `--automation-write-root DIR` (each repeatable, as in
PhotoCraft) confine what the agent can reach: files are read only under a read or write root and
written only under a write root; with no write root nothing is written. Every engine command
(`execute_command`, `command_run`, each `batch` step) is checked before it runs: its file
parameters must be absolute paths inside the roots (symbolic links resolved, no `..`), and so must
any other absolute path in its parameters. Save, Increment and Save and rendering the Render
Queue are checked against the files they write (the open project's, the queued outputs). Scripts
(`run_script`, `file.runScript`…), Execute File, Reveal in Finder, `$N` references to file paths
in a batch, and the bridge's UI-driving tools (`ui_click`, `ui_key`, `ui_type`, `ui_drag`,
`ui_set`, `script_ui`, `control`) are refused while confined. Without the flags nothing changes.
The server provides the same core conventions as FilmCraft #28:
| Tool | Arguments | Result |
| --- | --- | --- |
| `command_list` | `filter?`, `enabled_only?`, `schemas?` | Command catalog |
| `command_run` | `id`, `params?` | Engine command result |
| `command_batch` | `steps: [{id, params?}]`, `stop_on_error?` | `completed`, `failed`, per-step `results` |
| `doc_inspect` | none | Project overview and active composition |
| `render_preview` | `comp?`, `time?`, `max_side?`, `transparent?` | PNG and frame information |
`command_batch` stops at the first error by default; false continues. Each edit has its
own undo step. The existing `batch` tool retains its atomic undo grouping and result references.
Existing tools, including `list_commands`, `execute_command`, and `batch`, remain listed
because existing workflows use them; there are no hidden aliases. See [agents.md](agents.md)
for the full catalog. Each tool has a title and read-only, destructive, idempotent and
open-world hints. Tools with an optional output path are conservatively annotated as file writers.
Unknown top-level tool argument keys return JSON-RPC `-32602` naming the key and accepted
arguments. EffectCraft already rejects unknown engine command parameters; that behaviour is
preserved. Tool failures (including escaped panics) return `isError: true`, and so does any JSON
result with a `failed` count above zero (a `command_batch` / `batch` with a failed step, a
render whose queue items did not all finish), even though the other steps or items succeeded:
read `results` / `items` for the details. Malformed JSON
returns `-32700` with a null id and the session keeps serving. The MCP backend owns its session
without a mutex; the engine's render-job mutexes already recover poisoned locks.
Resources `effectcraft://document` and `effectcraft://commands` return JSON matching
`doc_inspect` and `command_list`. Clients declaring MCP 2026-07-28 in per-request `_meta`
receive `resultType: complete` and list/read cache hints; document reads are never cached.
```json
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"command_run","arguments":{"id":"comp.new","params":{"width":640,"height":360}}}}
{"jsonrpc":"2.0","id":2,"method":"resources/read","params":{"uri":"effectcraft://document"}}
```
## Progress and cancellation
Headless `command_run` / `execute_command` calls to `renderQueue.render` (unless `wait: false`)
run the existing background job while the MCP server keeps reading stdin. A
`params._meta.progressToken` string or number opts into `notifications/progress`, at most ten
per second, strictly increasing with a queue-item total. No token means no notifications.
`ping`, inspection and other requests are served while rendering. Closing stdin lets the
pending render finish and reply.
### Requests during a render
A render job owns a snapshot of the project taken when it started. While it runs:
- Every way of starting a second render is refused with `isError: true`: a blocking
`renderQueue.render`, one with `wait: false`, or one inside a batch.
- Other commands, including edits, run normally and change the project. They do not affect the
running job, which keeps rendering its snapshot; to render an edit, render again after the
job ends. Queue items are matched by id when the job reports their status, so an item removed
mid-render is left out of the reply and counted in `failed`.
Send `notifications/cancelled` with `params.requestId` to stop the matching request at the next
frame batch. No response is sent for that cancelled request. Unknown and completed ids are
ignored. The exporter records the exact files it opens, including storage overflow paths, and
removes those files on cancellation; it never scans for similarly named files. Finished earlier
queue items and skipped existing frames remain. Custom exporters implement the same cleanup
contract. A failed queue item gives `isError: true`.
Bridge calls, `wait: false`, batch tools and single-frame tools retain synchronous/polling
behaviour and do not report MCP progress or cancellation. `renderQueue.render {"wait": false}`
returns the ids being rendered at once; poll `renderQueue.list` for status and stop it with
`renderQueue.stop`. Use a direct blocking command call
for an MCP-cancellable render.