diff --git a/docs/automation/README.md b/docs/automation/README.md index 8333fc9eb..c76d22b5d 100644 --- a/docs/automation/README.md +++ b/docs/automation/README.md @@ -227,6 +227,14 @@ unconsumed, and the command closed rather than left armed for a second line. Geo `ocs_capture` defaults to the drawing viewport and a longest edge of 1600 pixels. Set `scope` to `window` for the full interface or change `max_dimension` between 256 and 4096. +With `diff: true`, an unchanged comparison returns metadata only, including +`diff.changed: false`, the hash and resource URI; it does not resend image or +resource content blocks, regardless of `delivery`. The initial baseline (also +after `reset_diff_baseline: true`) still uses the requested image delivery. +Changed captures retain the requested diff mode, including cropped patches. +Use `diff: false` or read the returned resource URI to retrieve the full image +when an unchanged comparison omitted it. + Clients using MCP 2026-07-28 can advertise `io.modelcontextprotocol/tasks`. OCS then returns a standard task handle when an operation is still running and accepts `tasks/get`, `tasks/update`, and `tasks/cancel`. Other clients continue to receive the existing operation status and can read it with `ocs_read`. The source-tree protocol smoke test treats the executable as a black box and uses only the Python standard library. It checks both supported protocol styles, the published schemas, structured errors, and the four-tool surface: diff --git a/src/mcp.rs b/src/mcp.rs index 116beb0a3..a51ae24ac 100644 --- a/src/mcp.rs +++ b/src/mcp.rs @@ -2311,6 +2311,16 @@ pub(crate) fn tool_definitions() -> Value { fn tool_result(value: Value) -> Value { if value.get("$resource").is_some() || value.get("$image").is_some() { + // A baseline also reports changed=false, but the client has not seen + // that image yet. Only omit media after an actual unchanged comparison. + // Keep the capture metadata (hash, URI, spatial data and request id) + // so clients can still fetch the stored image explicitly if needed. + let meta = &value["metadata"]; + if meta["diff"]["is_baseline"].as_bool() == Some(false) + && meta["diff"]["changed"].as_bool() == Some(false) + { + return tool_result(meta.clone()); + } let mut content = Vec::new(); if let Some(res) = value.get("$resource") { let uri = res["uri"].as_str().unwrap_or(""); @@ -3778,6 +3788,54 @@ mod tests { assert!(both_content.iter().any(|c| c["type"] == "image")); } + #[test] + fn unchanged_capture_omits_media_but_preserves_metadata() { + let image = image::RgbaImage::from_pixel(4, 4, image::Rgba([255, 255, 255, 255])); + let diff = crate::app::control::vision::compute_visual_diff( + &image, &image, &[1.0, 0.0, 0.0, 1.0, 0.0, 0.0], + ); + assert!(!diff.changed); + for delivery in ["inline", "resource", "both"] { + let metadata = json!({ + "hash":"unchanged-hash", "uri":"cad://session/s/snapshot/unchanged-hash.png", + "request_id":"capture-2", "revision":12, + "_spatial":{"crs":"CAD_WCS"}, + "diff":{"is_baseline":false,"changed":diff.changed} + }); + let mut capture = json!({"metadata":metadata}); + if delivery != "resource" { + capture["$image"] = json!("image-bytes-must-not-be-returned"); + } + if delivery != "inline" { + capture["$resource"] = json!({"uri":metadata["uri"],"mimeType":"image/png"}); + } + let result = tool_result(capture); + assert_eq!(result["isError"], false); + assert_eq!(result["structuredContent"], metadata); + let content = result["content"].as_array().unwrap(); + assert_eq!(content.len(), 1); + assert_eq!(content[0]["type"], "text"); + assert_eq!(serde_json::from_str::(content[0]["text"].as_str().unwrap()).unwrap(), metadata); + } + } + + #[test] + fn baseline_changed_and_non_diff_captures_keep_images() { + for diff in [ + json!({"is_baseline":true,"changed":false}), + json!({"is_baseline":false,"changed":true}), + Value::Null, + ] { + let result = tool_result(json!({ + "$image":"baseline-or-changed-png", + "metadata":{"diff":diff} + })); + assert!(result["content"].as_array().unwrap().iter().any(|block| + block["type"] == "image" && block["data"] == "baseline-or-changed-png" + )); + } + } + #[test] fn capture_tool_schema_and_diff_metadata_handling() { // 1. Verify ocs_capture schema has diff parameters