vex_llm/mcp/
types.rs

1//! MCP types and configuration
2//!
3//! Core types for MCP protocol integration.
4
5use serde::{Deserialize, Serialize};
6use std::time::Duration;
7
8/// MCP client configuration
9///
10/// # Security
11///
12/// - `connect_timeout`: Prevents hanging connections (DoS)
13/// - `request_timeout`: Prevents slow loris attacks
14/// - `max_response_size`: Limits memory usage
15/// - `require_tls`: Enforces encrypted connections
16#[derive(Debug, Clone)]
17pub struct McpConfig {
18    /// Connection timeout
19    pub connect_timeout: Duration,
20    /// Request timeout for tool calls
21    pub request_timeout: Duration,
22    /// Maximum response size in bytes
23    pub max_response_size: usize,
24    /// Require TLS for connections (enforced for non-localhost)
25    pub require_tls: bool,
26    /// OAuth 2.1 token (if authentication required)
27    pub auth_token: Option<String>,
28}
29
30impl Default for McpConfig {
31    fn default() -> Self {
32        Self {
33            connect_timeout: Duration::from_secs(10),
34            request_timeout: Duration::from_secs(30),
35            max_response_size: 10 * 1024 * 1024, // 10MB
36            require_tls: true,
37            auth_token: None,
38        }
39    }
40}
41
42impl McpConfig {
43    /// Create config with authentication
44    pub fn with_auth(mut self, token: impl Into<String>) -> Self {
45        self.auth_token = Some(token.into());
46        self
47    }
48
49    /// Allow non-TLS connections (for local development only)
50    ///
51    /// # Security Warning
52    /// Only use this for localhost connections during development
53    pub fn allow_insecure(mut self) -> Self {
54        self.require_tls = false;
55        self
56    }
57}
58
59/// Information about an MCP tool
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct McpToolInfo {
62    /// Tool name
63    pub name: String,
64    /// Tool description
65    pub description: String,
66    /// JSON Schema for parameters
67    pub input_schema: serde_json::Value,
68}
69
70/// MCP-specific errors
71#[derive(Debug, thiserror::Error)]
72pub enum McpError {
73    /// Connection failed
74    #[error("Failed to connect to MCP server: {0}")]
75    ConnectionFailed(String),
76
77    /// Authentication failed
78    #[error("Authentication failed: {0}")]
79    AuthenticationFailed(String),
80
81    /// Tool not found on server
82    #[error("Tool '{0}' not found on MCP server")]
83    ToolNotFound(String),
84
85    /// Tool execution failed
86    #[error("MCP tool execution failed: {0}")]
87    ExecutionFailed(String),
88
89    /// Response too large
90    #[error("Response exceeded maximum size ({0} bytes)")]
91    ResponseTooLarge(usize),
92
93    /// Timeout
94    #[error("Operation timed out after {0:?}")]
95    Timeout(Duration),
96
97    /// TLS required but not available
98    #[error("TLS required for remote connections")]
99    TlsRequired,
100
101    /// Protocol error
102    #[error("MCP protocol error: {0}")]
103    ProtocolError(String),
104
105    /// Serialization error
106    #[error("Serialization error: {0}")]
107    Serialization(String),
108}
109
110impl McpError {
111    /// Check if error is recoverable
112    pub fn is_retryable(&self) -> bool {
113        matches!(self, Self::ConnectionFailed(_) | Self::Timeout(_))
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_config_default() {
123        let config = McpConfig::default();
124        assert_eq!(config.connect_timeout, Duration::from_secs(10));
125        assert!(config.require_tls);
126        assert!(config.auth_token.is_none());
127    }
128
129    #[test]
130    fn test_config_with_auth() {
131        let config = McpConfig::default().with_auth("token123");
132        assert_eq!(config.auth_token, Some("token123".to_string()));
133    }
134
135    #[test]
136    fn test_error_retryable() {
137        assert!(McpError::ConnectionFailed("test".into()).is_retryable());
138        assert!(McpError::Timeout(Duration::from_secs(1)).is_retryable());
139        assert!(!McpError::ToolNotFound("test".into()).is_retryable());
140    }
141}