use serde::{Deserialize, Serialize};
use serde_json::Value;
pub const JSONRPC_VERSION: &str = "2.0";
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct JsonRpcRequest {
pub jsonrpc: String,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
pub id: RequestId,
}
impl JsonRpcRequest {
#[must_use]
pub fn new(method: impl Into<String>, params: Option<Value>, id: u64) -> Self {
Self {
jsonrpc: JSONRPC_VERSION.to_string(),
method: method.into(),
params,
id: RequestId::Number(id),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(untagged)]
pub enum RequestId {
Number(u64),
String(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct JsonRpcResponse {
pub jsonrpc: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<JsonRpcError>,
pub id: RequestId,
}
impl JsonRpcResponse {
#[must_use]
pub const fn is_error(&self) -> bool {
self.error.is_some()
}
#[must_use]
pub const fn result(&self) -> Option<&Value> {
self.result.as_ref()
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct JsonRpcError {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
pub mod error_codes {
pub const PARSE_ERROR: i32 = -32700;
pub const INVALID_REQUEST: i32 = -32600;
pub const METHOD_NOT_FOUND: i32 = -32601;
pub const INVALID_PARAMS: i32 = -32602;
pub const INTERNAL_ERROR: i32 = -32603;
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct McpToolDefinition {
pub name: String,
#[serde(default)]
pub description: Option<String>,
#[serde(rename = "inputSchema")]
pub input_schema: Value,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct McpToolCallResult {
pub content: Vec<McpContent>,
#[serde(default, rename = "isError")]
pub is_error: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum McpContent {
#[serde(rename = "text")]
Text {
text: String,
},
#[serde(rename = "image")]
Image {
data: String,
#[serde(rename = "mimeType")]
mime_type: String,
},
#[serde(rename = "resource")]
Resource {
uri: String,
#[serde(rename = "mimeType")]
mime_type: Option<String>,
text: Option<String>,
},
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct McpServerCapabilities {
#[serde(default)]
pub tools: Option<McpToolsCapability>,
#[serde(default)]
pub resources: Option<McpResourcesCapability>,
#[serde(default)]
pub prompts: Option<McpPromptsCapability>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct McpToolsCapability {
#[serde(default, rename = "listChanged")]
pub list_changed: bool,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct McpResourcesCapability {
#[serde(default)]
pub subscribe: bool,
#[serde(default, rename = "listChanged")]
pub list_changed: bool,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct McpPromptsCapability {
#[serde(default, rename = "listChanged")]
pub list_changed: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct InitializeParams {
#[serde(rename = "protocolVersion")]
pub protocol_version: String,
pub capabilities: ClientCapabilities,
#[serde(rename = "clientInfo")]
pub client_info: ClientInfo,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ClientCapabilities {
#[serde(default)]
pub roots: Option<RootsCapability>,
#[serde(default)]
pub sampling: Option<SamplingCapability>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct RootsCapability {
#[serde(default, rename = "listChanged")]
pub list_changed: bool,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct SamplingCapability {}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ClientInfo {
pub name: String,
pub version: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct InitializeResult {
#[serde(rename = "protocolVersion")]
pub protocol_version: String,
pub capabilities: McpServerCapabilities,
#[serde(rename = "serverInfo")]
pub server_info: ServerInfo,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ServerInfo {
pub name: String,
#[serde(default)]
pub version: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ToolsListResult {
pub tools: Vec<McpToolDefinition>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ToolCallParams {
pub name: String,
#[serde(default)]
pub arguments: Option<Value>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_json_rpc_request_serialization() {
let request =
JsonRpcRequest::new("test_method", Some(serde_json::json!({"key": "value"})), 1);
let json = serde_json::to_string(&request).expect("serialize");
assert!(json.contains("test_method"));
assert!(json.contains("2.0"));
}
#[test]
fn test_json_rpc_response_success() {
let response = JsonRpcResponse {
jsonrpc: JSONRPC_VERSION.to_string(),
result: Some(serde_json::json!({"success": true})),
error: None,
id: RequestId::Number(1),
};
assert!(!response.is_error());
assert!(response.result().is_some());
}
#[test]
fn test_json_rpc_response_error() {
let response = JsonRpcResponse {
jsonrpc: JSONRPC_VERSION.to_string(),
result: None,
error: Some(JsonRpcError {
code: error_codes::METHOD_NOT_FOUND,
message: "Method not found".to_string(),
data: None,
}),
id: RequestId::Number(1),
};
assert!(response.is_error());
assert!(response.result().is_none());
}
#[test]
fn test_mcp_tool_definition_deserialization() {
let json = r#"{
"name": "test_tool",
"description": "A test tool",
"inputSchema": {
"type": "object",
"properties": {}
}
}"#;
let tool: McpToolDefinition = serde_json::from_str(json).expect("deserialize");
assert_eq!(tool.name, "test_tool");
assert_eq!(tool.description.as_deref(), Some("A test tool"));
}
#[test]
fn test_mcp_content_text() {
let content = McpContent::Text {
text: "Hello".to_string(),
};
let json = serde_json::to_string(&content).expect("serialize");
assert!(json.contains("text"));
assert!(json.contains("Hello"));
}
#[test]
fn test_request_id_variants() {
let num_id = RequestId::Number(42);
let str_id = RequestId::String("req-1".to_string());
let json_num = serde_json::to_string(&num_id).expect("serialize");
let json_str = serde_json::to_string(&str_id).expect("serialize");
assert_eq!(json_num, "42");
assert_eq!(json_str, "\"req-1\"");
}
}