use rust_mcp_sdk::macros::{JsonSchema, mcp_tool};
use rust_mcp_sdk::schema::{CallToolResult, TextContent, schema_utils::CallToolError};
use tracing::{info, instrument};
use super::utils::serialize_result;
use crate::project::ProjectWorkspace;
#[mcp_tool(
name = "get_project_details",
description = "Get comprehensive project analysis including build configurations, components, \
and global compilation database information. Provides complete workspace \
intelligence for multi-provider build systems.
🚀 PRIMARY PURPOSE FOR AI AGENTS:
This tool provides ABSOLUTE BUILD DIRECTORY PATHS that you should use with search_symbols and analyze_symbol_context.
ALWAYS call this tool FIRST to discover available build directories before using other MCP tools.
TYPICAL AI AGENT WORKFLOW:
1. get_project_details {} → Get absolute build paths: {\"/home/project/build-debug\": {...}}
2. search_symbols {\"query\": \"\", \"build_directory\": \"/home/project/build-debug\"} → Explore symbols
3. analyze_symbol_context {\"symbol\": \"FoundSymbol\", \"build_directory\": \"/home/project/build-debug\"}
🏗️ PROJECT OVERVIEW:
• Project name and root directory information
• Global compilation database path (if configured)
• Component count and provider type summary
• Discovery timestamp and scan configuration
• ABSOLUTE BUILD DIRECTORY PATHS for use in other tools
🔧 MULTI-PROVIDER DISCOVERY:
• Automatic detection of CMake projects (CMakeLists.txt + build directories)
• Meson project support (meson.build + build configurations)
• Extensible architecture ready for Bazel, Buck, xmake, and other build systems
• Unified component representation across all providers
⚙️ BUILD CONFIGURATION ANALYSIS:
• Generator type identification (Ninja, Unix Makefiles, Visual Studio, etc.)
• Build type classification (Debug, Release, RelWithDebInfo, MinSizeRel)
• Compiler toolchain detection and version information
• Build options and feature flags extraction
📋 COMPILATION DATABASE STATUS:
• Global compilation database path (overrides component-specific databases)
• Per-component compile_commands.json availability and validity
• LSP server compatibility assessment across all build systems
🎯 PROJECT STRUCTURE DETAILS:
Each discovered component includes:
• Build directory path (ABSOLUTE) and source root location
• Provider type (cmake, meson, etc.) for build system identification
• Generator and build type information in standardized format
• Complete build options and configuration details
• Compilation database status for LSP integration
🎯 PRIMARY USE CASES:
Project assessment • Build system inventory • LSP setup validation
• Development environment verification • CI/CD build matrix generation
• Global compilation database configuration analysis
• PROVIDING ABSOLUTE PATHS for other MCP tools
INPUT PARAMETERS:
• path (optional): Project root path to scan (triggers fresh scan if different) - AVOID \".\" use None for cached scan
• depth (optional): Scan depth for component discovery (0-10 levels) - only specify if different from cached scan
• include_details (optional): Include detailed build options (default: false)
OUTPUT MODES:
• Short view (default): Essential info + build_options_count to prevent context exhaustion
• Detailed view (include_details=true): All build options for debugging/analysis
RECOMMENDED USAGE:
• Use default for project overview and general development
• Use include_details=true only for build configuration debugging
• Copy the absolute build directory paths from output to use in other tools"
)]
#[derive(Debug, ::serde::Deserialize, ::serde::Serialize, JsonSchema)]
pub struct GetProjectDetailsTool {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub depth: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub include_details: Option<bool>,
}
impl GetProjectDetailsTool {
#[instrument(name = "get_project_details", skip(self, meta_project))]
pub fn call_tool(
&self,
meta_project: &ProjectWorkspace,
) -> Result<CallToolResult, CallToolError> {
let requested_path = self.path.as_ref().map(std::path::PathBuf::from);
let requested_depth = self.depth.unwrap_or(meta_project.scan_depth as u32) as usize;
let needs_rescan = match &requested_path {
Some(path) => path != &meta_project.project_root_path,
None => false,
} || requested_depth != meta_project.scan_depth;
let fresh_scan = if needs_rescan {
let scan_root = requested_path
.as_ref()
.unwrap_or(&meta_project.project_root_path);
info!(
"Re-scanning project: path={}, depth={} (differs from cached scan)",
scan_root.display(),
requested_depth
);
Some(self.perform_fresh_scan(scan_root, requested_depth)?)
} else {
info!("Using cached ProjectWorkspace scan results");
None
};
let effective_meta_project = fresh_scan.as_ref().unwrap_or(meta_project);
let rescanned_fresh = fresh_scan.is_some();
let include_details = self.include_details.unwrap_or(false);
let view = if include_details {
effective_meta_project.get_full_view()
} else {
effective_meta_project.get_short_view()
};
let mut content = serde_json::to_value(&view).map_err(|e| {
CallToolError::new(std::io::Error::other(format!(
"Failed to serialize project view: {e}"
)))
})?;
if let Some(obj) = content.as_object_mut() {
obj.insert(
"rescanned".to_string(),
serde_json::Value::Bool(rescanned_fresh),
);
}
info!(
"Successfully analyzed project details: {} components across {} providers: {:?}",
effective_meta_project.component_count(),
effective_meta_project.get_provider_types().len(),
effective_meta_project.get_provider_types()
);
Ok(CallToolResult::text_content(vec![TextContent::from(
serialize_result(&content),
)]))
}
fn perform_fresh_scan(
&self,
scan_root: &std::path::Path,
depth: usize,
) -> Result<crate::project::ProjectWorkspace, CallToolError> {
use crate::project::ProjectScanner;
let scanner = ProjectScanner::with_default_providers();
scanner.scan_project(scan_root, depth, None).map_err(|e| {
CallToolError::new(std::io::Error::other(format!(
"Failed to scan project at {}: {}",
scan_root.display(),
e
)))
})
}
}