use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize)]
pub struct JsonRpcRequest {
pub jsonrpc: String,
pub id: u64,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<serde_json::Value>,
}
impl JsonRpcRequest {
pub fn new(id: u64, method: impl Into<String>, params: Option<serde_json::Value>) -> Self {
Self {
jsonrpc: "2.0".into(),
id,
method: method.into(),
params,
}
}
}
#[derive(Debug, Deserialize)]
pub struct JsonRpcResponse {
#[allow(dead_code)]
pub jsonrpc: String,
#[allow(dead_code)]
pub id: Option<u64>,
pub result: Option<serde_json::Value>,
pub error: Option<JsonRpcError>,
}
#[derive(Debug, Deserialize)]
pub struct JsonRpcError {
pub code: i64,
pub message: String,
#[allow(dead_code)]
pub data: Option<serde_json::Value>,
}
#[derive(Debug, Serialize)]
pub struct JsonRpcNotification {
pub jsonrpc: String,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<serde_json::Value>,
}
impl JsonRpcNotification {
pub fn new(method: impl Into<String>) -> Self {
Self {
jsonrpc: "2.0".into(),
method: method.into(),
params: None,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct McpToolInfo {
pub name: String,
#[serde(default)]
pub description: Option<String>,
#[serde(default, rename = "inputSchema")]
pub input_schema: serde_json::Value,
}
#[derive(Debug, Deserialize)]
pub struct McpContentBlock {
#[serde(rename = "type")]
pub content_type: String,
#[serde(default)]
pub text: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct McpToolCallResult {
pub content: Vec<McpContentBlock>,
#[serde(default, rename = "isError")]
pub is_error: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_request_serialization() {
let req = JsonRpcRequest::new(1, "tools/list", None);
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("\"jsonrpc\":\"2.0\""));
assert!(json.contains("\"method\":\"tools/list\""));
assert!(!json.contains("\"params\""));
}
#[test]
fn test_request_with_params() {
let req = JsonRpcRequest::new(
2,
"tools/call",
Some(serde_json::json!({"name": "test", "arguments": {}})),
);
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("\"params\""));
}
#[test]
fn test_response_deserialization() {
let json = r#"{"jsonrpc":"2.0","id":1,"result":{"tools":[]}}"#;
let resp: JsonRpcResponse = serde_json::from_str(json).unwrap();
assert!(resp.result.is_some());
assert!(resp.error.is_none());
}
#[test]
fn test_error_response() {
let json = r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"Invalid Request"}}"#;
let resp: JsonRpcResponse = serde_json::from_str(json).unwrap();
assert!(resp.error.is_some());
assert_eq!(resp.error.unwrap().code, -32600);
}
#[test]
fn test_tool_info_deserialization() {
let json = r#"{"name":"read_file","description":"Read a file","inputSchema":{"type":"object","properties":{"path":{"type":"string"}}}}"#;
let info: McpToolInfo = serde_json::from_str(json).unwrap();
assert_eq!(info.name, "read_file");
assert_eq!(info.description.as_deref(), Some("Read a file"));
}
#[test]
fn test_notification() {
let n = JsonRpcNotification::new("notifications/initialized");
let json = serde_json::to_string(&n).unwrap();
assert!(json.contains("\"method\":\"notifications/initialized\""));
assert!(!json.contains("\"id\""));
}
}