Skip to content

Commit e001542

Browse files
committed
fix: keep connection open for keep-alive ping in responseStream
After sending a response in responseStream(), if no listening stream exists yet (i.e., the client hasn't established a GET SSE connection), promote the current response stream to a listening stream instead of closing it. This allows KeepAliveScheduler to send periodic ping messages through the transport. Clients like Cursor that don't establish a separate GET listening stream would otherwise have the connection closed immediately after each response, causing the MCP server to appear as disconnected after the idle-timeout period. Fixes #681
1 parent 46bacda commit e001542

File tree

1 file changed

+21
-2
lines changed

1 file changed

+21
-2
lines changed

mcp-core/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerSession.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,17 @@ public Mono<Void> responseStream(McpSchema.JSONRPCRequest jsonrpcRequest, McpStr
171171
return transport
172172
.sendMessage(new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, jsonrpcRequest.id(), null,
173173
new McpSchema.JSONRPCResponse.JSONRPCError(McpSchema.ErrorCodes.METHOD_NOT_FOUND,
174-
error.message(), error.data())));
174+
error.message(), error.data())))
175+
.then(Mono.defer(() -> {
176+
McpLoggableSession currentListeningStream = this.listeningStreamRef.get();
177+
if (currentListeningStream == this.missingMcpTransportSession) {
178+
if (this.listeningStreamRef.compareAndSet(this.missingMcpTransportSession, stream)) {
179+
logger.debug("Converted response stream to listening stream for session {}", this.id);
180+
return Mono.empty();
181+
}
182+
}
183+
return transport.closeGracefully();
184+
}));
175185
}
176186
return requestHandler
177187
.handle(new McpAsyncServerExchange(this.id, stream, clientCapabilities.get(), clientInfo.get(),
@@ -189,7 +199,16 @@ public Mono<Void> responseStream(McpSchema.JSONRPCRequest jsonrpcRequest, McpStr
189199
return Mono.just(errorResponse);
190200
})
191201
.flatMap(transport::sendMessage)
192-
.then(transport.closeGracefully());
202+
.then(Mono.defer(() -> {
203+
McpLoggableSession currentListeningStream = this.listeningStreamRef.get();
204+
if (currentListeningStream == this.missingMcpTransportSession) {
205+
if (this.listeningStreamRef.compareAndSet(this.missingMcpTransportSession, stream)) {
206+
logger.debug("Converted response stream to listening stream for session {}", this.id);
207+
return Mono.empty();
208+
}
209+
}
210+
return transport.closeGracefully();
211+
}));
193212
});
194213
}
195214

0 commit comments

Comments
 (0)