use serde_json::Value;
use trusty_common::mcp::openrpc::discover_response;
use crate::mcp::tools::tool_descriptors;
mod scopes {
pub const SEARCH_READ: &str = "search.read";
pub const SEARCH_WRITE: &str = "search.write";
}
pub fn scopes_for_tool(name: &str) -> Vec<String> {
use scopes::*;
let s: &[&str] = match name {
"search_all" | "search_code" | "search_similar" | "search_health" | "list_indexes"
| "index_status" | "list_chunks" | "chat" => &[SEARCH_READ],
"index_file" | "remove_file" | "create_index" | "delete_index" | "reindex" => {
&[SEARCH_WRITE]
}
_ => &[],
};
s.iter().map(|x| (*x).to_string()).collect()
}
pub fn build_discover_response(version: &str) -> Value {
let defs = tool_descriptors();
let empty: Vec<Value> = Vec::new();
let tools: &[Value] = defs.as_array().map(|a| a.as_slice()).unwrap_or(&empty);
discover_response("trusty-search-mcp", version, tools, scopes_for_tool)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_tool_has_scopes() {
let defs = tool_descriptors();
let tools = defs.as_array().expect("tool_descriptors returns array");
for tool in tools {
let name = tool["name"].as_str().unwrap();
let scopes = scopes_for_tool(name);
assert!(
!scopes.is_empty(),
"tool {name} must declare at least one scope"
);
}
}
#[test]
fn read_write_classification() {
assert_eq!(scopes_for_tool("search_code"), vec!["search.read"]);
assert_eq!(scopes_for_tool("search_all"), vec!["search.read"]);
assert_eq!(scopes_for_tool("index_file"), vec!["search.write"]);
assert_eq!(scopes_for_tool("delete_index"), vec!["search.write"]);
assert_eq!(scopes_for_tool("list_indexes"), vec!["search.read"]);
assert_eq!(scopes_for_tool("chat"), vec!["search.read"]);
}
#[test]
fn discover_response_lists_all_tools() {
let doc = build_discover_response("9.9.9");
assert_eq!(doc["openrpc"], "1.3.2");
assert_eq!(doc["info"]["title"], "trusty-search-mcp");
assert_eq!(doc["info"]["version"], "9.9.9");
let methods = doc["methods"].as_array().expect("methods array");
let defs = tool_descriptors();
let tool_count = defs.as_array().unwrap().len();
assert_eq!(methods.len(), tool_count);
}
#[test]
fn discover_response_x_scopes_present() {
let doc = build_discover_response("0.1.0");
let methods = doc["methods"].as_array().unwrap();
for m in methods {
let scopes = m["x-scopes"].as_array().expect("x-scopes array");
assert!(
!scopes.is_empty(),
"method {} must carry at least one scope",
m["name"]
);
}
}
}