5
5
import io .modelcontextprotocol .spec .DefaultMcpTransportContext ;
6
6
import io .modelcontextprotocol .spec .McpError ;
7
7
import io .modelcontextprotocol .spec .McpSchema ;
8
- import io .modelcontextprotocol .spec .McpServerTransport ;
9
8
import io .modelcontextprotocol .spec .McpStreamableServerSession ;
9
+ import io .modelcontextprotocol .spec .McpStreamableServerTransport ;
10
10
import io .modelcontextprotocol .spec .McpStreamableServerTransportProvider ;
11
11
import io .modelcontextprotocol .spec .McpTransportContext ;
12
12
import io .modelcontextprotocol .util .Assert ;
@@ -45,6 +45,8 @@ public class WebFluxStreamableServerTransportProvider implements McpStreamableSe
45
45
46
46
private final String mcpEndpoint ;
47
47
48
+ private final boolean disallowDelete ;
49
+
48
50
private final RouterFunction <?> routerFunction ;
49
51
50
52
private McpStreamableServerSession .Factory sessionFactory ;
@@ -70,7 +72,7 @@ public class WebFluxStreamableServerTransportProvider implements McpStreamableSe
70
72
* @throws IllegalArgumentException if either parameter is null
71
73
*/
72
74
public WebFluxStreamableServerTransportProvider (ObjectMapper objectMapper , String mcpEndpoint ) {
73
- this (objectMapper , DEFAULT_BASE_URL , mcpEndpoint );
75
+ this (objectMapper , DEFAULT_BASE_URL , mcpEndpoint , false );
74
76
}
75
77
76
78
/**
@@ -83,17 +85,20 @@ public WebFluxStreamableServerTransportProvider(ObjectMapper objectMapper, Strin
83
85
* setup. Must not be null.
84
86
* @throws IllegalArgumentException if either parameter is null
85
87
*/
86
- public WebFluxStreamableServerTransportProvider (ObjectMapper objectMapper , String baseUrl , String mcpEndpoint ) {
88
+ public WebFluxStreamableServerTransportProvider (ObjectMapper objectMapper , String baseUrl , String mcpEndpoint ,
89
+ boolean disallowDelete ) {
87
90
Assert .notNull (objectMapper , "ObjectMapper must not be null" );
88
91
Assert .notNull (baseUrl , "Message base path must not be null" );
89
92
Assert .notNull (mcpEndpoint , "Message endpoint must not be null" );
90
93
91
94
this .objectMapper = objectMapper ;
92
95
this .baseUrl = baseUrl ;
93
96
this .mcpEndpoint = mcpEndpoint ;
97
+ this .disallowDelete = disallowDelete ;
94
98
this .routerFunction = RouterFunctions .route ()
95
99
.GET (this .mcpEndpoint , this ::handleGet )
96
100
.POST (this .mcpEndpoint , this ::handlePost )
101
+ .DELETE (this .mcpEndpoint , this ::handleDelete )
97
102
.build ();
98
103
}
99
104
@@ -306,7 +311,37 @@ else if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) {
306
311
}).contextWrite (ctx -> ctx .put (McpTransportContext .KEY , transportContext ));
307
312
}
308
313
309
- private class WebFluxStreamableMcpSessionTransport implements McpServerTransport {
314
+ private Mono <ServerResponse > handleDelete (ServerRequest request ) {
315
+ if (isClosing ) {
316
+ return ServerResponse .status (HttpStatus .SERVICE_UNAVAILABLE ).bodyValue ("Server is shutting down" );
317
+ }
318
+
319
+ McpTransportContext transportContext = this .contextExtractor .apply (request );
320
+
321
+ return Mono .defer (() -> {
322
+ if (!request .headers ().asHttpHeaders ().containsKey ("mcp-session-id" )) {
323
+ return ServerResponse .badRequest ().build (); // TODO: say we need a session
324
+ // id
325
+ }
326
+
327
+ // TODO: The user can configure whether deletions are permitted
328
+ if (this .disallowDelete ) {
329
+ return ServerResponse .status (HttpStatus .METHOD_NOT_ALLOWED ).build ();
330
+ }
331
+
332
+ String sessionId = request .headers ().asHttpHeaders ().getFirst ("mcp-session-id" );
333
+
334
+ McpStreamableServerSession session = this .sessions .get (sessionId );
335
+
336
+ if (session == null ) {
337
+ return ServerResponse .notFound ().build ();
338
+ }
339
+
340
+ return session .delete ().then (ServerResponse .ok ().build ());
341
+ }).contextWrite (ctx -> ctx .put (McpTransportContext .KEY , transportContext ));
342
+ }
343
+
344
+ private class WebFluxStreamableMcpSessionTransport implements McpStreamableServerTransport {
310
345
311
346
private final FluxSink <ServerSentEvent <?>> sink ;
312
347
@@ -316,6 +351,11 @@ public WebFluxStreamableMcpSessionTransport(FluxSink<ServerSentEvent<?>> sink) {
316
351
317
352
@ Override
318
353
public Mono <Void > sendMessage (McpSchema .JSONRPCMessage message ) {
354
+ return this .sendMessage (message , null );
355
+ }
356
+
357
+ @ Override
358
+ public Mono <Void > sendMessage (McpSchema .JSONRPCMessage message , String messageId ) {
319
359
return Mono .fromSupplier (() -> {
320
360
try {
321
361
return objectMapper .writeValueAsString (message );
@@ -325,6 +365,7 @@ public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
325
365
}
326
366
}).doOnNext (jsonText -> {
327
367
ServerSentEvent <Object > event = ServerSentEvent .builder ()
368
+ .id (messageId )
328
369
.event (MESSAGE_EVENT_TYPE )
329
370
.data (jsonText )
330
371
.build ();
@@ -419,7 +460,7 @@ public WebFluxStreamableServerTransportProvider build() {
419
460
Assert .notNull (objectMapper , "ObjectMapper must be set" );
420
461
Assert .notNull (mcpEndpoint , "Message endpoint must be set" );
421
462
422
- return new WebFluxStreamableServerTransportProvider (objectMapper , baseUrl , mcpEndpoint );
463
+ return new WebFluxStreamableServerTransportProvider (objectMapper , baseUrl , mcpEndpoint , false );
423
464
}
424
465
425
466
}
0 commit comments