use std::collections::HashSet;
use std::future::Future;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use rmcp::model::{JsonRpcMessage, RequestId};
use rmcp::service::{RxJsonRpcMessage, TxJsonRpcMessage};
use rmcp::transport::async_rw::AsyncRwTransport;
use rmcp::transport::Transport;
use rmcp::RoleServer;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::sync::Notify;
use tracing::{info, warn};
pub const DRAIN_LIMIT: Duration = Duration::from_secs(300);
#[derive(Default)]
struct Outstanding {
ids: Mutex<HashSet<RequestId>>,
changed: Notify,
}
impl Outstanding {
fn insert(&self, id: RequestId) {
self.ids.lock().expect("outstanding ids").insert(id);
}
fn answer(&self, id: &RequestId) {
self.ids.lock().expect("outstanding ids").remove(id);
self.changed.notify_waiters();
}
fn count(&self) -> usize {
self.ids.lock().expect("outstanding ids").len()
}
async fn drained(&self) {
loop {
let notified = self.changed.notified();
tokio::pin!(notified);
notified.as_mut().enable();
if self.count() == 0 {
return;
}
notified.await;
}
}
}
pub struct DrainOnEof<T> {
inner: T,
outstanding: Arc<Outstanding>,
drain_deadline: Option<tokio::time::Instant>,
}
impl<T> DrainOnEof<T> {
pub fn new(inner: T) -> Self {
Self {
inner,
outstanding: Arc::new(Outstanding::default()),
drain_deadline: None,
}
}
}
pub fn drain_on_eof_transport<R, W>(
read: R,
write: W,
) -> DrainOnEof<AsyncRwTransport<RoleServer, R, W>>
where
R: Send + AsyncRead + Unpin,
W: Send + AsyncWrite + Unpin + 'static,
{
DrainOnEof::new(AsyncRwTransport::new_server(read, write))
}
impl<T> Transport<RoleServer> for DrainOnEof<T>
where
T: Transport<RoleServer>,
{
type Error = T::Error;
fn send(
&mut self,
item: TxJsonRpcMessage<RoleServer>,
) -> impl Future<Output = Result<(), Self::Error>> + Send + 'static {
let answered = match &item {
JsonRpcMessage::Response(response) => Some(response.id.clone()),
JsonRpcMessage::Error(error) => Some(error.id.clone()),
_ => None,
};
let sending = self.inner.send(item);
let outstanding = Arc::clone(&self.outstanding);
async move {
let result = sending.await;
if let Some(id) = answered {
outstanding.answer(&id);
}
result
}
}
fn receive(&mut self) -> impl Future<Output = Option<RxJsonRpcMessage<RoleServer>>> + Send {
async move {
let deadline = match self.drain_deadline {
Some(deadline) => deadline,
None => {
if let Some(message) = self.inner.receive().await {
if let JsonRpcMessage::Request(request) = &message {
self.outstanding.insert(request.id.clone());
}
return Some(message);
}
let pending = self.outstanding.count();
if pending > 0 {
info!(
"MCP input ended with {pending} request(s) still being answered; \
finishing them before shutting down"
);
}
let deadline = tokio::time::Instant::now() + DRAIN_LIMIT;
self.drain_deadline = Some(deadline);
deadline
}
};
if tokio::time::timeout_at(deadline, self.outstanding.drained())
.await
.is_err()
{
warn!(
"MCP input ended and {} request(s) were still unanswered after {}s; \
shutting down without them",
self.outstanding.count(),
DRAIN_LIMIT.as_secs()
);
}
None
}
}
fn close(&mut self) -> impl Future<Output = Result<(), Self::Error>> + Send {
self.inner.close()
}
}