Skip to content

Commit cb79ea7

Browse files
authored
Update notification schema and refactor tests package structure (#199)
1 parent e5214fe commit cb79ea7

File tree

14 files changed

+1174
-151
lines changed

14 files changed

+1174
-151
lines changed

api/kotlin-sdk.api

Lines changed: 372 additions & 53 deletions
Large diffs are not rendered by default.

src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,7 @@ public open class Server(
558558
* @param params The logging message notification parameters.
559559
*/
560560
public suspend fun sendLoggingMessage(params: LoggingMessageNotification) {
561-
logger.trace { "Sending logging message: ${params.data}" }
561+
logger.trace { "Sending logging message: ${params.params.data}" }
562562
notification(params)
563563
}
564564

@@ -568,7 +568,7 @@ public open class Server(
568568
* @param params Details of the updated resource.
569569
*/
570570
public suspend fun sendResourceUpdated(params: ResourceUpdatedNotification) {
571-
logger.debug { "Sending resource updated notification for: ${params.uri}" }
571+
logger.debug { "Sending resource updated notification for: ${params.params.uri}" }
572572
notification(params)
573573
}
574574

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -292,11 +292,11 @@ public abstract class Protocol(
292292
}
293293

294294
private fun onProgress(notification: ProgressNotification) {
295-
LOGGER.trace { "Received progress notification: token=${notification.progressToken}, progress=${notification.progress}/${notification.total}" }
296-
val progress = notification.progress
297-
val total = notification.total
298-
val message = notification.message
299-
val progressToken = notification.progressToken
295+
LOGGER.trace { "Received progress notification: token=${notification.params.progressToken}, progress=${notification.params.progress}/${notification.params.total}" }
296+
val progress = notification.params.progress
297+
val total = notification.params.total
298+
val message = notification.params.message
299+
val progressToken = notification.params.progressToken
300300

301301
val handler = _progressHandlers.value[progressToken]
302302
if (handler == null) {
@@ -424,7 +424,12 @@ public abstract class Protocol(
424424
_responseHandlers.update { current -> current.remove(messageId) }
425425
_progressHandlers.update { current -> current.remove(messageId) }
426426

427-
val notification = CancelledNotification(requestId = messageId, reason = reason.message ?: "Unknown")
427+
val notification = CancelledNotification(
428+
params = CancelledNotification.Params(
429+
requestId = messageId,
430+
reason = reason.message ?: "Unknown"
431+
)
432+
)
428433

429434
val serialized = JSONRPCNotification(
430435
notification.method.value,

src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt

Lines changed: 133 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import kotlinx.serialization.Serializable
99
import kotlinx.serialization.json.JsonElement
1010
import kotlinx.serialization.json.JsonObject
1111
import kotlinx.serialization.json.JsonPrimitive
12+
import kotlinx.serialization.json.buildJsonObject
1213
import kotlinx.serialization.json.decodeFromJsonElement
1314
import kotlinx.serialization.json.encodeToJsonElement
1415
import kotlinx.serialization.json.jsonObject
@@ -125,10 +126,11 @@ public sealed interface Request {
125126
* @return The JSON-RPC request representation.
126127
*/
127128
internal fun Request.toJSON(): JSONRPCRequest {
128-
val encoded = JsonObject(McpJson.encodeToJsonElement(this).jsonObject.minus("method"))
129+
val fullJson = McpJson.encodeToJsonElement(this).jsonObject
130+
val params = JsonObject(fullJson.filterKeys { it != "method" })
129131
return JSONRPCRequest(
130132
method = method.value,
131-
params = encoded,
133+
params = params,
132134
jsonrpc = JSONRPC_VERSION,
133135
)
134136
}
@@ -139,8 +141,7 @@ internal fun Request.toJSON(): JSONRPCRequest {
139141
* @return The decoded [Request] or null
140142
*/
141143
internal fun JSONRPCRequest.fromJSON(): Request {
142-
val requestData = JsonObject(params.jsonObject.plus("method" to JsonPrimitive(method)))
143-
144+
val requestData = JsonObject(params.jsonObject + ("method" to JsonPrimitive(method)))
144145
val deserializer = selectRequestDeserializer(method)
145146
return McpJson.decodeFromJsonElement(deserializer, requestData)
146147
}
@@ -159,6 +160,7 @@ public open class CustomRequest(override val method: Method) : Request
159160
@Serializable(with = NotificationPolymorphicSerializer::class)
160161
public sealed interface Notification {
161162
public val method: Method
163+
public val params: NotificationParams?
162164
}
163165

164166
/**
@@ -167,10 +169,9 @@ public sealed interface Notification {
167169
* @return The JSON-RPC notification representation.
168170
*/
169171
internal fun Notification.toJSON(): JSONRPCNotification {
170-
val encoded = JsonObject(McpJson.encodeToJsonElement<Notification>(this).jsonObject.minus("method"))
171172
return JSONRPCNotification(
172-
method.value,
173-
params = encoded
173+
method = method.value,
174+
params = McpJson.encodeToJsonElement(params),
174175
)
175176
}
176177

@@ -180,7 +181,10 @@ internal fun Notification.toJSON(): JSONRPCNotification {
180181
* @return The decoded [Notification].
181182
*/
182183
internal fun JSONRPCNotification.fromJSON(): Notification {
183-
val data = JsonObject(params.jsonObject.plus("method" to JsonPrimitive(method)))
184+
val data = buildJsonObject {
185+
put("method", JsonPrimitive(method))
186+
put("params", params)
187+
}
184188
return McpJson.decodeFromJsonElement<Notification>(data)
185189
}
186190

@@ -295,6 +299,12 @@ public data class JSONRPCError(
295299
val data: JsonObject = EmptyJsonObject,
296300
) : JSONRPCMessage
297301

302+
/**
303+
* Base interface for notification parameters with optional metadata.
304+
*/
305+
@Serializable
306+
public sealed interface NotificationParams : WithMeta
307+
298308
/* Cancellation */
299309
/**
300310
* This notification can be sent by either side to indicate that it is cancelling a previously issued request.
@@ -307,19 +317,24 @@ public data class JSONRPCError(
307317
*/
308318
@Serializable
309319
public data class CancelledNotification(
310-
/**
311-
* The ID of the request to cancel.
312-
*
313-
* It MUST correspond to the ID of a request previously issued in the same direction.
314-
*/
315-
val requestId: RequestId,
316-
/**
317-
* An optional string describing the reason for the cancellation. This MAY be logged or presented to the user.
318-
*/
319-
val reason: String?,
320-
override val _meta: JsonObject = EmptyJsonObject,
321-
) : ClientNotification, ServerNotification, WithMeta {
320+
override val params: Params,
321+
) : ClientNotification, ServerNotification {
322322
override val method: Method = Method.Defined.NotificationsCancelled
323+
324+
@Serializable
325+
public data class Params(
326+
/**
327+
* The ID of the request to cancel.
328+
*
329+
* It MUST correspond to the ID of a request previously issued in the same direction.
330+
*/
331+
val requestId: RequestId,
332+
/**
333+
* An optional string describing the reason for the cancellation. This MAY be logged or presented to the user.
334+
*/
335+
val reason: String? = null,
336+
override val _meta: JsonObject = EmptyJsonObject,
337+
) : NotificationParams
323338
}
324339

325340
/* Initialization */
@@ -408,7 +423,7 @@ public sealed interface ServerResult : RequestResult
408423
*/
409424
@Serializable
410425
public data class UnknownMethodRequestOrNotification(
411-
override val method: Method,
426+
override val method: Method, override val params: NotificationParams? = null,
412427
) : ClientNotification, ClientRequest, ServerNotification, ServerRequest
413428

414429
/**
@@ -506,8 +521,15 @@ public data class InitializeResult(
506521
* This notification is sent from the client to the server after initialization has finished.
507522
*/
508523
@Serializable
509-
public class InitializedNotification : ClientNotification {
524+
public data class InitializedNotification(
525+
override val params: Params = Params(),
526+
) : ClientNotification {
510527
override val method: Method = Method.Defined.NotificationsInitialized
528+
529+
@Serializable
530+
public data class Params(
531+
override val _meta: JsonObject = EmptyJsonObject,
532+
) : NotificationParams
511533
}
512534

513535
/* Ping */
@@ -528,7 +550,7 @@ public sealed interface ProgressBase {
528550
/**
529551
* The progress thus far. This should increase every time progress is made, even if the total is unknown.
530552
*/
531-
public val progress: Int
553+
public val progress: Double
532554

533555
/**
534556
* Total number of items to a process (or total progress required), if known.
@@ -553,7 +575,7 @@ public open class Progress(
553575
/**
554576
* The progress thus far. This should increase every time progress is made, even if the total is unknown.
555577
*/
556-
override val progress: Int,
578+
override val progress: Double,
557579

558580
/**
559581
* Total number of items to a process (or total progress required), if known.
@@ -571,18 +593,32 @@ public open class Progress(
571593
*/
572594
@Serializable
573595
public data class ProgressNotification(
574-
override val progress: Int,
575-
/**
576-
* The progress token,
577-
* which was given in the initial request,
578-
* used to associate this notification with the request that is proceeding.
579-
*/
580-
public val progressToken: ProgressToken,
581-
@Suppress("PropertyName") val _meta: JsonObject = EmptyJsonObject,
582-
override val total: Double?,
583-
override val message: String?,
584-
) : ClientNotification, ServerNotification, ProgressBase {
596+
override val params: Params,
597+
) : ClientNotification, ServerNotification {
585598
override val method: Method = Method.Defined.NotificationsProgress
599+
600+
@Serializable
601+
public data class Params(
602+
/**
603+
* The progress thus far. This should increase every time progress is made, even if the total is unknown.
604+
*/
605+
override val progress: Double,
606+
/**
607+
* The progress token,
608+
* which was given in the initial request,
609+
* used to associate this notification with the request that is proceeding.
610+
*/
611+
val progressToken: ProgressToken,
612+
/**
613+
* Total number of items to process (or total progress required), if known.
614+
*/
615+
override val total: Double? = null,
616+
/**
617+
* An optional message describing the current progress.
618+
*/
619+
override val message: String? = null,
620+
override val _meta: JsonObject = EmptyJsonObject,
621+
) : NotificationParams, ProgressBase
586622
}
587623

588624
/* Pagination */
@@ -784,8 +820,15 @@ public class ReadResourceResult(
784820
* Servers may issue this without any previous subscription from the client.
785821
*/
786822
@Serializable
787-
public class ResourceListChangedNotification : ServerNotification {
823+
public data class ResourceListChangedNotification(
824+
override val params: Params = Params(),
825+
) : ServerNotification {
788826
override val method: Method = Method.Defined.NotificationsResourcesListChanged
827+
828+
@Serializable
829+
public data class Params(
830+
override val _meta: JsonObject = EmptyJsonObject,
831+
) : NotificationParams
789832
}
790833

791834
/**
@@ -821,13 +864,18 @@ public data class UnsubscribeRequest(
821864
*/
822865
@Serializable
823866
public data class ResourceUpdatedNotification(
824-
/**
825-
* The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to.
826-
*/
827-
val uri: String,
828-
override val _meta: JsonObject = EmptyJsonObject,
829-
) : ServerNotification, WithMeta {
867+
override val params: Params,
868+
) : ServerNotification {
830869
override val method: Method = Method.Defined.NotificationsResourcesUpdated
870+
871+
@Serializable
872+
public data class Params(
873+
/**
874+
* The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to.
875+
*/
876+
val uri: String,
877+
override val _meta: JsonObject = EmptyJsonObject,
878+
) : NotificationParams
831879
}
832880

833881
/* Prompts */
@@ -1044,8 +1092,15 @@ public class GetPromptResult(
10441092
* Servers may issue this without any previous subscription from the client.
10451093
*/
10461094
@Serializable
1047-
public class PromptListChangedNotification : ServerNotification {
1095+
public data class PromptListChangedNotification(
1096+
override val params: Params = Params(),
1097+
) : ServerNotification {
10481098
override val method: Method = Method.Defined.NotificationsPromptsListChanged
1099+
1100+
@Serializable
1101+
public data class Params(
1102+
override val _meta: JsonObject = EmptyJsonObject,
1103+
) : NotificationParams
10491104
}
10501105

10511106
/* Tools */
@@ -1223,8 +1278,15 @@ public data class CallToolRequest(
12231278
* Servers may issue this without any previous subscription from the client.
12241279
*/
12251280
@Serializable
1226-
public class ToolListChangedNotification : ServerNotification {
1281+
public data class ToolListChangedNotification(
1282+
override val params: Params = Params(),
1283+
) : ServerNotification {
12271284
override val method: Method = Method.Defined.NotificationsToolsListChanged
1285+
1286+
@Serializable
1287+
public data class Params(
1288+
override val _meta: JsonObject = EmptyJsonObject,
1289+
) : NotificationParams
12281290
}
12291291

12301292
/* Logging */
@@ -1252,22 +1314,27 @@ public enum class LoggingLevel {
12521314
*/
12531315
@Serializable
12541316
public data class LoggingMessageNotification(
1255-
/**
1256-
* The severity of this log message.
1257-
*/
1258-
val level: LoggingLevel,
1317+
override val params: Params,
1318+
) : ServerNotification {
1319+
override val method: Method = Method.Defined.NotificationsMessage
12591320

1260-
/**
1261-
* An optional name of the logger issuing this message.
1262-
*/
1263-
val logger: String? = null,
1321+
@Serializable
1322+
public data class Params(
1323+
/**
1324+
* The severity of this log message.
1325+
*/
1326+
val level: LoggingLevel,
1327+
/**
1328+
* An optional name of the logger issuing this message.
1329+
*/
1330+
val logger: String? = null,
1331+
/**
1332+
* The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here.
1333+
*/
1334+
val data: JsonElement,
1335+
override val _meta: JsonObject = EmptyJsonObject,
1336+
) : NotificationParams
12641337

1265-
/**
1266-
* The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here.
1267-
*/
1268-
val data: JsonObject = EmptyJsonObject,
1269-
override val _meta: JsonObject = EmptyJsonObject,
1270-
) : ServerNotification, WithMeta {
12711338
/**
12721339
* A request from the client to the server to enable or adjust logging.
12731340
*/
@@ -1281,8 +1348,6 @@ public data class LoggingMessageNotification(
12811348
) : ClientRequest, WithMeta {
12821349
override val method: Method = Method.Defined.LoggingSetLevel
12831350
}
1284-
1285-
override val method: Method = Method.Defined.NotificationsMessage
12861351
}
12871352

12881353
/* Sampling */
@@ -1578,8 +1643,15 @@ public class ListRootsResult(
15781643
* A notification from the client to the server, informing it that the list of roots has changed.
15791644
*/
15801645
@Serializable
1581-
public class RootsListChangedNotification : ClientNotification {
1646+
public data class RootsListChangedNotification(
1647+
override val params: Params = Params(),
1648+
) : ClientNotification {
15821649
override val method: Method = Method.Defined.NotificationsRootsListChanged
1650+
1651+
@Serializable
1652+
public data class Params(
1653+
override val _meta: JsonObject = EmptyJsonObject,
1654+
) : NotificationParams
15831655
}
15841656

15851657
/**

0 commit comments

Comments
 (0)