mod cli;
mod configuration;
mod spec_loader;
use std::{process, sync::Arc};
use actix_web::{App, HttpServer, web};
use cli::Cli;
use configuration::Configuration;
use rmcp::transport::streamable_http_server::session::local::LocalSessionManager;
use rmcp_actix_web::transport::StreamableHttpService;
use rmcp_openapi::Error;
use tracing::{debug, error, info, info_span};
#[actix_web::main]
async fn main() {
if let Err(e) = run().await {
error!("Application error: {}", e);
process::exit(1);
}
}
async fn run() -> Result<(), Error> {
let cli = Cli::parse_args();
let config = Configuration::from_cli(cli)?;
setup_logging();
let bind_address = config.bind_address.clone();
let port = config.port;
let stateful = config.stateful;
let span = info_span!(
"server_initialization",
bind_address = %bind_address,
port = port,
);
let _enter = span.enter();
let mut server = config.try_into_server().await?;
server.load_openapi_spec()?;
info!(
tool_count = server.tool_count(),
"Successfully loaded tools from OpenAPI specification"
);
info!(
authorization_mode = ?server.authorization_mode(),
"Authorization mode configured"
);
#[cfg(feature = "authorization-token-passthrough")]
match server.authorization_mode() {
rmcp_openapi::AuthorizationMode::PassthroughWarn => {
tracing::warn!(
"⚠️ Authorization header passthrough is enabled with warnings. \
This violates MCP specification but may be necessary for proxy scenarios."
);
}
rmcp_openapi::AuthorizationMode::PassthroughSilent => {
info!(
"Authorization header passthrough is enabled (silent mode). \
Headers will be forwarded without per-request warnings."
);
}
_ => {}
}
debug!(
tools = %server.get_tool_names().join(", "),
"Available tools"
);
debug!(
stats = %server.get_tool_stats(),
"Tool statistics"
);
server.validate_registry()?;
let bind_addr = format!("{}:{}", bind_address, port);
info!(
bind_address = %bind_addr,
"OpenAPI MCP Server starting"
);
let service = StreamableHttpService::builder()
.service_factory(Arc::new(move || Ok(server.clone())))
.session_manager(LocalSessionManager::default().into())
.stateful_mode(stateful)
.build();
let http_server = HttpServer::new(move || {
App::new()
.service(web::scope("/mcp").service(service.clone().scope()))
})
.bind(bind_addr.clone())?
.run();
info!(
connection_url = %format!("http://{bind_addr}/mcp"),
"Server ready for MCP client connections"
);
http_server.await?;
info!("Shutdown signal received, stopping server");
Ok(())
}
fn setup_logging() {
let env_filter = tracing_subscriber::EnvFilter::try_from_env("RMCP_OPENAPI_LOG")
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info"));
tracing_subscriber::fmt()
.with_env_filter(env_filter)
.with_target(true) .init();
}