Skip to content

Commit 6dd9c80

Browse files
authored
Add convenience constructors to JSONRPCRequest (#336)
- Add convenience constructors to JSONRPCRequest - Make UUID string a default JSONRPCRequest ## Motivation and Context To create request objects more easily ## How Has This Been Tested? Unit tests ## Breaking Changes No, this is a new API ## Types of changes <!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist <!-- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [x] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context <!-- Add any other context, implementation notes, or design decisions -->
1 parent 2b9dfd9 commit 6dd9c80

File tree

4 files changed

+75
-11
lines changed

4 files changed

+75
-11
lines changed

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage
1717
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification
1818
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest
1919
import io.modelcontextprotocol.kotlin.sdk.types.McpJson
20-
import io.modelcontextprotocol.kotlin.sdk.types.RequestId
2120
import kotlinx.coroutines.Dispatchers
2221
import kotlinx.coroutines.delay
2322
import kotlinx.coroutines.test.runTest
@@ -50,7 +49,7 @@ class StreamableHttpClientTransportTest {
5049
@Test
5150
fun testSendJsonRpcMessage() = runTest {
5251
val message = JSONRPCRequest(
53-
id = RequestId.StringId("test-id"),
52+
id = "test-id",
5453
method = "test",
5554
params = buildJsonObject { },
5655
)
@@ -78,7 +77,7 @@ class StreamableHttpClientTransportTest {
7877
@Test
7978
fun testStoreSessionId() = runTest {
8079
val initMessage = JSONRPCRequest(
81-
id = RequestId.StringId("test-id"),
80+
id = "test-id",
8281
method = "initialize",
8382
params = buildJsonObject {
8483
put(

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2118,8 +2118,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCNotification$
21182118

21192119
public final class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCRequest : io/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage {
21202120
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCRequest$Companion;
2121+
public fun <init> (JLjava/lang/String;Lkotlinx/serialization/json/JsonElement;)V
2122+
public synthetic fun <init> (JLjava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
2123+
public fun <init> (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;)V
21212124
public fun <init> (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)V
21222125
public synthetic fun <init> (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
2126+
public fun <init> (Ljava/lang/String;)V
2127+
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)V
2128+
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
21232129
public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;
21242130
public final fun component2 ()Ljava/lang/String;
21252131
public final fun component3 ()Lkotlinx/serialization/json/JsonElement;

kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,14 @@ import kotlinx.serialization.Serializable
88
import kotlinx.serialization.json.JsonElement
99
import kotlinx.serialization.json.decodeFromJsonElement
1010
import kotlinx.serialization.json.encodeToJsonElement
11-
import kotlin.concurrent.atomics.AtomicLong
1211
import kotlin.concurrent.atomics.ExperimentalAtomicApi
13-
import kotlin.concurrent.atomics.incrementAndFetch
1412
import kotlin.jvm.JvmInline
13+
import kotlin.jvm.JvmOverloads
14+
import kotlin.uuid.ExperimentalUuidApi
15+
import kotlin.uuid.Uuid
1516

1617
public const val JSONRPC_VERSION: String = "2.0"
1718

18-
private val REQUEST_MESSAGE_ID: AtomicLong = AtomicLong(0L)
19-
2019
public fun RequestId(value: String): RequestId = RequestId.StringId(value)
2120

2221
public fun RequestId(value: Long): RequestId = RequestId.NumberId(value)
@@ -94,23 +93,50 @@ public sealed interface JSONRPCMessage {
9493
* A request that expects a response.
9594
*
9695
* Requests are identified by a unique [id] and specify a [method] to invoke.
97-
* The server or client (depending on direction) must respond with either a
96+
* The server or client (depending on a direction) must respond with either a
9897
* [JSONRPCResponse] or [JSONRPCError] that has the same [id].
9998
*
10099
* @property jsonrpc Always "2.0" to indicate JSON-RPC 2.0 protocol.
101100
* @property id A unique identifier for this request. The response will include the same ID.
102-
* Can be a string or number.
101+
* Can be a string or number. UUID string is used by default.
103102
* @property method The name of the method to invoke (e.g., "tools/list", "resources/read").
104103
* @property params Optional parameters for the method. Structure depends on the specific method.
105104
*/
106105
@Serializable
107-
public data class JSONRPCRequest(
108-
val id: RequestId = RequestId(REQUEST_MESSAGE_ID.incrementAndFetch()),
106+
@OptIn(ExperimentalUuidApi::class)
107+
public data class JSONRPCRequest @JvmOverloads public constructor(
108+
val id: RequestId = RequestId(Uuid.random().toHexString()),
109109
val method: String,
110110
val params: JsonElement? = null,
111111
) : JSONRPCMessage {
112112
@EncodeDefault
113113
override val jsonrpc: String = JSONRPC_VERSION
114+
115+
/**
116+
* Secondary constructor for creating a `JSONRPCRequest` using a string-based ID.
117+
*
118+
* @param id The string ID for the request.
119+
* @param method The method name for the request.
120+
* @param params The parameters for the request as a JSON element. Defaults to `null`.
121+
*/
122+
public constructor(
123+
id: String,
124+
method: String,
125+
params: JsonElement? = null,
126+
) : this(id = RequestId.StringId(id), method = method, params = params)
127+
128+
/**
129+
* Constructs a JSON-RPC request using a numerical ID, method name, and optional parameters.
130+
*
131+
* @param id The numerical ID for the request.
132+
* @param method The method name for the request.
133+
* @param params The parameters for the request as a JSON element. Defaults to `null`.
134+
*/
135+
public constructor(
136+
id: Long,
137+
method: String,
138+
params: JsonElement? = null,
139+
) : this(id = RequestId.NumberId(id), method = method, params = params)
114140
}
115141

116142
// ============================================================================
@@ -207,6 +233,7 @@ public data class RPCError(val code: Int, val message: String, val data: JsonEle
207233
public const val REQUEST_TIMEOUT: Int = -32001
208234

209235
// Standard JSON-RPC 2.0 error codes
236+
210237
/** Invalid JSON was received */
211238
public const val PARSE_ERROR: Int = -32700
212239

kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package io.modelcontextprotocol.kotlin.sdk.types
22

33
import io.kotest.assertions.json.shouldEqualJson
4+
import io.kotest.matchers.shouldBe
5+
import io.kotest.matchers.types.shouldBeSameInstanceAs
46
import kotlinx.serialization.json.boolean
57
import kotlinx.serialization.json.buildJsonObject
68
import kotlinx.serialization.json.int
@@ -398,4 +400,34 @@ class JsonRpcTest {
398400
}
399401
""".trimIndent()
400402
}
403+
404+
@Test
405+
fun `should create JSONRPCRequest with string ID`() {
406+
val params = buildJsonObject {
407+
put("foo", "bar")
408+
}
409+
val request = JSONRPCRequest(
410+
id = "req-42",
411+
method = "notifications/log",
412+
params = params,
413+
)
414+
request.id shouldBe RequestId("req-42")
415+
request.method shouldBe "notifications/log"
416+
request.params shouldBeSameInstanceAs params
417+
}
418+
419+
@Test
420+
fun `should create JSONRPCRequest with numeric ID`() {
421+
val params = buildJsonObject {
422+
put("foo", "bar")
423+
}
424+
val request = JSONRPCRequest(
425+
id = 42,
426+
method = "notifications/log",
427+
params = params,
428+
)
429+
request.id shouldBe RequestId(42)
430+
request.method shouldBe "notifications/log"
431+
request.params shouldBeSameInstanceAs params
432+
}
401433
}

0 commit comments

Comments
 (0)