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 com.soklet.internal.mcp.protocol.McpServerRuntimeBridge; 020import org.jspecify.annotations.NonNull; 021import org.jspecify.annotations.Nullable; 022 023import javax.annotation.concurrent.ThreadSafe; 024import java.util.Objects; 025 026import static java.util.Objects.requireNonNull; 027 028/** 029 * Immutable server-initiated request carried by an MCP 030 * {@code input_required} result. 031 * 032 * <p>The declaration connects the emitted request to the capability and 033 * registration metadata that permits it. Applications should reuse a 034 * declaration registered through the operation's 035 * {@code inputRequestDeclarations} 036 * configuration. For a durable task, Soklet includes those declarations in 037 * the task origin for the application to persist, then validates that 038 * relationship before it emits the containing task snapshot. 039 * 040 * @author <a href="https://www.revetkn.com">Mark Allen</a> 041 */ 042@ThreadSafe 043public final class McpInputRequest { 044 @NonNull 045 private final McpInputRequestDeclaration declaration; 046 @NonNull 047 private final McpJsonObject params; 048 049 /** 050 * Creates an input request from its registered declaration. 051 * 052 * @param declaration registered input-request declaration 053 * @param params method-specific request parameters 054 * @return immutable input request 055 * @throws NullPointerException if an argument is null 056 */ 057 @NonNull 058 public static McpInputRequest fromDeclaration( 059 @NonNull McpInputRequestDeclaration declaration, 060 @NonNull McpJsonObject params) { 061 return new McpInputRequest(declaration, params); 062 } 063 064 private McpInputRequest(@NonNull McpInputRequestDeclaration declaration, 065 @NonNull McpJsonObject params) { 066 this.declaration = requireNonNull(declaration); 067 this.params = requireNonNull(params); 068 } 069 070 /** @return registered input-request declaration */ 071 @NonNull 072 public McpInputRequestDeclaration getDeclaration() { 073 return this.declaration; 074 } 075 076 /** @return method-specific request parameters */ 077 @NonNull 078 public McpJsonObject getParams() { 079 return this.params; 080 } 081 082 /** 083 * Returns the declared client request method. 084 * 085 * @return client JSON-RPC request method 086 */ 087 @NonNull 088 public String getJsonRpcMethod() { 089 return this.declaration.getJsonRpcMethod(); 090 } 091 092 /** 093 * Reports whether a client response matches this request's declared MCP 094 * input-response union branch. 095 * 096 * <p>This validates the protocol-level response shape for the declaration. 097 * It does not validate accepted elicitation content against this request's 098 * requested schema or against application-specific policy. 099 * Content values may be strings, numbers (including decimals), booleans, 100 * or arrays of strings. Numbers retain their exact {@link java.math.BigDecimal} 101 * values and must satisfy Soklet's production JSON number limits. Matching 102 * does not require a number to be integral, even when the requested schema 103 * declares an integer field; the application must validate that schema. 104 * 105 * @param inputResponse client response to inspect 106 * @return whether the response matches the declared MCP union branch 107 * @throws NullPointerException if {@code inputResponse} is null 108 */ 109 @NonNull 110 public Boolean matchesInputResponse(@NonNull McpJsonValue inputResponse) { 111 return McpServerRuntimeBridge.matchesInputResponse(this.declaration, 112 requireNonNull(inputResponse)); 113 } 114 115 void requireValidParams() { 116 McpServerRuntimeBridge.requireValidInputRequestParams(this.declaration, 117 this.params); 118 } 119 120 /** @return whether this value has the same declaration and parameters */ 121 @Override 122 public boolean equals(@Nullable Object other) { 123 if (this == other) 124 return true; 125 if (!(other instanceof McpInputRequest request)) 126 return false; 127 return this.declaration.equals(request.declaration) 128 && this.params.equals(request.params); 129 } 130 131 /** @return value-based hash code */ 132 @Override 133 public int hashCode() { 134 return Objects.hash(this.declaration, this.params); 135 } 136 137 /** @return rendering that identifies the method but redacts request parameters */ 138 @Override 139 @NonNull 140 public final String toString() { 141 return "McpInputRequest{method='%s', params=<redacted>}" 142 .formatted(getJsonRpcMethod()); 143 } 144}