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;
020
021import javax.annotation.concurrent.ThreadSafe;
022
023import static java.util.Objects.requireNonNull;
024
025/**
026 * Immutable policy for caller-aware tool and prompt availability.
027 * <p>
028 * Availability applies to discovery and direct access, not argument-level or
029 * object-level authorization. Evaluators receive canonical, untranslated
030 * registrations and must support concurrent invocation. The immutable policy
031 * does not make application state captured by an evaluator thread-safe.
032 * <p>
033 * Policies retain reference identity: application-owned evaluator capabilities
034 * do not participate in structural equality or diagnostic rendering.
035 *
036 * @author <a href="https://www.revetkn.com">Mark Allen</a>
037 */
038@ThreadSafe
039public final class McpCatalogAccessPolicy {
040        @NonNull
041        private static final McpCatalogAccessPolicy ALLOW_ALL =
042                        new McpCatalogAccessPolicy((requestContext, toolRegistration,
043                                        invocationFeatures) -> true,
044                                        (requestContext, promptRegistration, invocationFeatures) -> true);
045        @NonNull
046        private final ToolAccessEvaluator toolAccessEvaluator;
047        @NonNull
048        private final PromptAccessEvaluator promptAccessEvaluator;
049
050        private McpCatalogAccessPolicy(@NonNull ToolAccessEvaluator toolAccessEvaluator,
051                        @NonNull PromptAccessEvaluator promptAccessEvaluator) {
052                this.toolAccessEvaluator = requireNonNull(toolAccessEvaluator);
053                this.promptAccessEvaluator = requireNonNull(promptAccessEvaluator);
054        }
055
056        /**
057         * Creates a policy from two required application-owned evaluators.
058         *
059         * @param toolAccessEvaluator tool availability evaluator
060         * @param promptAccessEvaluator prompt availability evaluator
061         * @return immutable policy
062         * @throws NullPointerException if either evaluator is null
063         */
064        @NonNull
065        public static McpCatalogAccessPolicy fromEvaluators(
066                        @NonNull ToolAccessEvaluator toolAccessEvaluator,
067                        @NonNull PromptAccessEvaluator promptAccessEvaluator) {
068                return new McpCatalogAccessPolicy(toolAccessEvaluator, promptAccessEvaluator);
069        }
070
071        /** @return shared policy that permits every registered tool and prompt */
072        @NonNull
073        public static McpCatalogAccessPolicy allowAllInstance() {
074                return ALLOW_ALL;
075        }
076
077        boolean isToolAccessible(@NonNull McpRequestContext requestContext,
078                        @NonNull McpToolRegistration<?> toolRegistration,
079                        @NonNull McpInvocationFeatures invocationFeatures) throws Exception {
080                return requireNonNull(this.toolAccessEvaluator.isToolAccessible(
081                                requireNonNull(requestContext), requireNonNull(toolRegistration),
082                                requireNonNull(invocationFeatures)),
083                                "The MCP tool access evaluator returned null.");
084        }
085
086        boolean isPromptAccessible(@NonNull McpRequestContext requestContext,
087                        @NonNull McpPromptRegistration promptRegistration,
088                        @NonNull McpInvocationFeatures invocationFeatures) throws Exception {
089                return requireNonNull(this.promptAccessEvaluator.isPromptAccessible(
090                                requireNonNull(requestContext), requireNonNull(promptRegistration),
091                                requireNonNull(invocationFeatures)),
092                                "The MCP prompt access evaluator returned null.");
093        }
094
095        /** @return diagnostic rendering without application-owned evaluator data */
096        @Override
097        @NonNull
098        public String toString() {
099                return "McpCatalogAccessPolicy{evaluators=<redacted>}";
100        }
101
102        /**
103         * Concurrently callable tool availability evaluator.
104         *
105         * @author <a href="https://www.revetkn.com">Mark Allen</a>
106         */
107        @ThreadSafe
108        @FunctionalInterface
109        public interface ToolAccessEvaluator {
110                /**
111                 * Determines whether a caller may discover or invoke a tool.
112                 * Subscription projections supply a header-free derived request context;
113                 * use its current admitted principal, partition and application context.
114                 *
115                 * @param requestContext admitted request context
116                 * @param toolRegistration canonical, untranslated registration
117                 * @param invocationFeatures features scoped to this authorization check
118                 * @return true if accessible; never null
119                 * @throws Exception if availability cannot be established
120                 */
121                @NonNull
122                Boolean isToolAccessible(@NonNull McpRequestContext requestContext,
123                                @NonNull McpToolRegistration<?> toolRegistration,
124                                @NonNull McpInvocationFeatures invocationFeatures) throws Exception;
125        }
126
127        /**
128         * Concurrently callable prompt availability evaluator.
129         *
130         * @author <a href="https://www.revetkn.com">Mark Allen</a>
131         */
132        @ThreadSafe
133        @FunctionalInterface
134        public interface PromptAccessEvaluator {
135                /**
136                 * Determines whether a caller may discover or access a prompt.
137                 * Subscription projections supply a header-free derived request context;
138                 * use its current admitted principal, partition and application context.
139                 *
140                 * @param requestContext admitted request context
141                 * @param promptRegistration canonical, untranslated registration
142                 * @param invocationFeatures features scoped to this authorization check
143                 * @return true if accessible; never null
144                 * @throws Exception if availability cannot be established
145                 */
146                @NonNull
147                Boolean isPromptAccessible(@NonNull McpRequestContext requestContext,
148                                @NonNull McpPromptRegistration promptRegistration,
149                                @NonNull McpInvocationFeatures invocationFeatures) throws Exception;
150        }
151}