use crate::error::{Error, Result};
use crate::transport::Transport;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::sync::Arc;
use tracing;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tool {
pub name: String,
pub description: String,
#[serde(rename = "inputSchema")]
pub input_schema: Option<Value>,
#[serde(rename = "outputSchema")]
pub output_schema: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Resource {
pub uri: String,
pub name: String,
pub description: Option<String>,
#[serde(rename = "type")]
pub resource_type: Option<String>,
}
#[derive(Clone)]
pub struct McpClient {
name: String,
transport: Arc<dyn Transport>,
}
impl std::fmt::Debug for McpClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("McpClient")
.field("name", &self.name)
.field("transport", &"<dyn Transport>")
.finish()
}
}
impl McpClient {
#[tracing::instrument(skip(transport), fields(client_name = %name))]
pub fn new(name: String, transport: impl Transport + 'static) -> Self {
tracing::info!("Creating new McpClient");
Self {
name,
transport: Arc::new(transport),
}
}
#[tracing::instrument(skip(config), fields(server = %server_name))]
pub fn connect(server_name: &str, config: &crate::Config) -> Result<Self> {
tracing::info!("Connecting to server {}", server_name);
let server_config = config.mcp_servers.get(server_name).ok_or_else(|| {
tracing::error!("Server '{}' not found in configuration", server_name);
Error::ServerNotFound(server_name.to_string())
})?;
let transport = crate::transport::create_transport_for_config(server_name, server_config)?;
Ok(Self::new(server_name.to_string(), transport))
}
pub fn name(&self) -> &str {
&self.name
}
#[tracing::instrument(skip(self), fields(client_name = %self.name))]
pub async fn initialize(&self) -> Result<()> {
tracing::info!("Initializing client connection");
self.transport.initialize().await.map_err(|e| {
tracing::error!(error = %e, "Failed to initialize transport");
e
})
}
#[tracing::instrument(skip(self), fields(client_name = %self.name))]
pub async fn list_tools(&self) -> Result<Vec<Tool>> {
tracing::debug!("Listing tools via transport");
let tools_json = self.transport.list_tools().await?;
tracing::trace!(raw_tools = ?tools_json, "Received raw tools list");
let mut tools = Vec::new();
for tool_value in tools_json {
match serde_json::from_value::<Tool>(tool_value.clone()) {
Ok(tool) => {
tracing::trace!(tool_name = %tool.name, "Successfully deserialized tool");
tools.push(tool);
}
Err(e) => {
tracing::warn!(error = %e, value = ?tool_value, "Failed to deserialize tool from value");
}
}
}
tracing::debug!(num_tools = tools.len(), "Finished listing tools");
Ok(tools)
}
#[tracing::instrument(skip(self, args), fields(client_name = %self.name, tool_name = %name))]
pub async fn call_tool<T, R>(&self, name: &str, args: &T) -> Result<R>
where
T: Serialize + std::fmt::Debug,
R: for<'de> Deserialize<'de>,
{
tracing::debug!(args = ?args, "Calling tool");
let args_value = serde_json::to_value(args).map_err(|e| {
tracing::error!(error = %e, "Failed to serialize tool arguments");
Error::Serialization(format!("Failed to serialize tool arguments: {}", e))
})?;
tracing::trace!(args_json = ?args_value, "Serialized arguments");
let result_value = self.transport.call_tool(name, args_value).await?;
tracing::trace!(result_json = ?result_value, "Received raw tool result");
serde_json::from_value(result_value.clone()).map_err(|e| {
tracing::error!(error = %e, value = ?result_value, "Failed to deserialize tool result");
Error::Serialization(format!("Failed to deserialize tool result: {}", e))
})
}
#[tracing::instrument(skip(self), fields(client_name = %self.name))]
pub async fn list_resources(&self) -> Result<Vec<Resource>> {
tracing::debug!("Listing resources via transport");
let resources_json = self.transport.list_resources().await?;
tracing::trace!(raw_resources = ?resources_json, "Received raw resources list");
let mut resources = Vec::new();
for resource_value in resources_json {
match serde_json::from_value::<Resource>(resource_value.clone()) {
Ok(resource) => {
tracing::trace!(resource_uri = %resource.uri, "Successfully deserialized resource");
resources.push(resource);
}
Err(e) => {
tracing::warn!(error = %e, value = ?resource_value, "Failed to deserialize resource from value");
}
}
}
tracing::debug!(
num_resources = resources.len(),
"Finished listing resources"
);
Ok(resources)
}
#[tracing::instrument(skip(self), fields(client_name = %self.name, resource_uri = %uri))]
pub async fn get_resource<R>(&self, uri: &str) -> Result<R>
where
R: for<'de> Deserialize<'de>,
{
tracing::debug!("Getting resource via transport");
let resource_value = self.transport.get_resource(uri).await?;
tracing::trace!(raw_resource = ?resource_value, "Received raw resource value");
serde_json::from_value(resource_value.clone()).map_err(|e| {
tracing::error!(error = %e, value = ?resource_value, "Failed to deserialize resource");
Error::Serialization(format!("Failed to deserialize resource: {}", e))
})
}
#[tracing::instrument(skip(self), fields(client_name = %self.name))]
pub async fn close(&self) -> Result<()> {
tracing::info!(
"Close called on McpClient (Note: Transport closure depends on Arc references)"
);
Ok(())
}
}