Skip to content

Commit 7e087b4

Browse files
committed
Fix compilation and tests for meta.
small refactoring
1 parent 4a1a29e commit 7e087b4

File tree

3 files changed

+52
-52
lines changed

3 files changed

+52
-52
lines changed

kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt

Lines changed: 46 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ import kotlinx.collections.immutable.minus
5151
import kotlinx.collections.immutable.persistentMapOf
5252
import kotlinx.collections.immutable.toPersistentSet
5353
import kotlinx.serialization.ExperimentalSerializationApi
54-
import kotlinx.serialization.json.JsonArray
5554
import kotlinx.serialization.json.JsonElement
5655
import kotlinx.serialization.json.JsonNull
5756
import kotlinx.serialization.json.JsonObject
5857
import kotlinx.serialization.json.JsonPrimitive
58+
import kotlinx.serialization.json.add
5959
import kotlinx.serialization.json.buildJsonArray
6060
import kotlinx.serialization.json.buildJsonObject
6161
import kotlin.coroutines.cancellation.CancellationException
@@ -189,20 +189,14 @@ public open class Client(private val clientInfo: Implementation, options: Client
189189
}
190190
}
191191

192-
Method.Defined.PromptsGet,
193-
Method.Defined.PromptsList,
194-
Method.Defined.CompletionComplete,
195-
-> {
192+
Method.Defined.PromptsGet, Method.Defined.PromptsList, Method.Defined.CompletionComplete -> {
196193
if (serverCapabilities?.prompts == null) {
197194
throw IllegalStateException("Server does not support prompts (required for $method)")
198195
}
199196
}
200197

201-
Method.Defined.ResourcesList,
202-
Method.Defined.ResourcesTemplatesList,
203-
Method.Defined.ResourcesRead,
204-
Method.Defined.ResourcesSubscribe,
205-
Method.Defined.ResourcesUnsubscribe,
198+
Method.Defined.ResourcesList, Method.Defined.ResourcesTemplatesList,
199+
Method.Defined.ResourcesRead, Method.Defined.ResourcesSubscribe, Method.Defined.ResourcesUnsubscribe,
206200
-> {
207201
val resCaps = serverCapabilities?.resources
208202
?: error("Server does not support resources (required for $method)")
@@ -214,17 +208,13 @@ public open class Client(private val clientInfo: Implementation, options: Client
214208
}
215209
}
216210

217-
Method.Defined.ToolsCall,
218-
Method.Defined.ToolsList,
219-
-> {
211+
Method.Defined.ToolsCall, Method.Defined.ToolsList -> {
220212
if (serverCapabilities?.tools == null) {
221213
throw IllegalStateException("Server does not support tools (required for $method)")
222214
}
223215
}
224216

225-
Method.Defined.Initialize,
226-
Method.Defined.Ping,
227-
-> {
217+
Method.Defined.Initialize, Method.Defined.Ping -> {
228218
// No specific capability required
229219
}
230220

@@ -604,31 +594,37 @@ public open class Client(private val clientInfo: Implementation, options: Client
604594
private fun validateMetaKeys(keys: Set<String>) {
605595
val labelPattern = Regex("[a-zA-Z]([a-zA-Z0-9-]*[a-zA-Z0-9])?")
606596
val namePattern = Regex("[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?")
607-
597+
608598
keys.forEach { key ->
609599
require(key.isNotEmpty()) { "Meta key cannot be empty" }
610-
600+
611601
val (prefix, name) = key.split('/', limit = 2).let { parts ->
612602
when (parts.size) {
613603
1 -> null to parts[0]
614-
else -> parts[0] to parts[1]
604+
2 -> parts[0] to parts[1]
605+
else -> throw IllegalArgumentException("Unexpected split result for key: $key")
615606
}
616607
}
617-
608+
618609
// Validate prefix if present
619610
prefix?.let {
620611
require(it.isNotEmpty()) { "Invalid _meta key '$key': prefix cannot be empty" }
621-
612+
622613
val labels = it.split('.')
623614
require(labels.all { label -> label.matches(labelPattern) }) {
624615
"Invalid _meta key '$key': prefix labels must start with a letter, end with letter/digit, and contain only letters, digits, or hyphens"
625616
}
626-
627-
require(labels.none { label -> label.equals("modelcontextprotocol", ignoreCase = true) || label.equals("mcp", ignoreCase = true) }) {
617+
618+
require(
619+
labels.none { label ->
620+
label.equals("modelcontextprotocol", ignoreCase = true) ||
621+
label.equals("mcp", ignoreCase = true)
622+
},
623+
) {
628624
"Invalid _meta key '$key': prefix cannot contain reserved labels 'modelcontextprotocol' or 'mcp'"
629625
}
630626
}
631-
627+
632628
// Validate name (empty allowed)
633629
require(name.isEmpty() || name.matches(namePattern)) {
634630
"Invalid _meta key '$key': name must start and end with alphanumeric characters, and contain only alphanumerics, hyphens, underscores, or dots"
@@ -648,45 +644,53 @@ public open class Client(private val clientInfo: Implementation, options: Client
648644
@OptIn(ExperimentalUnsignedTypes::class, ExperimentalSerializationApi::class)
649645
private fun convertToJsonElement(value: Any?): JsonElement = when (value) {
650646
null -> JsonNull
647+
651648
is JsonElement -> value
649+
652650
is String -> JsonPrimitive(value)
651+
653652
is Number -> JsonPrimitive(value)
653+
654654
is Boolean -> JsonPrimitive(value)
655+
655656
is Char -> JsonPrimitive(value.toString())
657+
656658
is Enum<*> -> JsonPrimitive(value.name)
657659

658-
is Map<*, *> -> buildJsonObject {
659-
value.forEach { (k, v) ->
660-
put(k.toString(), convertToJsonElement(v))
661-
}
662-
}
660+
is Map<*, *> -> buildJsonObject { value.forEach { (k, v) -> put(k.toString(), convertToJsonElement(v)) } }
663661

664-
is Collection<*> -> buildJsonArray {
665-
value.forEach { add(convertToJsonElement(it)) }
666-
}
662+
is Collection<*> -> buildJsonArray { value.forEach { add(convertToJsonElement(it)) } }
667663

668-
is Array<*> -> buildJsonArray {
669-
value.forEach { add(convertToJsonElement(it)) }
670-
}
664+
is Array<*> -> buildJsonArray { value.forEach { add(convertToJsonElement(it)) } }
671665

672-
// Primitive arrays - use iterator for unified handling
666+
// Primitive arrays
673667
is IntArray -> buildJsonArray { value.forEach { add(it) } }
668+
674669
is LongArray -> buildJsonArray { value.forEach { add(it) } }
670+
675671
is FloatArray -> buildJsonArray { value.forEach { add(it) } }
672+
676673
is DoubleArray -> buildJsonArray { value.forEach { add(it) } }
674+
677675
is BooleanArray -> buildJsonArray { value.forEach { add(it) } }
676+
678677
is ShortArray -> buildJsonArray { value.forEach { add(it) } }
678+
679679
is ByteArray -> buildJsonArray { value.forEach { add(it) } }
680+
680681
is CharArray -> buildJsonArray { value.forEach { add(it.toString()) } }
681682

682-
// ExperimentalUnsignedTypes
683-
is UIntArray -> buildJsonArray { value.forEach { add(it) } }
684-
is ULongArray -> buildJsonArray { value.forEach { add(it) } }
685-
is UShortArray -> buildJsonArray { value.forEach { add(it) } }
686-
is UByteArray -> buildJsonArray { value.forEach { add(it) } }
683+
// Unsigned arrays
684+
is UIntArray -> buildJsonArray { value.forEach { add(JsonPrimitive(it)) } }
685+
686+
is ULongArray -> buildJsonArray { value.forEach { add(JsonPrimitive(it)) } }
687+
688+
is UShortArray -> buildJsonArray { value.forEach { add(JsonPrimitive(it)) } }
689+
690+
is UByteArray -> buildJsonArray { value.forEach { add(JsonPrimitive(it)) } }
687691

688692
else -> {
689-
logger.debug { "Converting unknown type ${value::class.simpleName} to string: $value" }
693+
logger.debug { "Converting unknown type ${value::class} to string: $value" }
690694
JsonPrimitive(value.toString())
691695
}
692696
}

kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientMetaParameterTest.kt

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class ClientMetaParameterTest {
8383
fun `should reject mcp reserved prefix`() = runTest {
8484
val invalidMeta = mapOf("mcp/internal" to "value")
8585

86-
val exception = assertFailsWith<Error> {
86+
val exception = assertFailsWith<IllegalArgumentException> {
8787
client.callTool("test-tool", emptyMap(), invalidMeta)
8888
}
8989

@@ -97,7 +97,7 @@ class ClientMetaParameterTest {
9797
fun `should reject modelcontextprotocol reserved prefix`() = runTest {
9898
val invalidMeta = mapOf("modelcontextprotocol/config" to "value")
9999

100-
val exception = assertFailsWith<Error> {
100+
val exception = assertFailsWith<IllegalArgumentException> {
101101
client.callTool("test-tool", emptyMap(), invalidMeta)
102102
}
103103

@@ -120,7 +120,7 @@ class ClientMetaParameterTest {
120120
)
121121

122122
invalidKeys.forEach { key ->
123-
val exception = assertFailsWith<Error>(
123+
val exception = assertFailsWith<IllegalArgumentException>(
124124
message = "Should reject nested reserved key: $key",
125125
) {
126126
client.callTool("test-tool", emptyMap(), mapOf(key to "value"))
@@ -144,7 +144,7 @@ class ClientMetaParameterTest {
144144
)
145145

146146
invalidKeys.forEach { key ->
147-
val exception = assertFailsWith<Error>(
147+
val exception = assertFailsWith<IllegalArgumentException>(
148148
message = "Should reject case-insensitive reserved key: $key",
149149
) {
150150
client.callTool("test-tool", emptyMap(), mapOf(key to "value"))
@@ -169,15 +169,11 @@ class ClientMetaParameterTest {
169169
)
170170

171171
invalidKeys.forEach { key ->
172-
val exception = assertFailsWith<Error>(
172+
assertFailsWith<IllegalArgumentException>(
173173
message = "Should reject invalid key format: '$key'",
174174
) {
175175
client.callTool("test-tool", emptyMap(), mapOf(key to "value"))
176176
}
177-
assertContains(
178-
charSequence = exception.message ?: "",
179-
other = "Invalid _meta key",
180-
)
181177
}
182178
}
183179

kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() {
484484
"result" : 11.0,
485485
"formattedResult" : "11,000",
486486
"precision" : 3,
487-
"tags" : [ ]
487+
"tags" : ["test", "calculator", "integration"]
488488
}
489489
""".trimIndent()
490490

0 commit comments

Comments
 (0)