use async_trait::async_trait;
use mofa_kernel::agent::components::mcp::McpToolInfo;
use mofa_kernel::agent::components::tool::{Tool, ToolInput, ToolMetadata, ToolResult};
use mofa_kernel::agent::context::AgentContext;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use super::McpClientManager;
pub struct McpToolAdapter {
server_name: String,
tool_info: McpToolInfo,
client: Arc<RwLock<McpClientManager>>,
}
impl McpToolAdapter {
pub fn new(
server_name: impl Into<String>,
tool_info: McpToolInfo,
client: Arc<RwLock<McpClientManager>>,
) -> Self {
Self {
server_name: server_name.into(),
tool_info,
client,
}
}
pub fn server_name(&self) -> &str {
&self.server_name
}
}
#[async_trait]
impl Tool for McpToolAdapter {
fn name(&self) -> &str {
&self.tool_info.name
}
fn description(&self) -> &str {
&self.tool_info.description
}
fn parameters_schema(&self) -> serde_json::Value {
self.tool_info.input_schema.clone()
}
async fn execute(&self, input: ToolInput, _ctx: &AgentContext) -> ToolResult {
use mofa_kernel::agent::components::mcp::McpClient;
let client = self.client.read().await;
match client
.call_tool(&self.server_name, &self.tool_info.name, input.arguments)
.await
{
Ok(output) => ToolResult::success(output),
Err(e) => ToolResult::failure(format!("MCP tool call failed: {}", e)),
}
}
fn metadata(&self) -> ToolMetadata {
let mut custom = HashMap::new();
custom.insert(
"mcp_server".to_string(),
serde_json::Value::String(self.server_name.clone()),
);
ToolMetadata {
category: Some("mcp".to_string()),
tags: vec!["mcp".to_string(), self.server_name.clone()],
is_dangerous: false,
requires_network: true,
requires_filesystem: false,
custom,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use mofa_kernel::agent::components::mcp::McpToolInfo;
#[test]
fn test_mcp_tool_adapter_metadata() {
let tool_info = McpToolInfo {
name: "list_repos".to_string(),
description: "List GitHub repositories".to_string(),
input_schema: serde_json::json!({
"type": "object",
"properties": {
"owner": { "type": "string" }
}
}),
};
let client = Arc::new(RwLock::new(McpClientManager::new()));
let adapter = McpToolAdapter::new("github", tool_info, client);
assert_eq!(adapter.name(), "list_repos");
assert_eq!(adapter.description(), "List GitHub repositories");
assert_eq!(adapter.server_name(), "github");
let metadata = adapter.metadata();
assert_eq!(metadata.category, Some("mcp".to_string()));
assert!(metadata.requires_network);
assert!(metadata.tags.contains(&"mcp".to_string()));
assert!(metadata.tags.contains(&"github".to_string()));
}
#[test]
fn test_mcp_tool_adapter_schema() {
let schema = serde_json::json!({
"type": "object",
"properties": {
"query": { "type": "string" }
},
"required": ["query"]
});
let tool_info = McpToolInfo {
name: "search".to_string(),
description: "Search for items".to_string(),
input_schema: schema.clone(),
};
let client = Arc::new(RwLock::new(McpClientManager::new()));
let adapter = McpToolAdapter::new("search-server", tool_info, client);
assert_eq!(adapter.parameters_schema(), schema);
}
}