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.net.URI; 025import java.util.Objects; 026import java.util.Optional; 027 028import static java.util.Objects.requireNonNull; 029 030/** 031 * Immutable MCP implementation metadata. 032 * <p> 033 * Servers use this value to identify themselves to MCP clients. Client 034 * implementation information received from the wire is informational and is 035 * not an authenticated identity. 036 * 037 * <p>String factories and setters reject unpaired UTF-16 surrogates with 038 * {@link IllegalArgumentException}; well-formed Unicode is preserved exactly. 039 * 040 * @author <a href="https://www.revetkn.com">Mark Allen</a> 041 */ 042@ThreadSafe 043public final class McpImplementation { 044 @NonNull 045 private final String name; 046 @NonNull 047 private final String version; 048 @Nullable 049 private final String title; 050 @Nullable 051 private final String description; 052 @Nullable 053 private final URI websiteUrl; 054 055 /** 056 * Vends a builder primed with the required implementation name and version. 057 * Client-provided strings are preserved, including blank values. Endpoints 058 * require a nonblank name and version for configured server information. 059 * 060 * @param name the implementation name 061 * @param version the implementation version 062 * @return a builder for implementation metadata 063 * @throws NullPointerException if {@code name} or {@code version} is null 064 */ 065 @NonNull 066 public static Builder withNameAndVersion(@NonNull String name, 067 @NonNull String version) { 068 return new Builder(name, version); 069 } 070 071 private McpImplementation(@NonNull Builder builder) { 072 requireNonNull(builder); 073 this.name = builder.name; 074 this.version = builder.version; 075 this.title = blankToNull(builder.title); 076 this.description = blankToNull(builder.description); 077 this.websiteUrl = builder.websiteUrl; 078 } 079 080 /** 081 * The implementation name. 082 * 083 * @return the implementation name, which may be blank for client information 084 */ 085 @NonNull 086 public String getName() { 087 return this.name; 088 } 089 090 /** 091 * The implementation version. 092 * 093 * @return the implementation version, which may be blank for client information 094 */ 095 @NonNull 096 public String getVersion() { 097 return this.version; 098 } 099 100 /** 101 * The optional human-readable implementation title. 102 * 103 * @return the title, or the empty optional if none was configured 104 */ 105 @NonNull 106 public Optional<@NonNull String> getTitle() { 107 return Optional.ofNullable(this.title); 108 } 109 110 /** 111 * The optional human-readable implementation description. 112 * 113 * @return the description, or the empty optional if none was configured 114 */ 115 @NonNull 116 public Optional<@NonNull String> getDescription() { 117 return Optional.ofNullable(this.description); 118 } 119 120 /** 121 * The optional absolute website URI for this implementation. 122 * 123 * @return the website URI, or the empty optional if none was configured 124 */ 125 @NonNull 126 public Optional<@NonNull URI> getWebsiteUrl() { 127 return Optional.ofNullable(this.websiteUrl); 128 } 129 130 /** @return whether every implementation property is structurally equal */ 131 @Override 132 public boolean equals(@Nullable Object other) { 133 if (this == other) 134 return true; 135 if (!(other instanceof McpImplementation implementation)) 136 return false; 137 return this.name.equals(implementation.name) 138 && this.version.equals(implementation.version) 139 && Objects.equals(this.title, implementation.title) 140 && Objects.equals(this.description, implementation.description) 141 && Objects.equals(this.websiteUrl, implementation.websiteUrl); 142 } 143 144 /** @return structural implementation hash code */ 145 @Override 146 public int hashCode() { 147 return Objects.hash(this.name, this.version, this.title, this.description, 148 this.websiteUrl); 149 } 150 151 @Nullable 152 private static String blankToNull(@Nullable String value) { 153 return value == null || value.isBlank() ? null : value; 154 } 155 156 /** 157 * Builder for immutable {@link McpImplementation} values. 158 * <p> 159 * This class is intended for use by a single thread. 160 * 161 * @author <a href="https://www.revetkn.com">Mark Allen</a> 162 */ 163 @NotThreadSafe 164 public static final class Builder { 165 @NonNull 166 private final String name; 167 @NonNull 168 private final String version; 169 @Nullable 170 private String title; 171 @Nullable 172 private String description; 173 @Nullable 174 private URI websiteUrl; 175 176 private Builder(@NonNull String name, @NonNull String version) { 177 this.name = McpContentValueSupport.requireWellFormedString(name); 178 this.version = McpContentValueSupport.requireWellFormedString(version); 179 } 180 181 /** 182 * Sets a human-readable title. A blank title is treated as absent. 183 * 184 * @param title the implementation title 185 * @return this builder 186 */ 187 @NonNull 188 public Builder title(@NonNull String title) { 189 this.title = McpContentValueSupport.requireWellFormedString(title); 190 return this; 191 } 192 193 /** 194 * Sets a human-readable description. A blank description is treated as 195 * absent. 196 * 197 * @param description the implementation description 198 * @return this builder 199 */ 200 @NonNull 201 public Builder description(@NonNull String description) { 202 this.description = McpContentValueSupport.requireWellFormedString(description); 203 return this; 204 } 205 206 /** 207 * Sets the implementation website URI. 208 * 209 * @param websiteUrl the absolute website URI 210 * @return this builder 211 * @throws IllegalArgumentException if the URI is relative 212 */ 213 @NonNull 214 public Builder websiteUrl(@NonNull URI websiteUrl) { 215 requireNonNull(websiteUrl); 216 217 if (!websiteUrl.isAbsolute()) 218 throw new IllegalArgumentException( 219 "Implementation website URL must be an absolute URI."); 220 221 this.websiteUrl = websiteUrl; 222 return this; 223 } 224 225 /** 226 * Builds immutable implementation metadata. 227 * 228 * @return the implementation metadata 229 */ 230 @NonNull 231 public McpImplementation build() { 232 return new McpImplementation(this); 233 } 234 } 235}