use actix_web::{App, HttpResponse, HttpServer, Result, web};
use rmcp_actix_web::{SseServer, SseServerConfig};
use tokio_util::sync::CancellationToken;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
mod common;
use common::calculator::Calculator;
async fn health_check() -> Result<HttpResponse> {
Ok(HttpResponse::Ok().json(serde_json::json!({
"status": "healthy",
"service": "mcp-calculator",
"version": "1.0.0"
})))
}
async fn root() -> Result<HttpResponse> {
Ok(HttpResponse::Ok().json(serde_json::json!({
"message": "MCP Calculator Service",
"endpoints": {
"health": "/health",
"mcp_sse": "/api/v1/mcp/sse",
"mcp_post": "/api/v1/mcp/message"
},
"documentation": "https://modelcontextprotocol.io/"
})))
}
#[actix_web::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "info".to_string().into()),
)
.with(tracing_subscriber::fmt::layer())
.init();
let bind_addr = "127.0.0.1:8080";
tracing::info!("Starting SSE composition example server on {}", bind_addr);
let _sse_config = SseServerConfig {
bind: bind_addr.parse()?,
sse_path: "/sse".to_string(),
post_path: "/message".to_string(),
ct: CancellationToken::new(),
sse_keep_alive: Some(std::time::Duration::from_secs(30)),
};
let server = HttpServer::new(move || {
let sse_config = SseServerConfig {
bind: "127.0.0.1:0".parse().unwrap(), sse_path: "/sse".to_string(),
post_path: "/message".to_string(),
ct: CancellationToken::new(),
sse_keep_alive: Some(std::time::Duration::from_secs(30)),
};
let (sse_server, mcp_scope) = SseServer::new(sse_config);
let _ct = sse_server.with_service(Calculator::new);
App::new()
.wrap(actix_web::middleware::Logger::default())
.route("/", web::get().to(root))
.route("/health", web::get().to(health_check))
.service(
web::scope("/api")
.service(web::scope("/v1").service(web::scope("/mcp").service(mcp_scope))),
)
})
.bind(bind_addr)?
.run();
tracing::info!("🚀 Server started successfully!");
tracing::info!("📊 Health check: http://{}/health", bind_addr);
tracing::info!("🔌 MCP SSE endpoint: http://{}/api/v1/mcp/sse", bind_addr);
tracing::info!(
"📨 MCP POST endpoint: http://{}/api/v1/mcp/message",
bind_addr
);
tracing::info!("Press Ctrl+C to stop the server");
tokio::select! {
result = server => {
if let Err(e) = result {
tracing::error!("HTTP server error: {}", e);
}
}
_ = tokio::signal::ctrl_c() => {
tracing::info!("Received Ctrl+C, shutting down gracefully");
}
}
Ok(())
}