use std::fmt;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, thiserror::Error)]
pub enum McpError {
#[error("mcp transport io: {0}")]
Io(#[from] std::io::Error),
#[error("mcp transport json: {0}")]
Json(#[from] serde_json::Error),
#[error("mcp transport closed")]
Closed,
#[error("mcp rpc error {code}: {message}")]
Rpc {
code: i64,
message: String,
},
#[error("mcp transport timeout")]
Timeout,
#[error("mcp transport: {0}")]
Other(String),
}
impl From<McpError> for crate::Error {
fn from(err: McpError) -> crate::Error {
match err {
McpError::Rpc { code, message } => crate::Error::Api {
code: code.unsigned_abs().min(u16::MAX as u64) as u16,
message,
},
McpError::Json(e) => crate::Error::Json(e),
other => crate::Error::Validation(other.to_string()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct JsonRpcMessage {
pub jsonrpc: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub method: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub result: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<JsonRpcError>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcError {
pub code: i64,
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
impl fmt::Display for JsonRpcError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "rpc {}: {}", self.code, self.message)
}
}
impl JsonRpcMessage {
pub fn request(id: impl Into<Value>, method: impl Into<String>, params: Value) -> Self {
Self {
jsonrpc: "2.0".into(),
id: Some(id.into()),
method: Some(method.into()),
params: Some(params),
..Default::default()
}
}
pub fn notification(method: impl Into<String>, params: Value) -> Self {
Self {
jsonrpc: "2.0".into(),
method: Some(method.into()),
params: Some(params),
..Default::default()
}
}
pub fn is_response(&self) -> bool {
self.id.is_some() && self.method.is_none()
}
}
#[async_trait]
pub trait Transport: Send + Sync {
async fn send(&mut self, msg: JsonRpcMessage) -> Result<(), McpError>;
async fn recv(&mut self) -> Result<JsonRpcMessage, McpError>;
async fn close(&mut self) -> Result<(), McpError>;
}