refactor: use builder pattern for CallToolResult and Resource (#652)

* use builder pattern for CallToolResult
* use builder pattern for Resource
* simplify lambda expressions in tests
This commit is contained in:
codeboyzhou 2025-11-21 20:44:29 +08:00 • committed by GitHub
commit 7e950ebb6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 405 additions and 202 deletions

View file

@ -24,7 +24,6 @@ import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
import io.modelcontextprotocol.spec.McpSchema.CompleteResult.CompleteCompletion;
import io.modelcontextprotocol.spec.McpSchema.ErrorCodes;
import io.modelcontextprotocol.spec.McpSchema.JSONRPCResponse;
import io.modelcontextprotocol.spec.McpSchema.LoggingLevel;
import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification;
import io.modelcontextprotocol.spec.McpSchema.PromptReference;
@ -398,11 +397,12 @@ public class McpAsyncServer {
// results that conform to this schema.
// https://modelcontextprotocol.io/specification/2025-06-18/server/tools#output-schema
if (result.structuredContent() == null) {
logger.warn(
"Response missing structured content which is expected when calling tool with non-empty outputSchema");
return new CallToolResult(
"Response missing structured content which is expected when calling tool with non-empty outputSchema",
true);
String content = "Response missing structured content which is expected when calling tool with non-empty outputSchema";
logger.warn(content);
return CallToolResult.builder()
.content(List.of(new McpSchema.TextContent(content)))
.isError(true)
.build();
}
// Validate the result against the output schema
@ -410,7 +410,10 @@ public class McpAsyncServer {
if (!validation.valid()) {
logger.warn("Tool call result validation failed: {}", validation.errorMessage());
return new CallToolResult(validation.errorMessage(), true);
return CallToolResult.builder()
.content(List.of(new McpSchema.TextContent(validation.errorMessage())))
.isError(true)
.build();
}
if (Utils.isEmpty(result.content())) {

View file

@ -67,7 +67,10 @@ import reactor.core.publisher.Mono;
* McpServer.sync(transportProvider)
* .serverInfo("my-server", "1.0.0")
* .tool(Tool.builder().name("calculator").title("Performs calculations").inputSchema(schema).build(),
* (exchange, args) -> new CallToolResult("Result: " + calculate(args)))
* (exchange, args) -> CallToolResult.builder()
* .content(List.of(new McpSchema.TextContent("Result: " + calculate(args))))
* .isError(false)
* .build())
* .build();
* }</pre>
*
@ -76,7 +79,10 @@ import reactor.core.publisher.Mono;
* .serverInfo("my-server", "1.0.0")
* .tool(Tool.builder().name("calculator").title("Performs calculations").inputSchema(schema).build(),
* (exchange, args) -> Mono.fromSupplier(() -> calculate(args))
* .map(result -> new CallToolResult("Result: " + result)))
* .map(result -> CallToolResult.builder()
* .content(List.of(new McpSchema.TextContent("Result: " + result)))
* .isError(false)
* .build()))
* .build();
* }</pre>
*
@ -90,12 +96,18 @@ import reactor.core.publisher.Mono;
* McpServerFeatures.AsyncToolSpecification.builder()
* .tool(calculatorTool)
* .callTool((exchange, args) -> Mono.fromSupplier(() -> calculate(args.arguments()))
* .map(result -> new CallToolResult("Result: " + result))))
* .map(result -> CallToolResult.builder()
* .content(List.of(new McpSchema.TextContent("Result: " + result)))
* .isError(false)
* .build()))
*. .build(),
* McpServerFeatures.AsyncToolSpecification.builder()
* .tool((weatherTool)
* .callTool((exchange, args) -> Mono.fromSupplier(() -> getWeather(args.arguments()))
* .map(result -> new CallToolResult("Weather: " + result))))
* .map(result -> CallToolResult.builder()
* .content(List.of(new McpSchema.TextContent("Weather: " + result)))
* .isError(false)
* .build()))
* .build()
* )
* // Register resources
@ -425,7 +437,10 @@ public interface McpServer {
* .tool(
* Tool.builder().name("calculator").title("Performs calculations").inputSchema(schema).build(),
* (exchange, args) -> Mono.fromSupplier(() -> calculate(args))
* .map(result -> new CallToolResult("Result: " + result))
* .map(result -> CallToolResult.builder()
* .content(List.of(new McpSchema.TextContent("Result: " + result)))
* .isError(false)
* .build()))
* )
* }</pre>
* @param tool The tool definition including name, description, and schema. Must
@ -1022,7 +1037,10 @@ public interface McpServer {
* Example usage: <pre>{@code
* .tool(
* Tool.builder().name("calculator").title("Performs calculations".inputSchema(schema).build(),
* (exchange, args) -> new CallToolResult("Result: " + calculate(args))
* (exchange, args) -> CallToolResult.builder()
* .content(List.of(new McpSchema.TextContent("Result: " + calculate(args))))
* .isError(false)
* .build())
* )
* }</pre>
* @param tool The tool definition including name, description, and schema. Must

View file

@ -334,7 +334,13 @@ public class McpServerFeatures {
*
* <pre>{@code
* new McpServerFeatures.AsyncResourceSpecification(
* new Resource("docs", "Documentation files", "text/markdown"),
* Resource.builder()
* .uri("docs")
* .name("Documentation files")
* .title("Documentation files")
* .mimeType("text/markdown")
* .description("Markdown documentation files")
* .build(),
* (exchange, request) -> Mono.fromSupplier(() -> readFile(request.getPath()))
* .map(ReadResourceResult::new))
* }</pre>
@ -508,7 +514,10 @@ public class McpServerFeatures {
* .build()
* .toolHandler((exchange, req) -> {
* String expr = (String) req.arguments().get("expression");
* return new CallToolResult("Result: " + evaluate(expr));
* return CallToolResult.builder()
* .content(List.of(new McpSchema.TextContent("Result: " + evaluate(expr))))
* .isError(false)
* .build();
* }))
* .build();
* }</pre>
@ -604,7 +613,13 @@ public class McpServerFeatures {
*
* <pre>{@code
* new McpServerFeatures.SyncResourceSpecification(
* new Resource("docs", "Documentation files", "text/markdown"),
* Resource.builder()
* .uri("docs")
* .name("Documentation files")
* .title("Documentation files")
* .mimeType("text/markdown")
* .description("Markdown documentation files")
* .build(),
* (exchange, request) -> {
* String content = readFile(request.getPath());
* return new ReadResourceResult(content);

View file

@ -14,7 +14,6 @@ import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
import io.modelcontextprotocol.spec.McpSchema.CompleteResult.CompleteCompletion;
import io.modelcontextprotocol.spec.McpSchema.ErrorCodes;
import io.modelcontextprotocol.spec.McpSchema.JSONRPCResponse;
import io.modelcontextprotocol.spec.McpSchema.PromptReference;
import io.modelcontextprotocol.spec.McpSchema.ResourceReference;
import io.modelcontextprotocol.spec.McpSchema.Tool;
@ -277,11 +276,12 @@ public class McpStatelessAsyncServer {
// results that conform to this schema.
// https://modelcontextprotocol.io/specification/2025-06-18/server/tools#output-schema
if (result.structuredContent() == null) {
logger.warn(
"Response missing structured content which is expected when calling tool with non-empty outputSchema");
return new CallToolResult(
"Response missing structured content which is expected when calling tool with non-empty outputSchema",
true);
String content = "Response missing structured content which is expected when calling tool with non-empty outputSchema";
logger.warn(content);
return CallToolResult.builder()
.content(List.of(new McpSchema.TextContent(content)))
.isError(true)
.build();
}
// Validate the result against the output schema
@ -289,7 +289,10 @@ public class McpStatelessAsyncServer {
if (!validation.valid()) {
logger.warn("Tool call result validation failed: {}", validation.errorMessage());
return new CallToolResult(validation.errorMessage(), true);
return CallToolResult.builder()
.content(List.of(new McpSchema.TextContent(validation.errorMessage())))
.isError(true)
.build();
}
if (Utils.isEmpty(result.content())) {

View file

@ -1562,6 +1562,7 @@ public final class McpSchema {
* content contains error information. If false or absent, indicates successful
* execution.
*/
@Deprecated
public CallToolResult(String content, Boolean isError) {
this(List.of(new TextContent(content)), isError, null);
}
@ -1676,7 +1677,7 @@ public final class McpSchema {
* @return a new CallToolResult instance
*/
public CallToolResult build() {
return new CallToolResult(content, isError, (Object) structuredContent, meta);
return new CallToolResult(content, isError, structuredContent, meta);
}
}