use sim_citizen_derive::Citizen;
use sim_kernel::{Error, Expr, Result, Symbol};
#[derive(Clone, Debug, PartialEq, Eq, Citizen)]
#[citizen(symbol = "skill/McpResourceDescriptor", version = 1)]
pub struct McpResourceDescriptor {
pub uri: String,
pub name: String,
pub description: String,
pub mime_type: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Citizen)]
#[citizen(symbol = "skill/McpResourceReadParams", version = 1)]
pub struct McpResourceReadParams {
pub uri: String,
}
impl Default for McpResourceDescriptor {
fn default() -> Self {
Self {
uri: "sim://citizen/resource".to_owned(),
name: "citizen-resource".to_owned(),
description: "Citizen fixture resource".to_owned(),
mime_type: Some("text/plain".to_owned()),
}
}
}
impl Default for McpResourceReadParams {
fn default() -> Self {
Self {
uri: "sim://citizen/resource".to_owned(),
}
}
}
impl McpResourceDescriptor {
pub fn to_expr(&self) -> Expr {
Expr::Map(vec![
field("kind", Expr::Symbol(Symbol::qualified("mcp", "resource"))),
field("uri", Expr::String(self.uri.clone())),
field("name", Expr::String(self.name.clone())),
field("description", Expr::String(self.description.clone())),
field(
"mimeType",
self.mime_type
.as_ref()
.map(|value| Expr::String(value.clone()))
.unwrap_or(Expr::Nil),
),
])
}
pub fn from_expr(expr: &Expr) -> Result<Self> {
let fields = map_fields(expr, "MCP resource descriptor")?;
Ok(Self {
uri: required_string(fields, "uri")?,
name: required_string(fields, "name")?,
description: required_string(fields, "description")?,
mime_type: optional_string(fields, "mimeType")?,
})
}
}
impl McpResourceReadParams {
pub fn to_expr(&self) -> Expr {
Expr::Map(vec![
field(
"kind",
Expr::Symbol(Symbol::qualified("mcp", "resources-read")),
),
field("uri", Expr::String(self.uri.clone())),
])
}
pub fn from_expr(expr: &Expr) -> Result<Self> {
let fields = map_fields(expr, "MCP resources/read params")?;
Ok(Self {
uri: required_string(fields, "uri")?,
})
}
}
use sim_value::access::map_entries as map_fields;
fn required_string(fields: &[(Expr, Expr)], name: &str) -> Result<String> {
match required_field(fields, name)? {
Expr::String(value) => Ok(value.clone()),
_ => Err(Error::TypeMismatch {
expected: "string",
found: "non-string",
}),
}
}
fn optional_string(fields: &[(Expr, Expr)], name: &str) -> Result<Option<String>> {
match optional_field(fields, name) {
Some(Expr::String(value)) => Ok(Some(value.clone())),
Some(Expr::Nil) | None => Ok(None),
Some(_) => Err(Error::TypeMismatch {
expected: "string or nil",
found: "invalid optional string",
}),
}
}
fn required_field<'a>(fields: &'a [(Expr, Expr)], name: &str) -> Result<&'a Expr> {
optional_field(fields, name)
.ok_or_else(|| Error::Eval(format!("MCP resource descriptor is missing {name}")))
}
use sim_value::access::entry_field as optional_field;
use sim_value::build::entry as field;
pub fn mcp_resource_descriptor_class_symbol() -> Symbol {
Symbol::qualified("skill", "McpResourceDescriptor")
}
pub fn mcp_resource_read_params_class_symbol() -> Symbol {
Symbol::qualified("skill", "McpResourceReadParams")
}