2424import io .modelcontextprotocol .client .transport .ResponseSubscribers .ResponseEvent ;
2525import io .modelcontextprotocol .client .transport .customizer .McpAsyncHttpClientRequestCustomizer ;
2626import io .modelcontextprotocol .client .transport .customizer .McpHttpClientAuthorizationErrorHandler ;
27+ import io .modelcontextprotocol .client .transport .customizer .McpHttpClientTransportAuthorizationErrorHandler ;
2728import io .modelcontextprotocol .client .transport .customizer .McpSyncHttpClientRequestCustomizer ;
2829import io .modelcontextprotocol .common .McpTransportContext ;
2930import io .modelcontextprotocol .json .McpJsonDefaults ;
@@ -120,7 +121,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
120121
121122 private final boolean openConnectionOnStartup ;
122123
123- private final McpHttpClientAuthorizationErrorHandler authorizationErrorHandler ;
124+ private final McpHttpClientTransportAuthorizationErrorHandler authorizationErrorHandler ;
124125
125126 private final boolean resumableStreams ;
126127
@@ -139,7 +140,8 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
139140 private HttpClientStreamableHttpTransport (McpJsonMapper jsonMapper , HttpClient httpClient ,
140141 HttpRequest .Builder requestBuilder , String baseUri , String endpoint , boolean resumableStreams ,
141142 boolean openConnectionOnStartup , McpAsyncHttpClientRequestCustomizer httpRequestCustomizer ,
142- McpHttpClientAuthorizationErrorHandler authorizationErrorHandler , List <String > supportedProtocolVersions ) {
143+ McpHttpClientTransportAuthorizationErrorHandler authorizationErrorHandler ,
144+ List <String > supportedProtocolVersions ) {
143145 this .jsonMapper = jsonMapper ;
144146 this .httpClient = httpClient ;
145147 this .requestBuilder = requestBuilder ;
@@ -295,10 +297,13 @@ private Mono<Disposable> reconnect(McpTransportStream<Disposable> stream) {
295297 int statusCode = responseEvent .responseInfo ().statusCode ();
296298 if (statusCode == 401 || statusCode == 403 ) {
297299 logger .debug ("Authorization error in reconnect with code {}" , statusCode );
300+ var request = requestBuilder .build ();
301+ var requestSnapshot = new HttpRequestSnapshot (request .uri (), request .method (),
302+ request .headers ());
298303 return Mono .<McpSchema .JSONRPCMessage >error (
299304 new McpHttpClientTransportAuthorizationException (
300305 "Authorization error connecting to SSE stream" ,
301- responseEvent .responseInfo ()));
306+ responseEvent .responseInfo (), requestSnapshot ));
302307 }
303308 else if (statusCode == METHOD_NOT_ALLOWED ) {
304309 logger .debug ("The server does not support SSE streams, using request-response mode." );
@@ -417,7 +422,8 @@ private Retry authorizationErrorRetrySpec() {
417422 return Mono .deferContextual (ctx -> {
418423 var transportContext = ctx .getOrDefault (McpTransportContext .KEY , McpTransportContext .EMPTY );
419424 return Mono
420- .from (this .authorizationErrorHandler .handle (authException .getResponseInfo (), transportContext ))
425+ .from (this .authorizationErrorHandler .handle (authException .getResponseInfo (),
426+ authException .getRequestSnapshot (), transportContext ))
421427 .switchIfEmpty (Mono .just (false ))
422428 .flatMap (shouldRetry -> shouldRetry ? Mono .just (retrySignal .totalRetries ())
423429 : Mono .error (retrySignal .failure ()));
@@ -489,7 +495,6 @@ public Mono<Void> sendMessage(McpSchema.JSONRPCMessage sentMessage) {
489495 return Mono
490496 .from (this .httpRequestCustomizer .customize (builder , "POST" , uri , jsonBody , transportContext ));
491497 }).flatMapMany (requestBuilder -> Flux .<ResponseEvent >create (responseEventSink -> {
492-
493498 // Create the async request with proper body subscriber selection
494499 Mono .fromFuture (this .httpClient
495500 .sendAsync (requestBuilder .build (), this .toSendMessageBodySubscriber (responseEventSink ))
@@ -502,12 +507,14 @@ public Mono<Void> sendMessage(McpSchema.JSONRPCMessage sentMessage) {
502507 }
503508 })).onErrorMap (CompletionException .class , t -> t .getCause ()).onErrorComplete ().subscribe ();
504509
505- })) .flatMap (responseEvent -> {
510+ }).flatMap (responseEvent -> {
506511 int statusCode = responseEvent .responseInfo ().statusCode ();
507512 if (statusCode == 401 || statusCode == 403 ) {
513+ var request = requestBuilder .build ();
514+ var requestSnapshot = new HttpRequestSnapshot (request .uri (), request .method (), request .headers ());
508515 logger .debug ("Authorization error in sendMessage with code {}" , statusCode );
509516 return Mono .<McpSchema .JSONRPCMessage >error (new McpHttpClientTransportAuthorizationException (
510- "Authorization error when sending message" , responseEvent .responseInfo ()));
517+ "Authorization error when sending message" , responseEvent .responseInfo (), requestSnapshot ));
511518 }
512519
513520 if (transportSession .markInitialized (
@@ -651,13 +658,12 @@ else if (statusCode == BAD_REQUEST) {
651658 if (ref != null ) {
652659 transportSession .removeConnection (ref );
653660 }
654- })
655- .contextWrite (deliveredSink .contextView ())
656- .subscribe ();
661+ })).contextWrite (deliveredSink .contextView ()).subscribe ();
657662
658663 disposableRef .set (connection );
659664 transportSession .addConnection (connection );
660665 });
666+
661667 }
662668
663669 private static String sessionIdOrPlaceholder (McpTransportSession <?> transportSession ) {
@@ -695,7 +701,7 @@ public static class Builder {
695701 private List <String > supportedProtocolVersions = List .of (ProtocolVersions .MCP_2024_11_05 ,
696702 ProtocolVersions .MCP_2025_03_26 , ProtocolVersions .MCP_2025_06_18 , ProtocolVersions .MCP_2025_11_25 );
697703
698- private McpHttpClientAuthorizationErrorHandler authorizationErrorHandler = McpHttpClientAuthorizationErrorHandler .NOOP ;
704+ private McpHttpClientTransportAuthorizationErrorHandler authorizationErrorHandler = McpHttpClientTransportAuthorizationErrorHandler .NOOP ;
699705
700706 /**
701707 * Creates a new builder with the specified base URI.
@@ -828,8 +834,34 @@ public Builder asyncHttpRequestCustomizer(McpAsyncHttpClientRequestCustomizer as
828834 * when sending a message.
829835 * @param authorizationErrorHandler the handler
830836 * @return this builder
837+ * @deprecated in favor of
838+ * {@link #authorizationErrorHandler(McpHttpClientTransportAuthorizationErrorHandler)}
831839 */
840+ @ Deprecated (forRemoval = true , since = "2.0.0" )
832841 public Builder authorizationErrorHandler (McpHttpClientAuthorizationErrorHandler authorizationErrorHandler ) {
842+ this .authorizationErrorHandler = new McpHttpClientTransportAuthorizationErrorHandler () {
843+ @ Override
844+ public Publisher <Boolean > handle (java .net .http .HttpResponse .ResponseInfo responseInfo ,
845+ HttpRequestSnapshot requestSnapshot , McpTransportContext context ) {
846+ return authorizationErrorHandler .handle (responseInfo , context );
847+ }
848+
849+ @ Override
850+ public int maxRetries () {
851+ return authorizationErrorHandler .maxRetries ();
852+ }
853+ };
854+ return this ;
855+ }
856+
857+ /**
858+ * Sets the handler to be used when the server responds with HTTP 401 or HTTP 403
859+ * when sending a message.
860+ * @param authorizationErrorHandler the handler
861+ * @return this builder
862+ */
863+ public Builder authorizationErrorHandler (
864+ McpHttpClientTransportAuthorizationErrorHandler authorizationErrorHandler ) {
833865 this .authorizationErrorHandler = authorizationErrorHandler ;
834866 return this ;
835867 }
0 commit comments