use sim_citizen::CitizenField;
use sim_citizen_derive::Citizen;
use sim_kernel::Expr;
use sim_kernel::{Result, Symbol};
#[derive(Clone, Debug, PartialEq)]
pub enum McpEnvelope {
Request(McpRequest),
Notification(McpNotification),
Response(McpResponse),
Error(McpErrorEnvelope),
}
#[derive(Clone, Debug, PartialEq, Citizen)]
#[citizen(symbol = "mcp/Request", version = 1)]
pub struct McpRequest {
pub id: Expr,
pub method: String,
pub params: Expr,
}
#[derive(Clone, Debug, PartialEq, Citizen)]
#[citizen(symbol = "mcp/Notification", version = 1)]
pub struct McpNotification {
pub method: String,
pub params: Expr,
}
#[derive(Clone, Debug, PartialEq, Citizen)]
#[citizen(symbol = "mcp/Response", version = 1)]
pub struct McpResponse {
pub id: Expr,
pub result: Expr,
}
#[derive(Clone, Debug, PartialEq, Citizen)]
#[citizen(symbol = "mcp/ErrorEnvelope", version = 1)]
pub struct McpErrorEnvelope {
pub id: Expr,
pub error: McpError,
}
#[derive(Clone, Debug, PartialEq, Citizen)]
#[citizen(symbol = "mcp/Error", version = 1)]
pub struct McpError {
pub code: i64,
pub message: String,
pub data: Expr,
}
impl Default for McpRequest {
fn default() -> Self {
Self {
id: Expr::String("fixture".to_owned()),
method: "tools/list".to_owned(),
params: Expr::Map(Vec::new()),
}
}
}
impl Default for McpNotification {
fn default() -> Self {
Self {
method: "notifications/initialized".to_owned(),
params: Expr::Map(Vec::new()),
}
}
}
impl Default for McpResponse {
fn default() -> Self {
Self {
id: Expr::String("fixture".to_owned()),
result: Expr::Map(Vec::new()),
}
}
}
impl Default for McpErrorEnvelope {
fn default() -> Self {
Self {
id: Expr::String("fixture".to_owned()),
error: McpError::default(),
}
}
}
impl Default for McpError {
fn default() -> Self {
Self {
code: -32603,
message: "fixture error".to_owned(),
data: Expr::Nil,
}
}
}
impl CitizenField for McpError {
fn encode_field(&self) -> Expr {
Expr::List(vec![
self.code.encode_field(),
self.message.encode_field(),
self.data.encode_field(),
])
}
fn decode_field_expr(expr: &Expr, field: &'static str) -> Result<Self> {
let Expr::List(items) = expr else {
return Err(sim_citizen::field_error(field, "expected MCP error list"));
};
let [code, message, data] = items.as_slice() else {
return Err(sim_citizen::field_error(
field,
format!("expected 3 MCP error field(s), found {}", items.len()),
));
};
Ok(Self {
code: i64::decode_field_expr(code, field)?,
message: String::decode_field_expr(message, field)?,
data: Expr::decode_field_expr(data, field)?,
})
}
}
pub fn mcp_request_class_symbol() -> Symbol {
Symbol::qualified("mcp", "Request")
}
pub fn mcp_notification_class_symbol() -> Symbol {
Symbol::qualified("mcp", "Notification")
}
pub fn mcp_response_class_symbol() -> Symbol {
Symbol::qualified("mcp", "Response")
}
pub fn mcp_error_envelope_class_symbol() -> Symbol {
Symbol::qualified("mcp", "ErrorEnvelope")
}
pub fn mcp_error_class_symbol() -> Symbol {
Symbol::qualified("mcp", "Error")
}
pub(crate) fn is_jsonrpc_id(expr: &Expr) -> bool {
matches!(expr, Expr::String(_) | Expr::Number(_) | Expr::Nil)
}