use serde::{Deserialize, Serialize, de, ser::SerializeStruct};
use serde_json::{Value, json};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RpcId {
Number(i64),
String(String),
Null,
}
impl Serialize for RpcId {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
match self {
RpcId::Number(n) => s.serialize_i64(*n),
RpcId::String(t) => s.serialize_str(t),
RpcId::Null => s.serialize_none(),
}
}
}
impl<'de> Deserialize<'de> for RpcId {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let v = Value::deserialize(d)?;
match v {
Value::Number(n) => n
.as_i64()
.map(RpcId::Number)
.ok_or_else(|| de::Error::custom("id is not a bounded integer")),
Value::String(s) => Ok(RpcId::String(s)),
Value::Null => Ok(RpcId::Null),
_ => Err(de::Error::custom("id must be number, string, or null")),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct Request {
pub jsonrpc: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<RpcId>,
pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Response {
pub jsonrpc: String,
pub id: Option<RpcId>,
pub result: Option<Value>,
pub error: Option<RpcError>,
}
impl Serialize for Response {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
let mut field_count = 2;
if self.result.is_some() {
field_count += 1;
}
if self.error.is_some() {
field_count += 1;
}
let mut st = s.serialize_struct("Response", field_count)?;
st.serialize_field("jsonrpc", &self.jsonrpc)?;
match &self.id {
Some(rid) => st.serialize_field("id", rid)?,
None => st.serialize_field("id", &Value::Null)?,
}
if let Some(r) = &self.result {
st.serialize_field("result", r)?;
}
if let Some(e) = &self.error {
st.serialize_field("error", e)?;
}
st.end()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RpcError {
pub code: i64,
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
#[must_use]
pub fn success_response(id: Option<RpcId>, value: Value) -> Response {
Response {
jsonrpc: "2.0".into(),
id,
result: Some(value),
error: None,
}
}
#[must_use]
pub fn error_response(id: Option<RpcId>, err: RpcError) -> Response {
Response {
jsonrpc: "2.0".into(),
id,
result: None,
error: Some(err),
}
}
#[must_use]
pub fn parse_error(msg: &str) -> RpcError {
RpcError {
code: -32700,
message: format!("Parse error: {msg}"),
data: None,
}
}
#[must_use]
pub fn invalid_request(msg: &str) -> RpcError {
RpcError {
code: -32600,
message: format!("Invalid Request: {msg}"),
data: None,
}
}
#[must_use]
pub fn method_not_found(name: &str) -> RpcError {
RpcError {
code: -32601,
message: format!("Method not found: {name}"),
data: None,
}
}
#[must_use]
pub fn invalid_params(msg: &str) -> RpcError {
RpcError {
code: -32602,
message: format!("Invalid params: {msg}"),
data: None,
}
}
#[must_use]
pub fn internal_error(msg: &str) -> RpcError {
RpcError {
code: -32603,
message: format!("Internal error: {msg}"),
data: None,
}
}
pub fn encode_message<T: Serialize>(v: &T) -> Result<String, serde_json::Error> {
serde_json::to_string(v)
}
pub fn decode_request(line: &str) -> Result<Request, serde_json::Error> {
serde_json::from_str::<Request>(line)
}
#[must_use]
pub fn to_value<T: Serialize>(t: &T) -> Value {
serde_json::to_value(t).unwrap_or(json!(null))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mcp_protocol_rpc_id_roundtrips() {
for id in [RpcId::Number(42), RpcId::String("abc".into()), RpcId::Null] {
let s = serde_json::to_string(&id).unwrap();
let parsed: RpcId = serde_json::from_str(&s).unwrap();
assert_eq!(parsed, id);
}
}
#[test]
fn mcp_protocol_request_decodes_with_and_without_id() {
let r: Request = serde_json::from_str(r#"{"jsonrpc":"2.0","id":1,"method":"x"}"#).unwrap();
assert_eq!(r.id, Some(RpcId::Number(1)));
let n: Request = serde_json::from_str(r#"{"jsonrpc":"2.0","method":"y"}"#).unwrap();
assert!(n.id.is_none());
}
#[test]
fn mcp_protocol_error_codes_match_spec() {
assert_eq!(parse_error("x").code, -32700);
assert_eq!(invalid_request("x").code, -32600);
assert_eq!(method_not_found("x").code, -32601);
assert_eq!(invalid_params("x").code, -32602);
assert_eq!(internal_error("x").code, -32603);
}
#[test]
fn mcp_protocol_response_emits_id_and_result_only_on_success() {
let r = success_response(Some(RpcId::Number(7)), json!({"ok": true}));
let s = serde_json::to_string(&r).unwrap();
assert!(s.contains(r#""jsonrpc":"2.0""#));
assert!(s.contains(r#""id":7"#));
assert!(s.contains(r#""result":{"ok":true}"#));
assert!(!s.contains(r#""error""#));
}
#[test]
fn mcp_protocol_response_emits_id_and_error_only_on_failure() {
let r = error_response(Some(RpcId::String("a".into())), invalid_params("nope"));
let s = serde_json::to_string(&r).unwrap();
assert!(s.contains(r#""error""#));
assert!(s.contains(r#""code":-32602"#));
assert!(!s.contains(r#""result""#));
}
#[test]
fn mcp_protocol_null_id_serializes_as_json_null() {
let r = error_response(Some(RpcId::Null), parse_error("bad"));
let s = serde_json::to_string(&r).unwrap();
assert!(s.contains(r#""id":null"#));
}
}