1use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::HashMap;
9
10use crate::logging::LoggingLevel;
11use turul_mcp_json_rpc_server::types::RequestId;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct NotificationParams {
17 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
19 pub meta: Option<HashMap<String, Value>>,
20 #[serde(flatten)]
22 pub other: HashMap<String, Value>,
23}
24
25impl Default for NotificationParams {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31impl NotificationParams {
32 pub fn new() -> Self {
33 Self {
34 meta: None,
35 other: HashMap::new(),
36 }
37 }
38
39 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
40 self.meta = Some(meta);
41 self
42 }
43
44 pub fn with_param(mut self, key: impl Into<String>, value: Value) -> Self {
45 self.other.insert(key.into(), value);
46 self
47 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52#[serde(rename_all = "camelCase")]
53pub struct Notification {
54 pub method: String,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub params: Option<NotificationParams>,
59}
60
61impl Notification {
62 pub fn new(method: impl Into<String>) -> Self {
63 Self {
64 method: method.into(),
65 params: None,
66 }
67 }
68
69 pub fn with_params(mut self, params: NotificationParams) -> Self {
70 self.params = Some(params);
71 self
72 }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
79#[serde(rename_all = "camelCase")]
80pub struct ResourceListChangedNotification {
81 pub method: String,
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub params: Option<NotificationParams>,
86}
87
88impl Default for ResourceListChangedNotification {
89 fn default() -> Self {
90 Self::new()
91 }
92}
93
94impl ResourceListChangedNotification {
95 pub fn new() -> Self {
96 Self {
97 method: "notifications/resources/listChanged".to_string(),
98 params: None,
99 }
100 }
101
102 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
103 self.params = Some(NotificationParams::new().with_meta(meta));
104 self
105 }
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110#[serde(rename_all = "camelCase")]
111pub struct ToolListChangedNotification {
112 pub method: String,
114 #[serde(skip_serializing_if = "Option::is_none")]
116 pub params: Option<NotificationParams>,
117}
118
119impl Default for ToolListChangedNotification {
120 fn default() -> Self {
121 Self::new()
122 }
123}
124
125impl ToolListChangedNotification {
126 pub fn new() -> Self {
127 Self {
128 method: "notifications/tools/listChanged".to_string(),
129 params: None,
130 }
131 }
132
133 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
134 self.params = Some(NotificationParams::new().with_meta(meta));
135 self
136 }
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141#[serde(rename_all = "camelCase")]
142pub struct PromptListChangedNotification {
143 pub method: String,
145 #[serde(skip_serializing_if = "Option::is_none")]
147 pub params: Option<NotificationParams>,
148}
149
150impl Default for PromptListChangedNotification {
151 fn default() -> Self {
152 Self::new()
153 }
154}
155
156impl PromptListChangedNotification {
157 pub fn new() -> Self {
158 Self {
159 method: "notifications/prompts/listChanged".to_string(),
160 params: None,
161 }
162 }
163
164 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
165 self.params = Some(NotificationParams::new().with_meta(meta));
166 self
167 }
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172#[serde(rename_all = "camelCase")]
173pub struct RootsListChangedNotification {
174 pub method: String,
176 #[serde(skip_serializing_if = "Option::is_none")]
178 pub params: Option<NotificationParams>,
179}
180
181impl Default for RootsListChangedNotification {
182 fn default() -> Self {
183 Self::new()
184 }
185}
186
187impl RootsListChangedNotification {
188 pub fn new() -> Self {
189 Self {
190 method: "notifications/roots/listChanged".to_string(),
191 params: None,
192 }
193 }
194
195 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
196 self.params = Some(NotificationParams::new().with_meta(meta));
197 self
198 }
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
203#[serde(rename_all = "camelCase")]
204pub struct ProgressNotification {
205 pub method: String,
207 pub params: ProgressNotificationParams,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize)]
212#[serde(rename_all = "camelCase")]
213pub struct ProgressNotificationParams {
214 pub progress_token: String,
216 pub progress: u64,
218 #[serde(skip_serializing_if = "Option::is_none")]
220 pub total: Option<u64>,
221 #[serde(skip_serializing_if = "Option::is_none")]
223 pub message: Option<String>,
224 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
226 pub meta: Option<HashMap<String, Value>>,
227}
228
229impl ProgressNotification {
230 pub fn new(progress_token: impl Into<String>, progress: u64) -> Self {
231 Self {
232 method: "notifications/progress".to_string(),
233 params: ProgressNotificationParams {
234 progress_token: progress_token.into(),
235 progress,
236 total: None,
237 message: None,
238 meta: None,
239 },
240 }
241 }
242
243 pub fn with_total(mut self, total: u64) -> Self {
244 self.params.total = Some(total);
245 self
246 }
247
248 pub fn with_message(mut self, message: impl Into<String>) -> Self {
249 self.params.message = Some(message.into());
250 self
251 }
252
253 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
254 self.params.meta = Some(meta);
255 self
256 }
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize)]
261#[serde(rename_all = "camelCase")]
262pub struct ResourceUpdatedNotification {
263 pub method: String,
265 pub params: ResourceUpdatedNotificationParams,
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
270#[serde(rename_all = "camelCase")]
271pub struct ResourceUpdatedNotificationParams {
272 pub uri: String,
274 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
276 pub meta: Option<HashMap<String, Value>>,
277}
278
279impl ResourceUpdatedNotification {
280 pub fn new(uri: impl Into<String>) -> Self {
281 Self {
282 method: "notifications/resources/updated".to_string(),
283 params: ResourceUpdatedNotificationParams {
284 uri: uri.into(),
285 meta: None,
286 },
287 }
288 }
289
290 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
291 self.params.meta = Some(meta);
292 self
293 }
294}
295
296#[derive(Debug, Clone, Serialize, Deserialize)]
298#[serde(rename_all = "camelCase")]
299pub struct CancelledNotification {
300 pub method: String,
302 pub params: CancelledNotificationParams,
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
307#[serde(rename_all = "camelCase")]
308pub struct CancelledNotificationParams {
309 pub request_id: RequestId,
311 #[serde(skip_serializing_if = "Option::is_none")]
313 pub reason: Option<String>,
314 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
316 pub meta: Option<HashMap<String, Value>>,
317}
318
319impl CancelledNotification {
320 pub fn new(request_id: RequestId) -> Self {
321 Self {
322 method: "notifications/cancelled".to_string(),
323 params: CancelledNotificationParams {
324 request_id,
325 reason: None,
326 meta: None,
327 },
328 }
329 }
330
331 pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
332 self.params.reason = Some(reason.into());
333 self
334 }
335
336 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
337 self.params.meta = Some(meta);
338 self
339 }
340}
341
342#[derive(Debug, Clone, Serialize, Deserialize)]
344#[serde(rename_all = "camelCase")]
345pub struct InitializedNotification {
346 pub method: String,
348 #[serde(skip_serializing_if = "Option::is_none")]
350 pub params: Option<NotificationParams>,
351}
352
353impl Default for InitializedNotification {
354 fn default() -> Self {
355 Self::new()
356 }
357}
358
359impl InitializedNotification {
360 pub fn new() -> Self {
361 Self {
362 method: "notifications/initialized".to_string(),
363 params: None,
364 }
365 }
366
367 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
368 self.params = Some(NotificationParams::new().with_meta(meta));
369 self
370 }
371}
372
373#[derive(Debug, Clone, Serialize, Deserialize)]
375#[serde(rename_all = "camelCase")]
376pub struct LoggingMessageNotification {
377 pub method: String,
379 pub params: LoggingMessageNotificationParams,
381}
382
383#[derive(Debug, Clone, Serialize, Deserialize)]
384#[serde(rename_all = "camelCase")]
385pub struct LoggingMessageNotificationParams {
386 pub level: LoggingLevel,
388 #[serde(skip_serializing_if = "Option::is_none")]
390 pub logger: Option<String>,
391 pub data: Value,
393 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
395 pub meta: Option<HashMap<String, Value>>,
396}
397
398impl LoggingMessageNotification {
399 pub fn new(level: LoggingLevel, data: Value) -> Self {
400 Self {
401 method: "notifications/message".to_string(),
402 params: LoggingMessageNotificationParams {
403 level,
404 logger: None,
405 data,
406 meta: None,
407 },
408 }
409 }
410
411 pub fn with_logger(mut self, logger: impl Into<String>) -> Self {
412 self.params.logger = Some(logger.into());
413 self
414 }
415
416 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
417 self.params.meta = Some(meta);
418 self
419 }
420}
421
422use crate::traits::*;
425
426impl Params for NotificationParams {}
428
429impl HasMetaParam for NotificationParams {
430 fn meta(&self) -> Option<&HashMap<String, Value>> {
431 self.meta.as_ref()
432 }
433}
434
435#[cfg(test)]
442mod tests {
443 use super::*;
444 use serde_json::json;
445
446 #[test]
447 fn test_resource_list_changed() {
448 let notification = ResourceListChangedNotification::new();
449 assert_eq!(notification.method, "notifications/resources/listChanged");
450 }
451
452 #[test]
453 fn test_tool_list_changed() {
454 let notification = ToolListChangedNotification::new();
455 assert_eq!(notification.method, "notifications/tools/listChanged");
456 }
457
458 #[test]
459 fn test_prompt_list_changed() {
460 let notification = PromptListChangedNotification::new();
461 assert_eq!(notification.method, "notifications/prompts/listChanged");
462 }
463
464 #[test]
465 fn test_roots_list_changed() {
466 let notification = RootsListChangedNotification::new();
467 assert_eq!(notification.method, "notifications/roots/listChanged");
468 }
469
470 #[test]
471 fn test_progress_notification() {
472 let notification = ProgressNotification::new("token123", 50)
473 .with_total(100)
474 .with_message("Processing...");
475
476 assert_eq!(notification.method, "notifications/progress");
477 assert_eq!(notification.params.progress_token, "token123");
478 assert_eq!(notification.params.progress, 50);
479 assert_eq!(notification.params.total, Some(100));
480 assert_eq!(
481 notification.params.message,
482 Some("Processing...".to_string())
483 );
484 }
485
486 #[test]
487 fn test_resource_updated() {
488 let notification = ResourceUpdatedNotification::new("file:///test.txt");
489 assert_eq!(notification.method, "notifications/resources/updated");
490 assert_eq!(notification.params.uri, "file:///test.txt");
491 }
492
493 #[test]
494 fn test_cancelled_notification() {
495 use turul_mcp_json_rpc_server::types::RequestId;
496 let notification =
497 CancelledNotification::new(RequestId::Number(123)).with_reason("User cancelled");
498
499 assert_eq!(notification.method, "notifications/cancelled");
500 assert_eq!(notification.params.request_id, RequestId::Number(123));
501 assert_eq!(
502 notification.params.reason,
503 Some("User cancelled".to_string())
504 );
505 }
506
507 #[test]
508 fn test_initialized_notification() {
509 let notification = InitializedNotification::new();
510 assert_eq!(notification.method, "notifications/initialized");
511 }
512
513 #[test]
514 fn test_logging_message_notification() {
515 use crate::logging::LoggingLevel;
516 let data = json!({"message": "Test log message", "context": "test"});
517 let notification = LoggingMessageNotification::new(LoggingLevel::Info, data.clone())
518 .with_logger("test-logger");
519
520 assert_eq!(notification.method, "notifications/message");
521 assert_eq!(notification.params.level, LoggingLevel::Info);
522 assert_eq!(notification.params.logger, Some("test-logger".to_string()));
523 assert_eq!(notification.params.data, data);
524 }
525
526 #[test]
527 fn test_serialization() {
528 let notification = InitializedNotification::new();
529 let json = serde_json::to_string(¬ification).unwrap();
530 assert!(json.contains("notifications/initialized"));
531
532 let parsed: InitializedNotification = serde_json::from_str(&json).unwrap();
533 assert_eq!(parsed.method, "notifications/initialized");
534 }
535}