use trusty_common::mcp::ServiceDescriptor;
#[derive(Debug, Default, Clone, Copy)]
pub struct SearchMcpService;
impl SearchMcpService {
pub const fn new() -> Self {
Self
}
}
impl ServiceDescriptor for SearchMcpService {
fn name(&self) -> &str {
"trusty-search"
}
fn version(&self) -> &str {
env!("CARGO_PKG_VERSION")
}
fn tools(&self) -> Vec<serde_json::Value> {
match crate::mcp::tools::tool_descriptors() {
serde_json::Value::Array(items) => items,
_ => Vec::new(),
}
}
fn scopes_for(&self, tool: &str) -> Vec<String> {
crate::mcp::openrpc::scopes_for_tool(tool)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn name_is_trusty_search() {
assert_eq!(SearchMcpService.name(), "trusty-search");
}
#[test]
fn version_matches_cargo_pkg_version() {
assert_eq!(SearchMcpService::new().version(), env!("CARGO_PKG_VERSION"));
}
#[test]
fn tools_returns_exactly_fifteen() {
let tools = SearchMcpService.tools();
assert_eq!(
tools.len(),
15,
"expected 15 MCP tools, got {}: {:?}",
tools.len(),
tools
.iter()
.map(|t| t.get("name").and_then(|n| n.as_str()).unwrap_or("?"))
.collect::<Vec<_>>()
);
}
#[test]
fn every_tool_has_non_empty_scopes() {
let svc = SearchMcpService;
for tool in svc.tools() {
let name = tool
.get("name")
.and_then(|n| n.as_str())
.expect("tool descriptor missing name");
assert!(
!svc.scopes_for(name).is_empty(),
"tool {name} must have at least one scope"
);
}
}
#[test]
fn read_tool_scope_mapping() {
assert_eq!(
SearchMcpService.scopes_for("search"),
vec!["search.read".to_string()]
);
}
#[test]
fn write_tool_scope_mapping() {
assert_eq!(
SearchMcpService.scopes_for("index_file"),
vec!["search.write".to_string()]
);
}
#[test]
fn trait_object_dispatch() {
let svc: Box<dyn ServiceDescriptor> = Box::new(SearchMcpService);
assert_eq!(svc.name(), "trusty-search");
assert_eq!(svc.tools().len(), 15);
}
}