use crate::error::Result;
use crate::server::handler::CratesDocsHandler;
use crate::server::CratesDocsServer;
use rust_mcp_sdk::{
error::McpSdkError,
event_store,
mcp_server::{hyper_server, server_runtime, HyperServerOptions, McpServerOptions},
McpServer, StdioTransport, ToMcpServerHandler, TransportOptions,
};
use std::sync::Arc;
pub async fn run_stdio_server(server: &CratesDocsServer) -> Result<()> {
tracing::info!("Starting Stdio MCP server...");
let server_info = server.server_info();
let handler = CratesDocsHandler::new(Arc::new(server.clone()));
let transport = StdioTransport::new(TransportOptions::default())
.map_err(|e| crate::error::Error::mcp("transport", e.to_string()))?;
let mcp_server: Arc<rust_mcp_sdk::mcp_server::ServerRuntime> =
server_runtime::create_server(McpServerOptions {
server_details: server_info,
transport,
handler: handler.to_mcp_server_handler(),
task_store: None,
client_task_store: None,
message_observer: None,
});
tracing::info!("Stdio MCP server started, waiting for connections...");
mcp_server
.start()
.await
.map_err(|e: McpSdkError| crate::error::Error::mcp("server_start", e.to_string()))?;
Ok(())
}
#[derive(Debug, Clone)]
pub struct HyperServerConfig {
protocol_name: String,
sse_support: bool,
}
impl HyperServerConfig {
#[must_use]
pub fn http() -> Self {
Self {
protocol_name: "HTTP".to_string(),
sse_support: false,
}
}
#[must_use]
pub fn sse() -> Self {
Self {
protocol_name: "SSE".to_string(),
sse_support: true,
}
}
#[must_use]
pub fn hybrid() -> Self {
Self {
protocol_name: "Hybrid".to_string(),
sse_support: true,
}
}
#[must_use]
pub fn protocol_name(&self) -> &str {
&self.protocol_name
}
#[must_use]
pub fn sse_support(&self) -> bool {
self.sse_support
}
}
pub async fn run_hyper_server(server: &CratesDocsServer, config: HyperServerConfig) -> Result<()> {
let server_config = server.config();
let server_info = server.server_info();
let handler = CratesDocsHandler::new(Arc::new(server.clone()));
tracing::info!(
"Starting {} MCP server on {}:{}...",
config.protocol_name(),
server_config.server.host,
server_config.server.port
);
let options = HyperServerOptions {
host: server_config.server.host.clone(),
port: server_config.server.port,
transport_options: Arc::new(TransportOptions::default()),
sse_support: config.sse_support(),
event_store: Some(Arc::new(event_store::InMemoryEventStore::default())),
task_store: None,
client_task_store: None,
allowed_hosts: Some(server_config.server.allowed_hosts.clone()),
allowed_origins: Some(server_config.server.allowed_origins.clone()),
health_endpoint: Some("/health".to_string()),
..Default::default()
};
let mcp_server =
hyper_server::create_server(server_info, handler.to_mcp_server_handler(), options);
let started_msg = if config.sse_support() && config.protocol_name() != "SSE" {
format!(
"{} MCP server started, listening on {}:{} (HTTP + SSE)",
config.protocol_name(),
server_config.server.host,
server_config.server.port
)
} else {
format!(
"{} MCP server started, listening on {}:{}",
config.protocol_name(),
server_config.server.host,
server_config.server.port
)
};
tracing::info!("{}", started_msg);
mcp_server
.start()
.await
.map_err(|e: McpSdkError| crate::error::Error::mcp("server_start", e.to_string()))?;
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
pub enum TransportMode {
Stdio,
Http,
Sse,
Hybrid,
}
impl std::str::FromStr for TransportMode {
type Err = String;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"stdio" => Ok(TransportMode::Stdio),
"http" => Ok(TransportMode::Http),
"sse" => Ok(TransportMode::Sse),
"hybrid" => Ok(TransportMode::Hybrid),
_ => Err(format!("Unknown transport mode: {s}")),
}
}
}
impl std::fmt::Display for TransportMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TransportMode::Stdio => write!(f, "stdio"),
TransportMode::Http => write!(f, "http"),
TransportMode::Sse => write!(f, "sse"),
TransportMode::Hybrid => write!(f, "hybrid"),
}
}
}
impl TransportMode {
#[must_use]
pub fn to_hyper_config(&self) -> Option<HyperServerConfig> {
match self {
TransportMode::Stdio => None,
TransportMode::Http => Some(HyperServerConfig::http()),
TransportMode::Sse => Some(HyperServerConfig::sse()),
TransportMode::Hybrid => Some(HyperServerConfig::hybrid()),
}
}
}
pub async fn run_server_with_mode(server: &CratesDocsServer, mode: TransportMode) -> Result<()> {
match mode {
TransportMode::Stdio => run_stdio_server(server).await,
TransportMode::Http | TransportMode::Sse | TransportMode::Hybrid => {
let config = mode
.to_hyper_config()
.expect("Hyper config should exist for HTTP/SSE/Hybrid");
run_hyper_server(server, config).await
}
}
}