#![allow(dead_code, unused_imports, unused_variables)]
use anyhow::{Context, Result};
use serde_json::Value;
use std::sync::Arc;
use tracing::{debug, info, warn};
use super::transport::Transport;
use super::McpServerConfig;
const MCP_PROTOCOL_VERSION: &str = "2024-11-05";
const CLIENT_NAME: &str = "selfware";
const CLIENT_VERSION: &str = env!("CARGO_PKG_VERSION");
pub struct McpClient {
transport: Arc<dyn Transport>,
server_name: String,
server_info: Option<Value>,
}
impl McpClient {
pub async fn connect(config: &McpServerConfig) -> Result<Self> {
let transport = super::StdioTransport::spawn(&config.command, &config.args, &config.env)
.await
.with_context(|| format!("Failed to spawn MCP server '{}'", config.name))?;
let transport: Arc<dyn Transport> = Arc::new(transport);
let mut client = Self {
transport,
server_name: config.name.clone(),
server_info: None,
};
tokio::time::timeout(
std::time::Duration::from_secs(config.init_timeout_secs.max(5)),
client.initialize(),
)
.await
.map_err(|_| {
anyhow::anyhow!(
"MCP server '{}' initialization timed out after {}s",
config.name,
config.init_timeout_secs
)
})??;
info!("MCP server '{}' initialized successfully", config.name);
Ok(client)
}
async fn initialize(&mut self) -> Result<()> {
let params = serde_json::json!({
"protocolVersion": MCP_PROTOCOL_VERSION,
"capabilities": {
"roots": { "listChanged": false },
},
"clientInfo": {
"name": CLIENT_NAME,
"version": CLIENT_VERSION,
}
});
let result = self
.transport
.request("initialize", Some(params))
.await
.with_context(|| {
format!("MCP initialize handshake failed for '{}'", self.server_name)
})?;
self.server_info = Some(result.clone());
self.transport
.notify("notifications/initialized", None)
.await?;
let server_name = result
.get("serverInfo")
.and_then(|i| i.get("name"))
.and_then(|n| n.as_str())
.unwrap_or("unknown");
let protocol_version = result
.get("protocolVersion")
.and_then(|v| v.as_str())
.unwrap_or("unknown");
info!(
"MCP server '{}' (protocol: {})",
server_name, protocol_version
);
Ok(())
}
pub async fn list_tools(&self) -> Result<Vec<Value>> {
let result = self.transport.request("tools/list", None).await?;
let tools = result
.get("tools")
.and_then(|t| t.as_array())
.cloned()
.unwrap_or_default();
debug!(
"MCP server '{}' offers {} tool(s)",
self.server_name,
tools.len()
);
Ok(tools)
}
pub async fn call_tool(&self, name: &str, arguments: Value) -> Result<Value> {
let params = serde_json::json!({
"name": name,
"arguments": arguments,
});
let result = self
.transport
.request("tools/call", Some(params))
.await
.with_context(|| {
format!(
"MCP tool call '{}' failed on server '{}'",
name, self.server_name
)
})?;
if let Some(content) = result.get("content").and_then(|c| c.as_array()) {
let text_parts: Vec<&str> = content
.iter()
.filter_map(|block| {
if block.get("type").and_then(|t| t.as_str()) == Some("text") {
block.get("text").and_then(|t| t.as_str())
} else {
None
}
})
.collect();
if !text_parts.is_empty() {
return Ok(serde_json::json!({
"content": text_parts.join("\n"),
"isError": result.get("isError").and_then(|e| e.as_bool()).unwrap_or(false),
}));
}
}
Ok(result)
}
pub fn server_name(&self) -> &str {
&self.server_name
}
pub async fn shutdown(&self) -> Result<()> {
info!("Shutting down MCP client for '{}'", self.server_name);
self.transport.shutdown().await
}
}
impl std::fmt::Debug for McpClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("McpClient")
.field("server_name", &self.server_name)
.finish()
}
}