1use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11#[non_exhaustive]
12#[serde(rename_all = "camelCase")]
13pub struct ProgressNotification {
14 pub progress_token: ProgressToken,
16 pub progress: f64,
20 #[serde(skip_serializing_if = "Option::is_none")]
25 pub total: Option<f64>,
26 #[serde(skip_serializing_if = "Option::is_none")]
30 pub message: Option<String>,
31}
32
33impl ProgressNotification {
34 pub fn new(progress_token: ProgressToken, progress: f64, message: Option<String>) -> Self {
38 Self {
39 progress_token,
40 progress,
41 total: None,
42 message,
43 }
44 }
45
46 pub fn with_total(mut self, total: f64) -> Self {
48 self.total = Some(total);
49 self
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
55#[serde(untagged)]
56pub enum ProgressToken {
57 String(String),
59 Number(i64),
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(tag = "method", content = "params", rename_all = "camelCase")]
66pub enum ClientNotification {
67 #[serde(rename = "notifications/initialized")]
69 Initialized,
70 #[serde(rename = "notifications/roots/list_changed")]
72 RootsListChanged,
73 #[serde(rename = "notifications/cancelled")]
75 Cancelled(CancelledNotification),
76 #[serde(rename = "notifications/progress")]
78 Progress(ProgressNotification),
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83#[non_exhaustive]
84#[serde(rename_all = "camelCase")]
85pub struct CancelledNotification {
86 pub request_id: super::RequestId,
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub reason: Option<String>,
91}
92
93impl CancelledNotification {
94 pub fn new(request_id: super::RequestId) -> Self {
98 Self {
99 request_id,
100 reason: None,
101 }
102 }
103
104 pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
106 self.reason = Some(reason.into());
107 self
108 }
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113#[serde(tag = "method", content = "params", rename_all = "camelCase")]
114pub enum ServerNotification {
115 #[serde(rename = "notifications/progress")]
117 Progress(ProgressNotification),
118 #[serde(rename = "notifications/tools/list_changed")]
120 ToolsChanged,
121 #[serde(rename = "notifications/prompts/list_changed")]
123 PromptsChanged,
124 #[serde(rename = "notifications/resources/list_changed")]
126 ResourcesChanged,
127 #[serde(rename = "notifications/roots/list_changed")]
129 RootsListChanged,
130 #[serde(rename = "notifications/resources/updated")]
132 ResourceUpdated(ResourceUpdatedParams),
133 #[serde(rename = "notifications/message")]
135 LogMessage(LogMessageParams),
136 #[serde(rename = "notifications/tasks/status")]
138 TaskStatus(super::tasks::TaskStatusNotification),
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143#[non_exhaustive]
144#[serde(rename_all = "camelCase")]
145pub struct ResourceUpdatedParams {
146 pub uri: String,
148}
149
150impl ResourceUpdatedParams {
151 pub fn new(uri: impl Into<String>) -> Self {
153 Self { uri: uri.into() }
154 }
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159#[non_exhaustive]
160#[serde(rename_all = "camelCase")]
161pub struct LogMessageParams {
162 pub level: LoggingLevel,
164 #[serde(skip_serializing_if = "Option::is_none")]
166 pub logger: Option<String>,
167 pub message: String,
169 #[serde(skip_serializing_if = "Option::is_none")]
171 pub data: Option<Value>,
172}
173
174impl LogMessageParams {
175 pub fn new(level: LoggingLevel, message: impl Into<String>) -> Self {
179 Self {
180 level,
181 logger: None,
182 message: message.into(),
183 data: None,
184 }
185 }
186
187 pub fn with_logger(mut self, logger: impl Into<String>) -> Self {
189 self.logger = Some(logger.into());
190 self
191 }
192
193 pub fn with_data(mut self, data: Value) -> Self {
195 self.data = Some(data);
196 self
197 }
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
202#[serde(untagged)]
203pub enum Notification {
204 Client(ClientNotification),
206 Server(ServerNotification),
208 Progress(ProgressNotification),
210 Cancelled(CancelledNotification),
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
216#[serde(rename_all = "lowercase")]
217pub enum LoggingLevel {
218 Debug,
220 Info,
222 Notice,
224 Warning,
226 Error,
228 Critical,
230 Alert,
232 Emergency,
234}
235
236pub type LogLevel = LoggingLevel;
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use serde_json::json;
246
247 #[test]
248 fn test_all_notification_types() {
249 let progress = ServerNotification::Progress(ProgressNotification::new(
250 ProgressToken::String("token123".to_string()),
251 50.0,
252 Some("Processing...".to_string()),
253 ));
254 let json = serde_json::to_value(&progress).unwrap();
255 assert_eq!(json["method"], "notifications/progress");
256
257 let tools_changed = ServerNotification::ToolsChanged;
258 let json = serde_json::to_value(&tools_changed).unwrap();
259 assert_eq!(json["method"], "notifications/tools/list_changed");
260
261 let prompts_changed = ServerNotification::PromptsChanged;
262 let json = serde_json::to_value(&prompts_changed).unwrap();
263 assert_eq!(json["method"], "notifications/prompts/list_changed");
264
265 let resources_changed = ServerNotification::ResourcesChanged;
266 let json = serde_json::to_value(&resources_changed).unwrap();
267 assert_eq!(json["method"], "notifications/resources/list_changed");
268
269 let roots_changed = ServerNotification::RootsListChanged;
270 let json = serde_json::to_value(&roots_changed).unwrap();
271 assert_eq!(json["method"], "notifications/roots/list_changed");
272
273 let resource_updated =
274 ServerNotification::ResourceUpdated(ResourceUpdatedParams::new("file://test.txt"));
275 let json = serde_json::to_value(&resource_updated).unwrap();
276 assert_eq!(json["method"], "notifications/resources/updated");
277
278 let log_msg = ServerNotification::LogMessage(
279 LogMessageParams::new(LoggingLevel::Info, "Test log message")
280 .with_data(json!({"extra": "data"})),
281 );
282 let json = serde_json::to_value(&log_msg).unwrap();
283 assert_eq!(json["method"], "notifications/message");
284 }
285
286 #[test]
287 fn test_logging_level_all_8_values() {
288 assert_eq!(serde_json::to_value(LoggingLevel::Debug).unwrap(), "debug");
289 assert_eq!(serde_json::to_value(LoggingLevel::Info).unwrap(), "info");
290 assert_eq!(
291 serde_json::to_value(LoggingLevel::Notice).unwrap(),
292 "notice"
293 );
294 assert_eq!(
295 serde_json::to_value(LoggingLevel::Warning).unwrap(),
296 "warning"
297 );
298 assert_eq!(serde_json::to_value(LoggingLevel::Error).unwrap(), "error");
299 assert_eq!(
300 serde_json::to_value(LoggingLevel::Critical).unwrap(),
301 "critical"
302 );
303 assert_eq!(serde_json::to_value(LoggingLevel::Alert).unwrap(), "alert");
304 assert_eq!(
305 serde_json::to_value(LoggingLevel::Emergency).unwrap(),
306 "emergency"
307 );
308 }
309
310 #[test]
311 fn test_log_level_alias_works() {
312 let level: LogLevel = LoggingLevel::Warning;
314 assert_eq!(serde_json::to_value(level).unwrap(), "warning");
315 }
316
317 #[test]
318 fn test_cancelled_notification() {
319 use crate::types::RequestId;
320
321 let cancelled =
322 CancelledNotification::new(RequestId::Number(123)).with_reason("User cancelled");
323
324 let json = serde_json::to_value(&cancelled).unwrap();
325 assert_eq!(json["requestId"], 123);
326 assert_eq!(json["reason"], "User cancelled");
327 }
328
329 #[test]
330 fn test_task_status_notification() {
331 use crate::types::tasks::{Task, TaskStatus as TStatus, TaskStatusNotification};
332
333 let notif = ServerNotification::TaskStatus(TaskStatusNotification {
334 task: Task::new("t-789", TStatus::Completed)
335 .with_timestamps("2025-11-25T00:00:00Z", "2025-11-25T00:05:00Z")
336 .with_status_message("Done"),
337 });
338 let json = serde_json::to_value(¬if).unwrap();
339 assert_eq!(json["method"], "notifications/tasks/status");
340 assert_eq!(json["params"]["task"]["taskId"], "t-789");
341 assert_eq!(json["params"]["task"]["status"], "completed");
342 }
343}