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; 024import java.util.Objects; 025import java.util.Optional; 026 027import static java.util.Objects.requireNonNull; 028 029/** 030 * Immutable declaration of one string-valued MCP prompt argument. 031 * 032 * <p>Prompt arguments are not JSON Schema values. A present argument is 033 * always delivered to the handler as its exact wire string; Soklet performs 034 * no coercion or domain-specific validation. 035 * 036 * @author <a href="https://www.revetkn.com">Mark Allen</a> 037 */ 038@ThreadSafe 039public final class McpPromptArgumentDeclaration { 040 @NonNull 041 private final String name; 042 @Nullable 043 private final String title; 044 @Nullable 045 private final String description; 046 private final boolean required; 047 048 /** 049 * Vends a builder primed with the published argument name. 050 * 051 * @param name nonblank published argument name 052 * @return argument-declaration builder 053 * @throws IllegalArgumentException if {@code name} is blank 054 */ 055 @NonNull 056 public static Builder withName(@NonNull String name) { 057 return new Builder(requireName(name)); 058 } 059 060 private McpPromptArgumentDeclaration(@NonNull Builder builder) { 061 this.name = builder.name; 062 this.title = builder.title; 063 this.description = builder.description; 064 this.required = builder.required; 065 } 066 067 /** @return published argument name */ 068 @NonNull 069 public String getName() { 070 return this.name; 071 } 072 073 /** @return human-readable title, if configured */ 074 @NonNull 075 public Optional<@NonNull String> getTitle() { 076 return Optional.ofNullable(this.title); 077 } 078 079 /** @return human-readable description, if configured */ 080 @NonNull 081 public Optional<@NonNull String> getDescription() { 082 return Optional.ofNullable(this.description); 083 } 084 085 /** @return whether the argument must be present in {@code prompts/get} */ 086 @NonNull 087 public Boolean isRequired() { 088 return this.required; 089 } 090 091 /** @return whether every argument-declaration property is structurally equal */ 092 @Override 093 public boolean equals(@Nullable Object other) { 094 if (this == other) 095 return true; 096 if (!(other instanceof McpPromptArgumentDeclaration declaration)) 097 return false; 098 return this.required == declaration.required 099 && this.name.equals(declaration.name) 100 && Objects.equals(this.title, declaration.title) 101 && Objects.equals(this.description, declaration.description); 102 } 103 104 /** @return structural argument-declaration hash code */ 105 @Override 106 public int hashCode() { 107 return Objects.hash(this.name, this.title, this.description, 108 this.required); 109 } 110 111 @NonNull 112 private static String requireName(@NonNull String name) { 113 McpContentValueSupport.requireWellFormedString(name); 114 if (name.isBlank()) 115 throw new IllegalArgumentException( 116 "MCP prompt argument names must not be blank."); 117 return name; 118 } 119 120 /** 121 * Mutable builder for an immutable prompt argument declaration. 122 * 123 * @author <a href="https://www.revetkn.com">Mark Allen</a> 124 */ 125 @NotThreadSafe 126 public static final class Builder { 127 @NonNull 128 private final String name; 129 @Nullable 130 private String title; 131 @Nullable 132 private String description; 133 private boolean required; 134 135 private Builder(@NonNull String name) { 136 this.name = McpContentValueSupport.requireWellFormedString(name); 137 } 138 139 /** 140 * Sets the human-readable argument title. 141 * 142 * @param title human-readable title 143 * @return this builder 144 */ 145 @NonNull 146 public Builder title(@NonNull String title) { 147 this.title = McpContentValueSupport.requireWellFormedString(title); 148 return this; 149 } 150 151 /** 152 * Sets the human-readable argument description. 153 * 154 * @param description human-readable description 155 * @return this builder 156 */ 157 @NonNull 158 public Builder description(@NonNull String description) { 159 this.description = McpContentValueSupport.requireWellFormedString(description); 160 return this; 161 } 162 163 /** 164 * Sets whether the argument is required. 165 * 166 * @param required whether callers must supply this argument 167 * @return this builder 168 * @throws NullPointerException if {@code required} is null 169 */ 170 @NonNull 171 public Builder required(@NonNull Boolean required) { 172 this.required = requireNonNull(required); 173 return this; 174 } 175 176 /** @return immutable prompt argument declaration */ 177 @NonNull 178 public McpPromptArgumentDeclaration build() { 179 return new McpPromptArgumentDeclaration(this); 180 } 181 } 182}