18
18
import io .modelcontextprotocol .spec .McpServerTransportProvider ;
19
19
import io .modelcontextprotocol .spec .McpServerSession ;
20
20
import io .modelcontextprotocol .util .Assert ;
21
+ import io .modelcontextprotocol .util .KeepAliveScheduler ;
22
+
21
23
import org .slf4j .Logger ;
22
24
import org .slf4j .LoggerFactory ;
23
25
import reactor .core .publisher .Flux ;
@@ -107,6 +109,8 @@ public class WebMvcSseServerTransportProvider implements McpServerTransportProvi
107
109
*/
108
110
private volatile boolean isClosing = false ;
109
111
112
+ private KeepAliveScheduler keepAliveScheduler ;
113
+
110
114
/**
111
115
* Constructs a new WebMvcSseServerTransportProvider instance with the default SSE
112
116
* endpoint.
@@ -116,7 +120,10 @@ public class WebMvcSseServerTransportProvider implements McpServerTransportProvi
116
120
* messages via HTTP POST. This endpoint will be communicated to clients through the
117
121
* SSE connection's initial endpoint event.
118
122
* @throws IllegalArgumentException if either objectMapper or messageEndpoint is null
123
+ * @deprecated Use the builder {@link #builder()} instead for better configuration
124
+ * options.
119
125
*/
126
+ @ Deprecated
120
127
public WebMvcSseServerTransportProvider (ObjectMapper objectMapper , String messageEndpoint ) {
121
128
this (objectMapper , messageEndpoint , DEFAULT_SSE_ENDPOINT );
122
129
}
@@ -130,7 +137,10 @@ public WebMvcSseServerTransportProvider(ObjectMapper objectMapper, String messag
130
137
* SSE connection's initial endpoint event.
131
138
* @param sseEndpoint The endpoint URI where clients establish their SSE connections.
132
139
* @throws IllegalArgumentException if any parameter is null
140
+ * @deprecated Use the builder {@link #builder()} instead for better configuration
141
+ * options.
133
142
*/
143
+ @ Deprecated
134
144
public WebMvcSseServerTransportProvider (ObjectMapper objectMapper , String messageEndpoint , String sseEndpoint ) {
135
145
this (objectMapper , "" , messageEndpoint , sseEndpoint );
136
146
}
@@ -146,9 +156,33 @@ public WebMvcSseServerTransportProvider(ObjectMapper objectMapper, String messag
146
156
* SSE connection's initial endpoint event.
147
157
* @param sseEndpoint The endpoint URI where clients establish their SSE connections.
148
158
* @throws IllegalArgumentException if any parameter is null
159
+ * @deprecated Use the builder {@link #builder()} instead for better configuration
160
+ * options.
149
161
*/
162
+ @ Deprecated
150
163
public WebMvcSseServerTransportProvider (ObjectMapper objectMapper , String baseUrl , String messageEndpoint ,
151
164
String sseEndpoint ) {
165
+ this (objectMapper , baseUrl , messageEndpoint , sseEndpoint , null );
166
+ }
167
+
168
+ /**
169
+ * Constructs a new WebMvcSseServerTransportProvider instance.
170
+ * @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
171
+ * of messages.
172
+ * @param baseUrl The base URL for the message endpoint, used to construct the full
173
+ * endpoint URL for clients.
174
+ * @param messageEndpoint The endpoint URI where clients should send their JSON-RPC
175
+ * messages via HTTP POST. This endpoint will be communicated to clients through the
176
+ * SSE connection's initial endpoint event.
177
+ * @param sseEndpoint The endpoint URI where clients establish their SSE connections.
178
+ * * @param keepAliveInterval The interval for sending keep-alive messages to
179
+ * @throws IllegalArgumentException if any parameter is null
180
+ * @deprecated Use the builder {@link #builder()} instead for better configuration
181
+ * options.
182
+ */
183
+ @ Deprecated
184
+ public WebMvcSseServerTransportProvider (ObjectMapper objectMapper , String baseUrl , String messageEndpoint ,
185
+ String sseEndpoint , Duration keepAliveInterval ) {
152
186
Assert .notNull (objectMapper , "ObjectMapper must not be null" );
153
187
Assert .notNull (baseUrl , "Message base URL must not be null" );
154
188
Assert .notNull (messageEndpoint , "Message endpoint must not be null" );
@@ -162,6 +196,17 @@ public WebMvcSseServerTransportProvider(ObjectMapper objectMapper, String baseUr
162
196
.GET (this .sseEndpoint , this ::handleSseConnection )
163
197
.POST (this .messageEndpoint , this ::handleMessage )
164
198
.build ();
199
+
200
+ if (keepAliveInterval != null ) {
201
+
202
+ this .keepAliveScheduler = KeepAliveScheduler
203
+ .builder (() -> (isClosing ) ? Flux .empty () : Flux .fromIterable (sessions .values ()))
204
+ .initialDelay (keepAliveInterval )
205
+ .interval (keepAliveInterval )
206
+ .build ();
207
+
208
+ this .keepAliveScheduler .start ();
209
+ }
165
210
}
166
211
167
212
@ Override
@@ -209,10 +254,13 @@ public Mono<Void> closeGracefully() {
209
254
return Flux .fromIterable (sessions .values ()).doFirst (() -> {
210
255
this .isClosing = true ;
211
256
logger .debug ("Initiating graceful shutdown with {} active sessions" , sessions .size ());
212
- })
213
- .flatMap (McpServerSession ::closeGracefully )
214
- .then ()
215
- .doOnSuccess (v -> logger .debug ("Graceful shutdown completed" ));
257
+ }).flatMap (McpServerSession ::closeGracefully ).then ().doOnSuccess (v -> {
258
+ logger .debug ("Graceful shutdown completed" );
259
+ sessions .clear ();
260
+ if (this .keepAliveScheduler != null ) {
261
+ this .keepAliveScheduler .shutdown ();
262
+ }
263
+ });
216
264
}
217
265
218
266
/**
@@ -435,4 +483,106 @@ public void close() {
435
483
436
484
}
437
485
486
+ /**
487
+ * Creates a new Builder instance for configuring and creating instances of
488
+ * WebMvcSseServerTransportProvider.
489
+ * @return A new Builder instance
490
+ */
491
+ public static Builder builder () {
492
+ return new Builder ();
493
+ }
494
+
495
+ /**
496
+ * Builder for creating instances of WebMvcSseServerTransportProvider.
497
+ * <p>
498
+ * This builder provides a fluent API for configuring and creating instances of
499
+ * WebMvcSseServerTransportProvider with custom settings.
500
+ */
501
+ public static class Builder {
502
+
503
+ private ObjectMapper objectMapper = new ObjectMapper ();
504
+
505
+ private String baseUrl = "" ;
506
+
507
+ private String messageEndpoint ;
508
+
509
+ private String sseEndpoint = DEFAULT_SSE_ENDPOINT ;
510
+
511
+ private Duration keepAliveInterval ;
512
+
513
+ /**
514
+ * Sets the JSON object mapper to use for message serialization/deserialization.
515
+ * @param objectMapper The object mapper to use
516
+ * @return This builder instance for method chaining
517
+ */
518
+ public Builder objectMapper (ObjectMapper objectMapper ) {
519
+ Assert .notNull (objectMapper , "ObjectMapper must not be null" );
520
+ this .objectMapper = objectMapper ;
521
+ return this ;
522
+ }
523
+
524
+ /**
525
+ * Sets the base URL for the server transport.
526
+ * @param baseUrl The base URL to use
527
+ * @return This builder instance for method chaining
528
+ */
529
+ public Builder baseUrl (String baseUrl ) {
530
+ Assert .notNull (baseUrl , "Base URL must not be null" );
531
+ this .baseUrl = baseUrl ;
532
+ return this ;
533
+ }
534
+
535
+ /**
536
+ * Sets the endpoint path where clients will send their messages.
537
+ * @param messageEndpoint The message endpoint path
538
+ * @return This builder instance for method chaining
539
+ */
540
+ public Builder messageEndpoint (String messageEndpoint ) {
541
+ Assert .hasText (messageEndpoint , "Message endpoint must not be empty" );
542
+ this .messageEndpoint = messageEndpoint ;
543
+ return this ;
544
+ }
545
+
546
+ /**
547
+ * Sets the endpoint path where clients will establish SSE connections.
548
+ * <p>
549
+ * If not specified, the default value of {@link #DEFAULT_SSE_ENDPOINT} will be
550
+ * used.
551
+ * @param sseEndpoint The SSE endpoint path
552
+ * @return This builder instance for method chaining
553
+ */
554
+ public Builder sseEndpoint (String sseEndpoint ) {
555
+ Assert .hasText (sseEndpoint , "SSE endpoint must not be empty" );
556
+ this .sseEndpoint = sseEndpoint ;
557
+ return this ;
558
+ }
559
+
560
+ /**
561
+ * Sets the interval for keep-alive pings.
562
+ * <p>
563
+ * If not specified, keep-alive pings will be disabled.
564
+ * @param keepAliveInterval The interval duration for keep-alive pings
565
+ * @return This builder instance for method chaining
566
+ */
567
+ public Builder keepAliveInterval (Duration keepAliveInterval ) {
568
+ this .keepAliveInterval = keepAliveInterval ;
569
+ return this ;
570
+ }
571
+
572
+ /**
573
+ * Builds a new instance of WebMvcSseServerTransportProvider with the configured
574
+ * settings.
575
+ * @return A new WebMvcSseServerTransportProvider instance
576
+ * @throws IllegalStateException if objectMapper or messageEndpoint is not set
577
+ */
578
+ public WebMvcSseServerTransportProvider build () {
579
+ if (messageEndpoint == null ) {
580
+ throw new IllegalStateException ("MessageEndpoint must be set" );
581
+ }
582
+ return new WebMvcSseServerTransportProvider (objectMapper , baseUrl , messageEndpoint , sseEndpoint ,
583
+ keepAliveInterval );
584
+ }
585
+
586
+ }
587
+
438
588
}
0 commit comments