///|
pub(all) enum Role {
User
Assistant
} derive(Debug, Eq)
///|
pub fn Role::to_string(self : Role) -> String {
match self {
User => "user"
Assistant => "assistant"
}
}
///|
pub impl ToJson for Role with fn to_json(self : Role) -> Json {
Json::string(self.to_string())
}
///|
pub impl @json.FromJson for Role with fn from_json(value, path) -> Role {
match value {
String("user") => User
String("assistant") => Assistant
_ =>
raise @json.JsonDecodeError(
(path, "role must be \"user\" or \"assistant\""),
)
}
}
///|
pub struct Annotations {
audience : Array[Role]
priority : Double?
last_modified : String?
} derive(Debug, Eq)
///|
pub fn Annotations::Annotations(
audience? : Array[Role] = [],
priority? : Double,
last_modified? : String,
) -> Annotations raise McpError {
if priority is Some(value) &&
(value.is_nan() || value.is_inf() || value < 0.0 || value > 1.0) {
raise InvalidParams(
"annotation priority must be finite and between 0 and 1",
)
}
{ audience: audience.copy(), priority, last_modified }
}
///|
fn Annotations::copy(self : Annotations) -> Annotations {
{ ..self, audience: self.audience.copy() }
}
///|
pub impl ToJson for Annotations with fn to_json(self : Annotations) -> Json {
let fields : Map[String, Json] = Map([])
if !self.audience.is_empty() {
fields["audience"] = self.audience
.map(fn(role) { ToJson::to_json(role) })
.to_json()
}
if self.priority is Some(priority) {
fields["priority"] = Json::number(priority)
}
fields.set_opt("lastModified", self.last_modified.map(Json::string))
Json::object(fields)
}
///|
pub impl @json.FromJson for Annotations with fn from_json(
value : Json,
path : @json.JsonPath,
) -> Annotations raise {
guard value is Object(fields) else {
raise JsonDecodeError((path, "expected object"))
}
let audience : Array[Role] = match fields.get("audience") {
None => []
Some(Array(values)) => {
let audience : Array[Role] = []
for i, item in values {
audience.push(
@json.from_json(item) catch {
JsonDecodeError((_, msg)) =>
raise JsonDecodeError(
(path.add_key("audience").add_index(i), msg),
)
},
)
}
audience
}
Some(_) =>
raise JsonDecodeError((path.add_key("audience"), "expected array"))
}
let priority = match fields.get("priority") {
Some(Number(value, ..)) => {
if value.is_nan() || value.is_inf() || value < 0.0 || value > 1.0 {
raise JsonDecodeError(
(path.add_key("priority"), "priority must be between 0 and 1"),
)
}
Some(value)
}
Some(Null) | None => None
Some(_) =>
raise JsonDecodeError((path.add_key("priority"), "expected number"))
}
let last_modified = match fields.get("lastModified") {
Some(String(s)) => Some(s)
Some(Null) | None => None
Some(_) =>
raise JsonDecodeError((path.add_key("lastModified"), "expected string"))
}
Annotations(audience~, priority?, last_modified?) catch {
error => raise JsonDecodeError((path, error.message()))
}
}
///|
pub fn Annotations::from_json(
value : Json,
path? : @json.JsonPath,
) -> Annotations raise McpError {
decode_json(value, path?)
}
///|
/// A link to a readable resource returned as content.
///
/// `size`, when known, is the raw resource byte length before base64 encoding
/// or tokenization.
pub struct ResourceLink {
uri : String
name : String
title : String?
description : String?
mime_type : String?
icons : Array[Icon]
annotations : Annotations?
size : Int64?
meta : Json?
} derive(Debug, Eq)
///|
pub fn ResourceLink::ResourceLink(
uri~ : String,
name~ : String,
title? : String,
description? : String,
mime_type? : String,
icons? : Array[Icon] = [],
annotations? : Annotations,
size? : Int64,
meta? : Json,
) -> ResourceLink raise McpError {
if size is Some(value) && value < 0L {
raise InvalidParams("resource link size must be non-negative")
}
{
uri,
name,
title,
description,
mime_type,
icons: icons.map(icon => icon.copy()),
annotations: annotations.map(fn(a) { a.copy() }),
size,
meta: meta.map(fn(m) { m.copy() }),
}
}
///|
pub impl ToJson for ResourceLink with fn to_json(self : ResourceLink) -> Json {
let fields : Map[String, Json] = {
"type": Json::string("resource_link"),
"uri": Json::string(self.uri),
"name": Json::string(self.name),
}
fields.set_opt("title", self.title.map(Json::string))
fields.set_opt("description", self.description.map(Json::string))
fields.set_opt("mimeType", self.mime_type.map(Json::string))
if !self.icons.is_empty() {
fields["icons"] = self.icons
.map(fn(icon) { ToJson::to_json(icon) })
.to_json()
}
if self.annotations is Some(annotations) {
fields["annotations"] = ToJson::to_json(annotations)
}
fields.set_opt(
"size",
self.size.map(fn(size) { Json::number(size.to_double(), repr="\{size}") }),
)
fields.set_opt("_meta", self.meta.map(fn(m) { m.copy() }))
Json::object(fields)
}
///|
pub impl @json.FromJson for ResourceLink with fn from_json(
value : Json,
path : @json.JsonPath,
) -> ResourceLink raise {
guard value is Object(fields) else {
raise JsonDecodeError((path, "expected object"))
}
let uri = match fields.get("uri") {
Some(String(u)) => u
Some(_) => raise JsonDecodeError((path.add_key("uri"), "expected string"))
None => raise JsonDecodeError((path.add_key("uri"), "uri is required"))
}
let name = match fields.get("name") {
Some(String(n)) => n
Some(_) => raise JsonDecodeError((path.add_key("name"), "expected string"))
None => raise JsonDecodeError((path.add_key("name"), "name is required"))
}
let title = match fields.get("title") {
Some(String(t)) => Some(t)
Some(Null) | None => None
Some(_) => raise JsonDecodeError((path.add_key("title"), "expected string"))
}
let description = match fields.get("description") {
Some(String(d)) => Some(d)
Some(Null) | None => None
Some(_) =>
raise JsonDecodeError((path.add_key("description"), "expected string"))
}
let mime_type = match fields.get("mimeType") {
Some(String(m)) => Some(m)
Some(Null) | None => None
Some(_) =>
raise JsonDecodeError((path.add_key("mimeType"), "expected string"))
}
let icons : Array[Icon] = match fields.get("icons") {
Some(Array(arr)) => {
let result : Array[Icon] = []
for i, it in arr {
result.push(
@json.from_json(it, path=path.add_key("icons").add_index(i)),
)
}
result
}
Some(Null) | None => []
Some(_) => raise JsonDecodeError((path.add_key("icons"), "expected array"))
}
let annotations : Annotations? = match fields {
{ "annotations": v, .. } =>
Some(@json.from_json(v, path=path.add_key("annotations")))
_ => None
}
let size : Int64? = match fields.get("size") {
Some(Number(s, ..)) => {
if s < 0.0 || s != s.to_int64().to_double() {
raise JsonDecodeError(
(path.add_key("size"), "expected non-negative integer"),
)
}
Some(s.to_int64())
}
Some(Null) | None => None
Some(_) => raise JsonDecodeError((path.add_key("size"), "expected number"))
}
let meta = fields.get("_meta")
ResourceLink(
uri~,
name~,
title?,
description?,
mime_type?,
icons~,
annotations?,
size?,
meta?,
) catch {
error => raise JsonDecodeError((path, error.to_string()))
}
}
///|
pub fn ResourceLink::from_json(
value : Json,
path? : @json.JsonPath,
) -> ResourceLink raise McpError {
decode_json(value, path?)
}
///|
/// Content payload accepted by MCP prompts and tool results.
///
/// Corresponds to `Content` in the MCP specification:
/// `TextContent | ImageContent | EmbeddedResource`
pub(all) enum Content {
TextContent(text~ : String, annotations~ : Annotations?, meta~ : Json?)
ImageContent(
data~ : String,
mime_type~ : String,
annotations~ : Annotations?,
meta~ : Json?
)
AudioContent(
data~ : String,
mime_type~ : String,
annotations~ : Annotations?,
meta~ : Json?
)
EmbeddedResource(
resource~ : ResourceContent,
annotations~ : Annotations?,
meta~ : Json?
)
ResourceLink(ResourceLink)
} derive(Debug, Eq)
///|
fn put_content_metadata(
fields : Map[String, Json],
annotations : Annotations?,
meta : Json?,
) -> Unit {
if annotations is Some(annotations) {
fields["annotations"] = ToJson::to_json(annotations)
}
fields.set_opt("_meta", meta.map(fn(m) { m.copy() }))
}
///|
pub impl ToJson for Content with fn to_json(self : Content) -> Json {
match self {
TextContent(text~, annotations~, meta~) => {
let fields : Map[String, Json] = {
"type": Json::string("text"),
"text": Json::string(text),
}
put_content_metadata(fields, annotations, meta)
Json::object(fields)
}
ImageContent(data~, mime_type~, annotations~, meta~) => {
let fields : Map[String, Json] = {
"type": Json::string("image"),
"data": Json::string(data),
"mimeType": Json::string(mime_type),
}
put_content_metadata(fields, annotations, meta)
Json::object(fields)
}
AudioContent(data~, mime_type~, annotations~, meta~) => {
let fields : Map[String, Json] = {
"type": Json::string("audio"),
"data": Json::string(data),
"mimeType": Json::string(mime_type),
}
put_content_metadata(fields, annotations, meta)
Json::object(fields)
}
EmbeddedResource(resource~, annotations~, meta~) => {
let fields : Map[String, Json] = {
"type": Json::string("resource"),
"resource": ToJson::to_json(resource),
}
put_content_metadata(fields, annotations, meta)
Json::object(fields)
}
ResourceLink(resource_link) => ToJson::to_json(resource_link)
}
}
///|
/// Construct a text content.
pub fn Content::text(
text : String,
annotations? : Annotations,
meta? : Json,
) -> Content {
TextContent(
text~,
annotations=annotations.map(fn(a) { a.copy() }),
meta=meta.map(fn(m) { m.copy() }),
)
}
///|
/// Construct an image content from base64-encoded data.
pub fn Content::image(
data~ : String,
mime_type~ : String,
annotations? : Annotations,
meta? : Json,
) -> Content {
ImageContent(
data~,
mime_type~,
annotations=annotations.map(fn(a) { a.copy() }),
meta=meta.map(fn(m) { m.copy() }),
)
}
///|
/// Construct an audio content from base64-encoded data.
pub fn Content::audio(
data~ : String,
mime_type~ : String,
annotations? : Annotations,
meta? : Json,
) -> Content {
AudioContent(
data~,
mime_type~,
annotations=annotations.map(fn(a) { a.copy() }),
meta=meta.map(fn(m) { m.copy() }),
)
}
///|
/// Construct a content that embeds resource contents inline.
pub fn Content::embedded_resource(
resource~ : ResourceContent,
annotations? : Annotations,
meta? : Json,
) -> Content {
EmbeddedResource(
resource~,
annotations=annotations.map(fn(a) { a.copy() }),
meta=meta.map(fn(m) { m.copy() }),
)
}
///|
/// Construct a content that links to a readable resource.
pub fn Content::resource_link(resource_link : ResourceLink) -> Content {
ResourceLink(resource_link)
}
///|
pub impl @json.FromJson for Content with fn from_json(
value : Json,
path : @json.JsonPath,
) -> Content raise {
guard value is Object(fields) else {
raise JsonDecodeError((path, "expected object"))
}
let annotations : Annotations? = match fields {
{ "annotations": v, .. } =>
Some(@json.from_json(v, path=path.add_key("annotations")))
_ => None
}
let meta = fields.get("_meta")
match fields.get("type") {
Some(String("text")) =>
match fields.get("text") {
Some(String(text)) => Content::text(text, annotations?, meta?)
Some(_) =>
raise JsonDecodeError((path.add_key("text"), "expected string"))
None =>
raise JsonDecodeError((path.add_key("text"), "text is required"))
}
Some(String("image")) => {
let data = match fields.get("data") {
Some(String(d)) => d
Some(_) =>
raise JsonDecodeError((path.add_key("data"), "expected string"))
None =>
raise JsonDecodeError((path.add_key("data"), "data is required"))
}
let mime_type = match fields.get("mimeType") {
Some(String(m)) => m
Some(_) =>
raise JsonDecodeError((path.add_key("mimeType"), "expected string"))
None =>
raise JsonDecodeError(
(path.add_key("mimeType"), "mimeType is required"),
)
}
Content::image(data~, mime_type~, annotations?, meta?)
}
Some(String("audio")) => {
let data = match fields.get("data") {
Some(String(d)) => d
Some(_) =>
raise JsonDecodeError((path.add_key("data"), "expected string"))
None =>
raise JsonDecodeError((path.add_key("data"), "data is required"))
}
let mime_type = match fields.get("mimeType") {
Some(String(m)) => m
Some(_) =>
raise JsonDecodeError((path.add_key("mimeType"), "expected string"))
None =>
raise JsonDecodeError(
(path.add_key("mimeType"), "mimeType is required"),
)
}
Content::audio(data~, mime_type~, annotations?, meta?)
}
Some(String("resource")) => {
guard fields is { "resource": res_val, .. } else {
raise JsonDecodeError(
(path.add_key("resource"), "resource is required"),
)
}
let resource : ResourceContent = @json.from_json(
res_val,
path=path.add_key("resource"),
)
Content::embedded_resource(resource~, annotations?, meta?)
}
Some(String("resource_link")) => {
let link : ResourceLink = @json.from_json(value, path~)
Content::resource_link(link)
}
Some(String(other)) =>
raise JsonDecodeError(
(path.add_key("type"), "unknown content type: \{other}"),
)
Some(_) => raise JsonDecodeError((path.add_key("type"), "expected string"))
None => raise JsonDecodeError((path.add_key("type"), "type is required"))
}
}
///|
/// Decode one official MCP content union value.
pub fn Content::from_json(
value : Json,
path? : @json.JsonPath,
) -> Content raise McpError {
decode_json(value, path?)
}