- 将MCP服务和Mock API合并到单个HTTP服务器(8080端口) - 添加POST /mcp端点,使用无状态StreamableHttpService - 新增docs/mcp-implementation.md文档
145 lines
4.9 KiB
Rust
145 lines
4.9 KiB
Rust
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 <DIR> Mocks directory path (default: ./mocks)");
|
|
println!(" --port <PORT> HTTP server port (default: 8080)");
|
|
println!(" --help Show this help message");
|
|
println!();
|
|
println!("MCP Endpoint: POST http://127.0.0.1:<PORT>/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<String> = 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::<u16>() {
|
|
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<mock_server::manager::MockManager>) {
|
|
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);
|
|
}
|
|
}
|