Skip to content

fix(http): drain SSE stream for connection reuse#790

Open
DaleSeo wants to merge 4 commits intomainfrom
fix/streamable-http-connection-reuse
Open

fix(http): drain SSE stream for connection reuse#790
DaleSeo wants to merge 4 commits intomainfrom
fix/streamable-http-connection-reuse

Conversation

@DaleSeo
Copy link
Copy Markdown
Member

@DaleSeo DaleSeo commented Apr 6, 2026

Fixes #789

Motivation and Context

Subsequent tool calls via StreamableHttpClientTransport took ~42ms each on Linux, roughly 10x slower than the Python SDK. The root cause is twofold: the server kept per-request SSE channels open after sending a response, and the client dropped POST SSE response bodies without consuming them. This left stale connections in the pool that reqwest would stall on (~40ms TCP Delayed ACK) before falling back to a fresh connection.

This PR fixes both sides. On the server, request-wise channels now close immediately after sending a Response or Error, and the associated resource_router entries are cleaned up to prevent leaks. On the client, per-request POST SSE streams use a lightweight raw_sse_to_jsonrpc adapter instead of SseAutoReconnectStream, and execute_sse_stream drains remaining bytes after receiving a response so the connection returns to the pool cleanly. The default reqwest client also sets pool_max_idle_per_host(0) because TCP Delayed ACK on Linux still prevents reliable connection reuse even with the drain. Users who provide a custom client via with_client() are unaffected. Also fixed is_response to match Error in addition to Response, since JSON-RPC errors are equally terminal for request-wise streams.

How Has This Been Tested?

  1. Start the example server in one terminal:
cargo run --release --manifest-path examples/servers/Cargo.toml \
  --example counter_hyper_streamable_http
  1. In another terminal, create a small benchmark client. Save this as bench.rs somewhere outside the workspace (e.g. /tmp/bench/src/main.rs) with a Cargo.toml pointing at the local rmcp crate:
[package]
name = "bench"
version = "0.1.0"
edition = "2024"

[dependencies]
rmcp = { path = "<path-to-this-repo>/crates/rmcp", features = ["client", "transport-streamable-http-client", "transport-streamable-http-client-reqwest"] }
tokio = { version = "1", features = ["full"] }
use rmcp::transport::StreamableHttpClientTransport;
use rmcp::{ServiceExt, model::*};
use std::time::Instant;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let transport = StreamableHttpClientTransport::from_uri("http://[::1]:8080/");
    let client = ClientInfo::default().serve(transport).await?;
    for i in 1..=5 {
        let start = Instant::now();
        let _res = client.call_tool(CallToolRequestParams::new("say_hello")).await?;
        println!("Call {}: {:.1}ms", i, start.elapsed().as_secs_f64() * 1000.0);
    }
    client.cancel().await?;
    Ok(())
}
  1. Run it and verify calls 2-5 complete under 1ms:
cargo run --release

On Linux before this fix, calls 2-5 consistently show ~42ms. After the fix, all calls should be under 1ms. The issue does not reproduce on macOS due to different TCP Delayed ACK behavior.

Breaking Changes

None

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

@github-actions github-actions bot added T-test Testing related changes T-core Core library changes T-transport Transport layer changes labels Apr 6, 2026
@DaleSeo DaleSeo force-pushed the fix/streamable-http-connection-reuse branch from 70e959d to 475ddeb Compare April 6, 2026 17:34
@DaleSeo DaleSeo force-pushed the fix/streamable-http-connection-reuse branch from 475ddeb to 1f675de Compare April 10, 2026 01:57
@github-actions github-actions bot added T-dependencies Dependencies related changes T-config Configuration file changes labels Apr 10, 2026
@DaleSeo DaleSeo marked this pull request as ready for review April 10, 2026 15:11
@DaleSeo DaleSeo requested a review from a team as a code owner April 10, 2026 15:11
@DaleSeo DaleSeo self-assigned this Apr 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-config Configuration file changes T-core Core library changes T-dependencies Dependencies related changes T-test Testing related changes T-transport Transport layer changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

StreamableHttpClientTransport has 10x higher latency on subsequent calls vs Python SDK

1 participant