14
14
import io .modelcontextprotocol .server .McpAsyncServerExchange ;
15
15
import io .modelcontextprotocol .spec .SseEvent ;
16
16
import io .modelcontextprotocol .spec .McpSchema .McpId ;
17
+ import io .modelcontextprotocol .spec .McpError ;
17
18
18
19
import org .slf4j .Logger ;
19
20
import org .slf4j .LoggerFactory ;
@@ -53,8 +54,6 @@ public class McpServerSession implements McpSession {
53
54
54
55
private final Map <String , NotificationHandler > notificationHandlers ;
55
56
56
- private final Sinks .One <McpAsyncServerExchange > exchangeSink = Sinks .one ();
57
-
58
57
private final AtomicReference <McpSchema .ClientCapabilities > clientCapabilities = new AtomicReference <>();
59
58
60
59
private final AtomicReference <McpSchema .Implementation > clientInfo = new AtomicReference <>();
@@ -73,6 +72,8 @@ public class McpServerSession implements McpSession {
73
72
74
73
private final Map <String , Map <String , SseEvent >> transportEventHistories = new ConcurrentHashMap <>();
75
74
75
+ private volatile McpSchema .LoggingLevel minLoggingLevel = McpSchema .LoggingLevel .INFO ;
76
+
76
77
/**
77
78
* Creates a new server session with the given parameters and the transport to use.
78
79
* @param id session id
@@ -104,6 +105,16 @@ public McpServerSession(String id, Duration requestTimeout, InitRequestHandler i
104
105
this (id , requestTimeout , null , initHandler , initNotificationHandler , requestHandlers , notificationHandlers );
105
106
}
106
107
108
+ /**
109
+ * Updates the session's minimum logging level for all future exchanges.
110
+ */
111
+ public void setMinLoggingLevel (McpSchema .LoggingLevel level ) {
112
+ if (level != null ) {
113
+ this .minLoggingLevel = level ;
114
+ logger .debug ("Updated session {} minimum logging level to {}" , id , level );
115
+ }
116
+ }
117
+
107
118
/**
108
119
* Retrieve the session initialization state
109
120
* @return session initialization state
@@ -240,14 +251,26 @@ public RequestHandler<?> getRequestHandler(String method) {
240
251
241
252
@ Override
242
253
public <T > Mono <T > sendRequest (String method , Object requestParams , TypeReference <T > typeRef ) {
243
- McpId requestId = this .generateRequestId ();
254
+ return sendRequest (method , requestParams , typeRef , LISTENING_TRANSPORT );
255
+ }
256
+
257
+ public <T > Mono <T > sendRequest (String method , Object requestParams , TypeReference <T > typeRef , String transportId ) {
258
+ McpServerTransport transport = getTransport (transportId );
259
+ if (transport == null ) {
260
+ // Fallback to listening transport if specific transport not found
261
+ transport = getTransport (LISTENING_TRANSPORT );
262
+ if (transport == null ) {
263
+ return Mono .error (new RuntimeException ("Transport not found: " + transportId ));
264
+ }
265
+ }
244
266
267
+ final McpServerTransport finalTransport = transport ;
268
+ McpId requestId = this .generateRequestId ();
245
269
return Mono .<McpSchema .JSONRPCResponse >create (sink -> {
246
270
this .pendingResponses .put (requestId , sink );
247
271
McpSchema .JSONRPCRequest jsonrpcRequest = new McpSchema .JSONRPCRequest (McpSchema .JSONRPC_VERSION , method ,
248
272
requestId , requestParams );
249
-
250
- Flux .from (listeningTransport .sendMessage (jsonrpcRequest )).subscribe (v -> {
273
+ Flux .from (finalTransport .sendMessage (jsonrpcRequest )).subscribe (v -> {
251
274
}, error -> {
252
275
this .pendingResponses .remove (requestId );
253
276
sink .error (error );
@@ -260,17 +283,29 @@ else if (typeRef.getType().equals(Void.class)) {
260
283
sink .complete ();
261
284
}
262
285
else {
263
- T result = listeningTransport .unmarshalFrom (jsonRpcResponse .result (), typeRef );
286
+ T result = finalTransport .unmarshalFrom (jsonRpcResponse .result (), typeRef );
264
287
sink .next (result );
265
288
}
266
289
});
267
290
}
268
291
269
292
@ Override
270
293
public Mono <Void > sendNotification (String method , Object params ) {
294
+ return sendNotification (method , params , LISTENING_TRANSPORT );
295
+ }
296
+
297
+ public Mono <Void > sendNotification (String method , Object params , String transportId ) {
298
+ McpServerTransport transport = getTransport (transportId );
299
+ if (transport == null ) {
300
+ // Fallback to listening transport if specific transport not found
301
+ transport = getTransport (LISTENING_TRANSPORT );
302
+ if (transport == null ) {
303
+ return Mono .error (new RuntimeException ("Transport not found: " + transportId ));
304
+ }
305
+ }
271
306
McpSchema .JSONRPCNotification jsonrpcNotification = new McpSchema .JSONRPCNotification (McpSchema .JSONRPC_VERSION ,
272
307
method , params );
273
- return this . listeningTransport .sendMessage (jsonrpcNotification );
308
+ return transport .sendMessage (jsonrpcNotification );
274
309
}
275
310
276
311
/**
@@ -300,26 +335,20 @@ public Mono<Void> handle(McpSchema.JSONRPCMessage message) {
300
335
}
301
336
else if (message instanceof McpSchema .JSONRPCRequest request ) {
302
337
logger .debug ("Received request: {}" , request );
303
- final String transportId ;
304
- if (transports .isEmpty ()) {
305
- transportId = LISTENING_TRANSPORT ;
306
- }
307
- else {
308
- transportId = request .id ().toString ();
309
- }
338
+ final String transportId = determineTransportId (request );
310
339
return handleIncomingRequest (request ).onErrorResume (error -> {
311
340
var errorResponse = new McpSchema .JSONRPCResponse (McpSchema .JSONRPC_VERSION , request .id (), null ,
312
341
new McpSchema .JSONRPCResponse .JSONRPCError (McpSchema .ErrorCodes .INTERNAL_ERROR ,
313
342
error .getMessage (), null ));
314
- McpServerTransport transport = getTransport (transportId );
343
+ McpServerTransport transport = getTransportWithFallback (transportId );
315
344
return transport != null ? transport .sendMessage (errorResponse ).then (Mono .empty ()) : Mono .empty ();
316
345
}).flatMap (response -> {
317
- McpServerTransport transport = getTransport (transportId );
346
+ McpServerTransport transport = getTransportWithFallback (transportId );
318
347
if (transport != null ) {
319
348
return transport .sendMessage (response );
320
349
}
321
350
else {
322
- return Mono .error (new RuntimeException ("Transport not found: " + transportId ));
351
+ return Mono .error (new RuntimeException ("No transport available" ));
323
352
}
324
353
});
325
354
}
@@ -369,10 +398,10 @@ private Mono<McpSchema.JSONRPCResponse> handleIncomingRequest(McpSchema.JSONRPCR
369
398
error .message (), error .data ())));
370
399
}
371
400
372
- // We would need to add request.id() as a parameter to handler.handle() if
373
- // we want client- request-driven requests/notifications to go to the
374
- // related stream
375
- resultMono = this . exchangeSink . asMono (). flatMap ( exchange -> handler .handle (exchange , request .params () ));
401
+ McpAsyncServerExchange requestExchange = new McpAsyncServerExchange ( this , clientCapabilities . get (),
402
+ clientInfo . get (), determineTransportId ( request ));
403
+ requestExchange . setMinLoggingLevel ( minLoggingLevel );
404
+ resultMono = handler .handle (requestExchange , request .params ());
376
405
}
377
406
return resultMono
378
407
.map (result -> new McpSchema .JSONRPCResponse (McpSchema .JSONRPC_VERSION , request .id (), result , null ))
@@ -392,7 +421,6 @@ private Mono<Void> handleIncomingNotification(McpSchema.JSONRPCNotification noti
392
421
return Mono .defer (() -> {
393
422
if (McpSchema .METHOD_NOTIFICATION_INITIALIZED .equals (notification .method ())) {
394
423
this .state .lazySet (STATE_INITIALIZED );
395
- exchangeSink .tryEmitValue (new McpAsyncServerExchange (this , clientCapabilities .get (), clientInfo .get ()));
396
424
return this .initNotificationHandler .handle ();
397
425
}
398
426
@@ -401,7 +429,10 @@ private Mono<Void> handleIncomingNotification(McpSchema.JSONRPCNotification noti
401
429
logger .error ("No handler registered for notification method: {}" , notification .method ());
402
430
return Mono .empty ();
403
431
}
404
- return this .exchangeSink .asMono ().flatMap (exchange -> handler .handle (exchange , notification .params ()));
432
+ McpAsyncServerExchange notificationExchange = new McpAsyncServerExchange (this , clientCapabilities .get (),
433
+ clientInfo .get (), LISTENING_TRANSPORT );
434
+ notificationExchange .setMinLoggingLevel (minLoggingLevel );
435
+ return handler .handle (notificationExchange , notification .params ());
405
436
});
406
437
}
407
438
@@ -412,6 +443,32 @@ private MethodNotFoundError getMethodNotFoundError(String method) {
412
443
return new MethodNotFoundError (method , "Method not found: " + method , null );
413
444
}
414
445
446
+ /**
447
+ * Determines the appropriate transport ID for a request. Uses request ID for
448
+ * per-request routing only if a transport with that ID exists, otherwise falls back
449
+ * to listening transport.
450
+ */
451
+ private String determineTransportId (McpSchema .JSONRPCRequest request ) {
452
+ String requestTransportId = request .id ().toString ();
453
+ // Check if a transport exists for this specific request ID
454
+ if (getTransport (requestTransportId ) != null ) {
455
+ return requestTransportId ;
456
+ }
457
+ // Fallback to listening transport
458
+ return LISTENING_TRANSPORT ;
459
+ }
460
+
461
+ /**
462
+ * Gets a transport with fallback to listening transport.
463
+ */
464
+ private McpServerTransport getTransportWithFallback (String transportId ) {
465
+ McpServerTransport transport = getTransport (transportId );
466
+ if (transport == null ) {
467
+ transport = getTransport (LISTENING_TRANSPORT );
468
+ }
469
+ return transport ;
470
+ }
471
+
415
472
@ Override
416
473
public Mono <Void > closeGracefully () {
417
474
return Mono .defer (() -> {
0 commit comments