Skip to content

Conversation

@devcrocod
Copy link
Contributor

quick fix #308

How Has This Been Tested?

locally

Breaking Changes

No

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

@devcrocod devcrocod requested review from Copilot and kpavlov October 9, 2025 16:51
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes the Streamable handling to properly support JSON-only responses, addressing issue #308. The change improves compatibility with servers that don't support Server-Sent Events (SSE) by checking the response content type.

  • Added content type checking for JSON responses in SSE error handling
  • Enhanced logging to distinguish between method not allowed and JSON-only mode
  • Graceful fallback when server returns JSON instead of SSE

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +237 to +239
val responseStatus = e.response?.status
val responseContentType = e.response?.contentType()
Copy link

Copilot AI Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider extracting the response object to a local variable to avoid repeated null-safe calls and improve readability: val response = e.response

Suggested change
val responseStatus = e.response?.status
val responseContentType = e.response?.contentType()
val response = e.response
val responseStatus = response?.status
val responseContentType = response?.contentType()

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@kpavlov kpavlov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests are needed.

@kpavlov kpavlov added the question Further information is requested label Oct 11, 2025
@kpavlov
Copy link
Contributor

kpavlov commented Oct 15, 2025

Let's create a test using the infrastructure introduced in #316

@devcrocod devcrocod force-pushed the devcrocod/fix-streamable-client-transport branch from 6485a21 to bc8be5d Compare October 20, 2025 11:36
@devcrocod devcrocod requested a review from kpavlov October 20, 2025 12:59
@kpavlov
Copy link
Contributor

kpavlov commented Oct 20, 2025

Let's merge #320, then this test is possible

package io.modelcontextprotocol.kotlin.sdk.client

import io.ktor.http.ContentType
import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities
import io.modelcontextprotocol.kotlin.sdk.Implementation
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.buildJsonObject
import kotlin.test.Test
import kotlin.time.Duration.Companion.seconds
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid

@OptIn(ExperimentalUuidApi::class)
internal class StreamingDisabledTest : AbstractStreamableHttpClientTest() {

    private suspend fun checkSupportNonStreamingResponse(contentType: ContentType, statusCode: HttpStatusCode) {
        val sessionId = "SID_${Uuid.random().toHexString()}"
        val clientName = "client-${Uuid.random().toHexString()}"
        val client = Client(
            clientInfo = Implementation(name = clientName, version = "1.0.0"),
            options = ClientOptions(
                capabilities = ClientCapabilities(),
            ),
        )

        mockMcp.onInitialize(clientName = clientName, sessionId = sessionId)

        mockMcp.handleJSONRPCRequest(
            jsonRpcMethod = "notifications/initialized",
            expectedSessionId = sessionId,
            sessionId = sessionId,
            statusCode = HttpStatusCode.Accepted,
        )

        mockMcp.onSubscribe(
            httpMethod = HttpMethod.Get,
            sessionId = sessionId,
        ) respondsWith {
            headers += MCP_SESSION_ID_HEADER to sessionId
            body = null
            httpStatus = statusCode
            this.contentType = contentType
        }

        mockMcp.handleWithResult(jsonRpcMethod = "ping", sessionId = sessionId) {
            buildJsonObject {}
        }

        mockMcp.mockUnsubscribeRequest(sessionId = sessionId)

        connect(client)

        delay(1.seconds)

        client.ping() // connection is still alive

        client.close()
    }

    @Test
    fun `Handle MethodNotAllowed`() = runBlocking {
        checkSupportNonStreamingResponse(
            ContentType.Text.EventStream,
            HttpStatusCode.MethodNotAllowed,
        )
    }

    @Test
    fun `Handle non-streaming response`() = runBlocking {
        checkSupportNonStreamingResponse(
            ContentType.Application.Json,
            HttpStatusCode.OK,
        )
    }
}

@devcrocod devcrocod force-pushed the devcrocod/fix-streamable-client-transport branch from bc8be5d to 0f60fa5 Compare October 21, 2025 09:17
@devcrocod devcrocod force-pushed the devcrocod/fix-streamable-client-transport branch from 0f60fa5 to 0666493 Compare October 21, 2025 11:38
kpavlov
kpavlov previously approved these changes Oct 21, 2025
@devcrocod devcrocod merged commit bfaf54e into main Oct 21, 2025
4 checks passed
@devcrocod devcrocod deleted the devcrocod/fix-streamable-client-transport branch October 21, 2025 12:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

question Further information is requested

Projects

None yet

Development

Successfully merging this pull request may close these issues.

StreamableHttpClientTransport fails when server uses JSON-only

3 participants