diff --git a/core/src/main/scala/chimp/protocol/Subscriptions.scala b/core/src/main/scala/chimp/protocol/Subscriptions.scala new file mode 100644 index 0000000..4a8992c --- /dev/null +++ b/core/src/main/scala/chimp/protocol/Subscriptions.scala @@ -0,0 +1,34 @@ +package chimp.protocol + +import io.circe.{Codec, Json} + +/** The notification types a client opts into on a `subscriptions/listen` stream (2026-07-28). Each type is opt-in: the server MUST NOT send + * a type the client has not requested. `resourceSubscriptions` replaces the former `resources/subscribe` RPC. + */ +final case class SubscriptionFilter( + promptsListChanged: Option[Boolean] = None, + resourceSubscriptions: Option[List[String]] = None, + resourcesListChanged: Option[Boolean] = None, + toolsListChanged: Option[Boolean] = None +) derives Codec + +/** Params of a `subscriptions/listen` request: the notification filter the client opts into on the stream. */ +final case class SubscriptionsListenParams(notifications: SubscriptionFilter, _meta: Option[Map[String, Json]] = None) derives Codec + +final case class SubscriptionsListenRequest(method: String = "subscriptions/listen", params: SubscriptionsListenParams) derives Codec + +/** Result of `subscriptions/listen`, sent only when the server tears the subscription down gracefully (e.g. on shutdown); the body is + * otherwise empty. The subscription id travels in `_meta` under `io.modelcontextprotocol/subscriptionId`. + */ +final case class SubscriptionsListenResult(resultType: ResultType = ResultType.Complete, _meta: Option[Map[String, Json]] = None) + derives Codec + +/** Params of the `notifications/subscriptions/acknowledged` notification: the subset of requested notification types the server will honor. + */ +final case class SubscriptionsAcknowledgedParams(notifications: SubscriptionFilter, _meta: Option[Map[String, Json]] = None) derives Codec + +/** The `notifications/subscriptions/acknowledged` notification the server sends first on a listen stream. */ +final case class SubscriptionsAcknowledgedNotification( + method: String = "notifications/subscriptions/acknowledged", + params: SubscriptionsAcknowledgedParams +) derives Codec diff --git a/core/src/main/scala/chimp/protocol/Versioning.scala b/core/src/main/scala/chimp/protocol/Versioning.scala index 3ee7283..55ccf33 100644 --- a/core/src/main/scala/chimp/protocol/Versioning.scala +++ b/core/src/main/scala/chimp/protocol/Versioning.scala @@ -17,6 +17,7 @@ object ProtocolMeta: val ClientInfo: String = "io.modelcontextprotocol/clientInfo" val ClientCapabilities: String = "io.modelcontextprotocol/clientCapabilities" val ServerInfo: String = "io.modelcontextprotocol/serverInfo" + val SubscriptionId: String = "io.modelcontextprotocol/subscriptionId" /** An `UnsupportedProtocolVersion` error (`-32022`) naming the versions the server supports, so the client can retry with one of them. */ def unsupportedVersionError(requested: String, supported: List[String]): JSONRPCErrorObject = diff --git a/core/src/test/scala/chimp/protocol/Schema20260728ConformanceSpec.scala b/core/src/test/scala/chimp/protocol/Schema20260728ConformanceSpec.scala index 7ed65d1..d06d66c 100644 --- a/core/src/test/scala/chimp/protocol/Schema20260728ConformanceSpec.scala +++ b/core/src/test/scala/chimp/protocol/Schema20260728ConformanceSpec.scala @@ -34,3 +34,37 @@ class Schema20260728ConformanceSpec extends SchemaConformance: val msg: JSONRPCMessage = JSONRPCMessage.Error(id = RequestId(1), error = ProtocolMeta.unsupportedVersionError("1900-01-01", List("2026-07-28", "2025-11-25"))) validate("UnsupportedProtocolVersionError", msg) + + it should "produce a SubscriptionFilter that matches the spec schema" in: + validate( + "SubscriptionFilter", + SubscriptionFilter(resourceSubscriptions = Some(List("file:///x")), resourcesListChanged = Some(true), toolsListChanged = Some(false)) + ) + + it should "produce SubscriptionsListenRequestParams that match the spec schema" in: + validate( + "SubscriptionsListenRequestParams", + SubscriptionsListenParams( + notifications = SubscriptionFilter(resourcesListChanged = Some(true)), + _meta = Some(Map(ProtocolMeta.ProtocolVersionKey -> "2026-07-28".asJson, ProtocolMeta.ClientCapabilities -> io.circe.Json.obj())) + ) + ) + + it should "produce a SubscriptionsListenResult that matches the spec schema" in: + validate( + "SubscriptionsListenResult", + SubscriptionsListenResult(_meta = + Some( + Map( + ProtocolMeta.ServerInfo -> Implementation(name = "chimp", version = "1.0").asJson.deepDropNullValues, + ProtocolMeta.SubscriptionId -> "sub-1".asJson + ) + ) + ) + ) + + it should "produce SubscriptionsAcknowledgedNotificationParams that match the spec schema" in: + validate( + "SubscriptionsAcknowledgedNotificationParams", + SubscriptionsAcknowledgedParams(notifications = SubscriptionFilter(promptsListChanged = Some(true))) + )