FEAT: Support all Tier 1, 2 and 3 targets by Kotlin & remove dependency on Kotest Json due to not supporting all targets (#151)

Co-authored-by: Leonid Stashevsky <e5l@users.noreply.github.com>
This commit is contained in:
Simon Vergauwen 2025-07-17 09:50:50 +02:00 • committed by GitHub
commit 21aec46cd0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 93 additions and 56 deletions

View file

@ -1,20 +1,20 @@
package io.modelcontextprotocol.kotlin.sdk
import io.kotest.assertions.json.shouldEqualJson
import io.modelcontextprotocol.kotlin.sdk.shared.McpJson
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.encodeToJsonElement
import kotlinx.serialization.json.put
import kotlin.test.Test
import kotlin.test.assertEquals
class AudioContentSerializationTest {
private val audioContentJson = """
{
"data": "base64-encoded-audio-data",
"mimeType": "audio/wav",
"type": "audio"
}
""".trimIndent()
private val audioContentJson = buildJsonObject {
put("data", "base64-encoded-audio-data")
put("mimeType", "audio/wav")
put("type", "audio")
}
private val audioContent = AudioContent(
data = "base64-encoded-audio-data",
@ -23,12 +23,17 @@ class AudioContentSerializationTest {
@Test
fun `should serialize audio content`() {
McpJson.encodeToString(audioContent) shouldEqualJson audioContentJson
val actual = McpJson.encodeToJsonElement(audioContent)
assertEquals(
audioContentJson,
actual,
"Expected $actual to be equal to $audioContentJson"
)
}
@Test
fun `should deserialize audio content`() {
val content = McpJson.decodeFromString<AudioContent>(audioContentJson)
val content = McpJson.decodeFromJsonElement<AudioContent>(audioContentJson)
assertEquals(expected = audioContent, actual = content)
}
}

View file

@ -1,51 +1,53 @@
package io.modelcontextprotocol.kotlin.sdk
import io.kotest.assertions.json.shouldEqualJson
import io.modelcontextprotocol.kotlin.sdk.shared.McpJson
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.encodeToJsonElement
import kotlin.test.Test
import kotlin.test.assertEquals
class ToolSerializationTest {
// see https://docs.anthropic.com/en/docs/build-with-claude/tool-use
/* language=json */
private val getWeatherToolJson = """
{
"name": "get_weather",
"description": "Get the current weather in a given location",
"inputSchema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
}
},
"required": ["location"]
},
"outputSchema": {
"type": "object",
"properties": {
"temperature": {
"type": "number",
"description": "Temperature in celsius"
},
"conditions": {
"type": "string",
"description": "Weather conditions description"
},
"humidity": {
"type": "number",
"description": "Humidity percentage"
}
},
"required": ["temperature", "conditions", "humidity"]
}
}
""".trimIndent()
private val getWeatherToolJson = buildJsonObject {
put("name", JsonPrimitive("get_weather"))
put("description", JsonPrimitive("Get the current weather in a given location"))
put("inputSchema", buildJsonObject {
put("properties", buildJsonObject {
put("location", buildJsonObject {
put("type", JsonPrimitive("string"))
put("description", JsonPrimitive("The city and state, e.g. San Francisco, CA"))
})
})
put("required", JsonArray(listOf(JsonPrimitive("location"))))
put("type", JsonPrimitive("object"))
})
put("outputSchema", buildJsonObject {
put("type", JsonPrimitive("object"))
put("properties", buildJsonObject {
put("temperature", buildJsonObject {
put("type", JsonPrimitive("number"))
put("description", JsonPrimitive("Temperature in celsius"))
})
put("conditions", buildJsonObject {
put("type", JsonPrimitive("string"))
put("description", JsonPrimitive("Weather conditions description"))
})
put("humidity", buildJsonObject {
put("type", JsonPrimitive("number"))
put("description", JsonPrimitive("Humidity percentage"))
})
})
put(
"required",
JsonArray(listOf(JsonPrimitive("temperature"), JsonPrimitive("conditions"), JsonPrimitive("humidity")))
)
})
}
val getWeatherTool = Tool(
name = "get_weather",
@ -81,12 +83,18 @@ class ToolSerializationTest {
@Test
fun `should serialize get_weather tool`() {
McpJson.encodeToString(getWeatherTool) shouldEqualJson getWeatherToolJson
val actual = McpJson.encodeToJsonElement(getWeatherTool)
assertEquals(
getWeatherToolJson,
actual,
"Expected $actual to be equal to $getWeatherToolJson"
)
}
@Test
fun `should deserialize get_weather tool`() {
val tool = McpJson.decodeFromString<Tool>(getWeatherToolJson)
val tool = McpJson.decodeFromJsonElement<Tool>(getWeatherToolJson)
assertEquals(expected = getWeatherTool, actual = tool)
}
@ -95,6 +103,11 @@ class ToolSerializationTest {
val json = Json(from = McpJson) {
encodeDefaults = false
}
json.encodeToString(getWeatherTool) shouldEqualJson getWeatherToolJson
val actual = json.encodeToJsonElement(getWeatherTool)
assertEquals(
getWeatherToolJson,
actual,
"Expected $actual to be equal to $getWeatherToolJson"
)
}
}

View file

@ -4,7 +4,6 @@ import io.ktor.utils.io.charsets.Charsets
import io.ktor.utils.io.core.toByteArray
import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlin.test.Test
import kotlin.test.assertEquals

View file

@ -0,0 +1,8 @@
package io.modelcontextprotocol.kotlin.sdk.internal
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
internal actual val IODispatcher: CoroutineDispatcher
get() = Dispatchers.IO