use serde_json::{json, Value};
pub const SUPPORTED_PROTOCOL_VERSIONS: &[&str] = &["2025-06-18", "2025-03-26", "2024-11-05"];
pub const LATEST_PROTOCOL_VERSION: &str = "2025-06-18";
pub const PARSE_ERROR: i64 = -32700;
pub const INVALID_REQUEST: i64 = -32600;
pub const METHOD_NOT_FOUND: i64 = -32601;
pub const INVALID_PARAMS: i64 = -32602;
pub const INTERNAL_ERROR: i64 = -32603;
pub struct Request {
pub id: Option<Value>,
pub method: String,
pub params: Value,
}
pub enum Incoming {
Call(Request),
Ignore,
Invalid {
id: Option<Value>,
code: i64,
},
}
pub fn parse(line: &str) -> Incoming {
let value: Value = match serde_json::from_str(line) {
Ok(v) => v,
Err(_) => {
return Incoming::Invalid {
id: None,
code: PARSE_ERROR,
}
}
};
let obj = match value.as_object() {
Some(o) => o,
None => {
return Incoming::Invalid {
id: None,
code: INVALID_REQUEST,
}
}
};
if obj.contains_key("result") || obj.contains_key("error") {
return Incoming::Ignore;
}
let id = obj.get("id").filter(|v| !v.is_null()).cloned();
let method = match obj.get("method").and_then(Value::as_str) {
Some(m) => m.to_string(),
None => {
return Incoming::Invalid {
id,
code: INVALID_REQUEST,
}
}
};
let params = obj.get("params").cloned().unwrap_or(Value::Null);
Incoming::Call(Request { id, method, params })
}
pub fn success(id: Value, result: Value) -> Value {
json!({"jsonrpc": "2.0", "id": id, "result": result})
}
pub fn error(id: Option<Value>, code: i64, message: impl Into<String>) -> Value {
json!({
"jsonrpc": "2.0",
"id": id.unwrap_or(Value::Null),
"error": {"code": code, "message": message.into()},
})
}
pub fn tool_success(payload: Value) -> Value {
let text = serde_json::to_string(&payload).unwrap_or_else(|_| "{}".to_string());
json!({
"content": [{"type": "text", "text": text}],
"structuredContent": payload,
"isError": false,
})
}
pub fn tool_error(message: impl Into<String>) -> Value {
json!({
"content": [{"type": "text", "text": message.into()}],
"isError": true,
})
}
pub fn negotiate_version(requested: Option<&str>) -> &'static str {
match requested {
Some(v) => SUPPORTED_PROTOCOL_VERSIONS
.iter()
.find(|s| **s == v)
.copied()
.unwrap_or(LATEST_PROTOCOL_VERSION),
None => LATEST_PROTOCOL_VERSION,
}
}