{"record":{"id":"2a8edb1034650d34","repo":"Hmbown/CodeWhale","slug":"mcp-sse-server-sent-a-message-before-declaring-its","errorCode":null,"errorMessage":"MCP SSE server sent a message before declaring its endpoint","messagePattern":"MCP SSE server sent a message before declaring its endpoint","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp/sse.rs","lineNumber":231,"sourceCode":"        let msg = tokio::select! {\n            _ = cancel_token.cancelled() => {\n                anyhow::bail!(\"SSE transport cancelled before endpoint was discovered\");\n            }\n            _ = &mut timeout => {\n                anyhow::bail!(\n                    \"SSE endpoint not received within {}ms\",\n                    endpoint_timeout.as_millis()\n                );\n            }\n            msg = self.receiver.recv() => {\n                msg.context(\"SSE transport closed before endpoint was discovered\")?\n            }\n        };\n\n        match msg {\n            SseInbound::Endpoint(endpoint) => self.store_endpoint(&endpoint),\n            SseInbound::Message(_) => {\n                anyhow::bail!(\"MCP SSE server sent a message before declaring its endpoint\");\n            }\n        }\n    }\n\n    fn store_endpoint(&mut self, endpoint: &str) -> Result<()> {\n        self.endpoint_url = Some(Self::resolve_endpoint_url(&self.base_url, endpoint)?);\n        Ok(())\n    }\n\n    fn resolve_endpoint_url(base_url: &str, endpoint_url: &str) -> Result<String> {\n        let base = reqwest::Url::parse(base_url)?;\n        let resolved =\n            if endpoint_url.starts_with(\"http://\") || endpoint_url.starts_with(\"https://\") {\n                reqwest::Url::parse(endpoint_url)?\n            } else {\n                base.join(endpoint_url)?\n            };\n        // Security: the server-supplied `endpoint` event must stay same-origin","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/mcp/sse.rs#L213-L249","documentation":"Per the SSE transport protocol, the server must send its endpoint event before any JSON-RPC messages. If a `Message` arrives on the inbound channel before an `Endpoint`, the client rejects it as a protocol violation rather than buffering or guessing the endpoint.","triggerScenarios":"Server emits JSON-RPC messages before the endpoint event — a non-conformant SSE MCP server implementation or a stream resumed mid-session replaying old messages.","commonSituations":"Custom/in-house MCP SSE servers that send data immediately; resuming a cached stream that replays messages; testing against a mock that skips the endpoint event.","solutions":["Fix the server to send the `endpoint` event as the first SSE event after connection","If testing, update the mock to emit `event: endpoint` before any message events","Check whether a stale session/stream replay is occurring and open a fresh connection","Verify server MCP SDK version supports the SSE transport handshake correctly"],"exampleFix":"// server, before\nsend(data_event);\nsend(endpoint_event);\n// after\nsend(endpoint_event); // endpoint MUST come first\nsend(data_event);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match wait_for_endpoint(...).await {\n    Err(e) if e.to_string().contains(\"before declaring its endpoint\") => {\n        error!(\"server violates SSE handshake order; update server or SDK\");\n    }\n    other => other?,\n}","preventionTips":["Ensure servers send the endpoint event first, before any messages","Test mocks against the real handshake order","Open fresh sessions instead of resuming replaying streams"],"tags":["mcp","sse","protocol","handshake"],"backgroundTag":"unexpected-response-shape","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}