From 825538fc8b68efe2e05a8ecbfe25befb646b0cbd Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:01:12 +0200 Subject: [PATCH] MCP type_text reports a refused paragraph break instead of typing the next line (#1356) (#1407) --- crates/mcp/src/tests.rs | 33 +++++++++++++++++++++++++++++++++ crates/mcp/src/tools.rs | 3 +++ 2 files changed, 36 insertions(+) diff --git a/crates/mcp/src/tests.rs b/crates/mcp/src/tests.rs index 5ea5ad9f5..781f24d02 100644 --- a/crates/mcp/src/tests.rs +++ b/crates/mcp/src/tests.rs @@ -119,3 +119,36 @@ fn headless_sessions_keep_the_dictionary_in_memory() { h.session.run("review.addToDictionary", &json!({"word": "zxqmcpword2"})).expect("added"); assert!(h.session.user_dict_path.is_none()); } + +/// A backend whose paragraph break is refused while text insertion would succeed. +struct RefusesParagraph { + calls: std::sync::Arc>>, +} + +impl crate::backend::Backend for RefusesParagraph { + fn call(&mut self, _method: &str, params: Value) -> Result { + let command = params["command"].as_str().unwrap_or("").to_string(); + self.calls.lock().unwrap().push(command.clone()); + match command.as_str() { + "text.newParagraph" => Err("paragraph break refused".into()), + _ => Ok(json!({"ok": true})), + } + } + fn has_ui(&self) -> bool { + false + } + fn describe(&self) -> String { + "refuses paragraphs".into() + } +} + +/// #1356: a refused paragraph break is reported and the next line is not typed after it. +#[test] +fn type_text_stops_at_a_refused_paragraph_break() { + let calls = std::sync::Arc::new(std::sync::Mutex::new(Vec::new())); + let mut s = Server::new(Box::new(RefusesParagraph { calls: calls.clone() })); + let r = call(&mut s, 11, "tools/call", json!({"name": "type_text", "arguments": {"text": "one\ntwo", "paragraphs": true}})); + assert_eq!(r["result"]["isError"], true, "{r}"); + assert!(r["result"]["content"][0]["text"].as_str().unwrap().contains("paragraph break refused"), "{r}"); + assert_eq!(*calls.lock().unwrap(), vec!["text.insert", "text.newParagraph"]); +} diff --git a/crates/mcp/src/tools.rs b/crates/mcp/src/tools.rs index b01106af6..7dd99085b 100644 --- a/crates/mcp/src/tools.rs +++ b/crates/mcp/src/tools.rs @@ -233,6 +233,9 @@ pub fn call_tool(b: &mut dyn Backend, name: &str, a: &Value) -> ToolResult { for (i, line) in t.split('\n').enumerate() { if i > 0 { last = exec(b, "text.newParagraph", json!({})); + if last.is_err() { + break; + } } if !line.is_empty() { last = exec(b, "text.insert", json!({"text": line, "raw": true}));