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}