use serde_json::Value;
use super::{
types::{require_str, DispatchError},
McpServer,
};
pub(super) async fn dispatch_misc_tool(
server: &McpServer,
tool: &str,
args: &Value,
) -> Option<Result<Value, DispatchError>> {
match tool {
"search_health" => Some(server.get("/health").await),
"chat" => {
let index_id = match server.resolve_index_id(args) {
Some(v) => v,
None => {
return Some(Err(DispatchError::InvalidParams(
"missing required string field: index_id".into(),
)))
}
};
let message = args
.get("message")
.and_then(Value::as_str)
.or_else(|| args.get("question").and_then(Value::as_str));
let message = match message {
Some(m) => m,
None => {
return Some(Err(DispatchError::InvalidParams(
"missing required string field: message (or question)".into(),
)))
}
};
let mut body = serde_json::json!({
"index_id": index_id,
"message": message,
});
if let Some(history) = args.get("history") {
body["history"] = history.clone();
}
if let Some(model) = args.get("model").and_then(Value::as_str) {
body["model"] = Value::String(model.to_string());
}
if let Some(top_k) = args.get("top_k").and_then(Value::as_u64) {
body["top_k"] = Value::from(top_k);
}
if let Some(key) = args.get("api_key").and_then(Value::as_str) {
body["api_key"] = Value::String(key.to_string());
}
Some(server.post("/chat", &body).await)
}
"get_call_chain" => {
let index_id = match server.resolve_index_id(args) {
Some(v) => v,
None => {
return Some(Err(DispatchError::InvalidParams(
"missing required string field: index_id".into(),
)))
}
};
let entry_point = match require_str(args, "entry_point") {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let mut query: Vec<(&str, String)> = vec![("entry_point", entry_point.to_string())];
if let Some(dir) = args.get("direction").and_then(Value::as_str) {
query.push(("direction", dir.to_string()));
}
if let Some(d) = args.get("max_depth").and_then(Value::as_u64) {
query.push(("max_depth", d.to_string()));
}
if let Some(inc) = args.get("include_source").and_then(Value::as_bool) {
query.push(("include_source", inc.to_string()));
}
Some(
server
.get_text(&format!("/indexes/{index_id}/call_chain"), &query)
.await
.map(|text| serde_json::json!({ "text": text })),
)
}
"grep" => {
let pattern = match require_str(args, "pattern") {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let mut body = serde_json::json!({ "pattern": pattern });
if let Some(v) = args.get("case_insensitive").and_then(Value::as_bool) {
body["case_insensitive"] = Value::Bool(v);
}
if let Some(v) = args.get("context").and_then(Value::as_u64) {
body["context"] = Value::from(v);
}
if let Some(v) = args.get("context_before").and_then(Value::as_u64) {
body["context_before"] = Value::from(v);
}
if let Some(v) = args.get("context_after").and_then(Value::as_u64) {
body["context_after"] = Value::from(v);
}
if let Some(v) = args.get("glob").and_then(Value::as_str) {
body["glob"] = Value::String(v.to_string());
}
if let Some(v) = args.get("multiline").and_then(Value::as_bool) {
body["multiline"] = Value::Bool(v);
}
if let Some(v) = args.get("fixed_strings").and_then(Value::as_bool) {
body["fixed_strings"] = Value::Bool(v);
}
if let Some(v) = args.get("files_with_matches").and_then(Value::as_bool) {
body["files_with_matches"] = Value::Bool(v);
}
if let Some(v) = args.get("invert_match").and_then(Value::as_bool) {
body["invert_match"] = Value::Bool(v);
}
if let Some(v) = args.get("word_regexp").and_then(Value::as_bool) {
body["word_regexp"] = Value::Bool(v);
}
if let Some(v) = args
.get("max_results")
.or_else(|| args.get("max_count"))
.and_then(Value::as_u64)
{
body["max_results"] = Value::from(v);
}
match server.resolve_index_id(args) {
Some(id) => Some(server.post(&format!("/indexes/{id}/grep"), &body).await),
None => Some(server.post("/grep", &body).await),
}
}
"upgrade" => {
let check = args.get("check").and_then(Value::as_bool).unwrap_or(true);
let confirm = args
.get("confirm")
.and_then(Value::as_bool)
.unwrap_or(false);
let body = serde_json::json!({ "check": check, "confirm": confirm });
Some(server.post("/upgrade", &body).await)
}
"console_metrics" => Some(handle_console_metrics(server).await),
_ => None,
}
}
async fn handle_console_metrics(server: &McpServer) -> Result<Value, DispatchError> {
use trusty_common::console_metrics::{make_report, ServiceHealth};
let (status, index_count, warm_boot_degraded) = match server.get("/health").await {
Ok(health) => {
let idx = health.get("indexes").and_then(Value::as_u64).unwrap_or(0) as usize;
let degraded = health
.get("warmboot_summary")
.and_then(|s| s.get("warm_boot_degraded"))
.and_then(Value::as_bool)
.unwrap_or(false);
(ServiceHealth::Ok, idx, degraded)
}
Err(_) => (ServiceHealth::Error, 0usize, false),
};
let raw = server
.get("/indexes?details=true")
.await
.unwrap_or_default();
let indexes: Vec<Value> = raw
.get("indexes")
.and_then(Value::as_array)
.map(|a| {
a.iter()
.map(|e| {
serde_json::json!({
"id": e.get("id").cloned().unwrap_or(Value::Null),
"root_path": e.get("root_path").cloned().unwrap_or(Value::Null),
"size_bytes":e.get("size_bytes").cloned().unwrap_or(Value::Null),
})
})
.collect()
})
.unwrap_or_default();
let metrics = serde_json::json!({
"index_count": index_count,
"warm_boot_degraded": warm_boot_degraded,
"indexes": indexes,
});
let report = make_report(
"trusty-search",
"Trusty Search",
env!("CARGO_PKG_VERSION"),
status,
metrics,
1,
);
serde_json::to_value(&report).map_err(|e| DispatchError::Transport(e.to_string()))
}