Skip to content

Commit b05232b

Browse files
committed
Add test with new schema types
1 parent 0e6a41d commit b05232b

File tree

64 files changed

+7288
-99
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+7288
-99
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.modelcontextprotocol.kotlin.sdk.client
22

3-
import io.modelcontextprotocol.kotlin.sdk.Implementation
4-
import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest
3+
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
4+
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest
55
import kotlinx.coroutines.test.runTest
66
import kotlinx.serialization.json.JsonObject
77
import kotlinx.serialization.json.boolean

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package io.modelcontextprotocol.kotlin.sdk.client
22

3-
import io.modelcontextprotocol.kotlin.sdk.CallToolResult
4-
import io.modelcontextprotocol.kotlin.sdk.Implementation
5-
import io.modelcontextprotocol.kotlin.sdk.InitializeResult
6-
import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
7-
import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest
8-
import io.modelcontextprotocol.kotlin.sdk.JSONRPCResponse
9-
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
103
import io.modelcontextprotocol.kotlin.sdk.shared.Transport
4+
import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
5+
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
6+
import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult
7+
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage
8+
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest
9+
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse
10+
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
1111
import kotlinx.coroutines.sync.Mutex
1212
import kotlinx.coroutines.sync.withLock
1313

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
package io.modelcontextprotocol.kotlin.sdk.client
2+
3+
import io.modelcontextprotocol.kotlin.sdk.Implementation
4+
import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest
5+
import kotlinx.coroutines.test.runTest
6+
import kotlinx.serialization.json.JsonObject
7+
import kotlinx.serialization.json.boolean
8+
import kotlinx.serialization.json.int
9+
import kotlinx.serialization.json.jsonPrimitive
10+
import kotlin.test.BeforeTest
11+
import kotlin.test.Test
12+
import kotlin.test.assertContains
13+
import kotlin.test.assertEquals
14+
import kotlin.test.assertFailsWith
15+
import kotlin.test.assertTrue
16+
17+
/**
18+
* Comprehensive test suite for MCP Client meta parameter functionality
19+
*
20+
* Tests cover:
21+
* - Meta key validation according to MCP specification
22+
* - JSON type conversion for various data types
23+
* - Error handling for invalid meta keys
24+
* - Integration with callTool method
25+
*/
26+
class OldSchemaClientMetaParameterTest {
27+
28+
private lateinit var client: Client
29+
private lateinit var mockTransport: OldSchemaMockTransport
30+
private val clientInfo = Implementation("test-client", "1.0.0")
31+
32+
@BeforeTest
33+
fun setup() = runTest {
34+
mockTransport = OldSchemaMockTransport()
35+
client = Client(clientInfo = clientInfo)
36+
mockTransport.setupInitializationResponse()
37+
client.connect(mockTransport)
38+
}
39+
40+
@Test
41+
fun `should accept valid meta keys without throwing exception`() = runTest {
42+
val validMeta = buildMap {
43+
put("simple-key", "value1")
44+
put("api.example.com/version", "1.0")
45+
put("com.company.app/setting", "enabled")
46+
put("retry_count", 3)
47+
put("user.preference", true)
48+
put("valid123", "alphanumeric")
49+
put("multi.dot.name", "multiple-dots")
50+
put("under_score", "underscore")
51+
put("hyphen-dash", "hyphen")
52+
put("org.apache.kafka/consumer-config", "complex-valid-prefix")
53+
}
54+
55+
val result = runCatching {
56+
client.callTool("test-tool", mapOf("arg" to "value"), validMeta)
57+
}
58+
59+
assertTrue(result.isSuccess, "Valid meta keys should not cause exceptions")
60+
mockTransport.lastJsonRpcRequest()?.let { request ->
61+
val params = request.params as JsonObject
62+
assertTrue(params.containsKey("_meta"), "Request should contain _meta field")
63+
val metaField = params["_meta"] as JsonObject
64+
65+
// Verify all meta keys are present
66+
assertEquals(validMeta.size, metaField.size, "All meta keys should be included")
67+
68+
// Verify specific key-value pairs
69+
assertEquals("value1", metaField["simple-key"]?.jsonPrimitive?.content)
70+
assertEquals("1.0", metaField["api.example.com/version"]?.jsonPrimitive?.content)
71+
assertEquals("enabled", metaField["com.company.app/setting"]?.jsonPrimitive?.content)
72+
assertEquals(3, metaField["retry_count"]?.jsonPrimitive?.int)
73+
assertEquals(true, metaField["user.preference"]?.jsonPrimitive?.boolean)
74+
assertEquals("alphanumeric", metaField["valid123"]?.jsonPrimitive?.content)
75+
assertEquals("multiple-dots", metaField["multi.dot.name"]?.jsonPrimitive?.content)
76+
assertEquals("underscore", metaField["under_score"]?.jsonPrimitive?.content)
77+
assertEquals("hyphen", metaField["hyphen-dash"]?.jsonPrimitive?.content)
78+
assertEquals("complex-valid-prefix", metaField["org.apache.kafka/consumer-config"]?.jsonPrimitive?.content)
79+
}
80+
}
81+
82+
@Test
83+
fun `should accept edge case valid prefixes and names`() = runTest {
84+
val edgeCaseValidMeta = buildMap {
85+
put("a/", "single-char-prefix-empty-name") // empty name is allowed
86+
put("a1-b2/test", "alphanumeric-hyphen-prefix")
87+
put("long.domain.name.here/config", "long-prefix")
88+
put("x/a", "minimal-valid-key")
89+
put("test123", "alphanumeric-name-only")
90+
}
91+
92+
val result = runCatching {
93+
client.callTool("test-tool", emptyMap(), edgeCaseValidMeta)
94+
}
95+
96+
assertTrue(result.isSuccess, "Edge case valid meta keys should be accepted")
97+
}
98+
99+
@Test
100+
fun `should reject mcp reserved prefix`() = runTest {
101+
val invalidMeta = mapOf("mcp/internal" to "value")
102+
103+
val exception = assertFailsWith<IllegalArgumentException> {
104+
client.callTool("test-tool", emptyMap(), invalidMeta)
105+
}
106+
107+
assertContains(
108+
charSequence = exception.message ?: "",
109+
other = "Invalid _meta key",
110+
)
111+
}
112+
113+
@Test
114+
fun `should reject modelcontextprotocol reserved prefix`() = runTest {
115+
val invalidMeta = mapOf("modelcontextprotocol/config" to "value")
116+
117+
val exception = assertFailsWith<IllegalArgumentException> {
118+
client.callTool("test-tool", emptyMap(), invalidMeta)
119+
}
120+
121+
assertContains(
122+
charSequence = exception.message ?: "",
123+
other = "Invalid _meta key",
124+
)
125+
}
126+
127+
@Test
128+
fun `should reject nested reserved prefixes`() = runTest {
129+
val invalidKeys = listOf(
130+
"api.mcp.io/setting",
131+
"com.modelcontextprotocol.test/value",
132+
"example.mcp/data",
133+
"subdomain.mcp.com/config",
134+
"app.modelcontextprotocol.dev/setting",
135+
"test.mcp/value",
136+
"service.modelcontextprotocol/data",
137+
)
138+
139+
invalidKeys.forEach { key ->
140+
val exception = assertFailsWith<IllegalArgumentException>(
141+
message = "Should reject nested reserved key: $key",
142+
) {
143+
client.callTool("test-tool", emptyMap(), mapOf(key to "value"))
144+
}
145+
assertContains(
146+
charSequence = exception.message ?: "",
147+
other = "Invalid _meta key",
148+
)
149+
}
150+
}
151+
152+
@Test
153+
fun `should reject case-insensitive reserved prefixes`() = runTest {
154+
val invalidKeys = listOf(
155+
"MCP/internal",
156+
"Mcp/config",
157+
"mCp/setting",
158+
"MODELCONTEXTPROTOCOL/data",
159+
"ModelContextProtocol/value",
160+
"modelContextProtocol/test",
161+
)
162+
163+
invalidKeys.forEach { key ->
164+
val exception = assertFailsWith<IllegalArgumentException>(
165+
message = "Should reject case-insensitive reserved key: $key",
166+
) {
167+
client.callTool("test-tool", emptyMap(), mapOf(key to "value"))
168+
}
169+
assertContains(
170+
charSequence = exception.message ?: "",
171+
other = "Invalid _meta key",
172+
)
173+
}
174+
}
175+
176+
@Test
177+
fun `should reject invalid key formats`() = runTest {
178+
val invalidKeys = listOf(
179+
"", // empty key - not allowed at key level
180+
"/invalid", // starts with slash
181+
"-invalid", // starts with hyphen
182+
".invalid", // starts with dot
183+
"in valid", // contains space
184+
"api../test", // consecutive dots
185+
"api./test", // label ends with dot
186+
)
187+
188+
invalidKeys.forEach { key ->
189+
assertFailsWith<IllegalArgumentException>(
190+
message = "Should reject invalid key format: '$key'",
191+
) {
192+
client.callTool("test-tool", emptyMap(), mapOf(key to "value"))
193+
}
194+
}
195+
}
196+
197+
@Test
198+
fun `should convert various data types to JSON correctly`() = runTest {
199+
val complexMeta = createComplexMetaData()
200+
201+
val result = runCatching {
202+
client.callTool(
203+
"test-tool",
204+
emptyMap(),
205+
complexMeta,
206+
)
207+
}
208+
209+
assertTrue(result.isSuccess, "Complex data type conversion should not throw exceptions")
210+
211+
mockTransport.lastJsonRpcRequest()?.let { request ->
212+
assertEquals("tools/call", request.method)
213+
val params = request.params as JsonObject
214+
assertTrue(params.containsKey("_meta"), "Request should contain _meta field")
215+
}
216+
}
217+
218+
@Test
219+
fun `should handle nested map structures correctly`() = runTest {
220+
val nestedMeta = buildNestedConfiguration()
221+
222+
val result = runCatching {
223+
client.callTool("test-tool", emptyMap(), nestedMeta)
224+
}
225+
226+
assertTrue(result.isSuccess)
227+
228+
mockTransport.lastJsonRpcRequest()?.let { request ->
229+
val params = request.params as JsonObject
230+
val metaField = params["_meta"] as JsonObject
231+
assertTrue(metaField.containsKey("config"))
232+
}
233+
}
234+
235+
@Test
236+
fun `should include empty meta object when meta parameter not provided`() = runTest {
237+
client.callTool("test-tool", mapOf("arg" to "value"))
238+
239+
mockTransport.lastJsonRpcRequest()?.let { request ->
240+
val params = request.params as JsonObject
241+
val metaField = params["_meta"] as JsonObject
242+
assertTrue(metaField.isEmpty(), "Meta field should be empty when not provided")
243+
}
244+
}
245+
246+
private fun createComplexMetaData(): Map<String, Any?> = buildMap {
247+
put("string", "text")
248+
put("number", 42)
249+
put("boolean", true)
250+
put("null_value", null)
251+
put("list", listOf(1, 2, 3))
252+
put("map", mapOf("nested" to "value"))
253+
put("enum", "STRING")
254+
put("int_array", intArrayOf(1, 2, 3))
255+
}
256+
257+
private fun buildNestedConfiguration(): Map<String, Any> = buildMap {
258+
put(
259+
"config",
260+
buildMap {
261+
put(
262+
"database",
263+
buildMap {
264+
put("host", "localhost")
265+
put("port", 5432)
266+
},
267+
)
268+
put("features", listOf("feature1", "feature2"))
269+
},
270+
)
271+
}
272+
}
273+
274+
suspend fun OldSchemaMockTransport.lastJsonRpcRequest(): JSONRPCRequest? =
275+
getSentMessages().lastOrNull() as? JSONRPCRequest

0 commit comments

Comments
 (0)