use crate::{error::TransportResult, message_dispatcher::MessageDispatcher};
use crate::{schema::RequestId, SessionId};
use async_trait::async_trait;
use std::{pin::Pin, sync::Arc, time::Duration};
use tokio::{
sync::oneshot::{self, Sender},
task::JoinHandle,
};
const DEFAULT_TIMEOUT_MSEC: u64 = 60_000;
pub enum IoStream {
Readable(Pin<Box<dyn tokio::io::AsyncRead + Send + Sync>>),
Writable(Pin<Box<dyn tokio::io::AsyncWrite + Send + Sync>>),
}
pub const DEFAULT_MAX_LINE_LENGTH: usize = 16 * 1024 * 1024;
#[derive(Debug, Clone)]
pub struct TransportOptions {
pub timeout: Duration,
pub max_line_length: usize,
pub channel_capacity: usize,
}
impl Default for TransportOptions {
fn default() -> Self {
Self {
timeout: Duration::from_millis(DEFAULT_TIMEOUT_MSEC),
max_line_length: DEFAULT_MAX_LINE_LENGTH,
channel_capacity: crate::mcp_stream::DEFAULT_MESSAGE_CHANNEL_CAPACITY,
}
}
}
#[async_trait]
pub trait McpDispatch<R, S, M, OM>: Send + Sync + 'static
where
R: Clone + Send + Sync + serde::de::DeserializeOwned + 'static,
S: Clone + Send + Sync + serde::Serialize + 'static,
M: Clone + Send + Sync + serde::de::DeserializeOwned + 'static,
OM: Clone + Send + Sync + serde::de::DeserializeOwned + 'static,
{
async fn send_message(
&self,
message: S,
request_timeout: Option<Duration>,
) -> TransportResult<Option<R>>;
async fn send(&self, message: OM, timeout: Option<Duration>) -> TransportResult<Option<M>>;
async fn write_str(&self, payload: &str, skip_store: bool) -> TransportResult<()>;
}
#[async_trait]
pub trait Transport<R, S, M, OR, OM>: Send + Sync + 'static
where
R: Clone + Send + Sync + serde::de::DeserializeOwned + 'static,
S: Clone + Send + Sync + serde::Serialize + 'static,
M: Clone + Send + Sync + serde::de::DeserializeOwned + 'static,
OR: Clone + Send + Sync + serde::Serialize + 'static,
OM: Clone + Send + Sync + serde::de::DeserializeOwned + 'static,
{
async fn start(&self) -> TransportResult<tokio_stream::wrappers::ReceiverStream<R>>
where
MessageDispatcher<M>: McpDispatch<R, OR, M, OM>;
fn message_sender(&self) -> Arc<tokio::sync::RwLock<Option<MessageDispatcher<M>>>>;
fn error_stream(&self) -> &tokio::sync::RwLock<Option<IoStream>>;
async fn shut_down(&self) -> TransportResult<()>;
async fn is_shut_down(&self) -> bool;
async fn consume_string_payload(&self, payload: &str) -> TransportResult<()>;
async fn pending_request_tx(&self, request_id: &RequestId) -> Option<Sender<M>>;
async fn keep_alive(
&self,
interval: Duration,
disconnect_tx: oneshot::Sender<()>,
) -> TransportResult<JoinHandle<()>>;
async fn session_id(&self) -> Option<SessionId> {
None
}
}
pub trait TransportDispatcher<R, S, M, OR, OM>:
Transport<R, S, M, OR, OM> + McpDispatch<R, OR, M, OM>
where
R: Clone + Send + Sync + serde::de::DeserializeOwned + 'static,
S: Clone + Send + Sync + serde::Serialize + 'static,
M: Clone + Send + Sync + serde::de::DeserializeOwned + 'static,
OR: Clone + Send + Sync + serde::Serialize + 'static,
OM: Clone + Send + Sync + serde::de::DeserializeOwned + 'static,
{
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_channel_capacity_matches_constant() {
assert_eq!(
TransportOptions::default().channel_capacity,
crate::mcp_stream::DEFAULT_MESSAGE_CHANNEL_CAPACITY
);
}
#[test]
fn channel_capacity_is_overridable() {
let options = TransportOptions {
channel_capacity: 256,
..Default::default()
};
assert_eq!(options.channel_capacity, 256);
}
}