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.NotThreadSafe; 023import javax.annotation.concurrent.ThreadSafe; 024 025import java.util.Objects; 026 027import static com.soklet.internal.mcp.protocol.McpApplicationMetadata.requireApplicationMetadata; 028import static java.util.Objects.requireNonNull; 029 030/** 031 * Immutable completed result returned by an MCP operation handler. 032 * 033 * @author <a href="https://www.revetkn.com">Mark Allen</a> 034 */ 035@ThreadSafe 036public final class McpCompleteResult implements McpOperationResult { 037 @NonNull 038 private final McpCompletePayload payload; 039 @NonNull 040 private final McpJsonObject metadata; 041 042 /** 043 * Creates a successful prose tool result. 044 * 045 * @param text prose text 046 * @return complete tool result 047 */ 048 @NonNull 049 public static McpCompleteResult fromToolText(@NonNull String text) { 050 return fromToolOutput(McpToolOutput.fromText(text)); 051 } 052 053 /** 054 * Creates a successful structured tool result. 055 * 056 * @param structuredContent structured JSON value 057 * @return complete tool result 058 */ 059 @NonNull 060 public static McpCompleteResult fromToolStructuredContent( 061 @NonNull McpJsonValue structuredContent) { 062 return fromToolOutput( 063 McpToolOutput.fromStructuredContent(structuredContent)); 064 } 065 066 /** 067 * Creates an application-level error tool result. 068 * 069 * @param text safe client-visible error text 070 * @return complete error tool result 071 */ 072 @NonNull 073 public static McpCompleteResult fromToolErrorText(@NonNull String text) { 074 return fromToolOutput(McpToolOutput.fromErrorText(text)); 075 } 076 077 /** 078 * Wraps explicit tool output. 079 * 080 * @param toolOutput tool output 081 * @return complete tool result 082 */ 083 @NonNull 084 public static McpCompleteResult fromToolOutput( 085 @NonNull McpToolOutput toolOutput) { 086 return new McpCompleteResult(toolOutput, McpJsonObject.emptyInstance()); 087 } 088 089 /** 090 * Wraps explicit prompt output. 091 * 092 * @param promptOutput prompt output 093 * @return complete prompt result 094 */ 095 @NonNull 096 public static McpCompleteResult fromPromptOutput( 097 @NonNull McpPromptOutput promptOutput) { 098 return new McpCompleteResult(promptOutput, McpJsonObject.emptyInstance()); 099 } 100 101 /** 102 * Wraps explicit resource output. 103 * 104 * @param resourceOutput resource output 105 * @return complete resource result 106 */ 107 @NonNull 108 public static McpCompleteResult fromResourceOutput( 109 @NonNull McpResourceOutput resourceOutput) { 110 return new McpCompleteResult(resourceOutput, McpJsonObject.emptyInstance()); 111 } 112 113 /** 114 * Starts a builder with tool output and empty result metadata. 115 * 116 * @param toolOutput complete tool output 117 * @return mutable result builder 118 */ 119 @NonNull 120 public static Builder withToolOutput(@NonNull McpToolOutput toolOutput) { 121 return new Builder(toolOutput); 122 } 123 124 /** 125 * Starts a builder with prompt output and empty result metadata. 126 * 127 * @param promptOutput complete prompt output 128 * @return mutable result builder 129 */ 130 @NonNull 131 public static Builder withPromptOutput(@NonNull McpPromptOutput promptOutput) { 132 return new Builder(promptOutput); 133 } 134 135 /** 136 * Starts a builder with resource output and empty result metadata. 137 * 138 * @param resourceOutput complete resource output 139 * @return mutable result builder 140 */ 141 @NonNull 142 public static Builder withResourceOutput(@NonNull McpResourceOutput resourceOutput) { 143 return new Builder(resourceOutput); 144 } 145 146 private McpCompleteResult(@NonNull McpCompletePayload payload, 147 @NonNull McpJsonObject metadata) { 148 this.payload = requireNonNull(payload); 149 this.metadata = requireApplicationMetadata(metadata); 150 } 151 152 /** 153 * Starts an independent builder preserving every field of this result. 154 * 155 * @return mutable result builder with the same payload and metadata 156 */ 157 @NonNull 158 public Builder toBuilder() { 159 return new Builder(this.payload).metadata(this.metadata); 160 } 161 162 /** @return operation-specific complete payload */ 163 @NonNull 164 public McpCompletePayload getPayload() { 165 return this.payload; 166 } 167 168 /** @return immutable protocol extension metadata */ 169 @NonNull 170 public McpJsonObject getMetadata() { 171 return this.metadata; 172 } 173 174 /** @return whether the payload and metadata are structurally equal */ 175 @Override 176 public boolean equals(@Nullable Object other) { 177 if (this == other) 178 return true; 179 if (!(other instanceof McpCompleteResult result)) 180 return false; 181 return this.payload.equals(result.payload) 182 && this.metadata.equals(result.metadata); 183 } 184 185 /** @return structural payload-and-metadata hash code */ 186 @Override 187 public int hashCode() { 188 return Objects.hash(this.payload, this.metadata); 189 } 190 191 /** 192 * Mutable builder for an immutable complete result. 193 * 194 * @author <a href="https://www.revetkn.com">Mark Allen</a> 195 */ 196 @NotThreadSafe 197 public static final class Builder { 198 @NonNull 199 private McpCompletePayload payload; 200 @NonNull 201 private McpJsonObject metadata = McpJsonObject.emptyInstance(); 202 203 private Builder(@NonNull McpCompletePayload payload) { 204 this.payload = requireNonNull(payload); 205 } 206 207 /** 208 * Replaces the operation-specific payload. 209 * 210 * @param payload complete tool, prompt, or resource output 211 * @return this builder 212 */ 213 @NonNull 214 public Builder payload(@NonNull McpCompletePayload payload) { 215 this.payload = requireNonNull(payload); 216 return this; 217 } 218 219 /** 220 * Replaces application-owned result metadata. 221 * Metadata is out-of-band data, not a confidential channel: hosts may log 222 * or forward it. Application authorization and redaction remain necessary. 223 * 224 * @param metadata immutable metadata without reserved MCP keys 225 * @return this builder 226 * @throws NullPointerException if {@code metadata} is null 227 * @throws IllegalArgumentException if metadata uses a reserved MCP key 228 */ 229 @NonNull 230 public Builder metadata(@NonNull McpJsonObject metadata) { 231 this.metadata = requireApplicationMetadata(metadata); 232 return this; 233 } 234 235 /** @return immutable snapshot of the current payload and metadata */ 236 @NonNull 237 public McpCompleteResult build() { 238 return new McpCompleteResult(this.payload, this.metadata); 239 } 240 } 241}