mcp-tools 0.1.0

Rust MCP tools library
Documentation
//! Common types and utilities shared across MCP Tools

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;

pub mod client_base;
pub mod protocol;
pub mod server_base;
pub mod transport;

pub use client_base::*;
pub use protocol::*;
pub use server_base::*;
pub use transport::*;

/// Standard MCP server configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
    /// Server name
    pub name: String,
    /// Server description
    pub description: String,
    /// Server version
    pub version: String,
    /// Host to bind to
    pub host: String,
    /// Port to bind to
    pub port: u16,
    /// Maximum number of concurrent connections
    pub max_connections: usize,
    /// Request timeout in seconds
    pub request_timeout_secs: u64,
    /// Enable request logging
    pub log_requests: bool,
    /// Server-specific configuration
    pub server_config: HashMap<String, serde_json::Value>,
}

impl Default for ServerConfig {
    fn default() -> Self {
        Self {
            name: "MCP Server".to_string(),
            description: "MCP Server powered by CoderLib".to_string(),
            version: crate::VERSION.to_string(),
            host: crate::DEFAULT_HOST.to_string(),
            port: crate::DEFAULT_PORT,
            max_connections: 100,
            request_timeout_secs: 30,
            log_requests: true,
            server_config: HashMap::new(),
        }
    }
}

/// Standard MCP client configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientConfig {
    /// Client name
    pub name: String,
    /// Client version
    pub version: String,
    /// Server URL to connect to
    pub server_url: String,
    /// Connection timeout in seconds
    pub connect_timeout_secs: u64,
    /// Request timeout in seconds
    pub request_timeout_secs: u64,
    /// Enable request logging
    pub log_requests: bool,
    /// Client-specific configuration
    pub client_config: HashMap<String, serde_json::Value>,
}

impl Default for ClientConfig {
    fn default() -> Self {
        Self {
            name: "MCP Client".to_string(),
            version: crate::VERSION.to_string(),
            server_url: format!("ws://{}:{}", crate::DEFAULT_HOST, crate::DEFAULT_PORT),
            connect_timeout_secs: 10,
            request_timeout_secs: 30,
            log_requests: true,
            client_config: HashMap::new(),
        }
    }
}

/// MCP tool definition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpTool {
    /// Tool name
    pub name: String,
    /// Tool description
    pub description: String,
    /// Input schema
    pub input_schema: serde_json::Value,
    /// Tool category
    pub category: String,
    /// Whether tool requires permissions
    pub requires_permission: bool,
    /// Required permissions
    pub permissions: Vec<String>,
}

/// MCP tool execution request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpToolRequest {
    /// Request ID
    pub id: Uuid,
    /// Tool name
    pub tool: String,
    /// Tool arguments
    pub arguments: serde_json::Value,
    /// Session ID
    pub session_id: String,
    /// Request metadata
    pub metadata: HashMap<String, serde_json::Value>,
}

/// MCP tool execution response
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpToolResponse {
    /// Request ID
    pub id: Uuid,
    /// Response content
    pub content: Vec<McpContent>,
    /// Whether the operation was successful
    pub is_error: bool,
    /// Error message if any
    pub error: Option<String>,
    /// Response metadata
    pub metadata: HashMap<String, serde_json::Value>,
}

/// MCP content types
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum McpContent {
    #[serde(rename = "text")]
    Text { text: String },
    #[serde(rename = "image")]
    Image { data: String, mime_type: String },
    #[serde(rename = "resource")]
    Resource {
        uri: String,
        mime_type: Option<String>,
        text: Option<String>,
    },
}

/// Server capabilities
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerCapabilities {
    /// Available tools
    pub tools: Vec<McpTool>,
    /// Supported features
    pub features: Vec<String>,
    /// Server information
    pub info: ServerInfo,
}

/// Server information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerInfo {
    /// Server name
    pub name: String,
    /// Server version
    pub version: String,
    /// Server description
    pub description: String,
    /// CoderLib version
    pub coderlib_version: String,
    /// Supported protocol version
    pub protocol_version: String,
}

/// Client capabilities
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientCapabilities {
    /// Supported content types
    pub content_types: Vec<String>,
    /// Supported features
    pub features: Vec<String>,
    /// Client information
    pub info: ClientInfo,
}

/// Client information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientInfo {
    /// Client name
    pub name: String,
    /// Client version
    pub version: String,
    /// Client description
    pub description: String,
}

/// Connection status
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConnectionStatus {
    Disconnected,
    Connecting,
    Connected,
    Error(String),
}

/// Server statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerStats {
    /// Number of active connections
    pub active_connections: usize,
    /// Total requests processed
    pub total_requests: u64,
    /// Total errors
    pub total_errors: u64,
    /// Server uptime in seconds
    pub uptime_secs: u64,
    /// Average request duration in milliseconds
    pub avg_request_duration_ms: f64,
}

/// Utility functions
impl McpContent {
    /// Create text content
    pub fn text(text: impl Into<String>) -> Self {
        Self::Text { text: text.into() }
    }

    /// Create image content
    pub fn image(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
        Self::Image {
            data: data.into(),
            mime_type: mime_type.into(),
        }
    }

    /// Create resource content
    pub fn resource(uri: impl Into<String>) -> Self {
        Self::Resource {
            uri: uri.into(),
            mime_type: None,
            text: None,
        }
    }

    /// Create resource content with MIME type
    pub fn resource_with_type(uri: impl Into<String>, mime_type: impl Into<String>) -> Self {
        Self::Resource {
            uri: uri.into(),
            mime_type: Some(mime_type.into()),
            text: None,
        }
    }
}

impl McpToolResponse {
    /// Create successful response
    pub fn success(id: Uuid, content: Vec<McpContent>) -> Self {
        Self {
            id,
            content,
            is_error: false,
            error: None,
            metadata: HashMap::new(),
        }
    }

    /// Create error response
    pub fn error(id: Uuid, error: impl Into<String>) -> Self {
        Self {
            id,
            content: vec![],
            is_error: true,
            error: Some(error.into()),
            metadata: HashMap::new(),
        }
    }

    /// Add metadata
    pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
        self.metadata.insert(key.into(), value);
        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_server_config_default() {
        let config = ServerConfig::default();
        assert_eq!(config.name, "MCP Server");
        assert_eq!(config.host, "127.0.0.1");
        assert_eq!(config.port, 3000);
    }

    #[test]
    fn test_client_config_default() {
        let config = ClientConfig::default();
        assert_eq!(config.name, "MCP Client");
        assert!(config.server_url.contains("127.0.0.1:3000"));
    }

    #[test]
    fn test_mcp_content_creation() {
        let text = McpContent::text("Hello, world!");
        match text {
            McpContent::Text { text } => assert_eq!(text, "Hello, world!"),
            _ => panic!("Expected text content"),
        }

        let image = McpContent::image("base64data", "image/png");
        match image {
            McpContent::Image { data, mime_type } => {
                assert_eq!(data, "base64data");
                assert_eq!(mime_type, "image/png");
            }
            _ => panic!("Expected image content"),
        }
    }

    #[test]
    fn test_mcp_tool_response() {
        let id = Uuid::new_v4();
        let response = McpToolResponse::success(id, vec![McpContent::text("Success")]);

        assert_eq!(response.id, id);
        assert!(!response.is_error);
        assert!(response.error.is_none());
        assert_eq!(response.content.len(), 1);

        let error_response = McpToolResponse::error(id, "Something went wrong");
        assert!(error_response.is_error);
        assert!(error_response.error.is_some());
    }
}