2828import io .modelcontextprotocol .json .McpJsonMapper ;
2929import io .modelcontextprotocol .spec .McpClientTransport ;
3030import io .modelcontextprotocol .spec .McpSchema ;
31+ import io .modelcontextprotocol .spec .McpSchema .ElicitRequest ;
32+ import io .modelcontextprotocol .spec .McpSchema .ElicitResult ;
3133import java .net .URI ;
3234import java .net .URLDecoder ;
3335import java .net .URLEncoder ;
4042import java .util .List ;
4143import java .util .Map ;
4244import java .util .function .Consumer ;
45+ import java .util .function .Function ;
4346import java .util .stream .Collectors ;
4447import reactor .core .publisher .Mono ;
4548
4851 *
4952 * <p>Supports three transport types:
5053 * <ul>
51- * <li>StdIO - for local process communication</li>
52- * <li>SSE - for HTTP Server-Sent Events (stateful)</li>
53- * <li>StreamableHTTP - for HTTP streaming (stateless)</li>
54+ * <li>StdIO - for local process communication</li>
55+ * <li>SSE - for HTTP Server-Sent Events (stateful)</li>
56+ * <li>StreamableHTTP - for HTTP streaming (stateless)</li>
5457 * </ul>
5558 *
5659 * <p>Example usage:
5760 * <pre>{@code
5861 * // StdIO transport
5962 * McpClientWrapper client = McpClientBuilder.create("git-mcp")
60- * .stdioTransport("python", "-m", "mcp_server_git")
61- * .buildAsync()
62- * .block();
63+ * .stdioTransport("python", "-m", "mcp_server_git")
64+ * .buildAsync()
65+ * .block();
6366 *
6467 * // SSE transport with headers and query parameters
6568 * McpClientWrapper client = McpClientBuilder.create("remote-mcp")
66- * .sseTransport("https://mcp.example.com/sse")
67- * .header("Authorization", "Bearer " + token)
68- * .queryParam("queryKey", "queryValue")
69- * .timeout(Duration.ofSeconds(60))
70- * .buildAsync()
71- * .block();
69+ * .sseTransport("https://mcp.example.com/sse")
70+ * .header("Authorization", "Bearer " + token)
71+ * .queryParam("queryKey", "queryValue")
72+ * .timeout(Duration.ofSeconds(60))
73+ * .buildAsync()
74+ * .block();
7275 *
7376 * // HTTP transport with multiple query parameters
7477 * McpClientWrapper client = McpClientBuilder.create("http-mcp")
75- * .streamableHttpTransport("https://mcp.example.com/http")
76- * .queryParams(Map.of("token", "abc123", "env", "prod"))
77- * .buildSync();
78+ * .streamableHttpTransport("https://mcp.example.com/http")
79+ * .queryParams(Map.of("token", "abc123", "env", "prod"))
80+ * .buildSync();
7881 * }</pre>
7982 */
8083public class McpClientBuilder {
@@ -86,6 +89,8 @@ public class McpClientBuilder {
8689 private TransportConfig transportConfig ;
8790 private Duration requestTimeout = DEFAULT_REQUEST_TIMEOUT ;
8891 private Duration initializationTimeout = DEFAULT_INIT_TIMEOUT ;
92+ private Function <ElicitRequest , Mono <ElicitResult >> asyncElicitationHandler ;
93+ private Function <ElicitRequest , ElicitResult > syncElicitationHandler ;
8994
9095 private McpClientBuilder (String name ) {
9196 this .name = name ;
@@ -108,7 +113,7 @@ public static McpClientBuilder create(String name) {
108113 * Configures StdIO transport for local process communication.
109114 *
110115 * @param command the executable command
111- * @param args command arguments
116+ * @param args command arguments
112117 * @return this builder
113118 */
114119 public McpClientBuilder stdioTransport (String command , String ... args ) {
@@ -120,8 +125,8 @@ public McpClientBuilder stdioTransport(String command, String... args) {
120125 * Configures StdIO transport with environment variables.
121126 *
122127 * @param command the executable command
123- * @param args command arguments list
124- * @param env environment variables
128+ * @param args command arguments list
129+ * @param env environment variables
125130 * @return this builder
126131 */
127132 public McpClientBuilder stdioTransport (
@@ -148,11 +153,11 @@ public McpClientBuilder sseTransport(String url) {
148153 * <p>Example usage for HTTP/2:
149154 * <pre>{@code
150155 * McpClientWrapper client = McpClientBuilder.create("mcp")
151- * .sseTransport("https://example.com/sse")
156+ * .sseTransport("https://example.com/sse")
152157 * .customizeSseClient(clientBuilder ->
153158 * clientBuilder.version(java.net.http.HttpClient.Version.HTTP_2))
154- * .buildAsync()
155- * .block();
159+ * .buildAsync()
160+ * .block();
156161 * }</pre>
157162 *
158163 * @param customizer consumer to customize the HttpClient.Builder
@@ -177,17 +182,21 @@ public McpClientBuilder streamableHttpTransport(String url) {
177182 }
178183
179184 /**
180- * Customizes the HTTP client for StreamableHTTP transport (only applicable after calling streamableHttpTransport).
181- * This allows advanced HTTP client configuration like HTTP/2, custom timeouts, SSL settings, etc.
185+ * Customizes the HTTP client for StreamableHTTP transport (only applicable
186+ * after calling streamableHttpTransport).
187+ * This allows advanced HTTP client configuration like HTTP/2, custom timeouts,
188+ * SSL settings, etc.
189+ *
190+ * <p>
191+ * Example usage for HTTP/2:
182192 *
183- * <p>Example usage for HTTP/2:
184193 * <pre>{@code
185194 * McpClientWrapper client = McpClientBuilder.create("mcp")
186- * .streamableHttpTransport("https://example.com/http")
187- * .customizeStreamableHttpClient(clientBuilder ->
188- * clientBuilder.version(java.net.http.HttpClient.Version.HTTP_2))
189- * .buildAsync()
190- * .block();
195+ * .streamableHttpTransport("https://example.com/http")
196+ * .customizeStreamableHttpClient(
197+ * clientBuilder -> clientBuilder.version(java.net.http.HttpClient.Version.HTTP_2))
198+ * .buildAsync()
199+ * .block();
191200 * }</pre>
192201 *
193202 * @param customizer consumer to customize the HttpClient.Builder
@@ -203,7 +212,7 @@ public McpClientBuilder customizeStreamableHttpClient(Consumer<HttpClient.Builde
203212 /**
204213 * Adds an HTTP header (only applicable for HTTP transports).
205214 *
206- * @param key header name
215+ * @param key header name
207216 * @param value header value
208217 * @return this builder
209218 */
@@ -230,11 +239,12 @@ public McpClientBuilder headers(Map<String, String> headers) {
230239 /**
231240 * Adds a query parameter to the URL (only applicable for HTTP transports).
232241 *
233- * <p>Query parameters added via this method will be merged with any existing
242+ * <p>
243+ * Query parameters added via this method will be merged with any existing
234244 * query parameters in the URL. If the same parameter key exists in both the URL
235245 * and the added parameters, the added parameter will take precedence.
236246 *
237- * @param key query parameter name
247+ * @param key query parameter name
238248 * @param value query parameter value
239249 * @return this builder
240250 */
@@ -248,7 +258,8 @@ public McpClientBuilder queryParam(String key, String value) {
248258 /**
249259 * Sets multiple query parameters (only applicable for HTTP transports).
250260 *
251- * <p>This method replaces any previously added query parameters.
261+ * <p>
262+ * This method replaces any previously added query parameters.
252263 * Query parameters in the original URL are still preserved and merged.
253264 *
254265 * @param queryParams map of query parameter name-value pairs
@@ -283,6 +294,79 @@ public McpClientBuilder initializationTimeout(Duration timeout) {
283294 return this ;
284295 }
285296
297+ /**
298+ * Registers an asynchronous elicitation handler for processing elicit requests
299+ * from the server.
300+ *
301+ * <p>
302+ * When an elicitation handler is registered, the client will automatically
303+ * enable
304+ * the elicitation capability in ClientCapabilities.
305+ *
306+ * <p>
307+ * This method is for use with {@link #buildAsync()}. The handler returns a
308+ * {@link Mono}
309+ * for asynchronous processing.
310+ *
311+ * <p>
312+ * Example usage:
313+ *
314+ * <pre>{@code
315+ * McpClientWrapper client = McpClientBuilder.create("mcp")
316+ * .stdioTransport("python", "-m", "mcp_server")
317+ * .asyncElicitation(request -> {
318+ * // Handle elicitation request asynchronously
319+ * return Mono.just(ElicitResult.builder()...build());
320+ * })
321+ * .buildAsync()
322+ * .block();
323+ * }
324+ * </pre>
325+ *
326+ * @param handler function to handle elicit requests asynchronously
327+ * @return this builder
328+ */
329+ public McpClientBuilder asyncElicitation (Function <ElicitRequest , Mono <ElicitResult >> handler ) {
330+ this .asyncElicitationHandler = handler ;
331+ return this ;
332+ }
333+
334+ /**
335+ * Registers a synchronous elicitation handler for processing elicit requests
336+ * from the server.
337+ *
338+ * <p>
339+ * When an elicitation handler is registered, the client will automatically
340+ * enable
341+ * the elicitation capability in ClientCapabilities.
342+ *
343+ * <p>
344+ * This method is for use with {@link #buildSync()}. The handler returns an
345+ * {@link ElicitResult}
346+ * directly for synchronous processing.
347+ *
348+ * <p>
349+ * Example usage:
350+ *
351+ * <pre>{@code
352+ * McpClientWrapper client = McpClientBuilder.create("mcp")
353+ * .stdioTransport("python", "-m", "mcp_server")
354+ * .syncElicitation(request -> {
355+ * // Handle elicitation request synchronously
356+ * return ElicitResult.builder()...build();
357+ * })
358+ * .buildSync();
359+ * }
360+ * </pre>
361+ *
362+ * @param handler function to handle elicit requests synchronously
363+ * @return this builder
364+ */
365+ public McpClientBuilder syncElicitation (Function <ElicitRequest , ElicitResult > handler ) {
366+ this .syncElicitationHandler = handler ;
367+ return this ;
368+ }
369+
286370 /**
287371 * Builds an asynchronous MCP client wrapper.
288372 *
@@ -302,15 +386,20 @@ public Mono<McpClientWrapper> buildAsync() {
302386 "agentscope-java" , "AgentScope Java Framework" , VERSION );
303387
304388 McpSchema .ClientCapabilities clientCapabilities =
305- McpSchema . ClientCapabilities . builder (). build ( );
389+ buildCapabilities ( asyncElicitationHandler != null );
306390
307- McpAsyncClient mcpClient =
391+ var clientBuilder =
308392 McpClient .async (transport )
309393 .requestTimeout (requestTimeout )
310394 .initializationTimeout (initializationTimeout )
311395 .clientInfo (clientInfo )
312- .capabilities (clientCapabilities )
313- .build ();
396+ .capabilities (clientCapabilities );
397+
398+ if (asyncElicitationHandler != null ) {
399+ clientBuilder = clientBuilder .elicitation (asyncElicitationHandler );
400+ }
401+
402+ McpAsyncClient mcpClient = clientBuilder .build ();
314403
315404 return new McpAsyncClientWrapper (name , mcpClient );
316405 });
@@ -333,19 +422,38 @@ public McpClientWrapper buildSync() {
333422 "agentscope-java" , "AgentScope Java Framework" , Version .VERSION );
334423
335424 McpSchema .ClientCapabilities clientCapabilities =
336- McpSchema . ClientCapabilities . builder (). build ( );
425+ buildCapabilities ( syncElicitationHandler != null );
337426
338- McpSyncClient mcpClient =
427+ var clientBuilder =
339428 McpClient .sync (transport )
340429 .requestTimeout (requestTimeout )
341430 .initializationTimeout (initializationTimeout )
342431 .clientInfo (clientInfo )
343- .capabilities (clientCapabilities )
344- .build ();
432+ .capabilities (clientCapabilities );
433+
434+ if (syncElicitationHandler != null ) {
435+ clientBuilder = clientBuilder .elicitation (syncElicitationHandler );
436+ }
437+
438+ McpSyncClient mcpClient = clientBuilder .build ();
345439
346440 return new McpSyncClientWrapper (name , mcpClient );
347441 }
348442
443+ /**
444+ * Builds client capabilities
445+ * @param withElicitation whether to include elicitation capability
446+ * @return client capabilities
447+ */
448+ private McpSchema .ClientCapabilities buildCapabilities (boolean withElicitation ) {
449+ McpSchema .ClientCapabilities .Builder capabilitiesBuilder =
450+ McpSchema .ClientCapabilities .builder ();
451+ if (withElicitation ) {
452+ capabilitiesBuilder .elicitation ();
453+ }
454+ return capabilitiesBuilder .build ();
455+ }
456+
349457 // ==================== Internal Transport Configuration Classes ====================
350458
351459 private interface TransportConfig {
@@ -419,8 +527,10 @@ public void setQueryParams(Map<String, String> queryParams) {
419527 }
420528
421529 /**
422- * Extracts the endpoint path from URL, merging with additional query parameters.
423- * Query parameters from the original URL are merged with additionally configured parameters.
530+ * Extracts the endpoint path from URL, merging with additional query
531+ * parameters.
532+ * Query parameters from the original URL are merged with additionally
533+ * configured parameters.
424534 * Additional parameters take precedence over URL parameters with the same key.
425535 *
426536 * @return endpoint path with query parameters (e.g., "/api/sse?token=abc")
0 commit comments