use std::sync::Arc;
use rmcp::ErrorData as McpError;
use rmcp::model::CallToolResult;
use super::ServerState;
use super::helpers::{json_result, scan_and_refresh};
use super::types::{
CacheClearParams, CacheClearResponse, CacheGcParams, CacheGcResponse, CacheStatsParams,
CacheStatsResponse,
};
use crate::store_gc::{self, CacheComponent};
pub(super) async fn run_cache_stats(
state: Arc<ServerState>,
_params: CacheStatsParams,
) -> Result<CallToolResult, McpError> {
let state_for_stats = Arc::clone(&state);
let stats = tokio::task::spawn_blocking(move || {
let store = state_for_stats.store.blocking_read();
store_gc::cache_stats(&store.basemind_dir)
})
.await
.map_err(|e| McpError::internal_error(format!("cache_stats join: {e}"), None))?
.map_err(|e| McpError::internal_error(format!("cache_stats: {e}"), None))?;
json_result(&CacheStatsResponse::from(stats))
}
pub(super) async fn run_cache_gc(
state: Arc<ServerState>,
_params: CacheGcParams,
) -> Result<CallToolResult, McpError> {
let state_for_gc = Arc::clone(&state);
let report = tokio::task::spawn_blocking(move || {
let store = state_for_gc.store.blocking_read();
let referenced = store_gc::collect_referenced_hashes(&store.basemind_dir)?;
store_gc::gc_blobs(&store.basemind_dir, &referenced)
})
.await
.map_err(|e| McpError::internal_error(format!("cache_gc join: {e}"), None))?
.map_err(|e| McpError::internal_error(format!("cache_gc: {e}"), None))?;
json_result(&CacheGcResponse::from(report))
}
pub(super) async fn run_cache_clear(
state: Arc<ServerState>,
params: CacheClearParams,
) -> Result<CallToolResult, McpError> {
let component: CacheComponent = params.component.parse().map_err(|e: String| {
McpError::invalid_request(
format!("{e} (valid: blobs|views|lance|git-cache|telemetry|all)"),
None,
)
})?;
match component {
CacheComponent::All | CacheComponent::Views => Err(McpError::invalid_request(
format!(
"clearing `{}` removes the live Fjall index out from under the running \
server; stop the server and run `basemind cache clear --component {}`",
component.as_str(),
component.as_str()
),
None,
)),
CacheComponent::Blobs => {
if !params.confirm {
return Err(McpError::invalid_request(
"clearing `blobs` drops cached extractions; pass confirm=true to proceed \
(a rescan runs afterwards to rebuild them)",
None,
));
}
clear_live_component(Arc::clone(&state), component).await?;
scan_and_refresh(state, None).await?;
json_result(&CacheClearResponse {
component: component.as_str().to_string(),
cleared: true,
})
}
CacheComponent::Lance | CacheComponent::GitCache | CacheComponent::Telemetry => {
clear_live_component(Arc::clone(&state), component).await?;
json_result(&CacheClearResponse {
component: component.as_str().to_string(),
cleared: true,
})
}
}
}
async fn clear_live_component(
state: Arc<ServerState>,
component: CacheComponent,
) -> Result<(), McpError> {
tokio::task::spawn_blocking(move || {
let store = state.store.blocking_write();
store_gc::clear_component(&store.basemind_dir, component)
})
.await
.map_err(|e| McpError::internal_error(format!("cache_clear join: {e}"), None))?
.map_err(|e| McpError::internal_error(format!("cache_clear: {e}"), None))
}