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 &sect;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}