Skip to content

Commit 25238ec

Browse files
committed
Reorder serializers and add kdocs
1 parent 3a6e76c commit 25238ec

File tree

1 file changed

+191
-56
lines changed
  • kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types

1 file changed

+191
-56
lines changed

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

Lines changed: 191 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,32 @@ import kotlinx.serialization.json.longOrNull
1818

1919
private val logger = KotlinLogging.logger {}
2020

21+
// ============================================================================
22+
// Utility Functions
23+
// ============================================================================
24+
25+
/**
26+
* Safely extracts the method field from a JSON element.
27+
* Returns null if the method field is not present.
28+
*/
29+
private fun JsonElement.getMethodOrNull(): String? = jsonObject["method"]?.jsonPrimitive?.content
30+
31+
/**
32+
* Extracts the method field from a JSON element.
33+
* Throws [SerializationException] if the method field is not present.
34+
*/
35+
private fun JsonElement.getMethod(): String =
36+
getMethodOrNull() ?: throw SerializationException("Missing required 'method' field in notification")
37+
38+
// ============================================================================
39+
// Method Serializer
40+
// ============================================================================
41+
42+
/**
43+
* Custom serializer for [Method] that handles both defined and custom methods.
44+
* Defined methods are deserialized to [Method.Defined], while unknown methods
45+
* are deserialized to [Method.Custom].
46+
*/
2147
internal object MethodSerializer : KSerializer<Method> {
2248
override val descriptor: SerialDescriptor =
2349
PrimitiveSerialDescriptor("io.modelcontextprotocol.kotlin.sdk.types.Method", PrimitiveKind.STRING)
@@ -36,6 +62,14 @@ internal object MethodSerializer : KSerializer<Method> {
3662
}
3763
}
3864

65+
// ============================================================================
66+
// Reference Serializers
67+
// ============================================================================
68+
69+
/**
70+
* Polymorphic serializer for [Reference] types.
71+
* Determines the concrete type based on the "type" field in JSON.
72+
*/
3973
internal object ReferencePolymorphicSerializer : JsonContentPolymorphicSerializer<Reference>(Reference::class) {
4074
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<Reference> =
4175
when (element.jsonObject.getValue("type").jsonPrimitive.content) {
@@ -45,6 +79,14 @@ internal object ReferencePolymorphicSerializer : JsonContentPolymorphicSerialize
4579
}
4680
}
4781

82+
// ============================================================================
83+
// Content Serializers
84+
// ============================================================================
85+
86+
/**
87+
* Polymorphic serializer for [ContentBlock] types.
88+
* Determines the concrete type based on the "type" field in JSON.
89+
*/
4890
internal object ContentBlockPolymorphicSerializer :
4991
JsonContentPolymorphicSerializer<ContentBlock>(ContentBlock::class) {
5092
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ContentBlock> =
@@ -58,6 +100,10 @@ internal object ContentBlockPolymorphicSerializer :
58100
}
59101
}
60102

103+
/**
104+
* Polymorphic serializer for [MediaContent] types (text, image, audio).
105+
* Determines the concrete type based on the "type" field in JSON.
106+
*/
61107
internal object MediaContentPolymorphicSerializer :
62108
JsonContentPolymorphicSerializer<MediaContent>(MediaContent::class) {
63109
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<MediaContent> =
@@ -69,6 +115,14 @@ internal object MediaContentPolymorphicSerializer :
69115
}
70116
}
71117

118+
// ============================================================================
119+
// Resource Serializers
120+
// ============================================================================
121+
122+
/**
123+
* Polymorphic serializer for [ResourceContents] types.
124+
* Determines the concrete type based on the presence of "text" or "blob" fields.
125+
*/
72126
internal object ResourceContentsPolymorphicSerializer :
73127
JsonContentPolymorphicSerializer<ResourceContents>(ResourceContents::class) {
74128
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ResourceContents> {
@@ -81,6 +135,14 @@ internal object ResourceContentsPolymorphicSerializer :
81135
}
82136
}
83137

138+
// ============================================================================
139+
// Request Serializers
140+
// ============================================================================
141+
142+
/**
143+
* Selects the appropriate deserializer for client requests based on the method name.
144+
* Returns null if the method is not a known client request method.
145+
*/
84146
internal fun selectClientRequestDeserializer(method: String): DeserializationStrategy<ClientRequest>? = when (method) {
85147
Method.Defined.CompletionComplete.value -> CompleteRequest.serializer()
86148
Method.Defined.Initialize.value -> InitializeRequest.serializer()
@@ -98,10 +160,43 @@ internal fun selectClientRequestDeserializer(method: String): DeserializationStr
98160
else -> null
99161
}
100162

101-
private fun JsonElement.getMethodOrNull(): String? = jsonObject["method"]?.jsonPrimitive?.content
102-
private fun JsonElement.getMethod(): String =
103-
getMethodOrNull() ?: throw SerializationException("Missing required 'method' field in notification")
163+
/**
164+
* Selects the appropriate deserializer for server requests based on the method name.
165+
* Returns null if the method is not a known server request method.
166+
*/
167+
internal fun selectServerRequestDeserializer(method: String): DeserializationStrategy<ServerRequest>? = when (method) {
168+
Method.Defined.ElicitationCreate.value -> ElicitRequest.serializer()
169+
Method.Defined.Ping.value -> PingRequest.serializer()
170+
Method.Defined.RootsList.value -> ListRootsRequest.serializer()
171+
Method.Defined.SamplingCreateMessage.value -> CreateMessageRequest.serializer()
172+
else -> null
173+
}
104174

175+
/**
176+
* Polymorphic serializer for [Request] types.
177+
* Supports both client and server requests, as well as custom requests.
178+
*/
179+
internal object RequestPolymorphicSerializer : JsonContentPolymorphicSerializer<Request>(Request::class) {
180+
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<Request> {
181+
val method = element.getMethodOrNull() ?: run {
182+
logger.error { "No method in $element" }
183+
error("No method in $element")
184+
}
185+
186+
return selectClientRequestDeserializer(method)
187+
?: selectServerRequestDeserializer(method)
188+
?: CustomRequest.serializer()
189+
}
190+
}
191+
192+
// ============================================================================
193+
// Notification Serializers
194+
// ============================================================================
195+
196+
/**
197+
* Selects the appropriate deserializer for client notifications based on the method name.
198+
* Returns null if the method is not a known client notification method.
199+
*/
105200
private fun selectClientNotificationDeserializer(element: JsonElement): DeserializationStrategy<ClientNotification>? {
106201
val method = element.getMethodOrNull() ?: return null
107202

@@ -114,21 +209,10 @@ private fun selectClientNotificationDeserializer(element: JsonElement): Deserial
114209
}
115210
}
116211

117-
internal object ClientNotificationPolymorphicSerializer :
118-
JsonContentPolymorphicSerializer<ClientNotification>(ClientNotification::class) {
119-
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ClientNotification> =
120-
selectClientNotificationDeserializer(element)
121-
?: CustomNotification.serializer()
122-
}
123-
124-
internal fun selectServerRequestDeserializer(method: String): DeserializationStrategy<ServerRequest>? = when (method) {
125-
Method.Defined.ElicitationCreate.value -> ElicitRequest.serializer()
126-
Method.Defined.Ping.value -> PingRequest.serializer()
127-
Method.Defined.RootsList.value -> ListRootsRequest.serializer()
128-
Method.Defined.SamplingCreateMessage.value -> CreateMessageRequest.serializer()
129-
else -> null
130-
}
131-
212+
/**
213+
* Selects the appropriate deserializer for server notifications based on the method name.
214+
* Returns null if the method is not a known server notification method.
215+
*/
132216
internal fun selectServerNotificationDeserializer(element: JsonElement): DeserializationStrategy<ServerNotification>? {
133217
val method = element.getMethodOrNull() ?: return null
134218

@@ -144,13 +228,10 @@ internal fun selectServerNotificationDeserializer(element: JsonElement): Deseria
144228
}
145229
}
146230

147-
internal object ServerNotificationPolymorphicSerializer :
148-
JsonContentPolymorphicSerializer<ServerNotification>(ServerNotification::class) {
149-
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ServerNotification> =
150-
selectServerNotificationDeserializer(element)
151-
?: CustomNotification.serializer()
152-
}
153-
231+
/**
232+
* Polymorphic serializer for [Notification] types.
233+
* Supports both client and server notifications, as well as custom notifications.
234+
*/
154235
internal object NotificationPolymorphicSerializer :
155236
JsonContentPolymorphicSerializer<Notification>(Notification::class) {
156237
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<Notification> =
@@ -159,35 +240,48 @@ internal object NotificationPolymorphicSerializer :
159240
?: CustomNotification.serializer()
160241
}
161242

162-
internal object RequestPolymorphicSerializer : JsonContentPolymorphicSerializer<Request>(Request::class) {
163-
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<Request> {
164-
val method = element.getMethodOrNull() ?: run {
165-
logger.error { "No method in $element" }
166-
error("No method in $element")
167-
}
243+
/**
244+
* Polymorphic serializer for [ClientNotification] types.
245+
* Falls back to [CustomNotification] for unknown methods.
246+
*/
247+
internal object ClientNotificationPolymorphicSerializer :
248+
JsonContentPolymorphicSerializer<ClientNotification>(ClientNotification::class) {
249+
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ClientNotification> =
250+
selectClientNotificationDeserializer(element)
251+
?: CustomNotification.serializer()
252+
}
168253

169-
return selectClientRequestDeserializer(method)
170-
?: selectServerRequestDeserializer(method)
171-
?: CustomRequest.serializer()
172-
}
254+
/**
255+
* Polymorphic serializer for [ServerNotification] types.
256+
* Falls back to [CustomNotification] for unknown methods.
257+
*/
258+
internal object ServerNotificationPolymorphicSerializer :
259+
JsonContentPolymorphicSerializer<ServerNotification>(ServerNotification::class) {
260+
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ServerNotification> =
261+
selectServerNotificationDeserializer(element)
262+
?: CustomNotification.serializer()
173263
}
174264

175-
private fun selectServerResultDeserializer(element: JsonElement): DeserializationStrategy<ServerResult>? {
265+
// ============================================================================
266+
// Result Serializers
267+
// ============================================================================
268+
269+
/**
270+
* Selects the appropriate deserializer for empty results.
271+
* Returns EmptyResult serializer if the JSON object is empty or contains only metadata.
272+
*/
273+
private fun selectEmptyResult(element: JsonElement): DeserializationStrategy<EmptyResult>? {
176274
val jsonObject = element.jsonObject
177275
return when {
178-
jsonObject.contains("protocolVersion") && jsonObject.contains("capabilities") -> InitializeResult.serializer()
179-
jsonObject.contains("completion") -> CompleteResult.serializer()
180-
jsonObject.contains("tools") -> ListToolsResult.serializer()
181-
jsonObject.contains("resources") -> ListResourcesResult.serializer()
182-
jsonObject.contains("resourceTemplates") -> ListResourceTemplatesResult.serializer()
183-
jsonObject.contains("prompts") -> ListPromptsResult.serializer()
184-
jsonObject.contains("messages") -> GetPromptResult.serializer()
185-
jsonObject.contains("contents") -> ReadResourceResult.serializer()
186-
jsonObject.contains("content") -> CallToolResult.serializer()
276+
jsonObject.isEmpty() || (jsonObject.size == 1 && jsonObject.contains("_meta")) -> EmptyResult.serializer()
187277
else -> null
188278
}
189279
}
190280

281+
/**
282+
* Selects the appropriate deserializer for client results based on JSON content.
283+
* Returns null if the structure doesn't match any known client result type.
284+
*/
191285
private fun selectClientResultDeserializer(element: JsonElement): DeserializationStrategy<ClientResult>? {
192286
val jsonObject = element.jsonObject
193287
return when {
@@ -198,22 +292,44 @@ private fun selectClientResultDeserializer(element: JsonElement): Deserializatio
198292
}
199293
}
200294

201-
private fun selectEmptyResult(element: JsonElement): DeserializationStrategy<EmptyResult>? {
295+
/**
296+
* Selects the appropriate deserializer for server results based on JSON content.
297+
* Returns null if the structure doesn't match any known server result type.
298+
*/
299+
private fun selectServerResultDeserializer(element: JsonElement): DeserializationStrategy<ServerResult>? {
202300
val jsonObject = element.jsonObject
203301
return when {
204-
jsonObject.isEmpty() || (jsonObject.size == 1 && jsonObject.contains("_meta")) -> EmptyResult.serializer()
302+
jsonObject.contains("protocolVersion") && jsonObject.contains("capabilities") -> InitializeResult.serializer()
303+
jsonObject.contains("completion") -> CompleteResult.serializer()
304+
jsonObject.contains("tools") -> ListToolsResult.serializer()
305+
jsonObject.contains("resources") -> ListResourcesResult.serializer()
306+
jsonObject.contains("resourceTemplates") -> ListResourceTemplatesResult.serializer()
307+
jsonObject.contains("prompts") -> ListPromptsResult.serializer()
308+
jsonObject.contains("messages") -> GetPromptResult.serializer()
309+
jsonObject.contains("contents") -> ReadResourceResult.serializer()
310+
jsonObject.contains("content") -> CallToolResult.serializer()
205311
else -> null
206312
}
207313
}
208314

209-
internal object ServerResultPolymorphicSerializer :
210-
JsonContentPolymorphicSerializer<ServerResult>(ServerResult::class) {
211-
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ServerResult> =
212-
selectServerResultDeserializer(element)
315+
/**
316+
* Polymorphic serializer for [RequestResult] types.
317+
* Supports both client and server results.
318+
* Throws [SerializationException] if the result type cannot be determined.
319+
*/
320+
internal object RequestResultPolymorphicSerializer :
321+
JsonContentPolymorphicSerializer<RequestResult>(RequestResult::class) {
322+
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<RequestResult> =
323+
selectClientResultDeserializer(element)
324+
?: selectServerResultDeserializer(element)
213325
?: selectEmptyResult(element)
214326
?: throw SerializationException("Cannot determine RequestResult type from JSON: ${element.jsonObject.keys}")
215327
}
216328

329+
/**
330+
* Polymorphic serializer for [ClientResult] types.
331+
* Throws [SerializationException] if the result type cannot be determined.
332+
*/
217333
internal object ClientResultPolymorphicSerializer :
218334
JsonContentPolymorphicSerializer<ClientResult>(ClientResult::class) {
219335
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ClientResult> =
@@ -222,15 +338,30 @@ internal object ClientResultPolymorphicSerializer :
222338
?: throw SerializationException("Cannot determine RequestResult type from JSON: ${element.jsonObject.keys}")
223339
}
224340

225-
internal object RequestResultPolymorphicSerializer :
226-
JsonContentPolymorphicSerializer<RequestResult>(RequestResult::class) {
227-
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<RequestResult> =
228-
selectClientResultDeserializer(element)
229-
?: selectServerResultDeserializer(element)
341+
/**
342+
* Polymorphic serializer for [ServerResult] types.
343+
* Throws [SerializationException] if the result type cannot be determined.
344+
*/
345+
internal object ServerResultPolymorphicSerializer :
346+
JsonContentPolymorphicSerializer<ServerResult>(ServerResult::class) {
347+
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ServerResult> =
348+
selectServerResultDeserializer(element)
230349
?: selectEmptyResult(element)
231350
?: throw SerializationException("Cannot determine RequestResult type from JSON: ${element.jsonObject.keys}")
232351
}
233352

353+
// ============================================================================
354+
// JSON-RPC Serializers
355+
// ============================================================================
356+
357+
/**
358+
* Polymorphic serializer for [JSONRPCMessage] types.
359+
* Determines the message type based on the presence of specific fields:
360+
* - "error" -> JSONRPCError
361+
* - "result" -> JSONRPCResponse
362+
* - "method" + "id" -> JSONRPCRequest
363+
* - "method" -> JSONRPCNotification
364+
*/
234365
internal object JSONRPCMessagePolymorphicSerializer :
235366
JsonContentPolymorphicSerializer<JSONRPCMessage>(JSONRPCMessage::class) {
236367
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<JSONRPCMessage> {
@@ -245,6 +376,10 @@ internal object JSONRPCMessagePolymorphicSerializer :
245376
}
246377
}
247378

379+
/**
380+
* Polymorphic serializer for [RequestId] types.
381+
* Supports both string and number IDs.
382+
*/
248383
internal object RequestIdPolymorphicSerializer : JsonContentPolymorphicSerializer<RequestId>(RequestId::class) {
249384
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<RequestId> = when (element) {
250385
is JsonPrimitive if (element.isString) -> RequestId.StringId.serializer()

0 commit comments

Comments
 (0)