use async_trait::async_trait;
use serde::Deserialize;
use serde_json::{Value, json};
use crate::mcp::{Tool, ToolContext, ToolResult};
#[derive(Debug, Default)]
pub struct SkillTool;
impl SkillTool {
pub fn new() -> Self {
Self
}
}
#[derive(Debug, Deserialize)]
struct SkillInput {
skill: String,
#[serde(default)]
args: String,
}
#[async_trait]
impl Tool for SkillTool {
fn name(&self) -> &str {
"Skill"
}
fn description(&self) -> &str {
"Execute a user-invocable skill"
}
fn input_schema(&self) -> Value {
json!({
"type": "object",
"title": "skill",
"description": "Execute a user-invocable skill",
"properties": {
"skill": {
"type": "string",
"description": "The skill name to invoke (e.g., 'commit', 'pdf')"
},
"args": {
"type": "string",
"description": "Optional arguments for the skill"
}
},
"required": ["skill"]
})
}
async fn execute(&self, input: Value, _context: &ToolContext) -> ToolResult {
let params: SkillInput = match serde_json::from_value(input) {
Ok(p) => p,
Err(e) => return ToolResult::error(format!("Invalid input: {}", e)),
};
ToolResult::success(format!("Skill: '{}'", params.skill)).with_metadata(json!({
"skill": params.skill,
"args": params.args
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_skill_tool_properties() {
let tool = SkillTool;
assert_eq!(tool.name(), "Skill");
assert!(tool.description().contains("skill"));
}
#[test]
fn test_skill_input_schema() {
let tool = SkillTool;
let schema = tool.input_schema();
assert_eq!(schema["type"], "object");
assert_eq!(schema["title"], "skill");
assert!(schema["properties"]["skill"].is_object());
assert!(
schema["required"]
.as_array()
.unwrap()
.contains(&json!("skill"))
);
}
}