From 12bea63796c2e5f31c9e424e4147f1cc700d354c Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Tue, 5 Aug 2025 15:49:20 +0200 Subject: [PATCH 1/3] Rename (Async)HttpRequestCustomizer to Mcp(Async)HttpRequestCustomizer --- .../HttpClientSseClientTransport.java | 22 +++++++++---------- .../HttpClientStreamableHttpTransport.java | 20 ++++++++--------- ...ava => McpAsyncHttpRequestCustomizer.java} | 8 +++---- ...java => McpSyncHttpRequestCustomizer.java} | 2 +- .../HttpClientSseClientTransportTests.java | 6 ++--- ...HttpClientStreamableHttpTransportTest.java | 4 ++-- 6 files changed, 31 insertions(+), 31 deletions(-) rename mcp/src/main/java/io/modelcontextprotocol/client/transport/{AsyncHttpRequestCustomizer.java => McpAsyncHttpRequestCustomizer.java} (83%) rename mcp/src/main/java/io/modelcontextprotocol/client/transport/{SyncHttpRequestCustomizer.java => McpSyncHttpRequestCustomizer.java} (90%) diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java index 473f71fbb..a800352a0 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java @@ -112,7 +112,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. @@ -187,7 +187,7 @@ public HttpClientSseClientTransport(HttpClient.Builder clientBuilder, HttpReques @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); } /** @@ -203,7 +203,7 @@ public HttpClientSseClientTransport(HttpClient.Builder clientBuilder, HttpReques * @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"); @@ -250,7 +250,7 @@ public static class Builder { private HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() .header("Content-Type", "application/json"); - private AsyncHttpRequestCustomizer httpRequestCustomizer = AsyncHttpRequestCustomizer.NOOP; + private McpAsyncHttpRequestCustomizer httpRequestCustomizer = McpAsyncHttpRequestCustomizer.NOOP; /** * Creates a new builder instance. @@ -354,16 +354,16 @@ public Builder objectMapper(ObjectMapper objectMapper) { * executing them. *

* This overrides the customizer from - * {@link #asyncHttpRequestCustomizer(AsyncHttpRequestCustomizer)}. + * {@link #asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer)}. *

- * 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; } @@ -372,13 +372,13 @@ public Builder httpRequestCustomizer(SyncHttpRequestCustomizer syncHttpRequestCu * executing them. *

* This overrides the customizer from - * {@link #httpRequestCustomizer(SyncHttpRequestCustomizer)}. + * {@link #httpRequestCustomizer(McpSyncHttpRequestCustomizer)}. *

* 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; } diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java index 3cfa7359b..860426ded 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java @@ -113,7 +113,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport { private final boolean resumableStreams; - private final AsyncHttpRequestCustomizer httpRequestCustomizer; + private final McpAsyncHttpRequestCustomizer httpRequestCustomizer; private final AtomicReference activeSession = new AtomicReference<>(); @@ -123,7 +123,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; @@ -567,7 +567,7 @@ public static class Builder { private HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(); - private AsyncHttpRequestCustomizer httpRequestCustomizer = AsyncHttpRequestCustomizer.NOOP; + private McpAsyncHttpRequestCustomizer httpRequestCustomizer = McpAsyncHttpRequestCustomizer.NOOP; /** * Creates a new builder with the specified base URI. @@ -676,16 +676,16 @@ public Builder openConnectionOnStartup(boolean openConnectionOnStartup) { * executing them. *

* This overrides the customizer from - * {@link #asyncHttpRequestCustomizer(AsyncHttpRequestCustomizer)}. + * {@link #asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer)}. *

- * 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; } @@ -694,13 +694,13 @@ public Builder httpRequestCustomizer(SyncHttpRequestCustomizer syncHttpRequestCu * executing them. *

* This overrides the customizer from - * {@link #httpRequestCustomizer(SyncHttpRequestCustomizer)}. + * {@link #httpRequestCustomizer(McpSyncHttpRequestCustomizer)}. *

* 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; } diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/AsyncHttpRequestCustomizer.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/McpAsyncHttpRequestCustomizer.java similarity index 83% rename from mcp/src/main/java/io/modelcontextprotocol/client/transport/AsyncHttpRequestCustomizer.java rename to mcp/src/main/java/io/modelcontextprotocol/client/transport/McpAsyncHttpRequestCustomizer.java index dee026d96..50b2f323b 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/AsyncHttpRequestCustomizer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/McpAsyncHttpRequestCustomizer.java @@ -19,12 +19,12 @@ * * @author Daniel Garnier-Moiroux */ -public interface AsyncHttpRequestCustomizer { +public interface McpAsyncHttpRequestCustomizer { Publisher customize(HttpRequest.Builder builder, String method, URI endpoint, @Nullable String body); - AsyncHttpRequestCustomizer NOOP = new Noop(); + McpAsyncHttpRequestCustomizer NOOP = new Noop(); /** * Wrap a sync implementation in an async wrapper. @@ -32,14 +32,14 @@ Publisher customize(HttpRequest.Builder builder, String met * Do NOT wrap a blocking implementation for use in a non-blocking context. For a * blocking implementation, consider using {@link Schedulers#boundedElastic()}. */ - static AsyncHttpRequestCustomizer fromSync(SyncHttpRequestCustomizer customizer) { + static McpAsyncHttpRequestCustomizer fromSync(McpSyncHttpRequestCustomizer customizer) { return (builder, method, uri, body) -> Mono.fromSupplier(() -> { customizer.customize(builder, method, uri, body); return builder; }); } - class Noop implements AsyncHttpRequestCustomizer { + class Noop implements McpAsyncHttpRequestCustomizer { @Override public Publisher customize(HttpRequest.Builder builder, String method, URI endpoint, diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/SyncHttpRequestCustomizer.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/McpSyncHttpRequestCustomizer.java similarity index 90% rename from mcp/src/main/java/io/modelcontextprotocol/client/transport/SyncHttpRequestCustomizer.java rename to mcp/src/main/java/io/modelcontextprotocol/client/transport/McpSyncHttpRequestCustomizer.java index 72b6e6c1b..82bfa3542 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/SyncHttpRequestCustomizer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/McpSyncHttpRequestCustomizer.java @@ -14,7 +14,7 @@ * * @author Daniel Garnier-Moiroux */ -public interface SyncHttpRequestCustomizer { +public interface McpSyncHttpRequestCustomizer { void customize(HttpRequest.Builder builder, String method, URI endpoint, @Nullable String body); diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java index 46b9207f6..aaab86dd3 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java @@ -72,7 +72,7 @@ static class TestHttpClientSseClientTransport extends HttpClientSseClientTranspo public TestHttpClientSseClientTransport(final String baseUri) { super(HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build(), HttpRequest.newBuilder().header("Content-Type", "application/json"), baseUri, "/sse", - new ObjectMapper(), AsyncHttpRequestCustomizer.NOOP); + new ObjectMapper(), McpAsyncHttpRequestCustomizer.NOOP); } public int getInboundMessageCount() { @@ -389,7 +389,7 @@ void testChainedCustomizations() { @Test void testRequestCustomizer() { - var mockCustomizer = mock(SyncHttpRequestCustomizer.class); + var mockCustomizer = mock(McpSyncHttpRequestCustomizer.class); // Create a transport with the customizer var customizedTransport = HttpClientSseClientTransport.builder(host) @@ -423,7 +423,7 @@ void testRequestCustomizer() { @Test void testAsyncRequestCustomizer() { - var mockCustomizer = mock(AsyncHttpRequestCustomizer.class); + var mockCustomizer = mock(McpAsyncHttpRequestCustomizer.class); when(mockCustomizer.customize(any(), any(), any(), any())) .thenAnswer(invocation -> Mono.just(invocation.getArguments()[0])); diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java index 479468f63..4e95bea68 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java @@ -63,7 +63,7 @@ void withTransport(HttpClientStreamableHttpTransport transport, Consumer Mono.just(invocation.getArguments()[0])); From e00b44e0346cb1b60620dbe70bf140ecdec13852 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Tue, 5 Aug 2025 16:42:50 +0200 Subject: [PATCH 2/3] Move Mcp(Async)HttpRequestCustomizer from client.transport to client.transport.customizer --- .../client/transport/HttpClientSseClientTransport.java | 2 ++ .../client/transport/HttpClientStreamableHttpTransport.java | 2 ++ .../{ => customizer}/McpAsyncHttpRequestCustomizer.java | 2 +- .../{ => customizer}/McpSyncHttpRequestCustomizer.java | 2 +- .../client/transport/HttpClientSseClientTransportTests.java | 2 ++ .../client/transport/HttpClientStreamableHttpTransportTest.java | 2 ++ 6 files changed, 10 insertions(+), 2 deletions(-) rename mcp/src/main/java/io/modelcontextprotocol/client/transport/{ => customizer}/McpAsyncHttpRequestCustomizer.java (95%) rename mcp/src/main/java/io/modelcontextprotocol/client/transport/{ => customizer}/McpSyncHttpRequestCustomizer.java (88%) diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java index a800352a0..f78e8e97d 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java @@ -22,6 +22,8 @@ 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.McpError; diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java index 860426ded..515a816cb 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java @@ -25,6 +25,8 @@ 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; diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/McpAsyncHttpRequestCustomizer.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpAsyncHttpRequestCustomizer.java similarity index 95% rename from mcp/src/main/java/io/modelcontextprotocol/client/transport/McpAsyncHttpRequestCustomizer.java rename to mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpAsyncHttpRequestCustomizer.java index 50b2f323b..2f685c350 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/McpAsyncHttpRequestCustomizer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpAsyncHttpRequestCustomizer.java @@ -2,7 +2,7 @@ * Copyright 2024-2025 the original author or authors. */ -package io.modelcontextprotocol.client.transport; +package io.modelcontextprotocol.client.transport.customizer; import java.net.URI; import java.net.http.HttpRequest; diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/McpSyncHttpRequestCustomizer.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpSyncHttpRequestCustomizer.java similarity index 88% rename from mcp/src/main/java/io/modelcontextprotocol/client/transport/McpSyncHttpRequestCustomizer.java rename to mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpSyncHttpRequestCustomizer.java index 82bfa3542..8d2c4a698 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/McpSyncHttpRequestCustomizer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpSyncHttpRequestCustomizer.java @@ -2,7 +2,7 @@ * Copyright 2024-2025 the original author or authors. */ -package io.modelcontextprotocol.client.transport; +package io.modelcontextprotocol.client.transport.customizer; import java.net.URI; import java.net.http.HttpRequest; diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java index aaab86dd3..f5a5ecb12 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java @@ -15,6 +15,8 @@ import java.util.function.Function; import com.fasterxml.jackson.databind.ObjectMapper; +import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpRequestCustomizer; +import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.JSONRPCRequest; import org.junit.jupiter.api.AfterAll; diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java index 4e95bea68..91ff5eaeb 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java @@ -4,6 +4,8 @@ package io.modelcontextprotocol.client.transport; +import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpRequestCustomizer; +import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; import io.modelcontextprotocol.spec.McpSchema; import java.net.URI; import java.net.URISyntaxException; From dce4b6a94fc67a21ae56900d64a7bd93953e4a6f Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Tue, 5 Aug 2025 16:43:19 +0200 Subject: [PATCH 3/3] Introduce DelegatingMcp(Async)HttpRequestCustomizer --- ...legatingMcpAsyncHttpRequestCustomizer.java | 38 +++++++++++ ...elegatingMcpSyncHttpRequestCustomizer.java | 32 +++++++++ ...tingMcpAsyncHttpRequestCustomizerTest.java | 67 +++++++++++++++++++ ...atingMcpSyncHttpRequestCustomizerTest.java | 58 ++++++++++++++++ 4 files changed, 195 insertions(+) create mode 100644 mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizer.java create mode 100644 mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizer.java create mode 100644 mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizerTest.java create mode 100644 mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizerTest.java diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizer.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizer.java new file mode 100644 index 000000000..22ba6a265 --- /dev/null +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizer.java @@ -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 customizers; + + public DelegatingMcpAsyncHttpRequestCustomizer(List customizers) { + Assert.notNull(customizers, "Customizers must not be null"); + this.customizers = customizers; + } + + @Override + public Publisher 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; + } + +} diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizer.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizer.java new file mode 100644 index 000000000..65649d916 --- /dev/null +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizer.java @@ -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 delegates; + + public DelegatingMcpSyncHttpRequestCustomizer(List 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)); + } + +} diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizerTest.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizerTest.java new file mode 100644 index 000000000..f136cd65e --- /dev/null +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizerTest.java @@ -0,0 +1,67 @@ +/* + * Copyright 2024-2025 the original author or authors. + */ + +package io.modelcontextprotocol.client.transport.customizer; + +import java.net.URI; +import java.net.http.HttpRequest; +import java.util.List; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link DelegatingMcpAsyncHttpRequestCustomizer}. + * + * @author Daniel Garnier-Moiroux + */ +class DelegatingMcpAsyncHttpRequestCustomizerTest { + + private static final URI TEST_URI = URI.create("https://example.com"); + + private final HttpRequest.Builder TEST_BUILDER = HttpRequest.newBuilder(TEST_URI); + + @Test + void delegates() { + var mockCustomizer = mock(McpAsyncHttpRequestCustomizer.class); + when(mockCustomizer.customize(any(), any(), any(), any())) + .thenAnswer(invocation -> Mono.just(invocation.getArguments()[0])); + var customizer = new DelegatingMcpAsyncHttpRequestCustomizer(List.of(mockCustomizer)); + + StepVerifier.create(customizer.customize(TEST_BUILDER, "GET", TEST_URI, "{\"everybody\": \"needs somebody\"}")) + .expectNext(TEST_BUILDER) + .verifyComplete(); + + verify(mockCustomizer).customize(TEST_BUILDER, "GET", TEST_URI, "{\"everybody\": \"needs somebody\"}"); + } + + @Test + void delegatesInOrder() { + var customizer = new DelegatingMcpAsyncHttpRequestCustomizer( + List.of((builder, method, uri, body) -> Mono.just(builder.copy().header("x-test", "one")), + (builder, method, uri, body) -> Mono.just(builder.copy().header("x-test", "two")))); + + var headers = Mono + .from(customizer.customize(TEST_BUILDER, "GET", TEST_URI, "{\"everybody\": \"needs somebody\"}")) + .map(HttpRequest.Builder::build) + .map(HttpRequest::headers) + .flatMapIterable(h -> h.allValues("x-test")); + + StepVerifier.create(headers).expectNext("one").expectNext("two").verifyComplete(); + } + + @Test + void constructorRequiresNonNull() { + assertThatThrownBy(() -> new DelegatingMcpAsyncHttpRequestCustomizer(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Customizers must not be null"); + } + +} diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizerTest.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizerTest.java new file mode 100644 index 000000000..427472912 --- /dev/null +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizerTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024-2025 the original author or authors. + */ + +package io.modelcontextprotocol.client.transport.customizer; + +import java.net.URI; +import java.net.http.HttpRequest; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link DelegatingMcpSyncHttpRequestCustomizer}. + * + * @author Daniel Garnier-Moiroux + */ +class DelegatingMcpSyncHttpRequestCustomizerTest { + + private static final URI TEST_URI = URI.create("https://example.com"); + + private final HttpRequest.Builder TEST_BUILDER = HttpRequest.newBuilder(TEST_URI); + + @Test + void delegates() { + var mockCustomizer = Mockito.mock(McpSyncHttpRequestCustomizer.class); + var customizer = new DelegatingMcpSyncHttpRequestCustomizer(List.of(mockCustomizer)); + + customizer.customize(TEST_BUILDER, "GET", TEST_URI, "{\"everybody\": \"needs somebody\"}"); + + verify(mockCustomizer).customize(TEST_BUILDER, "GET", TEST_URI, "{\"everybody\": \"needs somebody\"}"); + } + + @Test + void delegatesInOrder() { + var testHeaderName = "x-test"; + var customizer = new DelegatingMcpSyncHttpRequestCustomizer( + List.of((builder, method, uri, body) -> builder.header(testHeaderName, "one"), + (builder, method, uri, body) -> builder.header(testHeaderName, "two"))); + + customizer.customize(TEST_BUILDER, "GET", TEST_URI, ""); + var request = TEST_BUILDER.build(); + + assertThat(request.headers().allValues(testHeaderName)).containsExactly("one", "two"); + } + + @Test + void constructorRequiresNonNull() { + assertThatThrownBy(() -> new DelegatingMcpAsyncHttpRequestCustomizer(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Customizers must not be null"); + } + +}