turul_mcp_protocol_2025_06_18/
logging.rs

1//! MCP Logging Protocol Types
2//!
3//! This module defines types for logging in MCP.
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::collections::HashMap;
8
9/// Logging levels (per MCP spec)
10/// Maps to syslog message severities as specified in RFC-5424
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "lowercase")]
13pub enum LoggingLevel {
14    Debug,
15    Info,
16    Notice,
17    Warning,
18    Error,
19    Critical,
20    Alert,
21    Emergency,
22}
23
24/// Type alias for compatibility (per MCP spec)
25pub type LogLevel = LoggingLevel;
26
27/// Parameters for notifications/message logging (per MCP spec)
28#[derive(Debug, Clone, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct LoggingMessageParams {
31    /// Log level
32    pub level: LoggingLevel,
33    /// Optional logger name
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub logger: Option<String>,
36    /// Log data (any serializable type)
37    pub data: Value,
38    /// Meta information (optional _meta field inside params)
39    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
40    pub meta: Option<HashMap<String, Value>>,
41}
42
43/// Complete logging message notification (per MCP spec)
44#[derive(Debug, Clone, Serialize, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct LoggingMessageNotification {
47    /// Method name (always "notifications/message")
48    pub method: String,
49    /// Notification parameters
50    pub params: LoggingMessageParams,
51}
52
53impl LoggingMessageParams {
54    pub fn new(level: LoggingLevel, data: Value) -> Self {
55        Self {
56            level,
57            logger: None,
58            data,
59            meta: None,
60        }
61    }
62
63    pub fn with_logger(mut self, logger: impl Into<String>) -> Self {
64        self.logger = Some(logger.into());
65        self
66    }
67
68    pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
69        self.meta = Some(meta);
70        self
71    }
72}
73
74impl LoggingMessageNotification {
75    pub fn new(level: LoggingLevel, data: Value) -> Self {
76        Self {
77            method: "notifications/message".to_string(),
78            params: LoggingMessageParams::new(level, data),
79        }
80    }
81
82    pub fn with_logger(mut self, logger: impl Into<String>) -> Self {
83        self.params = self.params.with_logger(logger);
84        self
85    }
86
87    pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
88        self.params = self.params.with_meta(meta);
89        self
90    }
91}
92
93/// Parameters for logging/setLevel request (per MCP spec)
94#[derive(Debug, Clone, Serialize, Deserialize)]
95#[serde(rename_all = "camelCase")]
96pub struct SetLevelParams {
97    /// The log level to set
98    pub level: LoggingLevel,
99    /// Meta information (optional _meta field inside params)
100    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
101    pub meta: Option<HashMap<String, Value>>,
102}
103
104impl SetLevelParams {
105    pub fn new(level: LoggingLevel) -> Self {
106        Self { level, meta: None }
107    }
108
109    pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
110        self.meta = Some(meta);
111        self
112    }
113}
114
115/// Complete logging/setLevel request (matches TypeScript SetLevelRequest interface)
116#[derive(Debug, Clone, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct SetLevelRequest {
119    /// Method name (always "logging/setLevel")
120    pub method: String,
121    /// Request parameters
122    pub params: SetLevelParams,
123}
124
125impl SetLevelRequest {
126    pub fn new(level: LoggingLevel) -> Self {
127        Self {
128            method: "logging/setLevel".to_string(),
129            params: SetLevelParams::new(level),
130        }
131    }
132
133    pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
134        self.params = self.params.with_meta(meta);
135        self
136    }
137}
138
139/// Convenience constructors for LoggingLevel
140impl LoggingLevel {
141    /// Get logging level priority (0 = debug, 7 = emergency)
142    pub fn priority(&self) -> u8 {
143        match self {
144            LoggingLevel::Debug => 0,
145            LoggingLevel::Info => 1,
146            LoggingLevel::Notice => 2,
147            LoggingLevel::Warning => 3,
148            LoggingLevel::Error => 4,
149            LoggingLevel::Critical => 5,
150            LoggingLevel::Alert => 6,
151            LoggingLevel::Emergency => 7,
152        }
153    }
154
155    /// Check if this level should be logged at the given threshold
156    pub fn should_log(&self, threshold: LoggingLevel) -> bool {
157        self.priority() >= threshold.priority()
158    }
159}
160
161// Trait implementations for protocol compliance
162use crate::traits::*;
163
164// Params trait implementations
165impl Params for SetLevelParams {}
166impl Params for LoggingMessageParams {}
167
168// SetLevelParams specific traits
169impl HasLevelParam for SetLevelParams {
170    fn level(&self) -> &LoggingLevel {
171        &self.level
172    }
173}
174
175impl HasMetaParam for SetLevelParams {
176    fn meta(&self) -> Option<&HashMap<String, Value>> {
177        self.meta.as_ref()
178    }
179}
180
181// SetLevelRequest traits
182impl HasMethod for SetLevelRequest {
183    fn method(&self) -> &str {
184        &self.method
185    }
186}
187
188impl HasParams for SetLevelRequest {
189    fn params(&self) -> Option<&dyn Params> {
190        Some(&self.params)
191    }
192}
193
194// LoggingMessageParams specific traits
195impl HasLevelParam for LoggingMessageParams {
196    fn level(&self) -> &LoggingLevel {
197        &self.level
198    }
199}
200
201impl HasLoggerParam for LoggingMessageParams {
202    fn logger(&self) -> Option<&String> {
203        self.logger.as_ref()
204    }
205}
206
207impl HasMetaParam for LoggingMessageParams {
208    fn meta(&self) -> Option<&HashMap<String, Value>> {
209        self.meta.as_ref()
210    }
211}
212
213// LoggingMessageNotification traits
214impl HasMethod for LoggingMessageNotification {
215    fn method(&self) -> &str {
216        &self.method
217    }
218}
219
220impl HasParams for LoggingMessageNotification {
221    fn params(&self) -> Option<&dyn Params> {
222        Some(&self.params)
223    }
224}
225
226// ===========================================
227// === Fine-Grained Logging Traits ===
228// ===========================================
229
230/// Trait for logging metadata (method, logger name)
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235    use serde_json::json;
236
237    #[test]
238    fn test_logging_level_priority() {
239        assert_eq!(LoggingLevel::Debug.priority(), 0);
240        assert_eq!(LoggingLevel::Emergency.priority(), 7);
241
242        assert!(LoggingLevel::Error.should_log(LoggingLevel::Warning));
243        assert!(!LoggingLevel::Info.should_log(LoggingLevel::Error));
244    }
245
246    #[test]
247    fn test_set_level_request() {
248        let request = SetLevelRequest::new(LoggingLevel::Warning);
249
250        assert_eq!(request.method, "logging/setLevel");
251        assert_eq!(request.params.level, LoggingLevel::Warning);
252    }
253
254    #[test]
255    fn test_logging_message_notification() {
256        let data = json!({"message": "Test log message", "context": "test"});
257        let notification = LoggingMessageNotification::new(LoggingLevel::Info, data.clone())
258            .with_logger("test-logger");
259
260        assert_eq!(notification.method, "notifications/message");
261        assert_eq!(notification.params.level, LoggingLevel::Info);
262        assert_eq!(notification.params.logger, Some("test-logger".to_string()));
263        assert_eq!(notification.params.data, data);
264    }
265
266    #[test]
267    fn test_serialization() {
268        let request = SetLevelRequest::new(LoggingLevel::Error);
269        let json = serde_json::to_string(&request).unwrap();
270        assert!(json.contains("logging/setLevel"));
271        assert!(json.contains("error"));
272
273        let parsed: SetLevelRequest = serde_json::from_str(&json).unwrap();
274        assert_eq!(parsed.method, "logging/setLevel");
275        assert_eq!(parsed.params.level, LoggingLevel::Error);
276    }
277}