Kuberwastaken/claurst · error

legacy SSE endpoint event did not include a POST endpoint

Error message

legacy SSE endpoint event did not include a POST endpoint

What it means

Legacy SSE MCP servers first send an 'endpoint' event telling the client where to POST JSON-RPC messages. If that event's payload is empty or whitespace-only after trimming, the client cannot construct the POST endpoint and bails. This protects against malformed servers that open an SSE stream but never advertise a message endpoint.

Solutions

  1. Verify the server actually implements the legacy SSE MCP handshake and emits an 'endpoint' event with a non-empty value
  2. If the server supports the streamable HTTP transport, switch the config type from 'sse' to 'http'
  3. Check intermediate proxies/gateways for rewriting or dropping the endpoint event payload
  4. Confirm the base_url is correct so relative endpoints can be resolved
Defensive patterns

Strategy: try-catch

Try / catch

match client.connect().await {
    Err(e) if e.to_string().contains("legacy SSE endpoint event") => {
        // fall back to streamable HTTP transport or surface server-compat error
    }
    other => other?,
}

Prevention

When it happens

Trigger: resolve_legacy_endpoint is called with an empty or whitespace-only endpoint string, i.e. the server's SSE 'endpoint' event carried no endpoint data.

Common situations: Server behind a proxy that strips the endpoint event body; a non-MCP SSE endpoint mistakenly registered as an MCP server; old/broken SSE server implementations; URL path not URL-encoded when parsed upstream.

Related errors


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

Appendix: source

Thrown at src-rust/crates/mcp/src/lib.rs:417

    pub(crate) fn bearer_header_value(token: &str) -> anyhow::Result<HeaderValue> {
        HeaderValue::from_str(&format!("Bearer {}", token))
            .map_err(|e| anyhow::anyhow!("invalid bearer token header: {}", e))
    }

    pub(crate) fn is_event_stream_response(response: &reqwest::Response) -> bool {
        response
            .headers()
            .get(CONTENT_TYPE)
            .and_then(|value| value.to_str().ok())
            .map(|value| value.contains("text/event-stream"))
            .unwrap_or(false)
    }

    pub(crate) fn resolve_legacy_endpoint(base_url: &str, endpoint: &str) -> anyhow::Result<String> {
        let endpoint = endpoint.trim();
        if endpoint.is_empty() {
            anyhow::bail!("legacy SSE endpoint event did not include a POST endpoint");
        }
        if let Ok(url) = url::Url::parse(endpoint) {
            return Ok(url.to_string());
        }
        let base = url::Url::parse(base_url)
            .map_err(|e| anyhow::anyhow!("invalid legacy SSE base URL '{}': {}", base_url, e))?;
        base.join(endpoint)
            .map(|url| url.to_string())
            .map_err(|e| anyhow::anyhow!("failed to resolve legacy SSE endpoint '{}': {}", endpoint, e))
    }

    #[cfg(test)]
    pub(super) fn route_incoming_value(
        server_name: &str,
        value: serde_json::Value,
        response_tx: &mpsc::UnboundedSender<JsonRpcResponse>,
        notification_tx: &mpsc::UnboundedSender<serde_json::Value>,
    ) -> anyhow::Result<()> {

View on GitHub (pinned to b0637c97ec)