- 
                Notifications
    
You must be signed in to change notification settings  - Fork 174
 
Fix Streamable handling to support JSON-only responses #309
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this 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.
| val responseStatus = e.response?.status | ||
| val responseContentType = e.response?.contentType() | 
    
      
    
      Copilot
AI
    
    
    
      Oct 9, 2025 
    
  
There was a problem hiding this comment.
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
| val responseStatus = e.response?.status | |
| val responseContentType = e.response?.contentType() | |
| val response = e.response | |
| val responseStatus = response?.status | |
| val responseContentType = response?.contentType() | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests are needed.
| 
           Let's create a test using the infrastructure introduced in #316  | 
    
6485a21    to
    bc8be5d      
    Compare
  
    | 
           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,
        )
    }
}
 | 
    
bc8be5d    to
    0f60fa5      
    Compare
  
    0f60fa5    to
    0666493      
    Compare
  
    
quick fix #308
How Has This Been Tested?
locally
Breaking Changes
No
Types of changes
Checklist