feat(mcp): 实现HTTP统一方案并添加MCP文档

- 将MCP服务和Mock API合并到单个HTTP服务器(8080端口)
- 添加POST /mcp端点,使用无状态StreamableHttpService
- 新增docs/mcp-implementation.md文档
This commit is contained in:
2026-03-29 22:30:29 +08:00
parent d364307131
commit cfcebbe300
7 changed files with 1125 additions and 124 deletions

View File

@@ -3,37 +3,39 @@ use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use axum::{routing::any, Router};
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 support");
println!("Mock Server - A mock API server with hot-reload and MCP support");
println!();
println!("Usage: mock_server [OPTIONS]");
println!();
println!("Options:");
println!(" --mcp Run as MCP server (stdio transport)");
println!(" --mocks <DIR> Mocks directory path (default: ./mocks)");
println!(" --port <PORT> Server port (default: 8080)");
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() {
tracing_subscriber::fmt::init();
// 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;
#[cfg(feature = "mcp")]
let mut mcp_mode = false;
let mut i = 1;
while i < args.len() {
match args[i].as_str() {
@@ -41,17 +43,6 @@ async fn main() {
print_usage();
return;
}
"--mcp" => {
#[cfg(feature = "mcp")]
{
mcp_mode = true;
}
#[cfg(not(feature = "mcp"))]
{
eprintln!("MCP feature not enabled. Rebuild with --features mcp");
std::process::exit(1);
}
}
"--mocks" => {
if i + 1 < args.len() {
mocks_dir = PathBuf::from(&args[i + 1]);
@@ -89,37 +80,25 @@ async fn main() {
std::fs::create_dir_all(&mocks_dir).unwrap();
}
#[cfg(feature = "mcp")]
if mcp_mode {
run_mcp_server(mocks_dir).await;
return;
}
// 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_http_server(mocks_dir, port).await;
// Run unified HTTP server (includes MCP endpoint)
run_http_server(mocks_dir, port, manager).await;
}
#[cfg(feature = "mcp")]
async fn run_mcp_server(mocks_dir: PathBuf) {
println!("Starting MCP server...");
let manager = Arc::new(mock_server::manager::MockManager::new(mocks_dir));
println!("Loaded {} groups", manager.list_groups().len());
match mock_server::mcp::run_mcp_server(manager).await {
Ok(_) => println!("MCP server stopped"),
Err(e) => eprintln!("MCP server error: {}", e),
}
}
async fn run_http_server(mocks_dir: PathBuf, port: u16) {
println!("Scanning mocks directory...");
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();
@@ -129,24 +108,37 @@ async fn run_http_server(mocks_dir: PathBuf, port: u16) {
while let Ok(res) = rx.recv() {
match res {
Ok(_) => {
println!("🔄 Detecting changes in mocks/, reloading...");
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);
println!("✅ Mocks reloaded successfully.");
// Also reload in manager
manager_for_watcher.reload();
info!("Mocks reloaded successfully.");
}
Err(e) => eprintln!("Watcher error: {:?}", e),
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()
.fallback(any(mock_handler))
// 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));
println!("🚀 Server running at http://{}", addr);
info!("HTTP server running at http://{}", addr);
info!("MCP endpoint available at http://{}/mcp", addr);
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
if let Err(e) = axum::serve(listener, app).await {
error!("HTTP server error: {}", e);
}
}