{"record":{"id":"d7167540761453c0","repo":"spring-projects/spring-ai","slug":"error-parsing-json-rpc-message","errorCode":null,"errorMessage":"Error parsing JSON-RPC message: ","messagePattern":"Error parsing JSON-RPC message: ","errorType":"exception","errorClass":"McpTransportException","httpStatus":null,"severity":"error","filePath":"mcp/transport/mcp-spring-webflux/src/main/java/org/springframework/ai/mcp/client/webflux/transport/WebClientStreamableHttpTransport.java","lineNumber":568,"sourceCode":"\n\tprivate Tuple2<Optional<String>, Iterable<McpSchema.JSONRPCMessage>> parse(ServerSentEvent<String> event) {\n\t\tif (isMessageEvent(event.event())) {\n\t\t\tString data = event.data();\n\t\t\tif (data == null || data.isEmpty()) {\n\t\t\t\t// messages without `event: ` may be empty and should be ignored\n\t\t\t\tlogger.debug(\"Ignoring SSE message event with empty data: %s\".formatted(event));\n\t\t\t\treturn Tuples.of(Optional.empty(), List.of());\n\t\t\t}\n\t\t\ttry {\n\t\t\t\t// We don't support batching ATM and probably won't since the next version\n\t\t\t\t// considers removing it.\n\t\t\t\tMcpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(this.jsonMapper, data);\n\t\t\t\tString eventId = event.id();\n\t\t\t\tOptional<String> idOpt = (eventId != null) ? Optional.of(eventId) : Optional.empty();\n\t\t\t\treturn Tuples.of(idOpt, List.of(message));\n\t\t\t}\n\t\t\tcatch (IOException ioException) {\n\t\t\t\tthrow new McpTransportException(\"Error parsing JSON-RPC message: \" + data, ioException);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif (logger.isDebugEnabled()) {\n\t\t\t\tlogger.debug(\"Received SSE event with type: \" + event);\n\t\t\t}\n\t\t\treturn Tuples.of(Optional.empty(), List.of());\n\t\t}\n\t}\n\n\tprivate static boolean isMessageEvent(@Nullable String eventType) {\n\t\t// Per SSE semantics, missing/blank event type defaults to \"message\".\n\t\treturn !StringUtils.hasText(eventType) || MESSAGE_EVENT_TYPE.equals(eventType);\n\t}\n\n\t/**\n\t * Builder for {@link WebClientStreamableHttpTransport}.\n\t */","sourceCodeStart":550,"sourceCodeEnd":586,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/transport/mcp-spring-webflux/src/main/java/org/springframework/ai/mcp/client/webflux/transport/WebClientStreamableHttpTransport.java#L550-L586","documentation":"In WebClientStreamableHttpTransport's SSE event processing, incoming SSE data is parsed with McpSchema.deserializeJsonRpcMessage. If that throws an IOException (malformed JSON or not a valid JSON-RPC structure), the parse step wraps it in McpTransportException with message \"Error parsing JSON-RPC message: \" + the raw data, failing the reactive stream and typically closing the transport.","triggerScenarios":"An SSE event body is not valid JSON, is JSON but not a JSON-RPC request/response/notification, or contains truncated/garbled data — deserializeJsonRpcMessage throws IOException, which parse() converts to McpTransportException.","commonSituations":"Proxy or load balancer injecting HTML error pages into the SSE stream; server sending non-JSON keep-alive or custom event payloads; compression/encoding mismatch corrupting the body; server-side serialization bugs or protocol-version mismatches.","solutions":["Inspect the raw data in the exception message to see what the server actually sent; fix the sender or intermediary (proxy, gateway) to emit valid JSON-RPC over SSE.","Verify both client and server agree on the MCP streamable-HTTP protocol version and JSON content type (application/json vs text/event-stream).","Disable/bypass intermediaries that may rewrite responses (compression, buffering, HTML error pages) and retest."],"exampleFix":"// server bug example: sending plain text over SSE\n// before\nsendEvent(\"data: ping\\n\\n\");\n\n// after\nsendEvent(\"data: {\\\"jsonrpc\\\":\\\"2.0\\\",\\\"method\\\":\\\"notifications/message\\\",\\\"params\\\":{}}\\n\\n\");","handlingStrategy":"try-catch","validationCode":"// Client-side sanity check before trusting an SSE payload\nif (data == null || !data.trim().startsWith(\"{\")) {\n    logger.warn(\"Skipping non-JSON SSE payload: \" + data);\n    return;\n}","typeGuard":null,"tryCatchPattern":"try {\n    processSseEvent(event);\n} catch (McpTransportException e) {\n    if (e.getCause() instanceof IOException) {\n        logger.error(\"Malformed JSON-RPC payload: \" + e.getMessage());\n        // reconnect with backoff\n    } else {\n        throw e;\n    }\n}","preventionTips":["Ensure proxies/load balancers do not inject HTML into SSE streams.","Keep client and server MCP protocol versions and content types aligned.","Log raw payloads on parse failures to spot the offending sender quickly."],"tags":["json","network","mcp","sse","parsing","webflux"],"backgroundTag":"json-parse-error","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}