diff --git a/crates/rmcp/src/model.rs b/crates/rmcp/src/model.rs index f6cc5fb3e..fa47ea788 100644 --- a/crates/rmcp/src/model.rs +++ b/crates/rmcp/src/model.rs @@ -2697,6 +2697,7 @@ pub type ElicitationCompletionNotification = #[non_exhaustive] pub struct CallToolResult { /// The content returned by the tool (text, images, etc.) + #[serde(default)] pub content: Vec, /// An optional JSON object that represents the structured result of the tool call #[serde(skip_serializing_if = "Option::is_none")] @@ -3247,16 +3248,16 @@ ts_union!( | ListResourcesResult | ListResourceTemplatesResult | ReadResourceResult - | CallToolResult | ListToolsResult | CreateElicitationResult - | EmptyResult | CreateTaskResult | ListTasksResult | GetTaskResult | CancelTaskResult - | CustomResult + | CallToolResult | GetTaskPayloadResult + | EmptyResult + | CustomResult ; ); 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 4fb0febf0..bd8f744b0 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 @@ -388,6 +388,7 @@ "content": { "description": "The content returned by the tool (text, images, etc.)", "type": "array", + "default": [], "items": { "$ref": "#/definitions/Annotated" } @@ -402,10 +403,7 @@ "structuredContent": { "description": "An optional JSON object that represents the structured result of the tool call" } - }, - "required": [ - "content" - ] + } }, "CancelTaskResult": { "description": "Response to a `tasks/cancel` request.\n\nPer spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`.", @@ -2813,18 +2811,12 @@ { "$ref": "#/definitions/ReadResourceResult" }, - { - "$ref": "#/definitions/CallToolResult" - }, { "$ref": "#/definitions/ListToolsResult" }, { "$ref": "#/definitions/CreateElicitationResult" }, - { - "$ref": "#/definitions/EmptyObject" - }, { "$ref": "#/definitions/CreateTaskResult" }, @@ -2838,10 +2830,16 @@ "$ref": "#/definitions/CancelTaskResult" }, { - "$ref": "#/definitions/CustomResult" + "$ref": "#/definitions/CallToolResult" }, { "$ref": "#/definitions/GetTaskPayloadResult" + }, + { + "$ref": "#/definitions/EmptyObject" + }, + { + "$ref": "#/definitions/CustomResult" } ] }, 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 4fb0febf0..bd8f744b0 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 @@ -388,6 +388,7 @@ "content": { "description": "The content returned by the tool (text, images, etc.)", "type": "array", + "default": [], "items": { "$ref": "#/definitions/Annotated" } @@ -402,10 +403,7 @@ "structuredContent": { "description": "An optional JSON object that represents the structured result of the tool call" } - }, - "required": [ - "content" - ] + } }, "CancelTaskResult": { "description": "Response to a `tasks/cancel` request.\n\nPer spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`.", @@ -2813,18 +2811,12 @@ { "$ref": "#/definitions/ReadResourceResult" }, - { - "$ref": "#/definitions/CallToolResult" - }, { "$ref": "#/definitions/ListToolsResult" }, { "$ref": "#/definitions/CreateElicitationResult" }, - { - "$ref": "#/definitions/EmptyObject" - }, { "$ref": "#/definitions/CreateTaskResult" }, @@ -2838,10 +2830,16 @@ "$ref": "#/definitions/CancelTaskResult" }, { - "$ref": "#/definitions/CustomResult" + "$ref": "#/definitions/CallToolResult" }, { "$ref": "#/definitions/GetTaskPayloadResult" + }, + { + "$ref": "#/definitions/EmptyObject" + }, + { + "$ref": "#/definitions/CustomResult" } ] }, diff --git a/crates/rmcp/tests/test_structured_output.rs b/crates/rmcp/tests/test_structured_output.rs index 082d3e439..b498d3120 100644 --- a/crates/rmcp/tests/test_structured_output.rs +++ b/crates/rmcp/tests/test_structured_output.rs @@ -298,18 +298,20 @@ async fn test_empty_content_array_with_is_error() { assert_eq!(result.is_error, Some(false)); } -#[tokio::test] -async fn test_missing_content_is_rejected() { +#[test] +fn test_missing_content_defaults_to_empty() { let raw = json!({ "isError": false }); - let result: Result = serde_json::from_value(raw); - assert!(result.is_err()); + let result: CallToolResult = serde_json::from_value(raw).unwrap(); + assert!(result.content.is_empty()); + assert_eq!(result.is_error, Some(false)); } -#[tokio::test] -async fn test_missing_content_with_structured_content_is_rejected() { +#[test] +fn test_missing_content_with_structured_content_deserializes() { let raw = json!({ "structuredContent": {"key": "value"}, "isError": false }); - let result: Result = serde_json::from_value(raw); - assert!(result.is_err()); + let result: CallToolResult = serde_json::from_value(raw).unwrap(); + assert!(result.content.is_empty()); + assert_eq!(result.structured_content.unwrap()["key"], "value"); } #[tokio::test] @@ -333,3 +335,13 @@ async fn test_empty_content_roundtrip() { let deserialized: CallToolResult = serde_json::from_value(v).unwrap(); assert_eq!(deserialized, result); } + +#[test] +fn test_call_tool_result_deserialize_without_content() { + let json = json!({ + "structuredContent": {"message": "Hello"} + }); + let result: CallToolResult = serde_json::from_value(json).unwrap(); + assert!(result.content.is_empty()); + assert!(result.structured_content.is_some()); +}