fix: default CallToolResult content to empty vec on missing field (#752)
This commit is contained in:
parent
66712db808
commit
44dfcf5550
4 changed files with 42 additions and 33 deletions
|
|
@ -2697,6 +2697,7 @@ pub type ElicitationCompletionNotification =
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct CallToolResult {
|
pub struct CallToolResult {
|
||||||
/// The content returned by the tool (text, images, etc.)
|
/// The content returned by the tool (text, images, etc.)
|
||||||
|
#[serde(default)]
|
||||||
pub content: Vec<Content>,
|
pub content: Vec<Content>,
|
||||||
/// An optional JSON object that represents the structured result of the tool call
|
/// An optional JSON object that represents the structured result of the tool call
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
|
@ -3247,16 +3248,16 @@ ts_union!(
|
||||||
| ListResourcesResult
|
| ListResourcesResult
|
||||||
| ListResourceTemplatesResult
|
| ListResourceTemplatesResult
|
||||||
| ReadResourceResult
|
| ReadResourceResult
|
||||||
| CallToolResult
|
|
||||||
| ListToolsResult
|
| ListToolsResult
|
||||||
| CreateElicitationResult
|
| CreateElicitationResult
|
||||||
| EmptyResult
|
|
||||||
| CreateTaskResult
|
| CreateTaskResult
|
||||||
| ListTasksResult
|
| ListTasksResult
|
||||||
| GetTaskResult
|
| GetTaskResult
|
||||||
| CancelTaskResult
|
| CancelTaskResult
|
||||||
| CustomResult
|
| CallToolResult
|
||||||
| GetTaskPayloadResult
|
| GetTaskPayloadResult
|
||||||
|
| EmptyResult
|
||||||
|
| CustomResult
|
||||||
;
|
;
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -388,6 +388,7 @@
|
||||||
"content": {
|
"content": {
|
||||||
"description": "The content returned by the tool (text, images, etc.)",
|
"description": "The content returned by the tool (text, images, etc.)",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/Annotated"
|
"$ref": "#/definitions/Annotated"
|
||||||
}
|
}
|
||||||
|
|
@ -402,10 +403,7 @@
|
||||||
"structuredContent": {
|
"structuredContent": {
|
||||||
"description": "An optional JSON object that represents the structured result of the tool call"
|
"description": "An optional JSON object that represents the structured result of the tool call"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"required": [
|
|
||||||
"content"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"CancelTaskResult": {
|
"CancelTaskResult": {
|
||||||
"description": "Response to a `tasks/cancel` request.\n\nPer spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`.",
|
"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/ReadResourceResult"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"$ref": "#/definitions/CallToolResult"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/ListToolsResult"
|
"$ref": "#/definitions/ListToolsResult"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/CreateElicitationResult"
|
"$ref": "#/definitions/CreateElicitationResult"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"$ref": "#/definitions/EmptyObject"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/CreateTaskResult"
|
"$ref": "#/definitions/CreateTaskResult"
|
||||||
},
|
},
|
||||||
|
|
@ -2838,10 +2830,16 @@
|
||||||
"$ref": "#/definitions/CancelTaskResult"
|
"$ref": "#/definitions/CancelTaskResult"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/CustomResult"
|
"$ref": "#/definitions/CallToolResult"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/GetTaskPayloadResult"
|
"$ref": "#/definitions/GetTaskPayloadResult"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/EmptyObject"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/CustomResult"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -388,6 +388,7 @@
|
||||||
"content": {
|
"content": {
|
||||||
"description": "The content returned by the tool (text, images, etc.)",
|
"description": "The content returned by the tool (text, images, etc.)",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/Annotated"
|
"$ref": "#/definitions/Annotated"
|
||||||
}
|
}
|
||||||
|
|
@ -402,10 +403,7 @@
|
||||||
"structuredContent": {
|
"structuredContent": {
|
||||||
"description": "An optional JSON object that represents the structured result of the tool call"
|
"description": "An optional JSON object that represents the structured result of the tool call"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"required": [
|
|
||||||
"content"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"CancelTaskResult": {
|
"CancelTaskResult": {
|
||||||
"description": "Response to a `tasks/cancel` request.\n\nPer spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`.",
|
"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/ReadResourceResult"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"$ref": "#/definitions/CallToolResult"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/ListToolsResult"
|
"$ref": "#/definitions/ListToolsResult"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/CreateElicitationResult"
|
"$ref": "#/definitions/CreateElicitationResult"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"$ref": "#/definitions/EmptyObject"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/CreateTaskResult"
|
"$ref": "#/definitions/CreateTaskResult"
|
||||||
},
|
},
|
||||||
|
|
@ -2838,10 +2830,16 @@
|
||||||
"$ref": "#/definitions/CancelTaskResult"
|
"$ref": "#/definitions/CancelTaskResult"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/CustomResult"
|
"$ref": "#/definitions/CallToolResult"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/GetTaskPayloadResult"
|
"$ref": "#/definitions/GetTaskPayloadResult"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/EmptyObject"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/CustomResult"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -298,18 +298,20 @@ async fn test_empty_content_array_with_is_error() {
|
||||||
assert_eq!(result.is_error, Some(false));
|
assert_eq!(result.is_error, Some(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn test_missing_content_is_rejected() {
|
fn test_missing_content_defaults_to_empty() {
|
||||||
let raw = json!({ "isError": false });
|
let raw = json!({ "isError": false });
|
||||||
let result: Result<CallToolResult, _> = serde_json::from_value(raw);
|
let result: CallToolResult = serde_json::from_value(raw).unwrap();
|
||||||
assert!(result.is_err());
|
assert!(result.content.is_empty());
|
||||||
|
assert_eq!(result.is_error, Some(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn test_missing_content_with_structured_content_is_rejected() {
|
fn test_missing_content_with_structured_content_deserializes() {
|
||||||
let raw = json!({ "structuredContent": {"key": "value"}, "isError": false });
|
let raw = json!({ "structuredContent": {"key": "value"}, "isError": false });
|
||||||
let result: Result<CallToolResult, _> = serde_json::from_value(raw);
|
let result: CallToolResult = serde_json::from_value(raw).unwrap();
|
||||||
assert!(result.is_err());
|
assert!(result.content.is_empty());
|
||||||
|
assert_eq!(result.structured_content.unwrap()["key"], "value");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
@ -333,3 +335,13 @@ async fn test_empty_content_roundtrip() {
|
||||||
let deserialized: CallToolResult = serde_json::from_value(v).unwrap();
|
let deserialized: CallToolResult = serde_json::from_value(v).unwrap();
|
||||||
assert_eq!(deserialized, result);
|
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());
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue