use serde::{Deserialize, Serialize};
use serde_json::Value;
#[cfg(feature = "native")]
pub use rmcp::model::{
CallToolRequestParams, CallToolResult, Content, Prompt as RmcpPrompt, ProtocolVersion,
Resource as RmcpResource, Tool as RmcpTool,
};
#[cfg(feature = "native")]
pub use rmcp::model::{
ClientCapabilities as RmcpClientCapabilities, PromptsCapability, ResourcesCapability,
ServerCapabilities as RmcpServerCapabilities, ToolsCapability,
};
#[cfg(feature = "native")]
pub type McpTool = RmcpTool;
#[cfg(feature = "native")]
pub type McpResource = RmcpResource;
#[cfg(feature = "native")]
pub type McpPrompt = RmcpPrompt;
#[cfg(feature = "native")]
pub type CallToolParams = CallToolRequestParams;
#[cfg(feature = "native")]
pub type ServerCapabilities = RmcpServerCapabilities;
#[cfg(feature = "native")]
pub type ClientCapabilities = RmcpClientCapabilities;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcRequest {
pub jsonrpc: String,
pub id: serde_json::Value,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
}
impl JsonRpcRequest {
pub fn new<T: Serialize>(
id: impl Into<Value>,
method: String,
params: Option<T>,
) -> Result<Self, serde_json::Error> {
let params_value = match params {
Some(p) => Some(serde_json::to_value(p)?),
None => None,
};
Ok(Self {
jsonrpc: "2.0".to_string(),
id: id.into(),
method,
params: params_value,
})
}
pub fn new_unchecked<T: Serialize>(
id: impl Into<Value>,
method: String,
params: Option<T>,
) -> Self {
Self::new(id, method, params).expect("Failed to serialize JSON-RPC request params")
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcResponse {
pub jsonrpc: String,
pub id: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<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 jsonrpc: String,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
}
impl JsonRpcNotification {
pub fn new<T: Serialize>(
method: impl Into<String>,
params: Option<T>,
) -> Result<Self, serde_json::Error> {
let params_value = match params {
Some(p) => Some(serde_json::to_value(p)?),
None => None,
};
Ok(Self {
jsonrpc: "2.0".to_string(),
method: method.into(),
params: params_value,
})
}
pub fn new_unchecked<T: Serialize>(method: impl Into<String>, params: Option<T>) -> Self {
Self::new(method, params).expect("Failed to serialize JSON-RPC notification params")
}
}
#[derive(Debug, Clone)]
pub enum JsonRpcMessage {
Response(JsonRpcResponse),
Notification(JsonRpcNotification),
}
impl JsonRpcMessage {
pub fn is_response(&self) -> bool {
matches!(self, JsonRpcMessage::Response(_))
}
pub fn is_notification(&self) -> bool {
matches!(self, JsonRpcMessage::Notification(_))
}
pub fn as_response(self) -> Option<JsonRpcResponse> {
match self {
JsonRpcMessage::Response(r) => Some(r),
_ => None,
}
}
pub fn as_notification(self) -> Option<JsonRpcNotification> {
match self {
JsonRpcMessage::Notification(n) => Some(n),
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProgressParams {
#[serde(rename = "progressToken")]
pub progress_token: String,
pub progress: f64,
pub total: Option<f64>,
pub message: Option<String>,
}
#[derive(Debug, Clone)]
pub enum McpNotification {
Progress(ProgressParams),
Unknown {
method: String,
params: Option<Value>,
},
}
impl McpNotification {
pub fn from_notification(notif: &JsonRpcNotification) -> Self {
match notif.method.as_str() {
"notifications/progress" => {
if let Some(ref params) = notif.params
&& let Ok(progress) = serde_json::from_value::<ProgressParams>(params.clone())
{
return McpNotification::Progress(progress);
}
McpNotification::Unknown {
method: notif.method.clone(),
params: notif.params.clone(),
}
}
_ => McpNotification::Unknown {
method: notif.method.clone(),
params: notif.params.clone(),
},
}
}
}
#[cfg(feature = "native")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitializeParams {
#[serde(rename = "protocolVersion")]
pub protocol_version: String,
pub capabilities: ClientCapabilities,
#[serde(rename = "clientInfo")]
pub client_info: ClientInfo,
}
#[cfg(feature = "native")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientInfo {
pub name: String,
pub version: String,
}
#[cfg(feature = "native")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitializeResult {
#[serde(rename = "protocolVersion")]
pub protocol_version: String,
pub capabilities: ServerCapabilities,
#[serde(rename = "serverInfo")]
pub server_info: ServerInfo,
}
#[cfg(feature = "native")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerInfo {
pub name: String,
pub version: String,
}
#[cfg(feature = "native")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListToolsResult {
pub tools: Vec<McpTool>,
}
#[cfg(feature = "native")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListResourcesResult {
pub resources: Vec<McpResource>,
}
#[cfg(feature = "native")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListPromptsResult {
pub prompts: Vec<McpPrompt>,
}
#[cfg(feature = "native")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReadResourceParams {
pub uri: String,
}
#[cfg(feature = "native")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReadResourceResult {
pub contents: Vec<ResourceContent>,
}
#[cfg(feature = "native")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum ResourceContent {
Text {
uri: String,
mime_type: Option<String>,
text: String,
},
Blob {
uri: String,
mime_type: Option<String>,
blob: String,
},
}
#[cfg(feature = "native")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetPromptParams {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<Value>,
}
#[cfg(feature = "native")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetPromptResult {
pub description: String,
pub messages: Vec<PromptMessage>,
}
#[cfg(feature = "native")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptMessage {
pub role: String,
pub content: PromptContent,
}
#[cfg(feature = "native")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
#[allow(clippy::large_enum_variant)]
pub enum PromptContent {
Text {
text: String,
},
Image {
data: String,
mime_type: String,
},
Resource {
resource: McpResource,
},
}
#[cfg(feature = "native")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptArgument {
pub name: String,
pub description: String,
pub required: bool,
}
#[cfg(feature = "native")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
#[allow(clippy::large_enum_variant)]
pub enum ToolResultContent {
Text {
text: String,
},
Image {
data: String,
mime_type: String,
},
Resource {
resource: McpResource,
},
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_json_rpc_request_new() {
let request =
JsonRpcRequest::new(1, "test_method".to_string(), Some(json!({"key": "value"})))
.unwrap();
assert_eq!(request.jsonrpc, "2.0");
assert_eq!(request.id, json!(1));
assert_eq!(request.method, "test_method");
assert!(request.params.is_some());
}
#[test]
fn test_json_rpc_request_serialization() {
let request = JsonRpcRequest::new(1, "test".to_string(), None::<()>).unwrap();
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("jsonrpc"));
assert!(json.contains("2.0"));
assert!(json.contains("test"));
}
#[test]
fn test_json_rpc_response_success() {
let response = JsonRpcResponse {
jsonrpc: "2.0".to_string(),
id: json!(1),
result: Some(json!({"status": "ok"})),
error: None,
};
assert!(response.result.is_some());
assert!(response.error.is_none());
}
#[test]
fn test_json_rpc_response_error() {
let response = JsonRpcResponse {
jsonrpc: "2.0".to_string(),
id: json!(1),
result: None,
error: Some(JsonRpcError {
code: -32600,
message: "Invalid Request".to_string(),
data: None,
}),
};
assert!(response.result.is_none());
assert!(response.error.is_some());
}
#[cfg(feature = "native")]
#[test]
fn test_type_aliases_work() {
let _tool: McpTool;
let _resource: McpResource;
let _prompt: McpPrompt;
}
}