001/* 002 * Copyright 2022-2026 Revetware LLC. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.soklet; 018 019import org.jspecify.annotations.NonNull; 020import org.jspecify.annotations.Nullable; 021 022import javax.annotation.concurrent.Immutable; 023import javax.annotation.concurrent.NotThreadSafe; 024import java.util.ArrayList; 025import java.util.List; 026import java.util.Objects; 027import java.util.Optional; 028 029import static java.util.List.copyOf; 030import static java.util.Objects.requireNonNull; 031 032/** 033 * Immutable tool result used by v1 MCP tool handlers. 034 * 035 * @author <a href="https://www.revetkn.com">Mark Allen</a> 036 */ 037@Immutable 038public final class McpToolResult { 039 @NonNull 040 private final List<@NonNull McpTextContent> content; 041 @Nullable 042 private final Object structuredContent; 043 @NonNull 044 private final Boolean error; 045 046 private McpToolResult(@NonNull List<@NonNull McpTextContent> content, 047 @Nullable Object structuredContent, 048 @NonNull Boolean error) { 049 requireNonNull(content); 050 requireNonNull(error); 051 052 this.content = copyOf(content); 053 this.structuredContent = structuredContent; 054 this.error = error; 055 } 056 057 /** 058 * Provides the text content blocks returned by the tool. 059 * 060 * @return the tool content blocks 061 */ 062 @NonNull 063 public List<@NonNull McpTextContent> getContent() { 064 return this.content; 065 } 066 067 /** 068 * Provides optional app-owned structured content to be marshaled into the MCP response. 069 * 070 * @return the structured content, if present 071 */ 072 @NonNull 073 public Optional<Object> getStructuredContent() { 074 return Optional.ofNullable(this.structuredContent); 075 } 076 077 /** 078 * Indicates whether the tool result represents an error result. 079 * 080 * @return {@code true} if the tool result is an error result 081 */ 082 @NonNull 083 public Boolean isError() { 084 return this.error; 085 } 086 087 /** 088 * Creates a builder for {@link McpToolResult}. 089 * 090 * @return a new builder 091 */ 092 @NonNull 093 public static Builder builder() { 094 return new Builder(); 095 } 096 097 /** 098 * Creates an error result containing a single text message. 099 * 100 * @param message the error message 101 * @return an error tool result 102 */ 103 @NonNull 104 public static McpToolResult fromErrorMessage(@NonNull String message) { 105 requireNonNull(message); 106 return builder() 107 .isError(true) 108 .content(McpTextContent.fromText(message)) 109 .build(); 110 } 111 112 @Override 113 public boolean equals(@Nullable Object other) { 114 if (this == other) 115 return true; 116 117 if (!(other instanceof McpToolResult mcpToolResult)) 118 return false; 119 120 return getContent().equals(mcpToolResult.getContent()) 121 && Objects.equals(this.structuredContent, mcpToolResult.structuredContent) 122 && isError().equals(mcpToolResult.isError()); 123 } 124 125 @Override 126 public int hashCode() { 127 return Objects.hash(getContent(), this.structuredContent, isError()); 128 } 129 130 @Override 131 public String toString() { 132 return "McpToolResult{content=%s, structuredContent=%s, error=%s}".formatted(getContent(), this.structuredContent, isError()); 133 } 134 135 /** 136 * Builder used to construct {@link McpToolResult} instances. 137 */ 138 @NotThreadSafe 139 public static final class Builder { 140 @NonNull 141 private final List<@NonNull McpTextContent> content; 142 @Nullable 143 private Object structuredContent; 144 @NonNull 145 private Boolean error; 146 147 private Builder() { 148 this.content = new ArrayList<>(); 149 this.structuredContent = null; 150 this.error = false; 151 } 152 153 /** 154 * Appends a content block to the result. 155 * 156 * @param content the content block to append 157 * @return this builder 158 */ 159 @NonNull 160 public Builder content(@NonNull McpTextContent content) { 161 requireNonNull(content); 162 this.content.add(content); 163 return this; 164 } 165 166 /** 167 * Appends multiple content blocks to the result in order. 168 * 169 * @param content the content blocks to append 170 * @return this builder 171 */ 172 @NonNull 173 public Builder content(@NonNull List<@NonNull McpTextContent> content) { 174 requireNonNull(content); 175 this.content.addAll(content); 176 return this; 177 } 178 179 /** 180 * Sets the structured content value to be marshaled into the tool result. 181 * 182 * @param structuredContent the structured content value, possibly {@code null} 183 * @return this builder 184 */ 185 @NonNull 186 public Builder structuredContent(@Nullable Object structuredContent) { 187 this.structuredContent = structuredContent; 188 return this; 189 } 190 191 /** 192 * Sets whether the result should be flagged as an error result. 193 * 194 * @param error the error flag 195 * @return this builder 196 */ 197 @NonNull 198 public Builder isError(@NonNull Boolean error) { 199 requireNonNull(error); 200 this.error = error; 201 return this; 202 } 203 204 /** 205 * Builds the immutable tool result. 206 * 207 * @return the built tool result 208 */ 209 @NonNull 210 public McpToolResult build() { 211 return new McpToolResult(this.content, this.structuredContent, this.error); 212 } 213 } 214}