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.ThreadSafe; 023import java.net.URI; 024 025/** 026 * Immutable application event that identifies a coarse MCP subscription-visible 027 * change. 028 * <p> 029 * Events do not identify an endpoint, authorization partition, or connected 030 * client. Soklet applies endpoint configuration and accepted subscription 031 * filters before wire emission. The authorization partition stored when a 032 * subscription is admitted scopes registration and quota accounting; it is not 033 * an event target and does not authorize catalog or resource access. 034 * 035 * <p> 036 * This type may gain reviewed permitted subtypes in Soklet 4.x; use a forward-compatible default when switching over its values. 037 * 038 * @author <a href="https://www.revetkn.com">Mark Allen</a> 039 */ 040@ThreadSafe 041public sealed interface McpSubscriptionEvent 042 permits McpSubscriptionEvent.ResourcesListChanged, 043 McpSubscriptionEvent.ResourceUpdated, 044 McpSubscriptionEvent.ToolsListChanged, 045 McpSubscriptionEvent.PromptsListChanged { 046 /** 047 * Creates a resource-list-changed event. 048 * 049 * @return resource-list-changed event 050 */ 051 @NonNull 052 static ResourcesListChanged resourcesListChanged() { 053 return ResourcesListChanged.INSTANCE; 054 } 055 056 /** 057 * Creates a resource-updated event. 058 * 059 * @param resourceUri changed resource URI 060 * @return resource-updated event 061 * @throws NullPointerException if {@code resourceUri} is null 062 * @throws IllegalArgumentException if the URI is relative, not normalized, 063 * or not in ASCII wire form 064 */ 065 @NonNull 066 static ResourceUpdated resourceUpdated(@NonNull URI resourceUri) { 067 return new ResourceUpdated(resourceUri); 068 } 069 070 /** 071 * Creates a tool-list-changed event. 072 * 073 * @return tool-list-changed event 074 */ 075 @NonNull 076 static ToolsListChanged toolsListChanged() { 077 return ToolsListChanged.INSTANCE; 078 } 079 080 /** 081 * Creates a prompt-list-changed event. 082 * 083 * @return prompt-list-changed event 084 */ 085 @NonNull 086 static PromptsListChanged promptsListChanged() { 087 return PromptsListChanged.INSTANCE; 088 } 089 090 /** 091 * Signals that clients should request {@code resources/list} again. 092 * 093 * @author <a href="https://www.revetkn.com">Mark Allen</a> 094 */ 095 @ThreadSafe 096 public final class ResourcesListChanged implements McpSubscriptionEvent { 097 @NonNull 098 private static final ResourcesListChanged INSTANCE = 099 new ResourcesListChanged(); 100 101 private ResourcesListChanged() { 102 } 103 104 /** @return whether the other value is also a list-changed event */ 105 @Override 106 public boolean equals(@Nullable Object other) { 107 return other instanceof ResourcesListChanged; 108 } 109 110 /** @return value-based hash code */ 111 @Override 112 public int hashCode() { 113 return 0; 114 } 115 116 /** @return safe diagnostic rendering */ 117 @Override 118 @NonNull 119 public String toString() { 120 return "ResourcesListChanged{}"; 121 } 122 } 123 124 /** 125 * Signals that the representation at one absolute normalized resource URI 126 * in ASCII wire form changed. 127 * 128 * @author <a href="https://www.revetkn.com">Mark Allen</a> 129 */ 130 @ThreadSafe 131 public final class ResourceUpdated 132 implements McpSubscriptionEvent { 133 @NonNull 134 private final URI resourceUri; 135 136 /** 137 * Validates this resource-updated event. 138 * 139 * @throws NullPointerException if {@code resourceUri} is null 140 * @throws IllegalArgumentException if the URI is relative, not normalized, 141 * or not in ASCII wire form 142 */ 143 private ResourceUpdated(@NonNull URI resourceUri) { 144 this.resourceUri = McpResourceValueSupport.requireAbsoluteNormalizedUri( 145 resourceUri); 146 } 147 148 /** @return absolute normalized changed-resource URI in ASCII wire form */ 149 @NonNull 150 public URI getResourceUri() { 151 return this.resourceUri; 152 } 153 154 /** @return whether this event identifies the same changed resource URI */ 155 @Override 156 public boolean equals(@Nullable Object other) { 157 if (this == other) 158 return true; 159 if (!(other instanceof ResourceUpdated updated)) 160 return false; 161 return this.resourceUri.equals(updated.resourceUri); 162 } 163 164 /** @return value-based hash code */ 165 @Override 166 public int hashCode() { 167 return this.resourceUri.hashCode(); 168 } 169 170 /** @return rendering that does not expose the resource URI */ 171 @Override 172 @NonNull 173 public final String toString() { 174 return "ResourceUpdated{resourceUri=<redacted>}"; 175 } 176 } 177 178 /** 179 * Requests reevaluation of the caller-visible {@code tools/list} catalog for 180 * interested subscriptions. Publication alone does not assert that the 181 * visible catalog changed. 182 * 183 * @author <a href="https://www.revetkn.com">Mark Allen</a> 184 */ 185 @ThreadSafe 186 public final class ToolsListChanged implements McpSubscriptionEvent { 187 @NonNull 188 private static final ToolsListChanged INSTANCE = new ToolsListChanged(); 189 190 private ToolsListChanged() { 191 } 192 193 /** @return whether the other value is also a tool-list-changed event */ 194 @Override 195 public boolean equals(@Nullable Object other) { 196 return other instanceof ToolsListChanged; 197 } 198 199 /** @return value-based hash code */ 200 @Override 201 public int hashCode() { 202 return 0; 203 } 204 205 /** @return safe diagnostic rendering */ 206 @Override 207 @NonNull 208 public String toString() { 209 return "ToolsListChanged{}"; 210 } 211 } 212 213 /** 214 * Requests reevaluation of the caller-visible {@code prompts/list} catalog for 215 * interested subscriptions. Publication alone does not assert that the 216 * visible catalog changed. 217 * 218 * @author <a href="https://www.revetkn.com">Mark Allen</a> 219 */ 220 @ThreadSafe 221 public final class PromptsListChanged implements McpSubscriptionEvent { 222 @NonNull 223 private static final PromptsListChanged INSTANCE = 224 new PromptsListChanged(); 225 226 private PromptsListChanged() { 227 } 228 229 /** @return whether the other value is also a prompt-list-changed event */ 230 @Override 231 public boolean equals(@Nullable Object other) { 232 return other instanceof PromptsListChanged; 233 } 234 235 /** @return value-based hash code */ 236 @Override 237 public int hashCode() { 238 return 0; 239 } 240 241 /** @return safe diagnostic rendering */ 242 @Override 243 @NonNull 244 public String toString() { 245 return "PromptsListChanged{}"; 246 } 247 } 248}