use std::sync::Arc;
use actix_web::{web, App, HttpResponse, HttpServer};
use tokio::sync::mpsc;
use super::{McpServer, McpServerConfig, ServerError};
use crate::protocol::{ClientInbound, JsonRpcId, JsonRpcMessage, JsonRpcResponse, ServerOutbound};
struct AppState {
inbound_tx: mpsc::Sender<ClientInbound>,
server: Arc<McpServer>,
}
pub struct McpHttpServer;
impl McpHttpServer {
pub async fn run(config: McpServerConfig, host: &str, port: u16) -> Result<(), ServerError> {
let (server, mut channels) = McpServer::new(config);
let inbound_tx = channels.inbound_tx.clone();
let _outbound_handle = tokio::spawn(async move {
while let Some(outbound) = channels.outbound_rx.recv().await {
match &outbound {
ServerOutbound::Notification(n) => {
eprintln!("[MCP] Notification: {}", n.method);
}
ServerOutbound::Request(r) => {
eprintln!("[MCP] Server request: {}", r.method);
}
_ => {}
}
}
});
let state = web::Data::new(AppState {
inbound_tx,
server: Arc::clone(&server),
});
HttpServer::new(move || {
let state = state.clone();
App::new()
.app_data(state)
.route("/rpc", web::post().to(handle_rpc))
.route("/tools", web::get().to(handle_tools_list))
.route("/call", web::post().to(handle_tool_call))
.route("/health", web::get().to(handle_health))
})
.bind((host, port))
.map_err(|e| ServerError::Io(std::io::Error::new(std::io::ErrorKind::AddrInUse, e)))?
.run()
.await
.map_err(|e| ServerError::Io(std::io::Error::other(e)))
}
}
async fn handle_rpc(state: web::Data<AppState>, body: String) -> HttpResponse {
let message = match JsonRpcMessage::parse(&body) {
Ok(m) => m,
Err(e) => {
let error_response = JsonRpcResponse::error(
JsonRpcId::Null,
-32700,
format!("Parse error: {}", e),
None,
);
return HttpResponse::Ok().json(error_response);
}
};
match message {
JsonRpcMessage::Request(request) => {
let response = handle_request_directly(&state.server, request).await;
HttpResponse::Ok().json(response)
}
JsonRpcMessage::Notification(notification) => {
let inbound = ClientInbound::Notification(notification);
let _ = state.inbound_tx.send(inbound).await;
HttpResponse::NoContent().finish()
}
JsonRpcMessage::Response(_) => HttpResponse::BadRequest().json(serde_json::json!({
"error": "Unexpected response message"
})),
}
}
async fn handle_request_directly(
server: &McpServer,
request: crate::protocol::JsonRpcRequest,
) -> JsonRpcResponse {
match request.method.as_str() {
"initialize" => {
JsonRpcResponse::success(
request.id,
serde_json::json!({
"protocolVersion": crate::protocol::MCP_PROTOCOL_VERSION,
"serverInfo": server.server_info(),
"capabilities": {} }),
)
}
"tools/list" => {
let tools = server.list_tools();
JsonRpcResponse::success(request.id, serde_json::json!({ "tools": tools }))
}
"tools/call" => {
let params = match request.params {
Some(p) => p,
None => {
return JsonRpcResponse::error(
request.id,
-32602,
"Missing params".to_string(),
None,
);
}
};
let name = match params.get("name").and_then(|n| n.as_str()) {
Some(n) => n,
None => {
return JsonRpcResponse::error(
request.id,
-32602,
"Missing tool name".to_string(),
None,
);
}
};
let arguments = params
.get("arguments")
.cloned()
.unwrap_or(serde_json::json!({}));
let result = server.call_tool(name, arguments).await;
match result {
Ok(content) => JsonRpcResponse::success(
request.id,
serde_json::json!({
"content": content,
"isError": false
}),
),
Err(e) => JsonRpcResponse::success(
request.id,
serde_json::json!({
"content": [{ "type": "text", "text": e.to_string() }],
"isError": true
}),
),
}
}
"ping" => JsonRpcResponse::success(request.id, serde_json::json!({})),
_ => JsonRpcResponse::error(
request.id,
-32601,
format!("Method not found: {}", request.method),
None,
),
}
}
async fn handle_tools_list(state: web::Data<AppState>) -> HttpResponse {
let tools = state.server.list_tools();
HttpResponse::Ok().json(tools)
}
#[derive(serde::Deserialize)]
struct CallToolRequest {
name: String,
arguments: serde_json::Value,
}
async fn handle_tool_call(
state: web::Data<AppState>,
body: web::Json<CallToolRequest>,
) -> HttpResponse {
let result = state
.server
.call_tool(&body.name, body.arguments.clone())
.await;
match result {
Ok(content) => HttpResponse::Ok().json(content),
Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({
"error": e.to_string()
})),
}
}
async fn handle_health(state: web::Data<AppState>) -> HttpResponse {
let status = state.server.status();
HttpResponse::Ok().json(serde_json::json!({
"status": format!("{:?}", status),
"name": state.server.name(),
"version": state.server.version()
}))
}
#[cfg(test)]
mod tests {
#[test]
fn test_http_server_module_exists() {
}
}