Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ object Main:
case "elicitation-sep1034-client-defaults" =>
supervised:
val oxTransport = OxClientHttpTransport(backend, serverUrl, protocolVersion)
val handler = (req: ElicitRequest) => ElicitResult(ElicitAction.Accept, Some(defaultsFrom(req.params.requestedSchema)))
val handler = (req: ElicitRequest) =>
val requestedSchema = req.params match
case ElicitParams.Form(_, schema, _, _) => schema
case _: ElicitParams.Url => Json.obj()
ElicitResult(ElicitAction.Accept, Some(defaultsFrom(requestedSchema)))
val client = McpClient.bidirectional[Identity](
oxTransport,
clientInfo,
Expand Down
50 changes: 45 additions & 5 deletions core/src/main/scala/chimp/protocol/Elicitation.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package chimp.protocol

import io.circe.syntax.*
import io.circe.{Codec, Decoder, Encoder, Json}

enum ElicitAction:
Expand All @@ -16,11 +17,50 @@ object ElicitAction:
case "cancel" => Right(ElicitAction.Cancel)
case other => Left(s"Unknown elicitation action: $other")

final case class ElicitParams(
message: String,
requestedSchema: Json,
_meta: Option[Map[String, Json]] = None
) derives Codec
/** Params of an `elicitation/create` request (2026-07-28): either a `Form` (a JSON schema to fill in) or a `Url` (open a URL to complete
* the interaction out of band). `mode` distinguishes them on the wire; it is optional on `Form` for backward compatibility with
* 2025-11-25.
*/
enum ElicitParams:
case Form(message: String, requestedSchema: Json, mode: Option[String] = None, _meta: Option[Map[String, Json]] = None)
case Url(message: String, url: String, mode: String = "url", _meta: Option[Map[String, Json]] = None)

object ElicitParams:
given Encoder[ElicitParams] = Encoder.instance:
case Form(message, requestedSchema, mode, meta) =>
Json
.obj(
"message" -> Json.fromString(message),
"requestedSchema" -> requestedSchema,
"mode" -> mode.asJson,
"_meta" -> meta.asJson
)
.dropNullValues
case Url(message, url, mode, meta) =>
Json
.obj(
"message" -> Json.fromString(message),
"url" -> Json.fromString(url),
"mode" -> Json.fromString(mode),
"_meta" -> meta.asJson
)
.dropNullValues

given Decoder[ElicitParams] = Decoder.instance: c =>
if c.downField("url").succeeded then
for
message <- c.downField("message").as[String]
url <- c.downField("url").as[String]
mode <- c.downField("mode").as[Option[String]]
meta <- c.downField("_meta").as[Option[Map[String, Json]]]
yield Url(message, url, mode.getOrElse("url"), meta)
else
for
message <- c.downField("message").as[String]
requestedSchema <- c.downField("requestedSchema").as[Json]
mode <- c.downField("mode").as[Option[String]]
meta <- c.downField("_meta").as[Option[Map[String, Json]]]
yield Form(message, requestedSchema, mode, meta)

final case class ElicitRequest(method: String = "elicitation/create", params: ElicitParams) derives Codec

Expand Down
2 changes: 2 additions & 0 deletions core/src/main/scala/chimp/protocol/JsonRpc.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,6 @@ enum JSONRPCErrorCodes(val code: Int):
case InternalError extends JSONRPCErrorCodes(-32603)
case InvocationError extends JSONRPCErrorCodes(-32000)
case ResourceNotFound extends JSONRPCErrorCodes(-32002)
case HeaderMismatch extends JSONRPCErrorCodes(-32020)
case MissingRequiredClientCapability extends JSONRPCErrorCodes(-32021)
case UnsupportedProtocolVersion extends JSONRPCErrorCodes(-32022)
9 changes: 8 additions & 1 deletion core/src/main/scala/chimp/protocol/Lifecycle.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ package chimp.protocol

import io.circe.{Codec, Json}

final case class Implementation(name: String, version: String, title: Option[String] = None) derives Codec
final case class Implementation(
name: String,
version: String,
title: Option[String] = None,
icons: Option[List[Icon]] = None,
websiteUrl: Option[String] = None,
description: Option[String] = None
) derives Codec

final case class ClientRootsCapability(listChanged: Option[Boolean] = None) derives Codec

Expand Down
22 changes: 22 additions & 0 deletions core/src/main/scala/chimp/protocol/Metadata.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package chimp.protocol

import io.circe.Codec

/** An icon for a resource, tool, prompt, or implementation (2026-07-28). `src` is a URI (http(s) or data:); the other fields are optional
* hints. Present on `icons` fields across the shared types.
*/
final case class Icon(
src: String,
mimeType: Option[String] = None,
sizes: Option[List[String]] = None,
theme: Option[String] = None
) derives Codec

/** Optional annotations describing how an object or content should be used (2026-07-28): its intended `audience`, a `priority` (0.0-1.0),
* and a `lastModified` ISO-8601 timestamp.
*/
final case class Annotations(
audience: Option[List[Role]] = None,
lastModified: Option[String] = None,
priority: Option[Double] = None
) derives Codec
56 changes: 56 additions & 0 deletions core/src/main/scala/chimp/protocol/Mrtr.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package chimp.protocol

import io.circe.syntax.*
import io.circe.{Decoder, DecodingFailure, Encoder, HCursor, Json}

/** A server-to-client request embedded in an `input_required` tool result (MRTR, 2026-07-28): a sampling, roots or elicitation request. On
* the wire it is the bare request object, discriminated by its `method`.
*/
enum InputRequest:
case Sampling(request: CreateMessageRequest)
case Roots(request: ListRootsRequest)
case Elicit(request: ElicitRequest)

object InputRequest:
given Encoder[InputRequest] = Encoder.instance:
case Sampling(request) => request.asJson
case Roots(request) => request.asJson
case Elicit(request) => request.asJson

given Decoder[InputRequest] = Decoder.instance: (c: HCursor) =>
c.downField("method")
.as[String]
.flatMap:
case "sampling/createMessage" => c.as[CreateMessageRequest].map(Sampling(_))
case "roots/list" => c.as[ListRootsRequest].map(Roots(_))
case "elicitation/create" => c.as[ElicitRequest].map(Elicit(_))
case other => Left(DecodingFailure(s"Unknown InputRequest method: $other", c.history))

/** A client-to-server response to an [[InputRequest]] (MRTR): a sampling, roots or elicitation result. Discriminated structurally
* (elicitation by `action`, roots by `roots`, otherwise sampling).
*/
enum InputResponse:
case Sampling(result: CreateMessageResult)
case Roots(result: ListRootsResult)
case Elicit(result: ElicitResult)

object InputResponse:
given Encoder[InputResponse] = Encoder.instance:
case Sampling(result) => result.asJson
case Roots(result) => result.asJson
case Elicit(result) => result.asJson

given Decoder[InputResponse] = Decoder.instance: (c: HCursor) =>
if c.downField("action").succeeded then c.as[ElicitResult].map(Elicit(_))
else if c.downField("roots").succeeded then c.as[ListRootsResult].map(Roots(_))
else c.as[CreateMessageResult].map(Sampling(_))

/** Result of a tool call that needs client input before it can finish (MRTR): the pending [[InputRequest]]s keyed by an opaque key, the
* opaque `requestState` the client echoes back on its response, and the modern result envelope fields.
*/
final case class InputRequiredResult(
inputRequests: Map[String, InputRequest],
requestState: Option[String] = None,
resultType: ResultType = ResultType.Incomplete,
_meta: Option[Map[String, Json]] = None
) derives io.circe.Codec
5 changes: 4 additions & 1 deletion core/src/main/scala/chimp/protocol/Prompts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ final case class Prompt(
title: Option[String] = None,
description: Option[String] = None,
arguments: Option[List[PromptArgument]] = None,
icons: Option[List[Icon]] = None,
_meta: Option[Map[String, Json]] = None
) derives Codec

Expand All @@ -42,6 +43,8 @@ final case class ListPromptsResult(
final case class GetPromptParams(
name: String,
arguments: Option[Map[String, String]] = None,
inputResponses: Option[Map[String, InputResponse]] = None,
requestState: Option[String] = None,
_meta: Option[Map[String, Json]] = None
) derives Codec
final case class GetPromptRequest(method: String = "prompts/get", params: GetPromptParams) derives Codec
Expand All @@ -53,4 +56,4 @@ final case class GetPromptResult(

final case class PromptListChangedNotification(method: String = "notifications/prompts/list_changed") derives Codec

final case class PromptReference(`type`: String = "ref/prompt", name: String) derives Codec
final case class PromptReference(`type`: String = "ref/prompt", name: String, title: Option[String] = None) derives Codec
11 changes: 10 additions & 1 deletion core/src/main/scala/chimp/protocol/Resources.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ final case class Resource(
description: Option[String] = None,
mimeType: Option[String] = None,
size: Option[Long] = None,
icons: Option[List[Icon]] = None,
annotations: Option[Annotations] = None,
_meta: Option[Map[String, Json]] = None
) derives Codec

Expand All @@ -19,6 +21,8 @@ final case class ResourceTemplate(
title: Option[String] = None,
description: Option[String] = None,
mimeType: Option[String] = None,
icons: Option[List[Icon]] = None,
annotations: Option[Annotations] = None,
_meta: Option[Map[String, Json]] = None
) derives Codec

Expand Down Expand Up @@ -84,7 +88,12 @@ final case class ListResourceTemplatesResult(
_meta: Option[Map[String, Json]] = None
) derives Codec

final case class ReadResourceParams(uri: String, _meta: Option[Map[String, Json]] = None) derives Codec
final case class ReadResourceParams(
uri: String,
inputResponses: Option[Map[String, InputResponse]] = None,
requestState: Option[String] = None,
_meta: Option[Map[String, Json]] = None
) derives Codec
final case class ReadResourceRequest(method: String = "resources/read", params: ReadResourceParams) derives Codec
final case class ReadResourceResult(contents: List[ResourceContents], _meta: Option[Map[String, Json]] = None) derives Codec

Expand Down
Loading
Loading