Skip to content

Rename (Async)HttpRequestCustomizer to Mcp(Async)HttpRequestCustomizer, add delegating implementation #453

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -187,7 +189,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);
}

/**
Expand All @@ -203,7 +205,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");
Expand Down Expand Up @@ -250,7 +252,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.
Expand Down Expand Up @@ -354,16 +356,16 @@ public Builder objectMapper(ObjectMapper objectMapper) {
* 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;
}

Expand All @@ -372,13 +374,13 @@ public Builder httpRequestCustomizer(SyncHttpRequestCustomizer syncHttpRequestCu
* 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<>();

Expand All @@ -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;
Expand Down Expand Up @@ -567,7 +569,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.
Expand Down Expand Up @@ -676,16 +678,16 @@ public Builder openConnectionOnStartup(boolean openConnectionOnStartup) {
* 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;
}

Expand All @@ -694,13 +696,13 @@ public Builder httpRequestCustomizer(SyncHttpRequestCustomizer syncHttpRequestCu
* 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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't think of a more efficient way to apply async folding of this. Using reduceWith operator would still have to accumulate using flatMap and would add another operator to the mix, so the loop is fine IMO.

result = result.flatMap(b -> Mono.from(customizer.customize(b, method, endpoint, body)));
}
return result;
}

}
Original file line number Diff line number Diff line change
@@ -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));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,27 +19,27 @@
*
* @author Daniel Garnier-Moiroux
*/
public interface AsyncHttpRequestCustomizer {
public interface McpAsyncHttpRequestCustomizer {

Publisher<HttpRequest.Builder> 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.
* <p>
* 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<HttpRequest.Builder> customize(HttpRequest.Builder builder, String method, URI endpoint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -72,7 +74,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() {
Expand Down Expand Up @@ -389,7 +391,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)
Expand Down Expand Up @@ -423,7 +425,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]));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -63,7 +65,7 @@ void withTransport(HttpClientStreamableHttpTransport transport, Consumer<HttpCli
@Test
void testRequestCustomizer() throws URISyntaxException {
var uri = new URI(host + "/mcp");
var mockRequestCustomizer = mock(SyncHttpRequestCustomizer.class);
var mockRequestCustomizer = mock(McpSyncHttpRequestCustomizer.class);

var transport = HttpClientStreamableHttpTransport.builder(host)
.httpRequestCustomizer(mockRequestCustomizer)
Expand All @@ -88,7 +90,7 @@ void testRequestCustomizer() throws URISyntaxException {
@Test
void testAsyncRequestCustomizer() throws URISyntaxException {
var uri = new URI(host + "/mcp");
var mockRequestCustomizer = mock(AsyncHttpRequestCustomizer.class);
var mockRequestCustomizer = mock(McpAsyncHttpRequestCustomizer.class);
when(mockRequestCustomizer.customize(any(), any(), any(), any()))
.thenAnswer(invocation -> Mono.just(invocation.getArguments()[0]));

Expand Down
Loading