From cf65be8f229bb94b47e53f156fa10118431cb3d1 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 23 Oct 2025 19:19:29 +0300 Subject: [PATCH 1/5] Add convenience constructors to JSONRPCRequest --- .../StreamableHttpClientTransportTest.kt | 5 +-- kotlin-sdk-core/api/kotlin-sdk-core.api | 25 +++++++++++ .../modelcontextprotocol/kotlin/sdk/types.kt | 44 ++++++++++++++++++- 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt index a72bd853..a6ff6646 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt @@ -16,7 +16,6 @@ import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest -import io.modelcontextprotocol.kotlin.sdk.RequestId import io.modelcontextprotocol.kotlin.sdk.shared.McpJson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -50,7 +49,7 @@ class StreamableHttpClientTransportTest { @Test fun testSendJsonRpcMessage() = runTest { val message = JSONRPCRequest( - id = RequestId.StringId("test-id"), + id = "test-id", method = "test", params = buildJsonObject { }, ) @@ -78,7 +77,7 @@ class StreamableHttpClientTransportTest { @Test fun testStoreSessionId() = runTest { val initMessage = JSONRPCRequest( - id = RequestId.StringId("test-id"), + id = "test-id", method = "initialize", params = buildJsonObject { put( diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 4f230ab4..dc6f86ed 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -1143,8 +1143,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/JSONRPCNotification$Compan public final class io/modelcontextprotocol/kotlin/sdk/JSONRPCRequest : io/modelcontextprotocol/kotlin/sdk/JSONRPCMessage { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/JSONRPCRequest$Companion; + public fun (JLjava/lang/String;Lkotlinx/serialization/json/JsonElement;)V + public synthetic fun (JLjava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;)V public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/RequestId; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Lkotlinx/serialization/json/JsonElement; @@ -3367,3 +3371,24 @@ public final class io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTranspo public static final field MCP_SUBPROTOCOL Ljava/lang/String; } +public class io/modelcontextprotocol/kotlin/sdk/testing/MockTransport : io/modelcontextprotocol/kotlin/sdk/shared/Transport { + public fun ()V + public fun (Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun awaitMessage-ePrTys8 (JJLjava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun awaitMessage-ePrTys8$default (Lio/modelcontextprotocol/kotlin/sdk/testing/MockTransport;JJLjava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getReceivedMessages (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getSentMessages (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun onClose (Lkotlin/jvm/functions/Function0;)V + public fun onError (Lkotlin/jvm/functions/Function1;)V + public fun onMessage (Lkotlin/jvm/functions/Function2;)V + public final fun onMessageReply (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V + public final fun onMessageReplyError (Lio/modelcontextprotocol/kotlin/sdk/Method;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun onMessageReplyError$default (Lio/modelcontextprotocol/kotlin/sdk/testing/MockTransport;Lio/modelcontextprotocol/kotlin/sdk/Method;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public final fun onMessageReplyResult (Lio/modelcontextprotocol/kotlin/sdk/Method;Lkotlin/jvm/functions/Function1;)V + public fun send (Lio/modelcontextprotocol/kotlin/sdk/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setupInitializationResponse ()V + public fun start (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt index 60adcca4..2c3a8422 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt @@ -19,6 +19,8 @@ import kotlin.concurrent.atomics.incrementAndFetch import kotlin.jvm.JvmInline import kotlin.time.ExperimentalTime import kotlin.time.Instant +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid public const val LATEST_PROTOCOL_VERSION: String = "2025-03-26" @@ -232,7 +234,36 @@ public data class JSONRPCRequest( val method: String, val params: JsonElement = EmptyJsonObject, val jsonrpc: String = JSONRPC_VERSION, -) : JSONRPCMessage +) : JSONRPCMessage { + + /** + * Creates a JSON-RPC request with a randomly generated string ID (using UUID) and the given method and parameters. + * + * @param id The string ID for the request. If not provided, a random UUID string is generated. + * @param method The method name for the request. + * @param params The parameters for the request as a JSON element. If not provided, an empty JSON object is used. + */ + @OptIn(ExperimentalUuidApi::class) + public constructor( + id: String = Uuid.random().toHexString(), + method: String, + params: JsonElement = EmptyJsonObject, + ) : this(id = RequestId.StringId(id), method = method, params = params) + + /** + * Creates a JSON-RPC request with a numeric ID (long) generated by a counter (to ensure uniqueness) + * and the given method and parameters. + * + * The `id` parameter is optional and defaults to a new unique long value generated + * by `REQUEST_MESSAGE_ID.incrementAndFetch()`. + * The `params` parameter is optional and defaults to an empty JSON object. + */ + public constructor( + id: Long = REQUEST_MESSAGE_ID.incrementAndFetch(), + method: String, + params: JsonElement = EmptyJsonObject, + ) : this(id = RequestId.NumberId(id), method = method, params = params) +} /** * A notification which does not expect a response. @@ -302,6 +333,7 @@ public data class JSONRPCError(val code: ErrorCode, val message: String, val dat public sealed interface NotificationParams : WithMeta /* Cancellation */ + /** * This notification can be sent by either side to indicate that it is cancelling a previously issued request. * @@ -334,6 +366,7 @@ public data class CancelledNotification(override val params: Params) : } /* Initialization */ + /** * Describes the name and version of an MCP implementation. */ @@ -531,6 +564,7 @@ public data class InitializedNotification(override val params: Params = Params() } /* Ping */ + /** * A ping, issued by either the server or the client, to check that the other party is still alive. * The receiver must promptly respond, or else it may be disconnected. @@ -564,6 +598,7 @@ public sealed interface ProgressBase { } /* Progress notifications */ + /** * Represents a progress notification. * @@ -623,6 +658,7 @@ public data class ProgressNotification(override val params: Params) : } /* Pagination */ + /** * Represents a request supporting pagination. */ @@ -650,6 +686,7 @@ public sealed interface PaginatedResult : RequestResult { } /* Resources */ + /** * The contents of a specific resource or sub-resource. */ @@ -892,6 +929,7 @@ public data class ResourceUpdatedNotification(override val params: Params) : Ser } /* Prompts */ + /** * Describes an argument that a prompt can accept. */ @@ -1162,6 +1200,7 @@ public data class PromptListChangedNotification(override val params: Params = Pa } /* Tools */ + /** * Additional properties describing a Tool to clients. * @@ -1345,6 +1384,7 @@ public data class ToolListChangedNotification(override val params: Params = Para } /* Logging */ + /** * The severity of a log message. */ @@ -1404,6 +1444,7 @@ public data class LoggingMessageNotification(override val params: Params) : Serv } /* Sampling */ + /** * Hints to use for model selection. */ @@ -1648,6 +1689,7 @@ public data class CompleteResult(val completion: Completion, override val _meta: } /* Roots */ + /** * Represents a root directory or file that the server can operate on. */ From e8a1ab17a1ba8501db75f50d97fc5b9c9e5296f2 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Mon, 3 Nov 2025 22:19:59 +0200 Subject: [PATCH 2/5] Update API dump --- kotlin-sdk-core/api/kotlin-sdk-core.api | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index dc6f86ed..bece99e7 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -3371,24 +3371,3 @@ public final class io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTranspo public static final field MCP_SUBPROTOCOL Ljava/lang/String; } -public class io/modelcontextprotocol/kotlin/sdk/testing/MockTransport : io/modelcontextprotocol/kotlin/sdk/shared/Transport { - public fun ()V - public fun (Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun awaitMessage-ePrTys8 (JJLjava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun awaitMessage-ePrTys8$default (Lio/modelcontextprotocol/kotlin/sdk/testing/MockTransport;JJLjava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun getReceivedMessages (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun getSentMessages (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun onClose (Lkotlin/jvm/functions/Function0;)V - public fun onError (Lkotlin/jvm/functions/Function1;)V - public fun onMessage (Lkotlin/jvm/functions/Function2;)V - public final fun onMessageReply (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V - public final fun onMessageReplyError (Lio/modelcontextprotocol/kotlin/sdk/Method;Lkotlin/jvm/functions/Function1;)V - public static synthetic fun onMessageReplyError$default (Lio/modelcontextprotocol/kotlin/sdk/testing/MockTransport;Lio/modelcontextprotocol/kotlin/sdk/Method;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V - public final fun onMessageReplyResult (Lio/modelcontextprotocol/kotlin/sdk/Method;Lkotlin/jvm/functions/Function1;)V - public fun send (Lio/modelcontextprotocol/kotlin/sdk/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun setupInitializationResponse ()V - public fun start (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - From bd6c9f98687a1efe5cc32f06703b3bdd99598b84 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Mon, 3 Nov 2025 22:30:44 +0200 Subject: [PATCH 3/5] Update API --- kotlin-sdk-core/api/kotlin-sdk-core.api | 1 + .../modelcontextprotocol/kotlin/sdk/types.kt | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index bece99e7..5bcc1f0d 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -1147,6 +1147,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/JSONRPCRequest : io/modelc public synthetic fun (JLjava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;)V public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;)V public fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/RequestId; diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt index 2c3a8422..715dce0e 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt @@ -250,6 +250,21 @@ public data class JSONRPCRequest( params: JsonElement = EmptyJsonObject, ) : this(id = RequestId.StringId(id), method = method, params = params) + /** + * Creates a JSON-RPC request with a numeric ID generated by a counter (to ensure uniqueness) + * and the given method name. + * + * This constructor initializes the `id` field with a `NumberId` instance using the counter + * value from `REQUEST_MESSAGE_ID.incrementAndFetch()`, and assigns the provided method name + * to the `method` field of the request. + * + * @param method The method name for the request. + */ + @OptIn(ExperimentalUuidApi::class) + public constructor( + method: String, + ) : this(id = RequestId.NumberId(REQUEST_MESSAGE_ID.incrementAndFetch()), method = method) + /** * Creates a JSON-RPC request with a numeric ID (long) generated by a counter (to ensure uniqueness) * and the given method and parameters. @@ -337,7 +352,8 @@ public sealed interface NotificationParams : WithMeta /** * This notification can be sent by either side to indicate that it is cancelling a previously issued request. * - * The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished. + * The request SHOULD still be in-flight, but due to communication latency, + * it is always possible that this notification MAY arrive after the request has already finished. * * This notification indicates that the result will be unused, so any associated processing SHOULD cease. * From 9e78bda7e9d7ea90f169957e3f167966eeb80555 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Mon, 3 Nov 2025 23:04:28 +0200 Subject: [PATCH 4/5] Update API --- kotlin-sdk-core/api/kotlin-sdk-core.api | 2 ++ .../io/modelcontextprotocol/kotlin/sdk/types.kt | 14 +++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 5bcc1f0d..bdefe7f0 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -1150,6 +1150,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/JSONRPCRequest : io/modelc public fun (Ljava/lang/String;)V public fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)V public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/RequestId; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Lkotlinx/serialization/json/JsonElement; diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt index 715dce0e..9fcafa4a 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt @@ -17,6 +17,7 @@ import kotlin.concurrent.atomics.AtomicLong import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.concurrent.atomics.incrementAndFetch import kotlin.jvm.JvmInline +import kotlin.jvm.JvmOverloads import kotlin.time.ExperimentalTime import kotlin.time.Instant import kotlin.uuid.ExperimentalUuidApi @@ -251,19 +252,18 @@ public data class JSONRPCRequest( ) : this(id = RequestId.StringId(id), method = method, params = params) /** - * Creates a JSON-RPC request with a numeric ID generated by a counter (to ensure uniqueness) - * and the given method name. - * - * This constructor initializes the `id` field with a `NumberId` instance using the counter - * value from `REQUEST_MESSAGE_ID.incrementAndFetch()`, and assigns the provided method name - * to the `method` field of the request. + * Creates a JSON-RPC request with a randomly generated string ID (using UUID), + * the given method name and parameters. * * @param method The method name for the request. + * @param params The parameters for the request as a JSON element. If not provided, an empty JSON object is used. */ @OptIn(ExperimentalUuidApi::class) + @JvmOverloads public constructor( method: String, - ) : this(id = RequestId.NumberId(REQUEST_MESSAGE_ID.incrementAndFetch()), method = method) + params: JsonElement = EmptyJsonObject, + ) : this(id = RequestId.StringId(Uuid.random().toHexString()), method = method, params = params) /** * Creates a JSON-RPC request with a numeric ID (long) generated by a counter (to ensure uniqueness) From bcae5fbac67c15755e57f831f76b75e9e1c33c49 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Tue, 18 Nov 2025 17:09:07 +0200 Subject: [PATCH 5/5] Add overloaded constructors for JSONRPCRequest and refactor tests - Updated `JSONRPCRequest` with new string and numeric ID constructors. - Replaced atomic ID handling with UUID defaults. - Enhanced test coverage for JSONRPCRequest creation scenarios. --- .../StreamableHttpClientTransportTest.kt | 6 --- kotlin-sdk-core/api/kotlin-sdk-core.api | 6 +++ .../kotlin/sdk/types/jsonRpc.kt | 43 +++++++++++++++---- .../kotlin/sdk/types/JsonRpcTest.kt | 32 ++++++++++++++ 4 files changed, 73 insertions(+), 14 deletions(-) diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt index 1572a249..9968ceb1 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt @@ -17,12 +17,6 @@ import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest import io.modelcontextprotocol.kotlin.sdk.types.McpJson -import io.modelcontextprotocol.kotlin.sdk.types.RequestId -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification -import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest -import io.modelcontextprotocol.kotlin.sdk.shared.McpJson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index fa85476f..8fddc097 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -1980,8 +1980,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCNotification$ public final class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCRequest : io/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCRequest$Companion; + public fun (JLjava/lang/String;Lkotlinx/serialization/json/JsonElement;)V + public synthetic fun (JLjava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;)V public fun (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)V public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;)V + public fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Lkotlinx/serialization/json/JsonElement; diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt index 66229001..8a580eb0 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt @@ -8,15 +8,14 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.encodeToJsonElement -import kotlin.concurrent.atomics.AtomicLong import kotlin.concurrent.atomics.ExperimentalAtomicApi -import kotlin.concurrent.atomics.incrementAndFetch import kotlin.jvm.JvmInline +import kotlin.jvm.JvmOverloads +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid public const val JSONRPC_VERSION: String = "2.0" -private val REQUEST_MESSAGE_ID: AtomicLong = AtomicLong(0L) - public fun RequestId(value: String): RequestId = RequestId.StringId(value) public fun RequestId(value: Long): RequestId = RequestId.NumberId(value) @@ -94,23 +93,50 @@ public sealed interface JSONRPCMessage { * A request that expects a response. * * Requests are identified by a unique [id] and specify a [method] to invoke. - * The server or client (depending on direction) must respond with either a + * The server or client (depending on a direction) must respond with either a * [JSONRPCResponse] or [JSONRPCError] that has the same [id]. * * @property jsonrpc Always "2.0" to indicate JSON-RPC 2.0 protocol. * @property id A unique identifier for this request. The response will include the same ID. - * Can be a string or number. + * Can be a string or number. UUID string is used by default. * @property method The name of the method to invoke (e.g., "tools/list", "resources/read"). * @property params Optional parameters for the method. Structure depends on the specific method. */ @Serializable -public data class JSONRPCRequest( - val id: RequestId = RequestId(REQUEST_MESSAGE_ID.incrementAndFetch()), +@OptIn(ExperimentalUuidApi::class) +public data class JSONRPCRequest @JvmOverloads public constructor( + val id: RequestId = RequestId(Uuid.random().toHexString()), val method: String, val params: JsonElement? = null, ) : JSONRPCMessage { @EncodeDefault override val jsonrpc: String = JSONRPC_VERSION + + /** + * Secondary constructor for creating a `JSONRPCRequest` using a string-based ID. + * + * @param id The string ID for the request. + * @param method The method name for the request. + * @param params The parameters for the request as a JSON element. Defaults to `null`. + */ + public constructor( + id: String, + method: String, + params: JsonElement? = null, + ) : this(id = RequestId.StringId(id), method = method, params = params) + + /** + * Constructs a JSON-RPC request using a numerical ID, method name, and optional parameters. + * + * @param id The numerical ID for the request. + * @param method The method name for the request. + * @param params The parameters for the request as a JSON element. Defaults to `null`. + */ + public constructor( + id: Long, + method: String, + params: JsonElement? = null, + ) : this(id = RequestId.NumberId(id), method = method, params = params) } // ============================================================================ @@ -207,6 +233,7 @@ public data class RPCError(val code: Int, val message: String, val data: JsonEle public const val REQUEST_TIMEOUT: Int = -32001 // Standard JSON-RPC 2.0 error codes + /** Invalid JSON was received */ public const val PARSE_ERROR: Int = -32700 diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt index 9b970322..d8d20583 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt @@ -1,6 +1,8 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.kotest.assertions.json.shouldEqualJson +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeSameInstanceAs import kotlinx.serialization.json.boolean import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.int @@ -398,4 +400,34 @@ class JsonRpcTest { } """.trimIndent() } + + @Test + fun `should create JSONRPCRequest with string ID`() { + val params = buildJsonObject { + put("foo", "bar") + } + val request = JSONRPCRequest( + id = "req-42", + method = "notifications/log", + params = params, + ) + request.id shouldBe RequestId("req-42") + request.method shouldBe "notifications/log" + request.params shouldBeSameInstanceAs params + } + + @Test + fun `should create JSONRPCRequest with numeric ID`() { + val params = buildJsonObject { + put("foo", "bar") + } + val request = JSONRPCRequest( + id = 42, + method = "notifications/log", + params = params, + ) + request.id shouldBe RequestId(42) + request.method shouldBe "notifications/log" + request.params shouldBeSameInstanceAs params + } }