use super::client::{McpClient, ToolDefinition};
use crate::tool::{Tool, ToolError, ToolResult};
use std::sync::Arc;
pub struct McpToolAdapter {
client: Arc<McpClient>,
definition: ToolDefinition,
original_name: String,
}
impl McpToolAdapter {
pub fn new(client: Arc<McpClient>, definition: ToolDefinition) -> Self {
let original_name = definition.name.clone();
Self {
client,
definition,
original_name,
}
}
pub fn new_with_namespace(
client: Arc<McpClient>,
definition: ToolDefinition,
namespace: &str,
) -> Self {
let original_name = definition.name.clone();
let namespaced_name = format!("{}{}", namespace, definition.name);
let mut namespaced_def = definition;
namespaced_def.name = namespaced_name;
Self {
client,
definition: namespaced_def,
original_name,
}
}
#[cfg(test)]
pub fn definition(&self) -> &ToolDefinition {
&self.definition
}
}
impl Tool for McpToolAdapter {
type Input = serde_json::Value;
fn name(&self) -> &str {
&self.definition.name
}
fn description(&self) -> &str {
&self.definition.description
}
async fn execute(&self, input: Self::Input) -> Result<ToolResult, ToolError> {
let result = self
.client
.call_tool(self.original_name.clone(), input)
.await
.map_err(|e| ToolError::Custom(format!("MCP tool error: {}", e)))?;
Ok(ToolResult::Json(result))
}
fn input_schema(&self) -> serde_json::Value {
self.definition.input_schema.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mcp::{McpServerConfig, McpTransport};
use std::collections::HashMap;
#[test]
fn test_adapter_metadata_forwarding() {
let config = McpServerConfig::new(
"test",
McpTransport::Stdio {
command: "echo".to_string(),
args: vec![],
env: HashMap::new(),
},
);
let client = Arc::new(McpClient::new(config).unwrap());
let schema = serde_json::json!({
"type": "object",
"properties": {
"path": {"type": "string"},
"content": {"type": "string"}
},
"required": ["path"]
});
let definition = ToolDefinition {
name: "write_file".to_string(),
description: "Write content to a file".to_string(),
input_schema: schema.clone(),
};
let adapter = McpToolAdapter::new(client, definition.clone());
assert_eq!(adapter.name(), "write_file");
assert_eq!(adapter.description(), "Write content to a file");
assert_eq!(adapter.input_schema(), schema);
assert_eq!(adapter.definition().name, "write_file");
}
#[test]
fn test_adapter_schema_preservation() {
let config = McpServerConfig::new(
"test",
McpTransport::Stdio {
command: "echo".to_string(),
args: vec![],
env: HashMap::new(),
},
);
let client = Arc::new(McpClient::new(config).unwrap());
let complex_schema = serde_json::json!({
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "User name"
},
"age": {
"type": "integer",
"minimum": 0,
"maximum": 150
},
"email": {
"type": "string",
"format": "email"
}
},
"required": ["name", "email"]
});
let definition = ToolDefinition {
name: "create_user".to_string(),
description: "Create a new user".to_string(),
input_schema: complex_schema.clone(),
};
let adapter = McpToolAdapter::new(client, definition);
assert_eq!(adapter.input_schema(), complex_schema);
}
#[test]
fn test_adapter_with_empty_description() {
let config = McpServerConfig::new(
"test",
McpTransport::Stdio {
command: "echo".to_string(),
args: vec![],
env: HashMap::new(),
},
);
let client = Arc::new(McpClient::new(config).unwrap());
let definition = ToolDefinition {
name: "test_tool".to_string(),
description: String::new(),
input_schema: serde_json::json!({}),
};
let adapter = McpToolAdapter::new(client, definition);
assert_eq!(adapter.name(), "test_tool");
assert_eq!(adapter.description(), "");
}
#[test]
fn test_multiple_adapters_same_client() {
let config = McpServerConfig::new(
"test",
McpTransport::Stdio {
command: "echo".to_string(),
args: vec![],
env: HashMap::new(),
},
);
let client = Arc::new(McpClient::new(config).unwrap());
let def1 = ToolDefinition {
name: "tool1".to_string(),
description: "First tool".to_string(),
input_schema: serde_json::json!({"type": "object"}),
};
let def2 = ToolDefinition {
name: "tool2".to_string(),
description: "Second tool".to_string(),
input_schema: serde_json::json!({"type": "string"}),
};
let adapter1 = McpToolAdapter::new(Arc::clone(&client), def1);
let adapter2 = McpToolAdapter::new(Arc::clone(&client), def2);
assert_eq!(adapter1.name(), "tool1");
assert_eq!(adapter2.name(), "tool2");
assert_ne!(adapter1.input_schema(), adapter2.input_schema());
}
}