001/* 002 * Copyright 2022-2026 Revetware LLC. 003 * Licensed under the Apache License, Version 2.0 (the "License"); 004 * you may not use this file except in compliance with the License. 005 * You may obtain a copy at https://www.apache.org/licenses/LICENSE-2.0 006 * Unless required by applicable law or agreed to in writing, software 007 * distributed under the License is distributed on an "AS IS" BASIS, 008 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 009 * See the License for the specific language governing permissions and 010 * limitations under the License. 011 */ 012package com.soklet; 013 014import org.jspecify.annotations.NonNull; 015 016import javax.annotation.concurrent.ThreadSafe; 017import java.util.List; 018import java.util.Optional; 019 020import static java.util.Objects.requireNonNull; 021 022/** 023 * Immutable, framework-supplied Skills pagination input. 024 * 025 * <p>The initial-selection optional is the authoritative phase marker: present, 026 * even for an empty list, means first-page selection was performed; absent means 027 * the application must restore a continuation snapshot. A present cursor, 028 * including an empty string, always identifies continuation. Soklet preserves 029 * opaque cursor text without authenticating or retaining application snapshots. 030 * 031 * @author <a href="https://www.revetkn.com">Mark Allen</a> 032 */ 033@ThreadSafe 034public final class McpSkillListContext { 035 @NonNull 036 private final Optional<@NonNull String> cursor; 037 @NonNull 038 private final Optional<@NonNull List<@NonNull McpSkillRegistration>> initialSkillRegistrations; 039 040 private McpSkillListContext(@NonNull Optional<@NonNull String> cursor, 041 @NonNull Optional<@NonNull List<@NonNull McpSkillRegistration>> initialSkillRegistrations) { 042 this.cursor = requireNonNull(cursor); 043 this.initialSkillRegistrations = requireNonNull(initialSkillRegistrations).map(List::copyOf); 044 if (cursor.isPresent() == initialSkillRegistrations.isPresent()) 045 throw new IllegalArgumentException("Invalid MCP Skills list context phase."); 046 } 047 048 @NonNull 049 static McpSkillListContext from(@NonNull Optional<@NonNull String> cursor, 050 @NonNull Optional<@NonNull List<@NonNull McpSkillRegistration>> initialSkillRegistrations) { 051 return new McpSkillListContext(cursor, initialSkillRegistrations); 052 } 053 054 /** 055 * Returns exact opaque client cursor text, including present-empty values. 056 * The application owns integrity, authorization and content binding, expiry, 057 * retained snapshot availability, and cross-node portability. 058 * 059 * @return client cursor, or empty when omitted on the first page 060 */ 061 @NonNull 062 public Optional<@NonNull String> getCursor() { return this.cursor; } 063 064 /** 065 * Returns the first-page access/discovery-filtered and variant-selected 066 * registrations in canonical endpoint order. The first page must be an ordered 067 * subsequence of these exact instances. Present-empty still denotes the first 068 * page; absent requires restoration of an application-owned continuation. 069 * 070 * @return immutable initial selection, or empty for a continuation 071 */ 072 @NonNull 073 public Optional<@NonNull List<@NonNull McpSkillRegistration>> getInitialSkillRegistrations() { 074 return this.initialSkillRegistrations; 075 } 076 077 /** @return diagnostic rendering without cursor or registration data */ 078 @Override 079 @NonNull 080 public String toString() { return "McpSkillListContext[redacted]"; } 081}