Skip to content

WebFluxStreamableServerTransportProvider closes connection after response, blocking keep-alive ping #681

@JGoP-L

Description

@JGoP-L

Bug description

The streamable-http server closes the connection immediately after responding to the client's request. As a result, the server fails to send ping messages to the client. Since IDEs like Cursor do not receive the ping, they mark the MCP server as closed (red state) after the idle-timeout period.

Related Issue: This issue was initially reported in Spring AI project: spring-projects/spring-ai#4862

Root Cause: The issue is located in WebFluxStreamableServerTransportProvider.handlePost() method in the mcp-spring/mcp-spring-webflux module. While the initialize request is handled correctly (converting response to SSE stream and keeping connection open), other JSON-RPC requests (like tools/list) cause the connection to close immediately after sending the response, preventing keep-alive ping messages from being sent.

Environment

  • MCP Java SDK version: 0.17.0-SNAPSHOT
  • Spring AI version: 1.1.0
  • Spring Framework version: 6.2.1
  • Java version: 17+
  • WebFlux: Yes
  • Transport: streamable-http

Steps to reproduce

  1. Use the Spring AI example project: https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-webflux-server
  2. Change protocol from STATELESS to STREAMABLE in application.properties:
    spring.ai.mcp.server.protocol=STREAMABLE
  3. Add the following settings to application.properties:
    logging.level.root=DEBUG
    spring.ai.mcp.server.keep-alive-interval=30s
    spring.ai.mcp.server.streamable-http.keep-alive-interval=30s
  4. Start the webflux-mcp-server and wait for connection with MCP client
  5. Send "Initialize" and "tools/list" requests
  6. Monitor the logs

Expected behavior

The webflux-mcp-server should keep the connection open after sending a response to the client's request. It must only close the connection according to the logic defined in the KeepAliveScheduler. This allows keep-alive ping messages to be sent periodically to verify connection health.

Actual behavior

The connection is closed immediately after sending the response. Logs show:

2025-11-13T10:47:56.088+09:00 DEBUG ... r.n.http.server.HttpServerOperations : [f1db6cad-2, L:/[0:0:0:0:0:0:0:1]:8080 - R:/[0:0:0:0:0:0:0:1]:55851] Last HTTP packet was sent, terminating the channel
2025-11-13T10:47:56.089+09:00 DEBUG ... r.netty.channel.ChannelOperations : [HttpServer] Channel inbound receiver cancelled (subscription disposed).
2025-11-13T10:48:20.038+09:00 WARN  ... i.m.util.KeepAliveScheduler : Failed to send keep-alive ping to session io.modelcontextprotocol.spec.McpStreamableServerSession@5fd8c0ef: Stream unavailable for session f7223451-6867-4b8c-8b46-e8ded21a7691

Minimal Complete Reproducible example

Use the Spring AI example project as the base:

Technical Details

The problem is in mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java:

  1. Initialize request (lines 236-277): Correctly handled - converts response to SSE stream and keeps connection open by calling listeningStream() (lines 267-268)
  2. Other JSONRPCRequest (lines 298-312): Issue location
    • Creates SSE stream but doesn't convert it to a listening stream like initialize does
    • While McpStreamableServerSession.responseStream() method (lines 211-229 in McpStreamableServerSession.java) has logic to convert the response stream to a listening stream to keep connection open
    • The WebFlux response stream closes immediately after responseStream() completes, preventing keep-alive ping from being sent

Suggested Fix

Ensure that when handling non-initialize requests in handlePost() method, the response stream remains open after sending the response, allowing KeepAliveScheduler to send ping messages through the stream. This can be achieved by following the same pattern used for initialize requests (lines 250-271), where the response stream is converted to a listening stream.
If you confirm this is a bug, I'm happy to fix it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    waiting for userWaiting for user feedback or more details

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions