pub mod config;
pub mod factory;
#[cfg(feature = "stdio")]
pub mod stdio;
#[cfg(feature = "http-sse")]
pub mod http_sse;
#[cfg(feature = "http-stream")]
pub mod http_stream;
pub use config::*;
pub use factory::*;
use crate::error::{McpResult, TransportError};
use crate::messages::{JsonRpcMessage, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse};
use async_trait::async_trait;
use std::time::Duration;
use tokio::sync::mpsc;
#[async_trait]
pub trait Transport: Send + Sync {
async fn connect(&mut self) -> McpResult<()>;
async fn disconnect(&mut self) -> McpResult<()>;
fn is_connected(&self) -> bool;
async fn send_request(
&mut self,
request: JsonRpcRequest,
timeout: Option<Duration>,
) -> McpResult<JsonRpcResponse>;
async fn send_notification(&mut self, notification: JsonRpcNotification) -> McpResult<()>;
async fn receive_message(&mut self, timeout: Option<Duration>) -> McpResult<JsonRpcMessage>;
fn get_info(&self) -> TransportInfo;
fn get_config(&self) -> &TransportConfig;
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct TransportInfo {
pub transport_type: String,
pub connected: bool,
pub connected_since: Option<std::time::SystemTime>,
pub requests_sent: u64,
pub responses_received: u64,
pub notifications_sent: u64,
pub notifications_received: u64,
pub errors: u64,
pub metadata: std::collections::HashMap<String, serde_json::Value>,
}
impl TransportInfo {
pub fn new(transport_type: impl Into<String>) -> Self {
Self {
transport_type: transport_type.into(),
connected: false,
connected_since: None,
requests_sent: 0,
responses_received: 0,
notifications_sent: 0,
notifications_received: 0,
errors: 0,
metadata: std::collections::HashMap::new(),
}
}
pub fn mark_connected(&mut self) {
self.connected = true;
self.connected_since = Some(std::time::SystemTime::now());
}
pub fn mark_disconnected(&mut self) {
self.connected = false;
self.connected_since = None;
}
pub fn increment_requests_sent(&mut self) {
self.requests_sent += 1;
}
pub fn increment_responses_received(&mut self) {
self.responses_received += 1;
}
pub fn increment_notifications_sent(&mut self) {
self.notifications_sent += 1;
}
pub fn increment_notifications_received(&mut self) {
self.notifications_received += 1;
}
pub fn increment_errors(&mut self) {
self.errors += 1;
}
pub fn add_metadata(&mut self, key: impl Into<String>, value: serde_json::Value) {
self.metadata.insert(key.into(), value);
}
pub fn connection_duration(&self) -> Option<Duration> {
self.connected_since.map(|since| {
std::time::SystemTime::now()
.duration_since(since)
.unwrap_or_default()
})
}
}
pub type MessageSender = mpsc::UnboundedSender<JsonRpcMessage>;
pub type MessageReceiver = mpsc::UnboundedReceiver<JsonRpcMessage>;
pub trait TransportHelper {
fn generate_request_id() -> String {
uuid::Uuid::new_v4().to_string()
}
fn timeout_future(duration: Duration) -> tokio::time::Sleep {
tokio::time::sleep(duration)
}
fn validate_message(message: &JsonRpcMessage) -> McpResult<()> {
match message {
JsonRpcMessage::Request(req) => {
if req.jsonrpc != "2.0" {
return Err(TransportError::InvalidConfig {
transport_type: "generic".to_string(),
reason: format!("Invalid jsonrpc version: {}", req.jsonrpc),
}
.into());
}
}
JsonRpcMessage::Response(resp) => {
if resp.jsonrpc != "2.0" {
return Err(TransportError::InvalidConfig {
transport_type: "generic".to_string(),
reason: format!("Invalid jsonrpc version: {}", resp.jsonrpc),
}
.into());
}
}
JsonRpcMessage::Notification(notif) => {
if notif.jsonrpc != "2.0" {
return Err(TransportError::InvalidConfig {
transport_type: "generic".to_string(),
reason: format!("Invalid jsonrpc version: {}", notif.jsonrpc),
}
.into());
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transport_info_creation() {
let mut info = TransportInfo::new("test");
assert_eq!(info.transport_type, "test");
assert!(!info.connected);
assert_eq!(info.requests_sent, 0);
info.mark_connected();
assert!(info.connected);
assert!(info.connected_since.is_some());
info.increment_requests_sent();
assert_eq!(info.requests_sent, 1);
}
#[test]
fn test_transport_info_metadata() {
let mut info = TransportInfo::new("test");
info.add_metadata("version", serde_json::json!("1.0.0"));
assert_eq!(
info.metadata.get("version").unwrap(),
&serde_json::json!("1.0.0")
);
}
#[test]
fn test_connection_duration() {
let mut info = TransportInfo::new("test");
assert!(info.connection_duration().is_none());
info.mark_connected();
let duration = info.connection_duration();
assert!(duration.is_some());
assert!(duration.unwrap().as_millis() < 100);
info.mark_disconnected();
assert!(info.connection_duration().is_none());
}
}