Kuberwastaken/claurst · error

MCP server ' ': legacy SSE stream ended before endpoint…

Error message

MCP server '{}': legacy SSE stream ended before endpoint discovery

What it means

The legacy SSE stream terminated cleanly before endpoint discovery completed — after the stream task finished, the endpoint receiver channel closed without yielding a POST endpoint. Distinct from a stream error: the stream simply ended (server-side close) while the transport still lacked a discovered endpoint.

Solutions

  1. Confirm the server keeps the SSE connection open during the session
  2. Check intermediaries that may close idle SSE connections
  3. Prefer the streamable-http transport for servers that support it
Defensive patterns

Strategy: fallback

When it happens

Trigger: Thrown at src-rust/crates/mcp/src/rmcp_backend.rs:333 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/87627522bfa139d1. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/mcp/src/rmcp_backend.rs:333

                }
            } else if let Some(tx) = lock_recover(&endpoint_tx_for_task).take() {
                let _ = tx.send(Err(anyhow::anyhow!(
                    "legacy SSE stream closed before announcing endpoint"
                )));
            }
        });
        lock_recover(&self.background_tasks).push(task);

        let endpoint = tokio::time::timeout(std::time::Duration::from_secs(10), endpoint_rx)
            .await
            .map_err(|_| {
                anyhow::anyhow!(
                    "MCP server '{}': timed out waiting for legacy SSE endpoint event",
                    self.server_name
                )
            })?
            .map_err(|_| {
                anyhow::anyhow!(
                    "MCP server '{}': legacy SSE stream ended before endpoint discovery",
                    self.server_name
                )
            })??;
        *lock_recover(&self.post_endpoint) = Some(endpoint);
        Ok(())
    }
}

impl Drop for LegacySseRmcpTransport {
    fn drop(&mut self) {
        // Some upper-layer paths drop the backend instead of calling close()
        // explicitly. Abort the listener tasks again here so legacy SSE does
        // not outlive the connection teardown.
        let mut tasks = lock_recover(&self.background_tasks);
        for handle in tasks.drain(..) {
            handle.abort();
        }

View on GitHub (pinned to b0637c97ec)