Skip to content
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.modelcontextprotocol.kotlin.sdk.integration.kotlin

import io.kotest.assertions.json.shouldEqualJson
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest
import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
Expand All @@ -9,6 +10,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.ImageContent
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
import io.modelcontextprotocol.kotlin.sdk.types.TextContent
import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
import io.modelcontextprotocol.kotlin.sdk.types.invoke
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
Expand All @@ -28,6 +30,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue

@OptIn(ExperimentalMcpApi::class)
abstract class AbstractToolIntegrationTest : KotlinTestBase() {
private val testToolName = "echo"
private val testToolDescription = "A simple echo tool that returns the input text"
Expand Down Expand Up @@ -84,12 +87,12 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() {
) { request ->
val text = (request.params.arguments?.get("text") as? JsonPrimitive)?.content ?: "No text provided"

CallToolResult(
content = listOf(TextContent(text = "Echo: $text")),
structuredContent = buildJsonObject {
CallToolResult {
textContent("Echo: $text")
structuredContent {
put("result", text)
},
)
}
}
}
}

Expand All @@ -112,12 +115,12 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() {
) { request ->
val text = (request.params.arguments?.get("text") as? JsonPrimitive)?.content ?: "No text provided"

CallToolResult(
content = listOf(TextContent(text = "Echo: $text")),
structuredContent = buildJsonObject {
CallToolResult {
textContent("Echo: $text")
structuredContent {
put("result", text)
},
)
}
}
}

server.addTool(
Expand Down Expand Up @@ -164,9 +167,7 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() {
val delay = (request.params.arguments?.get("delay") as? JsonPrimitive)?.content?.toIntOrNull() ?: 1000

// simulate slow operation
runBlocking {
delay(delay.toLong())
}
delay(delay.toLong())

CallToolResult(
content = listOf(TextContent(text = "Completed after ${delay}ms delay")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import io.ktor.server.routing.delete
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.routing
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
import io.modelcontextprotocol.kotlin.sdk.server.Server
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport
Expand All @@ -40,6 +41,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
import io.modelcontextprotocol.kotlin.sdk.types.TextContent
import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents
import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
import io.modelcontextprotocol.kotlin.sdk.types.invoke
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.channels.Channel
Expand All @@ -59,6 +61,7 @@ import java.util.concurrent.ConcurrentHashMap

private val logger = KotlinLogging.logger {}

@OptIn(ExperimentalMcpApi::class)
class KotlinServerForTsClient {
private val serverTransports = ConcurrentHashMap<String, HttpServerTransport>()
private val jsonFormat = Json { ignoreUnknownKeys = true }
Expand Down Expand Up @@ -173,9 +176,7 @@ class KotlinServerForTsClient {
logger.info { "Terminating session: $sessionId" }
val transport = serverTransports[sessionId]!!
serverTransports.remove(sessionId)
runBlocking {
transport.close()
}
transport.close()
call.respond(HttpStatusCode.OK)
} else {
logger.warn { "Invalid session termination request: $sessionId" }
Expand Down Expand Up @@ -226,12 +227,12 @@ class KotlinServerForTsClient {
),
) { request ->
val name = (request.params.arguments?.get("name") as? JsonPrimitive)?.content ?: "World"
CallToolResult(
content = listOf(TextContent("Hello, $name!")),
structuredContent = buildJsonObject {
CallToolResult {
textContent("Hello, $name!")
structuredContent {
put("greeting", JsonPrimitive("Hello, $name!"))
},
)
}
}
}

server.addTool(
Expand All @@ -252,13 +253,13 @@ class KotlinServerForTsClient {
) { request ->
val name = (request.params.arguments?.get("name") as? JsonPrimitive)?.content ?: "World"

CallToolResult(
content = listOf(TextContent("Multiple greetings sent to $name!")),
structuredContent = buildJsonObject {
CallToolResult {
textContent("Multiple greetings sent to $name!")
structuredContent {
put("greeting", JsonPrimitive("Multiple greetings sent to $name!"))
put("notificationCount", JsonPrimitive(3))
},
)
}
}
}

server.addPrompt(
Expand Down
Loading
Loading