Automation: file-writing tools no longer advertise read-only (#130) (#138)

doc_export_data, accessibility_report and image_save each write to their required path and replace an existing file there, but the tool table marked all three read-only, so MCP clients were told the calls change no file. They now carry the destructive flag the table defines for a tool that overwrites files, and a test pins the three names.

Co-authored-by: Mohammed Munzir <Mohammed.Munzir@iga.gov.bh>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
munzir
2026-10-07 21:14:16 -04:00
committed by GitHub
co-authored by Mohammed Munzir Brandon Thomas
parent ff1a8b85dc
commit 38c62bda3e
2 changed files with 14 additions and 3 deletions
+3 -3
View File
@@ -307,7 +307,7 @@ pub fn tools() -> Vec<ToolDef> {
&["doc"],
)),
t("doc_export_data", "Export comments or form data", "Write comments and/or form data to path; the extension picks the format: .xfdf or .fdf (comments and/or fields), .xml, .csv or .txt (form data). what: all (default), comments, fields.")
.ro()
.destructive()
.with(schema(json!({ "doc": doc(), "path": { "type": "string" }, "what": { "type": "string", "enum": ["all", "comments", "fields"] } }), &["doc", "path"])),
t("doc_import_data", "Import comments or form data", "Import comments and/or field values from an XFDF, FDF, XML, CSV or tab-delimited text file (detected from its content). Comments with the same name are replaced; values go through the form's formats and validation. Undoable.")
.with(schema(json!({ "doc": doc(), "path": { "type": "string" } }), &["doc", "path"])),
@@ -720,7 +720,7 @@ pub fn tools() -> Vec<ToolDef> {
&["doc"],
)),
t("accessibility_report", "Accessibility report", "Run the full check and write the accessibility report (HTML) to path; returns the results too. Takes the same options as accessibility_check.")
.ro()
.destructive()
.cmd("a11y.report")
.with(schema(
json!({
@@ -943,7 +943,7 @@ pub fn tools() -> Vec<ToolDef> {
&["doc", "page", "image", "action"],
)),
t("image_save", "Save image as", "Write one of a page's images to path: JPEG images unchanged, others as PNG (the extension is added when missing).")
.ro()
.destructive()
.cmd("edit.edit_text")
.with(schema(json!({ "doc": doc(), "page": { "type": "integer", "minimum": 1 }, "image": { "type": "integer", "minimum": 1 }, "path": { "type": "string" } }), &["doc", "page", "image", "path"])),
t("text_paragraphs", "List paragraphs", "The paragraphs on a page (lines grouped by font, size, alignment and spacing): number, text, its line numbers, box (top-left-origin points), font and size. Use the number with text_edit's paragraph.")
+11
View File
@@ -82,6 +82,17 @@ fn tool_table_is_well_formed() {
}
}
/// Each of these writes to its required `path` and replaces an existing file there, so the MCP
/// annotations must not tell clients the call is read-only or harmless (#130).
#[test]
fn file_writing_tools_are_not_read_only() {
for name in ["doc_export_data", "accessibility_report", "image_save"] {
let t = tools().into_iter().find(|t| t.name == name).unwrap();
assert!(!t.read_only, "{name} writes a file but advertises read-only");
assert!(t.destructive, "{name} overwrites its path but advertises non-destructive");
}
}
#[test]
fn open_inspect_render_and_find() {
let dir = workdir("inspect");