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}