feat(transport): Add customizable HTTP request builder support (#86)

Enhances FlowSseClient and HttpClientSseClientTransport to accept a custom HttpRequest.Builder,
allowing for greater flexibility when configuring HTTP requests.
This enables clients to customize headers, timeouts, and other request properties across
all SSE connections and message sending operations.

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
This commit is contained in:
Renxia Wang 2025-03-29 20:49:51 -04:00 • committed by Christian Tzolov
commit eb8e3744a7
2 changed files with 50 additions and 6 deletions

View file

@ -39,6 +39,8 @@ public class FlowSseClient {
private final HttpClient httpClient;
private final HttpRequest.Builder requestBuilder;
/**
* Pattern to extract the data content from SSE data field lines. Matches lines
* starting with "data:" and captures the remaining content.
@ -92,7 +94,17 @@ public class FlowSseClient {
* @param httpClient the {@link HttpClient} instance to use for SSE connections
*/
public FlowSseClient(HttpClient httpClient) {
this(httpClient, HttpRequest.newBuilder());
}
/**
* Creates a new FlowSseClient with the specified HTTP client and request builder.
* @param httpClient the {@link HttpClient} instance to use for SSE connections
* @param requestBuilder the {@link HttpRequest.Builder} to use for SSE requests
*/
public FlowSseClient(HttpClient httpClient, HttpRequest.Builder requestBuilder) {
this.httpClient = httpClient;
this.requestBuilder = requestBuilder;
}
/**
@ -109,8 +121,7 @@ public class FlowSseClient {
* @throws RuntimeException if the connection fails with a non-200 status code
*/
public void subscribe(String url, SseEventHandler eventHandler) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
HttpRequest request = this.requestBuilder.uri(URI.create(url))
.header("Accept", "text/event-stream")
.header("Cache-Control", "no-cache")
.GET()

View file

@ -82,6 +82,9 @@ public class HttpClientSseClientTransport implements McpClientTransport {
*/
private final HttpClient httpClient;
/** HTTP request builder for building requests to send messages to the server */
private final HttpRequest.Builder requestBuilder;
/** JSON object mapper for message serialization/deserialization */
protected ObjectMapper objectMapper;
@ -126,15 +129,33 @@ public class HttpClientSseClientTransport implements McpClientTransport {
*/
public HttpClientSseClientTransport(HttpClient.Builder clientBuilder, String baseUri, String sseEndpoint,
ObjectMapper objectMapper) {
this(clientBuilder, HttpRequest.newBuilder(), baseUri, sseEndpoint, objectMapper);
}
/**
* Creates a new transport instance with custom HTTP client builder, object mapper,
* and headers.
* @param clientBuilder the HTTP client builder to use
* @param requestBuilder the HTTP request builder to use
* @param baseUri the base URI of the MCP server
* @param sseEndpoint the SSE endpoint path
* @param objectMapper the object mapper for JSON serialization/deserialization
* @throws IllegalArgumentException if objectMapper, clientBuilder, or headers is null
*/
public HttpClientSseClientTransport(HttpClient.Builder clientBuilder, HttpRequest.Builder requestBuilder,
String baseUri, String sseEndpoint, ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
Assert.hasText(baseUri, "baseUri must not be empty");
Assert.hasText(sseEndpoint, "sseEndpoint must not be empty");
Assert.notNull(clientBuilder, "clientBuilder must not be null");
Assert.notNull(requestBuilder, "requestBuilder must not be null");
this.baseUri = baseUri;
this.sseEndpoint = sseEndpoint;
this.objectMapper = objectMapper;
this.httpClient = clientBuilder.connectTimeout(Duration.ofSeconds(10)).build();
this.sseClient = new FlowSseClient(this.httpClient);
this.requestBuilder = requestBuilder;
this.sseClient = new FlowSseClient(this.httpClient, requestBuilder);
}
/**
@ -159,6 +180,8 @@ public class HttpClientSseClientTransport implements McpClientTransport {
private ObjectMapper objectMapper = new ObjectMapper();
private HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
/**
* Creates a new builder with the specified base URI.
* @param baseUri the base URI of the MCP server
@ -190,6 +213,17 @@ public class HttpClientSseClientTransport implements McpClientTransport {
return this;
}
/**
* Sets the HTTP request builder.
* @param requestBuilder the HTTP request builder
* @return this builder
*/
public Builder requestBuilder(HttpRequest.Builder requestBuilder) {
Assert.notNull(requestBuilder, "requestBuilder must not be null");
this.requestBuilder = requestBuilder;
return this;
}
/**
* Sets the object mapper for JSON serialization/deserialization.
* @param objectMapper the object mapper
@ -206,7 +240,7 @@ public class HttpClientSseClientTransport implements McpClientTransport {
* @return a new transport instance
*/
public HttpClientSseClientTransport build() {
return new HttpClientSseClientTransport(clientBuilder, baseUri, sseEndpoint, objectMapper);
return new HttpClientSseClientTransport(clientBuilder, requestBuilder, baseUri, sseEndpoint, objectMapper);
}
}
@ -301,8 +335,7 @@ public class HttpClientSseClientTransport implements McpClientTransport {
try {
String jsonText = this.objectMapper.writeValueAsString(message);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(this.baseUri + endpoint))
HttpRequest request = this.requestBuilder.uri(URI.create(this.baseUri + endpoint))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonText))
.build();