From c8a6b4d9c3c458d5e28708a18293ec98f019fa7b Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 12 Nov 2026 09:12:40 -0800 Subject: [PATCH] MCP: a base64 thumbnail in a reply is sent as it is Under automation read roots, a reply's base64 `thumbnail` (a library item's PNG in `library.get`, a page's PNG in `document.pdfInfo`) was searched for hidden library paths like any other text, so a short path that happened to occur in the encoded bytes was rewritten and the image broke. `thumbnail` now joins `dataBase64`, `pngBase64` and `thumbnailBase64` as encoded data that is left unchanged; text under it that is not base64 is still searched. --- crates/ui-egui/src/control.rs | 5 +++-- crates/ui-egui/src/control/tests.rs | 10 +++++++--- docs/mcp.md | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/crates/ui-egui/src/control.rs b/crates/ui-egui/src/control.rs index f568f5446c..002a739d23 100644 --- a/crates/ui-egui/src/control.rs +++ b/crates/ui-egui/src/control.rs @@ -572,8 +572,9 @@ struct Rewrite<'a> { fold: bool, } -/// The keys of encoded file data in a reply. -const BASE64_FIELDS: [&str; 3] = ["dataBase64", "pngBase64", "thumbnailBase64"]; +/// The keys of encoded file data in a reply. `thumbnail` holds the PNG of a library item in +/// `library.get` and of a page in `document.pdfInfo`. +const BASE64_FIELDS: [&str; 4] = ["dataBase64", "pngBase64", "thumbnailBase64", "thumbnail"]; /// Is `s` written in the base64 alphabet, padded to a multiple of 4? fn is_base64(s: &str) -> bool { diff --git a/crates/ui-egui/src/control/tests.rs b/crates/ui-egui/src/control/tests.rs index 6ae364e172..5797b0ddb9 100644 --- a/crates/ui-egui/src/control/tests.rs +++ b/crates/ui-egui/src/control/tests.rs @@ -40,14 +40,16 @@ fn hide_under_a_root(tag: &str, reply: &mut Value, hidden: &super::Hiding) { let _ = std::fs::remove_dir_all(root); } -/// Encoded file data in a reply, a base64 string under `dataBase64`, `pngBase64` or -/// `thumbnailBase64`, stays as it is when it holds a hidden library's path. Text that is not base64 -/// under such a key, an object or a list under it, and any other key ending in `Base64` are searched. +/// Encoded file data in a reply, a base64 string under `dataBase64`, `pngBase64`, `thumbnailBase64` +/// or `thumbnail` (a library item's or a PDF page's PNG), stays as it is when it holds a hidden +/// library's path. Text that is not base64 under such a key, an object or a list under it, and any +/// other key ending in `Base64` are searched. #[test] fn a_reply_keeps_encoded_data_that_holds_a_hidden_path() { let hidden = hiding(&[("loaded/assets/icon", "loaded/icon")], &[]); let data = "QUJ+assets/iconREVGxQQ=="; let mut reply = json!({"ok": true, "result": { + "graphics": [{"name": "a", "thumbnail": data}, {"name": "b", "thumbnail": "assets/icon.png"}], "files": [{"name": "a.png", "dataBase64": data, "pngBase64": "assets/icon.png", "thumbnailBase64": data}], "status": "from assets/icon", "dataBase64": {"note": "from assets/icon"}, @@ -58,6 +60,8 @@ fn a_reply_keeps_encoded_data_that_holds_a_hidden_path() { let files = &reply["result"]["files"][0]; assert_eq!((&files["dataBase64"], &files["thumbnailBase64"]), (&json!(data), &json!(data)), "{reply}"); assert_eq!(files["pngBase64"], "icon.png", "{reply}"); + let graphics = &reply["result"]["graphics"]; + assert_eq!((&graphics[0]["thumbnail"], &graphics[1]["thumbnail"]), (&json!(data), &json!("icon.png")), "{reply}"); assert_eq!(reply["result"]["status"], "from icon", "{reply}"); assert_eq!(reply["result"]["dataBase64"]["note"], "from icon", "{reply}"); assert_eq!(reply["result"]["pngBase64"][0], "from icon", "{reply}"); diff --git a/docs/mcp.md b/docs/mcp.md index faae559e61..ab3a372be9 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -187,7 +187,7 @@ the suite) keep the agent to the folders you give it: matched before the paths the request sent, so a sent path written over part of a hidden library's path does not keep that path in the reply. A relative path is not replaced where it ends a longer path (`/work/data/icon.svg` keeps `data/icon.svg`), and the absolute path it reads as from the app's working folder is replaced too. Encoded file data, a base64 string under `dataBase64`, - `pngBase64` or `thumbnailBase64`, is sent as it is. + `pngBase64`, `thumbnailBase64` or `thumbnail` (`library.get`, `document.pdfInfo`), is sent as it is. - `swatch.library.list`, `brush.libraries`, `symbol.libraries` and `graphicStyle.libraries` list every library but leave out the paths outside the read roots: a library's `path` and the user library folder in `userFolder` are `null` there. A library loaded from a file outside the roots is listed as `loaded/`, numbered