message.rs•9.44 kB
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
pub type RequestId = Value;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "jsonrpc")]
pub enum JsonRpcMessage {
#[serde(rename = "2.0")]
V2(JsonRpcV2Message),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum JsonRpcV2Message {
Request(JsonRpcRequest),
Response(JsonRpcResponse),
Notification(JsonRpcNotification),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcRequest {
pub id: RequestId,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcResponse {
pub id: RequestId,
#[serde(flatten)]
pub result: JsonRpcResult,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum JsonRpcResult {
Success { result: Value },
Error { error: JsonRpcError },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcError {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcNotification {
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpInitializeParams {
#[serde(rename = "protocolVersion")]
pub protocol_version: String,
pub capabilities: McpCapabilities,
#[serde(rename = "clientInfo")]
pub client_info: McpClientInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub experimental: Option<HashMap<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sampling: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpClientInfo {
pub name: String,
pub version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpInitializeResult {
#[serde(rename = "protocolVersion")]
pub protocol_version: String,
pub capabilities: McpServerCapabilities,
#[serde(rename = "serverInfo")]
pub server_info: McpServerInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpServerCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub logging: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompts: Option<McpPromptsCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resources: Option<McpResourcesCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<McpToolsCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub experimental: Option<HashMap<String, Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpPromptsCapability {
#[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpResourcesCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub subscribe: Option<bool>,
#[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpToolsCapability {
#[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpServerInfo {
pub name: String,
pub version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpPingRequest;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpPingResult;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpCancelledNotification {
#[serde(rename = "requestId")]
pub request_id: RequestId,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpProgressNotification {
#[serde(rename = "progressToken")]
pub progress_token: Value,
pub progress: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<f64>,
}
impl JsonRpcRequest {
pub fn new(id: RequestId, method: impl Into<String>, params: Option<Value>) -> Self {
Self {
id,
method: method.into(),
params,
}
}
}
impl JsonRpcResponse {
pub fn success(id: RequestId, result: Value) -> Self {
Self {
id,
result: JsonRpcResult::Success { result },
}
}
pub fn error(id: RequestId, error: JsonRpcError) -> Self {
Self {
id,
result: JsonRpcResult::Error { error },
}
}
}
impl JsonRpcNotification {
pub fn new(method: impl Into<String>, params: Option<Value>) -> Self {
Self {
method: method.into(),
params,
}
}
}
impl JsonRpcError {
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;
pub fn parse_error() -> Self {
Self {
code: Self::PARSE_ERROR,
message: "Parse error".to_string(),
data: None,
}
}
pub fn invalid_request() -> Self {
Self {
code: Self::INVALID_REQUEST,
message: "Invalid Request".to_string(),
data: None,
}
}
pub fn method_not_found() -> Self {
Self {
code: Self::METHOD_NOT_FOUND,
message: "Method not found".to_string(),
data: None,
}
}
pub fn invalid_params() -> Self {
Self {
code: Self::INVALID_PARAMS,
message: "Invalid params".to_string(),
data: None,
}
}
pub fn internal_error() -> Self {
Self {
code: Self::INTERNAL_ERROR,
message: "Internal error".to_string(),
data: None,
}
}
pub fn custom(code: i32, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
data: None,
}
}
pub fn with_data(mut self, data: Value) -> Self {
self.data = Some(data);
self
}
}
pub fn validate_message(message: &JsonRpcMessage) -> crate::Result<()> {
match message {
JsonRpcMessage::V2(msg) => match msg {
JsonRpcV2Message::Request(req) => {
// jsonrpc version is handled by enum tag validation
if req.method.is_empty() {
return Err(crate::McpError::InvalidMessage(
"Method cannot be empty".to_string(),
));
}
}
JsonRpcV2Message::Response(_res) => {
// jsonrpc version is handled by enum tag validation
// Response validation is handled by the result enum
}
JsonRpcV2Message::Notification(notif) => {
// jsonrpc version is handled by enum tag validation
if notif.method.is_empty() {
return Err(crate::McpError::InvalidMessage(
"Method cannot be empty".to_string(),
));
}
}
},
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_serialize_request() {
let req = JsonRpcRequest::new(json!(1), "initialize", Some(json!({"test": true})));
let serialized = serde_json::to_string(&req).unwrap();
assert!(serialized.contains("\"jsonrpc\":\"2.0\""));
assert!(serialized.contains("\"id\":1"));
assert!(serialized.contains("\"method\":\"initialize\""));
}
#[test]
fn test_serialize_response() {
let res = JsonRpcResponse::success(json!(1), json!({"result": "success"}));
let serialized = serde_json::to_string(&res).unwrap();
assert!(serialized.contains("\"jsonrpc\":\"2.0\""));
assert!(serialized.contains("\"id\":1"));
assert!(serialized.contains("\"result\""));
}
#[test]
fn test_serialize_notification() {
let notif = JsonRpcNotification::new("cancelled", Some(json!({"requestId": 1})));
let serialized = serde_json::to_string(¬if).unwrap();
assert!(serialized.contains("\"jsonrpc\":\"2.0\""));
assert!(serialized.contains("\"method\":\"cancelled\""));
assert!(!serialized.contains("\"id\""));
}
#[test]
fn test_validate_message() {
let valid_req = JsonRpcRequest::new(json!(1), "test", None);
let msg = JsonRpcMessage::V2(JsonRpcV2Message::Request(valid_req));
assert!(validate_message(&msg).is_ok());
let invalid_req = JsonRpcRequest {
jsonrpc: "1.0".to_string(),
id: json!(1),
method: "test".to_string(),
params: None,
};
let msg = JsonRpcMessage::V2(JsonRpcV2Message::Request(invalid_req));
assert!(validate_message(&msg).is_err());
}
}