use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum BridgeError {
#[error("Minimal MCP error: {0}")]
Minimal(#[from] crate::mcp::minimal::MinimalMcpError),
#[error("Protocol error: {0}")]
Protocol(String),
#[error("Bridge error: {0}")]
Bridge(String),
}
pub type BridgeResult<T> = Result<T, BridgeError>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BridgeTool {
pub name: String,
pub description: Option<String>,
pub input_schema: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BridgeToolCall {
pub name: String,
pub arguments: Option<HashMap<String, Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BridgeToolResult {
pub content: Vec<BridgeContent>,
pub is_error: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum BridgeContent {
#[serde(rename = "text")]
Text { text: String },
#[serde(rename = "image")]
Image { data: String, mime_type: String },
#[serde(rename = "resource")]
Resource { resource: BridgeResource },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BridgeResource {
pub uri: String,
pub name: Option<String>,
pub description: Option<String>,
pub mime_type: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BridgeServerInfo {
pub name: String,
pub version: String,
pub protocol_version: String,
pub capabilities: BridgeServerCapabilities,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BridgeServerCapabilities {
pub tools: Option<BridgeToolsCapability>,
pub resources: Option<BridgeResourcesCapability>,
pub prompts: Option<BridgePromptsCapability>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BridgeToolsCapability {
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BridgeResourcesCapability {
pub subscribe: Option<bool>,
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BridgePromptsCapability {
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone)]
pub enum McpBackend {
Minimal,
#[cfg(feature = "mcp-full-protocol")]
Full,
}
pub struct BridgeClient {
backend: McpBackend,
minimal_client: Option<crate::mcp::minimal::MinimalMcpClient>,
#[cfg(feature = "mcp-full-protocol")]
full_client: Option<rmcp::service::RunningService<rmcp::service::RoleClient, rmcp::model::ClientInfo>>,
}
impl BridgeClient {
pub fn new() -> Self {
let backend = Self::select_backend();
Self {
backend,
minimal_client: Some(crate::mcp::minimal::MinimalMcpClient::new()),
#[cfg(feature = "mcp-full-protocol")]
full_client: None,
}
}
pub fn with_backend(backend: McpBackend) -> Self {
Self {
backend,
minimal_client: Some(crate::mcp::minimal::MinimalMcpClient::new()),
#[cfg(feature = "mcp-full-protocol")]
full_client: None,
}
}
fn select_backend() -> McpBackend {
#[cfg(feature = "mcp-full-protocol")]
{
McpBackend::Full
}
#[cfg(not(feature = "mcp-full-protocol"))]
{
McpBackend::Minimal
}
}
pub async fn connect(&mut self, command: &str, args: &[String], env: HashMap<String, String>) -> BridgeResult<BridgeServerInfo> {
match self.backend {
McpBackend::Minimal => {
if let Some(client) = &mut self.minimal_client {
client.connect(command, args, env).await?;
Ok(BridgeServerInfo {
name: "MCP Server".to_string(),
version: "1.0.0".to_string(),
protocol_version: "2024-11-05".to_string(),
capabilities: BridgeServerCapabilities {
tools: Some(BridgeToolsCapability { list_changed: Some(true) }),
resources: Some(BridgeResourcesCapability { subscribe: Some(false), list_changed: Some(true) }),
prompts: Some(BridgePromptsCapability { list_changed: Some(true) }),
},
})
} else {
Err(BridgeError::Bridge("Minimal client not initialized".to_string()))
}
}
#[cfg(feature = "mcp-full-protocol")]
McpBackend::Full => {
use rmcp::{ServiceExt, transport::TokioChildProcess};
let mut cmd = tokio::process::Command::new(command);
cmd.args(args);
for (key, value) in env {
cmd.env(key, value);
}
let transport = TokioChildProcess::new(cmd)
.map_err(|e| BridgeError::Protocol(format!("Failed to create transport: {}", e)))?;
let client_info = rmcp::model::ClientInfo::default();
let service = client_info.serve(transport).await
.map_err(|e| BridgeError::Protocol(format!("Failed to start service: {}", e)))?;
self.full_client = Some(service);
Ok(BridgeServerInfo {
name: "MCP Server (Full Protocol)".to_string(),
version: "1.0.0".to_string(),
protocol_version: "2024-11-05".to_string(),
capabilities: BridgeServerCapabilities {
tools: Some(BridgeToolsCapability { list_changed: Some(true) }),
resources: Some(BridgeResourcesCapability { subscribe: Some(true), list_changed: Some(true) }),
prompts: Some(BridgePromptsCapability { list_changed: Some(true) }),
},
})
}
}
}
pub async fn list_tools(&mut self) -> BridgeResult<Vec<BridgeTool>> {
match self.backend {
McpBackend::Minimal => {
if let Some(client) = &mut self.minimal_client {
let tools = client.list_tools().await?;
let bridge_tools = tools.into_iter().map(|tool| {
BridgeTool {
name: tool.get("name").and_then(|v| v.as_str()).unwrap_or("Unknown").to_string(),
description: tool.get("description").and_then(|v| v.as_str()).map(|s| s.to_string()),
input_schema: tool.get("inputSchema").cloned().unwrap_or(Value::Object(Default::default())),
}
}).collect();
Ok(bridge_tools)
} else {
Err(BridgeError::Bridge("Minimal client not initialized".to_string()))
}
}
#[cfg(feature = "mcp-full-protocol")]
McpBackend::Full => {
if let Some(service) = &mut self.full_client {
let rmcp_tools = service.list_all_tools().await
.map_err(|e| BridgeError::Protocol(format!("Failed to list tools: {}", e)))?;
let bridge_tools = rmcp_tools.into_iter().map(|tool| {
BridgeTool {
name: tool.name.to_string(),
description: tool.description.map(|s| s.to_string()),
input_schema: serde_json::Value::Object((*tool.input_schema).clone()),
}
}).collect();
Ok(bridge_tools)
} else {
Err(BridgeError::Bridge("Full client not connected".to_string()))
}
}
}
}
pub async fn call_tool(&mut self, request: BridgeToolCall) -> BridgeResult<BridgeToolResult> {
match self.backend {
McpBackend::Minimal => {
if let Some(client) = &mut self.minimal_client {
let result = client.call_tool(&request.name, request.arguments.map(|args| serde_json::to_value(args).unwrap_or(Value::Null))).await?;
let content = if let Some(content_array) = result.get("content").and_then(|v| v.as_array()) {
content_array.iter().filter_map(|item| {
if let Some(text) = item.get("text").and_then(|v| v.as_str()) {
Some(BridgeContent::Text { text: text.to_string() })
} else {
None
}
}).collect()
} else {
vec![BridgeContent::Text { text: result.to_string() }]
};
Ok(BridgeToolResult {
content,
is_error: result.get("isError").and_then(|v| v.as_bool()).unwrap_or(false),
})
} else {
Err(BridgeError::Bridge("Minimal client not initialized".to_string()))
}
}
#[cfg(feature = "mcp-full-protocol")]
McpBackend::Full => {
if let Some(service) = &mut self.full_client {
let rmcp_request = rmcp::model::CallToolRequestParam {
name: request.name.clone().into(),
arguments: request.arguments.map(|args| {
args.into_iter().collect::<serde_json::Map<String, serde_json::Value>>()
}),
};
let rmcp_result = service.call_tool(rmcp_request).await
.map_err(|e| BridgeError::Protocol(format!("Failed to call tool: {}", e)))?;
let bridge_content = rmcp_result.content.into_iter().map(|content| {
match content.raw {
rmcp::model::RawContent::Text(text_content) => {
BridgeContent::Text { text: text_content.text }
}
rmcp::model::RawContent::Image(image_content) => {
BridgeContent::Image {
data: image_content.data,
mime_type: image_content.mime_type
}
}
rmcp::model::RawContent::Resource(resource_content) => {
BridgeContent::Resource {
resource: BridgeResource {
uri: match &resource_content.resource {
rmcp::model::ResourceContents::TextResourceContents { uri, .. } => uri.clone(),
rmcp::model::ResourceContents::BlobResourceContents { uri, .. } => uri.clone(),
},
name: None,
description: None,
mime_type: match &resource_content.resource {
rmcp::model::ResourceContents::TextResourceContents { mime_type, .. } => mime_type.clone(),
rmcp::model::ResourceContents::BlobResourceContents { mime_type, .. } => mime_type.clone(),
},
}
}
}
rmcp::model::RawContent::Audio(audio_content) => {
BridgeContent::Image {
data: audio_content.data.clone(),
mime_type: audio_content.mime_type.clone()
}
}
}
}).collect();
Ok(BridgeToolResult {
content: bridge_content,
is_error: rmcp_result.is_error.unwrap_or(false),
})
} else {
Err(BridgeError::Bridge("Full client not connected".to_string()))
}
}
}
}
pub async fn disconnect(&mut self) -> BridgeResult<()> {
match self.backend {
McpBackend::Minimal => {
if let Some(client) = &mut self.minimal_client {
client.disconnect().await?;
Ok(())
} else {
Err(BridgeError::Bridge("Minimal client not initialized".to_string()))
}
}
#[cfg(feature = "mcp-full-protocol")]
McpBackend::Full => {
if let Some(service) = self.full_client.take() {
service.cancel().await
.map_err(|e| BridgeError::Protocol(format!("Failed to cancel service: {}", e)))?;
Ok(())
} else {
Ok(())
}
}
}
}
pub fn backend(&self) -> &McpBackend {
&self.backend
}
}
impl Default for BridgeClient {
fn default() -> Self {
Self::new()
}
}