Skip to content

Commit 3a6e76c

Browse files
committed
Add tests for types and fix serialization issues
1 parent 5655f71 commit 3a6e76c

File tree

41 files changed

+4133
-2051
lines changed

Some content is hidden

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

41 files changed

+4133
-2051
lines changed

build.gradle.kts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ kover {
3838
reports {
3939
filters {
4040
includes.classes("io.modelcontextprotocol.kotlin.sdk.*")
41-
excludes.classes("io.modelcontextprotocol.kotlin.sdk.models.*") // temporary
42-
excludes.classes("io.modelcontextprotocol.kotlin.sdk.models.infrastructure.*") // generated
41+
excludes {
42+
annotatedBy("kotlin.Deprecated")
43+
classes("io.modelcontextprotocol.kotlin.sdk.models.*") // temporary
44+
classes("io.modelcontextprotocol.kotlin.sdk.models.infrastructure.*") // generated
45+
}
4346
}
4447
total {
4548
log {

kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import io.ktor.http.HttpMethod
66
import io.ktor.http.HttpStatusCode
77
import io.ktor.sse.ServerSentEvent
88
import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
9+
import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
910
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
1011
import io.modelcontextprotocol.kotlin.sdk.types.Tool
1112
import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
@@ -22,12 +23,6 @@ import kotlin.time.Duration.Companion.seconds
2223
import kotlin.uuid.ExperimentalUuidApi
2324
import kotlin.uuid.Uuid
2425

25-
/**
26-
* Integration tests for the `StreamableHttpClientTransport` implementation
27-
* using the [Mokksy](https://mokksy.dev) library
28-
* to simulate Streaming HTTP with server-sent events (SSE).
29-
* @author Konstantin Pavlov
30-
*/
3126
@OptIn(ExperimentalUuidApi::class)
3227
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
3328
@Suppress("LongMethod")
@@ -149,6 +144,7 @@ internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() {
149144
required = listOf("temperature"),
150145
),
151146
annotations = null,
147+
meta = EmptyJsonObject,
152148
)
153149

154150
mockMcp.mockUnsubscribeRequest(sessionId = sessionId)

kotlin-sdk-core/api/kotlin-sdk-core.api

Lines changed: 79 additions & 30 deletions
Large diffs are not rendered by default.

kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt

Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@ import kotlinx.serialization.json.Json
3838
import kotlinx.serialization.json.JsonObject
3939
import kotlinx.serialization.json.JsonPrimitive
4040
import kotlinx.serialization.json.encodeToJsonElement
41-
import kotlinx.serialization.serializer
42-
import kotlin.reflect.KType
43-
import kotlin.reflect.typeOf
4441
import kotlin.time.Duration
4542
import kotlin.time.Duration.Companion.seconds
4643

@@ -262,14 +259,11 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
262259
logger.trace { "No handler found for request: ${request.method}" }
263260
try {
264261
transport?.send(
265-
JSONRPCResponse(
262+
JSONRPCError(
266263
id = request.id,
267-
error = JSONRPCError(
268-
id = request.id,
269-
error = RPCError(
270-
code = RPCError.ErrorCode.METHOD_NOT_FOUND,
271-
message = "Server does not support ${request.method}",
272-
),
264+
error = RPCError(
265+
code = RPCError.ErrorCode.METHOD_NOT_FOUND,
266+
message = "Server does not support ${request.method}",
273267
),
274268
),
275269
)
@@ -287,22 +281,19 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
287281
transport?.send(
288282
JSONRPCResponse(
289283
id = request.id,
290-
result = result,
284+
result = result ?: EmptyResult(),
291285
),
292286
)
293287
} catch (cause: Throwable) {
294288
logger.error(cause) { "Error handling request: ${request.method} (id: ${request.id})" }
295289

296290
try {
297291
transport?.send(
298-
JSONRPCResponse(
292+
JSONRPCError(
299293
id = request.id,
300-
error = JSONRPCError(
301-
id = request.id,
302-
error = RPCError(
303-
code = RPCError.ErrorCode.INTERNAL_ERROR,
304-
message = cause.message ?: "Internal error",
305-
),
294+
error = RPCError(
295+
code = RPCError.ErrorCode.INTERNAL_ERROR,
296+
message = cause.message ?: "Internal error",
306297
),
307298
),
308299
)
@@ -338,7 +329,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
338329
}
339330

340331
private fun onResponse(response: JSONRPCResponse?, error: JSONRPCError?) {
341-
val messageId = response?.id
332+
val messageId = response?.id ?: error?.id
342333

343334
val oldResponseHandlers = _responseHandlers.getAndUpdate { current ->
344335
if (messageId != null && messageId in current) {
@@ -429,11 +420,6 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
429420
return@put
430421
}
431422

432-
if (response?.error != null) {
433-
result.completeExceptionally(IllegalStateException(response.error.toString()))
434-
return@put
435-
}
436-
437423
try {
438424
@Suppress("UNCHECKED_CAST")
439425
result.complete(response!!.result as T)
@@ -505,30 +491,21 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
505491
method: Method,
506492
noinline block: suspend (T, RequestHandlerExtra) -> RequestResult?,
507493
) {
508-
setRequestHandler(typeOf<T>(), method, block)
494+
setRequestHandlerInternal(method, block)
509495
}
510496

511497
@PublishedApi
512-
internal fun <T : Request> setRequestHandler(
513-
requestType: KType,
498+
@Suppress("UNCHECKED_CAST")
499+
internal fun <T : Request> setRequestHandlerInternal(
514500
method: Method,
515501
block: suspend (T, RequestHandlerExtra) -> RequestResult?,
516502
) {
517503
assertRequestHandlerCapability(method)
518504

519-
val serializer = McpJson.serializersModule.serializer(requestType)
520-
521505
_requestHandlers.update { current ->
522-
current.put(method.value) { request, extraHandler ->
523-
val result = request.params?.let { params ->
524-
McpJson.decodeFromJsonElement(serializer, params)
525-
}
526-
val response = if (result != null) {
527-
@Suppress("UNCHECKED_CAST")
528-
block(result as T, extraHandler)
529-
} else {
530-
EmptyResult()
531-
}
506+
current.put(method.value) { jSONRPCRequest, extraHandler ->
507+
val request = jSONRPCRequest.fromJSON()
508+
val response = block(request as T, extraHandler)
532509
response
533510
}
534511
}

0 commit comments

Comments
 (0)