Skip to content

Client.connect with StreamableHttpClientTransport hangs indefinitely on invalid JSON response #226

@AdrianMiska

Description

@AdrianMiska

Describe the bug
When McpJson.decodeFromString fails inside StreamableHttpClientTransport .send or .handleInlineSse, the Client/Protocol is left waiting on a CompletableDeferred that never completes. As a result, the program hangs indefinitely with no indication of the underlying error.

To Reproduce
Steps to reproduce the behavior:

  1. Create a Client
  2. Create a StreamableHttpClientTransport
  3. Have the MCP server respond with invalid JSON (or trigger another deserialization error, e.g. same as in Request deserilization is broken / throwing exception in version 0.6.0 #189).
  4. Call client.connect(transport)

Observed behavior
The program hangs indefinitely without surfacing the decoding failure.

Expected behavior
client.connect should fail by throwing an exception, making the error visible to the caller.

Additional context
What's happening is that StreamableHttpClientTransport passes the exception to its onError callback without re-throwing it. Meanwhile, Protocol is still awaiting completion of its CompletableDeferred, which only resolves when an actual message is received. Since that never happens, the await call blocks forever.

Test to reproduce

@Test
fun testClientConnectWithInvalidJson() = runTest {
    // Transport under test: respond with invalid JSON for the initialize request
    val transport = createTransport { _ ->
        respond(
            "this is not valid json",
            status = HttpStatusCode.OK,
            headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString()),
        )
    }

    val client = Client(
        clientInfo = io.modelcontextprotocol.kotlin.sdk.Implementation(
            name = "test-client",
            version = "1.0",
        ),
    )

    try {
        // Real time-keeping is needed; otherwise Protocol will always throw TimeoutCancellationException in tests
        withContext(Dispatchers.Default.limitedParallelism(1)) {
            kotlinx.coroutines.withTimeout(5.seconds) {
                client.connect(transport)
            }
        }
        kotlin.test.fail("Expected client.connect to fail on invalid JSON response")
    } catch (e: TimeoutCancellationException) {
        // Current behavior: result.await() in Protocol is never resolved, leading to a hang.
        kotlin.test.fail("Client connect caused a hang", e)
    } catch (_: IllegalStateException) {
        // Expected behavior: connect finishes and fails with an exception.
    } catch (e: Throwable) {
        kotlin.test.fail("Unexpected exception during client.connect", e)
    } finally {
        transport.close()
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions