coderlib 0.1.0

A Rust library for AI-powered code assistance and agentic system
Documentation
//! Minimal MCP implementation for immediate use
//! 
//! This provides a working MCP client/server implementation that can replace
//! the rmcp dependency in our examples while we work on the full protocol integration.

use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::collections::HashMap;
use std::io::{BufRead, BufReader, Write};
use std::process::{Child, Command, Stdio};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader as TokioBufReader};
use tokio::process::{Child as TokioChild, Command as TokioCommand};
use tokio::sync::mpsc;
use thiserror::Error;

/// Minimal MCP Error
#[derive(Error, Debug)]
pub enum MinimalMcpError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    #[error("JSON error: {0}")]
    Json(#[from] serde_json::Error),
    #[error("Protocol error: {0}")]
    Protocol(String),
    #[error("Transport error: {0}")]
    Transport(String),
}

pub type MinimalMcpResult<T> = Result<T, MinimalMcpError>;

/// MCP JSON-RPC message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpMessage {
    pub jsonrpc: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub method: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub params: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub result: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<McpError>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpError {
    pub code: i32,
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<Value>,
}

impl McpMessage {
    pub fn request(id: Value, method: String, params: Option<Value>) -> Self {
        Self {
            jsonrpc: "2.0".to_string(),
            id: Some(id),
            method: Some(method),
            params,
            result: None,
            error: None,
        }
    }

    pub fn response(id: Value, result: Value) -> Self {
        Self {
            jsonrpc: "2.0".to_string(),
            id: Some(id),
            method: None,
            params: None,
            result: Some(result),
            error: None,
        }
    }

    pub fn error_response(id: Value, error: McpError) -> Self {
        Self {
            jsonrpc: "2.0".to_string(),
            id: Some(id),
            method: None,
            params: None,
            result: None,
            error: Some(error),
        }
    }

    pub fn notification(method: String, params: Option<Value>) -> Self {
        Self {
            jsonrpc: "2.0".to_string(),
            id: None,
            method: Some(method),
            params,
            result: None,
            error: None,
        }
    }
}

/// Minimal MCP Client
pub struct MinimalMcpClient {
    child: Option<TokioChild>,
    stdin: Option<tokio::process::ChildStdin>,
    stdout_reader: Option<TokioBufReader<tokio::process::ChildStdout>>,
    next_id: u64,
    server_info: Option<Value>,
}

impl MinimalMcpClient {
    pub fn new() -> Self {
        Self {
            child: None,
            stdin: None,
            stdout_reader: None,
            next_id: 1,
            server_info: None,
        }
    }

    pub async fn connect(&mut self, command: &str, args: &[String], env: HashMap<String, String>) -> MinimalMcpResult<()> {
        let mut cmd = TokioCommand::new(command);
        cmd.args(args)
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped());

        for (key, value) in env {
            cmd.env(key, value);
        }

        let mut child = cmd.spawn()?;
        let stdin = child.stdin.take().ok_or_else(|| MinimalMcpError::Transport("Failed to get stdin".to_string()))?;
        let stdout = child.stdout.take().ok_or_else(|| MinimalMcpError::Transport("Failed to get stdout".to_string()))?;
        
        self.child = Some(child);
        self.stdin = Some(stdin);
        self.stdout_reader = Some(TokioBufReader::new(stdout));

        // Initialize the connection
        self.initialize().await?;

        Ok(())
    }

    async fn initialize(&mut self) -> MinimalMcpResult<()> {
        let init_request = McpMessage::request(
            json!(self.next_id),
            "initialize".to_string(),
            Some(json!({
                "protocolVersion": "2024-11-05",
                "capabilities": {
                    "roots": {
                        "listChanged": true
                    },
                    "sampling": {}
                },
                "clientInfo": {
                    "name": "coderlib",
                    "version": "0.1.0"
                }
            }))
        );

        self.next_id += 1;
        self.send_message(init_request).await?;

        // Read the initialize response
        if let Some(response) = self.read_message().await? {
            if let Some(result) = response.result {
                self.server_info = Some(result);
            }
        }

        // Send initialized notification
        let initialized = McpMessage::notification("initialized".to_string(), None);
        self.send_message(initialized).await?;

        Ok(())
    }

    async fn send_message(&mut self, message: McpMessage) -> MinimalMcpResult<()> {
        if let Some(stdin) = &mut self.stdin {
            let json_str = serde_json::to_string(&message)?;
            stdin.write_all(json_str.as_bytes()).await?;
            stdin.write_all(b"\n").await?;
            stdin.flush().await?;
        }
        Ok(())
    }

    async fn read_message(&mut self) -> MinimalMcpResult<Option<McpMessage>> {
        if let Some(reader) = &mut self.stdout_reader {
            let mut line = String::new();
            match reader.read_line(&mut line).await? {
                0 => Ok(None), // EOF
                _ => {
                    let message: McpMessage = serde_json::from_str(&line)?;
                    Ok(Some(message))
                }
            }
        } else {
            Ok(None)
        }
    }

    pub async fn list_tools(&mut self) -> MinimalMcpResult<Vec<Value>> {
        let request = McpMessage::request(
            json!(self.next_id),
            "tools/list".to_string(),
            None
        );
        self.next_id += 1;
        
        self.send_message(request).await?;
        
        if let Some(response) = self.read_message().await? {
            if let Some(result) = response.result {
                if let Some(tools) = result.get("tools") {
                    if let Some(tools_array) = tools.as_array() {
                        return Ok(tools_array.clone());
                    }
                }
            }
        }
        
        Ok(vec![])
    }

    pub async fn call_tool(&mut self, name: &str, arguments: Option<Value>) -> MinimalMcpResult<Value> {
        let request = McpMessage::request(
            json!(self.next_id),
            "tools/call".to_string(),
            Some(json!({
                "name": name,
                "arguments": arguments
            }))
        );
        self.next_id += 1;
        
        self.send_message(request).await?;
        
        if let Some(response) = self.read_message().await? {
            if let Some(result) = response.result {
                return Ok(result);
            } else if let Some(error) = response.error {
                return Err(MinimalMcpError::Protocol(format!("Tool call failed: {}", error.message)));
            }
        }
        
        Err(MinimalMcpError::Protocol("No response received".to_string()))
    }

    pub async fn disconnect(&mut self) -> MinimalMcpResult<()> {
        if let Some(mut child) = self.child.take() {
            let _ = child.kill().await;
        }
        Ok(())
    }
}

impl Drop for MinimalMcpClient {
    fn drop(&mut self) {
        if let Some(mut child) = self.child.take() {
            let _ = child.start_kill();
        }
    }
}