From 9c42c0c5fefc7f02208e11636bd50dc31e72b7cf Mon Sep 17 00:00:00 2001 From: Eromosele Date: Thu, 15 Oct 2026 02:34:20 -0400 Subject: [PATCH] fix(automation): no top-level oneOf in form_set_props input schema (#1961) (#1967) Anthropic's tool API rejects a request when any tool's input_schema has oneOf, anyOf or allOf at the top level, so attaching `pdfcraft-cli mcp` to a Claude-based client made every prompt fail. The field/fields rule is already enforced in the form_set_props handler and described in the schema, so the oneOf is dropped. tool_table_is_well_formed now checks that no tool has a top-level oneOf/anyOf/allOf. Co-authored-by: Eromosele <188490945+andreero0@users.noreply.github.com> --- crates/automation/src/tools.rs | 8 ++------ crates/automation/tests/automation.rs | 3 +++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/automation/src/tools.rs b/crates/automation/src/tools.rs index 0475d84656..26744483af 100644 --- a/crates/automation/src/tools.rs +++ b/crates/automation/src/tools.rs @@ -372,8 +372,7 @@ pub fn tools() -> Vec { "Field properties", "Change field properties: give field for one field, or fields for a list of unique field names (1–1000). A bulk change is atomic and one Undo step; an invalid or locked field leaves every field unchanged. Only the given properties change, and partial appearance changes preserve each field's other colours and font. name (rename), rect (position) and rotation require a single field. General and Options: tooltip, read_only, required, multiline, max_length (0 = no limit), options (combo/list items), font_size (0 = auto), format, validate, calculate (Acrobat's native AF rules). Options: align (left, center, right), default (Reset form's value; check boxes/radio buttons use their on state, \"\" for off), flags { scroll, rich_text, password, file_select, spell_check, comb (needs max_length), sort, editable, multi_select, commit_immediately }, check_style (check boxes/radio buttons: check, circle, cross, diamond, square, star). rotation is 0, 90, 180 or 270 degrees counterclockwise and swaps width/height when the axis changes. Undoable.", ) - .with({ - let mut input = schema( + .with(schema( json!({ "doc": doc(), "field": { "type": "string", "description": "The field's current name." }, @@ -399,10 +398,7 @@ pub fn tools() -> Vec { "calculate": { "description": "Calculate tab: {\"op\": \"sum\"|\"product\"|\"average\"|\"min\"|\"max\", \"fields\": [\"a\", \"b\"]}, {\"notation\": \"Price * Qty\"} (simplified field notation), or \"none\"." }, }), &["doc"], - ); - input["oneOf"] = json!([{ "required": ["field"] }, { "required": ["fields"] }]); - input - }), + )), t("form_tab_order", "Set the tab order", "Set the tab order of pages (default all): row (top to bottom, left to right), column, structure, or annotations (unspecified). Or order tabs manually: `field` with `move` earlier|later moves that field one place on its page. Returns the resulting order of fields. Undoable.") .with(schema( json!({ "doc": doc(), "order": { "type": "string", "enum": ["row", "column", "structure", "annotations"] }, "pages": pages("to set (default: all)"), "field": { "type": "string" }, "move": { "type": "string", "enum": ["earlier", "later"] } }), diff --git a/crates/automation/tests/automation.rs b/crates/automation/tests/automation.rs index ba6780a281..9f4ca1b406 100644 --- a/crates/automation/tests/automation.rs +++ b/crates/automation/tests/automation.rs @@ -238,6 +238,9 @@ fn tool_table_is_well_formed() { assert!(props.contains_key(r.as_str().unwrap()), "{}: required {r} is not a property", t.name); } assert!(!(t.read_only && t.destructive), "{} is both read-only and destructive", t.name); + for k in ["oneOf", "anyOf", "allOf"] { + assert!(t.input_schema.get(k).is_none(), "{}: top-level {k} in input_schema is rejected by MCP clients such as Claude", t.name); + } if let Some(c) = t.command { assert!(pdfcraft_engine::commands::command(c).is_some(), "{} names unregistered command {c}", t.name); }