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
- Confirm the server keeps the SSE connection open during the session
- Check intermediaries that may close idle SSE connections
- 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)