use rmcp::ServiceExt as _;
use super::shutdown::ShutdownSignal;
pub async fn run_stdio(
mcp_server: crate::mcp::McplsServer,
mut shutdown_signal: ShutdownSignal,
) -> Result<(), crate::Error> {
serve_until(mcp_server, rmcp::transport::stdio(), async move {
shutdown_signal.recv().await;
})
.await
}
pub async fn serve_until<T, E, A>(
mcp_server: crate::mcp::McplsServer,
transport: T,
signal: impl Future<Output = ()>,
) -> Result<(), crate::Error>
where
T: rmcp::transport::IntoTransport<rmcp::RoleServer, E, A>,
E: std::error::Error + Send + Sync + 'static,
{
let stop = mcp_server.stop_token();
let signal = async {
signal.await;
stop.cancel();
};
let mut signal = std::pin::pin!(signal);
let service = tokio::select! {
result = mcp_server.serve(transport) => {
result.map_err(|e| crate::Error::McpServerStart(Box::new(e)))?
}
() = &mut signal => {
tracing::info!("shutdown signal received during handshake, stopping stdio transport");
return Ok(());
}
};
tokio::select! {
result = service.waiting() => result
.map(|_| ())
.map_err(|source| crate::Error::TaskFailed {
task: crate::error::BackgroundTask::McpService,
source,
}),
() = &mut signal => {
tracing::info!("shutdown signal received, stopping stdio transport");
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use std::assert_matches;
use crate::bridge::WorkspaceRoots;
#[tokio::test]
async fn test_run_stdio_returns_promptly_when_stdin_is_already_closed() {
use std::sync::Arc;
use tokio::sync::Mutex;
use crate::bridge::{NotificationCache, Translator};
use crate::config::McpConfig;
use crate::mcp::{McplsServer, SubscriptionRegistry};
let translator = Arc::new(Translator::new());
let notification_cache = Arc::new(Mutex::new(NotificationCache::new()));
let workspace_roots = WorkspaceRoots::default();
let subs = SubscriptionRegistry::new();
let server = McplsServer::new(
translator,
notification_cache,
workspace_roots,
subs,
crate::ProjectConfigStatus::NotIgnored,
McpConfig::default(),
);
let outcome = tokio::time::timeout(
std::time::Duration::from_secs(2),
super::run_stdio(server, super::ShutdownSignal::new()),
)
.await;
assert!(
outcome.is_ok(),
"run_stdio must not hang when stdin is already closed"
);
let result = outcome.unwrap();
assert_matches!(
result,
Err(crate::Error::McpServerStart(_)),
"expected a McpServerStart error from the failed handshake, got: {result:?}"
);
}
}