Using mcp-core:2.0.1 on Java 17, we found that initialization times out when the server immediately returns invalid JSON. The caller gets a TimeoutException instead of the JSON parsing error.
Reproduction
Configure HttpClientStreamableHttpTransport with initialization and request timeouts both set to 5000 ms.
When the server receives initialize, immediately return:
HTTP/1.1 200 OK
Content-Type: application/json
{broken
Subscribe to initialization and wait for its terminal signal.
We reproduced this through an application using the SDK client and its standard transport.
Expected and actual behavior
Expected: initialization fails as soon as parsing fails, with the original parsing exception preserved in the cause chain.
Actual: it fails about 5 seconds later with TimeoutException. The parsing cause is missing from the terminal exception chain. No tool call is executed.
What we found in the source
In HttpClientStreamableHttpTransport.sendMessage, the application/json branch calls deliveredSink.success() before deserializeJsonRpcMessage().
When parsing throws IOException, the code wraps it in McpTransportException. The outer error handler then calls deliveredSink.error(...), but the sink has already completed successfully.
McpClientSession.sendRequest registers the request in pendingResponses and relies on the sendMessage error callback to remove it and signal failure. Since that callback doesn't receive the parsing error, the request keeps waiting until timeout or later cleanup.
Relevant source:
Could the JSON branch defer its success signal until parsing succeeds, so parsing failures reach the caller and trigger pending-request cleanup?
We also tested HTTP 503, connection refusal, and a genuinely delayed response. Those behaved as expected; the invalid-JSON case was the one that lost its original failure.
Using
mcp-core:2.0.1on Java 17, we found that initialization times out when the server immediately returns invalid JSON. The caller gets aTimeoutExceptioninstead of the JSON parsing error.Reproduction
Configure
HttpClientStreamableHttpTransportwith initialization and request timeouts both set to 5000 ms.When the server receives
initialize, immediately return:Subscribe to initialization and wait for its terminal signal.
We reproduced this through an application using the SDK client and its standard transport.
Expected and actual behavior
Expected: initialization fails as soon as parsing fails, with the original parsing exception preserved in the cause chain.
Actual: it fails about 5 seconds later with
TimeoutException. The parsing cause is missing from the terminal exception chain. No tool call is executed.What we found in the source
In
HttpClientStreamableHttpTransport.sendMessage, theapplication/jsonbranch callsdeliveredSink.success()beforedeserializeJsonRpcMessage().When parsing throws
IOException, the code wraps it inMcpTransportException. The outer error handler then callsdeliveredSink.error(...), but the sink has already completed successfully.McpClientSession.sendRequestregisters the request inpendingResponsesand relies on thesendMessageerror callback to remove it and signal failure. Since that callback doesn't receive the parsing error, the request keeps waiting until timeout or later cleanup.Relevant source:
Could the JSON branch defer its success signal until parsing succeeds, so parsing failures reach the caller and trigger pending-request cleanup?
We also tested HTTP 503, connection refusal, and a genuinely delayed response. Those behaved as expected; the invalid-JSON case was the one that lost its original failure.