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.server.mcp.v20260728; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.util.*; 022 023import org.apache.juneau.bean.mcp.v20260728.*; 024import org.apache.juneau.marshall.collections.*; 025import org.apache.juneau.marshall.marshaller.*; 026 027/** 028 * Static helper that builds an {@link McpInputRequiredSignal} from one or more typed {@link ElicitRequest}s (MCP 029 * {@code 2026-07-28} SEP-2322 elicitation, riding the Multi-Round-Trip Requests loop). 030 * 031 * <p> 032 * {@link McpInputRequiredSignal}'s constructor takes {@code Map<String,Object>} whose values must be {@code Map} 033 * at runtime (see its own Javadoc); this helper serializes each {@link ElicitRequest} to that shape via Juneau's 034 * JSON marshaller (round-tripped through {@link JsonMap}, the same technique 035 * {@link McpMrtrResumeContext#continuationAs(Class)} already demonstrates for the continuation side) so a 036 * handler never hand-builds the raw map itself. Pure marshalling: no dispatcher-visible state, no new capability 037 * gate — the existing elicitation-specific gate in {@code McpRevision#pause} already covers every signal this 038 * helper produces. 039 */ 040public final class ElicitationRequests { 041 042 private ElicitationRequests() {} 043 044 /** 045 * Builds a single-question {@link McpInputRequiredSignal}. 046 * 047 * @param id The server-assigned id for this question. Must not be <jk>null</jk>. 048 * @param request The typed elicitation request. Must not be <jk>null</jk>. 049 * @param continuation Handler-opaque continuation value. Can be <jk>null</jk> if the handler needs no 050 * state carried between rounds. 051 * @return A new signal ready to be thrown. 052 * @throws IllegalArgumentException If {@code id} or {@code request} is <jk>null</jk>. 053 */ 054 public static McpInputRequiredSignal of(String id, ElicitRequest request, Object continuation) { 055 reqnn("id", id); 056 reqnn("request", request); 057 return of(Map.of(id, request), continuation); 058 } 059 060 /** 061 * Builds a multi-question {@link McpInputRequiredSignal} (MRTR's multi-request-per-round case: several 062 * elicitations paused on together, resumed together). 063 * 064 * <p> 065 * Unlike {@code ElicitationResponses.toInputResponses(Map)} (client side), an empty {@code requests} map is 066 * rejected, not accepted — deliberately asymmetric, since a signal built with zero questions would be 067 * a pointless pause, whereas a caller resuming may legitimately have zero elicitation answers on hand if 068 * the round it is resuming only carried non-elicitation MRTR pauses. 069 * 070 * @param requests Server-assigned-id-keyed typed elicitation requests. Must not be <jk>null</jk> or empty, 071 * and no value may be <jk>null</jk>. Output iteration order mirrors this map's order, so a caller 072 * wanting deterministic ordering should pass a {@link LinkedHashMap}. 073 * @param continuation Handler-opaque continuation value. Can be <jk>null</jk>. 074 * @return A new signal ready to be thrown. 075 * @throws IllegalArgumentException If {@code requests} is <jk>null</jk> or empty, or any value in it is 076 * <jk>null</jk>. 077 */ 078 public static McpInputRequiredSignal of(Map<String,ElicitRequest> requests, Object continuation) { 079 reqnn("requests", requests); 080 if (requests.isEmpty()) 081 throw iaex("requests must not be empty"); 082 Map<String,Object> raw = new LinkedHashMap<>(); 083 requests.forEach((id, request) -> { 084 reqnn("requests[" + id + "]", request); 085 raw.put(id, Json.to(Json.of(request), JsonMap.class)); 086 }); 087 return new McpInputRequiredSignal(raw, continuation); 088 } 089}