use rmcp::model::Tool;
use rmcp::ErrorData;
use serde_json::{Map, Value};
pub struct ToolOutput {
pub text: String,
pub original_tokens: usize,
pub saved_tokens: usize,
pub mode: Option<String>,
pub path: Option<String>,
}
impl ToolOutput {
pub fn simple(text: String) -> Self {
Self {
text,
original_tokens: 0,
saved_tokens: 0,
mode: None,
path: None,
}
}
pub fn with_savings(text: String, original: usize, saved: usize) -> Self {
Self {
text,
original_tokens: original,
saved_tokens: saved,
mode: None,
path: None,
}
}
}
pub trait McpTool: Send + Sync {
fn name(&self) -> &'static str;
fn tool_def(&self) -> Tool;
fn handle(&self, args: &Map<String, Value>, ctx: &ToolContext)
-> Result<ToolOutput, ErrorData>;
}
pub struct ToolContext {
pub project_root: String,
pub minimal: bool,
pub resolved_paths: std::collections::HashMap<String, String>,
}
impl ToolContext {
pub fn resolved_path(&self, arg: &str) -> Option<&str> {
self.resolved_paths.get(arg).map(String::as_str)
}
}
pub fn get_str(args: &Map<String, Value>, key: &str) -> Option<String> {
args.get(key).and_then(|v| v.as_str()).map(String::from)
}
pub fn get_int(args: &Map<String, Value>, key: &str) -> Option<i64> {
args.get(key).and_then(serde_json::Value::as_i64)
}
pub fn get_bool(args: &Map<String, Value>, key: &str) -> Option<bool> {
args.get(key).and_then(serde_json::Value::as_bool)
}
pub fn get_str_array(args: &Map<String, Value>, key: &str) -> Option<Vec<String>> {
args.get(key).and_then(|v| v.as_array()).map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
}