Skip to content

Streamable HTTP client ignores SSE messages when event name is omitted #885

Closed

Description

@nympheon

Disclaimer: I am not a native English speaker. This issue was drafted with AI translation assistance. I apologize for any awkward phrasing.

Bug description

When using the Streamable HTTP transport, the Java client receives an SSE frame whose payload contains a valid JSON-RPC response, but the SSE event name is omitted by the server.

In standard SSE semantics, if the event: field is omitted, the event type defaults to message. However, the current Java SDK only parses the frame if event.event() is explicitly equal to "message". If the event name is missing and Spring/WebFlux exposes it as null, the client drops the frame instead of treating it as a default message event.

As a result, the initialize response is ignored even though the client has already received the JSON-RPC result, and initialization eventually times out with:

Client failed to initialize by explicit API call

In my case, the server is rmcp over Streamable HTTP.

Environment

  • Spring AI: 2.0.0-M3
  • MCP Java SDK: 1.1.0
  • Transport: WebClientStreamableHttpTransport (via spring-ai-starter-mcp-client-webflux)
  • Spring Boot: 4.0.4
  • Java: 25.0.2
  • Reactor Netty: 1.3.4
  • MCP server: rmcp 1.2.0 (Rust, Streamable HTTP)
  • OS: macOS

Steps to reproduce

  1. Run an MCP server over Streamable HTTP.
  2. Make the server return a valid JSON-RPC initialize result as text/event-stream.
  3. The SSE frame should contain only data: ... and omit the event: field. For example:
   data: {"jsonrpc":"2.0","id":"1","result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{}},"serverInfo":{"name":"test","version":"1.0.0"}}}
  1. Start a Spring AI / MCP Java SDK client using WebClientStreamableHttpTransport.
  2. Call client.initialize() (or let Spring AI auto-initialize the MCP client during startup).

Expected behavior

The client should treat an SSE frame without an explicit event: field as a default message event and parse the JSON-RPC payload normally.

So the initialize response above should be accepted, and client initialization should complete successfully.

Actual behavior

The client receives the SSE payload, but the event type is exposed as null and is not parsed as a JSON-RPC message.

As a result, the initialize response is effectively ignored, and initialization times out.

From debug logs:

Received SSE event with type: ServerSentEvent [id = 'null', event='null', retry=null, comment='null', data={"jsonrpc":"2.0","id":"9642da98-0","result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{}},"serverInfo":{"name":"rmcp","version":"1.2.0"},"instructions":"..."}}]
...
Caused by: java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 20000ms
...
Client failed to initialize by explicit API call

Activity

  1. added a commit that references this issue on Apr 9, 2026
    3c200d3
  2. iaJingda commented on Apr 9, 2026

    @iaJingda
    Contributor

    Hi maintainers, I dug into this and pushed a fix on my fork (referenced above).

    Scope clarification

    WebClientStreamableHttpTransport mentioned in the original report was moved to Spring AI 2.0 as part of #805, so the WebClient-specific fix would need to land there. However, the same root cause is present in HttpClientStreamableHttpTransport (still in this repo), with an interesting asymmetry:

    • The sendMessage() POST path processes data() directly without checking the SSE event type, so initialize() happens to succeed on HttpClient.
    • The reconnect() GET stream path uses MESSAGE_EVENT_TYPE.equals(sseEvent.event()), which evaluates to false when the server omits the event: line. Per the SSE spec (WHATWG HTML §9.2.6), a missing event: field must default to message. As a result, server-initiated messages arriving on the open GET stream are silently dropped.

    So HttpClient users experience a more subtle form of the same bug: initialization succeeds, but server-pushed notifications on the SSE stream never reach the handler.

    Fix approach

    Extracted the classification into a package-private isMessageEvent(String) helper that treats null or empty event names as equivalent to "message" per the SSE spec, and covered it with parameterized unit tests (null, empty, explicit message, plus negative cases including case sensitivity). Happy to open a PR.

    One question: should a separate issue be filed against spring-projects/spring-ai for the WebClient variant, or is that being tracked there already?

  3. nympheon commented on Apr 9, 2026

    @nympheon
    Author

    Hi maintainers, I dug into this and pushed a fix on my fork (referenced above).

    Scope clarification

    WebClientStreamableHttpTransport mentioned in the original report was moved to Spring AI 2.0 as part of #805, so the WebClient-specific fix would need to land there. However, the same root cause is present in HttpClientStreamableHttpTransport (still in this repo), with an interesting asymmetry:

    • The sendMessage() POST path processes data() directly without checking the SSE event type, so initialize() happens to succeed on HttpClient.
    • The reconnect() GET stream path uses MESSAGE_EVENT_TYPE.equals(sseEvent.event()), which evaluates to false when the server omits the event: line. Per the SSE spec (WHATWG HTML §9.2.6), a missing event: field must default to message. As a result, server-initiated messages arriving on the open GET stream are silently dropped.

    So HttpClient users experience a more subtle form of the same bug: initialization succeeds, but server-pushed notifications on the SSE stream never reach the handler.

    Fix approach

    Extracted the classification into a package-private isMessageEvent(String) helper that treats null or empty event names as equivalent to "message" per the SSE spec, and covered it with parameterized unit tests (null, empty, explicit message, plus negative cases including case sensitivity). Happy to open a PR.

    One question: should a separate issue be filed against spring-projects/spring-ai for the WebClient variant, or is that being tracked there already?

    Thanks for digging into this and for the detailed analysis.
    I think this should also be filed in spring-projects/spring-ai for the WebClient variant. I haven’t opened an issue there yet.
    If you haven’t already filed one, I’d be happy to open it on my side. Of course, if you prefer to do it yourself, that works too.

  4. iaJingda commented on Apr 9, 2026

    @iaJingda
    Contributor

    @Qiaofanxing thanks for the quick follow-up! Please go ahead and open the issue in spring-projects/spring-ai — you have the original reproducer with the Rust rmcp server, so the report will be more actionable from your side.

    Feel free to link back to #913 and this thread for context on the shared root cause. If the Spring AI maintainers are open to it, I'd be happy to help with a PR there as well once we see how things land, but let's not block your issue on that.

    Thanks again for reporting the original bug with such clear reproduction steps — made digging into this much easier.

  5. added a commit that references this issue on Aug 4, 2026
    cfa72e4
  6. added a commit that references this issue on Aug 7, 2026
    f5aab36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions