spring-projects/spring-ai · error · McpTransportException
Error parsing JSON-RPC message:
Error message
Error parsing JSON-RPC message:
What it means
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.
Source
Thrown at mcp/transport/mcp-spring-webflux/src/main/java/org/springframework/ai/mcp/client/webflux/transport/WebClientStreamableHttpTransport.java:568
private Tuple2<Optional<String>, Iterable<McpSchema.JSONRPCMessage>> parse(ServerSentEvent<String> event) {
if (isMessageEvent(event.event())) {
String data = event.data();
if (data == null || data.isEmpty()) {
// messages without `event: ` may be empty and should be ignored
logger.debug("Ignoring SSE message event with empty data: %s".formatted(event));
return Tuples.of(Optional.empty(), List.of());
}
try {
// We don't support batching ATM and probably won't since the next version
// considers removing it.
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(this.jsonMapper, data);
String eventId = event.id();
Optional<String> idOpt = (eventId != null) ? Optional.of(eventId) : Optional.empty();
return Tuples.of(idOpt, List.of(message));
}
catch (IOException ioException) {
throw new McpTransportException("Error parsing JSON-RPC message: " + data, ioException);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Received SSE event with type: " + event);
}
return Tuples.of(Optional.empty(), List.of());
}
}
private static boolean isMessageEvent(@Nullable String eventType) {
// Per SSE semantics, missing/blank event type defaults to "message".
return !StringUtils.hasText(eventType) || MESSAGE_EVENT_TYPE.equals(eventType);
}
/**
* Builder for {@link WebClientStreamableHttpTransport}.
*/View on GitHub (pinned to 98a7beda4f)
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.
Example fix
// server bug example: sending plain text over SSE
// before
sendEvent("data: ping\n\n");
// after
sendEvent("data: {\"jsonrpc\":\"2.0\",\"method\":\"notifications/message\",\"params\":{}}\n\n"); Defensive patterns
Strategy: try-catch
Validate before calling
// Client-side sanity check before trusting an SSE payload
if (data == null || !data.trim().startsWith("{")) {
logger.warn("Skipping non-JSON SSE payload: " + data);
return;
} Try / catch
try {
processSseEvent(event);
} catch (McpTransportException e) {
if (e.getCause() instanceof IOException) {
logger.error("Malformed JSON-RPC payload: " + e.getMessage());
// reconnect with backoff
} else {
throw e;
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
Related errors
- McpTransportSessionClosedException
- SSE connection '<connectionName>' requires a 'url' property.
- Failed to create SSE transport for connection '<connectionNa
- MessageEndpoint must be set
- Failed to parse JSON:
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/d7167540761453c0.
Report an issue: GitHub.