Skip to content

Commit 9194334

Browse files
chemicLtzolov
authored andcommitted
Polish
Signed-off-by: Dariusz Jędrzejczyk <dariusz.jedrzejczyk@broadcom.com>
1 parent 6839d3b commit 9194334

File tree

8 files changed

+181
-69
lines changed

8 files changed

+181
-69
lines changed

mcp/src/main/java/org/springframework/ai/mcp/client/McpAsyncClient.java

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ public Mono<Void> rootsListChangedNotification() {
517517
return this.mcpSession.sendNotification(McpSchema.METHOD_NOTIFICATION_ROOTS_LIST_CHANGED);
518518
}
519519

520-
private RequestHandler rootsListRequestHandler() {
520+
private RequestHandler<McpSchema.ListRootsResult> rootsListRequestHandler() {
521521
return params -> {
522522
McpSchema.PaginatedRequest request = transport.unmarshalFrom(params,
523523
new TypeReference<McpSchema.PaginatedRequest>() {
@@ -545,10 +545,10 @@ private RequestHandler<CreateMessageResult> samplingCreateMessageHandler() {
545545
// --------------------------
546546
// Tools
547547
// --------------------------
548-
private static TypeReference<McpSchema.CallToolResult> CALL_TOOL_RESULT_TYPE_REF = new TypeReference<>() {
548+
private static final TypeReference<McpSchema.CallToolResult> CALL_TOOL_RESULT_TYPE_REF = new TypeReference<>() {
549549
};
550550

551-
private static TypeReference<McpSchema.ListToolsResult> LIST_TOOLS_RESULT_TYPE_REF = new TypeReference<>() {
551+
private static final TypeReference<McpSchema.ListToolsResult> LIST_TOOLS_RESULT_TYPE_REF = new TypeReference<>() {
552552
};
553553

554554
/**
@@ -601,6 +601,7 @@ public Mono<McpSchema.ListToolsResult> listTools(String cursor) {
601601
* list to all registered consumers 3. Handling any errors that occur during this
602602
* process
603603
*/
604+
@Deprecated
604605
private NotificationHandler toolsChangeNotificationHandler(
605606
List<Consumer<List<McpSchema.Tool>>> toolsChangeConsumers) {
606607

@@ -631,20 +632,24 @@ private NotificationHandler asyncToolsChangeNotificationHandler(
631632
// TODO: params are not used yet
632633
return params -> listTools().flatMap(listToolsResult -> Flux.fromIterable(toolsChangeConsumers)
633634
.flatMap(consumer -> consumer.apply(listToolsResult.tools()))
635+
.onErrorResume(error -> {
636+
logger.error("Error handling tools list change notification", error);
637+
return Mono.empty();
638+
})
634639
.then());
635640
}
636641

637642
// --------------------------
638643
// Resources
639644
// --------------------------
640645

641-
private static TypeReference<McpSchema.ListResourcesResult> LIST_RESOURCES_RESULT_TYPE_REF = new TypeReference<>() {
646+
private static final TypeReference<McpSchema.ListResourcesResult> LIST_RESOURCES_RESULT_TYPE_REF = new TypeReference<>() {
642647
};
643648

644-
private static TypeReference<McpSchema.ReadResourceResult> READ_RESOURCE_RESULT_TYPE_REF = new TypeReference<>() {
649+
private static final TypeReference<McpSchema.ReadResourceResult> READ_RESOURCE_RESULT_TYPE_REF = new TypeReference<>() {
645650
};
646651

647-
private static TypeReference<McpSchema.ListResourceTemplatesResult> LIST_RESOURCE_TEMPLATES_RESULT_TYPE_REF = new TypeReference<>() {
652+
private static final TypeReference<McpSchema.ListResourceTemplatesResult> LIST_RESOURCE_TEMPLATES_RESULT_TYPE_REF = new TypeReference<>() {
648653
};
649654

650655
/**
@@ -742,6 +747,7 @@ public Mono<Void> unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRe
742747
VOID_TYPE_REFERENCE);
743748
}
744749

750+
@Deprecated
745751
private NotificationHandler resourcesChangeNotificationHandler(
746752
List<Consumer<List<McpSchema.Resource>>> resourcesChangeConsumers) {
747753

@@ -759,16 +765,20 @@ private NotificationHandler asyncResourcesChangeNotificationHandler(
759765
List<Function<List<McpSchema.Resource>, Mono<Void>>> resourcesChangeConsumers) {
760766
return params -> listResources().flatMap(listResourcesResult -> Flux.fromIterable(resourcesChangeConsumers)
761767
.flatMap(consumer -> consumer.apply(listResourcesResult.resources()))
768+
.onErrorResume(error -> {
769+
logger.error("Error handling resources list change notification", error);
770+
return Mono.empty();
771+
})
762772
.then());
763773
}
764774

765775
// --------------------------
766776
// Prompts
767777
// --------------------------
768-
private static TypeReference<McpSchema.ListPromptsResult> LIST_PROMPTS_RESULT_TYPE_REF = new TypeReference<>() {
778+
private static final TypeReference<McpSchema.ListPromptsResult> LIST_PROMPTS_RESULT_TYPE_REF = new TypeReference<>() {
769779
};
770780

771-
private static TypeReference<McpSchema.GetPromptResult> GET_PROMPT_RESULT_TYPE_REF = new TypeReference<>() {
781+
private static final TypeReference<McpSchema.GetPromptResult> GET_PROMPT_RESULT_TYPE_REF = new TypeReference<>() {
772782
};
773783

774784
/**
@@ -808,6 +818,7 @@ public Mono<Void> promptListChangedNotification() {
808818
return this.mcpSession.sendNotification(McpSchema.METHOD_NOTIFICATION_PROMPTS_LIST_CHANGED);
809819
}
810820

821+
@Deprecated
811822
private NotificationHandler promptsChangeNotificationHandler(
812823
List<Consumer<List<McpSchema.Prompt>>> promptsChangeConsumers) {
813824

@@ -827,6 +838,10 @@ private NotificationHandler asyncPromptsChangeNotificationHandler(
827838
List<Function<List<McpSchema.Prompt>, Mono<Void>>> promptsChangeConsumers) {
828839
return params -> listPrompts().flatMap(listPromptsResult -> Flux.fromIterable(promptsChangeConsumers)
829840
.flatMap(consumer -> consumer.apply(listPromptsResult.prompts()))
841+
.onErrorResume(error -> {
842+
logger.error("Error handling prompts list change notification", error);
843+
return Mono.empty();
844+
})
830845
.then());
831846
}
832847

mcp/src/main/java/org/springframework/ai/mcp/client/McpClient.java

Lines changed: 76 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
* .requestTimeout(Duration.ofSeconds(5))
6666
* .build();
6767
* }</pre>
68-
*
68+
*
6969
* Example of creating a basic asynchronous client: <pre>{@code
7070
* McpClient.async(transport)
7171
* .requestTimeout(Duration.ofSeconds(5))
@@ -114,6 +114,40 @@
114114
*/
115115
public interface McpClient {
116116

117+
/**
118+
* Start building a synchronous MCP client with the specified transport layer. The
119+
* synchronous MCP client provides blocking operations. Synchronous clients wait for
120+
* each operation to complete before returning, making them simpler to use but
121+
* potentially less performant for concurrent operations. The transport layer handles
122+
* the low-level communication between client and server using protocols like stdio or
123+
* Server-Sent Events (SSE).
124+
* @param transport The transport layer implementation for MCP communication. Common
125+
* implementations include {@code StdioClientTransport} for stdio-based communication
126+
* and {@code SseClientTransport} for SSE-based communication.
127+
* @return A new builder instance for configuring the client
128+
* @throws IllegalArgumentException if transport is null
129+
*/
130+
static SyncSpec sync(ClientMcpTransport transport) {
131+
return new SyncSpec(transport);
132+
}
133+
134+
/**
135+
* Start building an asynchronous MCP client with the specified transport layer. The
136+
* asynchronous MCP client provides non-blocking operations. Asynchronous clients
137+
* return reactive primitives (Mono/Flux) immediately, allowing for concurrent
138+
* operations and reactive programming patterns. The transport layer handles the
139+
* low-level communication between client and server using protocols like stdio or
140+
* Server-Sent Events (SSE).
141+
* @param transport The transport layer implementation for MCP communication. Common
142+
* implementations include {@code StdioClientTransport} for stdio-based communication
143+
* and {@code SseClientTransport} for SSE-based communication.
144+
* @return A new builder instance for configuring the client
145+
* @throws IllegalArgumentException if transport is null
146+
*/
147+
static AsyncSpec async(ClientMcpTransport transport) {
148+
return new AsyncSpec(transport);
149+
}
150+
117151
/**
118152
* Start building an MCP client with the specified transport layer. The transport
119153
* layer handles the low-level communication between client and server using protocols
@@ -371,7 +405,20 @@ public McpAsyncClient async() {
371405
}
372406

373407
/**
374-
* Synchronous client specification.
408+
* Synchronous client specification. This class follows the builder pattern to provide
409+
* a fluent API for setting up clients with custom configurations.
410+
*
411+
* <p>
412+
* The builder supports configuration of:
413+
* <ul>
414+
* <li>Transport layer for client-server communication
415+
* <li>Request timeouts for operation boundaries
416+
* <li>Client capabilities for feature negotiation
417+
* <li>Client implementation details for version tracking
418+
* <li>Root URIs for resource access
419+
* <li>Change notification handlers for tools, resources, and prompts
420+
* <li>Custom message sampling logic
421+
* </ul>
375422
*/
376423
class SyncSpec {
377424

@@ -381,21 +428,22 @@ class SyncSpec {
381428

382429
private ClientCapabilities capabilities;
383430

384-
private Implementation clientInfo = new Implementation("Spring AI MCP Client", "0.3.1");
431+
private Implementation clientInfo = new Implementation("Spring AI MCP Client", "1.0.0");
385432

386-
private Map<String, Root> roots = new HashMap<>();
433+
private final Map<String, Root> roots = new HashMap<>();
387434

388-
private List<Consumer<List<McpSchema.Tool>>> toolsChangeConsumers = new ArrayList<>();
435+
private final List<Consumer<List<McpSchema.Tool>>> toolsChangeConsumers = new ArrayList<>();
389436

390-
private List<Consumer<List<McpSchema.Resource>>> resourcesChangeConsumers = new ArrayList<>();
437+
private final List<Consumer<List<McpSchema.Resource>>> resourcesChangeConsumers = new ArrayList<>();
391438

392-
private List<Consumer<List<McpSchema.Prompt>>> promptsChangeConsumers = new ArrayList<>();
439+
private final List<Consumer<List<McpSchema.Prompt>>> promptsChangeConsumers = new ArrayList<>();
393440

394-
private List<Consumer<McpSchema.LoggingMessageNotification>> loggingConsumers = new ArrayList<>();
441+
private final List<Consumer<McpSchema.LoggingMessageNotification>> loggingConsumers = new ArrayList<>();
395442

396443
private Function<CreateMessageRequest, CreateMessageResult> samplingHandler;
397444

398445
private SyncSpec(ClientMcpTransport transport) {
446+
Assert.notNull(transport, "Transport must not be null");
399447
this.transport = transport;
400448
}
401449

@@ -581,7 +629,20 @@ public McpSyncClient build() {
581629
}
582630

583631
/**
584-
* Asynchronous client specification.
632+
* Asynchronous client specification. This class follows the builder pattern to
633+
* provide a fluent API for setting up clients with custom configurations.
634+
*
635+
* <p>
636+
* The builder supports configuration of:
637+
* <ul>
638+
* <li>Transport layer for client-server communication
639+
* <li>Request timeouts for operation boundaries
640+
* <li>Client capabilities for feature negotiation
641+
* <li>Client implementation details for version tracking
642+
* <li>Root URIs for resource access
643+
* <li>Change notification handlers for tools, resources, and prompts
644+
* <li>Custom message sampling logic
645+
* </ul>
585646
*/
586647
class AsyncSpec {
587648

@@ -593,19 +654,20 @@ class AsyncSpec {
593654

594655
private Implementation clientInfo = new Implementation("Spring AI MCP Client", "0.3.1");
595656

596-
private Map<String, Root> roots = new HashMap<>();
657+
private final Map<String, Root> roots = new HashMap<>();
597658

598-
private List<Function<List<McpSchema.Tool>, Mono<Void>>> toolsChangeConsumers = new ArrayList<>();
659+
private final List<Function<List<McpSchema.Tool>, Mono<Void>>> toolsChangeConsumers = new ArrayList<>();
599660

600-
private List<Function<List<McpSchema.Resource>, Mono<Void>>> resourcesChangeConsumers = new ArrayList<>();
661+
private final List<Function<List<McpSchema.Resource>, Mono<Void>>> resourcesChangeConsumers = new ArrayList<>();
601662

602-
private List<Function<List<McpSchema.Prompt>, Mono<Void>>> promptsChangeConsumers = new ArrayList<>();
663+
private final List<Function<List<McpSchema.Prompt>, Mono<Void>>> promptsChangeConsumers = new ArrayList<>();
603664

604-
private List<Function<McpSchema.LoggingMessageNotification, Mono<Void>>> loggingConsumers = new ArrayList<>();
665+
private final List<Function<McpSchema.LoggingMessageNotification, Mono<Void>>> loggingConsumers = new ArrayList<>();
605666

606667
private Function<CreateMessageRequest, Mono<CreateMessageResult>> samplingHandler;
607668

608669
private AsyncSpec(ClientMcpTransport transport) {
670+
Assert.notNull(transport, "Transport must not be null");
609671
this.transport = transport;
610672
}
611673

@@ -789,12 +851,4 @@ public McpAsyncClient build() {
789851

790852
}
791853

792-
static SyncSpec sync(ClientMcpTransport transport) {
793-
return new SyncSpec(transport);
794-
}
795-
796-
static AsyncSpec async(ClientMcpTransport transport) {
797-
return new AsyncSpec(transport);
798-
}
799-
800854
}

mcp/src/main/java/org/springframework/ai/mcp/client/McpClientFeatures.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
import org.springframework.ai.mcp.util.Utils;
1717

1818
/**
19-
* Internal representation of features and capabilities for Model Context Protocol (MCP)
20-
* clients. This class provides two record types for managing client features:
19+
* Representation of features and capabilities for Model Context Protocol (MCP) clients.
20+
* This class provides two record types for managing client features:
2121
* <ul>
2222
* <li>{@link Async} for non-blocking operations with Project Reactor's Mono responses
2323
* <li>{@link Sync} for blocking operations with direct responses
@@ -38,6 +38,7 @@
3838
* through the {@link Async#fromSync} method, which ensures proper handling of blocking
3939
* operations in non-blocking contexts by scheduling them on a bounded elastic scheduler.
4040
*
41+
* @author Dariusz Jędrzejczyk
4142
* @see McpClient
4243
* @see McpSchema.Implementation
4344
* @see McpSchema.ClientCapabilities
@@ -92,10 +93,10 @@ public Async(McpSchema.Implementation clientInfo, McpSchema.ClientCapabilities c
9293
samplingHandler != null ? new McpSchema.ClientCapabilities.Sampling() : null);
9394
this.roots = roots != null ? new ConcurrentHashMap<>(roots) : new ConcurrentHashMap<>();
9495

95-
this.toolsChangeConsumers = toolsChangeConsumers;
96-
this.resourcesChangeConsumers = resourcesChangeConsumers;
97-
this.promptsChangeConsumers = promptsChangeConsumers;
98-
this.loggingConsumers = loggingConsumers;
96+
this.toolsChangeConsumers = toolsChangeConsumers != null ? toolsChangeConsumers : List.of();
97+
this.resourcesChangeConsumers = resourcesChangeConsumers != null ? resourcesChangeConsumers : List.of();
98+
this.promptsChangeConsumers = promptsChangeConsumers != null ? promptsChangeConsumers : List.of();
99+
this.loggingConsumers = loggingConsumers != null ? loggingConsumers : List.of();
99100
this.samplingHandler = samplingHandler;
100101
}
101102

@@ -189,10 +190,10 @@ public Sync(McpSchema.Implementation clientInfo, McpSchema.ClientCapabilities cl
189190
samplingHandler != null ? new McpSchema.ClientCapabilities.Sampling() : null);
190191
this.roots = roots != null ? new HashMap<>(roots) : new HashMap<>();
191192

192-
this.toolsChangeConsumers = toolsChangeConsumers;
193-
this.resourcesChangeConsumers = resourcesChangeConsumers;
194-
this.promptsChangeConsumers = promptsChangeConsumers;
195-
this.loggingConsumers = loggingConsumers;
193+
this.toolsChangeConsumers = toolsChangeConsumers != null ? toolsChangeConsumers : List.of();
194+
this.resourcesChangeConsumers = resourcesChangeConsumers != null ? resourcesChangeConsumers : List.of();
195+
this.promptsChangeConsumers = promptsChangeConsumers != null ? promptsChangeConsumers : List.of();
196+
this.loggingConsumers = loggingConsumers != null ? loggingConsumers : List.of();
196197
this.samplingHandler = samplingHandler;
197198
}
198199
}

mcp/src/main/java/org/springframework/ai/mcp/client/McpSyncClient.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.slf4j.Logger;
2222
import org.slf4j.LoggerFactory;
2323

24+
import org.springframework.ai.mcp.spec.ClientMcpTransport;
2425
import org.springframework.ai.mcp.spec.McpSchema;
2526
import org.springframework.ai.mcp.spec.McpSchema.ClientCapabilities;
2627
import org.springframework.ai.mcp.spec.McpSchema.GetPromptRequest;
@@ -74,6 +75,14 @@ public class McpSyncClient implements AutoCloseable {
7475

7576
private final McpAsyncClient delegate;
7677

78+
/**
79+
* Create a new McpSyncClient with the given delegate.
80+
* @param delegate the asynchronous kernel on top of which this synchronous client
81+
* provides a blocking API.
82+
* @deprecated Use {@link McpClient#sync(ClientMcpTransport)} to obtain an instance.
83+
*/
84+
@Deprecated
85+
// TODO make the constructor package private post-deprecation
7786
public McpSyncClient(McpAsyncClient delegate) {
7887
Assert.notNull(delegate, "The delegate can not be null");
7988
this.delegate = delegate;

0 commit comments

Comments
 (0)