001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.rest.client.mcp.auth; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.net.*; 022import java.util.*; 023 024/** 025 * Immutable view of an RFC 9728 Protected Resource Metadata (PRM) document, as an MCP client consumes it to discover 026 * which authorization server(s) protect a given MCP server. 027 * 028 * <p> 029 * Only the fields the client needs are surfaced; unknown fields are preserved in {@link #extras()}. 030 * 031 * <h5 class='section'>See Also:</h5> 032 * <ul> 033 * <li class='link'><a class="doclink" href="https://datatracker.ietf.org/doc/html/rfc9728">RFC 9728</a> 034 * </ul> 035 * 036 * @param resource The {@code resource} identifier (the canonical URI of the protected MCP server). Never {@code null}. 037 * @param authorizationServers The {@code authorization_servers} issuer URIs, in document order. May be empty. 038 * @param scopesSupported The {@code scopes_supported} values, in document order. May be empty. 039 * @param extras Unknown / server-specific fields. Read-only map. 040 * @since 10.0.0 041 */ 042public record McpProtectedResourceMetadata( 043 URI resource, 044 List<URI> authorizationServers, 045 Set<String> scopesSupported, 046 Map<String,Object> extras) { 047 048 /** 049 * Compact constructor enforcing a non-null {@code resource} and defensively copying the collections. 050 */ 051 public McpProtectedResourceMetadata { 052 Objects.requireNonNull(resource, "resource"); 053 authorizationServers = authorizationServers == null ? List.of() : u(cp(authorizationServers)); 054 scopesSupported = scopesSupported == null ? Set.of() : u(cp(scopesSupported)); 055 extras = extras == null ? Map.of() : u(cp(extras)); 056 } 057 058 /** 059 * Returns the first advertised authorization server, if any. 060 * 061 * <p> 062 * Per RFC 9728 §7.6 the client selects the authorization server; selecting the first entry is compliant 063 * and no mandatory alternate-AS fallback is required. 064 * 065 * @return The first authorization-server issuer URI, or {@link Optional#empty()} if none are advertised. 066 */ 067 public Optional<URI> firstAuthorizationServer() { 068 return authorizationServers.isEmpty() ? oe() : o(authorizationServers.get(0)); 069 } 070}