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 NotificationParams {
26 pub fn new() -> Self {
27 Self {
28 meta: None,
29 other: HashMap::new(),
30 }
31 }
32
33 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
34 self.meta = Some(meta);
35 self
36 }
37
38 pub fn with_param(mut self, key: impl Into<String>, value: Value) -> Self {
39 self.other.insert(key.into(), value);
40 self
41 }
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46#[serde(rename_all = "camelCase")]
47pub struct Notification {
48 pub method: String,
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub params: Option<NotificationParams>,
53}
54
55impl Notification {
56 pub fn new(method: impl Into<String>) -> Self {
57 Self {
58 method: method.into(),
59 params: None,
60 }
61 }
62
63 pub fn with_params(mut self, params: NotificationParams) -> Self {
64 self.params = Some(params);
65 self
66 }
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
73#[serde(rename_all = "camelCase")]
74pub struct ResourceListChangedNotification {
75 pub method: String,
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub params: Option<NotificationParams>,
80}
81
82impl ResourceListChangedNotification {
83 pub fn new() -> Self {
84 Self {
85 method: "notifications/resources/list_changed".to_string(),
86 params: None,
87 }
88 }
89
90 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
91 self.params = Some(NotificationParams::new().with_meta(meta));
92 self
93 }
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98#[serde(rename_all = "camelCase")]
99pub struct ToolListChangedNotification {
100 pub method: String,
102 #[serde(skip_serializing_if = "Option::is_none")]
104 pub params: Option<NotificationParams>,
105}
106
107impl ToolListChangedNotification {
108 pub fn new() -> Self {
109 Self {
110 method: "notifications/tools/list_changed".to_string(),
111 params: None,
112 }
113 }
114
115 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
116 self.params = Some(NotificationParams::new().with_meta(meta));
117 self
118 }
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123#[serde(rename_all = "camelCase")]
124pub struct PromptListChangedNotification {
125 pub method: String,
127 #[serde(skip_serializing_if = "Option::is_none")]
129 pub params: Option<NotificationParams>,
130}
131
132impl PromptListChangedNotification {
133 pub fn new() -> Self {
134 Self {
135 method: "notifications/prompts/list_changed".to_string(),
136 params: None,
137 }
138 }
139
140 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
141 self.params = Some(NotificationParams::new().with_meta(meta));
142 self
143 }
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
148#[serde(rename_all = "camelCase")]
149pub struct RootsListChangedNotification {
150 pub method: String,
152 #[serde(skip_serializing_if = "Option::is_none")]
154 pub params: Option<NotificationParams>,
155}
156
157impl RootsListChangedNotification {
158 pub fn new() -> Self {
159 Self {
160 method: "notifications/roots/list_changed".to_string(),
161 params: None,
162 }
163 }
164
165 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
166 self.params = Some(NotificationParams::new().with_meta(meta));
167 self
168 }
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
173#[serde(rename_all = "camelCase")]
174pub struct ProgressNotification {
175 pub method: String,
177 pub params: ProgressNotificationParams,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
182#[serde(rename_all = "camelCase")]
183pub struct ProgressNotificationParams {
184 pub progress_token: String,
186 pub progress: u64,
188 #[serde(skip_serializing_if = "Option::is_none")]
190 pub total: Option<u64>,
191 #[serde(skip_serializing_if = "Option::is_none")]
193 pub message: Option<String>,
194 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
196 pub meta: Option<HashMap<String, Value>>,
197}
198
199impl ProgressNotification {
200 pub fn new(progress_token: impl Into<String>, progress: u64) -> Self {
201 Self {
202 method: "notifications/progress".to_string(),
203 params: ProgressNotificationParams {
204 progress_token: progress_token.into(),
205 progress,
206 total: None,
207 message: None,
208 meta: None,
209 },
210 }
211 }
212
213 pub fn with_total(mut self, total: u64) -> Self {
214 self.params.total = Some(total);
215 self
216 }
217
218 pub fn with_message(mut self, message: impl Into<String>) -> Self {
219 self.params.message = Some(message.into());
220 self
221 }
222
223 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
224 self.params.meta = Some(meta);
225 self
226 }
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize)]
231#[serde(rename_all = "camelCase")]
232pub struct ResourceUpdatedNotification {
233 pub method: String,
235 pub params: ResourceUpdatedNotificationParams,
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize)]
240#[serde(rename_all = "camelCase")]
241pub struct ResourceUpdatedNotificationParams {
242 pub uri: String,
244 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
246 pub meta: Option<HashMap<String, Value>>,
247}
248
249impl ResourceUpdatedNotification {
250 pub fn new(uri: impl Into<String>) -> Self {
251 Self {
252 method: "notifications/resources/updated".to_string(),
253 params: ResourceUpdatedNotificationParams {
254 uri: uri.into(),
255 meta: None,
256 },
257 }
258 }
259
260 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
261 self.params.meta = Some(meta);
262 self
263 }
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
268#[serde(rename_all = "camelCase")]
269pub struct CancelledNotification {
270 pub method: String,
272 pub params: CancelledNotificationParams,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
277#[serde(rename_all = "camelCase")]
278pub struct CancelledNotificationParams {
279 pub request_id: RequestId,
281 #[serde(skip_serializing_if = "Option::is_none")]
283 pub reason: Option<String>,
284 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
286 pub meta: Option<HashMap<String, Value>>,
287}
288
289impl CancelledNotification {
290 pub fn new(request_id: RequestId) -> Self {
291 Self {
292 method: "notifications/cancelled".to_string(),
293 params: CancelledNotificationParams {
294 request_id,
295 reason: None,
296 meta: None,
297 },
298 }
299 }
300
301 pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
302 self.params.reason = Some(reason.into());
303 self
304 }
305
306 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
307 self.params.meta = Some(meta);
308 self
309 }
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize)]
314#[serde(rename_all = "camelCase")]
315pub struct InitializedNotification {
316 pub method: String,
318 #[serde(skip_serializing_if = "Option::is_none")]
320 pub params: Option<NotificationParams>,
321}
322
323impl InitializedNotification {
324 pub fn new() -> Self {
325 Self {
326 method: "notifications/initialized".to_string(),
327 params: None,
328 }
329 }
330
331 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
332 self.params = Some(NotificationParams::new().with_meta(meta));
333 self
334 }
335}
336
337
338#[derive(Debug, Clone, Serialize, Deserialize)]
340#[serde(rename_all = "camelCase")]
341pub struct LoggingMessageNotification {
342 pub method: String,
344 pub params: LoggingMessageNotificationParams,
346}
347
348#[derive(Debug, Clone, Serialize, Deserialize)]
349#[serde(rename_all = "camelCase")]
350pub struct LoggingMessageNotificationParams {
351 pub level: LoggingLevel,
353 #[serde(skip_serializing_if = "Option::is_none")]
355 pub logger: Option<String>,
356 pub data: Value,
358 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
360 pub meta: Option<HashMap<String, Value>>,
361}
362
363impl LoggingMessageNotification {
364 pub fn new(level: LoggingLevel, data: Value) -> Self {
365 Self {
366 method: "notifications/message".to_string(),
367 params: LoggingMessageNotificationParams {
368 level,
369 logger: None,
370 data,
371 meta: None,
372 },
373 }
374 }
375
376 pub fn with_logger(mut self, logger: impl Into<String>) -> Self {
377 self.params.logger = Some(logger.into());
378 self
379 }
380
381 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
382 self.params.meta = Some(meta);
383 self
384 }
385}
386
387use crate::traits::*;
390
391impl Params for NotificationParams {}
393
394impl HasMetaParam for NotificationParams {
395 fn meta(&self) -> Option<&HashMap<String, Value>> {
396 self.meta.as_ref()
397 }
398}
399
400pub trait HasNotificationMetadata {
406 fn method(&self) -> &str;
408
409 fn notification_type(&self) -> Option<&str> {
411 None
412 }
413
414 fn requires_ack(&self) -> bool {
416 false
417 }
418}
419
420pub trait HasNotificationPayload {
422 fn payload(&self) -> Option<&Value> {
424 None
425 }
426
427 fn serialize_payload(&self) -> Result<String, String> {
429 match self.payload() {
430 Some(data) => serde_json::to_string(data)
431 .map_err(|e| format!("Serialization error: {}", e)),
432 None => Ok("{}".to_string()),
433 }
434 }
435}
436
437pub trait HasNotificationRules {
439 fn priority(&self) -> u32 {
441 0
442 }
443
444 fn can_batch(&self) -> bool {
446 true
447 }
448
449 fn max_retries(&self) -> u32 {
451 3
452 }
453
454 fn should_deliver(&self) -> bool {
456 true
457 }
458}
459
460pub trait NotificationDefinition:
462 HasNotificationMetadata +
463 HasNotificationPayload +
464 HasNotificationRules
465{
466 fn to_notification(&self) -> Notification {
468 let mut notification = Notification::new(self.method());
469 if let Some(payload) = self.payload() {
470 let mut params = NotificationParams::new();
471 if let Ok(obj) = serde_json::from_value::<HashMap<String, Value>>(payload.clone()) {
473 params.other = obj;
474 }
475 notification = notification.with_params(params);
476 }
477 notification
478 }
479
480 fn validate(&self) -> Result<(), String> {
482 if self.method().is_empty() {
483 return Err("Notification method cannot be empty".to_string());
484 }
485 if !self.method().starts_with("notifications/") {
486 return Err("Notification method must start with 'notifications/'".to_string());
487 }
488 Ok(())
489 }
490}
491
492impl<T> NotificationDefinition for T
494where
495 T: HasNotificationMetadata + HasNotificationPayload + HasNotificationRules
496{}
497
498#[cfg(test)]
499mod tests {
500 use super::*;
501 use serde_json::json;
502
503 #[test]
504 fn test_resource_list_changed() {
505 let notification = ResourceListChangedNotification::new();
506 assert_eq!(notification.method, "notifications/resources/list_changed");
507 }
508
509 #[test]
510 fn test_tool_list_changed() {
511 let notification = ToolListChangedNotification::new();
512 assert_eq!(notification.method, "notifications/tools/list_changed");
513 }
514
515 #[test]
516 fn test_prompt_list_changed() {
517 let notification = PromptListChangedNotification::new();
518 assert_eq!(notification.method, "notifications/prompts/list_changed");
519 }
520
521 #[test]
522 fn test_roots_list_changed() {
523 let notification = RootsListChangedNotification::new();
524 assert_eq!(notification.method, "notifications/roots/list_changed");
525 }
526
527 #[test]
528 fn test_progress_notification() {
529 let notification = ProgressNotification::new("token123", 50)
530 .with_total(100)
531 .with_message("Processing...");
532
533 assert_eq!(notification.method, "notifications/progress");
534 assert_eq!(notification.params.progress_token, "token123");
535 assert_eq!(notification.params.progress, 50);
536 assert_eq!(notification.params.total, Some(100));
537 assert_eq!(notification.params.message, Some("Processing...".to_string()));
538 }
539
540 #[test]
541 fn test_resource_updated() {
542 let notification = ResourceUpdatedNotification::new("file:///test.txt");
543 assert_eq!(notification.method, "notifications/resources/updated");
544 assert_eq!(notification.params.uri, "file:///test.txt");
545 }
546
547 #[test]
548 fn test_cancelled_notification() {
549 use turul_mcp_json_rpc_server::types::RequestId;
550 let notification = CancelledNotification::new(RequestId::Number(123))
551 .with_reason("User cancelled");
552
553 assert_eq!(notification.method, "notifications/cancelled");
554 assert_eq!(notification.params.request_id, RequestId::Number(123));
555 assert_eq!(notification.params.reason, Some("User cancelled".to_string()));
556 }
557
558 #[test]
559 fn test_initialized_notification() {
560 let notification = InitializedNotification::new();
561 assert_eq!(notification.method, "notifications/initialized");
562 }
563
564 #[test]
565 fn test_logging_message_notification() {
566 use crate::logging::LoggingLevel;
567 let data = json!({"message": "Test log message", "context": "test"});
568 let notification = LoggingMessageNotification::new(LoggingLevel::Info, data.clone())
569 .with_logger("test-logger");
570
571 assert_eq!(notification.method, "notifications/message");
572 assert_eq!(notification.params.level, LoggingLevel::Info);
573 assert_eq!(notification.params.logger, Some("test-logger".to_string()));
574 assert_eq!(notification.params.data, data);
575 }
576
577 #[test]
578 fn test_serialization() {
579 let notification = InitializedNotification::new();
580 let json = serde_json::to_string(¬ification).unwrap();
581 assert!(json.contains("notifications/initialized"));
582
583 let parsed: InitializedNotification = serde_json::from_str(&json).unwrap();
584 assert_eq!(parsed.method, "notifications/initialized");
585 }
586}