Skip to content

Commit fab434c

Browse files
Aliaksietzolov
andcommitted
refactor(client): enhance HttpClientSseClientTransport with flexible customization API (#117)
- Add builder customizeClient() and customizeRequest() methods - Enable HTTP client and request configuration through consumer-based customization - Deprecate direct constructors in favor of the more flexible builder approach - Add test coverage for customization capabilities Co-authored-by: Christian Tzolov <[email protected]> Signed-off-by: Christian Tzolov <[email protected]>
1 parent fbea833 commit fab434c

File tree

5 files changed

+185
-22
lines changed

5 files changed

+185
-22
lines changed

Diff for: mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseIntegrationTests.java

+5-7
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
import static org.awaitility.Awaitility.await;
4545
import static org.mockito.Mockito.mock;
4646

47-
public class WebMvcSseIntegrationTests {
47+
class WebMvcSseIntegrationTests {
4848

4949
private static final int PORT = 8183;
5050

@@ -79,13 +79,13 @@ public void before() {
7979

8080
try {
8181
tomcatServer.tomcat().start();
82-
assertThat(tomcatServer.tomcat().getServer().getState() == LifecycleState.STARTED);
82+
assertThat(tomcatServer.tomcat().getServer().getState()).isEqualTo(LifecycleState.STARTED);
8383
}
8484
catch (Exception e) {
8585
throw new RuntimeException("Failed to start Tomcat", e);
8686
}
8787

88-
clientBuilder = McpClient.sync(new HttpClientSseClientTransport("http://localhost:" + PORT));
88+
clientBuilder = McpClient.sync(HttpClientSseClientTransport.builder("http://localhost:" + PORT).build());
8989

9090
// Get the transport from Spring context
9191
mcpServerTransportProvider = tomcatServer.appContext().getBean(WebMvcSseServerTransportProvider.class);
@@ -200,8 +200,7 @@ void testCreateMessageSuccess() throws InterruptedException {
200200

201201
CallToolResult response = mcpClient.callTool(new McpSchema.CallToolRequest("tool1", Map.of()));
202202

203-
assertThat(response).isNotNull();
204-
assertThat(response).isEqualTo(callResponse);
203+
assertThat(response).isNotNull().isEqualTo(callResponse);
205204

206205
mcpClient.close();
207206
mcpServer.close();
@@ -410,8 +409,7 @@ void testToolCallSuccess() {
410409

411410
CallToolResult response = mcpClient.callTool(new McpSchema.CallToolRequest("tool1", Map.of()));
412411

413-
assertThat(response).isNotNull();
414-
assertThat(response).isEqualTo(callResponse);
412+
assertThat(response).isNotNull().isEqualTo(callResponse);
415413

416414
mcpClient.close();
417415
mcpServer.close();

Diff for: mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java

+84-8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.util.concurrent.CountDownLatch;
1414
import java.util.concurrent.TimeUnit;
1515
import java.util.concurrent.atomic.AtomicReference;
16+
import java.util.function.Consumer;
1617
import java.util.function.Function;
1718

1819
import com.fasterxml.jackson.core.type.TypeReference;
@@ -103,7 +104,10 @@ public class HttpClientSseClientTransport implements McpClientTransport {
103104
/**
104105
* Creates a new transport instance with default HTTP client and object mapper.
105106
* @param baseUri the base URI of the MCP server
107+
* @deprecated Use {@link HttpClientSseClientTransport#builder(String)} instead. This
108+
* constructor will be removed in future versions.
106109
*/
110+
@Deprecated(forRemoval = true)
107111
public HttpClientSseClientTransport(String baseUri) {
108112
this(HttpClient.newBuilder(), baseUri, new ObjectMapper());
109113
}
@@ -114,7 +118,10 @@ public HttpClientSseClientTransport(String baseUri) {
114118
* @param baseUri the base URI of the MCP server
115119
* @param objectMapper the object mapper for JSON serialization/deserialization
116120
* @throws IllegalArgumentException if objectMapper or clientBuilder is null
121+
* @deprecated Use {@link HttpClientSseClientTransport#builder(String)} instead. This
122+
* constructor will be removed in future versions.
117123
*/
124+
@Deprecated(forRemoval = true)
118125
public HttpClientSseClientTransport(HttpClient.Builder clientBuilder, String baseUri, ObjectMapper objectMapper) {
119126
this(clientBuilder, baseUri, DEFAULT_SSE_ENDPOINT, objectMapper);
120127
}
@@ -126,7 +133,10 @@ public HttpClientSseClientTransport(HttpClient.Builder clientBuilder, String bas
126133
* @param sseEndpoint the SSE endpoint path
127134
* @param objectMapper the object mapper for JSON serialization/deserialization
128135
* @throws IllegalArgumentException if objectMapper or clientBuilder is null
136+
* @deprecated Use {@link HttpClientSseClientTransport#builder(String)} instead. This
137+
* constructor will be removed in future versions.
129138
*/
139+
@Deprecated(forRemoval = true)
130140
public HttpClientSseClientTransport(HttpClient.Builder clientBuilder, String baseUri, String sseEndpoint,
131141
ObjectMapper objectMapper) {
132142
this(clientBuilder, HttpRequest.newBuilder(), baseUri, sseEndpoint, objectMapper);
@@ -141,18 +151,37 @@ public HttpClientSseClientTransport(HttpClient.Builder clientBuilder, String bas
141151
* @param sseEndpoint the SSE endpoint path
142152
* @param objectMapper the object mapper for JSON serialization/deserialization
143153
* @throws IllegalArgumentException if objectMapper, clientBuilder, or headers is null
154+
* @deprecated Use {@link HttpClientSseClientTransport#builder(String)} instead. This
155+
* constructor will be removed in future versions.
144156
*/
157+
@Deprecated(forRemoval = true)
145158
public HttpClientSseClientTransport(HttpClient.Builder clientBuilder, HttpRequest.Builder requestBuilder,
146159
String baseUri, String sseEndpoint, ObjectMapper objectMapper) {
160+
this(clientBuilder.connectTimeout(Duration.ofSeconds(10)).build(), requestBuilder, baseUri, sseEndpoint,
161+
objectMapper);
162+
}
163+
164+
/**
165+
* Creates a new transport instance with custom HTTP client builder, object mapper,
166+
* and headers.
167+
* @param httpClient the HTTP client to use
168+
* @param requestBuilder the HTTP request builder to use
169+
* @param baseUri the base URI of the MCP server
170+
* @param sseEndpoint the SSE endpoint path
171+
* @param objectMapper the object mapper for JSON serialization/deserialization
172+
* @throws IllegalArgumentException if objectMapper, clientBuilder, or headers is null
173+
*/
174+
HttpClientSseClientTransport(HttpClient httpClient, HttpRequest.Builder requestBuilder, String baseUri,
175+
String sseEndpoint, ObjectMapper objectMapper) {
147176
Assert.notNull(objectMapper, "ObjectMapper must not be null");
148177
Assert.hasText(baseUri, "baseUri must not be empty");
149178
Assert.hasText(sseEndpoint, "sseEndpoint must not be empty");
150-
Assert.notNull(clientBuilder, "clientBuilder must not be null");
179+
Assert.notNull(httpClient, "httpClient must not be null");
151180
Assert.notNull(requestBuilder, "requestBuilder must not be null");
152181
this.baseUri = baseUri;
153182
this.sseEndpoint = sseEndpoint;
154183
this.objectMapper = objectMapper;
155-
this.httpClient = clientBuilder.connectTimeout(Duration.ofSeconds(10)).build();
184+
this.httpClient = httpClient;
156185
this.requestBuilder = requestBuilder;
157186

158187
this.sseClient = new FlowSseClient(this.httpClient, requestBuilder);
@@ -164,33 +193,58 @@ public HttpClientSseClientTransport(HttpClient.Builder clientBuilder, HttpReques
164193
* @return a new builder instance
165194
*/
166195
public static Builder builder(String baseUri) {
167-
return new Builder(baseUri);
196+
return new Builder().baseUri(baseUri);
168197
}
169198

170199
/**
171200
* Builder for {@link HttpClientSseClientTransport}.
172201
*/
173202
public static class Builder {
174203

175-
private final String baseUri;
204+
private String baseUri;
176205

177206
private String sseEndpoint = DEFAULT_SSE_ENDPOINT;
178207

179-
private HttpClient.Builder clientBuilder = HttpClient.newBuilder();
208+
private HttpClient.Builder clientBuilder = HttpClient.newBuilder()
209+
.version(HttpClient.Version.HTTP_1_1)
210+
.connectTimeout(Duration.ofSeconds(10));
180211

181212
private ObjectMapper objectMapper = new ObjectMapper();
182213

183-
private HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
214+
private HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
215+
.header("Content-Type", "application/json");
216+
217+
/**
218+
* Creates a new builder instance.
219+
*/
220+
Builder() {
221+
// Default constructor
222+
}
184223

185224
/**
186225
* Creates a new builder with the specified base URI.
187226
* @param baseUri the base URI of the MCP server
227+
* @deprecated Use {@link HttpClientSseClientTransport#builder(String)} instead.
228+
* This constructor is deprecated and will be removed or made {@code protected} or
229+
* {@code private} in a future release.
188230
*/
231+
@Deprecated(forRemoval = true)
189232
public Builder(String baseUri) {
190233
Assert.hasText(baseUri, "baseUri must not be empty");
191234
this.baseUri = baseUri;
192235
}
193236

237+
/**
238+
* Sets the base URI.
239+
* @param baseUri the base URI
240+
* @return this builder
241+
*/
242+
Builder baseUri(String baseUri) {
243+
Assert.hasText(baseUri, "baseUri must not be empty");
244+
this.baseUri = baseUri;
245+
return this;
246+
}
247+
194248
/**
195249
* Sets the SSE endpoint path.
196250
* @param sseEndpoint the SSE endpoint path
@@ -213,6 +267,17 @@ public Builder clientBuilder(HttpClient.Builder clientBuilder) {
213267
return this;
214268
}
215269

270+
/**
271+
* Customizes the HTTP client builder.
272+
* @param clientCustomizer the consumer to customize the HTTP client builder
273+
* @return this builder
274+
*/
275+
public Builder customizeClient(final Consumer<HttpClient.Builder> clientCustomizer) {
276+
Assert.notNull(clientCustomizer, "clientCustomizer must not be null");
277+
clientCustomizer.accept(clientBuilder);
278+
return this;
279+
}
280+
216281
/**
217282
* Sets the HTTP request builder.
218283
* @param requestBuilder the HTTP request builder
@@ -224,6 +289,17 @@ public Builder requestBuilder(HttpRequest.Builder requestBuilder) {
224289
return this;
225290
}
226291

292+
/**
293+
* Customizes the HTTP client builder.
294+
* @param requestCustomizer the consumer to customize the HTTP request builder
295+
* @return this builder
296+
*/
297+
public Builder customizeRequest(final Consumer<HttpRequest.Builder> requestCustomizer) {
298+
Assert.notNull(requestCustomizer, "requestCustomizer must not be null");
299+
requestCustomizer.accept(requestBuilder);
300+
return this;
301+
}
302+
227303
/**
228304
* Sets the object mapper for JSON serialization/deserialization.
229305
* @param objectMapper the object mapper
@@ -240,7 +316,8 @@ public Builder objectMapper(ObjectMapper objectMapper) {
240316
* @return a new transport instance
241317
*/
242318
public HttpClientSseClientTransport build() {
243-
return new HttpClientSseClientTransport(clientBuilder, requestBuilder, baseUri, sseEndpoint, objectMapper);
319+
return new HttpClientSseClientTransport(clientBuilder.build(), requestBuilder, baseUri, sseEndpoint,
320+
objectMapper);
244321
}
245322

246323
}
@@ -336,7 +413,6 @@ public Mono<Void> sendMessage(JSONRPCMessage message) {
336413
try {
337414
String jsonText = this.objectMapper.writeValueAsString(message);
338415
HttpRequest request = this.requestBuilder.uri(URI.create(this.baseUri + endpoint))
339-
.header("Content-Type", "application/json")
340416
.POST(HttpRequest.BodyPublishers.ofString(jsonText))
341417
.build();
342418

Diff for: mcp/src/test/java/io/modelcontextprotocol/client/HttpSseMcpAsyncClientTests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*
1616
* @author Christian Tzolov
1717
*/
18-
@Timeout(15) // Giving extra time beyond the client timeout
18+
@Timeout(15)
1919
class HttpSseMcpAsyncClientTests extends AbstractMcpAsyncClientTests {
2020

2121
String host = "http://localhost:3004";
@@ -29,7 +29,7 @@ class HttpSseMcpAsyncClientTests extends AbstractMcpAsyncClientTests {
2929

3030
@Override
3131
protected McpClientTransport createMcpTransport() {
32-
return new HttpClientSseClientTransport(host);
32+
return HttpClientSseClientTransport.builder(host).build();
3333
}
3434

3535
@Override

Diff for: mcp/src/test/java/io/modelcontextprotocol/client/HttpSseMcpSyncClientTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class HttpSseMcpSyncClientTests extends AbstractMcpSyncClientTests {
2929

3030
@Override
3131
protected McpClientTransport createMcpTransport() {
32-
return new HttpClientSseClientTransport(host);
32+
return HttpClientSseClientTransport.builder(host).build();
3333
}
3434

3535
@Override

0 commit comments

Comments
 (0)