use serde_json::{Value, json};
pub(super) const MCP_PROTOCOL_VERSION: &str = "2025-11-25";
pub(super) const MCP_SUPPORTED_PROTOCOL_VERSIONS: &[&str] = &[MCP_PROTOCOL_VERSION, "2025-06-18"];
pub(super) struct FrameAction {
pub(super) response: Option<Value>,
}
pub(super) fn evaluate_frame(frame: &str) -> FrameAction {
match serde_json::from_str::<Value>(frame) {
Ok(message) => FrameAction {
response: response_for(&message),
},
Err(_) => FrameAction {
response: Some(jsonrpc_error(Value::Null, -32700, "Parse error")),
},
}
}
pub(super) fn response_for(message: &Value) -> Option<Value> {
let raw_id = message.get("id");
let response_id = raw_id
.filter(|id| valid_request_id(id))
.cloned()
.unwrap_or(Value::Null);
if !message.is_object() || message.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
return Some(jsonrpc_error(response_id, -32600, "Invalid Request"));
}
let method = message.get("method").and_then(Value::as_str);
if raw_id.is_some_and(|id| !valid_request_id(id)) {
return Some(jsonrpc_error(Value::Null, -32600, "Invalid Request"));
}
if method.is_none() {
return Some(jsonrpc_error(response_id, -32600, "Invalid Request"));
}
let id = raw_id?.clone();
match method {
Some("initialize") => {
let Some(requested_protocol) = message
.pointer("/params/protocolVersion")
.and_then(Value::as_str)
else {
return Some(jsonrpc_error(id, -32602, "Missing protocolVersion"));
};
let protocol_version = if MCP_SUPPORTED_PROTOCOL_VERSIONS.contains(&requested_protocol)
{
requested_protocol
} else {
MCP_PROTOCOL_VERSION
};
Some(json!({
"jsonrpc": "2.0",
"id": id,
"result": {
"protocolVersion": protocol_version,
"capabilities": {},
"serverInfo": {
"name": "nemo-relay",
"version": env!("CARGO_PKG_VERSION")
}
}
}))
}
Some("tools/list") => Some(json!({
"jsonrpc": "2.0",
"id": id,
"result": { "tools": [] }
})),
Some("ping") => Some(json!({
"jsonrpc": "2.0",
"id": id,
"result": {}
})),
Some(_) => Some(jsonrpc_error(id, -32601, "Method not found")),
None => Some(jsonrpc_error(id, -32600, "Invalid Request")),
}
}
fn valid_request_id(id: &Value) -> bool {
id.is_string() || id.as_i64().is_some() || id.as_u64().is_some()
}
pub(super) fn jsonrpc_error(id: Value, code: i64, message: &str) -> Value {
json!({
"jsonrpc": "2.0",
"id": id,
"error": {
"code": code,
"message": message
}
})
}