Files
crank/apps/mcp-server/src/catalog.rs
T
2026-03-28 00:58:56 +03:00

85 lines
2.3 KiB
Rust

use std::{
collections::HashMap,
sync::Arc,
time::{Duration, Instant},
};
use crank_registry::{PostgresRegistry, RegistryError, RegistryOperation};
use tokio::sync::RwLock;
use tracing::info;
#[derive(Clone)]
pub struct PublishedToolCatalog {
registry: PostgresRegistry,
refresh_interval: Duration,
cached: Arc<RwLock<CachedCatalog>>,
}
#[derive(Default)]
struct CachedCatalog {
loaded_at: Option<Instant>,
tools: Vec<RegistryOperation>,
tools_by_name: HashMap<String, RegistryOperation>,
}
impl PublishedToolCatalog {
pub fn new(registry: PostgresRegistry, refresh_interval: Duration) -> Self {
Self {
registry,
refresh_interval,
cached: Arc::new(RwLock::new(CachedCatalog::default())),
}
}
pub async fn list_tools(&self) -> Result<Vec<RegistryOperation>, RegistryError> {
self.refresh_if_stale().await?;
let guard = self.cached.read().await;
Ok(guard.tools.clone())
}
pub async fn get_tool(
&self,
tool_name: &str,
) -> Result<Option<RegistryOperation>, RegistryError> {
self.refresh_if_stale().await?;
let guard = self.cached.read().await;
Ok(guard.tools_by_name.get(tool_name).cloned())
}
async fn refresh_if_stale(&self) -> Result<(), RegistryError> {
let should_refresh = {
let guard = self.cached.read().await;
match guard.loaded_at {
Some(loaded_at) => loaded_at.elapsed() >= self.refresh_interval,
None => true,
}
};
if !should_refresh {
return Ok(());
}
let tools = self.registry.list_published_operations().await?;
let tools_by_name = tools
.iter()
.cloned()
.map(|operation| (operation.name.clone(), operation))
.collect::<HashMap<_, _>>();
let mut guard = self.cached.write().await;
let previous_count = guard.tools.len();
guard.loaded_at = Some(Instant::now());
guard.tools = tools;
guard.tools_by_name = tools_by_name;
info!(
published_tool_count = guard.tools.len(),
previous_published_tool_count = previous_count,
"published tool catalog refreshed"
);
Ok(())
}
}