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.v20260728; 018 019 020import java.util.*; 021 022import org.apache.juneau.bean.mcp.v20260728.*; 023import org.apache.juneau.marshall.collections.*; 024import org.apache.juneau.marshall.marshaller.*; 025import static org.apache.juneau.commons.utils.Shorts.*; 026 027/** 028 * Static helper for encoding the client's answers to server→client MCP {@code 2026-07-28} SEP-2322 029 * elicitation requests back into the raw {@code inputResponses} wire shape a resume call expects (MRTR, 030 * the Multi-Round-Trip Requests loop, client side). 031 * 032 * <p> 033 * Paired with {@link ElicitationRequests}, which reads the server's questions out of a paused 034 * {@link McpClient#callRaw} result — the same {@code ElicitationRequests}/{@code ElicitationResponses} 035 * split the server side already uses (server {@code ElicitationRequests} builds the pause signal; server 036 * {@code ElicitationResponses} reads the resumed answers). 037 * 038 * @since 10.0.0 039 */ 040public final class ElicitationResponses { 041 042 private ElicitationResponses() {} 043 044 /** 045 * Encodes a single typed {@link ElicitResult} into the raw {@code inputResponses} payload shape. 046 * 047 * @param id The server-assigned id this answer responds to. Must not be <jk>null</jk>. 048 * @param result The typed answer. Must not be <jk>null</jk>. 049 * @return A single-entry map ready to pass to {@link #toInputResponses(Map)}'s multi-answer form, or to hand 050 * directly to a concrete request bean's {@code setInputResponses(...)} after merging with other answers. 051 * @throws IllegalArgumentException If {@code id} or {@code result} is <jk>null</jk>. 052 */ 053 public static Map<String,Object> toInputResponse(String id, ElicitResult result) { 054 reqnn("id", id); 055 reqnn("result", result); 056 return toInputResponses(Map.of(id, result)); 057 } 058 059 /** 060 * Encodes several typed {@link ElicitResult}s into the raw {@code inputResponses} payload shape a resume 061 * call's request bean expects (e.g. {@code CallToolRequest.setInputResponses(...)}). 062 * 063 * <p> 064 * Unlike {@code ElicitationRequests.of(Map,Object)} (server side), an empty {@code results} map is accepted 065 * (returning an empty map), not rejected — deliberately asymmetric, since a caller may legitimately 066 * resume with zero elicitation answers if a round only carried non-elicitation MRTR pauses, whereas building 067 * a signal with zero questions server-side would be a pointless pause. 068 * 069 * @param results Server-assigned-id-keyed typed answers. Must not be <jk>null</jk>, and no value may be 070 * <jk>null</jk>. Output iteration order mirrors this map's order, so a caller wanting deterministic 071 * ordering should pass a {@link LinkedHashMap}. 072 * @return A keyed map of raw, wire-shaped answers. Never <jk>null</jk> (empty if {@code results} was empty). 073 * @throws IllegalArgumentException If {@code results} is <jk>null</jk>, or any value in it is <jk>null</jk>. 074 */ 075 public static Map<String,Object> toInputResponses(Map<String,ElicitResult> results) { 076 reqnn("results", results); 077 Map<String,Object> out = new LinkedHashMap<>(); 078 results.forEach((id, result) -> { 079 reqnn("results[" + id + "]", result); 080 // Pre-marshalled to JsonMap here (rather than left as the typed ElicitResult for McpClient.call's 081 // own toWireParams flattening) so that (a) a null-check on the encoded shape is meaningful even 082 // when this helper is used standalone, outside McpClient.call's flow, and (b) the returned map is 083 // immediately wire-ready for a caller who hands it straight to a concrete request bean's 084 // setInputResponses(...) without ever going through McpClient at all. 085 out.put(id, Json.to(Json.of(result), JsonMap.class)); 086 }); 087 return out; 088 } 089}