Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
import io.modelcontextprotocol.kotlin.sdk.client.Client
import io.modelcontextprotocol.kotlin.sdk.client.SseClientTransport
import io.modelcontextprotocol.kotlin.sdk.integration.utils.Retry
import io.modelcontextprotocol.kotlin.sdk.integration.utils.port
import io.modelcontextprotocol.kotlin.sdk.server.Server
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
import io.modelcontextprotocol.kotlin.sdk.server.mcp
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.awaitility.kotlin.await
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import kotlin.time.Duration.Companion.seconds
Expand All @@ -40,12 +40,7 @@ abstract class KotlinTestBase {
@BeforeEach
fun setUp() {
setupServer()
await
.ignoreExceptions()
.until {
port = runBlocking { serverEngine.engine.resolvedConnectors().first().port }
port != 0
}
port = serverEngine.port()
runBlocking {
setupClient()
}
Expand Down Expand Up @@ -74,7 +69,7 @@ abstract class KotlinTestBase {

configureServer()

serverEngine = embeddedServer(ServerCIO, host = host, port = port) {
serverEngine = embeddedServer(ServerCIO, host = host, port = 0) {
install(ServerSSE)
routing {
mcp { server }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
import io.modelcontextprotocol.kotlin.sdk.TextContent
import io.modelcontextprotocol.kotlin.sdk.TextResourceContents
import io.modelcontextprotocol.kotlin.sdk.Tool
import io.modelcontextprotocol.kotlin.sdk.integration.utils.port
import io.modelcontextprotocol.kotlin.sdk.server.Server
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport
Expand Down Expand Up @@ -65,10 +66,10 @@ class KotlinServerForTypeScriptClient {
private val jsonFormat = Json { ignoreUnknownKeys = true }
private var server: EmbeddedServer<*, *>? = null

fun start(port: Int = 3000) {
logger.info { "Starting HTTP server on port $port" }
fun start(): Int {
logger.info { "Starting HTTP server on random port" }

server = embeddedServer(CIO, port = port) {
server = embeddedServer(CIO, port = 0) {
routing {
get("/mcp") {
val sessionId = call.request.header("mcp-session-id")
Expand Down Expand Up @@ -186,7 +187,9 @@ class KotlinServerForTypeScriptClient {
}
}

server?.start(wait = false)
val theServer = requireNotNull(server) { "Server must be created" }
theServer.start(wait = false)
return theServer.port()
}

fun stop() {
Expand Down Expand Up @@ -357,6 +360,8 @@ class HttpServerTransport(private val sessionId: String) : AbstractTransport() {
write("\n\n")
flush()
}
} catch (e: CancellationException) {
logger.info(e) { e.message }
} catch (e: Exception) {
logger.warn(e) { "SSE stream terminated for session: $sessionId" }
} finally {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
package io.modelcontextprotocol.kotlin.sdk.integration.typescript

import kotlinx.coroutines.test.runTest
import org.awaitility.kotlin.await
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
import java.util.concurrent.TimeUnit
import kotlin.test.Ignore
import kotlin.test.assertTrue
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration

class TypeScriptClientKotlinServerTest : TypeScriptTestBase() {

private var port: Int = 0
private lateinit var serverUrl: String
private var httpServer: KotlinServerForTypeScriptClient? = null

@BeforeEach
fun setUp() {
port = findFreePort()
serverUrl = "http://localhost:$port/mcp"
killProcessOnPort(port)
httpServer = KotlinServerForTypeScriptClient()
httpServer?.start(port)
if (!waitForPort(port = port)) {
throw IllegalStateException("Kotlin test server did not become ready on localhost:$port within timeout")
}
val port = httpServer!!.start()
serverUrl = "http://localhost:$port/mcp"
println("Kotlin server started on port $port")
}

Expand Down Expand Up @@ -145,10 +143,14 @@ class TypeScriptClientKotlinServerTest : TypeScriptTestBase() {
}
threads.add(thread)
thread.start()
Thread.sleep(500)
}

threads.forEach { it.join() }
await
.pollInterval(100.milliseconds.toJavaDuration())
.atMost(30.seconds.toJavaDuration())
.until {
outputs.size == clientCount
}

if (exceptions.isNotEmpty()) {
println(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,9 @@ class TypeScriptEdgeCasesTest : TypeScriptTestBase() {

@BeforeEach
fun setUp() {
port = findFreePort()
serverUrl = "http://localhost:$port/mcp"
killProcessOnPort(port)
httpServer = KotlinServerForTypeScriptClient()
httpServer?.start(port)
if (!waitForPort(port = port)) {
throw IllegalStateException("Kotlin test server did not become ready on localhost:$port within timeout")
}
port = httpServer!!.start()
serverUrl = "http://localhost:$port/mcp"
println("Kotlin server started on port $port")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ abstract class TypeScriptTestBase {
.redirectErrorStream(true)
.start()

val output = StringBuilder()
val output = StringBuffer()
BufferedReader(InputStreamReader(process.inputStream)).use { reader ->
var line: String?
while (reader.readLine().also { line = it } != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.modelcontextprotocol.kotlin.sdk.integration.utils

import io.ktor.server.engine.EmbeddedServer
import kotlinx.coroutines.runBlocking
import org.awaitility.kotlin.await

internal fun EmbeddedServer<*, *>.port(): Int {
var port = 0
val server = this
await
.ignoreExceptions()
.until {
port = runBlocking { server.engine.resolvedConnectors().first().port }
port != 0
}
return port
}
Loading