Skip to content

Commit 044bff3

Browse files
committed
fix: default CallToolResult content to empty vec on missing field
1 parent 1a4a52a commit 044bff3

File tree

4 files changed

+42
-33
lines changed

4 files changed

+42
-33
lines changed

crates/rmcp/src/model.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2697,6 +2697,7 @@ pub type ElicitationCompletionNotification =
26972697
#[non_exhaustive]
26982698
pub struct CallToolResult {
26992699
/// The content returned by the tool (text, images, etc.)
2700+
#[serde(default)]
27002701
pub content: Vec<Content>,
27012702
/// An optional JSON object that represents the structured result of the tool call
27022703
#[serde(skip_serializing_if = "Option::is_none")]
@@ -3247,16 +3248,16 @@ ts_union!(
32473248
| ListResourcesResult
32483249
| ListResourceTemplatesResult
32493250
| ReadResourceResult
3250-
| CallToolResult
32513251
| ListToolsResult
32523252
| CreateElicitationResult
3253-
| EmptyResult
32543253
| CreateTaskResult
32553254
| ListTasksResult
32563255
| GetTaskResult
32573256
| CancelTaskResult
3258-
| CustomResult
3257+
| CallToolResult
32593258
| GetTaskPayloadResult
3259+
| EmptyResult
3260+
| CustomResult
32603261
;
32613262
);
32623263

crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema.json

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@
388388
"content": {
389389
"description": "The content returned by the tool (text, images, etc.)",
390390
"type": "array",
391+
"default": [],
391392
"items": {
392393
"$ref": "#/definitions/Annotated"
393394
}
@@ -402,10 +403,7 @@
402403
"structuredContent": {
403404
"description": "An optional JSON object that represents the structured result of the tool call"
404405
}
405-
},
406-
"required": [
407-
"content"
408-
]
406+
}
409407
},
410408
"CancelTaskResult": {
411409
"description": "Response to a `tasks/cancel` request.\n\nPer spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`.",
@@ -2813,18 +2811,12 @@
28132811
{
28142812
"$ref": "#/definitions/ReadResourceResult"
28152813
},
2816-
{
2817-
"$ref": "#/definitions/CallToolResult"
2818-
},
28192814
{
28202815
"$ref": "#/definitions/ListToolsResult"
28212816
},
28222817
{
28232818
"$ref": "#/definitions/CreateElicitationResult"
28242819
},
2825-
{
2826-
"$ref": "#/definitions/EmptyObject"
2827-
},
28282820
{
28292821
"$ref": "#/definitions/CreateTaskResult"
28302822
},
@@ -2838,10 +2830,16 @@
28382830
"$ref": "#/definitions/CancelTaskResult"
28392831
},
28402832
{
2841-
"$ref": "#/definitions/CustomResult"
2833+
"$ref": "#/definitions/CallToolResult"
28422834
},
28432835
{
28442836
"$ref": "#/definitions/GetTaskPayloadResult"
2837+
},
2838+
{
2839+
"$ref": "#/definitions/EmptyObject"
2840+
},
2841+
{
2842+
"$ref": "#/definitions/CustomResult"
28452843
}
28462844
]
28472845
},

crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema_current.json

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@
388388
"content": {
389389
"description": "The content returned by the tool (text, images, etc.)",
390390
"type": "array",
391+
"default": [],
391392
"items": {
392393
"$ref": "#/definitions/Annotated"
393394
}
@@ -402,10 +403,7 @@
402403
"structuredContent": {
403404
"description": "An optional JSON object that represents the structured result of the tool call"
404405
}
405-
},
406-
"required": [
407-
"content"
408-
]
406+
}
409407
},
410408
"CancelTaskResult": {
411409
"description": "Response to a `tasks/cancel` request.\n\nPer spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`.",
@@ -2813,18 +2811,12 @@
28132811
{
28142812
"$ref": "#/definitions/ReadResourceResult"
28152813
},
2816-
{
2817-
"$ref": "#/definitions/CallToolResult"
2818-
},
28192814
{
28202815
"$ref": "#/definitions/ListToolsResult"
28212816
},
28222817
{
28232818
"$ref": "#/definitions/CreateElicitationResult"
28242819
},
2825-
{
2826-
"$ref": "#/definitions/EmptyObject"
2827-
},
28282820
{
28292821
"$ref": "#/definitions/CreateTaskResult"
28302822
},
@@ -2838,10 +2830,16 @@
28382830
"$ref": "#/definitions/CancelTaskResult"
28392831
},
28402832
{
2841-
"$ref": "#/definitions/CustomResult"
2833+
"$ref": "#/definitions/CallToolResult"
28422834
},
28432835
{
28442836
"$ref": "#/definitions/GetTaskPayloadResult"
2837+
},
2838+
{
2839+
"$ref": "#/definitions/EmptyObject"
2840+
},
2841+
{
2842+
"$ref": "#/definitions/CustomResult"
28452843
}
28462844
]
28472845
},

crates/rmcp/tests/test_structured_output.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -298,18 +298,20 @@ async fn test_empty_content_array_with_is_error() {
298298
assert_eq!(result.is_error, Some(false));
299299
}
300300

301-
#[tokio::test]
302-
async fn test_missing_content_is_rejected() {
301+
#[test]
302+
fn test_missing_content_defaults_to_empty() {
303303
let raw = json!({ "isError": false });
304-
let result: Result<CallToolResult, _> = serde_json::from_value(raw);
305-
assert!(result.is_err());
304+
let result: CallToolResult = serde_json::from_value(raw).unwrap();
305+
assert!(result.content.is_empty());
306+
assert_eq!(result.is_error, Some(false));
306307
}
307308

308-
#[tokio::test]
309-
async fn test_missing_content_with_structured_content_is_rejected() {
309+
#[test]
310+
fn test_missing_content_with_structured_content_deserializes() {
310311
let raw = json!({ "structuredContent": {"key": "value"}, "isError": false });
311-
let result: Result<CallToolResult, _> = serde_json::from_value(raw);
312-
assert!(result.is_err());
312+
let result: CallToolResult = serde_json::from_value(raw).unwrap();
313+
assert!(result.content.is_empty());
314+
assert_eq!(result.structured_content.unwrap()["key"], "value");
313315
}
314316

315317
#[tokio::test]
@@ -333,3 +335,13 @@ async fn test_empty_content_roundtrip() {
333335
let deserialized: CallToolResult = serde_json::from_value(v).unwrap();
334336
assert_eq!(deserialized, result);
335337
}
338+
339+
#[test]
340+
fn test_call_tool_result_deserialize_without_content() {
341+
let json = json!({
342+
"structuredContent": {"message": "Hello"}
343+
});
344+
let result: CallToolResult = serde_json::from_value(json).unwrap();
345+
assert!(result.content.is_empty());
346+
assert!(result.structured_content.is_some());
347+
}

0 commit comments

Comments
 (0)