coderlib 0.1.0

A Rust library for AI-powered code assistance and agentic system
Documentation
//! MCP transport abstraction layer

#[cfg(feature = "mcp")]
use rmcp::transport::{TokioChildProcess, SseClientTransport};
use std::collections::HashMap;
use tokio::process::Command;

use crate::mcp::{
    config::McpTransportConfig,
    error::{McpError, McpResult},
};

/// Transport abstraction for MCP connections
pub enum McpTransport {
    /// Standard I/O transport
    #[cfg(feature = "mcp")]
    Stdio(TokioChildProcess),
    /// Server-Sent Events transport
    #[cfg(feature = "mcp")]
    Sse(SseClientTransport<reqwest::Client>),
    /// Placeholder when MCP feature is disabled
    #[cfg(not(feature = "mcp"))]
    Disabled,
}

impl McpTransport {
    /// Create a new transport from configuration
    pub async fn from_config(
        config: &McpTransportConfig,
        env_vars: HashMap<String, String>,
        working_dir: Option<&std::path::Path>,
    ) -> McpResult<Self> {
        match config {
            McpTransportConfig::Stdio { command, args } => {
                Self::create_stdio_transport(command, args, env_vars, working_dir).await
            }
            McpTransportConfig::Sse { url, headers } => {
                Self::create_sse_transport(url, headers).await
            }
        }
    }
    
    /// Create a stdio transport
    #[cfg(feature = "mcp")]
    async fn create_stdio_transport(
        command: &str,
        args: &[String],
        env_vars: HashMap<String, String>,
        working_dir: Option<&std::path::Path>,
    ) -> McpResult<Self> {
        let mut cmd = Command::new(command);
        
        // Add arguments
        for arg in args {
            cmd.arg(arg);
        }
        
        // Set working directory
        if let Some(dir) = working_dir {
            cmd.current_dir(dir);
        }
        
        // Set environment variables
        for (key, value) in env_vars {
            cmd.env(key, value);
        }
        
        let transport = TokioChildProcess::new(cmd)
            .map_err(|e| McpError::transport(format!("Failed to create child process: {}", e)))?;
        
        Ok(McpTransport::Stdio(transport))
    }
    
    #[cfg(not(feature = "mcp"))]
    async fn create_stdio_transport(
        _command: &str,
        _args: &[String],
        _env_vars: HashMap<String, String>,
        _working_dir: Option<&std::path::Path>,
    ) -> McpResult<Self> {
        Err(McpError::configuration("MCP feature not enabled"))
    }
    
    /// Create an SSE transport
    #[cfg(feature = "mcp")]
    async fn create_sse_transport(
        url: &str,
        headers: &HashMap<String, String>,
    ) -> McpResult<Self> {
        let transport = SseClientTransport::start(url).await
            .map_err(|e| McpError::transport(format!("Failed to create SSE transport: {}", e)))?;

        // TODO: Add header support for SSE transport
        // Headers are currently not supported in the simple start() method

        Ok(McpTransport::Sse(transport))
    }
    
    #[cfg(not(feature = "mcp"))]
    async fn create_sse_transport(
        _url: &str,
        _headers: &HashMap<String, String>,
    ) -> McpResult<Self> {
        Err(McpError::configuration("MCP feature not enabled"))
    }
    
    /// Get a description of the transport
    pub fn description(&self) -> String {
        match self {
            #[cfg(feature = "mcp")]
            McpTransport::Stdio(_) => "stdio".to_string(),
            #[cfg(feature = "mcp")]
            McpTransport::Sse(_) => "sse".to_string(),
            #[cfg(not(feature = "mcp"))]
            McpTransport::Disabled => "disabled".to_string(),
        }
    }
    
    /// Check if the transport is stdio
    pub fn is_stdio(&self) -> bool {
        matches!(self, McpTransport::Stdio(_))
    }
    
    /// Check if the transport is SSE
    pub fn is_sse(&self) -> bool {
        matches!(self, McpTransport::Sse(_))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::mcp::config::McpTransportConfig;
    
    #[test]
    fn test_transport_description() {
        #[cfg(not(feature = "mcp"))]
        {
            let transport = McpTransport::Disabled;
            assert_eq!(transport.description(), "disabled");
        }
    }
    
    #[test]
    fn test_transport_type_checks() {
        #[cfg(not(feature = "mcp"))]
        {
            let transport = McpTransport::Disabled;
            assert!(!transport.is_stdio());
            assert!(!transport.is_sse());
        }
    }
}