Skip to content

Commit 4a8ecdf

Browse files
committed
Add option to disable standalone SSE stream
Signed-off-by: Arnab Nandy <arnab_nandy7@yahoo.com>
1 parent 8ee8ccb commit 4a8ecdf

3 files changed

Lines changed: 52 additions & 5 deletions

File tree

docs/client.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,14 @@ McpTransport transport = new StdioClientTransport(params, McpJsonDefaults.getMap
156156
McpTransport transport = HttpClientStreamableHttpTransport
157157
.builder("http://your-mcp-server")
158158
.endpoint("/mcp")
159+
.openSseStream(false) // Optional: use POST request-response mode only
159160
.build();
160161
```
161162

162163
The Streamable HTTP transport supports:
163164

164165
- Resumable streams for connection recovery
166+
- Optional standalone GET SSE stream for server-initiated messages
165167
- Configurable connect timeout
166168
- Custom HTTP request customization
167169
- Multiple protocol version negotiation

mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ static boolean isMessageEvent(String eventName) {
145145

146146
private final boolean openConnectionOnStartup;
147147

148+
private final boolean openSseStream;
149+
148150
private final McpHttpClientTransportAuthorizationErrorHandler authorizationErrorHandler;
149151

150152
private final boolean resumableStreams;
@@ -163,7 +165,8 @@ static boolean isMessageEvent(String eventName) {
163165

164166
private HttpClientStreamableHttpTransport(McpJsonMapper jsonMapper, HttpClient httpClient,
165167
HttpRequest.Builder requestBuilder, String baseUri, String endpoint, boolean resumableStreams,
166-
boolean openConnectionOnStartup, McpAsyncHttpClientRequestCustomizer httpRequestCustomizer,
168+
boolean openConnectionOnStartup, boolean openSseStream,
169+
McpAsyncHttpClientRequestCustomizer httpRequestCustomizer,
167170
McpHttpClientTransportAuthorizationErrorHandler authorizationErrorHandler,
168171
List<String> supportedProtocolVersions) {
169172
this.jsonMapper = jsonMapper;
@@ -173,6 +176,7 @@ private HttpClientStreamableHttpTransport(McpJsonMapper jsonMapper, HttpClient h
173176
this.endpoint = endpoint;
174177
this.resumableStreams = resumableStreams;
175178
this.openConnectionOnStartup = openConnectionOnStartup;
179+
this.openSseStream = openSseStream;
176180
this.authorizationErrorHandler = authorizationErrorHandler;
177181
this.activeSession.set(createTransportSession());
178182
this.httpRequestCustomizer = httpRequestCustomizer;
@@ -196,7 +200,7 @@ public static Builder builder(String baseUri) {
196200
public Mono<Void> connect(Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler) {
197201
return Mono.deferContextual(ctx -> {
198202
this.handler.set(handler);
199-
if (this.openConnectionOnStartup) {
203+
if (this.openConnectionOnStartup && this.openSseStream) {
200204
logger.debug("Eagerly opening connection on startup");
201205
return this.reconnect(null).onErrorComplete(t -> {
202206
logger.warn("Eager connect failed ", t);
@@ -560,8 +564,10 @@ public Mono<Void> sendMessage(McpSchema.JSONRPCMessage sentMessage) {
560564
"Authorization error when sending message", requestSnapshot, responseEvent.responseInfo()));
561565
}
562566

563-
if (transportSession.markInitialized(
564-
responseEvent.responseInfo().headers().firstValue("mcp-session-id").orElseGet(() -> null))) {
567+
if (transportSession.markInitialized(responseEvent.responseInfo()
568+
.headers()
569+
.firstValue("mcp-session-id")
570+
.orElseGet(() -> null)) && this.openSseStream) {
565571
// Once we have a session, we try to open an async stream for
566572
// the server to send notifications and requests out-of-band.
567573

@@ -739,6 +745,8 @@ public static class Builder {
739745

740746
private boolean openConnectionOnStartup = false;
741747

748+
private boolean openSseStream = true;
749+
742750
private HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
743751

744752
private McpAsyncHttpClientRequestCustomizer httpRequestCustomizer = McpAsyncHttpClientRequestCustomizer.NOOP;
@@ -841,6 +849,20 @@ public Builder openConnectionOnStartup(boolean openConnectionOnStartup) {
841849
return this;
842850
}
843851

852+
/**
853+
* Configure whether the client should open a standalone SSE stream using an HTTP
854+
* GET request. By default, this value is {@code true}. When disabled, the client
855+
* operates without a standalone SSE stream, but can still process SSE responses
856+
* returned by HTTP POST requests.
857+
* @param openSseStream if {@code true}, the client may open a standalone SSE
858+
* stream
859+
* @return the builder instance
860+
*/
861+
public Builder openSseStream(boolean openSseStream) {
862+
this.openSseStream = openSseStream;
863+
return this;
864+
}
865+
844866
/**
845867
* Sets the customizer for {@link HttpRequest.Builder}, to modify requests before
846868
* executing them.
@@ -957,7 +979,7 @@ public HttpClientStreamableHttpTransport build() {
957979
HttpClient httpClient = this.clientBuilder.connectTimeout(this.connectTimeout).build();
958980
return new HttpClientStreamableHttpTransport(jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper,
959981
httpClient, requestBuilder, baseUri, endpoint, resumableStreams, openConnectionOnStartup,
960-
httpRequestCustomizer, authorizationErrorHandler, supportedProtocolVersions);
982+
openSseStream, httpRequestCustomizer, authorizationErrorHandler, supportedProtocolVersions);
961983
}
962984

963985
}

mcp-test/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportErrorHandlingTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,29 @@ void test405OnConnectReturnsEmptyFlux() {
389389
StepVerifier.create(transport.closeGracefully()).verifyComplete();
390390
}
391391

392+
@Test
393+
void shouldNotOpenSseStreamWhenDisabled() {
394+
currentServerSessionId.set("test-session-123");
395+
var getRequestCount = new AtomicInteger();
396+
transport = HttpClientStreamableHttpTransport.builder(HOST)
397+
.openConnectionOnStartup(true)
398+
.openSseStream(false)
399+
.asyncHttpRequestCustomizer((builder, method, uri, body, context) -> {
400+
if ("GET".equals(method)) {
401+
getRequestCount.incrementAndGet();
402+
}
403+
return Mono.just(builder);
404+
})
405+
.build();
406+
407+
StepVerifier.create(transport.connect(msg -> msg)).verifyComplete();
408+
StepVerifier.create(transport.sendMessage(createTestRequestMessage())).verifyComplete();
409+
410+
assertThat(processedMessagesCount.get()).isEqualTo(1);
411+
assertThat(getRequestCount.get()).isZero();
412+
assertThat(processedSseConnectCount.get()).isZero();
413+
}
414+
392415
@Nested
393416
class AuthorizationError {
394417

0 commit comments

Comments
 (0)