From b55e3ad70a8e7e321e0f3991cecc8fa8fba16644 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Wed, 7 Oct 2026 11:50:55 +0700 Subject: [PATCH] Fix #258: the save_file tool says what it saves It ran document.save, which keeps the document's own file and format (or takes the format the path's extension picks), but described itself as a native .vectorcraft save. The description now says so, with the formats generated from SAVE_FORMATS, and points to export for the others. Co-Authored-By: Claude Opus 5.5 --- crates/mcp/src/tests.rs | 11 +++++++++++ crates/mcp/src/tools.rs | 15 ++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/crates/mcp/src/tests.rs b/crates/mcp/src/tests.rs index 0cec1c75..ae33efec 100644 --- a/crates/mcp/src/tests.rs +++ b/crates/mcp/src/tests.rs @@ -303,6 +303,17 @@ fn inspect_document_reports_the_paint_of_type() { assert_eq!((t["fill"].as_str(), t["stroke"].as_str(), t["strokeWidth"].as_f64()), (Some("#0000ff"), Some("#00ff00"), Some(3.0)), "{t}"); } +/// save_file says it saves in the document's own format or the one the path's extension picks. +#[test] +fn save_file_describes_the_formats_it_writes() { + let tools = tool_definitions(); + let d = tools.iter().find(|t| t["name"] == "save_file").and_then(|t| t["description"].as_str()).unwrap(); + for ext in [".vectorcraft", ".vctemplate", ".pdf", ".svg", ".svgz", ".ai"] { + assert!(d.contains(ext), "{ext}: {d}"); + } + assert!(d.contains("its own format"), "{d}"); +} + #[test] fn errors_are_tool_results_not_crashes() { let mut s = server(); diff --git a/crates/mcp/src/tools.rs b/crates/mcp/src/tools.rs index f12cb198..328fe3ce 100644 --- a/crates/mcp/src/tools.rs +++ b/crates/mcp/src/tools.rs @@ -2,7 +2,7 @@ //! [`Backend`]. use serde_json::{Map, Value, json}; -use vectorcraft_engine::cmd::fileio::{ARTBOARD_PARAMS, FORMATS, OPEN_EXTS}; +use vectorcraft_engine::cmd::fileio::{ARTBOARD_PARAMS, FORMATS, OPEN_EXTS, SAVE_FORMATS, format}; use crate::backend::Backend; @@ -57,6 +57,12 @@ fn obj(props: Value, required: &[&str]) -> Value { } o } + +/// The extension of each format Save writes, in [`SAVE_FORMATS`] order. +fn save_extensions() -> Vec<&'static str> { + SAVE_FORMATS.iter().filter_map(|id| format(id)?.extensions.first().copied()).collect() +} + fn tool(name: &str, title: &str, desc: &str, schema: Value, read_only: bool) -> Value { json!({ "name": name, @@ -259,8 +265,11 @@ pub fn tool_definitions() -> Vec { tool( "save_file", "Save file", - "Save the active document in the native .vectorcraft format.", - obj(json!({"path": string("Destination (default: the document's current path)")}), &[]), + &format!( + "Save the active document with the engine's document.save: by default to its own file in its own format (native .vectorcraft unless it was opened from or saved as SVG, PDF or a restorable .ai). With `path`, the extension picks the format: .{}. Other formats are exports (see export). The reply's `format` says what was written and `warnings` what that format loses.", + save_extensions().join(", .") + ), + obj(json!({"path": string("Destination; its extension picks the format (default: the document's own file)")}), &[]), false, ), tool(