use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::collections::HashMap;
use crate::integration::HostIntegration;
use super::{Tool, ToolResponse, ToolError, Permission};
#[derive(Debug, Clone)]
pub struct Parameters<T>(pub T);
impl<T> Parameters<T>
where
T: for<'de> Deserialize<'de>,
{
pub fn from_json(value: JsonValue) -> Result<Self, ToolError> {
let params: T = serde_json::from_value(value)
.map_err(|e| ToolError::InvalidParameters(format!("Parameter validation failed: {}", e)))?;
Ok(Parameters(params))
}
}
#[derive(Debug, Clone, Serialize)]
pub struct CallToolResult {
pub content: Vec<Content>,
pub is_error: bool,
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type")]
pub enum Content {
#[serde(rename = "text")]
Text { text: String },
#[serde(rename = "image")]
Image { data: String, mime_type: String },
#[serde(rename = "resource")]
Resource { resource: ResourceReference },
}
#[derive(Debug, Clone, Serialize)]
pub struct ResourceReference {
pub uri: String,
pub text: Option<String>,
}
impl CallToolResult {
pub fn success(content: Vec<Content>) -> Self {
Self {
content,
is_error: false,
}
}
pub fn error(message: String) -> Self {
Self {
content: vec![Content::text(message)],
is_error: true,
}
}
}
impl Content {
pub fn text(text: String) -> Self {
Self::Text { text }
}
pub fn image(data: String, mime_type: String) -> Self {
Self::Image { data, mime_type }
}
pub fn resource(uri: String, text: Option<String>) -> Self {
Self::Resource {
resource: ResourceReference { uri, text },
}
}
}
#[async_trait]
pub trait EnhancedTool: Send + Sync {
async fn execute_enhanced(
&self,
parameters: JsonValue,
host: &dyn HostIntegration,
) -> Result<CallToolResult, ToolError>;
fn name(&self) -> &str;
fn description(&self) -> &str;
fn parameter_schema(&self) -> JsonValue;
fn requires_permission(&self) -> Permission;
fn clone_enhanced(&self) -> Box<dyn EnhancedTool>;
}
pub struct ToolRouter {
tools: HashMap<String, Box<dyn EnhancedTool>>,
}
impl ToolRouter {
pub fn new() -> Self {
Self {
tools: HashMap::new(),
}
}
pub fn register<T: EnhancedTool + 'static>(&mut self, tool: T) {
self.tools.insert(tool.name().to_string(), Box::new(tool));
}
pub fn get(&self, name: &str) -> Option<&dyn EnhancedTool> {
self.tools.get(name).map(|t| t.as_ref())
}
pub fn list_tools(&self) -> Vec<&str> {
self.tools.keys().map(|s| s.as_str()).collect()
}
pub fn get_tool_schemas(&self) -> HashMap<String, ToolSchema> {
self.tools
.iter()
.map(|(name, tool)| {
let schema = ToolSchema {
name: name.clone(),
description: tool.description().to_string(),
input_schema: tool.parameter_schema(),
};
(name.clone(), schema)
})
.collect()
}
pub async fn execute_tool(
&self,
name: &str,
parameters: JsonValue,
host: &dyn HostIntegration,
) -> Result<CallToolResult, ToolError> {
let tool = self.get(name)
.ok_or_else(|| ToolError::NotFound(name.to_string()))?;
tool.execute_enhanced(parameters, host).await
}
}
#[derive(Debug, Clone, Serialize)]
pub struct ToolSchema {
pub name: String,
pub description: String,
#[serde(rename = "inputSchema")]
pub input_schema: JsonValue,
}
pub struct EnhancedToolAdapter {
enhanced_tool: Box<dyn EnhancedTool>,
}
impl EnhancedToolAdapter {
pub fn new<T: EnhancedTool + 'static>(tool: T) -> Self {
Self {
enhanced_tool: Box::new(tool),
}
}
}
#[async_trait]
impl Tool for EnhancedToolAdapter {
async fn execute(
&self,
parameters: JsonValue,
host: &dyn HostIntegration,
) -> Result<ToolResponse, ToolError> {
let result = self.enhanced_tool.execute_enhanced(parameters, host).await?;
let content = result.content
.into_iter()
.map(|c| match c {
Content::Text { text } => text,
Content::Image { data, mime_type } => format!("Image: {} ({})", data, mime_type),
Content::Resource { resource } => {
format!("Resource: {} ({})", resource.uri, resource.text.unwrap_or_default())
}
})
.collect::<Vec<_>>()
.join("\n");
Ok(ToolResponse {
content,
success: !result.is_error,
metadata: JsonValue::Null,
affected_files: Vec::new(),
})
}
fn requires_permission(&self) -> Permission {
self.enhanced_tool.requires_permission()
}
fn description(&self) -> &str {
self.enhanced_tool.description()
}
fn name(&self) -> &str {
self.enhanced_tool.name()
}
fn parameter_schema(&self) -> JsonValue {
self.enhanced_tool.parameter_schema()
}
fn clone_box(&self) -> Box<dyn Tool> {
Box::new(EnhancedToolAdapter {
enhanced_tool: self.enhanced_tool.clone_enhanced(),
})
}
}
impl Default for ToolRouter {
fn default() -> Self {
Self::new()
}
}
pub mod schema_utils {
use super::*;
#[cfg(feature = "mcp-server")]
use schemars::{JsonSchema, schema_for};
#[cfg(feature = "mcp-server")]
pub fn generate_schema<T: JsonSchema>() -> JsonValue {
let schema = schema_for!(T);
serde_json::to_value(schema).unwrap_or(JsonValue::Null)
}
#[cfg(not(feature = "mcp-server"))]
pub fn generate_schema<T>() -> JsonValue {
serde_json::json!({})
}
pub fn string_param(description: &str, required: bool) -> JsonValue {
serde_json::json!({
"type": "object",
"properties": {
"value": {
"type": "string",
"description": description
}
},
"required": if required { vec!["value"] } else { vec![] }
})
}
pub fn number_param(description: &str, required: bool) -> JsonValue {
serde_json::json!({
"type": "object",
"properties": {
"value": {
"type": "number",
"description": description
}
},
"required": if required { vec!["value"] } else { vec![] }
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "schemars")]
use schemars::JsonSchema;
#[derive(Deserialize)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
struct TestParams {
message: String,
count: Option<i32>,
}
struct TestTool;
#[async_trait]
impl EnhancedTool for TestTool {
async fn execute_enhanced(
&self,
parameters: JsonValue,
_host: &dyn HostIntegration,
) -> Result<CallToolResult, ToolError> {
let Parameters(params): Parameters<TestParams> = Parameters::from_json(parameters)?;
let response = format!("Message: {}, Count: {:?}", params.message, params.count);
Ok(CallToolResult::success(vec![Content::text(response)]))
}
fn name(&self) -> &str {
"test_tool"
}
fn description(&self) -> &str {
"A test tool for demonstration"
}
fn parameter_schema(&self) -> JsonValue {
schema_utils::generate_schema::<TestParams>()
}
fn requires_permission(&self) -> Permission {
Permission::None
}
fn clone_enhanced(&self) -> Box<dyn EnhancedTool> {
Box::new(TestTool)
}
}
#[tokio::test]
async fn test_enhanced_tool_router() {
let mut router = ToolRouter::new();
router.register(TestTool);
assert_eq!(router.list_tools(), vec!["test_tool"]);
assert!(router.get("test_tool").is_some());
assert!(router.get("nonexistent").is_none());
}
#[test]
fn test_parameters_extraction() {
let json = serde_json::json!({
"message": "hello",
"count": 42
});
let params: Parameters<TestParams> = Parameters::from_json(json).unwrap();
assert_eq!(params.0.message, "hello");
assert_eq!(params.0.count, Some(42));
}
#[test]
fn test_call_tool_result() {
let result = CallToolResult::success(vec![Content::text("success".to_string())]);
assert!(!result.is_error);
assert_eq!(result.content.len(), 1);
let error_result = CallToolResult::error("error message".to_string());
assert!(error_result.is_error);
}
}