Kuberwastaken/claurst · error
MCP server ' ': failed to parse legacy SSE JSON payload
Error message
MCP server '{}': failed to parse legacy SSE JSON payload: {} What it means
Raised by `parse_server_message` when a JSON payload arriving from a legacy SSE MCP server (either from the long-lived SSE stream or from a POST response body) cannot be deserialized into an rmcp `RxJsonRpcMessage<RoleClient>`. The library expects every server-sent event data block to be a valid JSON-RPC 2.0 message conforming to the MCP schema. If the payload is malformed JSON, empty-but-not-blank, or structurally not a JSON-RPC message, serde deserialization fails and the server name plus serde error are wrapped into this anyhow error.
Solutions
- Inspect the raw SSE payload (log `data` before parsing) to see what the server actually sent.
- Confirm the endpoint is a real MCP server speaking the SSE transport (GET /sse), not a generic HTTP route.
- Upgrade the MCP server to a protocol version compatible with the rmcp client schema.
- If the server sends non-JSON keepalive frames, configure it to use SSE comments (`: ping`) instead of data events.
Example fix
// before: pointing the SSE transport at the messages endpoint instead of the SSE endpoint
let backend = connect_legacy_sse("http://host:3000/message").await?;
// after: use the server's advertised SSE endpoint
let backend = connect_legacy_sse("http://host:3000/sse").await?; Defensive patterns
Strategy: validation
Validate before calling
fn looks_like_jsonrpc(data: &str) -> bool {
serde_json::from_str::<serde_json::Value>(data)
.map(|v| v.get("jsonrpc").is_some() && v.get("method").is_some() || v.get("id").is_some())
.unwrap_or(false)
} Type guard
fn as_jsonrpc_message(data: &str) -> Option<serde_json::Value> {
serde_json::from_str(data).ok().filter(|v| v.is_object())
} Try / catch
match parse_server_message(&server_name, data) {
Ok(msg) => incoming_tx.send(msg).ok(),
Err(e) => {
tracing::warn!(%server_name, raw = %data, "dropping unparseable SSE payload: {e}");
Ok(())
}
} Prevention
- Confirm the SSE endpoint is the MCP server's SSE route, not a generic HTTP path.
- Pin and test the MCP protocol version supported by both client and server.
- Log raw SSE data frames at debug level so malformed payloads are diagnosable in the field.
- Keep the MCP server updated to a revision whose message schema matches the rmcp client.
When it happens
Trigger: `parse_server_message` called from `start_sse_listener` or `handle_legacy_sse_http_response` with `data` that is not valid JSON-RPC: server sends HTML error pages over SSE, sends JSON that lacks required JSON-RPC fields (e.g. no `jsonrpc: "2.0"`), sends a JSON array, or truncates a message.
Common situations: Connecting the legacy SSE transport to a non-MCP HTTP endpoint that streams HTML or plain text; an old MCP server implementing a pre-standard protocol revision whose message shapes no longer deserialize; a proxy mangling the SSE stream; server bug emitting comments/keepalives as data payloads.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- legacy SSE endpoint event did not include a POST endpoint
- MCP server ' ': legacy SSE stream returned HTTP
- MCP server ' ': HTTP from legacy SSE transport
- mcpServers must be an object
- mcpServers. must be an object
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/efc0422bda0187e2.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/mcp/src/rmcp_backend.rs:428
fn close(&mut self) -> impl std::future::Future<Output = Result<(), Self::Error>> + Send {
let background_tasks = Arc::clone(&self.background_tasks);
async move {
let mut tasks = lock_recover(&background_tasks);
for handle in tasks.drain(..) {
handle.abort();
}
Ok(())
}
}
}
fn parse_server_message(
server_name: &str,
data: &str,
) -> anyhow::Result<rmcp::service::RxJsonRpcMessage<RoleClient>> {
serde_json::from_str(data).map_err(|e| {
anyhow::anyhow!(
"MCP server '{}': failed to parse legacy SSE JSON payload: {}",
server_name,
e
)
})
}
async fn handle_legacy_sse_http_response(
server_name: String,
response: reqwest::Response,
incoming_tx: mpsc::UnboundedSender<rmcp::service::RxJsonRpcMessage<RoleClient>>,
background_tasks: Arc<StdMutex<Vec<JoinHandle<()>>>>,
) -> anyhow::Result<()> {
let status = response.status();
if !status.is_success() && status != reqwest::StatusCode::ACCEPTED {
let body = response.text().await.unwrap_or_default();
anyhow::bail!(
"MCP server '{}': HTTP {} from legacy SSE transport: {}",View on GitHub (pinned to b0637c97ec)