From 7431486afaeedaf305495521185cf78876d3e57a Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 8 Nov 2026 21:54:46 -0800 Subject: [PATCH] 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. --- apps/vectorcraft-cli/src/main.rs | 9 ++++++++- apps/vectorcraft-cli/tests/it/cli.rs | 8 +++++++- docs/mcp.md | 7 +++++-- docs/plugins.md | 11 +++++++---- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/apps/vectorcraft-cli/src/main.rs b/apps/vectorcraft-cli/src/main.rs index 3c53c9d31..126d361c5 100644 --- a/apps/vectorcraft-cli/src/main.rs +++ b/apps/vectorcraft-cli/src/main.rs @@ -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 `".into()); } diff --git a/apps/vectorcraft-cli/tests/it/cli.rs b/apps/vectorcraft-cli/tests/it/cli.rs index d6295e2ba..4b09e14ca 100644 --- a/apps/vectorcraft-cli/tests/it/cli.rs +++ b/apps/vectorcraft-cli/tests/it/cli.rs @@ -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")); } diff --git a/docs/mcp.md b/docs/mcp.md index 4f3dc0c77..308b05afd 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -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` diff --git a/docs/plugins.md b/docs/plugins.md index dd0cbcb16..0783f9013 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -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