refactor: move and rename HTTP request customizers to dedicated package (#453)

- Move Mcp(Async)HttpRequestCustomizer from client.transport to client.transport.customizer
- Rename (Async)HttpRequestCustomizer to Mcp(Async)HttpRequestCustomizer
- Add DelegatingMcpAsyncHttpRequestCustomizer and DelegatingMcpSyncHttpRequestCustomizer for composing multiple customizers
- Update all references across transport classes and tests
- Improve package organization and naming consistency with MCP prefix
This commit is contained in:
Daniel Garnier-Moiroux 2025-08-05 15:49:20 +02:00 • committed by Christian Tzolov
commit 95ba8e77ec
6 changed files with 238 additions and 34 deletions

View file

@ -22,6 +22,8 @@ import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpRequestCustomizer;
import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer;
import io.modelcontextprotocol.client.transport.ResponseSubscribers.ResponseEvent;
import io.modelcontextprotocol.spec.McpClientTransport;
import io.modelcontextprotocol.spec.McpSchema;
@ -112,7 +114,7 @@ public class HttpClientSseClientTransport implements McpClientTransport {
/**
* Customizer to modify requests before they are executed.
*/
private final AsyncHttpRequestCustomizer httpRequestCustomizer;
private final McpAsyncHttpRequestCustomizer httpRequestCustomizer;
/**
* Creates a new transport instance with default HTTP client and object mapper.
@ -186,7 +188,7 @@ public class HttpClientSseClientTransport implements McpClientTransport {
@Deprecated(forRemoval = true)
HttpClientSseClientTransport(HttpClient httpClient, HttpRequest.Builder requestBuilder, String baseUri,
String sseEndpoint, ObjectMapper objectMapper) {
this(httpClient, requestBuilder, baseUri, sseEndpoint, objectMapper, AsyncHttpRequestCustomizer.NOOP);
this(httpClient, requestBuilder, baseUri, sseEndpoint, objectMapper, McpAsyncHttpRequestCustomizer.NOOP);
}
/**
@ -202,7 +204,7 @@ public class HttpClientSseClientTransport implements McpClientTransport {
* @throws IllegalArgumentException if objectMapper, clientBuilder, or headers is null
*/
HttpClientSseClientTransport(HttpClient httpClient, HttpRequest.Builder requestBuilder, String baseUri,
String sseEndpoint, ObjectMapper objectMapper, AsyncHttpRequestCustomizer httpRequestCustomizer) {
String sseEndpoint, ObjectMapper objectMapper, McpAsyncHttpRequestCustomizer httpRequestCustomizer) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
Assert.hasText(baseUri, "baseUri must not be empty");
Assert.hasText(sseEndpoint, "sseEndpoint must not be empty");
@ -246,7 +248,7 @@ public class HttpClientSseClientTransport implements McpClientTransport {
private HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
private AsyncHttpRequestCustomizer httpRequestCustomizer = AsyncHttpRequestCustomizer.NOOP;
private McpAsyncHttpRequestCustomizer httpRequestCustomizer = McpAsyncHttpRequestCustomizer.NOOP;
private Duration connectTimeout = Duration.ofSeconds(10);
@ -352,16 +354,16 @@ public class HttpClientSseClientTransport implements McpClientTransport {
* executing them.
* <p>
* This overrides the customizer from
* {@link #asyncHttpRequestCustomizer(AsyncHttpRequestCustomizer)}.
* {@link #asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer)}.
* <p>
* Do NOT use a blocking {@link SyncHttpRequestCustomizer} in a non-blocking
* context. Use {@link #asyncHttpRequestCustomizer(AsyncHttpRequestCustomizer)}
* Do NOT use a blocking {@link McpSyncHttpRequestCustomizer} in a non-blocking
* context. Use {@link #asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer)}
* instead.
* @param syncHttpRequestCustomizer the request customizer
* @return this builder
*/
public Builder httpRequestCustomizer(SyncHttpRequestCustomizer syncHttpRequestCustomizer) {
this.httpRequestCustomizer = AsyncHttpRequestCustomizer.fromSync(syncHttpRequestCustomizer);
public Builder httpRequestCustomizer(McpSyncHttpRequestCustomizer syncHttpRequestCustomizer) {
this.httpRequestCustomizer = McpAsyncHttpRequestCustomizer.fromSync(syncHttpRequestCustomizer);
return this;
}
@ -370,13 +372,13 @@ public class HttpClientSseClientTransport implements McpClientTransport {
* executing them.
* <p>
* This overrides the customizer from
* {@link #httpRequestCustomizer(SyncHttpRequestCustomizer)}.
* {@link #httpRequestCustomizer(McpSyncHttpRequestCustomizer)}.
* <p>
* Do NOT use a blocking implementation in a non-blocking context.
* @param asyncHttpRequestCustomizer the request customizer
* @return this builder
*/
public Builder asyncHttpRequestCustomizer(AsyncHttpRequestCustomizer asyncHttpRequestCustomizer) {
public Builder asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer asyncHttpRequestCustomizer) {
this.httpRequestCustomizer = asyncHttpRequestCustomizer;
return this;
}

View file

@ -25,6 +25,8 @@ import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpRequestCustomizer;
import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer;
import io.modelcontextprotocol.client.transport.ResponseSubscribers.ResponseEvent;
import io.modelcontextprotocol.spec.DefaultMcpTransportSession;
import io.modelcontextprotocol.spec.DefaultMcpTransportStream;
@ -113,7 +115,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
private final boolean resumableStreams;
private final AsyncHttpRequestCustomizer httpRequestCustomizer;
private final McpAsyncHttpRequestCustomizer httpRequestCustomizer;
private final AtomicReference<DefaultMcpTransportSession> activeSession = new AtomicReference<>();
@ -123,7 +125,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
private HttpClientStreamableHttpTransport(ObjectMapper objectMapper, HttpClient httpClient,
HttpRequest.Builder requestBuilder, String baseUri, String endpoint, boolean resumableStreams,
boolean openConnectionOnStartup, AsyncHttpRequestCustomizer httpRequestCustomizer) {
boolean openConnectionOnStartup, McpAsyncHttpRequestCustomizer httpRequestCustomizer) {
this.objectMapper = objectMapper;
this.httpClient = httpClient;
this.requestBuilder = requestBuilder;
@ -598,7 +600,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
private HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
private AsyncHttpRequestCustomizer httpRequestCustomizer = AsyncHttpRequestCustomizer.NOOP;
private McpAsyncHttpRequestCustomizer httpRequestCustomizer = McpAsyncHttpRequestCustomizer.NOOP;
private Duration connectTimeout = Duration.ofSeconds(10);
@ -709,16 +711,16 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
* executing them.
* <p>
* This overrides the customizer from
* {@link #asyncHttpRequestCustomizer(AsyncHttpRequestCustomizer)}.
* {@link #asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer)}.
* <p>
* Do NOT use a blocking {@link SyncHttpRequestCustomizer} in a non-blocking
* context. Use {@link #asyncHttpRequestCustomizer(AsyncHttpRequestCustomizer)}
* Do NOT use a blocking {@link McpSyncHttpRequestCustomizer} in a non-blocking
* context. Use {@link #asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer)}
* instead.
* @param syncHttpRequestCustomizer the request customizer
* @return this builder
*/
public Builder httpRequestCustomizer(SyncHttpRequestCustomizer syncHttpRequestCustomizer) {
this.httpRequestCustomizer = AsyncHttpRequestCustomizer.fromSync(syncHttpRequestCustomizer);
public Builder httpRequestCustomizer(McpSyncHttpRequestCustomizer syncHttpRequestCustomizer) {
this.httpRequestCustomizer = McpAsyncHttpRequestCustomizer.fromSync(syncHttpRequestCustomizer);
return this;
}
@ -727,13 +729,13 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
* executing them.
* <p>
* This overrides the customizer from
* {@link #httpRequestCustomizer(SyncHttpRequestCustomizer)}.
* {@link #httpRequestCustomizer(McpSyncHttpRequestCustomizer)}.
* <p>
* Do NOT use a blocking implementation in a non-blocking context.
* @param asyncHttpRequestCustomizer the request customizer
* @return this builder
*/
public Builder asyncHttpRequestCustomizer(AsyncHttpRequestCustomizer asyncHttpRequestCustomizer) {
public Builder asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer asyncHttpRequestCustomizer) {
this.httpRequestCustomizer = asyncHttpRequestCustomizer;
return this;
}

View file

@ -0,0 +1,38 @@
/*
* Copyright 2024-2025 the original author or authors.
*/
package io.modelcontextprotocol.client.transport.customizer;
import io.modelcontextprotocol.util.Assert;
import java.net.URI;
import java.net.http.HttpRequest;
import java.util.List;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
/**
* Composable {@link McpAsyncHttpRequestCustomizer} that applies multiple customizers, in
* order.
*
* @author Daniel Garnier-Moiroux
*/
public class DelegatingMcpAsyncHttpRequestCustomizer implements McpAsyncHttpRequestCustomizer {
private final List<McpAsyncHttpRequestCustomizer> customizers;
public DelegatingMcpAsyncHttpRequestCustomizer(List<McpAsyncHttpRequestCustomizer> customizers) {
Assert.notNull(customizers, "Customizers must not be null");
this.customizers = customizers;
}
@Override
public Publisher<HttpRequest.Builder> customize(HttpRequest.Builder builder, String method, URI endpoint,
String body) {
var result = Mono.just(builder);
for (var customizer : this.customizers) {
result = result.flatMap(b -> Mono.from(customizer.customize(b, method, endpoint, body)));
}
return result;
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright 2024-2025 the original author or authors.
*/
package io.modelcontextprotocol.client.transport.customizer;
import io.modelcontextprotocol.util.Assert;
import java.net.URI;
import java.net.http.HttpRequest;
import java.util.List;
/**
* Composable {@link McpSyncHttpRequestCustomizer} that applies multiple customizers, in
* order.
*
* @author Daniel Garnier-Moiroux
*/
public class DelegatingMcpSyncHttpRequestCustomizer implements McpSyncHttpRequestCustomizer {
private final List<McpSyncHttpRequestCustomizer> delegates;
public DelegatingMcpSyncHttpRequestCustomizer(List<McpSyncHttpRequestCustomizer> customizers) {
Assert.notNull(customizers, "Customizers must not be null");
this.delegates = customizers;
}
@Override
public void customize(HttpRequest.Builder builder, String method, URI endpoint, String body) {
this.delegates.forEach(delegate -> delegate.customize(builder, method, endpoint, body));
}
}

View file

@ -0,0 +1,52 @@
/*
* Copyright 2024-2025 the original author or authors.
*/
package io.modelcontextprotocol.client.transport.customizer;
import java.net.URI;
import java.net.http.HttpRequest;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import reactor.util.annotation.Nullable;
/**
* Customize {@link HttpRequest.Builder} before executing the request, in either SSE or
* Streamable HTTP transport.
* <p>
* When used in a non-blocking context, implementations MUST be non-blocking.
*
* @author Daniel Garnier-Moiroux
*/
public interface McpAsyncHttpRequestCustomizer {
Publisher<HttpRequest.Builder> customize(HttpRequest.Builder builder, String method, URI endpoint,
@Nullable String body);
McpAsyncHttpRequestCustomizer NOOP = new Noop();
/**
* Wrap a sync implementation in an async wrapper.
* <p>
* Do NOT wrap a blocking implementation for use in a non-blocking context. For a
* blocking implementation, consider using {@link Schedulers#boundedElastic()}.
*/
static McpAsyncHttpRequestCustomizer fromSync(McpSyncHttpRequestCustomizer customizer) {
return (builder, method, uri, body) -> Mono.fromSupplier(() -> {
customizer.customize(builder, method, uri, body);
return builder;
});
}
class Noop implements McpAsyncHttpRequestCustomizer {
@Override
public Publisher<HttpRequest.Builder> customize(HttpRequest.Builder builder, String method, URI endpoint,
String body) {
return Mono.just(builder);
}
}
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024-2025 the original author or authors.
*/
package io.modelcontextprotocol.client.transport.customizer;
import java.net.URI;
import java.net.http.HttpRequest;
import reactor.util.annotation.Nullable;
/**
* Customize {@link HttpRequest.Builder} before executing the request, either in SSE or
* Streamable HTTP transport.
*
* @author Daniel Garnier-Moiroux
*/
public interface McpSyncHttpRequestCustomizer {
void customize(HttpRequest.Builder builder, String method, URI endpoint, @Nullable String body);
}