From 4dcb918fa4d70f445ab471fb446a40fb29b4ea1a Mon Sep 17 00:00:00 2001 From: Dale Seo <5466341+DaleSeo@users.noreply.github.com> Date: Tue, 23 Jun 2026 15:32:13 -0400 Subject: [PATCH] feat!: relax tool result structuredContent type --- crates/rmcp/src/model/content.rs | 4 +-- .../client_json_rpc_message_schema.json | 8 +---- ...lient_json_rpc_message_schema_current.json | 8 +---- .../server_json_rpc_message_schema.json | 8 +---- ...erver_json_rpc_message_schema_current.json | 8 +---- crates/rmcp/tests/test_sampling.rs | 32 +++++++++++++++++++ 6 files changed, 38 insertions(+), 30 deletions(-) diff --git a/crates/rmcp/src/model/content.rs b/crates/rmcp/src/model/content.rs index c32f81b3..d505c97b 100644 --- a/crates/rmcp/src/model/content.rs +++ b/crates/rmcp/src/model/content.rs @@ -8,7 +8,7 @@ //! variants for sampling messages (SEP-1577). use serde::{Deserialize, Serialize}; -use serde_json::json; +use serde_json::{Value, json}; use super::{Annotations, Meta, resource::ResourceContents}; @@ -197,7 +197,7 @@ pub struct ToolResultContent { pub tool_use_id: String, pub content: Vec, #[serde(skip_serializing_if = "Option::is_none")] - pub structured_content: Option, + pub structured_content: Option, #[serde(skip_serializing_if = "Option::is_none")] pub is_error: Option, } diff --git a/crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema.json b/crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema.json index 46378bc9..6214078c 100644 --- a/crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema.json +++ b/crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema.json @@ -2346,13 +2346,7 @@ "null" ] }, - "structuredContent": { - "type": [ - "object", - "null" - ], - "additionalProperties": true - }, + "structuredContent": true, "toolUseId": { "type": "string" } diff --git a/crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema_current.json b/crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema_current.json index 46378bc9..6214078c 100644 --- a/crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema_current.json +++ b/crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema_current.json @@ -2346,13 +2346,7 @@ "null" ] }, - "structuredContent": { - "type": [ - "object", - "null" - ], - "additionalProperties": true - }, + "structuredContent": true, "toolUseId": { "type": "string" } diff --git a/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema.json b/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema.json index 2b3b4173..b6990809 100644 --- a/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema.json +++ b/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema.json @@ -3446,13 +3446,7 @@ "null" ] }, - "structuredContent": { - "type": [ - "object", - "null" - ], - "additionalProperties": true - }, + "structuredContent": true, "toolUseId": { "type": "string" } diff --git a/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema_current.json b/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema_current.json index 2b3b4173..b6990809 100644 --- a/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema_current.json +++ b/crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema_current.json @@ -3446,13 +3446,7 @@ "null" ] }, - "structuredContent": { - "type": [ - "object", - "null" - ], - "additionalProperties": true - }, + "structuredContent": true, "toolUseId": { "type": "string" } diff --git a/crates/rmcp/tests/test_sampling.rs b/crates/rmcp/tests/test_sampling.rs index 83d3f6fb..b1c1710b 100644 --- a/crates/rmcp/tests/test_sampling.rs +++ b/crates/rmcp/tests/test_sampling.rs @@ -370,6 +370,38 @@ fn test_tool_result_content_requires_content() { assert!(err.to_string().contains("missing field `content`")); } +#[tokio::test] +async fn test_tool_result_content_with_array_structured_content() -> Result<()> { + let structured = + serde_json::json!([{ "city": "SF", "temp": 72 }, { "city": "NY", "temp": 65 }]); + let mut tool_result = ToolResultContent::new("call_123", vec![ContentBlock::text("forecast")]); + tool_result.structured_content = Some(structured); + + let json = serde_json::to_string(&tool_result)?; + let deserialized: ToolResultContent = serde_json::from_str(&json)?; + assert_eq!(tool_result, deserialized); + assert!(deserialized.structured_content.unwrap().is_array()); + + Ok(()) +} + +#[tokio::test] +async fn test_tool_result_content_with_primitive_structured_content() -> Result<()> { + let structured = serde_json::json!(42); + let mut tool_result = ToolResultContent::new("call_123", vec![ContentBlock::text("count")]); + tool_result.structured_content = Some(structured); + + let json = serde_json::to_string(&tool_result)?; + let deserialized: ToolResultContent = serde_json::from_str(&json)?; + assert_eq!(tool_result, deserialized); + assert!(matches!( + deserialized.structured_content, + Some(serde_json::Value::Number(_)) + )); + + Ok(()) +} + #[tokio::test] async fn test_sampling_message_with_tool_use() -> Result<()> { let message = SamplingMessage::assistant_tool_use(