Plug-ins: mcp refuses --allow-plugin-grants with automation roots

A server confined with --automation-read-root or
--automation-write-root has no plug-in store, so a grant accepted under
the flag could not be written to grants.json and recorded grants were
not read. mcp now refuses the combination at start-up.

The help text, docs/plugins.md and docs/mcp.md state that a confined
server does not install a module that lists permissions or asks for a
heavy budget, and that with VECTORCRAFT_NO_PREFS set a grant accepted
under the flag lasts for that session only.
This commit is contained in:
dotCooCoo
2026-11-08 21:54:46 -08:00
parent ae52c802e4
commit 7431486afa
4 changed files with 27 additions and 8 deletions
+8 -1
View File
@@ -91,7 +91,11 @@ USAGE:
with nobody approving them. Without it they are refused, and a plug-in that lists permissions
installs only when its hash is already recorded in grants.json, by a person's approval in the
desktop app or by a grant accepted under this flag. A grant accepted under this flag stays
recorded and counts as approved in later sessions. It implies --headless.
recorded and counts as approved in later sessions. It implies --headless. It can't be used
with --automation-read-root or --automation-write-root: a server confined to folders has no
plug-in store, so it reads no grants.json, and a plug-in that lists permissions or asks for a
heavy budget does not install there. With VECTORCRAFT_NO_PREFS set there is no store either,
and a grant accepted under this flag lasts for that session only.
vectorcraft-cli run [--in FILE] [--cmd ID [--params JSON] | --commands FILE|-]... [--export FILE]... [--scale N] [--ppi N] [--allow-plugin-grants]
Headless batch: open FILE (any readable format) or start a new document, run commands in
@@ -283,6 +287,9 @@ fn mcp(args: &[String], safe_mode: bool) -> Result<(), String> {
if allow_grants && connect.is_some() {
return Err("--allow-plugin-grants applies to the headless server; in a running app a person approves each plug-in".into());
}
if allow_grants && !(read_roots.is_empty() && write_roots.is_empty()) {
return Err("--allow-plugin-grants can't be used with --automation-read-root or --automation-write-root: a server confined to folders has no plug-in store to record a grant in".into());
}
if safe_mode && connect.is_some() {
return Err("--safe-mode runs a headless session without plug-ins; a running app loads its own: start it with `vectorcraft --safe-mode --control <port>`".into());
}
+7 -1
View File
@@ -20,7 +20,7 @@ fn empty_dir(dir: std::path::PathBuf) -> std::path::PathBuf {
}
/// `run` and `mcp --headless` refuse `plugin.install`'s `grant` unless the process was started with
/// `--allow-plugin-grants`, which `mcp` won't take with `--connect`.
/// `--allow-plugin-grants`, which `mcp` won't take with `--connect` or the automation roots.
#[test]
fn plugin_grants_need_the_start_up_flag() {
let module = vectorcraft_format::base64_encode(include_bytes!("../../../../crates/plugins/tests/fixtures/desaturate.wasm"));
@@ -73,6 +73,12 @@ fn plugin_grants_need_the_start_up_flag() {
let out = Command::new(BIN).args(["mcp", "--allow-plugin-grants", "--connect", "127.0.0.1:1"]).output().unwrap();
assert!(!out.status.success());
assert!(String::from_utf8_lossy(&out.stderr).contains("--allow-plugin-grants applies to the headless server"));
let root = std::env::temp_dir();
for flag in ["--automation-read-root", "--automation-write-root"] {
let out = Command::new(BIN).args(["mcp", "--allow-plugin-grants", flag, root.to_str().unwrap()]).stdin(Stdio::null()).output().unwrap();
assert!(!out.status.success(), "{flag}");
assert!(String::from_utf8_lossy(&out.stderr).contains("--allow-plugin-grants can't be used with"), "{flag}");
}
let usage = Command::new(BIN).arg("help").output().unwrap();
assert!(String::from_utf8_lossy(&usage.stdout).contains("--allow-plugin-grants lets plugin.install's grant and allowHeavy take effect"));
}
+5 -2
View File
@@ -6709,8 +6709,11 @@ needs a person's approval too, and `plugin.install` refuses `grant: [permission]
app connected, `ui.installPlugin` opens the approval dialog and returns `{pending: "pluginInstall"}`; `answer_dialog`,
`ui.dialog.confirm` and injected input can't confirm it. A `vectorcraft-cli mcp` started with `--allow-plugin-grants`
accepts `grant` and `allowHeavy` from any request, in that session only (headless; it can't be combined with
`--connect`). Without `--allow-plugin-grants` and without a recorded grant for the module, `plugin.install` returns
an error naming the permissions needed. The approval, or a `grant` accepted under the flag, is kept with the module's
`--connect`, `--automation-read-root` or `--automation-write-root`). A server confined to folders has no plug-in
store, so it reads no `grants.json`, and a module that lists permissions or asks for a heavy budget does not install
there. Without `--allow-plugin-grants` and without a recorded grant for the module, `plugin.install` returns an error
naming the permissions needed. With `VECTORCRAFT_NO_PREFS` set there is no store, and a `grant` accepted under the
flag lasts for that session only. In other sessions the approval, or a `grant` accepted under the flag, is kept with the module's
hash, counts as approved in later sessions and in the desktop app, and covers only that module: the
same bytes install again without one, also from a headless session that reads the same `grants.json`, and a module
with other bytes has `needsApproval: true` and needs a new one even when it lists the same permissions. `plugin.remove`
+7 -4
View File
@@ -202,10 +202,13 @@ capability model proposed for PhotoCraft plug-ins
(`plugin.inspect`'s `needsApproval` tells it beforehand). A request's `grant` is refused too: `plugin.install` takes
it only with the approval the dialog gives. A `vectorcraft-cli mcp` or `run` started with `--allow-plugin-grants`
takes `grant` and `allowHeavy` from any request; only the person starting the process can set that flag, and it
doesn't apply to a running app. Without it, MCP in headless mode and the CLI install a module that lists
permissions only when its hash is recorded in the shared `grants.json`. A person's approval in the desktop app
records it, and so does a `grant` accepted under `--allow-plugin-grants`. A recorded grant counts as approved in
later sessions without the flag and in the desktop app.
doesn't apply to a running app. `mcp` refuses the flag together with `--automation-read-root` or
`--automation-write-root`: a server confined to folders has no plug-in store, so it reads no `grants.json`, and a
module that lists permissions or asks for a heavy budget does not install there. Without the flag, MCP in headless
mode and the CLI install a module that lists permissions only when its hash is recorded in the shared `grants.json`.
A person's approval in the desktop app records it, and so does a `grant` accepted under `--allow-plugin-grants`. A
recorded grant counts as approved in later sessions without the flag and in the desktop app. With
`VECTORCRAFT_NO_PREFS` set there is no store, and a `grant` accepted under the flag lasts for that session only.
With `--allow-plugin-grants`, pass `hash` set to the `plugin.inspect` entry's `hash` so that the module installed is
the one the user saw.
- **Replacing asks first.** When the file is another module than the plug-in installed with its id, or, with none