#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ListMode {
#[default]
Full,
Discovery,
Names,
}
#[derive(Debug, Clone)]
pub struct ToolHit {
pub name: String,
pub description: String,
pub score: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Scope {
Local,
Federation,
}
pub(crate) const FORCE_DEFERRED_ENV_VAR: &str = "YGG_MCP_FORCE_DEFERRED";
pub(crate) fn force_deferred_via_env() -> bool {
std::env::var(FORCE_DEFERRED_ENV_VAR)
.map(|v| {
matches!(
v.trim().to_lowercase().as_str(),
"1" | "true" | "yes" | "on"
)
})
.unwrap_or(false)
}
pub fn determine_default_mode(client_capabilities: &rmcp::model::ClientCapabilities) -> ListMode {
let client_opted_in = client_capabilities
.experimental
.as_ref()
.map(|exp| exp.contains_key("x-deferred-tools"))
.unwrap_or(false);
let forced = force_deferred_via_env();
let deferred = client_opted_in || forced;
if deferred {
ListMode::Discovery
} else {
ListMode::Full
}
}
pub trait SearchEngine: Send + Sync {
fn search(&self, query: &str, tools: &[ToolHit]) -> Vec<ToolHit>;
}
pub struct KeywordSearchEngine;
impl SearchEngine for KeywordSearchEngine {
fn search(&self, query: &str, tools: &[ToolHit]) -> Vec<ToolHit> {
let q = query.to_lowercase();
let mut hits: Vec<ToolHit> = tools
.iter()
.filter(|t| {
t.name.to_lowercase().contains(&q) || t.description.to_lowercase().contains(&q)
})
.cloned()
.collect();
hits.sort_by(|a, b| {
let a_name = a.name.to_lowercase().contains(&q);
let b_name = b.name.to_lowercase().contains(&q);
b_name.cmp(&a_name).then(a.name.cmp(&b.name))
});
hits
}
}