Skip to content

WebClientStreamableHttpTransport: SSE body errors silently dropped, callTool hangs until timeout #889

@Planview-JamesK

Description

@Planview-JamesK

Description

When an MCP server returns an SSE response whose body exceeds the Spring WebClient maxInMemorySize buffer limit (default 256KB), the DataBufferLimitException is silently dropped and the callTool() call hangs until the request timeout expires. The caller receives a TimeoutException instead of a meaningful error.

Root Cause

In WebClientStreamableHttpTransport.sendMessage(), for SSE responses, sink.success() is called as soon as the SSE headers arrive — before the body is read:

else if (mediaType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM)) {
    logger.debug("Established SSE stream via POST");
    sink.success();          // <-- signals success before body is read
    return newEventStream(response, sessionRepresentation);
}

When the body reading subsequently fails (e.g., DataBufferLimitException from an oversized response), the error flows through:

  1. DefaultMcpTransportStream.consumeSseStream()doOnError
  2. sendMessage() .onErrorComplete() handler → calls handleException(t) then sink.error(t)
  3. sink.error(t) is a no-op because sink.success() was already called

The pendingResponses entry in McpClientSession is orphaned — no response or error is ever delivered. The callTool() Mono waits indefinitely until requestTimeout.

Note: the same early sink.success() pattern also exists for JSON (APPLICATION_JSON) responses in the same method, so the issue may affect non-SSE responses as well.

Reproduction

  1. Configure a WebClient with a small maxInMemorySize (e.g., 256KB default)
  2. Have an MCP server tool return a response larger than that limit
  3. Call the tool via McpSyncClient.callTool()
  4. Observe: the call hangs for the full requestTimeout duration, then throws TimeoutException

Expected Behavior

The callTool() call should fail promptly with a meaningful error (e.g., wrapping the DataBufferLimitException) instead of hanging until timeout.

Suggested Fix

Either:

  • Don't call sink.success() until the body is fully consumed, or
  • Propagate body-reading errors to the pendingResponses sink in McpClientSession via a separate error path (since the MonoSink is already terminated)

Environment

  • io.modelcontextprotocol.sdk:mcp-spring-webflux:0.17.0
  • io.modelcontextprotocol.sdk:mcp-core:0.17.0
  • Spring Boot 3.5.x
  • Spring WebFlux (Reactor Netty)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions