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:
parent
b4fef5241f
commit
95ba8e77ec
11 changed files with 238 additions and 34 deletions
|
|
@ -22,6 +22,8 @@ import org.slf4j.LoggerFactory;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
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.client.transport.ResponseSubscribers.ResponseEvent;
|
||||||
import io.modelcontextprotocol.spec.McpClientTransport;
|
import io.modelcontextprotocol.spec.McpClientTransport;
|
||||||
import io.modelcontextprotocol.spec.McpSchema;
|
import io.modelcontextprotocol.spec.McpSchema;
|
||||||
|
|
@ -112,7 +114,7 @@ public class HttpClientSseClientTransport implements McpClientTransport {
|
||||||
/**
|
/**
|
||||||
* Customizer to modify requests before they are executed.
|
* 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.
|
* Creates a new transport instance with default HTTP client and object mapper.
|
||||||
|
|
@ -186,7 +188,7 @@ public class HttpClientSseClientTransport implements McpClientTransport {
|
||||||
@Deprecated(forRemoval = true)
|
@Deprecated(forRemoval = true)
|
||||||
HttpClientSseClientTransport(HttpClient httpClient, HttpRequest.Builder requestBuilder, String baseUri,
|
HttpClientSseClientTransport(HttpClient httpClient, HttpRequest.Builder requestBuilder, String baseUri,
|
||||||
String sseEndpoint, ObjectMapper objectMapper) {
|
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
|
* @throws IllegalArgumentException if objectMapper, clientBuilder, or headers is null
|
||||||
*/
|
*/
|
||||||
HttpClientSseClientTransport(HttpClient httpClient, HttpRequest.Builder requestBuilder, String baseUri,
|
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.notNull(objectMapper, "ObjectMapper must not be null");
|
||||||
Assert.hasText(baseUri, "baseUri must not be empty");
|
Assert.hasText(baseUri, "baseUri must not be empty");
|
||||||
Assert.hasText(sseEndpoint, "sseEndpoint 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 HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
|
||||||
|
|
||||||
private AsyncHttpRequestCustomizer httpRequestCustomizer = AsyncHttpRequestCustomizer.NOOP;
|
private McpAsyncHttpRequestCustomizer httpRequestCustomizer = McpAsyncHttpRequestCustomizer.NOOP;
|
||||||
|
|
||||||
private Duration connectTimeout = Duration.ofSeconds(10);
|
private Duration connectTimeout = Duration.ofSeconds(10);
|
||||||
|
|
||||||
|
|
@ -352,16 +354,16 @@ public class HttpClientSseClientTransport implements McpClientTransport {
|
||||||
* executing them.
|
* executing them.
|
||||||
* <p>
|
* <p>
|
||||||
* This overrides the customizer from
|
* This overrides the customizer from
|
||||||
* {@link #asyncHttpRequestCustomizer(AsyncHttpRequestCustomizer)}.
|
* {@link #asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer)}.
|
||||||
* <p>
|
* <p>
|
||||||
* Do NOT use a blocking {@link SyncHttpRequestCustomizer} in a non-blocking
|
* Do NOT use a blocking {@link McpSyncHttpRequestCustomizer} in a non-blocking
|
||||||
* context. Use {@link #asyncHttpRequestCustomizer(AsyncHttpRequestCustomizer)}
|
* context. Use {@link #asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer)}
|
||||||
* instead.
|
* instead.
|
||||||
* @param syncHttpRequestCustomizer the request customizer
|
* @param syncHttpRequestCustomizer the request customizer
|
||||||
* @return this builder
|
* @return this builder
|
||||||
*/
|
*/
|
||||||
public Builder httpRequestCustomizer(SyncHttpRequestCustomizer syncHttpRequestCustomizer) {
|
public Builder httpRequestCustomizer(McpSyncHttpRequestCustomizer syncHttpRequestCustomizer) {
|
||||||
this.httpRequestCustomizer = AsyncHttpRequestCustomizer.fromSync(syncHttpRequestCustomizer);
|
this.httpRequestCustomizer = McpAsyncHttpRequestCustomizer.fromSync(syncHttpRequestCustomizer);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -370,13 +372,13 @@ public class HttpClientSseClientTransport implements McpClientTransport {
|
||||||
* executing them.
|
* executing them.
|
||||||
* <p>
|
* <p>
|
||||||
* This overrides the customizer from
|
* This overrides the customizer from
|
||||||
* {@link #httpRequestCustomizer(SyncHttpRequestCustomizer)}.
|
* {@link #httpRequestCustomizer(McpSyncHttpRequestCustomizer)}.
|
||||||
* <p>
|
* <p>
|
||||||
* Do NOT use a blocking implementation in a non-blocking context.
|
* Do NOT use a blocking implementation in a non-blocking context.
|
||||||
* @param asyncHttpRequestCustomizer the request customizer
|
* @param asyncHttpRequestCustomizer the request customizer
|
||||||
* @return this builder
|
* @return this builder
|
||||||
*/
|
*/
|
||||||
public Builder asyncHttpRequestCustomizer(AsyncHttpRequestCustomizer asyncHttpRequestCustomizer) {
|
public Builder asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer asyncHttpRequestCustomizer) {
|
||||||
this.httpRequestCustomizer = asyncHttpRequestCustomizer;
|
this.httpRequestCustomizer = asyncHttpRequestCustomizer;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ import org.slf4j.LoggerFactory;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
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.client.transport.ResponseSubscribers.ResponseEvent;
|
||||||
import io.modelcontextprotocol.spec.DefaultMcpTransportSession;
|
import io.modelcontextprotocol.spec.DefaultMcpTransportSession;
|
||||||
import io.modelcontextprotocol.spec.DefaultMcpTransportStream;
|
import io.modelcontextprotocol.spec.DefaultMcpTransportStream;
|
||||||
|
|
@ -113,7 +115,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
|
||||||
|
|
||||||
private final boolean resumableStreams;
|
private final boolean resumableStreams;
|
||||||
|
|
||||||
private final AsyncHttpRequestCustomizer httpRequestCustomizer;
|
private final McpAsyncHttpRequestCustomizer httpRequestCustomizer;
|
||||||
|
|
||||||
private final AtomicReference<DefaultMcpTransportSession> activeSession = new AtomicReference<>();
|
private final AtomicReference<DefaultMcpTransportSession> activeSession = new AtomicReference<>();
|
||||||
|
|
||||||
|
|
@ -123,7 +125,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
|
||||||
|
|
||||||
private HttpClientStreamableHttpTransport(ObjectMapper objectMapper, HttpClient httpClient,
|
private HttpClientStreamableHttpTransport(ObjectMapper objectMapper, HttpClient httpClient,
|
||||||
HttpRequest.Builder requestBuilder, String baseUri, String endpoint, boolean resumableStreams,
|
HttpRequest.Builder requestBuilder, String baseUri, String endpoint, boolean resumableStreams,
|
||||||
boolean openConnectionOnStartup, AsyncHttpRequestCustomizer httpRequestCustomizer) {
|
boolean openConnectionOnStartup, McpAsyncHttpRequestCustomizer httpRequestCustomizer) {
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
this.httpClient = httpClient;
|
this.httpClient = httpClient;
|
||||||
this.requestBuilder = requestBuilder;
|
this.requestBuilder = requestBuilder;
|
||||||
|
|
@ -598,7 +600,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
|
||||||
|
|
||||||
private HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
|
private HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
|
||||||
|
|
||||||
private AsyncHttpRequestCustomizer httpRequestCustomizer = AsyncHttpRequestCustomizer.NOOP;
|
private McpAsyncHttpRequestCustomizer httpRequestCustomizer = McpAsyncHttpRequestCustomizer.NOOP;
|
||||||
|
|
||||||
private Duration connectTimeout = Duration.ofSeconds(10);
|
private Duration connectTimeout = Duration.ofSeconds(10);
|
||||||
|
|
||||||
|
|
@ -709,16 +711,16 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
|
||||||
* executing them.
|
* executing them.
|
||||||
* <p>
|
* <p>
|
||||||
* This overrides the customizer from
|
* This overrides the customizer from
|
||||||
* {@link #asyncHttpRequestCustomizer(AsyncHttpRequestCustomizer)}.
|
* {@link #asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer)}.
|
||||||
* <p>
|
* <p>
|
||||||
* Do NOT use a blocking {@link SyncHttpRequestCustomizer} in a non-blocking
|
* Do NOT use a blocking {@link McpSyncHttpRequestCustomizer} in a non-blocking
|
||||||
* context. Use {@link #asyncHttpRequestCustomizer(AsyncHttpRequestCustomizer)}
|
* context. Use {@link #asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer)}
|
||||||
* instead.
|
* instead.
|
||||||
* @param syncHttpRequestCustomizer the request customizer
|
* @param syncHttpRequestCustomizer the request customizer
|
||||||
* @return this builder
|
* @return this builder
|
||||||
*/
|
*/
|
||||||
public Builder httpRequestCustomizer(SyncHttpRequestCustomizer syncHttpRequestCustomizer) {
|
public Builder httpRequestCustomizer(McpSyncHttpRequestCustomizer syncHttpRequestCustomizer) {
|
||||||
this.httpRequestCustomizer = AsyncHttpRequestCustomizer.fromSync(syncHttpRequestCustomizer);
|
this.httpRequestCustomizer = McpAsyncHttpRequestCustomizer.fromSync(syncHttpRequestCustomizer);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -727,13 +729,13 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
|
||||||
* executing them.
|
* executing them.
|
||||||
* <p>
|
* <p>
|
||||||
* This overrides the customizer from
|
* This overrides the customizer from
|
||||||
* {@link #httpRequestCustomizer(SyncHttpRequestCustomizer)}.
|
* {@link #httpRequestCustomizer(McpSyncHttpRequestCustomizer)}.
|
||||||
* <p>
|
* <p>
|
||||||
* Do NOT use a blocking implementation in a non-blocking context.
|
* Do NOT use a blocking implementation in a non-blocking context.
|
||||||
* @param asyncHttpRequestCustomizer the request customizer
|
* @param asyncHttpRequestCustomizer the request customizer
|
||||||
* @return this builder
|
* @return this builder
|
||||||
*/
|
*/
|
||||||
public Builder asyncHttpRequestCustomizer(AsyncHttpRequestCustomizer asyncHttpRequestCustomizer) {
|
public Builder asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer asyncHttpRequestCustomizer) {
|
||||||
this.httpRequestCustomizer = asyncHttpRequestCustomizer;
|
this.httpRequestCustomizer = asyncHttpRequestCustomizer;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,8 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
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;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.JSONRPCRequest;
|
import io.modelcontextprotocol.spec.McpSchema.JSONRPCRequest;
|
||||||
import org.junit.jupiter.api.AfterAll;
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
|
@ -72,7 +74,7 @@ class HttpClientSseClientTransportTests {
|
||||||
public TestHttpClientSseClientTransport(final String baseUri) {
|
public TestHttpClientSseClientTransport(final String baseUri) {
|
||||||
super(HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build(),
|
super(HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build(),
|
||||||
HttpRequest.newBuilder().header("Content-Type", "application/json"), baseUri, "/sse",
|
HttpRequest.newBuilder().header("Content-Type", "application/json"), baseUri, "/sse",
|
||||||
new ObjectMapper(), AsyncHttpRequestCustomizer.NOOP);
|
new ObjectMapper(), McpAsyncHttpRequestCustomizer.NOOP);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getInboundMessageCount() {
|
public int getInboundMessageCount() {
|
||||||
|
|
@ -389,7 +391,7 @@ class HttpClientSseClientTransportTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRequestCustomizer() {
|
void testRequestCustomizer() {
|
||||||
var mockCustomizer = mock(SyncHttpRequestCustomizer.class);
|
var mockCustomizer = mock(McpSyncHttpRequestCustomizer.class);
|
||||||
|
|
||||||
// Create a transport with the customizer
|
// Create a transport with the customizer
|
||||||
var customizedTransport = HttpClientSseClientTransport.builder(host)
|
var customizedTransport = HttpClientSseClientTransport.builder(host)
|
||||||
|
|
@ -423,7 +425,7 @@ class HttpClientSseClientTransportTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testAsyncRequestCustomizer() {
|
void testAsyncRequestCustomizer() {
|
||||||
var mockCustomizer = mock(AsyncHttpRequestCustomizer.class);
|
var mockCustomizer = mock(McpAsyncHttpRequestCustomizer.class);
|
||||||
when(mockCustomizer.customize(any(), any(), any(), any()))
|
when(mockCustomizer.customize(any(), any(), any(), any()))
|
||||||
.thenAnswer(invocation -> Mono.just(invocation.getArguments()[0]));
|
.thenAnswer(invocation -> Mono.just(invocation.getArguments()[0]));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import org.junit.jupiter.api.Timeout;
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpServer;
|
import com.sun.net.httpserver.HttpServer;
|
||||||
|
|
||||||
|
import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer;
|
||||||
import io.modelcontextprotocol.server.transport.TomcatTestUtil;
|
import io.modelcontextprotocol.server.transport.TomcatTestUtil;
|
||||||
import io.modelcontextprotocol.spec.McpSchema;
|
import io.modelcontextprotocol.spec.McpSchema;
|
||||||
import io.modelcontextprotocol.spec.ProtocolVersions;
|
import io.modelcontextprotocol.spec.ProtocolVersions;
|
||||||
|
|
@ -70,7 +71,7 @@ public class HttpClientStreamableHttpTransportEmptyJsonResponseTest {
|
||||||
void testNotificationInitialized() throws URISyntaxException {
|
void testNotificationInitialized() throws URISyntaxException {
|
||||||
|
|
||||||
var uri = new URI(host + "/mcp");
|
var uri = new URI(host + "/mcp");
|
||||||
var mockRequestCustomizer = mock(SyncHttpRequestCustomizer.class);
|
var mockRequestCustomizer = mock(McpSyncHttpRequestCustomizer.class);
|
||||||
var transport = HttpClientStreamableHttpTransport.builder(host)
|
var transport = HttpClientStreamableHttpTransport.builder(host)
|
||||||
.httpRequestCustomizer(mockRequestCustomizer)
|
.httpRequestCustomizer(mockRequestCustomizer)
|
||||||
.build();
|
.build();
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
package io.modelcontextprotocol.client.transport;
|
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 io.modelcontextprotocol.spec.McpSchema;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
|
@ -63,7 +65,7 @@ class HttpClientStreamableHttpTransportTest {
|
||||||
@Test
|
@Test
|
||||||
void testRequestCustomizer() throws URISyntaxException {
|
void testRequestCustomizer() throws URISyntaxException {
|
||||||
var uri = new URI(host + "/mcp");
|
var uri = new URI(host + "/mcp");
|
||||||
var mockRequestCustomizer = mock(SyncHttpRequestCustomizer.class);
|
var mockRequestCustomizer = mock(McpSyncHttpRequestCustomizer.class);
|
||||||
|
|
||||||
var transport = HttpClientStreamableHttpTransport.builder(host)
|
var transport = HttpClientStreamableHttpTransport.builder(host)
|
||||||
.httpRequestCustomizer(mockRequestCustomizer)
|
.httpRequestCustomizer(mockRequestCustomizer)
|
||||||
|
|
@ -88,7 +90,7 @@ class HttpClientStreamableHttpTransportTest {
|
||||||
@Test
|
@Test
|
||||||
void testAsyncRequestCustomizer() throws URISyntaxException {
|
void testAsyncRequestCustomizer() throws URISyntaxException {
|
||||||
var uri = new URI(host + "/mcp");
|
var uri = new URI(host + "/mcp");
|
||||||
var mockRequestCustomizer = mock(AsyncHttpRequestCustomizer.class);
|
var mockRequestCustomizer = mock(McpAsyncHttpRequestCustomizer.class);
|
||||||
when(mockRequestCustomizer.customize(any(), any(), any(), any()))
|
when(mockRequestCustomizer.customize(any(), any(), any(), any()))
|
||||||
.thenAnswer(invocation -> Mono.just(invocation.getArguments()[0]));
|
.thenAnswer(invocation -> Mono.just(invocation.getArguments()[0]));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue