@@ -70,172 +70,156 @@ class KotlinClientTypeScriptServerEdgeCasesTest : TypeScriptTestBase() {
7070 @Test
7171 @Timeout(30 , unit = TimeUnit .SECONDS )
7272 fun testNonExistentTool () = runTest {
73- client = HttpClient ( CIO ) {
74- install( SSE )
75- }.mcpStreamableHttp(serverUrl )
73+ withClient(serverUrl ) { client ->
74+ val nonExistentToolName = " non-existent-tool "
75+ val arguments = mapOf ( " name " to " TestUser " )
7676
77- val nonExistentToolName = " non-existent-tool"
78- val arguments = mapOf (" name" to " TestUser" )
77+ val exception = assertThrows<IllegalStateException > {
78+ client.callTool(nonExistentToolName, arguments)
79+ }
7980
80- val exception = assertThrows<IllegalStateException > {
81- client.callTool(nonExistentToolName, arguments)
81+ val expectedMessage =
82+ " JSONRPCError(code=InvalidParams, message=MCP error -32602: Tool non-existent-tool not found, data={})"
83+ assertEquals(
84+ expectedMessage,
85+ exception.message,
86+ " Unexpected error message for non-existent tool" ,
87+ )
8288 }
83-
84- val expectedMessage =
85- " JSONRPCError(code=InvalidParams, message=MCP error -32602: Tool non-existent-tool not found, data={})"
86- assertEquals(
87- expectedMessage,
88- exception.message,
89- " Unexpected error message for non-existent tool" ,
90- )
9189 }
9290
9391 @Test
9492 @Timeout(30 , unit = TimeUnit .SECONDS )
9593 fun testSpecialCharactersInArguments () = runTest {
96- client = HttpClient (CIO ) {
97- install(SSE )
98- }.mcpStreamableHttp(serverUrl)
99-
100- val specialChars = " !@#$%^&*()_+{}[]|\\ :;\" '<>,.?/"
101- val arguments = mapOf (" name" to specialChars)
94+ withClient(serverUrl) { client ->
95+ val specialChars = " !@#$%^&*()_+{}[]|\\ :;\" '<>,.?/"
96+ val arguments = mapOf (" name" to specialChars)
10297
103- val result = client.callTool(" greet" , arguments)
104- assertNotNull(result, " Tool call result should not be null" )
98+ val result = client.callTool(" greet" , arguments)
99+ assertNotNull(result, " Tool call result should not be null" )
105100
106- val callResult = result as CallToolResult
107- val textContent = callResult.content.firstOrNull { it is TextContent } as ? TextContent
108- assertNotNull(textContent, " Text content should be present in the result" )
101+ val callResult = result as CallToolResult
102+ val textContent = callResult.content.firstOrNull { it is TextContent } as ? TextContent
103+ assertNotNull(textContent, " Text content should be present in the result" )
109104
110- val text = textContent.text ? : " "
111- assertTrue(
112- text.contains(specialChars),
113- " Tool response should contain the special characters" ,
114- )
105+ val text = textContent.text ? : " "
106+ assertTrue(
107+ text.contains(specialChars),
108+ " Tool response should contain the special characters" ,
109+ )
110+ }
115111 }
116112
117113 @Test
118114 @Timeout(30 , unit = TimeUnit .SECONDS )
119115 fun testLargePayload () = runTest {
120- client = HttpClient ( CIO ) {
121- install( SSE )
122- }.mcpStreamableHttp(serverUrl )
116+ withClient(serverUrl ) { client ->
117+ val largeName = " A " .repeat( 10 * 1024 )
118+ val arguments = mapOf ( " name " to largeName )
123119
124- val largeName = " A" .repeat(10 * 1024 )
125- val arguments = mapOf (" name" to largeName)
126-
127- val result = client.callTool(" greet" , arguments)
128- assertNotNull(result, " Tool call result should not be null" )
120+ val result = client.callTool(" greet" , arguments)
121+ assertNotNull(result, " Tool call result should not be null" )
129122
130- val callResult = result as CallToolResult
131- val textContent = callResult.content.firstOrNull { it is TextContent } as ? TextContent
132- assertNotNull(textContent, " Text content should be present in the result" )
123+ val callResult = result as CallToolResult
124+ val textContent = callResult.content.firstOrNull { it is TextContent } as ? TextContent
125+ assertNotNull(textContent, " Text content should be present in the result" )
133126
134- val text = textContent.text ? : " "
135- assertTrue(
136- text.contains(" Hello," ) && text.contains(" A" ),
137- " Tool response should contain the greeting with the large name" ,
138- )
127+ val text = textContent.text ? : " "
128+ assertTrue(
129+ text.contains(" Hello," ) && text.contains(" A" ),
130+ " Tool response should contain the greeting with the large name" ,
131+ )
132+ }
139133 }
140134
141135 @Test
142136 @Timeout(60 , unit = TimeUnit .SECONDS )
143137 fun testConcurrentRequests () = runTest {
144- client = HttpClient (CIO ) {
145- install(SSE )
146- }.mcpStreamableHttp(serverUrl)
147-
148- val concurrentCount = 5
149- val results = mutableListOf<Deferred <String >>()
150-
151- for (i in 1 .. concurrentCount) {
152- runBlocking {
153- val deferred = async {
154- val name = " ConcurrentClient$i "
155- val arguments = mapOf (" name" to name)
156-
157- val result = client.callTool(" greet" , arguments)
158- assertNotNull(result, " Tool call result should not be null for client $i " )
159-
160- val callResult = result as CallToolResult
161- val textContent = callResult.content.firstOrNull { it is TextContent } as ? TextContent
162- assertNotNull(textContent, " Text content should be present for client $i " )
163-
164- textContent.text ? : " "
138+ withClient(serverUrl) { client ->
139+ val concurrentCount = 5
140+ val responses = kotlinx.coroutines.coroutineScope {
141+ val results = (1 .. concurrentCount).map { i ->
142+ async {
143+ val name = " ConcurrentClient$i "
144+ val arguments = mapOf (" name" to name)
145+
146+ val result = client.callTool(" greet" , arguments)
147+ assertNotNull(result, " Tool call result should not be null for client $i " )
148+
149+ val callResult = result as CallToolResult
150+ val textContent = callResult.content.firstOrNull { it is TextContent } as ? TextContent
151+ assertNotNull(textContent, " Text content should be present for client $i " )
152+
153+ textContent.text ? : " "
154+ }
165155 }
166- results.add(deferred )
156+ results.awaitAll( )
167157 }
168- }
169158
170- val responses = results.awaitAll()
171-
172- for (i in 1 .. concurrentCount) {
173- val expectedName = " ConcurrentClient$i "
174- val matchingResponses = responses.filter { it.contains(" Hello, $expectedName !" ) }
175- assertEquals(
176- 1 ,
177- matchingResponses.size,
178- " Should have exactly one response for $expectedName " ,
179- )
159+ for (i in 1 .. concurrentCount) {
160+ val expectedName = " ConcurrentClient$i "
161+ val matchingResponses = responses.filter { it.contains(" Hello, $expectedName !" ) }
162+ assertEquals(
163+ 1 ,
164+ matchingResponses.size,
165+ " Should have exactly one response for $expectedName " ,
166+ )
167+ }
180168 }
181169 }
182170
183171 @Test
184172 @Timeout(30 , unit = TimeUnit .SECONDS )
185173 fun testInvalidArguments () = runTest {
186- client = HttpClient (CIO ) {
187- install(SSE )
188- }.mcpStreamableHttp(serverUrl)
174+ withClient(serverUrl) { client ->
175+ val invalidArguments = mapOf (
176+ " name" to JsonObject (mapOf (" nested" to JsonPrimitive (" value" ))),
177+ )
189178
190- val invalidArguments = mapOf (
191- " name " to JsonObject ( mapOf ( " nested " to JsonPrimitive ( " value " ))),
192- )
179+ val exception = assertThrows< IllegalStateException > {
180+ client.callTool( " greet " , invalidArguments)
181+ }
193182
194- val exception = assertThrows<IllegalStateException > {
195- client.callTool(" greet" , invalidArguments)
183+ val msg = exception.message ? : " "
184+ val expectedMessage = """
185+ JSONRPCError(code=InvalidParams, message=MCP error -32602: Invalid arguments for tool greet: [
186+ {
187+ "code": "invalid_type",
188+ "expected": "string",
189+ "received": "object",
190+ "path": [
191+ "name"
192+ ],
193+ "message": "Expected string, received object"
194+ }
195+ ], data={})
196+ """ .trimIndent()
197+
198+ assertEquals(expectedMessage, msg, " Unexpected error message for invalid arguments" )
196199 }
197-
198- val msg = exception.message ? : " "
199- val expectedMessage = """
200- JSONRPCError(code=InvalidParams, message=MCP error -32602: Invalid arguments for tool greet: [
201- {
202- "code": "invalid_type",
203- "expected": "string",
204- "received": "object",
205- "path": [
206- "name"
207- ],
208- "message": "Expected string, received object"
209- }
210- ], data={})
211- """ .trimIndent()
212-
213- assertEquals(expectedMessage, msg, " Unexpected error message for invalid arguments" )
214200 }
215201
216202 @Test
217203 @Timeout(30 , unit = TimeUnit .SECONDS )
218204 fun testMultipleToolCalls () = runTest {
219- client = HttpClient (CIO ) {
220- install(SSE )
221- }.mcpStreamableHttp(serverUrl)
222-
223- repeat(10 ) { i ->
224- val name = " SequentialClient$i "
225- val arguments = mapOf (" name" to name)
226-
227- val result = client.callTool(" greet" , arguments)
228- assertNotNull(result, " Tool call result should not be null for call $i " )
229-
230- val callResult = result as CallToolResult
231- val textContent = callResult.content.firstOrNull { it is TextContent } as ? TextContent
232- assertNotNull(textContent, " Text content should be present for call $i " )
233-
234- assertEquals(
235- " Hello, $name !" ,
236- textContent.text,
237- " Tool response should contain the greeting with the provided name" ,
238- )
205+ withClient(serverUrl) { client ->
206+ repeat(10 ) { i ->
207+ val name = " SequentialClient$i "
208+ val arguments = mapOf (" name" to name)
209+
210+ val result = client.callTool(" greet" , arguments)
211+ assertNotNull(result, " Tool call result should not be null for call $i " )
212+
213+ val callResult = result as CallToolResult
214+ val textContent = callResult.content.firstOrNull { it is TextContent } as ? TextContent
215+ assertNotNull(textContent, " Text content should be present for call $i " )
216+
217+ assertEquals(
218+ " Hello, $name !" ,
219+ textContent.text,
220+ " Tool response should contain the greeting with the provided name" ,
221+ )
222+ }
239223 }
240224 }
241225}
0 commit comments