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