use std::sync::Arc;
use async_trait::async_trait;
use serde_json::Value;
use crate::error::KovaError;
use crate::models::ToolResult;
use crate::tool::Tool;
use super::{McpClient, McpToolDefinition};
pub struct McpTool {
definition: McpToolDefinition,
client: Arc<McpClient>,
server_name: String,
qualified_name: String,
}
impl McpTool {
pub fn new(definition: McpToolDefinition, client: Arc<McpClient>, server_name: &str) -> Self {
let qualified_name = format!("{}__{}", server_name, definition.name);
Self {
definition,
client,
server_name: server_name.to_string(),
qualified_name,
}
}
pub fn bare_name(&self) -> &str {
&self.definition.name
}
pub fn server_name(&self) -> &str {
&self.server_name
}
}
#[async_trait]
impl Tool for McpTool {
fn name(&self) -> &str {
&self.qualified_name
}
fn description(&self) -> &str {
self.definition.description.as_deref().unwrap_or("")
}
fn parameters_schema(&self) -> Value {
self.definition
.input_schema
.clone()
.unwrap_or_else(|| serde_json::json!({"type": "object"}))
}
async fn execute(&self, args: Value) -> Result<ToolResult, KovaError> {
let (content, is_error) = self.client.tools_call(&self.definition.name, args).await?;
Ok(ToolResult { content, is_error })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mcp_tool_name_and_description() {
let def = McpToolDefinition {
name: "get_weather".to_string(),
description: Some("Get current weather for a city".to_string()),
input_schema: Some(serde_json::json!({
"type": "object",
"properties": {
"city": { "type": "string" }
}
})),
};
assert_eq!(def.name, "get_weather");
assert_eq!(
def.description.as_deref(),
Some("Get current weather for a city")
);
}
#[test]
fn mcp_tool_defaults_for_missing_fields() {
let def = McpToolDefinition {
name: "minimal".to_string(),
description: None,
input_schema: None,
};
assert_eq!(def.description.as_deref().unwrap_or(""), "");
let schema = def
.input_schema
.clone()
.unwrap_or_else(|| serde_json::json!({"type": "object"}));
assert_eq!(schema, serde_json::json!({"type": "object"}));
}
#[test]
fn mcp_tool_conversion_preserves_fields() {
let def = McpToolDefinition {
name: "search".to_string(),
description: Some("Search the web".to_string()),
input_schema: Some(serde_json::json!({
"type": "object",
"properties": {
"query": { "type": "string" }
},
"required": ["query"]
})),
};
assert_eq!(&def.name, "search");
assert_eq!(def.description.as_deref().unwrap_or(""), "Search the web");
assert_eq!(
def.input_schema.as_ref().unwrap()["properties"]["query"]["type"],
"string"
);
}
}