From eb8e3744a7903932ab02982e506d6a75e79fe3ab Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sat, 29 Mar 2025 20:49:51 -0400 Subject: [PATCH] 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 --- .../client/transport/FlowSseClient.java | 15 ++++++- .../HttpClientSseClientTransport.java | 41 +++++++++++++++++-- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/FlowSseClient.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/FlowSseClient.java index 7fc6799..50af35c 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/FlowSseClient.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/FlowSseClient.java @@ -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() 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 696efdf..0b48253 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java @@ -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();