mod clangd;
mod io;
mod logging;
mod lsp;
mod mcp_server;
mod project;
mod symbol;
#[cfg(test)]
mod test_utils;
use clap::Parser;
use logging::{LogConfig, init_logging};
use mcp_server::CppServerHandler;
use project::{ProjectScanner, ProjectWorkspace};
use rust_mcp_sdk::schema::{
Implementation, InitializeResult, LATEST_PROTOCOL_VERSION, ServerCapabilities,
ServerCapabilitiesTools,
};
use rust_mcp_sdk::{
McpServer, StdioTransport, TransportOptions, error::SdkResult, mcp_server::server_runtime,
};
use std::path::PathBuf;
use tracing::info;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(long, value_name = "DIR")]
root: Option<PathBuf>,
#[arg(long, value_name = "PATH")]
clangd_path: Option<String>,
#[arg(long, value_name = "LEVEL")]
log_level: Option<String>,
#[arg(long, value_name = "FILE")]
log_file: Option<PathBuf>,
}
fn resolve_clangd_path(clangd_path_arg: Option<String>) -> String {
clangd_path_arg
.or_else(|| std::env::var("CLANGD_PATH").ok())
.unwrap_or_else(|| "clangd".to_string())
}
fn create_project_workspace(project_root: PathBuf) -> ProjectWorkspace {
info!(
"Scanning project root for build configurations: {} (depth: 3)",
project_root.display()
);
let scanner = ProjectScanner::with_default_providers();
match scanner.scan_project(&project_root, 3, None) {
Ok(project_workspace) => {
info!(
"Successfully discovered {} components across {} providers: {:?}",
project_workspace.component_count(),
project_workspace.get_provider_types().len(),
project_workspace.get_provider_types()
);
project_workspace
}
Err(e) => {
eprintln!(
"Failed to scan project at {}: {}",
project_root.display(),
e
);
ProjectWorkspace::new(project_root, Vec::new(), 3)
}
}
}
#[tokio::main]
async fn main() -> SdkResult<()> {
let args = Args::parse();
let log_level = args.log_level.clone();
let log_file = args.log_file.clone();
let root_arg = args.root.clone();
let log_config = LogConfig::from_env().with_overrides(log_level, log_file);
if let Err(e) = init_logging(log_config) {
eprintln!("Failed to initialize logging: {e}");
std::process::exit(1);
}
let project_root = root_arg.unwrap_or_else(|| {
std::env::current_dir().unwrap_or_else(|e| {
eprintln!("Failed to get current directory: {e}");
std::process::exit(1);
})
});
let project_workspace = create_project_workspace(project_root);
info!(
"Starting C++ MCP Server with project root: {}",
project_workspace.project_root_path.display()
);
let server_details = InitializeResult {
server_info: Implementation {
name: "C++ MCP Server".to_string(),
version: "0.1.0".to_string(),
title: Some("C++ Project Analysis MCP Server".to_string()),
},
capabilities: ServerCapabilities {
tools: Some(ServerCapabilitiesTools { list_changed: None }),
..Default::default()
},
meta: None,
instructions: Some("C++ project analysis and LSP bridge server".to_string()),
protocol_version: LATEST_PROTOCOL_VERSION.to_string(),
};
let clangd_path = resolve_clangd_path(args.clangd_path);
info!("Using clangd: {}", clangd_path);
let transport = StdioTransport::new(TransportOptions::default())?;
let handler = match CppServerHandler::new(project_workspace, clangd_path) {
Ok(handler) => handler,
Err(e) => {
eprintln!("Failed to create server handler: {}", e);
std::process::exit(1);
}
};
let server = server_runtime::create_server(server_details, transport, handler);
info!("C++ MCP Server ready and listening for requests");
if let Err(start_error) = server.start().await {
eprintln!(
"{}",
start_error
.rpc_error_message()
.unwrap_or(&start_error.to_string())
);
}
Ok(())
}