Streamable HTTP client: slow reception of large SSE tool responses (fromLineSubscriber line-assembly bottleneck)

オープン
#1,042 コメント 3 件 リアクション 0 件 担当者 1 名 GitHub で見る

@chemicL がすでに取り組んでいます。

2026年7月3日 から。

評価

この issue はまだ評価されていません。

説明

area/client area/transport enhancement P2

Summary

When a tool returns a large response over the Streamable HTTP client transport, the client takes ~5s to receive a ~4 MB body that curl/HttpClient.ofString() reads in ~0.4s. The bottleneck is the client-side SSE body reading in ResponseSubscribers.sseToBodySubscriber, not the server, network, or JSON parsing.

Environment

  • io.modelcontextprotocol.sdk:mcp-core / mcp 2.0.0 (latest)
  • JDK 25, Reactor (via SDK)
  • Server: Spring AI 2.0.0 mcp-spring-webmvc (HttpServletStreamableServerTransportProvider) returning a single large SSE message event — one compact-JSON data: line (~4.17 MB)
  • Client: HttpClientStreamableHttpTransport (McpSyncClient.callTool)

Measurements (steady-state, 3 runs)

Path Time to receive ~4.17 MB
McpSyncClient.callTool (this SDK) ~5,300 ms
Same payload via curl / HttpClient BodyHandlers.ofString() ~0.4–0.75 s
Jackson parse of the received JSON < 70 ms

→ ~10–13× slower than a plain one-shot read of the identical bytes.

Root cause analysis

The response body is effectively a single huge data: line (compact JSON has no newlines). ResponseSubscribers.sseToBodySubscriber uses HttpResponse.BodySubscribers.fromLineSubscriber(...); assembling that one ~4 MB line through the JDK line subscriber is the cost.

Things I tried:

  • Changing SseLineSubscriber demand from upstream().request(1) to request(Long.MAX_VALUE)no improvement (so it isn't per-line backpressure).
  • Replacing the body subscriber with a streaming byte-level SSE parser (BodySubscribers.fromSubscriber, unbounded demand, accumulate ByteBuffers, split on \n\n event boundaries, decode each event once) → ~0.4 s (≈13×).

Important constraint (must stay streaming)

A whole-body ofString read fixes the speed but breaks progress: the server interleaves notifications/progress on the same POST response SSE stream before the final result. So the fix must remain a streaming parser that emits each SSE event as its boundary arrives (a byte-level parser does this while still avoiding the line-assembly cost).

Questions

  1. Is there a recommended approach/workaround for large tool responses on the client that we're missing?
  2. Would you accept a PR replacing fromLineSubscriber with a streaming byte-level SSE parser in ResponseSubscribers.sseToBodySubscriber (preserving incremental event emission)?
  3. Is this related to #844 (opt-in application/json response mode)? That would avoid SSE framing on the server side, but clients receiving SSE responses would still benefit from this fix.

Happy to open a PR with the streaming parser + a benchmark if that's welcome.

主要言語
Java
スター
3.7k
フォーク
1.1k
平均マージ
1日 15時間
マージ済み PR(30日)
9

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

modelcontextprotocol/java-sdk のほかの issue

modelcontextprotocol/java-sdk の issue をすべて見る

似ている issue

Java の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。