11package io.modelcontextprotocol.kotlin.sdk.client
22
33import io.kotest.matchers.collections.shouldContain
4+ import io.ktor.http.ContentType
45import io.ktor.http.HttpMethod
56import io.ktor.http.HttpStatusCode
67import io.ktor.sse.ServerSentEvent
@@ -10,20 +11,24 @@ import io.modelcontextprotocol.kotlin.sdk.Tool
1011import kotlinx.coroutines.delay
1112import kotlinx.coroutines.flow.flow
1213import kotlinx.coroutines.runBlocking
14+ import kotlinx.coroutines.test.runTest
1315import kotlinx.serialization.json.buildJsonObject
1416import kotlinx.serialization.json.put
1517import kotlinx.serialization.json.putJsonObject
1618import org.junit.jupiter.api.TestInstance
17- import java.util.UUID
1819import kotlin.test.Test
1920import kotlin.time.Duration.Companion.milliseconds
21+ import kotlin.time.Duration.Companion.seconds
22+ import kotlin.uuid.ExperimentalUuidApi
23+ import kotlin.uuid.Uuid
2024
2125/* *
2226 * Integration tests for the `StreamableHttpClientTransport` implementation
2327 * using the [Mokksy](https://mokksy.dev) library
2428 * to simulate Streaming HTTP with server-sent events (SSE).
2529 * @author Konstantin Pavlov
2630 */
31+ @OptIn(ExperimentalUuidApi ::class )
2732@TestInstance(TestInstance .Lifecycle .PER_CLASS )
2833@Suppress(" LongMethod" )
2934internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest () {
@@ -40,7 +45,7 @@ internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() {
4045 ),
4146 )
4247
43- val sessionId = UUID .randomUUID ().toString()
48+ val sessionId = Uuid .random ().toString()
4449
4550 mockMcp.onInitialize(
4651 clientName = " client1" ,
@@ -151,17 +156,32 @@ internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() {
151156 }
152157
153158 @Test
154- fun `handle streaming not supported` () = runBlocking {
159+ fun `handle MethodNotAllowed` () = runTest {
160+ checkSupportNonStreamingResponse(
161+ ContentType .Text .EventStream ,
162+ HttpStatusCode .MethodNotAllowed ,
163+ )
164+ }
165+
166+ @Test
167+ fun `handle non-streaming response` () = runTest {
168+ checkSupportNonStreamingResponse(
169+ ContentType .Application .Json ,
170+ HttpStatusCode .OK ,
171+ )
172+ }
173+
174+ private suspend fun checkSupportNonStreamingResponse (contentType : ContentType , statusCode : HttpStatusCode ) {
175+ val sessionId = " SID_${Uuid .random().toHexString()} "
176+ val clientName = " client-${Uuid .random().toHexString()} "
155177 val client = Client (
156- clientInfo = Implementation (name = " client2 " , version = " 1.0.0" ),
178+ clientInfo = Implementation (name = clientName , version = " 1.0.0" ),
157179 options = ClientOptions (
158180 capabilities = ClientCapabilities (),
159181 ),
160182 )
161183
162- val sessionId = UUID .randomUUID().toString()
163-
164- mockMcp.onInitialize(clientName = " client2" , sessionId = sessionId)
184+ mockMcp.onInitialize(clientName = clientName, sessionId = sessionId)
165185
166186 mockMcp.handleJSONRPCRequest(
167187 jsonRpcMethod = " notifications/initialized" ,
@@ -176,13 +196,22 @@ internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() {
176196 ) respondsWith {
177197 headers + = MCP_SESSION_ID_HEADER to sessionId
178198 body = null
179- httpStatus = HttpStatusCode .UnsupportedMediaType
199+ httpStatus = statusCode
200+ this .contentType = contentType
201+ }
202+
203+ mockMcp.handleWithResult(jsonRpcMethod = " ping" , sessionId = sessionId) {
204+ buildJsonObject {}
180205 }
181206
182207 mockMcp.mockUnsubscribeRequest(sessionId = sessionId)
183208
184209 connect(client)
185210
211+ delay(1 .seconds)
212+
213+ client.ping() // connection is still alive
214+
186215 client.close()
187216 }
188217}
0 commit comments