use crate::symbols::{
indexer::SymbolIndexStore,
relationships::RelationshipGraph,
context::{create_search_result, SymbolContext},
Symbol, SymbolKind, SymbolSearchResult, SymbolRelationType,
};
use rmcp::handler::server::wrapper::{Json, Parameters};
use std::path::{Path, PathBuf};
use std::sync::Arc;
static SYMBOL_STORE: once_cell::sync::Lazy<Arc<SymbolIndexStore>> =
once_cell::sync::Lazy::new(|| {
let store_path = std::env::current_dir()
.unwrap()
.join(".codesearch")
.join("symbol_index");
Arc::new(SymbolIndexStore::new(store_path))
});
static RELATIONSHIP_GRAPH: once_cell::sync::Lazy<Arc<RelationshipGraph>> =
once_cell::sync::Lazy::new(|| Arc::new(RelationshipGraph::new()));
pub async fn search_symbols_tool(
params: Parameters<SearchSymbolsParams>,
) -> Json<Vec<SymbolSearchResult>> {
let params = params.0;
let path_buf = PathBuf::from(params.path.as_deref().unwrap_or("."));
let _ = SYMBOL_STORE.index_directory(
&path_buf,
params.extensions.as_deref(),
params.exclude.as_deref(),
);
let symbols = SYMBOL_STORE.index().search_symbols(
params.pattern.as_deref(),
params.kind.as_ref(),
params.file_path.as_deref(),
None,
);
let mut results = Vec::new();
for symbol in symbols {
let score = calculate_symbol_score(&symbol, ¶ms);
if let Some(ref kind) = params.kind {
if symbol.kind != *kind {
continue;
}
}
if let Some(ref file_path) = params.file_path {
if symbol.file_path != *file_path {
continue;
}
}
let mut result = create_search_result(&symbol, score, params.context_lines.unwrap_or(3));
if params.include_related.unwrap_or(false) {
result.related_symbols = find_related_symbols(&symbol);
}
results.push(result);
}
results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal));
if let Some(limit) = params.limit {
results.truncate(limit);
}
Json(results)
}
pub async fn get_symbol_details_tool(
params: Parameters<GetSymbolDetailsParams>,
) -> Json<serde_json::Value> {
let params = params.0;
let symbol = if let Some(ref id) = params.id {
SYMBOL_STORE.index().get_symbol(id)
} else if let Some(ref name) = params.name {
let matches = SYMBOL_STORE.index().find_by_name(name);
if let Some(ref file_path) = params.file_path {
matches.into_iter().find(|s| &s.file_path == file_path)
} else {
matches.first().cloned()
}
} else {
None
};
if let Some(symbol) = symbol {
let file_path = Path::new(&symbol.file_path);
let context = SymbolContext::extract(file_path, symbol.line, 5);
let relationships = RELATIONSHIP_GRAPH.get_relationships(&symbol.id);
Json(serde_json::json!({
"symbol": symbol,
"context": context.ok(),
"relationships": relationships,
}))
} else {
Json(serde_json::json!({
"error": "Symbol not found"
}))
}
}
pub async fn find_symbol_relationships_tool(
params: Parameters<FindSymbolRelationshipsParams>,
) -> Json<serde_json::Value> {
let params = params.0;
let symbol = if let Some(ref id) = params.symbol_id {
RELATIONSHIP_GRAPH.get_symbol(id)
} else if let Some(ref name) = params.symbol_name {
SYMBOL_STORE.index().find_by_name(name).first().cloned()
} else {
None
};
if let Some(symbol) = symbol {
let mut related = Vec::new();
if let Some(ref relation_types) = params.relation_types {
for rel_type in relation_types {
let rel_type: SymbolRelationType = rel_type.clone();
let relations = RELATIONSHIP_GRAPH.get_relationships_by_type(
&symbol.id,
rel_type,
);
for relation in relations {
if let Some(related_symbol) = RELATIONSHIP_GRAPH.get_symbol(&relation.symbol_id) {
related.push(serde_json::json!({
"symbol": related_symbol,
"relation_type": relation.relation_type,
"confidence": relation.confidence,
}));
}
}
}
} else {
let relations = RELATIONSHIP_GRAPH.get_relationships(&symbol.id);
for relation in relations {
if let Some(related_symbol) = RELATIONSHIP_GRAPH.get_symbol(&relation.symbol_id) {
related.push(serde_json::json!({
"symbol": related_symbol,
"relation_type": relation.relation_type,
"confidence": relation.confidence,
}));
}
}
}
Json(serde_json::json!({
"symbol": symbol,
"related_symbols": related,
}))
} else {
Json(serde_json::json!({
"error": "Symbol not found"
}))
}
}
pub async fn build_symbol_index_tool(
params: Parameters<BuildSymbolIndexParams>,
) -> Json<serde_json::Value> {
let params = params.0;
let path_buf = PathBuf::from(params.path.as_deref().unwrap_or("."));
let start = std::time::Instant::now();
let count = SYMBOL_STORE.index_directory(
&path_buf,
params.extensions.as_deref(),
params.exclude.as_deref(),
).unwrap_or(0);
let duration = start.elapsed();
let stats = SYMBOL_STORE.index().get_stats();
Json(serde_json::json!({
"success": true,
"indexed_symbols": count,
"duration_ms": duration.as_millis(),
"total_symbols": stats.total_symbols,
"total_files": stats.total_files,
"symbols_by_kind": stats.symbols_by_kind,
"symbols_by_language": stats.symbols_by_language,
}))
}
pub async fn get_index_stats_tool(
params: Parameters<GetIndexStatsParams>,
) -> Json<serde_json::Value> {
let _params = params.0;
let stats = SYMBOL_STORE.index().get_stats();
Json(serde_json::json!({
"total_symbols": stats.total_symbols,
"total_files": stats.total_files,
"symbols_by_kind": stats.symbols_by_kind,
"symbols_by_language": stats.symbols_by_language,
"index_size_bytes": stats.index_size_bytes,
"last_updated": stats.last_updated,
}))
}
pub async fn find_symbol_hierarchy_tool(
params: Parameters<FindSymbolHierarchyParams>,
) -> Json<serde_json::Value> {
let params = params.0;
let symbol = if let Some(ref id) = params.symbol_id {
RELATIONSHIP_GRAPH.get_symbol(id)
} else if let Some(ref name) = params.symbol_name {
SYMBOL_STORE.index().find_by_name(name).first().cloned()
} else {
None
};
if let Some(symbol) = symbol {
let hierarchy = RELATIONSHIP_GRAPH.find_hierarchy(&symbol.id);
Json(serde_json::json!({
"root_symbol": symbol,
"hierarchy": hierarchy,
}))
} else {
Json(serde_json::json!({
"error": "Symbol not found"
}))
}
}
fn calculate_symbol_score(symbol: &Symbol, params: &SearchSymbolsParams) -> f64 {
let mut score: f64 = 50.0;
if let Some(ref pattern) = params.pattern {
if symbol.name == *pattern {
score += 30.0;
} else if symbol.name.contains(pattern) {
score += 15.0;
}
}
if symbol.is_public() {
score += 10.0;
}
if symbol.is_function() || symbol.is_type() {
score += 10.0;
}
if let Some(ref visibility) = params.visibility {
if symbol.visibility != *visibility {
score -= 50.0; }
}
score.min(100.0).max(0.0)
}
fn find_related_symbols(symbol: &Symbol) -> Vec<crate::symbols::SymbolRelation> {
let relations = RELATIONSHIP_GRAPH.get_relationships(&symbol.id);
relations
.into_iter()
.map(|r| crate::symbols::SymbolRelation {
symbol_id: r.symbol_id,
relation_type: r.relation_type,
confidence: r.confidence,
})
.collect()
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SearchSymbolsParams {
#[serde(default)]
pub pattern: Option<String>,
#[serde(default)]
pub path: Option<String>,
#[serde(default)]
pub extensions: Option<Vec<String>>,
#[serde(default)]
pub exclude: Option<Vec<String>>,
#[serde(default)]
pub kind: Option<SymbolKind>,
#[serde(default)]
pub file_path: Option<String>,
#[serde(default)]
pub visibility: Option<crate::symbols::SymbolVisibility>,
#[serde(default)]
pub context_lines: Option<usize>,
#[serde(default)]
pub include_related: Option<bool>,
#[serde(default)]
pub limit: Option<usize>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct GetSymbolDetailsParams {
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub file_path: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct FindSymbolRelationshipsParams {
#[serde(default)]
pub symbol_id: Option<String>,
#[serde(default)]
pub symbol_name: Option<String>,
#[serde(default)]
pub relation_types: Option<Vec<SymbolRelationType>>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct BuildSymbolIndexParams {
#[serde(default)]
pub path: Option<String>,
#[serde(default)]
pub extensions: Option<Vec<String>>,
#[serde(default)]
pub exclude: Option<Vec<String>>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct GetIndexStatsParams {
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct FindSymbolHierarchyParams {
#[serde(default)]
pub symbol_id: Option<String>,
#[serde(default)]
pub symbol_name: Option<String>,
}