use crate::config::load_default_mcp_config;
use crate::{McpHttpClient, McpStdioClient};
use async_trait::async_trait;
use enact_core::tool::{DynTool, Tool};
use serde_json::json;
use std::sync::Arc;
use tokio::sync::Mutex;
enum McpClient {
Stdio(Box<McpStdioClient>),
Http(McpHttpClient),
}
pub struct McpToolAdapter {
name: String,
description: String,
parameters: serde_json::Value,
tool_name: String,
client: Arc<Mutex<McpClient>>,
}
impl McpToolAdapter {
fn new(
server_name: &str,
tool_name: String,
description: String,
parameters: serde_json::Value,
client: Arc<Mutex<McpClient>>,
) -> Self {
Self {
name: format!("mcp__{}__{}", server_name, tool_name),
description,
parameters,
tool_name,
client,
}
}
}
#[async_trait]
impl Tool for McpToolAdapter {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
&self.description
}
fn parameters_schema(&self) -> serde_json::Value {
self.parameters.clone()
}
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<serde_json::Value> {
let mut client = self.client.lock().await;
let output = match &mut *client {
McpClient::Stdio(c) => c.call_tool(&self.tool_name, args).await?,
McpClient::Http(c) => c.call_tool(&self.tool_name, args).await?,
};
Ok(json!({ "success": true, "output": output }))
}
}
pub async fn discover_mcp_tools() -> anyhow::Result<Vec<DynTool>> {
let config = load_default_mcp_config()?;
let mut tools = Vec::new();
for server in config.servers {
let transport = server.transport.to_lowercase();
let (discovered, shared): (Vec<crate::McpTool>, Arc<Mutex<McpClient>>) = if transport
== "http"
{
let Some(url) = server.url.clone() else {
tracing::warn!(
"MCP server '{}' missing url for HTTP transport",
server.name
);
continue;
};
let client = McpHttpClient::new(url);
let discovered = match client.list_tools().await {
Ok(list) => list,
Err(e) => {
tracing::warn!("Failed to list MCP tools for '{}': {}", server.name, e);
continue;
}
};
(discovered, Arc::new(Mutex::new(McpClient::Http(client))))
} else {
let args: Vec<&str> = server.args.iter().map(|s| s.as_str()).collect();
let mut client = match McpStdioClient::new(&server.command, &args, &server.env).await {
Ok(client) => client,
Err(e) => {
tracing::warn!("Failed to connect MCP server '{}': {}", server.name, e);
continue;
}
};
let discovered = match client.list_tools().await {
Ok(list) => list,
Err(e) => {
tracing::warn!("Failed to list MCP tools for '{}': {}", server.name, e);
continue;
}
};
(
discovered,
Arc::new(Mutex::new(McpClient::Stdio(Box::new(client)))),
)
};
for tool in discovered {
tools.push(Arc::new(McpToolAdapter::new(
&server.name,
tool.name,
tool.description,
tool.parameters,
Arc::clone(&shared),
)) as DynTool);
}
}
Ok(tools)
}