use std::net::SocketAddr; use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; use axum::{routing::post, Router}; use notify_debouncer_mini::{new_debouncer, notify::RecursiveMode}; use tracing::{info, error}; use mock_server::loader::MockLoader; use mock_server::router::MockRouter; use mock_server::handler::{mock_handler, AppState}; use mock_server::logging; fn print_usage() { println!("Mock Server - A mock API server with hot-reload and MCP support"); println!(); println!("Usage: mock_server [OPTIONS]"); println!(); println!("Options:"); println!(" --mocks Mocks directory path (default: ./mocks)"); println!(" --port HTTP server port (default: 8080)"); println!(" --help Show this help message"); println!(); println!("MCP Endpoint: POST http://127.0.0.1:/mcp"); } #[tokio::main] async fn main() { // Initialize logging system let log_dir = PathBuf::from("./logs"); logging::init(log_dir); // Parse command line arguments let args: Vec = std::env::args().collect(); let mut mocks_dir = PathBuf::from("./mocks"); let mut port: u16 = 8080; let mut i = 1; while i < args.len() { match args[i].as_str() { "--help" | "-h" => { print_usage(); return; } "--mocks" => { if i + 1 < args.len() { mocks_dir = PathBuf::from(&args[i + 1]); i += 1; } else { eprintln!("--mocks requires a directory path"); return; } } "--port" => { if i + 1 < args.len() { match args[i + 1].parse::() { Ok(p) => port = p, Err(_) => { eprintln!("Invalid port number: {}", args[i + 1]); return; } } i += 1; } else { eprintln!("--port requires a port number"); return; } } _ => { eprintln!("Unknown option: {}", args[i]); print_usage(); return; } } i += 1; } if !mocks_dir.exists() { std::fs::create_dir_all(&mocks_dir).unwrap(); } // Create shared MockManager for both HTTP and MCP let manager = Arc::new(mock_server::manager::MockManager::new(mocks_dir.clone())); info!("Loaded {} groups", manager.list_groups().len()); // Run unified HTTP server (includes MCP endpoint) run_http_server(mocks_dir, port, manager).await; } async fn run_http_server(mocks_dir: PathBuf, port: u16, manager: Arc) { info!("Scanning mocks directory..."); let index = MockLoader::load_all_from_dir(&mocks_dir); let shared_state = Arc::new(AppState { router: std::sync::RwLock::new(MockRouter::new(index)), }); // Setup hot-reload watcher let state_for_watcher = shared_state.clone(); let watch_path = mocks_dir.clone(); let manager_for_watcher = manager.clone(); let (tx, rx) = std::sync::mpsc::channel(); let mut debouncer = new_debouncer(Duration::from_millis(200), tx).unwrap(); debouncer.watcher().watch(&watch_path, RecursiveMode::Recursive).unwrap(); tokio::spawn(async move { while let Ok(res) = rx.recv() { match res { Ok(_) => { info!("Detected changes in mocks/, reloading..."); let new_index = MockLoader::load_all_from_dir(&watch_path); let mut writer = state_for_watcher.router.write().expect("Failed to acquire write lock"); *writer = MockRouter::new(new_index); // Also reload in manager manager_for_watcher.reload(); info!("Mocks reloaded successfully."); } Err(e) => error!("Watcher error: {:?}", e), } } }); // Create MCP HTTP service (stateless) let mcp_service = mock_server::mcp::create_mcp_http_service(manager.clone()); let app = Router::new() // MCP endpoint (stateless HTTP transport) .route("/mcp", post(|req| async move { mcp_service.handle(req).await })) // Mock API fallback .fallback(axum::routing::any(mock_handler)) .with_state(shared_state); let addr = SocketAddr::from(([127, 0, 0, 1], port)); info!("HTTP server running at http://{}", addr); info!("MCP endpoint available at http://{}/mcp", addr); let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); if let Err(e) = axum::serve(listener, app).await { error!("HTTP server error: {}", e); } }