Skip to content

Commit aa88079

Browse files
olaservoclaude
andcommitted
feat: add with_content for pairing structured results with a custom rendering
content and structuredContent need not match: with_content replaces the content blocks on any result, so a structured_only result can carry a short text summary while structuredContent holds the full value. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 9b7b3c4 commit aa88079

2 files changed

Lines changed: 42 additions & 0 deletions

File tree

crates/rmcp/src/model.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3869,6 +3869,9 @@ impl CallToolResult {
38693869
/// but such clients will receive an empty `content` array — only opt out
38703870
/// when you know your callers consume `structuredContent`.
38713871
///
3872+
/// To send a custom rendering instead of none (for example a short text
3873+
/// summary of a large value), chain [`CallToolResult::with_content`].
3874+
///
38723875
/// # Example
38733876
///
38743877
/// ```rust,ignore
@@ -3906,6 +3909,27 @@ impl CallToolResult {
39063909
}
39073910
}
39083911

3912+
/// Replace the `content` blocks on this result.
3913+
///
3914+
/// The `content` and `structuredContent` fields need not carry the same
3915+
/// data: pairing a hand-written rendering with a structured value is
3916+
/// valid, e.g. a short text summary in `content` while
3917+
/// `structured_content` holds the full value.
3918+
///
3919+
/// # Example
3920+
///
3921+
/// ```rust,ignore
3922+
/// use rmcp::model::{CallToolResult, ContentBlock};
3923+
/// use serde_json::json;
3924+
///
3925+
/// let result = CallToolResult::structured_only(json!({"rows": [1, 2, 3]}))
3926+
/// .with_content(vec![ContentBlock::text("3 rows matched")]);
3927+
/// ```
3928+
pub fn with_content(mut self, content: Vec<ContentBlock>) -> Self {
3929+
self.content = content;
3930+
self
3931+
}
3932+
39093933
/// Set the metadata on this result
39103934
pub fn with_meta(mut self, meta: Option<MetaObject>) -> Self {
39113935
self.meta = meta;

crates/rmcp/tests/test_structured_output.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,24 @@ async fn test_structured_only_in_call_result() {
238238
assert_eq!(serialized["structuredContent"]["sum"], 7);
239239
}
240240

241+
#[tokio::test]
242+
async fn test_structured_only_with_divergent_content() {
243+
// with_content pairs structured_content with a custom rendering instead of
244+
// the serialized mirror
245+
let structured_data = json!({"rows": [1, 2, 3]});
246+
247+
let result = CallToolResult::structured_only(structured_data.clone())
248+
.with_content(vec![ContentBlock::text("3 rows matched")]);
249+
250+
assert_eq!(result.content.len(), 1);
251+
assert_eq!(
252+
result.content.first().unwrap().as_text().unwrap().text,
253+
"3 rows matched"
254+
);
255+
assert_eq!(result.structured_content, Some(structured_data));
256+
assert_eq!(result.is_error, Some(false));
257+
}
258+
241259
#[tokio::test]
242260
async fn test_structured_error_only_in_call_result() {
243261
let error_data = json!({

0 commit comments

Comments
 (0)