feat(McpSchema): CallToolResult and CallToolRequest usability improvements (#87)
- Add constructor to CallToolResult with one String entry - Add a new constructor to CallToolRequest that accepts JSON string arguments - Implement a builder pattern for CallToolResult with methods for adding content items - Add test coverage for new functionality Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com> Co-authored-by: Christian Tzolov <christian.tzolov@broadcom.com>
This commit is contained in:
parent
bda3cab843
commit
8d5872fd66
2 changed files with 223 additions and 0 deletions
|
|
@ -18,6 +18,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
|||
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.modelcontextprotocol.util.Assert;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -741,6 +742,19 @@ public final class McpSchema {
|
|||
public record CallToolRequest(// @formatter:off
|
||||
@JsonProperty("name") String name,
|
||||
@JsonProperty("arguments") Map<String, Object> arguments) implements Request {
|
||||
|
||||
public CallToolRequest(String name, String jsonArguments) {
|
||||
this(name, parseJsonArguments(jsonArguments));
|
||||
}
|
||||
|
||||
private static Map<String, Object> parseJsonArguments(String jsonArguments) {
|
||||
try {
|
||||
return OBJECT_MAPPER.readValue(jsonArguments, MAP_TYPE_REF);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IllegalArgumentException("Invalid arguments: " + jsonArguments, e);
|
||||
}
|
||||
}
|
||||
}// @formatter:off
|
||||
|
||||
/**
|
||||
|
|
@ -756,6 +770,103 @@ public final class McpSchema {
|
|||
public record CallToolResult( // @formatter:off
|
||||
@JsonProperty("content") List<Content> content,
|
||||
@JsonProperty("isError") Boolean isError) {
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link CallToolResult} with a string containing the
|
||||
* tool result.
|
||||
*
|
||||
* @param content The content of the tool result. This will be mapped to a one-sized list
|
||||
* with a {@link TextContent} element.
|
||||
* @param isError If true, indicates that the tool execution failed and the content contains error information.
|
||||
* If false or absent, indicates successful execution.
|
||||
*/
|
||||
public CallToolResult(String content, Boolean isError) {
|
||||
this(List.of(new TextContent(content)), isError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for {@link CallToolResult}.
|
||||
* @return a new builder instance
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link CallToolResult}.
|
||||
*/
|
||||
public static class Builder {
|
||||
private List<Content> content = new ArrayList<>();
|
||||
private Boolean isError;
|
||||
|
||||
/**
|
||||
* Sets the content list for the tool result.
|
||||
* @param content the content list
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder content(List<Content> content) {
|
||||
Assert.notNull(content, "content must not be null");
|
||||
this.content = content;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text content for the tool result.
|
||||
* @param textContent the text content
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder textContent(List<String> textContent) {
|
||||
Assert.notNull(textContent, "textContent must not be null");
|
||||
textContent.stream()
|
||||
.map(TextContent::new)
|
||||
.forEach(this.content::add);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a content item to the tool result.
|
||||
* @param contentItem the content item to add
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder addContent(Content contentItem) {
|
||||
Assert.notNull(contentItem, "contentItem must not be null");
|
||||
if (this.content == null) {
|
||||
this.content = new ArrayList<>();
|
||||
}
|
||||
this.content.add(contentItem);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a text content item to the tool result.
|
||||
* @param text the text content
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder addTextContent(String text) {
|
||||
Assert.notNull(text, "text must not be null");
|
||||
return addContent(new TextContent(text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the tool execution resulted in an error.
|
||||
* @param isError true if the tool execution failed, false otherwise
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder isError(Boolean isError) {
|
||||
Assert.notNull(isError, "isError must not be null");
|
||||
this.isError = isError;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link CallToolResult} instance.
|
||||
* @return a new CallToolResult instance
|
||||
*/
|
||||
public CallToolResult build() {
|
||||
return new CallToolResult(content, isError);
|
||||
}
|
||||
}
|
||||
|
||||
} // @formatter:on
|
||||
|
||||
// ---------------------------
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ package io.modelcontextprotocol.spec;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
|
@ -493,6 +494,25 @@ public class McpSchemaTests {
|
|||
{"name":"test-tool","arguments":{"name":"test","value":42}}"""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCallToolRequestJsonArguments() throws Exception {
|
||||
|
||||
McpSchema.CallToolRequest request = new McpSchema.CallToolRequest("test-tool", """
|
||||
{
|
||||
"name": "test",
|
||||
"value": 42
|
||||
}
|
||||
""");
|
||||
|
||||
String value = mapper.writeValueAsString(request);
|
||||
|
||||
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
|
||||
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
|
||||
.isObject()
|
||||
.isEqualTo(json("""
|
||||
{"name":"test-tool","arguments":{"name":"test","value":42}}"""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCallToolResult() throws Exception {
|
||||
McpSchema.TextContent content = new McpSchema.TextContent("Tool execution result");
|
||||
|
|
@ -508,6 +528,98 @@ public class McpSchemaTests {
|
|||
{"content":[{"type":"text","text":"Tool execution result"}],"isError":false}"""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCallToolResultBuilder() throws Exception {
|
||||
McpSchema.CallToolResult result = McpSchema.CallToolResult.builder()
|
||||
.addTextContent("Tool execution result")
|
||||
.isError(false)
|
||||
.build();
|
||||
|
||||
String value = mapper.writeValueAsString(result);
|
||||
|
||||
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
|
||||
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
|
||||
.isObject()
|
||||
.isEqualTo(json("""
|
||||
{"content":[{"type":"text","text":"Tool execution result"}],"isError":false}"""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCallToolResultBuilderWithMultipleContents() throws Exception {
|
||||
McpSchema.TextContent textContent = new McpSchema.TextContent("Text result");
|
||||
McpSchema.ImageContent imageContent = new McpSchema.ImageContent(null, null, "base64data", "image/png");
|
||||
|
||||
McpSchema.CallToolResult result = McpSchema.CallToolResult.builder()
|
||||
.addContent(textContent)
|
||||
.addContent(imageContent)
|
||||
.isError(false)
|
||||
.build();
|
||||
|
||||
String value = mapper.writeValueAsString(result);
|
||||
|
||||
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
|
||||
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
|
||||
.isObject()
|
||||
.isEqualTo(
|
||||
json("""
|
||||
{"content":[{"type":"text","text":"Text result"},{"type":"image","data":"base64data","mimeType":"image/png"}],"isError":false}"""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCallToolResultBuilderWithContentList() throws Exception {
|
||||
McpSchema.TextContent textContent = new McpSchema.TextContent("Text result");
|
||||
McpSchema.ImageContent imageContent = new McpSchema.ImageContent(null, null, "base64data", "image/png");
|
||||
List<McpSchema.Content> contents = Arrays.asList(textContent, imageContent);
|
||||
|
||||
McpSchema.CallToolResult result = McpSchema.CallToolResult.builder().content(contents).isError(true).build();
|
||||
|
||||
String value = mapper.writeValueAsString(result);
|
||||
|
||||
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
|
||||
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
|
||||
.isObject()
|
||||
.isEqualTo(
|
||||
json("""
|
||||
{"content":[{"type":"text","text":"Text result"},{"type":"image","data":"base64data","mimeType":"image/png"}],"isError":true}"""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCallToolResultBuilderWithErrorResult() throws Exception {
|
||||
McpSchema.CallToolResult result = McpSchema.CallToolResult.builder()
|
||||
.addTextContent("Error: Operation failed")
|
||||
.isError(true)
|
||||
.build();
|
||||
|
||||
String value = mapper.writeValueAsString(result);
|
||||
|
||||
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
|
||||
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
|
||||
.isObject()
|
||||
.isEqualTo(json("""
|
||||
{"content":[{"type":"text","text":"Error: Operation failed"}],"isError":true}"""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCallToolResultStringConstructor() throws Exception {
|
||||
// Test the existing string constructor alongside the builder
|
||||
McpSchema.CallToolResult result1 = new McpSchema.CallToolResult("Simple result", false);
|
||||
McpSchema.CallToolResult result2 = McpSchema.CallToolResult.builder()
|
||||
.addTextContent("Simple result")
|
||||
.isError(false)
|
||||
.build();
|
||||
|
||||
String value1 = mapper.writeValueAsString(result1);
|
||||
String value2 = mapper.writeValueAsString(result2);
|
||||
|
||||
// Both should produce the same JSON
|
||||
assertThat(value1).isEqualTo(value2);
|
||||
assertThatJson(value1).when(Option.IGNORING_ARRAY_ORDER)
|
||||
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
|
||||
.isObject()
|
||||
.isEqualTo(json("""
|
||||
{"content":[{"type":"text","text":"Simple result"}],"isError":false}"""));
|
||||
}
|
||||
|
||||
// Sampling Tests
|
||||
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Reference in a new issue